From 9af892ae6f98cd439e6df613986829a36ab35ffb Mon Sep 17 00:00:00 2001 From: stevegee1 <34712687+stevegee1@users.noreply.github.com> Date: Tue, 21 Nov 2023 00:49:08 +0100 Subject: [PATCH 01/11] fix Single-quoted string interpolation in src/web5.ts (#304) --- packages/api/src/web5.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/web5.ts b/packages/api/src/web5.ts index 1431e0d4a..334da01d7 100644 --- a/packages/api/src/web5.ts +++ b/packages/api/src/web5.ts @@ -152,7 +152,7 @@ export class Web5 { // Set the stored identity as the connected DID. connectedDid = identity.did; } else { - throw new Error('connect() failed due to unexpected state: ${storedIdentities} stored identities'); + throw new Error(`connect() failed due to unexpected state: ${storedIdentities} stored identities`); } } From da7786d4ae8b2e5ed4294122962190d68658406b Mon Sep 17 00:00:00 2001 From: nitro-neal <5314059+nitro-neal@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:20:36 -0600 Subject: [PATCH 02/11] adjusting vc.create (#311) * adjusting vc.create * fix spec --- .web5-spec/credentials.ts | 8 +- .../credentials/src/verifiable-credential.ts | 48 +++++++--- .../tests/presentation-exchange.spec.ts | 36 +++---- .../tests/verifiable-credential.spec.ts | 96 +++++++++---------- 4 files changed, 106 insertions(+), 82 deletions(-) diff --git a/.web5-spec/credentials.ts b/.web5-spec/credentials.ts index 39908fb4a..04449ce01 100644 --- a/.web5-spec/credentials.ts +++ b/.web5-spec/credentials.ts @@ -34,7 +34,13 @@ export async function credentialIssue(req: Request, res: Response) { signer : signer }; - const vc: VerifiableCredential = VerifiableCredential.create(body.credential.type[body.credential.type.length - 1], body.credential.issuer, subjectIssuerDid, body.credential.credentialSubject); + const vc: VerifiableCredential = VerifiableCredential.create({ + type: body.credential.type[body.credential.type.length - 1], + issuer: body.credential.issuer, + subject: subjectIssuerDid, + data: body.credential.credentialSubject + }); + const vcJwt: string = await vc.sign(signOptions); const resp: paths["/credentials/issue"]["post"]["responses"]["200"]["content"]["application/json"] = diff --git a/packages/credentials/src/verifiable-credential.ts b/packages/credentials/src/verifiable-credential.ts index 2da6086bf..0fc754f5d 100644 --- a/packages/credentials/src/verifiable-credential.ts +++ b/packages/credentials/src/verifiable-credential.ts @@ -21,6 +21,24 @@ export const DEFAULT_VC_TYPE = 'VerifiableCredential'; */ export type VcDataModel = ICredential; +/** + * @param type Optional. The type of the credential, can be a string or an array of strings. + * @param issuer The issuer URI of the credential, as a string. + * @param subject The subject URI of the credential, as a string. + * @param data The credential data, as a generic type any. + * @param issuanceDate Optional. The issuance date of the credential, as a string. + * Defaults to the current date if not specified. + * @param expirationDate Optional. The expiration date of the credential, as a string. + */ +export type VerifiableCredentialCreateOptions = { + type?: string | string[]; + issuer: string; + subject: string; + data: any; + issuanceDate?: string; + expirationDate?: string; +}; + export type SignOptions = { kid: string; issuerDid: string; @@ -113,24 +131,22 @@ export class VerifiableCredential { /** * Create a [VerifiableCredential] based on the provided parameters. * - * @param type The type of the credential, as a [String]. - * @param issuer The issuer URI of the credential, as a [String]. - * @param subject The subject URI of the credential, as a [String]. - * @param data The credential data, as a generic type [T]. + * @param vcCreateOptions The options to use when creating the Verifiable Credential. * @return A [VerifiableCredential] instance. * * Example: * ``` - * const vc = VerifiableCredential.create("ExampleCredential", "http://example.com/issuers/1", "http://example.com/subjects/1", myData) + * const vc = VerifiableCredential.create({ + * type: 'StreetCredibility', + * issuer: 'did:ex:issuer', + * subject: 'did:ex:subject', + * data: { 'arbitrary': 'data' } + * }) * ``` */ - public static create( - type: string, - issuer: string, - subject: string, - data: T, - expirationDate?: string - ): VerifiableCredential { + public static create(vcCreateOptions: VerifiableCredentialCreateOptions): VerifiableCredential { + const { type, issuer, subject, data, issuanceDate, expirationDate } = vcCreateOptions; + const jsonData = JSON.parse(JSON.stringify(data)); if (typeof jsonData !== 'object') { @@ -147,11 +163,13 @@ export class VerifiableCredential { }; const vcDataModel: VcDataModel = { - '@context' : [DEFAULT_CONTEXT], - type : [DEFAULT_VC_TYPE, type], + '@context' : [DEFAULT_CONTEXT], + type : Array.isArray(type) + ? [DEFAULT_VC_TYPE, ...type] + : (type ? [DEFAULT_VC_TYPE, type] : [DEFAULT_VC_TYPE]), id : `urn:uuid:${uuidv4()}`, issuer : issuer, - issuanceDate : getCurrentXmlSchema112Timestamp(), + issuanceDate : issuanceDate || getCurrentXmlSchema112Timestamp(), // use default if undefined credentialSubject : credentialSubject, ...(expirationDate && { expirationDate }), // optional property }; diff --git a/packages/credentials/tests/presentation-exchange.spec.ts b/packages/credentials/tests/presentation-exchange.spec.ts index 881614b85..5e923a323 100644 --- a/packages/credentials/tests/presentation-exchange.spec.ts +++ b/packages/credentials/tests/presentation-exchange.spec.ts @@ -36,12 +36,12 @@ describe('PresentationExchange', () => { signer : signer }; - const vc = VerifiableCredential.create( - 'StreetCred', - alice.did, - alice.did, - new BitcoinCredential('btcAddress123'), - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : alice.did, + subject : alice.did, + data : new BitcoinCredential('btcAddress123'), + }); btcCredentialJwt = await vc.sign(signOptions); presentationDefinition = createPresentationDefinition(); @@ -57,12 +57,12 @@ describe('PresentationExchange', () => { }); it('should return the only one verifiable credential', async () => { - const vc = VerifiableCredential.create( - 'StreetCred', - signOptions.issuerDid, - signOptions.subjectDid, - new OtherCredential('otherstuff'), - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : signOptions.issuerDid, + subject : signOptions.subjectDid, + data : new OtherCredential('otherstuff'), + }); const otherCredJwt = await vc.sign(signOptions); @@ -127,12 +127,12 @@ describe('PresentationExchange', () => { }); it('should fail to create a presentation with vc that does not match presentation definition', async() => { - const vc = VerifiableCredential.create( - 'StreetCred', - signOptions.issuerDid, - signOptions.subjectDid, - new OtherCredential('otherstuff'), - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : signOptions.issuerDid, + subject : signOptions.subjectDid, + data : new OtherCredential('otherstuff'), + }); const otherCredJwt = await vc.sign(signOptions); await expectThrowsAsync(() => PresentationExchange.createPresentationFromCredentials([otherCredJwt], presentationDefinition), 'Failed to create Verifiable Presentation JWT due to: Required Credentials Not Present'); diff --git a/packages/credentials/tests/verifiable-credential.spec.ts b/packages/credentials/tests/verifiable-credential.spec.ts index 13a9476dc..d4f9bfbe0 100644 --- a/packages/credentials/tests/verifiable-credential.spec.ts +++ b/packages/credentials/tests/verifiable-credential.spec.ts @@ -36,12 +36,12 @@ describe('Verifiable Credential Tests', () => { const issuerDid = signOptions.issuerDid; const subjectDid = signOptions.subjectDid; - const vc = VerifiableCredential.create( - 'StreetCred', - issuerDid, - subjectDid, - new StreetCredibility('high', true), - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : issuerDid, + subject : subjectDid, + data : new StreetCredibility('high', true), + }); expect(vc.issuer).to.equal(issuerDid); expect(vc.subject).to.equal(subjectDid); @@ -57,12 +57,12 @@ describe('Verifiable Credential Tests', () => { const invalidData = 'NotAJSONObject'; expect(() => { - VerifiableCredential.create( - 'InvalidDataTest', - issuerDid, - subjectDid, - invalidData - ); + VerifiableCredential.create({ + type : 'InvalidDataTest', + issuer : issuerDid, + subject : subjectDid, + data : invalidData + }); }).to.throw('Expected data to be parseable into a JSON object'); }); @@ -72,21 +72,21 @@ describe('Verifiable Credential Tests', () => { const validData = new StreetCredibility('high', true); expect(() => { - VerifiableCredential.create( - 'IssuerUndefinedTest', - '', - subjectDid, - validData - ); + VerifiableCredential.create({ + type : 'IssuerUndefinedTest', + issuer : '', + subject : subjectDid, + data : validData + }); }).to.throw('Issuer and subject must be defined'); expect(() => { - VerifiableCredential.create( - 'SubjectUndefinedTest', - issuerDid, - '', - validData - ); + VerifiableCredential.create({ + type : 'SubjectUndefinedTest', + issuer : issuerDid, + subject : '', + data : validData + }); }).to.throw('Issuer and subject must be defined'); }); @@ -95,12 +95,12 @@ describe('Verifiable Credential Tests', () => { const issuerDid = signOptions.issuerDid; const subjectDid = signOptions.subjectDid; - const vc = VerifiableCredential.create( - 'StreetCred', - issuerDid, - subjectDid, - new StreetCredibility('high', true), - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : issuerDid, + subject : subjectDid, + data : new StreetCredibility('high', true), + }); const vcJwt = await vc.sign(signOptions); expect(vcJwt).to.not.be.null; @@ -117,12 +117,12 @@ describe('Verifiable Credential Tests', () => { }); it('verify fails with bad issuer did', async () => { - const vc = VerifiableCredential.create( - 'StreetCred', - 'bad:did: invalidDid', - signOptions.subjectDid, - new StreetCredibility('high', true) - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : 'bad:did: invalidDid', + subject : signOptions.subjectDid, + data : new StreetCredibility('high', true) + }); const badSignOptions = { issuerDid : 'bad:did: invalidDid', @@ -141,12 +141,12 @@ describe('Verifiable Credential Tests', () => { }); it('parseJwt returns an instance of VerifiableCredential on success', async () => { - const vc = VerifiableCredential.create( - 'StreetCred', - signOptions.issuerDid, - signOptions.subjectDid, - new StreetCredibility('high', true) - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : signOptions.issuerDid, + subject : signOptions.subjectDid, + data : new StreetCredibility('high', true), + }); const vcJwt = await vc.sign(signOptions); const parsedVc = VerifiableCredential.parseJwt(vcJwt); @@ -170,12 +170,12 @@ describe('Verifiable Credential Tests', () => { }); it('verify does not throw an exception with vaild vc', async () => { - const vc = VerifiableCredential.create( - 'StreetCred', - signOptions.issuerDid, - signOptions.subjectDid, - new StreetCredibility('high', true) - ); + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : signOptions.issuerDid, + subject : signOptions.subjectDid, + data : new StreetCredibility('high', true), + }); const vcJwt = await vc.sign(signOptions); From c417ba0cb07d6a74e255b364879a4391fd7a61d6 Mon Sep 17 00:00:00 2001 From: LiranCohen Date: Wed, 22 Nov 2023 16:13:04 -0500 Subject: [PATCH 03/11] Bump DWN SDK 0.2.8 (#310) * bump dwn-sdk to v0.2.8, update ProtocolQuery tests --------- Co-authored-by: Frank Hinek --- package-lock.json | 26 +++------- packages/agent/package.json | 2 +- packages/api/package.json | 2 +- packages/api/tests/dwn-api.spec.ts | 72 ++++++++++++++++++++++++---- packages/dev-env/docker-compose.yaml | 2 +- 5 files changed, 71 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7e2a31f6..2e72351ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1011,9 +1011,9 @@ } }, "node_modules/@tbd54566975/dwn-sdk-js": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.2.6.tgz", - "integrity": "sha512-q9HjMhW9KyUD94XVjuO4N+tkeZaOsgtRINIioMKucuZZTCb8Z2lilUleZqc7LiVePCMzlqdRBVeJpMKbnAGj8Q==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.2.8.tgz", + "integrity": "sha512-oiKk+ekAQO94bUkt6yk+xkDY8uCGmNC+rKaYQLhAoTrhYrczeRSuDT04F5/vPBT5K6NfAoRcQsIyBmvgRCUvgA==", "dependencies": { "@ipld/dag-cbor": "9.0.3", "@js-temporal/polyfill": "0.4.4", @@ -1035,7 +1035,7 @@ "ms": "2.1.3", "multiformats": "11.0.2", "randombytes": "2.1.0", - "readable-stream": "4.4.0", + "readable-stream": "4.4.2", "ulidx": "2.1.0", "uuid": "8.3.2", "varint": "6.0.0" @@ -1079,20 +1079,6 @@ } } }, - "node_modules/@tbd54566975/dwn-sdk-js/node_modules/readable-stream": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", - "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@tbd54566975/dwn-sdk-js/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -9808,7 +9794,7 @@ "version": "0.2.4", "license": "Apache-2.0", "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.6", + "@tbd54566975/dwn-sdk-js": "0.2.8", "@web5/common": "0.2.1", "@web5/crypto": "0.2.2", "@web5/dids": "0.2.2", @@ -9998,7 +9984,7 @@ "version": "0.8.3", "license": "Apache-2.0", "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.6", + "@tbd54566975/dwn-sdk-js": "0.2.8", "@web5/agent": "0.2.4", "@web5/crypto": "0.2.2", "@web5/dids": "0.2.2", diff --git a/packages/agent/package.json b/packages/agent/package.json index fd77af0e8..3534fa1f9 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -67,7 +67,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.6", + "@tbd54566975/dwn-sdk-js": "0.2.8", "@web5/common": "0.2.1", "@web5/crypto": "0.2.2", "@web5/dids": "0.2.2", diff --git a/packages/api/package.json b/packages/api/package.json index f5eaa53da..b64b2d2df 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -75,7 +75,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.6", + "@tbd54566975/dwn-sdk-js": "0.2.8", "@web5/agent": "0.2.4", "@web5/crypto": "0.2.2", "@web5/dids": "0.2.2", diff --git a/packages/api/tests/dwn-api.spec.ts b/packages/api/tests/dwn-api.spec.ts index 68de357f7..1320f5165 100644 --- a/packages/api/tests/dwn-api.spec.ts +++ b/packages/api/tests/dwn-api.spec.ts @@ -149,25 +149,77 @@ describe('DwnApi', () => { expect(response.protocols.length).to.equal(0); }); - it('returns a 401 when authorization fails', async () => { - /** Create a new DID to represent an external entity who has a remote - * DWN server defined in their DID document. */ - const { did: bob } = await testAgent.createIdentity({ testDwnUrls }); + it('returns published protocol definitions for requests from external DID', async () => { + // Configure a published protocol on Alice's local DWN. + const publicProtocol = await dwnAlice.protocols.configure({ + message: { + definition: { ...emailProtocolDefinition, protocol: 'http://proto-published', published: true } + } + }); + expect(publicProtocol.status.code).to.equal(202); - // Attempt to query for a protocol using Bob's DWN tenant. - const response = await dwnAlice.protocols.query({ - from : bob.did, + // Configure the published protocol on Alice's remote DWN. + const sendPublic = await publicProtocol.protocol.send(aliceDid.did); + expect(sendPublic.status.code).to.equal(202); + + // Attempt to query for the published protocol on Alice's remote DWN authored by Bob. + const publishedResponse = await dwnBob.protocols.query({ + from : aliceDid.did, + message : { + filter: { + protocol: 'http://proto-published' + } + } + }); + + // Verify that one query result is returned. + expect(publishedResponse.status.code).to.equal(200); + expect(publishedResponse.protocols.length).to.equal(1); + expect(publishedResponse.protocols[0].definition.protocol).to.equal('http://proto-published'); + }); + + it('does not return unpublished protocol definitions for requests from external DID', async () => { + // Configure an unpublished protocol on Alice's DWN. + const notPublicProtocol = await dwnAlice.protocols.configure({ + message: { + definition: { ...emailProtocolDefinition, protocol: 'http://proto-not-published', published: false } + } + }); + expect(notPublicProtocol.status.code).to.equal(202); + + // Configure the unpublished protocol on Alice's remote DWN. + const sendNotPublic = await notPublicProtocol.protocol.send(aliceDid.did); + expect(sendNotPublic.status.code).to.equal(202); + + // Attempt to query for the unpublished protocol on Alice's remote DWN authored by Bob. + const nonPublishedResponse = await dwnBob.protocols.query({ + from : aliceDid.did, message : { filter: { + protocol: 'http://proto-not-published' + } + } + }); + + // Verify that no query results are returned. + expect(nonPublishedResponse.status.code).to.equal(200); + expect(nonPublishedResponse.protocols.length).to.equal(0); + }); + + it('returns a 401 with an invalid permissions grant', async () => { + // Attempt to query for a record using Bob's DWN tenant with an invalid grant. + const response = await dwnAlice.protocols.query({ + from : bobDid.did, + message : { + permissionsGrantId : 'bafyreiduimprbncdo2oruvjrvmfmwuyz4xx3d5biegqd2qntlryvuuosem', + filter : { protocol: 'https://doesnotexist.com/protocol' } } }); - /** Confirm that authorization failed because the test identity does not have - * permission to delete a record from Bob's DWN. */ expect(response.status.code).to.equal(401); - expect(response.status.detail).to.include('ProtocolsQuery failed authorization'); + expect(response.status.detail).to.include('GrantAuthorizationGrantMissing'); expect(response.protocols).to.exist; expect(response.protocols.length).to.equal(0); }); diff --git a/packages/dev-env/docker-compose.yaml b/packages/dev-env/docker-compose.yaml index 5b983c7cb..fe4f895d6 100644 --- a/packages/dev-env/docker-compose.yaml +++ b/packages/dev-env/docker-compose.yaml @@ -3,6 +3,6 @@ version: "3.98" services: dwn-server: container_name: dwn-server - image: ghcr.io/tbd54566975/dwn-server:dwn-sdk-0.2.6 + image: ghcr.io/tbd54566975/dwn-server:dwn-sdk-0.2.8 ports: - "3000:3000" From 590a5fcedf257a144ce60e7338d19639b9f25c68 Mon Sep 17 00:00:00 2001 From: Frank Hinek Date: Tue, 28 Nov 2023 17:24:17 -0500 Subject: [PATCH 04/11] Refactor `@web5/crypto` to replace Web Crypto `CryptoKey` with JWK (#318) * Refactor Ed25519 to generateKey instead of generateKeyPair * Refactor Secp256k1 to generateKey instead of generateKeyPair and simplify sign/verify * Refactor X25519 to generateKey instead of generateKeyPair * Refactor PBKDF2 to use JWKs * Remove CryptoKeyToJwkMixin * Improve test coverage for PBKDF2 * Refactor Ed25519, Secp256k1, and X25519 to use JWKs * Refactor EcdhAlgorithm to use JWK * Refactor EcdsaAlgorithm to use JWK * Refactor EdDsaAlgorithm to use JWK * Refactor AesCtrAlgorithm to use JWK * Refactor AesCtrAlgorithm to JWK * Refactor AesGcm to use JWK * Bump @noble ciphers, curves, and hashes dependencies --------- Signed-off-by: Frank Hinek --- .web5-spec/credentials.ts | 7 +- package-lock.json | 531 ++++-- packages/crypto/package.json | 8 +- .../crypto/src/algorithms-api/aes/base.ts | 59 +- packages/crypto/src/algorithms-api/aes/ctr.ts | 73 +- .../src/algorithms-api/crypto-algorithm.ts | 77 +- .../crypto/src/algorithms-api/crypto-key.ts | 56 - packages/crypto/src/algorithms-api/ec/base.ts | 113 +- packages/crypto/src/algorithms-api/ec/ecdh.ts | 67 +- .../crypto/src/algorithms-api/ec/ecdsa.ts | 53 +- .../crypto/src/algorithms-api/ec/eddsa.ts | 48 +- packages/crypto/src/algorithms-api/index.ts | 1 - .../crypto/src/algorithms-api/pbkdf/pbkdf2.ts | 55 +- .../crypto/src/crypto-algorithms/aes-ctr.ts | 60 +- packages/crypto/src/crypto-algorithms/ecdh.ts | 94 +- .../crypto/src/crypto-algorithms/ecdsa.ts | 99 +- .../crypto/src/crypto-algorithms/eddsa.ts | 96 +- .../crypto/src/crypto-algorithms/pbkdf2.ts | 41 +- .../crypto/src/crypto-primitives/aes-ctr.ts | 252 ++- .../crypto/src/crypto-primitives/aes-gcm.ts | 267 ++- .../crypto/src/crypto-primitives/ed25519.ts | 511 ++++-- .../crypto/src/crypto-primitives/pbkdf2.ts | 62 + .../crypto/src/crypto-primitives/secp256k1.ts | 789 ++++++--- .../crypto/src/crypto-primitives/x25519.ts | 375 ++++- .../crypto-primitives/xchacha20-poly1305.ts | 248 ++- .../crypto/src/crypto-primitives/xchacha20.ts | 232 ++- packages/crypto/src/index.ts | 1 - packages/crypto/src/jose.ts | 511 ++---- packages/crypto/src/types/crypto-key.ts | 4 - packages/crypto/src/types/web5-crypto.ts | 74 +- packages/crypto/src/utils.ts | 32 +- packages/crypto/tests/algorithms-api.spec.ts | 1299 +++++++++------ .../crypto/tests/crypto-algorithms.spec.ts | 1460 ++++++----------- .../crypto/tests/crypto-primitives.spec.ts | 1272 -------------- .../tests/crypto-primitives/aes-ctr.spec.ts | 153 ++ .../tests/crypto-primitives/aes-gcm.spec.ts | 156 ++ .../crypto-primitives/concat-kdf.spec.ts | 129 ++ .../tests/crypto-primitives/ed25519.spec.ts | 381 +++++ .../tests/crypto-primitives/pbkdf2.spec.ts | 133 ++ .../tests/crypto-primitives/secp256k1.spec.ts | 442 +++++ .../tests/crypto-primitives/x25519.spec.ts | 241 +++ .../xchacha20-poly1305.spec.ts | 190 +++ .../tests/crypto-primitives/xchacha20.spec.ts | 153 ++ .../tests/fixtures/test-vectors/ed25519.ts | 20 - .../ed25519/bytes-to-private-key.json | 44 + .../ed25519/bytes-to-public-key.json | 41 + .../ed25519/compute-public-key.json | 73 + .../convert-private-key-to-x25519.json | 41 + .../ed25519/convert-public-key-to-x25519.json | 37 + .../ed25519/private-key-to-bytes.json | 44 + .../ed25519/public-key-to-bytes.json | 41 + .../fixtures/test-vectors/ed25519/sign.json | 61 + .../fixtures/test-vectors/ed25519/verify.json | 61 + .../tests/fixtures/test-vectors/jose.ts | 632 +------ .../tests/fixtures/test-vectors/secp256k1.ts | 46 - .../secp256k1/bytes-to-private-key.json | 61 + .../secp256k1/bytes-to-public-key.json | 57 + .../secp256k1/get-curve-points.json | 45 + .../secp256k1/private-key-to-bytes.json | 61 + .../secp256k1/public-key-to-bytes.json | 57 + .../secp256k1/validate-private-key.json | 33 + .../secp256k1/validate-public-key.json | 33 + .../x25519/bytes-to-private-key.json | 44 + .../x25519/bytes-to-public-key.json | 41 + .../x25519/private-key-to-bytes.json | 44 + .../x25519/public-key-to-bytes.json | 41 + packages/crypto/tests/jose.spec.ts | 648 ++++---- packages/crypto/tests/tsconfig.json | 3 +- packages/crypto/tests/utils.spec.ts | 38 - packages/identity-agent/package.json | 4 +- 70 files changed, 8001 insertions(+), 5255 deletions(-) delete mode 100644 packages/crypto/src/algorithms-api/crypto-key.ts delete mode 100644 packages/crypto/src/types/crypto-key.ts delete mode 100644 packages/crypto/tests/crypto-primitives.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/aes-ctr.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/aes-gcm.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/concat-kdf.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/ed25519.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/pbkdf2.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/secp256k1.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/x25519.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/xchacha20-poly1305.spec.ts create mode 100644 packages/crypto/tests/crypto-primitives/xchacha20.spec.ts delete mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519.ts create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-private-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-public-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/compute-public-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/convert-private-key-to-x25519.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/convert-public-key-to-x25519.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/private-key-to-bytes.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/public-key-to-bytes.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/sign.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519/verify.json delete mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1.ts create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-private-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-public-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1/get-curve-points.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1/private-key-to-bytes.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1/public-key-to-bytes.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-private-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-public-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-private-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-public-key.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/x25519/private-key-to-bytes.json create mode 100644 packages/crypto/tests/fixtures/test-vectors/x25519/public-key-to-bytes.json diff --git a/.web5-spec/credentials.ts b/.web5-spec/credentials.ts index 04449ce01..c2a2787d3 100644 --- a/.web5-spec/credentials.ts +++ b/.web5-spec/credentials.ts @@ -1,7 +1,7 @@ import { Request, Response } from 'express'; import { VerifiableCredential, SignOptions } from '@web5/credentials'; import { DidKeyMethod, PortableDid } from '@web5/dids'; -import { Ed25519, Jose } from '@web5/crypto'; +import { Ed25519, PrivateKeyJwk } from '@web5/crypto'; import { paths } from './openapi.js'; type Signer = (data: Uint8Array) => Promise; @@ -24,9 +24,8 @@ export async function credentialIssue(req: Request, res: Response) { // build signing options const [signingKeyPair] = ownDid.keySet.verificationMethodKeys!; - const privateKey = (await Jose.jwkToKey({ key: signingKeyPair.privateKeyJwk!})).keyMaterial; const subjectIssuerDid = body.credential.credentialSubject["id"] as string; - const signer = EdDsaSigner(privateKey); + const signer = EdDsaSigner(signingKeyPair.privateKeyJwk as PrivateKeyJwk); const signOptions: SignOptions = { issuerDid : ownDid.did, subjectDid : subjectIssuerDid, @@ -51,7 +50,7 @@ export async function credentialIssue(req: Request, res: Response) { res.json(resp); } -function EdDsaSigner(privateKey: Uint8Array): Signer { +function EdDsaSigner(privateKey: PrivateKeyJwk): Signer { return async (data: Uint8Array): Promise => { const signature = await Ed25519.sign({ data, key: privateKey}); return signature; diff --git a/package-lock.json b/package-lock.json index 2e72351ce..c663b537f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -757,19 +757,19 @@ } }, "node_modules/@noble/ciphers": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", - "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.0.tgz", + "integrity": "sha512-xaUaUUDWbHIFSxaQ/pIe+33VG2mfJp6N/KxKLmZr5biWdNznCAmfu24QRhX10BbVAuqOahAoyp0S4M9md6GPDw==", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/curves": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", - "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dependencies": { - "@noble/hashes": "1.3.1" + "@noble/hashes": "1.3.2" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -787,9 +787,9 @@ ] }, "node_modules/@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "engines": { "node": ">= 16" }, @@ -998,9 +998,9 @@ } }, "node_modules/@sphereon/pex-models": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sphereon/pex-models/-/pex-models-2.1.1.tgz", - "integrity": "sha512-0UX/CMwgiJSxzuBn6SLOTSKkm+uPq3dkNjl8w4EtppXp6zBB4lQMd1mJX7OifX5Bp5vPUfoz7bj2B+yyDtbZww==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@sphereon/pex-models/-/pex-models-2.1.2.tgz", + "integrity": "sha512-Ec1qZl8tuPd+s6E+ZM7v+HkGkSOjGDMLNN1kqaxAfWpITBYtTLb+d5YvwjvBZ1P2upZ7zwNER97FfW5n/30y2w==" }, "node_modules/@sphereon/ssi-types": { "version": "0.13.0", @@ -1109,9 +1109,9 @@ "dev": true }, "node_modules/@types/cors": { - "version": "2.8.16", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.16.tgz", - "integrity": "sha512-Trx5or1Nyg1Fq138PCuWqoApzvoSLWzZ25ORBiHMbbUT42g578lH1GT4TwYDbiUOLFuDsCkfLneT2105fsFWGg==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dev": true, "dependencies": { "@types/node": "*" @@ -1187,9 +1187,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", - "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", + "version": "20.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz", + "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1206,9 +1206,9 @@ } }, "node_modules/@types/semver": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", - "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@types/sinon": { @@ -1268,16 +1268,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz", - "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", + "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/typescript-estree": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/scope-manager": "6.13.1", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", "debug": "^4.3.4" }, "engines": { @@ -1297,14 +1297,14 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz", - "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", + "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0" + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1315,13 +1315,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz", - "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", + "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/types": "6.13.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1430,9 +1430,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz", - "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", + "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", "dev": true, "peer": true, "engines": { @@ -1444,14 +1444,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz", - "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", + "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/visitor-keys": "6.13.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1472,13 +1472,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz", - "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", + "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/types": "6.13.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1806,6 +1806,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", "dev": true }, "node_modules/abort-controller": { @@ -2619,9 +2620,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001563", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", - "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", + "version": "1.0.30001565", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", + "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", "dev": true, "funding": [ { @@ -3289,14 +3290,6 @@ "uint8arrays": "3.1.1" } }, - "node_modules/did-jwt/node_modules/@noble/ciphers": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.0.tgz", - "integrity": "sha512-xaUaUUDWbHIFSxaQ/pIe+33VG2mfJp6N/KxKLmZr5biWdNznCAmfu24QRhX10BbVAuqOahAoyp0S4M9md6GPDw==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/did-jwt/node_modules/multiformats": { "version": "9.9.0", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", @@ -3419,28 +3412,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/eciesjs/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/eciesjs/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3448,9 +3419,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.588", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.588.tgz", - "integrity": "sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==", + "version": "1.4.595", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.595.tgz", + "integrity": "sha512-+ozvXuamBhDOKvMNUQvecxfbyICmIAwS4GpLmR0bsiSBlGnLaOcs2Cj7J8XSbW+YEaN3Xl3ffgpm+srTUWFwFQ==", "dev": true, "peer": true }, @@ -4584,9 +4555,9 @@ } }, "node_modules/hamt-sharding/node_modules/uint8arrays": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", - "integrity": "sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.9.tgz", + "integrity": "sha512-iHU8XJJnfeijILZWzV7RgILdPHqe0mjJvyzY4mO8aUUtHsDbPa2Gc8/02Kc4zeokp2W6Qq8z9Ap1xkQ1HfbKwg==", "dependencies": { "multiformats": "^12.0.1" } @@ -4970,9 +4941,9 @@ } }, "node_modules/ipfs-unixfs-exporter/node_modules/uint8arrays": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", - "integrity": "sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.9.tgz", + "integrity": "sha512-iHU8XJJnfeijILZWzV7RgILdPHqe0mjJvyzY4mO8aUUtHsDbPa2Gc8/02Kc4zeokp2W6Qq8z9Ap1xkQ1HfbKwg==", "dependencies": { "multiformats": "^12.0.1" } @@ -5014,9 +4985,9 @@ } }, "node_modules/ipfs-unixfs-importer/node_modules/uint8arrays": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", - "integrity": "sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.9.tgz", + "integrity": "sha512-iHU8XJJnfeijILZWzV7RgILdPHqe0mjJvyzY4mO8aUUtHsDbPa2Gc8/02Kc4zeokp2W6Qq8z9Ap1xkQ1HfbKwg==", "dependencies": { "multiformats": "^12.0.1" } @@ -5640,9 +5611,9 @@ "dev": true }, "node_modules/json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -6302,9 +6273,9 @@ } }, "node_modules/lru-cache": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.3.tgz", - "integrity": "sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -6843,9 +6814,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.0.tgz", - "integrity": "sha512-PbZERfeFdrHQOOXiAKOY0VPbykZy90ndPKk0d+CFDegTKmWp1VgOTz2xACVbr1BjCWxrQp68CXtvNsveFhqDJg==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", + "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -7488,9 +7459,9 @@ } }, "node_modules/protons-runtime/node_modules/uint8arrays": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", - "integrity": "sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.9.tgz", + "integrity": "sha512-iHU8XJJnfeijILZWzV7RgILdPHqe0mjJvyzY4mO8aUUtHsDbPa2Gc8/02Kc4zeokp2W6Qq8z9Ap1xkQ1HfbKwg==", "dependencies": { "multiformats": "^12.0.1" } @@ -9112,15 +9083,11 @@ } }, "node_modules/uint8arraylist": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.3.tgz", - "integrity": "sha512-oEVZr4/GrH87K0kjNce6z8pSCzLEPqHNLNR5sj8cJOySrTP8Vb/pMIbZKLJGhQKxm1TiZ31atNrpn820Pyqpow==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.7.tgz", + "integrity": "sha512-ohRElqR6C5dd60vRFLq40MCiSnUe1AzkpHvbCEMCGGP6zMoFYECsjdhL6bR1kTK37ONNRDuHQ3RIpScRYcYYIg==", "dependencies": { "uint8arrays": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" } }, "node_modules/uint8arraylist/node_modules/multiformats": { @@ -9133,9 +9100,9 @@ } }, "node_modules/uint8arraylist/node_modules/uint8arrays": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", - "integrity": "sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.9.tgz", + "integrity": "sha512-iHU8XJJnfeijILZWzV7RgILdPHqe0mjJvyzY4mO8aUUtHsDbPa2Gc8/02Kc4zeokp2W6Qq8z9Ap1xkQ1HfbKwg==", "dependencies": { "multiformats": "^12.0.1" } @@ -9314,9 +9281,9 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -9835,6 +9802,36 @@ "node": ">=18.0.0" } }, + "packages/agent/node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/agent/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/agent/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/agent/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -9903,6 +9900,20 @@ } } }, + "packages/agent/node_modules/@web5/crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", + "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/agent/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -10031,6 +10042,36 @@ "node": ">=18.0.0" } }, + "packages/api/node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/api/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/api/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/api/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -10099,6 +10140,20 @@ } } }, + "packages/api/node_modules/@web5/crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", + "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/api/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -10399,6 +10454,39 @@ "node": ">=18.0.0" } }, + "packages/credentials/node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "dev": true, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/credentials/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/credentials/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/credentials/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -10467,6 +10555,21 @@ } } }, + "packages/credentials/node_modules/@web5/crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", + "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", + "dev": true, + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/credentials/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -10545,12 +10648,12 @@ }, "packages/crypto": { "name": "@web5/crypto", - "version": "0.2.2", + "version": "0.2.3", "license": "Apache-2.0", "dependencies": { - "@noble/ciphers": "0.1.4", - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", + "@noble/ciphers": "0.4.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", "@web5/common": "0.2.1" }, "devDependencies": { @@ -10784,6 +10887,36 @@ "node": ">=18.0.0" } }, + "packages/dids/node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/dids/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/dids/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/dids/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -10852,6 +10985,20 @@ } } }, + "packages/dids/node_modules/@web5/crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", + "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/dids/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -10934,7 +11081,9 @@ "license": "Apache-2.0", "dependencies": { "@web5/agent": "0.2.4", - "@web5/api": "0.8.3" + "@web5/common": "0.2.1", + "@web5/crypto": "0.2.2", + "@web5/dids": "0.2.2" }, "devDependencies": { "@playwright/test": "1.36.2", @@ -10968,6 +11117,36 @@ "node": ">=18.0.0" } }, + "packages/identity-agent/node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/identity-agent/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/identity-agent/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/identity-agent/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -11036,6 +11215,20 @@ } } }, + "packages/identity-agent/node_modules/@web5/crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", + "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/identity-agent/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -11154,6 +11347,36 @@ "node": ">=18.0.0" } }, + "packages/proxy-agent/node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/proxy-agent/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/proxy-agent/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/proxy-agent/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -11222,6 +11445,20 @@ } } }, + "packages/proxy-agent/node_modules/@web5/crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", + "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/proxy-agent/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -11340,6 +11577,36 @@ "node": ">=18.0.0" } }, + "packages/user-agent/node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/user-agent/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/user-agent/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/user-agent/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -11408,6 +11675,20 @@ } } }, + "packages/user-agent/node_modules/@web5/crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", + "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.2.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/user-agent/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 610db602b..70f35d6a5 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,6 +1,6 @@ { "name": "@web5/crypto", - "version": "0.2.2", + "version": "0.2.3", "description": "TBD crypto library", "type": "module", "main": "./dist/cjs/index.js", @@ -73,9 +73,9 @@ "node": ">=18.0.0" }, "dependencies": { - "@noble/ciphers": "0.1.4", - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", + "@noble/ciphers": "0.4.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", "@web5/common": "0.2.1" }, "devDependencies": { diff --git a/packages/crypto/src/algorithms-api/aes/base.ts b/packages/crypto/src/algorithms-api/aes/base.ts index ad29f93ea..2158ab433 100644 --- a/packages/crypto/src/algorithms-api/aes/base.ts +++ b/packages/crypto/src/algorithms-api/aes/base.ts @@ -1,49 +1,64 @@ -import { universalTypeOf } from '@web5/common'; - import type { Web5Crypto } from '../../types/web5-crypto.js'; +import type { JwkOperation, PrivateKeyJwk } from '../../jose.js'; +import { Jose } from '../../jose.js'; +import { InvalidAccessError } from '../errors.js'; import { checkRequiredProperty } from '../../utils.js'; import { CryptoAlgorithm } from '../crypto-algorithm.js'; -import { InvalidAccessError, OperationError } from '../errors.js'; export abstract class BaseAesAlgorithm extends CryptoAlgorithm { - public checkGenerateKey(options: { + public checkGenerateKeyOptions(options: { algorithm: Web5Crypto.AesGenerateKeyOptions, - keyUsages: Web5Crypto.KeyUsage[] + keyOperations: JwkOperation[] }): void { - const { algorithm, keyUsages } = options; + const { algorithm, keyOperations } = options; + // Algorithm specified in the operation must match the algorithm implementation processing the operation. this.checkAlgorithmName({ algorithmName: algorithm.name }); - // The algorithm object must contain a length property. - checkRequiredProperty({ property: 'length', inObject: algorithm }); - // The length specified must be a number. - if (universalTypeOf(algorithm.length) !== 'Number') { - throw new TypeError(`Algorithm 'length' is not of type: Number.`); + + // If specified, key operations must be permitted by the algorithm implementation processing the operation. + if (keyOperations) { + this.checkKeyOperations({ keyOperations, allowedKeyOperations: this.keyOperations }); } - // The length specified must be one of the allowed bit lengths for AES. - if (![128, 192, 256].includes(algorithm.length)) { - throw new OperationError(`Algorithm 'length' must be 128, 192, or 256.`); + } + + public checkSecretKey(options: { + key: PrivateKeyJwk + }): void { + const { key } = options; + + // The options object must contain a key property. + checkRequiredProperty({ property: 'key', inObject: options }); + + // The key object must be a JSON Web key (JWK). + this.checkJwk({ key }); + + // The key object must be an octet sequence (oct) private key in JWK format. + if (!Jose.isOctPrivateKeyJwk(key)) { + throw new InvalidAccessError('Requested operation is only valid for oct private keys.'); + } + + // If specified, the key's algorithm must match the algorithm implementation processing the operation. + if (key.alg) { + this.checkKeyAlgorithm({ keyAlgorithmName: key.alg }); } - // The key usages specified must be permitted by the algorithm implementation processing the operation. - this.checkKeyUsages({ keyUsages, allowedKeyUsages: this.keyUsages }); } public abstract generateKey(options: { algorithm: Web5Crypto.AesGenerateKeyOptions, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): Promise; + keyOperations: JwkOperation[] + }): Promise; public override async deriveBits(): Promise { - throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for AES algorithm.`); } public override async sign(): Promise { - throw new InvalidAccessError(`Requested operation 'sign' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'sign' is not valid for AES algorithm.`); } public override async verify(): Promise { - throw new InvalidAccessError(`Requested operation 'verify' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'verify' is not valid for AES algorithm.`); } } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/aes/ctr.ts b/packages/crypto/src/algorithms-api/aes/ctr.ts index 18b2c958b..bc619de40 100644 --- a/packages/crypto/src/algorithms-api/aes/ctr.ts +++ b/packages/crypto/src/algorithms-api/aes/ctr.ts @@ -1,6 +1,7 @@ import { universalTypeOf } from '@web5/common'; import type { Web5Crypto } from '../../types/web5-crypto.js'; +import type { JwkOperation, PrivateKeyJwk } from '../../jose.js'; import { BaseAesAlgorithm } from './base.js'; import { OperationError } from '../errors.js'; @@ -8,44 +9,88 @@ import { checkRequiredProperty } from '../../utils.js'; export abstract class BaseAesCtrAlgorithm extends BaseAesAlgorithm { - public readonly name = 'AES-CTR'; - - public readonly keyUsages: Web5Crypto.KeyUsage[] = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; + public readonly keyOperations: JwkOperation[] = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; public checkAlgorithmOptions(options: { - algorithm: Web5Crypto.AesCtrOptions, - key: Web5Crypto.CryptoKey + algorithm: Web5Crypto.AesCtrOptions }): void { - const { algorithm, key } = options; + const { algorithm } = options; + // Algorithm specified in the operation must match the algorithm implementation processing the operation. this.checkAlgorithmName({ algorithmName: algorithm.name }); + // The algorithm object must contain a counter property. checkRequiredProperty({ property: 'counter', inObject: algorithm }); + // The counter must a Uint8Array. if (!(universalTypeOf(algorithm.counter) === 'Uint8Array')) { throw new TypeError(`Algorithm 'counter' is not of type: Uint8Array.`); } + // The initial value of the counter block must be 16 bytes long (the AES block size). if (algorithm.counter.byteLength !== 16) { throw new OperationError(`Algorithm 'counter' must have length: 16 bytes.`); } + // The algorithm object must contain a length property. checkRequiredProperty({ property: 'length', inObject: algorithm }); + // The length specified must be a number. if (universalTypeOf(algorithm.length) !== 'Number') { throw new TypeError(`Algorithm 'length' is not of type: Number.`); } + // The length specified must be between 1 and 128. if ((algorithm.length < 1 || algorithm.length > 128)) { throw new OperationError(`Algorithm 'length' should be in the range: 1 to 128.`); } - // The options object must contain a key property. - checkRequiredProperty({ property: 'key', inObject: options }); - // The key object must be a CryptoKey. - this.checkCryptoKey({ key }); - // The key algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: key.algorithm.name }); - // The CryptoKey object must be a secret key. - this.checkKeyType({ keyType: key.type, allowedKeyType: 'secret' }); + } + + public checkDecryptOptions(options: { + algorithm: Web5Crypto.AesCtrOptions, + key: PrivateKeyJwk, + data: Uint8Array + }): void { + const { algorithm, key, data } = options; + + // Validate the algorithm input parameters. + this.checkAlgorithmOptions({ algorithm }); + + // Validate the secret key. + this.checkSecretKey({ key }); + + // If specified, the secret key must be allowed to be used for 'decrypt' operations. + if (key.key_ops) { + this.checkKeyOperations({ keyOperations: ['decrypt'], allowedKeyOperations: key.key_ops }); + } + + // The data must be a Uint8Array. + if (universalTypeOf(data) !== 'Uint8Array') { + throw new TypeError('The data must be of type Uint8Array.'); + } + } + + public checkEncryptOptions(options: { + algorithm: Web5Crypto.AesCtrOptions, + key: PrivateKeyJwk, + data: Uint8Array + }): void { + const { algorithm, key, data } = options; + + // Validate the algorithm and key input parameters. + this.checkAlgorithmOptions({ algorithm }); + + // Validate the secret key. + this.checkSecretKey({ key }); + + // If specified, the secret key must be allowed to be used for 'encrypt' operations. + if (key.key_ops) { + this.checkKeyOperations({ keyOperations: ['encrypt'], allowedKeyOperations: key.key_ops }); + } + + // The data must be a Uint8Array. + if (universalTypeOf(data) !== 'Uint8Array') { + throw new TypeError('The data must be of type Uint8Array.'); + } } } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/crypto-algorithm.ts b/packages/crypto/src/algorithms-api/crypto-algorithm.ts index 7f962aaf9..59faa5a12 100644 --- a/packages/crypto/src/algorithms-api/crypto-algorithm.ts +++ b/packages/crypto/src/algorithms-api/crypto-algorithm.ts @@ -1,18 +1,19 @@ import type { Web5Crypto } from '../types/web5-crypto.js'; +import type { JsonWebKey, JwkOperation, JwkType, PrivateKeyJwk, PublicKeyJwk } from '../jose.js'; import { InvalidAccessError, NotSupportedError } from './errors.js'; export abstract class CryptoAlgorithm { /** - * Name of the algorithm + * Name(s) of the algorithm supported by the implementation. */ - public abstract readonly name: string; + public abstract readonly names: ReadonlyArray; /** * Indicates which cryptographic operations are permissible to be used with this algorithm. */ - public abstract readonly keyUsages: Web5Crypto.KeyUsage[] | Web5Crypto.KeyPairUsage; + public abstract readonly keyOperations: JwkOperation[]; public checkAlgorithmName(options: { algorithmName: string @@ -21,17 +22,17 @@ export abstract class CryptoAlgorithm { if (algorithmName === undefined) { throw new TypeError(`Required parameter missing: 'algorithmName'`); } - if (algorithmName !== this.name) { + if (!this.names.includes(algorithmName)) { throw new NotSupportedError(`Algorithm not supported: '${algorithmName}'`); } } - public checkCryptoKey(options: { - key: Web5Crypto.CryptoKey + public checkJwk(options: { + key: JsonWebKey }): void { const { key } = options; - if (!('algorithm' in key && 'extractable' in key && 'type' in key && 'usages' in key)) { - throw new TypeError('Object is not a CryptoKey'); + if (typeof key !== 'object' || !('kty' in key)) { + throw new TypeError('Object is not a JSON Web Key (JWK)'); } } @@ -42,35 +43,40 @@ export abstract class CryptoAlgorithm { if (keyAlgorithmName === undefined) { throw new TypeError(`Required parameter missing: 'keyAlgorithmName'`); } - if (keyAlgorithmName && keyAlgorithmName !== this.name) { - throw new InvalidAccessError(`Algorithm '${this.name}' does not match the provided '${keyAlgorithmName}' key.`); + if (keyAlgorithmName && !this.names.includes(keyAlgorithmName)) { + throw new InvalidAccessError(`Algorithm '${this.names.join(', ')}' does not match the provided '${keyAlgorithmName}' key.`); } } public checkKeyType(options: { - keyType: Web5Crypto.KeyType, - allowedKeyType: Web5Crypto.KeyType + keyType: JwkType, + allowedKeyTypes: JwkType[] }): void { - const { keyType, allowedKeyType } = options; - if (keyType === undefined || allowedKeyType === undefined) { - throw new TypeError(`One or more required parameters missing: 'keyType, allowedKeyType'`); + const { keyType, allowedKeyTypes } = options; + if (keyType === undefined || allowedKeyTypes === undefined) { + throw new TypeError(`One or more required parameters missing: 'keyType, allowedKeyTypes'`); } - if (keyType && keyType !== allowedKeyType) { - throw new InvalidAccessError(`Requested operation is not valid for the provided '${keyType}' key.`); + if (!Array.isArray(allowedKeyTypes)) { + throw new TypeError(`The provided 'allowedKeyTypes' is not of type Array.`); + } + if (keyType && !allowedKeyTypes.includes(keyType)) { + throw new InvalidAccessError(`Key type of the provided key must be '${allowedKeyTypes.join(', ')}' but '${keyType}' was specified.`); } } - public checkKeyUsages(options: { - keyUsages: Web5Crypto.KeyUsage[], - allowedKeyUsages: Web5Crypto.KeyUsage[] | Web5Crypto.KeyPairUsage + public checkKeyOperations(options: { + keyOperations: JwkOperation[], + allowedKeyOperations: JwkOperation[] }): void { - const { keyUsages, allowedKeyUsages } = options; - if (!(keyUsages && keyUsages.length > 0)) { - throw new TypeError(`Required parameter missing or empty: 'keyUsages'`); + const { keyOperations, allowedKeyOperations } = options; + if (!(keyOperations && keyOperations.length > 0)) { + throw new TypeError(`Required parameter missing or empty: 'keyOperations'`); + } + if (!Array.isArray(allowedKeyOperations)) { + throw new TypeError(`The provided 'allowedKeyOperations' is not of type Array.`); } - const allowedUsages = (Array.isArray(allowedKeyUsages)) ? allowedKeyUsages : [...allowedKeyUsages.privateKey, ...allowedKeyUsages.publicKey]; - if (!keyUsages.every(usage => allowedUsages.includes(usage))) { - throw new InvalidAccessError(`Requested operation(s) '${keyUsages.join(', ')}' is not valid for the provided key.`); + if (!keyOperations.every(operation => allowedKeyOperations.includes(operation))) { + throw new InvalidAccessError(`Requested operation(s) '${keyOperations.join(', ')}' is not valid for the provided key.`); } } @@ -90,37 +96,36 @@ export abstract class CryptoAlgorithm { public abstract decrypt(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions, - key: Web5Crypto.CryptoKey, + key: PrivateKeyJwk, data: Uint8Array }): Promise; public abstract deriveBits(options: { - algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdhDeriveKeyOptions, - baseKey: Web5Crypto.CryptoKey, - length: number | null + algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdhDeriveKeyOptions | Web5Crypto.Pbkdf2Options, + baseKey: JsonWebKey, + length?: number }): Promise; public abstract encrypt(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions, - key: Web5Crypto.CryptoKey, + key: PrivateKeyJwk, data: Uint8Array }): Promise; public abstract generateKey(options: { algorithm: Partial, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[], - }): Promise; + keyOperations?: JwkOperation[], + }): Promise; public abstract sign(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions, - key: Web5Crypto.CryptoKey, + key: PrivateKeyJwk, data: Uint8Array }): Promise; public abstract verify(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions, - key: Web5Crypto.CryptoKey, + key: PublicKeyJwk, signature: Uint8Array, data: Uint8Array }): Promise; diff --git a/packages/crypto/src/algorithms-api/crypto-key.ts b/packages/crypto/src/algorithms-api/crypto-key.ts deleted file mode 100644 index fdc05a2c3..000000000 --- a/packages/crypto/src/algorithms-api/crypto-key.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { Web5Crypto } from '../types/web5-crypto.js'; - -export class CryptoKey implements Web5Crypto.CryptoKey { - public algorithm: Web5Crypto.KeyAlgorithm | Web5Crypto.GenerateKeyOptions; - public extractable: boolean; - public material: Uint8Array; - public type: Web5Crypto.KeyType; - public usages: Web5Crypto.KeyUsage[]; - - constructor (algorithm: Web5Crypto.Algorithm | Web5Crypto.GenerateKeyOptions, extractable: boolean, material: Uint8Array, type: Web5Crypto.KeyType, usages: Web5Crypto.KeyUsage[]) { - this.algorithm = algorithm; - this.extractable = extractable; - this.material = material; - this.type = type; - this.usages = usages; - - // ensure values are not writeable - Object.defineProperties(this, { - // TODO - // These properties can't be fixed immediately on creation of the - // object because the implementation may build it up in stages. - // At some point in the operations before returning a key we should - // freeze the object to prevent further manipulation. - - type: { - enumerable : true, - writable : false, - value : type - }, - extractable: { - enumerable : true, - writable : true, - value : extractable - }, - algorithm: { - enumerable : true, - writable : false, - value : algorithm - }, - usages: { - enumerable : true, - writable : true, - value : usages - }, - - // this is the "key material" used internally - // it is not enumerable, but we need it to be - // accessible by algorithm implementations - material: { - enumerable : false, - writable : false, - value : material - } - }); - } -} \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/ec/base.ts b/packages/crypto/src/algorithms-api/ec/base.ts index de47f285d..0994b9d05 100644 --- a/packages/crypto/src/algorithms-api/ec/base.ts +++ b/packages/crypto/src/algorithms-api/ec/base.ts @@ -1,39 +1,122 @@ +import { universalTypeOf } from '@web5/common'; + import type { Web5Crypto } from '../../types/web5-crypto.js'; +import type { JwkOperation, PrivateKeyJwk, PublicKeyJwk } from '../../jose.js'; +import { Jose } from '../../jose.js'; import { InvalidAccessError } from '../errors.js'; import { CryptoAlgorithm } from '../crypto-algorithm.js'; import { checkValidProperty, checkRequiredProperty } from '../../utils.js'; export abstract class BaseEllipticCurveAlgorithm extends CryptoAlgorithm { - public abstract namedCurves: string[]; + public abstract readonly curves: ReadonlyArray; - public checkGenerateKey(options: { + public checkGenerateKeyOptions(options: { algorithm: Web5Crypto.EcGenerateKeyOptions, - keyUsages: Web5Crypto.KeyUsage[] + keyOperations?: JwkOperation[] + }): void { + const { algorithm, keyOperations } = options; + + // Algorithm specified in the operation must match the algorithm implementation processing the operation. + this.checkAlgorithmName({ algorithmName: algorithm.name }); + + // The algorithm object must contain a curve property. + checkRequiredProperty({ property: 'curve', inObject: algorithm }); + + // The curve specified must be supported by the algorithm implementation processing the operation. + checkValidProperty({ property: algorithm.curve, allowedProperties: this.curves }); + + // If specified, key operations must be permitted by the algorithm implementation processing the operation. + if (keyOperations) { + this.checkKeyOperations({ keyOperations, allowedKeyOperations: this.keyOperations }); + } + } + + public checkSignOptions(options: { + algorithm: Web5Crypto.EcdsaOptions, + key: PrivateKeyJwk, + data: Uint8Array + }): void { + const { algorithm, data, key } = options; + + // Algorithm specified in the operation must match the algorithm implementation processing the operation. + this.checkAlgorithmName({ algorithmName: algorithm.name }); + + // The key object must be an Elliptic Curve (EC) or Octet Key Pair (OKP) private key in JWK format. + if (!(Jose.isEcPrivateKeyJwk(key) || Jose.isOkpPrivateKeyJwk(key))) { + throw new InvalidAccessError('Requested operation is only valid for private keys.'); + } + + // The curve specified must be supported by the algorithm implementation processing the operation. + checkValidProperty({ property: key.crv, allowedProperties: this.curves }); + + // The data must be a Uint8Array. + if (universalTypeOf(data) !== 'Uint8Array') { + throw new TypeError('The data must be of type Uint8Array.'); + } + + // If specified, the key's algorithm must match the algorithm implementation processing the operation. + if (key.alg) { + this.checkKeyAlgorithm({ keyAlgorithmName: key.alg }); + } + + // If specified, the key's `key_ops` must include the 'sign' operation. + if (key.key_ops) { + this.checkKeyOperations({ keyOperations: ['sign'], allowedKeyOperations: key.key_ops }); + } + } + + public checkVerifyOptions(options: { + algorithm: Web5Crypto.EcdsaOptions; + key: PublicKeyJwk; + signature: Uint8Array; + data: Uint8Array; }): void { - const { algorithm, keyUsages } = options; + const { algorithm, key, signature, data } = options; + // Algorithm specified in the operation must match the algorithm implementation processing the operation. this.checkAlgorithmName({ algorithmName: algorithm.name }); - // The algorithm object must contain a namedCurve property. - checkRequiredProperty({ property: 'namedCurve', inObject: algorithm }); - // The named curve specified must be supported by the algorithm implementation processing the operation. - checkValidProperty({ property: algorithm.namedCurve, allowedProperties: this.namedCurves }); - // The key usages specified must be permitted by the algorithm implementation processing the operation. - this.checkKeyUsages({ keyUsages, allowedKeyUsages: this.keyUsages }); + + // The key object must be an Elliptic Curve (EC) or Octet Key Pair (OKP) public key in JWK format. + if (!(Jose.isEcPublicKeyJwk(key) || Jose.isOkpPublicKeyJwk(key))) { + throw new InvalidAccessError('Requested operation is only valid for public keys.'); + } + + // The curve specified must be supported by the algorithm implementation processing the operation. + checkValidProperty({ property: key.crv, allowedProperties: this.curves }); + + // The signature must be a Uint8Array. + if (universalTypeOf(signature) !== 'Uint8Array') { + throw new TypeError('The signature must be of type Uint8Array.'); + } + + // The data must be a Uint8Array. + if (universalTypeOf(data) !== 'Uint8Array') { + throw new TypeError('The data must be of type Uint8Array.'); + } + + // If specified, the key's algorithm must match the algorithm implementation processing the operation. + if (key.alg) { + this.checkKeyAlgorithm({ keyAlgorithmName: key.alg }); + } + + // If specified, the key's `key_ops` must include the 'verify' operation. + if (key.key_ops) { + this.checkKeyOperations({ keyOperations: ['verify'], allowedKeyOperations: key.key_ops }); + } } public override async decrypt(): Promise { - throw new InvalidAccessError(`Requested operation 'decrypt' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'decrypt' is not valid for Elliptic Curve algorithms.`); } public override async encrypt(): Promise { - throw new InvalidAccessError(`Requested operation 'encrypt' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'encrypt' is not valid for Elliptic Curve algorithms.`); } public abstract generateKey(options: { algorithm: Web5Crypto.EcGenerateKeyOptions, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): Promise; + keyOperations?: JwkOperation[] + }): Promise; } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/ec/ecdh.ts b/packages/crypto/src/algorithms-api/ec/ecdh.ts index 4667c3830..414519fab 100644 --- a/packages/crypto/src/algorithms-api/ec/ecdh.ts +++ b/packages/crypto/src/algorithms-api/ec/ecdh.ts @@ -1,4 +1,5 @@ import type { Web5Crypto } from '../../types/web5-crypto.js'; +import { Jose, type JwkOperation, type PrivateKeyJwk } from '../../jose.js'; import { InvalidAccessError } from '../errors.js'; import { BaseEllipticCurveAlgorithm } from './base.js'; @@ -6,48 +7,62 @@ import { checkRequiredProperty } from '../../utils.js'; export abstract class BaseEcdhAlgorithm extends BaseEllipticCurveAlgorithm { - public readonly name: string = 'ECDH'; + public readonly keyOperations: JwkOperation[] = ['deriveBits', 'deriveKey']; - public keyUsages: Web5Crypto.KeyPairUsage = { - privateKey : ['deriveBits', 'deriveKey'], - publicKey : ['deriveBits', 'deriveKey'], - }; - - public checkAlgorithmOptions(options: { + public checkDeriveBitsOptions(options: { algorithm: Web5Crypto.EcdhDeriveKeyOptions, - baseKey: Web5Crypto.CryptoKey + baseKey: PrivateKeyJwk }): void { const { algorithm, baseKey } = options; // Algorithm specified in the operation must match the algorithm implementation processing the operation. this.checkAlgorithmName({ algorithmName: algorithm.name }); + // The algorithm object must contain a publicKey property. checkRequiredProperty({ property: 'publicKey', inObject: algorithm }); - // The publicKey object must be a CryptoKey. - this.checkCryptoKey({ key: algorithm.publicKey }); - // The CryptoKey object must be a public key. - this.checkKeyType({ keyType: algorithm.publicKey.type, allowedKeyType: 'public' }); - // The publicKey algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: algorithm.publicKey.algorithm.name }); + // The publicKey object must be a JSON Web key (JWK). + this.checkJwk({ key: algorithm.publicKey }); + // The publicKey object must be of key type EC or OKP. + this.checkKeyType({ keyType: algorithm.publicKey.kty, allowedKeyTypes: ['EC', 'OKP'] }); + // The publicKey object must be an Elliptic Curve (EC) or Octet Key Pair (OKP) public key in JWK format. + if (!(Jose.isEcPublicKeyJwk(algorithm.publicKey) || Jose.isOkpPublicKeyJwk(algorithm.publicKey))) { + throw new InvalidAccessError(`Requested operation is only valid for public keys.`); + } + // If specified, the public key's `key_ops` must include the 'deriveBits' operation. + if (algorithm.publicKey.key_ops) { + this.checkKeyOperations({ keyOperations: ['deriveBits'], allowedKeyOperations: algorithm.publicKey.key_ops }); + } + // The options object must contain a baseKey property. checkRequiredProperty({ property: 'baseKey', inObject: options }); - // The baseKey object must be a CryptoKey. - this.checkCryptoKey({ key: baseKey }); - // The baseKey algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: baseKey.algorithm.name }); - // The CryptoKey object must be a private key. - this.checkKeyType({ keyType: baseKey.type, allowedKeyType: 'private' }); - // The public and base key named curves must match. - if (('namedCurve' in algorithm.publicKey.algorithm) && ('namedCurve' in baseKey.algorithm) - && (algorithm.publicKey.algorithm.namedCurve !== baseKey.algorithm.namedCurve)) { - throw new InvalidAccessError('The named curve of the publicKey and baseKey must match.'); + // The baseKey object must be a JSON Web Key (JWK). + this.checkJwk({ key: baseKey }); + // The baseKey object must be of key type EC or OKP. + this.checkKeyType({ keyType: baseKey.kty, allowedKeyTypes: ['EC', 'OKP'] }); + // The baseKey object must be an Elliptic Curve (EC) or Octet Key Pair (OKP) private key in JWK format. + if (!(Jose.isEcPrivateKeyJwk(baseKey) || Jose.isOkpPrivateKeyJwk(baseKey))) { + throw new InvalidAccessError(`Requested operation is only valid for private keys.`); + } + // If specified, the base key's `key_ops` must include the 'deriveBits' operation. + if (baseKey.key_ops) { + this.checkKeyOperations({ keyOperations: ['deriveBits'], allowedKeyOperations: baseKey.key_ops }); + } + + // The public and base key types must match. + if ((algorithm.publicKey.kty !== baseKey.kty)) { + throw new InvalidAccessError('The key type of the publicKey and baseKey must match.'); + } + // The public and base key curves must match. + if (('crv' in algorithm.publicKey) && ('crv' in baseKey) + && (algorithm.publicKey.crv !== baseKey.crv)) { + throw new InvalidAccessError('The curve of the publicKey and baseKey must match.'); } } public override async sign(): Promise { - throw new InvalidAccessError(`Requested operation 'sign' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'sign' is not valid for ECDH algorithm.`); } public override async verify(): Promise { - throw new InvalidAccessError(`Requested operation 'verify' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'verify' is not valid for ECDH algorithm.`); } } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/ec/ecdsa.ts b/packages/crypto/src/algorithms-api/ec/ecdsa.ts index cfec1b22c..74ce4a5ab 100644 --- a/packages/crypto/src/algorithms-api/ec/ecdsa.ts +++ b/packages/crypto/src/algorithms-api/ec/ecdsa.ts @@ -1,37 +1,52 @@ import type { Web5Crypto } from '../../types/web5-crypto.js'; +import type { JwkOperation, PrivateKeyJwk, PublicKeyJwk } from '../../jose.js'; +import { Jose } from '../../jose.js'; import { InvalidAccessError } from '../errors.js'; import { BaseEllipticCurveAlgorithm } from './base.js'; -import { checkValidProperty, checkRequiredProperty } from '../../utils.js'; export abstract class BaseEcdsaAlgorithm extends BaseEllipticCurveAlgorithm { - public readonly name: string = 'ECDSA'; + public readonly keyOperations: JwkOperation[] = ['sign', 'verify']; - public readonly abstract hashAlgorithms: string[]; + public checkSignOptions(options: { + algorithm: Web5Crypto.EcdsaOptions, + key: PrivateKeyJwk, + data: Uint8Array + }): void { + const { key } = options; + + // Input parameter validation that is specified to ECDSA. + if (!Jose.isEcPrivateKeyJwk(key)) { + throw new InvalidAccessError('Requested operation is only valid for EC private keys.'); + } - public readonly keyUsages: Web5Crypto.KeyPairUsage = { - privateKey : ['sign'], - publicKey : ['verify'], - }; + // Input parameter validation that is common to all Elliptic Curve (EC) signature algorithms. + super.checkSignOptions(options); + } - public checkAlgorithmOptions(options: { - algorithm: Web5Crypto.EcdsaOptions + public checkVerifyOptions(options: { + algorithm: Web5Crypto.EcdsaOptions; + key: PublicKeyJwk; + signature: Uint8Array; + data: Uint8Array; }): void { - const { algorithm } = options; - // Algorithm specified in the operation must match the algorithm implementation processing the operation. - this.checkAlgorithmName({ algorithmName: algorithm.name }); - // The algorithm object must contain a hash property. - checkRequiredProperty({ property: 'hash', inObject: algorithm }); - // The hash algorithm specified must be supported by the algorithm implementation processing the operation. - checkValidProperty({ property: algorithm.hash, allowedProperties: this.hashAlgorithms }); + const { key } = options; + + // Input parameter validation that is specified to ECDSA. + if (!Jose.isEcPublicKeyJwk(key)) { + throw new InvalidAccessError('Requested operation is only valid for EC public keys.'); + } + + // Input parameter validation that is common to all Elliptic Curve (EC) signature algorithms. + super.checkVerifyOptions(options); } public override async deriveBits(): Promise { - throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for ECDSA algorithm.`); } - public abstract sign(options: { algorithm: Web5Crypto.EcdsaOptions; key: Web5Crypto.CryptoKey; data: Uint8Array; }): Promise; + public abstract sign(options: { algorithm: Web5Crypto.EcdsaOptions; key: PrivateKeyJwk; data: Uint8Array; }): Promise; - public abstract verify(options: { algorithm: Web5Crypto.EcdsaOptions; key: Web5Crypto.CryptoKey; signature: Uint8Array; data: Uint8Array; }): Promise; + public abstract verify(options: { algorithm: Web5Crypto.EcdsaOptions; key: PublicKeyJwk; signature: Uint8Array; data: Uint8Array; }): Promise; } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/ec/eddsa.ts b/packages/crypto/src/algorithms-api/ec/eddsa.ts index 75f302221..3592c0db2 100644 --- a/packages/crypto/src/algorithms-api/ec/eddsa.ts +++ b/packages/crypto/src/algorithms-api/ec/eddsa.ts @@ -1,30 +1,52 @@ import type { Web5Crypto } from '../../types/web5-crypto.js'; +import type { JwkOperation, PrivateKeyJwk, PublicKeyJwk } from '../../jose.js'; +import { Jose } from '../../jose.js'; import { InvalidAccessError } from '../errors.js'; import { BaseEllipticCurveAlgorithm } from './base.js'; export abstract class BaseEdDsaAlgorithm extends BaseEllipticCurveAlgorithm { - public readonly name: string = 'EdDSA'; + public readonly keyOperations: JwkOperation[] = ['sign', 'verify']; - public readonly keyUsages: Web5Crypto.KeyPairUsage = { - privateKey : ['sign'], - publicKey : ['verify'], - }; + public checkSignOptions(options: { + algorithm: Web5Crypto.EcdsaOptions, + key: PrivateKeyJwk, + data: Uint8Array + }): void { + const { key } = options; + + // Input parameter validation that is specified to EdDSA. + if (!Jose.isOkpPrivateKeyJwk(key)) { + throw new InvalidAccessError('Requested operation is only valid for OKP private keys.'); + } + + // Input parameter validation that is common to all Elliptic Curve (EC) signature algorithms. + super.checkSignOptions(options); + } - public checkAlgorithmOptions(options: { - algorithm: Web5Crypto.EdDsaOptions + public checkVerifyOptions(options: { + algorithm: Web5Crypto.EcdsaOptions; + key: PublicKeyJwk; + signature: Uint8Array; + data: Uint8Array; }): void { - const { algorithm } = options; - // Algorithm specified in the operation must match the algorithm implementation processing the operation. - this.checkAlgorithmName({ algorithmName: algorithm.name }); + const { key } = options; + + // Input parameter validation that is specified to EdDSA. + if (!Jose.isOkpPublicKeyJwk(key)) { + throw new InvalidAccessError('Requested operation is only valid for OKP public keys.'); + } + + // Input parameter validation that is common to all Elliptic Curve (EC) signature algorithms. + super.checkVerifyOptions(options); } public override async deriveBits(): Promise { - throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for EdDSA algorithm.`); } - public abstract sign(options: { algorithm: Web5Crypto.EdDsaOptions; key: Web5Crypto.CryptoKey; data: Uint8Array; }): Promise; + public abstract sign(options: { algorithm: Web5Crypto.EdDsaOptions; key: PrivateKeyJwk; data: Uint8Array; }): Promise; - public abstract verify(options: { algorithm: Web5Crypto.EdDsaOptions; key: Web5Crypto.CryptoKey; signature: Uint8Array; data: Uint8Array; }): Promise; + public abstract verify(options: { algorithm: Web5Crypto.EdDsaOptions; key: PublicKeyJwk; signature: Uint8Array; data: Uint8Array; }): Promise; } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/index.ts b/packages/crypto/src/algorithms-api/index.ts index fed29904f..c758b1739 100644 --- a/packages/crypto/src/algorithms-api/index.ts +++ b/packages/crypto/src/algorithms-api/index.ts @@ -1,6 +1,5 @@ export * from './errors.js'; export * from './ec/index.js'; export * from './aes/index.js'; -export * from './crypto-key.js'; export * from './pbkdf/index.js'; export * from './crypto-algorithm.js'; \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/pbkdf/pbkdf2.ts b/packages/crypto/src/algorithms-api/pbkdf/pbkdf2.ts index de5ea703b..54e1f6ba4 100644 --- a/packages/crypto/src/algorithms-api/pbkdf/pbkdf2.ts +++ b/packages/crypto/src/algorithms-api/pbkdf/pbkdf2.ts @@ -1,21 +1,21 @@ +import { universalTypeOf } from '@web5/common'; + +import type { JwkOperation, PrivateKeyJwk } from '../../jose.js'; import type { Web5Crypto } from '../../types/web5-crypto.js'; -import { InvalidAccessError, OperationError } from '../errors.js'; import { CryptoAlgorithm } from '../crypto-algorithm.js'; +import { InvalidAccessError, OperationError } from '../errors.js'; import { checkRequiredProperty, checkValidProperty } from '../../utils.js'; -import { universalTypeOf } from '@web5/common'; export abstract class BasePbkdf2Algorithm extends CryptoAlgorithm { - public readonly name: string = 'PBKDF2'; - - public readonly abstract hashAlgorithms: string[]; + public readonly abstract hashAlgorithms: ReadonlyArray; - public readonly keyUsages: Web5Crypto.KeyUsage[] = ['deriveBits', 'deriveKey']; + public readonly keyOperations: JwkOperation[] = ['deriveBits', 'deriveKey']; public checkAlgorithmOptions(options: { algorithm: Web5Crypto.Pbkdf2Options, - baseKey: Web5Crypto.CryptoKey + baseKey: PrivateKeyJwk }): void { const { algorithm, baseKey } = options; // Algorithm specified in the operation must match the algorithm implementation processing the operation. @@ -42,50 +42,29 @@ export abstract class BasePbkdf2Algorithm extends CryptoAlgorithm { } // The options object must contain a baseKey property. checkRequiredProperty({ property: 'baseKey', inObject: options }); - // The baseKey object must be a CryptoKey. - this.checkCryptoKey({ key: baseKey }); - // The baseKey algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: baseKey.algorithm.name }); - } - - public checkImportKey(options: { - algorithm: Web5Crypto.Algorithm, - format: Web5Crypto.KeyFormat, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): void { - const { algorithm, format, extractable, keyUsages } = options; - // Algorithm specified in the operation must match the algorithm implementation processing the operation. - this.checkAlgorithmName({ algorithmName: algorithm.name }); - // The format specified must be 'raw'. - if (format !== 'raw') { - throw new SyntaxError(`Format '${format}' not supported. Only 'raw' is supported.`); - } - // The extractable value specified must be false. - if (extractable !== false) { - throw new SyntaxError(`Extractable '${extractable}' not supported. Only 'false' is supported.`); - } - // The key usages specified must be permitted by the algorithm implementation processing the operation. - this.checkKeyUsages({ keyUsages, allowedKeyUsages: this.keyUsages }); + // The baseKey object must be a JSON Web Key (JWK). + this.checkJwk({ key: baseKey }); + // The baseKey must be of type 'oct' (octet sequence). + this.checkKeyType({ keyType: baseKey.kty, allowedKeyTypes: ['oct'] }); } public override async decrypt(): Promise { - throw new InvalidAccessError(`Requested operation 'decrypt' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'decrypt' is not valid for '${this.names.join(', ')}' keys.`); } public override async encrypt(): Promise { - throw new InvalidAccessError(`Requested operation 'encrypt' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'encrypt' is not valid for '${this.names.join(', ')}' keys.`); } - public override async generateKey(): Promise { - throw new InvalidAccessError(`Requested operation 'generateKey' is not valid for ${this.name} keys.`); + public override async generateKey(): Promise { + throw new InvalidAccessError(`Requested operation 'generateKey' is not valid for '${this.names.join(', ')}' keys.`); } public override async sign(): Promise { - throw new InvalidAccessError(`Requested operation 'sign' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'sign' is not valid for '${this.names.join(', ')}' keys.`); } public override async verify(): Promise { - throw new InvalidAccessError(`Requested operation 'verify' is not valid for ${this.name} keys.`); + throw new InvalidAccessError(`Requested operation 'verify' is not valid for '${this.names.join(', ')}' keys.`); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-algorithms/aes-ctr.ts b/packages/crypto/src/crypto-algorithms/aes-ctr.ts index 8567f830f..366201b88 100644 --- a/packages/crypto/src/crypto-algorithms/aes-ctr.ts +++ b/packages/crypto/src/crypto-algorithms/aes-ctr.ts @@ -1,26 +1,26 @@ -import { universalTypeOf } from '@web5/common'; - import type { Web5Crypto } from '../types/web5-crypto.js'; +import type{ JwkOperation, PrivateKeyJwk } from '../jose.js'; import { AesCtr } from '../crypto-primitives/index.js'; -import { BaseAesCtrAlgorithm, CryptoKey } from '../algorithms-api/index.js'; +import { BaseAesCtrAlgorithm } from '../algorithms-api/index.js'; export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { + public readonly names = ['A128CTR', 'A192CTR', 'A256CTR'] as const; + public async decrypt(options: { algorithm: Web5Crypto.AesCtrOptions, - key: Web5Crypto.CryptoKey, + key: PrivateKeyJwk, data: Uint8Array }): Promise { const { algorithm, key, data } = options; - this.checkAlgorithmOptions({ algorithm, key }); - // The secret key must be allowed to be used for 'decrypt' operations. - this.checkKeyUsages({ keyUsages: ['decrypt'], allowedKeyUsages: key.usages }); + // Validate the input parameters. + this.checkDecryptOptions(options); const plaintext = AesCtr.decrypt({ counter : algorithm.counter, data : data, - key : key.material, + key : key, length : algorithm.length }); @@ -29,19 +29,18 @@ export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { public async encrypt(options: { algorithm: Web5Crypto.AesCtrOptions, - key: Web5Crypto.CryptoKey, + key: PrivateKeyJwk, data: Uint8Array }): Promise { const { algorithm, key, data } = options; - this.checkAlgorithmOptions({ algorithm, key }); - // The secret key must be allowed to be used for 'encrypt' operations. - this.checkKeyUsages({ keyUsages: ['encrypt'], allowedKeyUsages: key.usages }); + // Validate the input parameters. + this.checkEncryptOptions(options); const ciphertext = AesCtr.encrypt({ counter : algorithm.counter, data : data, - key : key.material, + key : key, length : algorithm.length }); @@ -50,21 +49,28 @@ export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { public async generateKey(options: { algorithm: Web5Crypto.AesGenerateKeyOptions, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): Promise { - const { algorithm, extractable, keyUsages } = options; - - this.checkGenerateKey({ algorithm, keyUsages }); - - const secretKey = await AesCtr.generateKey({ length: algorithm.length }); - - if (universalTypeOf(secretKey) !== 'Uint8Array') { - throw new Error('Operation failed to generate key.'); + keyOperations: JwkOperation[] + }): Promise { + const { algorithm, keyOperations } = options; + + // Validate the input parameters. + this.checkGenerateKeyOptions({ algorithm, keyOperations }); + + // Map algorithm name to key length. + const algorithmNameToLength: Record = { + A128CTR : 128, + A192CTR : 192, + A256CTR : 256 + }; + + const secretKey = await AesCtr.generateKey({ length: algorithmNameToLength[algorithm.name] }); + + if (secretKey) { + secretKey.alg = algorithm.name; + if (keyOperations) secretKey.key_ops = keyOperations; + return secretKey; } - const secretCryptoKey = new CryptoKey(algorithm, extractable, secretKey, 'secret', this.keyUsages); - - return secretCryptoKey; + throw new Error('Operation failed: generateKey'); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-algorithms/ecdh.ts b/packages/crypto/src/crypto-algorithms/ecdh.ts index a87fa67be..e9d6e00c8 100644 --- a/packages/crypto/src/crypto-algorithms/ecdh.ts +++ b/packages/crypto/src/crypto-algorithms/ecdh.ts @@ -1,70 +1,70 @@ import type { Web5Crypto } from '../types/web5-crypto.js'; -import type { BytesKeyPair } from '../types/crypto-key.js'; +import type { + JwkOperation, + PrivateKeyJwk, + JwkParamsEcPrivate, + JwkParamsOkpPrivate, +} from '../jose.js'; -import { isBytesKeyPair } from '../utils.js'; import { Secp256k1, X25519 } from '../crypto-primitives/index.js'; -import { CryptoKey, BaseEcdhAlgorithm, OperationError } from '../algorithms-api/index.js'; +import { BaseEcdhAlgorithm, OperationError } from '../algorithms-api/index.js'; export class EcdhAlgorithm extends BaseEcdhAlgorithm { - public readonly namedCurves = ['secp256k1', 'X25519']; + public readonly names = ['ECDH'] as const; + public readonly curves = ['secp256k1', 'X25519'] as const; public async deriveBits(options: { algorithm: Web5Crypto.EcdhDeriveKeyOptions, - baseKey: Web5Crypto.CryptoKey, - length: number | null + baseKey: PrivateKeyJwk, + length?: number }): Promise { const { algorithm, baseKey, length } = options; - this.checkAlgorithmOptions({ algorithm, baseKey }); - // The base key must be allowed to be used for deriveBits operations. - this.checkKeyUsages({ keyUsages: ['deriveBits'], allowedKeyUsages: baseKey.usages }); - // The public key must be allowed to be used for deriveBits operations. - this.checkKeyUsages({ keyUsages: ['deriveBits'], allowedKeyUsages: algorithm.publicKey.usages }); + // Validate the input parameters. + this.checkDeriveBitsOptions({ algorithm, baseKey }); let sharedSecret: Uint8Array; + const curve = (baseKey as JwkParamsEcPrivate | JwkParamsOkpPrivate).crv; // checkDeriveBitsOptions verifies that the base key is of type EC or OKP. - const ownKeyAlgorithm = baseKey.algorithm as Web5Crypto.EcGenerateKeyOptions; // Type guard. - - switch (ownKeyAlgorithm.namedCurve) { + switch (curve) { case 'secp256k1': { - const ownPrivateKey = baseKey.material; - const otherPartyPublicKey = algorithm.publicKey.material; sharedSecret = await Secp256k1.sharedSecret({ - privateKey : ownPrivateKey, - publicKey : otherPartyPublicKey + privateKeyA : baseKey, + publicKeyB : algorithm.publicKey }); break; } case 'X25519': { - const ownPrivateKey = baseKey.material; - const otherPartyPublicKey = algorithm.publicKey.material; sharedSecret = await X25519.sharedSecret({ - privateKey : ownPrivateKey, - publicKey : otherPartyPublicKey + privateKeyA : baseKey, + publicKeyB : algorithm.publicKey }); break; } - default: - throw new TypeError(`Out of range: '${ownKeyAlgorithm.namedCurve}'. Must be one of '${this.namedCurves.join(', ')}'`); + default: { + throw new TypeError(`Out of range: '${curve}'. Must be one of '${this.curves.join(', ')}'`); + } } - // Length is null, return the full derived secret. - if (length === null) + // If 'length' is not specified, return the full derived secret. + if (length === undefined) return sharedSecret; // If the length is not a multiple of 8, throw. - if (length && length % 8 !== 0) + if (length && length % 8 !== 0) { throw new OperationError(`To be compatible with all browsers, 'length' must be a multiple of 8.`); + } // Convert length from bits to bytes. const lengthInBytes = length / 8; // If length (converted to bytes) is larger than the derived secret, throw. - if (sharedSecret.byteLength < lengthInBytes) + if (sharedSecret.byteLength < lengthInBytes) { throw new OperationError(`Requested 'length' exceeds the byte length of the derived secret.`); + } // Otherwise, either return the secret or a truncated slice. return lengthInBytes === sharedSecret.byteLength ? @@ -73,43 +73,35 @@ export class EcdhAlgorithm extends BaseEcdhAlgorithm { } public async generateKey(options: { - algorithm: Web5Crypto.EcGenerateKeyOptions | Web5Crypto.EcdsaGenerateKeyOptions, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): Promise { - const { algorithm, extractable, keyUsages } = options; + algorithm: Web5Crypto.EcGenerateKeyOptions, + keyOperations?: JwkOperation[] + }): Promise { + const { algorithm, keyOperations } = options; - this.checkGenerateKey({ algorithm, keyUsages }); + // Validate the input parameters. + this.checkGenerateKeyOptions({ algorithm, keyOperations }); - let keyPair: BytesKeyPair | undefined; - let cryptoKeyPair: Web5Crypto.CryptoKeyPair; + let privateKey: PrivateKeyJwk | undefined; - switch (algorithm.namedCurve) { + switch (algorithm.curve) { case 'secp256k1': { - (algorithm as Web5Crypto.EcdsaGenerateKeyOptions).compressedPublicKey ??= true; - keyPair = await Secp256k1.generateKeyPair({ - compressedPublicKey: (algorithm as Web5Crypto.EcdsaGenerateKeyOptions).compressedPublicKey - }); + privateKey = await Secp256k1.generateKey(); break; } case 'X25519': { - keyPair = await X25519.generateKeyPair(); + privateKey = await X25519.generateKey(); break; } - // Default case not needed because checkGenerateKey() already validates the specified namedCurve is supported. + // Default case not needed because checkGenerateKeyOptions() already validates the specified curve is supported. } - if (!isBytesKeyPair(keyPair)) { - throw new Error('Operation failed to generate key pair.'); + if (privateKey) { + if (keyOperations) privateKey.key_ops = keyOperations; + return privateKey; } - cryptoKeyPair = { - privateKey : new CryptoKey(algorithm, extractable, keyPair.privateKey, 'private', this.keyUsages.privateKey), - publicKey : new CryptoKey(algorithm, true, keyPair.publicKey, 'public', this.keyUsages.publicKey) - }; - - return cryptoKeyPair; + throw new Error('Operation failed: generateKey'); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-algorithms/ecdsa.ts b/packages/crypto/src/crypto-algorithms/ecdsa.ts index 828a42a6a..bda521dbe 100644 --- a/packages/crypto/src/crypto-algorithms/ecdsa.ts +++ b/packages/crypto/src/crypto-algorithms/ecdsa.ts @@ -1,111 +1,86 @@ import type { Web5Crypto } from '../types/web5-crypto.js'; -import type { BytesKeyPair } from '../types/crypto-key.js'; +import type { JwkOperation, JwkParamsEcPrivate, JwkParamsEcPublic, PrivateKeyJwk, PublicKeyJwk } from '../jose.js'; -import { isBytesKeyPair } from '../utils.js'; import { Secp256k1 } from '../crypto-primitives/index.js'; -import { CryptoKey, BaseEcdsaAlgorithm } from '../algorithms-api/index.js'; +import { BaseEcdsaAlgorithm } from '../algorithms-api/index.js'; + export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { - public readonly hashAlgorithms = ['SHA-256']; - public readonly namedCurves = ['secp256k1']; + public readonly names = ['ES256K'] as const; + public readonly curves = ['secp256k1'] as const; public async generateKey(options: { algorithm: Web5Crypto.EcdsaGenerateKeyOptions, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): Promise { - const { algorithm, extractable, keyUsages } = options; + keyOperations?: JwkOperation[] + }): Promise { + const { algorithm, keyOperations } = options; - this.checkGenerateKey({ algorithm, keyUsages }); + // Validate the input parameters. + this.checkGenerateKeyOptions({ algorithm, keyOperations }); - let keyPair: BytesKeyPair | undefined; - let cryptoKeyPair: Web5Crypto.CryptoKeyPair; + let privateKey: PrivateKeyJwk | undefined; - switch (algorithm.namedCurve) { + switch (algorithm.curve) { case 'secp256k1': { - algorithm.compressedPublicKey ??= true; - keyPair = await Secp256k1.generateKeyPair({ compressedPublicKey: algorithm.compressedPublicKey }); + privateKey = await Secp256k1.generateKey(); + privateKey.alg = 'ES256K'; break; } - // Default case not needed because checkGenerateKey() already validates the specified namedCurve is supported. + // Default case unnecessary because checkGenerateKeyOptions() validates the input parameters. } - if (!isBytesKeyPair(keyPair)) { - throw new Error('Operation failed to generate key pair.'); + if (privateKey) { + if (keyOperations) privateKey.key_ops = keyOperations; + return privateKey; } - cryptoKeyPair = { - privateKey : new CryptoKey(algorithm, extractable, keyPair.privateKey, 'private', this.keyUsages.privateKey), - publicKey : new CryptoKey(algorithm, true, keyPair.publicKey, 'public', this.keyUsages.publicKey) - }; - - return cryptoKeyPair; + throw new Error('Operation failed: generateKey'); } public async sign(options: { algorithm: Web5Crypto.EcdsaOptions, - key: Web5Crypto.CryptoKey, + key: PrivateKeyJwk, data: Uint8Array }): Promise { - const { algorithm, key, data } = options; + const { key, data } = options; - this.checkAlgorithmOptions({ algorithm }); - // The key's algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: key.algorithm.name }); - // The key must be a private key. - this.checkKeyType({ keyType: key.type, allowedKeyType: 'private' }); - // The key must be allowed to be used for sign operations. - this.checkKeyUsages({ keyUsages: ['sign'], allowedKeyUsages: key.usages }); + // Validate the input parameters. + this.checkSignOptions(options); - let signature: Uint8Array; + const curve = (key as JwkParamsEcPrivate).crv; // checkSignOptions verifies that the key is an EC private key. - const keyAlgorithm = key.algorithm as Web5Crypto.EcdsaGenerateKeyOptions; // Type guard. - - switch (keyAlgorithm.namedCurve) { + switch (curve) { case 'secp256k1': { - signature = await Secp256k1.sign({ hash: algorithm.hash, key: key.material, data }); - break; + return await Secp256k1.sign({ key, data }); } - - default: - throw new TypeError(`Out of range: '${keyAlgorithm.namedCurve}'. Must be one of '${this.namedCurves.join(', ')}'`); + // Default case unnecessary because checkSignOptions() validates the input parameters. } - return signature; + throw new Error('Operation failed: sign'); } public async verify(options: { algorithm: Web5Crypto.EcdsaOptions; - key: Web5Crypto.CryptoKey; + key: PublicKeyJwk; signature: Uint8Array; data: Uint8Array; }): Promise { - const { algorithm, key, signature, data } = options; - - this.checkAlgorithmOptions({ algorithm }); - // The key's algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: key.algorithm.name }); - // The key must be a public key. - this.checkKeyType({ keyType: key.type, allowedKeyType: 'public' }); - // The key must be allowed to be used for verify operations. - this.checkKeyUsages({ keyUsages: ['verify'], allowedKeyUsages: key.usages }); + const { key, signature, data } = options; - let isValid: boolean; + // Validate the input parameters. + this.checkVerifyOptions(options); - const keyAlgorithm = key.algorithm as Web5Crypto.EcdsaGenerateKeyOptions; // Type guard. + const curve = (key as JwkParamsEcPublic).crv; // checkVerifyOptions verifies that the key is an EC public key. - switch (keyAlgorithm.namedCurve) { + switch (curve) { case 'secp256k1': { - isValid = await Secp256k1.verify({ hash: algorithm.hash, key: key.material, signature, data }); - break; + return await Secp256k1.verify({ key, signature, data }); } - - default: - throw new TypeError(`Out of range: '${keyAlgorithm.namedCurve}'. Must be one of '${this.namedCurves.join(', ')}'`); + // Default case unnecessary because checkVerifyOptions() validates the input parameters. } - return isValid; + throw new Error('Operation failed: verify'); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-algorithms/eddsa.ts b/packages/crypto/src/crypto-algorithms/eddsa.ts index 1932443ba..ffcf9d45d 100644 --- a/packages/crypto/src/crypto-algorithms/eddsa.ts +++ b/packages/crypto/src/crypto-algorithms/eddsa.ts @@ -1,110 +1,86 @@ import type { Web5Crypto } from '../types/web5-crypto.js'; -import type { BytesKeyPair } from '../types/crypto-key.js'; +import type { JwkOperation, JwkParamsOkpPrivate, JwkParamsOkpPublic, PrivateKeyJwk, PublicKeyJwk } from '../jose.js'; -import { isBytesKeyPair } from '../utils.js'; import { Ed25519 } from '../crypto-primitives/index.js'; -import { CryptoKey, BaseEdDsaAlgorithm } from '../algorithms-api/index.js'; +import { BaseEdDsaAlgorithm } from '../algorithms-api/index.js'; export class EdDsaAlgorithm extends BaseEdDsaAlgorithm { - public readonly namedCurves = ['Ed25519', 'Ed448']; + public readonly names = ['EdDSA'] as const; + public readonly curves = ['Ed25519'] as const; public async generateKey(options: { algorithm: Web5Crypto.EdDsaGenerateKeyOptions, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): Promise { - const { algorithm, extractable, keyUsages } = options; + keyOperations?: JwkOperation[] + }): Promise { + const { algorithm, keyOperations } = options; - this.checkGenerateKey({ algorithm, keyUsages }); + // Validate the input parameters. + this.checkGenerateKeyOptions({ algorithm, keyOperations }); - let keyPair: BytesKeyPair | undefined; - let cryptoKeyPair: Web5Crypto.CryptoKeyPair; + let privateKey: PrivateKeyJwk | undefined; - switch (algorithm.namedCurve) { + switch (algorithm.curve) { case 'Ed25519': { - keyPair = await Ed25519.generateKeyPair(); + privateKey = await Ed25519.generateKey(); + privateKey.alg = 'EdDSA'; break; } - // Default case not needed because checkGenerateKey() already validates the specified namedCurve is supported. + // Default case unnecessary because checkGenerateKeyOptions() validates the input parameters. } - if (!isBytesKeyPair(keyPair)) { - throw new Error('Operation failed to generate key pair.'); + if (privateKey) { + if (keyOperations) privateKey.key_ops = keyOperations; + return privateKey; } - cryptoKeyPair = { - privateKey : new CryptoKey(algorithm, extractable, keyPair.privateKey, 'private', this.keyUsages.privateKey), - publicKey : new CryptoKey(algorithm, true, keyPair.publicKey, 'public', this.keyUsages.publicKey) - }; - - return cryptoKeyPair; + throw new Error('Operation failed: generateKey'); } public async sign(options: { algorithm: Web5Crypto.EdDsaOptions, - key: Web5Crypto.CryptoKey, + key: PrivateKeyJwk, data: Uint8Array }): Promise { - const { algorithm, key, data } = options; - - this.checkAlgorithmOptions({ algorithm }); - // The key's algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: key.algorithm.name }); - // The key must be a private key. - this.checkKeyType({ keyType: key.type, allowedKeyType: 'private' }); - // The key must be allowed to be used for sign operations. - this.checkKeyUsages({ keyUsages: ['sign'], allowedKeyUsages: key.usages }); + const { key, data } = options; - let signature: Uint8Array; + // Validate the input parameters. + this.checkSignOptions(options); - const keyAlgorithm = key.algorithm as Web5Crypto.EdDsaGenerateKeyOptions; // Type guard. + const curve = (key as JwkParamsOkpPrivate).crv; // checkSignOptions verifies that the key is an OKP private key. - switch (keyAlgorithm.namedCurve) { + switch (curve) { case 'Ed25519': { - signature = await Ed25519.sign({ key: key.material, data }); - break; + return await Ed25519.sign({ key, data }); } - - default: - throw new TypeError(`Out of range: '${keyAlgorithm.namedCurve}'. Must be one of '${this.namedCurves.join(', ')}'`); + // Default case unnecessary because checkSignOptions() validates the input parameters. } - return signature; + throw new Error('Operation failed: sign'); } public async verify(options: { algorithm: Web5Crypto.EdDsaOptions; - key: Web5Crypto.CryptoKey; + key: PublicKeyJwk; signature: Uint8Array; data: Uint8Array; }): Promise { - const { algorithm, key, signature, data } = options; + const { key, signature, data } = options; - this.checkAlgorithmOptions({ algorithm }); - // The key's algorithm must match the algorithm implementation processing the operation. - this.checkKeyAlgorithm({ keyAlgorithmName: key.algorithm.name }); - // The key must be a public key. - this.checkKeyType({ keyType: key.type, allowedKeyType: 'public' }); - // The key must be allowed to be used for verify operations. - this.checkKeyUsages({ keyUsages: ['verify'], allowedKeyUsages: key.usages }); + // Validate the input parameters. + this.checkVerifyOptions(options); - let isValid: boolean; + const curve = (key as JwkParamsOkpPublic).crv; // checkVerifyOptions verifies that the key is an OKP public key. - const keyAlgorithm = key.algorithm as Web5Crypto.EdDsaGenerateKeyOptions; // Type guard. - - switch (keyAlgorithm.namedCurve) { + switch (curve) { case 'Ed25519': { - isValid = await Ed25519.verify({ key: key.material, signature, data }); - break; + return await Ed25519.verify({ key, signature, data }); } - - default: - throw new TypeError(`Out of range: '${keyAlgorithm.namedCurve}'. Must be one of '${this.namedCurves.join(', ')}'`); + // Default case unnecessary because checkVerifyOptions() validates the input parameters. } - return isValid; + throw new Error('Operation failed: verify'); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-algorithms/pbkdf2.ts b/packages/crypto/src/crypto-algorithms/pbkdf2.ts index 68f4aa12f..782c28c5e 100644 --- a/packages/crypto/src/crypto-algorithms/pbkdf2.ts +++ b/packages/crypto/src/crypto-algorithms/pbkdf2.ts @@ -1,54 +1,51 @@ +import { Convert } from '@web5/common'; + +import type { JwkParamsOctPrivate, PrivateKeyJwk } from '../jose.js'; import type { Web5Crypto } from '../types/web5-crypto.js'; -import { BasePbkdf2Algorithm, CryptoKey, OperationError } from '../algorithms-api/index.js'; import { Pbkdf2 } from '../crypto-primitives/pbkdf2.js'; +import { BasePbkdf2Algorithm, OperationError } from '../algorithms-api/index.js'; export class Pbkdf2Algorithm extends BasePbkdf2Algorithm { - public readonly hashAlgorithms = ['SHA-256', 'SHA-384', 'SHA-512']; + public readonly names = ['PBKDF2'] as const; + public readonly hashAlgorithms = ['SHA-256', 'SHA-384', 'SHA-512'] as const; public async deriveBits(options: { algorithm: Web5Crypto.Pbkdf2Options, - baseKey: Web5Crypto.CryptoKey, + baseKey: PrivateKeyJwk, length: number }): Promise { const { algorithm, baseKey, length } = options; + // Check the `algorithm` and `baseKey` values for PBKDF2 requirements. this.checkAlgorithmOptions({ algorithm, baseKey }); - // The base key must be allowed to be used for deriveBits operations. - this.checkKeyUsages({ keyUsages: ['deriveBits'], allowedKeyUsages: baseKey.usages }); + + // If specified, the base key's `key_ops` must include the 'deriveBits' operation. + if (baseKey.key_ops) { + this.checkKeyOperations({ keyOperations: ['deriveBits'], allowedKeyOperations: baseKey.key_ops }); + } + // If the length is 0, throw. if (typeof length !== 'undefined' && length === 0) { throw new OperationError(`The value of 'length' cannot be zero.`); } + // If the length is not a multiple of 8, throw. if (length && length % 8 !== 0) { throw new OperationError(`To be compatible with all browsers, 'length' must be a multiple of 8.`); } + // Convert the base key to bytes. + const baseKeyBytes = Convert.base64Url((baseKey as JwkParamsOctPrivate).k).toUint8Array(); + const derivedBits = Pbkdf2.deriveKey({ hash : algorithm.hash as 'SHA-256' | 'SHA-384' | 'SHA-512', iterations : algorithm.iterations, length : length, - password : baseKey.material, + password : baseKeyBytes, salt : algorithm.salt }); return derivedBits; } - - public async importKey(options: { - format: Web5Crypto.KeyFormat, - keyData: Uint8Array, - algorithm: Web5Crypto.Algorithm, - extractable: boolean, - keyUsages: Web5Crypto.KeyUsage[] - }): Promise { - const { format, keyData, algorithm, extractable, keyUsages } = options; - - this.checkImportKey({ algorithm, format, extractable, keyUsages }); - - const cryptoKey = new CryptoKey(algorithm, extractable, keyData, 'secret', keyUsages); - - return cryptoKey; - } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/aes-ctr.ts b/packages/crypto/src/crypto-primitives/aes-ctr.ts index f218bdf63..2019c07c6 100644 --- a/packages/crypto/src/crypto-primitives/aes-ctr.ts +++ b/packages/crypto/src/crypto-primitives/aes-ctr.ts @@ -1,50 +1,137 @@ +import { Convert } from '@web5/common'; import { crypto } from '@noble/hashes/crypto'; +import type { PrivateKeyJwk } from '../jose.js'; + +import { Jose } from '../jose.js'; + /** - * The `AesCtr` class provides an interface for AES-CTR - * (Advanced Encryption Standard - Counter) encryption and decryption - * operations. The class uses the Web Crypto API for cryptographic operations. + * The `AesCtr` class provides a comprehensive set of utilities for cryptographic operations + * using the Advanced Encryption Standard (AES) in Counter (CTR) mode. This class includes + * methods for key generation, encryption, decryption, and conversions between raw byte arrays + * and JSON Web Key (JWK) formats. It is designed to support AES-CTR, a symmetric key algorithm + * that is widely used in various cryptographic applications for its efficiency and security. + * + * AES-CTR mode operates as a stream cipher using a block cipher (AES) and is well-suited for + * scenarios where parallel processing is beneficial or where the same key is required to + * encrypt multiple data blocks. The class adheres to standard cryptographic practices, ensuring + * compatibility and security in its implementations. + * + * Key Features: + * - Key Generation: Generate AES symmetric keys in JWK format. + * - Key Conversion: Transform keys between raw byte arrays and JWK formats. + * - Encryption: Encrypt data using AES-CTR with the provided symmetric key. + * - Decryption: Decrypt data encrypted with AES-CTR using the corresponding symmetric key. * - * All methods of this class are asynchronous and return Promises. They all - * use the Uint8Array type for keys and data, providing a consistent - * interface for working with binary data. + * The methods in this class are asynchronous, returning Promises to accommodate various + * JavaScript environments. * - * Example usage: + * Usage Examples: * * ```ts - * const key = await AesCtr.generateKey({ length: 128 }); - * const counter = new Uint8Array(16); // initialize a 16-byte counter - * const message = new TextEncoder().encode('Hello, world!'); - * const ciphertext = await AesCtr.encrypt({ + * // Key Generation + * const length = 256; // Length of the key in bits (e.g., 128, 192, 256) + * const privateKey = await AesCtr.generateKey({ length }); + * + * // Encryption + * const data = new TextEncoder().encode('Hello, world!'); + * const counter = new Uint8Array(16); // 16-byte (128-bit) counter block + * const encryptedData = await AesCtr.encrypt({ + * data, * counter, - * data: message, - * key, - * length: 128 // counter length in bits + * key: privateKey, + * length: 128 // Length of the counter block in bits * }); - * const plaintext = await AesCtr.decrypt({ + * + * // Decryption + * const decryptedData = await AesCtr.decrypt({ + * data: encryptedData, * counter, - * data: ciphertext, - * key, - * length: 128 // counter length in bits + * key: privateKey, + * length: 128 // Length of the counter block in bits * }); - * console.log(new TextDecoder().decode(plaintext)); // 'Hello, world!' + * + * // Key Conversion + * const privateKeyBytes = await AesCtr.privateKeyToBytes({ privateKey }); * ``` */ export class AesCtr { /** - * Decrypts the provided data using AES-CTR. + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method takes a symmetric key represented as a byte array (Uint8Array) and + * converts it into a JWK object for use with AES (Advanced Encryption Standard) + * in Counter (CTR) mode. The conversion process involves encoding the key into + * base64url format and setting the appropriate JWK parameters. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'oct' for Octet Sequence (representing a symmetric key). + * - `k`: The symmetric key, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint. + * + * Example usage: + * + * ```ts + * const privateKeyBytes = new Uint8Array([...]); // Replace with actual symmetric key bytes + * const privateKey = await AesCtr.bytesToPrivateKey({ privateKeyBytes }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKeyBytes - The raw symmetric key as a Uint8Array. + * + * @returns A Promise that resolves to the symmetric key in JWK format. + */ + public static async bytesToPrivateKey(options: { + privateKeyBytes: Uint8Array + }): Promise { + const { privateKeyBytes } = options; + + // Construct the private key in JWK format. + const privateKey: PrivateKeyJwk = { + k : Convert.uint8Array(privateKeyBytes).toBase64Url(), + kty : 'oct' + }; + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Decrypts the provided data using AES in Counter (CTR) mode. + * + * This method performs AES-CTR decryption on the given encrypted data using the specified key. + * Similar to the encryption process, it requires an initial counter block and the length + * of the counter block, along with the encrypted data and the decryption key. The method + * returns the decrypted data as a Uint8Array. + * + * Example usage: + * + * ```ts + * const encryptedData = new Uint8Array([...]); // Encrypted data + * const counter = new Uint8Array(16); // 16-byte (128-bit) counter block used during encryption + * const key = { ... }; // A PrivateKeyJwk object representing the same AES key used for encryption + * const decryptedData = await AesCtr.decrypt({ + * data: encryptedData, + * counter, + * key, + * length: 128 // Length of the counter block in bits + * }); + * ``` * * @param options - The options for the decryption operation. * @param options.counter - The initial value of the counter block. - * @param options.data - The data to decrypt. - * @param options.key - The key to use for decryption. + * @param options.data - The encrypted data to decrypt, represented as a Uint8Array. + * @param options.key - The key to use for decryption, represented in JWK format. * @param options.length - The length of the counter block in bits. + * * @returns A Promise that resolves to the decrypted data as a Uint8Array. */ public static async decrypt(options: { counter: Uint8Array, data: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, length: number }): Promise { const { counter, data, key, length } = options; @@ -64,19 +151,39 @@ export class AesCtr { } /** - * Encrypts the provided data using AES-CTR. + * Encrypts the provided data using AES in Counter (CTR) mode. + * + * This method performs AES-CTR encryption on the given data using the specified key. + * It requires the initial counter block and the length of the counter block, alongside + * the data and key. The method is designed to work asynchronously and returns the + * encrypted data as a Uint8Array. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); + * const counter = new Uint8Array(16); // 16-byte (128-bit) counter block + * const key = { ... }; // A PrivateKeyJwk object representing an AES key + * const encryptedData = await AesCtr.encrypt({ + * data, + * counter, + * key, + * length: 128 // Length of the counter block in bits + * }); + * ``` * * @param options - The options for the encryption operation. * @param options.counter - The initial value of the counter block. - * @param options.data - The data to encrypt. - * @param options.key - The key to use for encryption. + * @param options.data - The data to encrypt, represented as a Uint8Array. + * @param options.key - The key to use for encryption, represented in JWK format. * @param options.length - The length of the counter block in bits. + * * @returns A Promise that resolves to the encrypted data as a Uint8Array. */ public static async encrypt(options: { counter: Uint8Array, data: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, length: number }): Promise { const { counter, data, key, length } = options; @@ -96,36 +203,97 @@ export class AesCtr { } /** - * Generates an AES key of a given length. + * Generates a symmetric key for AES in Counter (CTR) mode in JSON Web Key (JWK) format. + * + * This method creates a new symmetric key of a specified length suitable for use with + * AES-CTR encryption. It uses cryptographically secure random number generation to + * ensure the uniqueness and security of the key. The generated key adheres to the JWK + * format, making it compatible with common cryptographic standards and easy to use in + * various cryptographic processes. + * + * The generated key includes the following components: + * - `kty`: Key Type, set to 'oct' for Octet Sequence. + * - `k`: The symmetric key component, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint. + * + * Example usage: * - * @param length - The length of the key in bits. - * @returns A Promise that resolves to the generated key as a Uint8Array. + * ```ts + * const length = 256; // Length of the key in bits (e.g., 128, 192, 256) + * const privateKey = await AesCtr.generateKey({ length }); + * ``` + * + * @param options - The options for the key generation. + * @param options.length - The length of the key in bits. Common lengths are 128, 192, and 256 bits. + * + * @returns A Promise that resolves to the generated symmetric key in JWK format. */ public static async generateKey(options: { length: number - }): Promise { + }): Promise { const { length } = options; - // Generate the secret key. + // Generate a random private key. const lengthInBytes = length / 8; - const secretKey = crypto.getRandomValues(new Uint8Array(lengthInBytes)); + const privateKeyBytes = crypto.getRandomValues(new Uint8Array(lengthInBytes)); + + // Convert private key from bytes to JWK format. + const privateKey = await AesCtr.bytesToPrivateKey({ privateKeyBytes }); + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method takes a symmetric key in JWK format and extracts its raw byte representation. + * It decodes the 'k' parameter of the JWK value, which represents the symmetric key in base64url + * encoding, into a byte array. + * + * Example usage: + * + * ```ts + * const privateKey = { ... }; // A symmetric key in JWK format + * const privateKeyBytes = await AesCtr.privateKeyToBytes({ privateKey }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKey - The symmetric key in JWK format. + * + * @returns A Promise that resolves to the symmetric key as a Uint8Array. + */ + public static async privateKeyToBytes(options: { + privateKey: PrivateKeyJwk + }): Promise { + const { privateKey } = options; + + // Verify the provided JWK represents a valid oct private key. + if (!Jose.isOctPrivateKeyJwk(privateKey)) { + throw new Error(`AesCtr: The provided key is not a valid oct private key.`); + } + + // Decode the provided private key to bytes. + const privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); - return secretKey; + return privateKeyBytes; } /** - * A private method to import a raw key for use with the Web Crypto API. + * A private method to import a key in JWK format for use with the Web Crypto API. * - * @param key - The raw key material. + * @param key - The key in JWK format. * @returns A Promise that resolves to a CryptoKey. */ - private static async importKey(key: Uint8Array): Promise { + private static async importKey(key: PrivateKeyJwk): Promise { return crypto.subtle.importKey( - 'raw', - key.buffer, - { name: 'AES-CTR', length: key.byteLength * 8 }, - true, - ['encrypt', 'decrypt'] + 'jwk', // format + key, // keyData + { name: 'AES-CTR' }, // algorithm + true, // extractable + ['encrypt', 'decrypt'] // usages ); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/aes-gcm.ts b/packages/crypto/src/crypto-primitives/aes-gcm.ts index 852bb15e7..137463c32 100644 --- a/packages/crypto/src/crypto-primitives/aes-gcm.ts +++ b/packages/crypto/src/crypto-primitives/aes-gcm.ts @@ -1,53 +1,142 @@ +import { Convert } from '@web5/common'; import { crypto } from '@noble/hashes/crypto'; +import type { PrivateKeyJwk } from '../jose.js'; + +import { Jose } from '../jose.js'; + /** - * The `AesGcm` class provides an interface for AES-GCM - * (Advanced Encryption Standard - Galois/Counter Mode) encryption and - * decryption operations. The class uses the Web Crypto API for - * cryptographic operations. + * The `AesGcm` class provides a comprehensive set of utilities for cryptographic operations + * using the Advanced Encryption Standard (AES) in Galois/Counter Mode (GCM). This class includes + * methods for key generation, encryption, decryption, and conversions between raw byte arrays + * and JSON Web Key (JWK) formats. It is designed to support AES-GCM, a symmetric key algorithm + * that is widely used for its efficiency, security, and provision of authenticated encryption. * - * All methods of this class are asynchronous and return Promises. They all - * use the Uint8Array type for keys and data, providing a consistent - * interface for working with binary data. + * AES-GCM is particularly favored for scenarios that require both confidentiality and integrity + * of data. It integrates the counter mode of encryption with the Galois mode of authentication, + * offering high performance and parallel processing capabilities. * - * Example usage: + * Key Features: + * - Key Generation: Generate AES symmetric keys in JWK format. + * - Key Conversion: Transform keys between raw byte arrays and JWK formats. + * - Encryption: Encrypt data using AES-GCM with the provided symmetric key. + * - Decryption: Decrypt data encrypted with AES-GCM using the corresponding symmetric key. + * + * The methods in this class are asynchronous, returning Promises to accommodate various + * JavaScript environments. + * + * Usage Examples: * * ```ts - * const key = await AesGcm.generateKey({ length: 128 }); - * const iv = new Uint8Array(12); // generate a 12-byte initialization vector - * const message = new TextEncoder().encode('Hello, world!'); - * const ciphertext = await AesGcm.encrypt({ - * data: message, + * // Key Generation + * const length = 256; // Length of the key in bits (e.g., 128, 192, 256) + * const privateKey = await AesGcm.generateKey({ length }); + * + * // Encryption + * const data = new TextEncoder().encode('Hello, world!'); + * const iv = new Uint8Array(12); // 12-byte initialization vector + * const encryptedData = await AesGcm.encrypt({ + * data, * iv, - * key, - * tagLength: 128 + * key: privateKey, + * tagLength: 128 // Length of the authentication tag in bits * }); - * const plaintext = await AesGcm.decrypt({ - * data: ciphertext, + * + * // Decryption + * const decryptedData = await AesGcm.decrypt({ + * data: encryptedData, * iv, - * key, - * tagLength: 128 + * key: privateKey, + * tagLength: 128 // Length of the authentication tag in bits * }); - * console.log(new TextDecoder().decode(plaintext)); // 'Hello, world!' + * + * // Key Conversion + * const privateKeyBytes = await AesGcm.privateKeyToBytes({ privateKey }); * ``` */ export class AesGcm { + /** + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method accepts a symmetric key represented as a byte array (Uint8Array) and + * converts it into a JWK object for use with AES-GCM (Advanced Encryption Standard - + * Galois/Counter Mode). The conversion process involves encoding the key into + * base64url format and setting the appropriate JWK parameters. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'oct' for Octet Sequence (representing a symmetric key). + * - `k`: The symmetric key, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint. + * + * Example usage: + * + * ```ts + * const privateKeyBytes = new Uint8Array([...]); // Replace with actual symmetric key bytes + * const privateKey = await AesGcm.bytesToPrivateKey({ privateKeyBytes }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKeyBytes - The raw symmetric key as a Uint8Array. + * + * @returns A Promise that resolves to the symmetric key in JWK format. + */ + public static async bytesToPrivateKey(options: { + privateKeyBytes: Uint8Array + }): Promise { + const { privateKeyBytes } = options; + + // Construct the private key in JWK format. + const privateKey: PrivateKeyJwk = { + k : Convert.uint8Array(privateKeyBytes).toBase64Url(), + kty : 'oct' + }; + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + /** * Decrypts the provided data using AES-GCM. * + * This method performs AES-GCM decryption on the given encrypted data using the specified key. + * It requires an initialization vector (IV), the encrypted data along with the decryption key, + * and optionally, additional authenticated data (AAD). The method returns the decrypted data as a + * Uint8Array. The optional `tagLength` parameter specifies the size in bits of the authentication + * tag used when encrypting the data. If not specified, the default tag length of 128 bits is + * used. + * + * Example usage: + * + * ```ts + * const encryptedData = new Uint8Array([...]); // Encrypted data + * const iv = new Uint8Array([...]); // Initialization vector used during encryption + * const additionalData = new Uint8Array([...]); // Optional additional authenticated data + * const key = { ... }; // A PrivateKeyJwk object representing the AES key + * const decryptedData = await AesGcm.decrypt({ + * data: encryptedData, + * iv, + * additionalData, + * key, + * tagLength: 128 // Optional tag length in bits + * }); + * ``` + * * @param options - The options for the decryption operation. - * @param options.additionalData - Data that will be authenticated along with the encrypted data. - * @param options.data - The data to decrypt. - * @param options.iv - A unique initialization vector. - * @param options.key - The key to use for decryption. - * @param options.tagLength - This size of the authentication tag generated in bits. + * @param options.data - The encrypted data to decrypt, represented as a Uint8Array. + * @param options.iv - The initialization vector, represented as a Uint8Array. + * @param options.additionalData - Optional additional authenticated data. + * @param options.key - The key to use for decryption, represented in JWK format. + * @param options.tagLength - The length of the authentication tag in bits. + * * @returns A Promise that resolves to the decrypted data as a Uint8Array. */ public static async decrypt(options: { additionalData?: Uint8Array, data: Uint8Array, iv: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, tagLength?: number }): Promise { const { additionalData, data, iv, key, tagLength } = options; @@ -70,19 +159,43 @@ export class AesGcm { /** * Encrypts the provided data using AES-GCM. * + * This method performs AES-GCM encryption on the given data using the specified key. + * It requires an initialization vector (IV), the encrypted data along with the decryption key, + * and optionally, additional authenticated data (AAD). The method returns the encrypted data as a + * Uint8Array. The optional `tagLength` parameter specifies the size in bits of the authentication + * tag generated in the encryption operation and used for authentication in the corresponding + * decryption. If not specified, the default tag length of 128 bits is used. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); + * const iv = new Uint8Array([...]); // Initialization vector + * const additionalData = new Uint8Array([...]); // Optional additional authenticated data + * const key = { ... }; // A PrivateKeyJwk object representing an AES key + * const encryptedData = await AesGcm.encrypt({ + * data, + * iv, + * additionalData, + * key, + * tagLength: 128 // Optional tag length in bits + * }); + * ``` + * * @param options - The options for the encryption operation. - * @param options.additionalData - Data that will be authenticated along with the encrypted data. - * @param options.data - The data to decrypt. - * @param options.iv - A unique initialization vector. - * @param options.key - The key to use for decryption. - * @param options.tagLength - This size of the authentication tag generated in bits. + * @param options.data - The data to encrypt, represented as a Uint8Array. + * @param options.iv - The initialization vector, represented as a Uint8Array. + * @param options.additionalData - Optional additional authenticated data. + * @param options.key - The key to use for encryption, represented in JWK format. + * @param options.tagLength - The length of the authentication tag in bits. + * * @returns A Promise that resolves to the encrypted data as a Uint8Array. */ public static async encrypt(options: { additionalData?: Uint8Array, data: Uint8Array, iv: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, tagLength?: number }): Promise { const { additionalData, data, iv, key, tagLength } = options; @@ -103,36 +216,98 @@ export class AesGcm { } /** - * Generates an AES key of a given length. + * Generates a symmetric key for AES in Galois/Counter Mode (GCM) in JSON Web Key (JWK) format. + * + * This method creates a new symmetric key of a specified length suitable for use with + * AES-GCM encryption. It leverages cryptographically secure random number generation + * to ensure the uniqueness and security of the key. The generated key adheres to the JWK + * format, facilitating compatibility with common cryptographic standards and ease of use + * in various cryptographic applications. + * + * The generated key includes these components: + * - `kty`: Key Type, set to 'oct' for Octet Sequence, indicating a symmetric key. + * - `k`: The symmetric key component, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint, providing a unique identifier. * - * @param length - The length of the key in bits. - * @returns A Promise that resolves to the generated key as a Uint8Array. + * Example usage: + * + * ```ts + * const length = 256; // Length of the key in bits (e.g., 128, 192, 256) + * const privateKey = await AesGcm.generateKey({ length }); + * ``` + * + * @param options - The options for the key generation. + * @param options.length - The length of the key in bits. Common lengths are 128, 192, and 256 bits. + * + * @returns A Promise that resolves to the generated symmetric key in JWK format. */ public static async generateKey(options: { length: number - }): Promise { + }): Promise { const { length } = options; // Generate the secret key. const lengthInBytes = length / 8; - const secretKey = crypto.getRandomValues(new Uint8Array(lengthInBytes)); + const privateKeyBytes = crypto.getRandomValues(new Uint8Array(lengthInBytes)); + + // Convert private key from bytes to JWK format. + const privateKey = await AesGcm.bytesToPrivateKey({ privateKeyBytes }); + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method takes a symmetric key in JWK format and extracts its raw byte representation. + * It focuses on the 'k' parameter of the JWK, which represents the symmetric key component + * in base64url encoding. The method decodes this value into a byte array, providing + * the symmetric key in its raw binary form. + * + * Example usage: + * + * ```ts + * const privateKey = { ... }; // A symmetric key in JWK format + * const privateKeyBytes = await AesGcm.privateKeyToBytes({ privateKey }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKey - The symmetric key in JWK format. + * + * @returns A Promise that resolves to the symmetric key as a Uint8Array. + */ + public static async privateKeyToBytes(options: { + privateKey: PrivateKeyJwk + }): Promise { + const { privateKey } = options; + + // Verify the provided JWK represents a valid oct private key. + if (!Jose.isOctPrivateKeyJwk(privateKey)) { + throw new Error(`AesGcm: The provided key is not a valid oct private key.`); + } + + // Decode the provided private key to bytes. + const privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); - return secretKey; + return privateKeyBytes; } /** - * A private method to import a raw key for use with the Web Crypto API. + * A private method to import a key in JWK format for use with the Web Crypto API. * - * @param key - The raw key material. + * @param key - The key in JWK format. * @returns A Promise that resolves to a CryptoKey. */ - private static async importKey(key: Uint8Array): Promise { + private static async importKey(key: PrivateKeyJwk): Promise { return crypto.subtle.importKey( - 'raw', - key.buffer, - { name: 'AES-GCM', length: key.byteLength * 8 }, - true, - ['encrypt', 'decrypt'] + 'jwk', // format + key, // keyData + { name: 'AES-GCM' }, // algorithm + true, // extractable + ['encrypt', 'decrypt'] // usages ); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/ed25519.ts b/packages/crypto/src/crypto-primitives/ed25519.ts index 2ba767718..54fe83305 100644 --- a/packages/crypto/src/crypto-primitives/ed25519.ts +++ b/packages/crypto/src/crypto-primitives/ed25519.ts @@ -1,28 +1,33 @@ -import type { BytesKeyPair } from '../types/crypto-key.js'; +import { Convert } from '@web5/common'; +import { ed25519, edwardsToMontgomeryPub, edwardsToMontgomeryPriv, x25519 } from '@noble/curves/ed25519'; -import { ed25519, edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519'; +import type { PrivateKeyJwk, PublicKeyJwk } from '../jose.js'; + +import { Jose } from '../jose.js'; /** - * The `Ed25519` class provides an interface for generating Ed25519 key pairs, - * computing public keys from private keys, and signing and verifying messages. + * The `Ed25519` class provides an interface for generating Ed25519 private + * keys, computing public keys from private keys, and signing and verifying + * messages. * * The class uses the '@noble/curves' package for the cryptographic operations. * - * The methods of this class are all asynchronous and return Promises. They all use - * the Uint8Array type for keys, signatures, and data, providing a consistent - * interface for working with binary data. + * The methods of this class are all asynchronous and return Promises. They all + * use the Uint8Array type for signatures and data, providing a + * consistent interface for working with binary data. * * Example usage: * * ```ts - * const keyPair = await Ed25519.generateKeyPair(); + * const privateKey = await Ed25519.generateKey(); * const message = new TextEncoder().encode('Hello, world!'); * const signature = await Ed25519.sign({ - * key: keyPair.privateKey, + * key: privateKey, * data: message * }); + * const publicKey = await Ed25519.getPublicKey({ privateKey }); * const isValid = await Ed25519.verify({ - * key: keyPair.publicKey, + * key: publicKey, * signature, * data: message * }); @@ -30,135 +35,463 @@ import { ed25519, edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble * ``` */ export class Ed25519 { + /** + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method accepts a private key as a byte array (Uint8Array) for the Curve25519 curve in + * Twisted Edwards form and transforms it into a JWK object. The process involves first deriving + * the public key from the private key, then encoding both the private and public keys into + * base64url format. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'OKP' for Octet Key Pair. + * - `crv`: Curve Name, set to 'Ed25519'. + * - `d`: The private key component, base64url-encoded. + * - `x`: The computed public key, base64url-encoded. + * + * Example usage: + * + * ```ts + * const privateKeyBytes = new Uint8Array([...]); // Replace with actual private key bytes + * const privateKey = await Ed25519.bytesToPrivateKey({ privateKeyBytes }); + * ``` + * + * @param options - The options for the private key conversion. + * @param options.privateKeyBytes - The raw private key as a Uint8Array. + * + * @returns A Promise that resolves to the private key in JWK format. + */ + public static async bytesToPrivateKey(options: { + privateKeyBytes: Uint8Array + }): Promise { + const { privateKeyBytes } = options; + + // Derive the public key from the private key. + const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes); + + // Construct the private key in JWK format. + const privateKey: PrivateKeyJwk = { + crv : 'Ed25519', + d : Convert.uint8Array(privateKeyBytes).toBase64Url(), + kty : 'OKP', + x : Convert.uint8Array(publicKeyBytes).toBase64Url(), + }; + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method accepts a public key as a byte array (Uint8Array) for the Curve25519 curve in + * Twisted Edwards form and transforms it into a JWK object. The process involves encoding the + * public key bytes into base64url format. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'OKP' for Octet Key Pair. + * - `crv`: Curve Name, set to 'X25519'. + * - `x`: The public key, base64url-encoded. + * + * Example usage: + * + * ```ts + * const publicKeyBytes = new Uint8Array([...]); // Replace with actual public key bytes + * const publicKey = await X25519.bytesToPublicKey({ publicKeyBytes }); + * ``` + * + * @param options - The options for the public key conversion. + * @param options.publicKeyBytes - The raw public key as a Uint8Array. + * + * @returns A Promise that resolves to the public key in JWK format. + */ + public static async bytesToPublicKey(options: { + publicKeyBytes: Uint8Array + }): Promise { + const { publicKeyBytes } = options; + + // Construct the public key in JWK format. + const publicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : Convert.uint8Array(publicKeyBytes).toBase64Url(), + }; + + // Compute the JWK thumbprint and set as the key ID. + publicKey.kid = await Jose.jwkThumbprint({ key: publicKey }); + + return publicKey; + } /** * Converts an Ed25519 private key to its X25519 counterpart. * - * Similar to the public key conversion, this method aids in transitioning - * from signing to encryption operations. By converting an Ed25519 private - * key to X25519 format, one can use the same key pair for both digital - * signatures and key exchange operations. + * This method enables the use of the same key pair for both digital signature (Ed25519) + * and key exchange (X25519) operations. It takes an Ed25519 private key and converts it + * to the corresponding X25519 format, facilitating interoperability between signing + * and encryption protocols. + * + * Example usage: + * + * ```ts + * const ed25519PrivateKey = { ... }; // An Ed25519 private key in JWK format + * const x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519({ + * privateKey: ed25519PrivateKey + * }); + * ``` * * @param options - The options for the conversion. - * @param options.privateKey - The Ed25519 private key to convert, represented as a Uint8Array. - * @returns A Promise that resolves to the X25519 private key as a Uint8Array. + * @param options.privateKey - The Ed25519 private key to convert, in JWK format. + * + * @returns A Promise that resolves to the X25519 private key in JWK format. */ public static async convertPrivateKeyToX25519(options: { - privateKey: Uint8Array - }): Promise { + privateKey: PrivateKeyJwk + }): Promise { const { privateKey } = options; - // Converts Ed25519 private key to X25519 private key. - const montgomeryPrivateKey = edwardsToMontgomeryPriv(privateKey); + // Convert the provided Ed25519 private key to bytes. + const ed25519PrivateKeyBytes = await Ed25519.privateKeyToBytes({ privateKey }); + + // Convert the Ed25519 private key to an X25519 private key. + const x25519PrivateKeyBytes = edwardsToMontgomeryPriv(ed25519PrivateKeyBytes); - return montgomeryPrivateKey; + // Derive the X25519 public key from the X25519 private key. + const x25519PublicKeyBytes = x25519.getPublicKey(x25519PrivateKeyBytes); + + // Construct the X25519 private key in JWK format. + const x25519PrivateKey: PrivateKeyJwk = { + kty : 'OKP', + crv : 'X25519', + d : Convert.uint8Array(x25519PrivateKeyBytes).toBase64Url(), + x : Convert.uint8Array(x25519PublicKeyBytes).toBase64Url(), + }; + + // Compute the JWK thumbprint and set as the key ID. + x25519PrivateKey.kid = await Jose.jwkThumbprint({ key: x25519PrivateKey }); + + return x25519PrivateKey; } /** - * Converts an Ed25519 public key to its X25519 counterpart. - * - * This method is useful when transitioning from signing to encryption - * operations, as Ed25519 and X25519 keys share the same mathematical - * foundation but serve different purposes. Ed25519 keys are used for - * digital signatures, while X25519 keys are used for key exchange in - * encryption protocols like Diffie-Hellman. - * - * @param options - The options for the conversion. - * @param options.publicKey - The Ed25519 public key to convert, represented as a Uint8Array. - * @returns A Promise that resolves to the X25519 public key as a Uint8Array. - */ + * Converts an Ed25519 public key to its X25519 counterpart. + * + * This method enables the use of the same key pair for both digital signature (Ed25519) + * and key exchange (X25519) operations. It takes an Ed25519 public key and converts it + * to the corresponding X25519 format, facilitating interoperability between signing + * and encryption protocols. + * + * Example usage: + * + * ```ts + * const ed25519PublicKey = { ... }; // An Ed25519 public key in JWK format + * const x25519PublicKey = await Ed25519.convertPublicKeyToX25519({ + * publicKey: ed25519PublicKey + * }); + * + * @param options - The options for the conversion. + * @param options.publicKey - The Ed25519 public key to convert, in JWK format. + * + * @returns A Promise that resolves to the X25519 public key in JWK format. + */ public static async convertPublicKeyToX25519(options: { - publicKey: Uint8Array - }): Promise { + publicKey: PublicKeyJwk + }): Promise { const { publicKey } = options; + // Convert the provided private key to a byte array. + const ed25519PublicKeyBytes = await Ed25519.publicKeyToBytes({ publicKey }); + // Verify Edwards public key is valid. - const isValid = await Ed25519.validatePublicKey({ key: publicKey }); + const isValid = await Ed25519.validatePublicKey({ key: ed25519PublicKeyBytes }); if (!isValid) { throw new Error('Ed25519: Invalid public key.'); } - // Converts Ed25519 public key to X25519 public key. - const montgomeryPublicKey = edwardsToMontgomeryPub(publicKey); + // Convert the Ed25519 public key to an X25519 private key. + const x25519PublicKeyBytes = edwardsToMontgomeryPub(ed25519PublicKeyBytes); - return montgomeryPublicKey; + // Construct the X25519 private key in JWK format. + const x25519PublicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'X25519', + x : Convert.uint8Array(x25519PublicKeyBytes).toBase64Url(), + }; + + // Compute the JWK thumbprint and set as the key ID. + x25519PublicKey.kid = await Jose.jwkThumbprint({ key: x25519PublicKey }); + + return x25519PublicKey; } /** - * Generates an Ed25519 key pair. + * Derives the public key in JWK format from a given Ed25519 private key. + * + * This method takes a private key in JWK format and derives its corresponding public key, + * also in JWK format. The derivation process involves converting the private key to a + * raw byte array and then computing the corresponding public key on the Curve25519 curve in + * Twisted Edwards form. The public key is then encoded into base64url format to construct + * a JWK representation. * - * @returns A Promise that resolves to an object containing the private and public keys as Uint8Array. + * Example usage: + * + * ```ts + * const privateKey = { ... }; // A PrivateKeyJwk object representing an Ed25519 private key + * const publicKey = await Ed25519.computePublicKey({ privateKey }); + * ``` + * + * @param options - The options for the public key derivation. + * @param options.privateKey - The private key in JWK format from which to derive the public key. + * + * @returns A Promise that resolves to the computed public key in JWK format. */ - public static async generateKeyPair(): Promise { - // Generate the private key and compute its public key. - const privateKey = ed25519.utils.randomPrivateKey(); - const publicKey = ed25519.getPublicKey(privateKey); - - const keyPair = { - privateKey : privateKey, - publicKey : publicKey + public static async computePublicKey(options: { + privateKey: PrivateKeyJwk + }): Promise { + let { privateKey } = options; + + // Convert the provided private key to a byte array. + const privateKeyBytes = await Ed25519.privateKeyToBytes({ privateKey }); + + // Derive the public key from the private key. + const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes); + + // Construct the public key in JWK format. + const publicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : Convert.uint8Array(publicKeyBytes).toBase64Url() }; - return keyPair; + // Compute the JWK thumbprint and set as the key ID. + publicKey.kid = await Jose.jwkThumbprint({ key: publicKey }); + + return publicKey; } /** - * Computes the public key from a given private key. + * Generates an Ed25519 private key in JSON Web Key (JWK) format. * - * @param options - The options for the public key computation. - * @param options.privateKey - The 32-byte private key from which to compute the public key. - * @returns A Promise that resolves to the computed 32-byte public key as a Uint8Array. + * This method creates a new private key suitable for use with the Curve25519 elliptic curve in + * Twisted Edwards form. The key generation process involves using cryptographically secure + * random number generation to ensure the uniqueness and security of the key. The resulting + * private key adheres to the JWK format making it compatible with common cryptographic + * standards and easy to use in various cryptographic processes. + * + * The generated private key in JWK format includes the following components: + * - `kty`: Key Type, set to 'OKP' for Octet Key Pair. + * - `crv`: Curve Name, set to 'Ed25519'. + * - `d`: The private key component, base64url-encoded. + * - `x`: The derived public key, base64url-encoded. + * + * Example usage: + * + * ```ts + * const privateKey = await X25519.generateKey(); + * ``` + * + * @returns A Promise that resolves to the generated private key in JWK format. */ - public static async getPublicKey(options: { - privateKey: Uint8Array + public static async generateKey(): Promise { + // Generate a random private key. + const privateKeyBytes = ed25519.utils.randomPrivateKey(); + + // Convert private key from bytes to JWK format. + const privateKey = await Ed25519.bytesToPrivateKey({ privateKeyBytes }); + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method accepts a private key in JWK format and extracts its raw byte representation. + * + * This method accepts a public key in JWK format and converts it into its raw binary + * form. The conversion process involves decoding the 'd' parameter of the JWK + * from base64url format into a byte array. + * + * Example usage: + * + * ```ts + * const privateKey = { ... }; // An Ed25519 private key in JWK format + * const privateKeyBytes = await Ed25519.privateKeyToBytes({ privateKey }); + * ``` + * + * @param options - The options for the private key conversion. + * @param options.privateKey - The private key in JWK format. + * + * @returns A Promise that resolves to the private key as a Uint8Array. + */ + public static async privateKeyToBytes(options: { + privateKey: PrivateKeyJwk }): Promise { - let { privateKey } = options; + const { privateKey } = options; - // Compute public key. - const publicKey = ed25519.getPublicKey(privateKey); + // Verify the provided JWK represents a valid OKP private key. + if (!Jose.isOkpPrivateKeyJwk(privateKey)) { + throw new Error(`Ed25519: The provided key is not a valid OKP private key.`); + } - return publicKey; + // Decode the provided private key to bytes. + const privateKeyBytes = Convert.base64Url(privateKey.d).toUint8Array(); + + return privateKeyBytes; + } + + /** + * Converts a public key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method accepts a public key in JWK format and converts it into its raw binary form. + * The conversion process involves decoding the 'x' parameter of the JWK (which represent the + * x coordinate of the elliptic curve point) from base64url format into a byte array. + * + * Example usage: + * + * ```ts + * const publicKey = { ... }; // An Ed25519 public key in JWK format + * const publicKeyBytes = await Ed25519.publicKeyToBytes({ publicKey }); + * ``` + * + * @param options - The options for the public key conversion. + * @param options.publicKey - The public key in JWK format. + * + * @returns A Promise that resolves to the public key as a Uint8Array. + */ + public static async publicKeyToBytes(options: { + publicKey: PublicKeyJwk + }): Promise { + const { publicKey } = options; + + // Verify the provided JWK represents a valid OKP public key. + if (!Jose.isOkpPublicKeyJwk(publicKey)) { + throw new Error(`Ed25519: The provided key is not a valid OKP public key.`); + } + + // Decode the provided public key to bytes. + const publicKeyBytes = Convert.base64Url(publicKey.x).toUint8Array(); + + return publicKeyBytes; } /** - * Generates a RFC8032 EdDSA signature of given data with a given private key. + * Generates an RFC8032-compliant EdDSA signature of given data using an Ed25519 private key. + * + * This method signs the provided data with a specified private key using the EdDSA + * (Edwards-curve Digital Signature Algorithm) as defined in RFC8032. It + * involves converting the private key from JWK format to a byte array and then employing + * the Ed25519 algorithm to sign the data. The output is a digital signature in the form + * of a Uint8Array, uniquely corresponding to both the data and the private key used for + * signing. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); // Data to be signed + * const privateKey = { ... }; // A PrivateKeyJwk object representing an Ed25519 private key + * const signature = await Ed25519.sign({ + * data, + * key: privateKey + * }); + * ``` * * @param options - The options for the signing operation. - * @param options.key - The private key to use for signing. - * @param options.data - The data to sign. + * @param options.data - The data to sign, represented as a Uint8Array. + * @param options.key - The private key to use for signing, represented in JWK format. + * * @returns A Promise that resolves to the signature as a Uint8Array. */ public static async sign(options: { data: Uint8Array, - key: Uint8Array + key: PrivateKeyJwk }): Promise { const { key, data } = options; - // Signature operation. - const signature = ed25519.sign(data, key); + // Convert the private key from JWK format to bytes. + const privateKeyBytes = await Ed25519.privateKeyToBytes({ privateKey: key }); + + // Sign the provided data using the EdDSA algorithm. + const signature = ed25519.sign(data, privateKeyBytes); return signature; } /** - * Validates a given public key to ensure that it corresponds to a - * valid point on the Ed25519 elliptic curve. + * Verifies an RFC8032-compliant EdDSA signature against given data using an Ed25519 public key. + * + * This method validates a digital signature to ensure its authenticity and integrity. + * It uses the EdDSA (Edwards-curve Digital Signature Algorithm) as specified in RFC8032. + * The verification process involves converting the public key from JWK format to a raw + * byte array and using the Ed25519 algorithm to validate the signature against the provided data. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); // Data that was signed + * const publicKey = { ... }; // A PublicKeyJwk object representing an Ed25519 public key + * const signature = new Uint8Array([...]); // Signature to verify + * const isValid = await Ed25519.verify({ + * data, + * key: publicKey, + * signature + * }); + * console.log(isValid); // true if the signature is valid, false otherwise + * ``` + * + * @param options - The options for the verification operation. + * @param options.data - The data that was signed, represented as a Uint8Array. + * @param options.key - The public key in JWK format used for verification. + * @param options.signature - The signature to verify, represented as a Uint8Array. + * + * @returns A Promise that resolves to a boolean indicating whether the signature is valid. + */ + public static async verify(options: { + data: Uint8Array, + key: PublicKeyJwk, + signature: Uint8Array + }): Promise { + const { key, signature, data } = options; + + // Convert the public key from JWK format to bytes. + const publicKeyBytes = await Ed25519.publicKeyToBytes({ publicKey: key }); + + // Perform the verification of the signature. + const isValid = ed25519.verify(signature, data, publicKeyBytes); + + return isValid; + } + + /** + * Validates a given public key to confirm its mathematical correctness on the Edwards curve. * - * This method decodes the Edwards points from the key bytes and - * asserts their validity on the curve. If the points are not valid, - * the method returns false. If the points are valid, the method - * returns true. + * This method decodes the Edwards points from the key bytes and asserts their validity on the + * Curve25519 curve in Twisted Edwards form. If the points are not valid, the method returns + * false. If the points are valid, the method returns true. * - * Note: This method does not check whether the key corresponds to a - * known or authorized entity, or whether it has been compromised. - * It only checks the mathematical validity of the key. + * Note that this validation strictly pertains to the key's format and numerical validity; it does + * not assess whether the key corresponds to a known entity or its security status (e.g., whether + * it has been compromised). + * + * Example usage: + * + * ```ts + * const publicKey = new Uint8Array([...]); // A public key in byte format + * const isValid = await Secp256k1.validatePublicKey({ key: publicKey }); + * console.log(isValid); // true if the key is valid on the Edwards curve, false otherwise + * ``` + * + * @param options - The options for the public key validation. + * @param options.key - The public key to validate, represented as a Uint8Array. * - * @param options - The options for the key validation. - * @param options.key - The key to validate, represented as a Uint8Array. * @returns A Promise that resolves to a boolean indicating whether the key * corresponds to a valid point on the Edwards curve. */ - public static async validatePublicKey(options: { + private static async validatePublicKey(options: { key: Uint8Array }): Promise { const { key } = options; @@ -176,26 +509,4 @@ export class Ed25519 { return true; } - - /** - * Verifies a RFC8032 EdDSA signature of given data with a given public key. - * - * @param options - The options for the verification operation. - * @param options.key - The public key to use for verification. - * @param options.signature - The signature to verify. - * @param options.data - The data that was signed. - * @returns A Promise that resolves to a boolean indicating whether the signature is valid. - */ - public static async verify(options: { - data: Uint8Array, - key: Uint8Array, - signature: Uint8Array - }): Promise { - const { key, signature, data } = options; - - // Verify operation. - const isValid = ed25519.verify(signature, data, key); - - return isValid; - } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/pbkdf2.ts b/packages/crypto/src/crypto-primitives/pbkdf2.ts index d10e60cc6..fe7e15061 100644 --- a/packages/crypto/src/crypto-primitives/pbkdf2.ts +++ b/packages/crypto/src/crypto-primitives/pbkdf2.ts @@ -10,7 +10,69 @@ type DeriveKeyOptions = { length: number }; +/** + * The `Pbkdf2` class provides a secure way to derive cryptographic keys from a password + * using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm. This class + * supports both the Web Crypto API and Node.js Crypto module to offer flexibility in + * different JavaScript environments. + * + * The PBKDF2 algorithm is widely used for generating keys from passwords, as it applies + * a pseudorandom function to the input password along with a salt value and iterates the + * process multiple times to increase the key's resistance to brute-force attacks. + * + * This class offers a single static method `deriveKey` to perform key derivation. It + * automatically chooses between Web Crypto and Node.js Crypto based on the runtime + * environment's support. + * + * Usage Examples: + * + * ```ts + * const options = { + * hash: 'SHA-256', // The hash function to use ('SHA-256', 'SHA-384', 'SHA-512') + * password: new TextEncoder().encode('password'), // The password as a Uint8Array + * salt: new Uint8Array([...]), // The salt value + * iterations: 1000, // The number of iterations + * length: 256 // The length of the derived key in bits + * }; + * const derivedKey = await Pbkdf2.deriveKey(options); + * ``` + * + * @remarks + * This class relies on the availability of the Web Crypto API or Node.js Crypto module. + * It falls back to Node.js Crypto if Web Crypto is not supported in the environment. + */ export class Pbkdf2 { + /** + * Derives a cryptographic key from a password using the PBKDF2 algorithm. + * + * This method applies the PBKDF2 algorithm to the provided password along with + * a salt value and iterates the process a specified number of times. It uses + * a cryptographic hash function to enhance security and produce a key of the + * desired length. The method is capable of utilizing either the Web Crypto API + * or the Node.js Crypto module, depending on the environment's support. + * + * Example usage: + * + * ```ts + * const options = { + * hash: 'SHA-256', + * password: new TextEncoder().encode('password'), + * salt: new Uint8Array([...]), + * iterations: 1000, + * length: 256 + * }; + * const derivedKey = await Pbkdf2.deriveKey(options); + * + * @param options - The options for key derivation. + * @param options.hash - The hash function to use, such as 'SHA-256', 'SHA-384', or 'SHA-512'. + * @param options.password - The password from which to derive the key, represented as a Uint8Array. + * @param options.salt - The salt value to use in the derivation process, as a Uint8Array. + * @param options.iterations - The number of iterations to apply in the PBKDF2 algorithm. + * @param options.length - The desired length of the derived key in bits. + * + * @returns A Promise that resolves to the derived key as a Uint8Array. + * ``` + */ public static async deriveKey(options: DeriveKeyOptions): Promise { if (isWebCryptoSupported()) { return Pbkdf2.deriveKeyWithWebCrypto(options); diff --git a/packages/crypto/src/crypto-primitives/secp256k1.ts b/packages/crypto/src/crypto-primitives/secp256k1.ts index a958210bb..79ffd16c9 100644 --- a/packages/crypto/src/crypto-primitives/secp256k1.ts +++ b/packages/crypto/src/crypto-primitives/secp256k1.ts @@ -1,180 +1,440 @@ -import type { BytesKeyPair } from '../types/crypto-key.js'; - +import { Convert } from '@web5/common'; import { sha256 } from '@noble/hashes/sha256'; import { secp256k1 } from '@noble/curves/secp256k1'; import { numberToBytesBE } from '@noble/curves/abstract/utils'; -export type HashFunction = (data: Uint8Array) => Uint8Array; +import type { PrivateKeyJwk, PublicKeyJwk } from '../jose.js'; + +import { Jose } from '../jose.js'; /** - * The `Secp256k1` class provides an interface for generating secp256k1 key pairs, - * computing public keys from private keys, generating shaerd secrets, and - * signing and verifying messages. + * The `Secp256k1` class provides a comprehensive suite of utilities for working with + * the secp256k1 elliptic curve, commonly used in blockchain and cryptographic applications. + * This class includes methods for key generation, conversion, signing, verification, and + * Elliptic Curve Diffie-Hellman (ECDH) key agreement, all compliant with relevant cryptographic + * standards. * - * The class uses the '@noble/secp256k1' package for the cryptographic operations, - * and the '@noble/hashes/sha256' package for generating the hash digests needed - * for the signing and verification operations. + * The class supports conversions between raw byte formats and JSON Web Key (JWK) formats, + * making it versatile for various cryptographic tasks. It adheres to RFC6090 for ECDH and + * RFC6979 for ECDSA signing and verification, ensuring compatibility and security. * - * The methods of this class are all asynchronous and return Promises. They all use - * the Uint8Array type for keys, signatures, and data, providing a consistent - * interface for working with binary data. + * Key Features: + * - Key Generation: Generate secp256k1 private keys in JWK format. + * - Key Conversion: Transform keys between raw byte arrays and JWK formats. + * - Public Key Derivation: Derive public keys from private keys. + * - ECDH Shared Secret Computation: Securely derive shared secrets using private and public keys. + * - ECDSA Signing and Verification: Sign data and verify signatures with secp256k1 keys. + * - Key Validation: Validate the mathematical correctness of secp256k1 keys. * - * Example usage: + * The methods in this class are asynchronous, returning Promises to accommodate various + * JavaScript environments. + * + * Usage Examples: * * ```ts - * const keyPair = await Secp256k1.generateKeyPair(); - * const message = new TextEncoder().encode('Hello, world!'); + * // Key Generation + * const privateKey = await Secp256k1.generateKey(); + * + * // Public Key Derivation + * const publicKey = await Secp256k1.computePublicKey({ privateKey }); + * + * // ECDH Shared Secret Computation + * const sharedSecret = await Secp256k1.sharedSecret({ + * privateKeyA: privateKey, + * publicKeyB: anotherPublicKey + * }); + * + * // ECDSA Signing * const signature = await Secp256k1.sign({ - * algorithm: { hash: 'SHA-256' }, - * key: keyPair.privateKey, - * data: message + * data: new TextEncoder().encode('Message'), + * key: privateKey * }); + * + * // ECDSA Signature Verification * const isValid = await Secp256k1.verify({ - * algorithm: { hash: 'SHA-256' }, - * key: keyPair.publicKey, - * signature, - * data: message + * data: new TextEncoder().encode('Message'), + * key: publicKey, + * signature: signature + * }); + * + * // Key Conversion + * const publicKeyBytes = await Secp256k1.publicKeyToBytes({ publicKey }); + * const privateKeyBytes = await Secp256k1.privateKeyToBytes({ privateKey }); + * const compressedPublicKey = await Secp256k1.convertPublicKey({ + * publicKey: publicKeyBytes, + * compressedPublicKey: true * }); - * console.log(isValid); // true + * const uncompressedPublicKey = await Secp256k1.convertPublicKey({ + * publicKey: publicKeyBytes, + * compressedPublicKey: false + * }); + * + * // Key Validation + * const isPrivateKeyValid = await Secp256k1.validatePrivateKey({ key: privateKeyBytes }); + * const isPublicKeyValid = await Secp256k1.validatePublicKey({ key: publicKeyBytes }); * ``` */ export class Secp256k1 { /** - * A private static field containing a map of hash algorithm names to their - * corresponding hash functions. The map is used in the 'sign' and 'verify' - * methods to get the specified hash function. + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method takes a private key represented as a byte array (Uint8Array) and + * converts it into a JWK object. The conversion involves extracting the + * elliptic curve points (x and y coordinates) from the private key and encoding + * them into base64url format, alongside other JWK parameters. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'EC' for Elliptic Curve. + * - `crv`: Curve Name, set to 'secp256k1'. + * - `d`: The private key component, base64url-encoded. + * - `x`: The x-coordinate of the public key point, base64url-encoded. + * - `y`: The y-coordinate of the public key point, base64url-encoded. + * + * This method is useful for converting raw public keys into a standardized + * JSON format, facilitating their use in cryptographic operations and making + * them easy to share and store. + * + * Example usage: + * + * ```ts + * const privateKeyBytes = new Uint8Array([...]); // Replace with actual private key bytes + * const privateKey = await Secp256k1.bytesToPrivateKey({ privateKeyBytes }); + * ``` + * + * @param options - The options for the private key conversion. + * @param options.privateKeyBytes - The raw private key as a Uint8Array. + * + * @returns A Promise that resolves to the private key in JWK format. */ - private static hashAlgorithms: Record = { - 'SHA-256': sha256 - }; + public static async bytesToPrivateKey(options: { + privateKeyBytes: Uint8Array + }): Promise { + const { privateKeyBytes } = options; + + // Get the elliptic curve points (x and y coordinates) for the provided private key. + const points = await Secp256k1.getCurvePoints({ key: privateKeyBytes }); + + // Construct the private key in JWK format. + const privateKey: PrivateKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + d : Convert.uint8Array(privateKeyBytes).toBase64Url(), + x : Convert.uint8Array(points.x).toBase64Url(), + y : Convert.uint8Array(points.y).toBase64Url() + }; + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } /** - * Converts a public key between its compressed and uncompressed forms. + * Converts a raw public key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method accepts a public key in a byte array (Uint8Array) format and + * transforms it to a JWK object. It involves decoding the elliptic curve points + * (x and y coordinates) from the raw public key bytes and encoding them into + * base64url format, along with setting appropriate JWK parameters. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'EC' for Elliptic Curve. + * - `crv`: Curve Name, set to 'secp256k1'. + * - `x`: The x-coordinate of the public key point, base64url-encoded. + * - `y`: The y-coordinate of the public key point, base64url-encoded. * - * Given a public key, this method can either compress or decompress it - * depending on the provided `compressedPublicKey` option. The conversion - * process involves decoding the Weierstrass points from the key bytes - * and then returning the key in the desired format. + * This method is useful for converting raw public keys into a standardized + * JSON format, facilitating their use in cryptographic operations and making + * them easy to share and store. * - * This is useful in scenarios where space is a consideration or when - * interfacing with systems that expect a specific public key format. + * Example usage: + * + * ```ts + * const publicKeyBytes = new Uint8Array([...]); // Replace with actual public key bytes + * const publicKey = await Secp256k1.bytesToPublicKey({ publicKeyBytes }); + * ``` * * @param options - The options for the public key conversion. - * @param options.publicKey - The original public key, represented as a Uint8Array. - * @param options.compressedPublicKey - A boolean indicating whether the output - * should be in compressed form. If true, the - * method returns the compressed form of the - * provided public key. If false, it returns - * the uncompressed form. - * - * @returns A Promise that resolves to the converted public key as a Uint8Array. + * @param options.publicKeyBytes - The raw public key as a Uint8Array. + * + * @returns A Promise that resolves to the public key in JWK format. + */ + public static async bytesToPublicKey(options: { + publicKeyBytes: Uint8Array + }): Promise { + const { publicKeyBytes } = options; + + // Get the elliptic curve points (x and y coordinates) for the provided public key. + const points = await Secp256k1.getCurvePoints({ key: publicKeyBytes }); + + // Construct the public key in JWK format. + const publicKey: PublicKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + x : Convert.uint8Array(points.x).toBase64Url(), + y : Convert.uint8Array(points.y).toBase64Url() + }; + + // Compute the JWK thumbprint and set as the key ID. + publicKey.kid = await Jose.jwkThumbprint({ key: publicKey }); + + return publicKey; + } + + /** + * Converts a public key to its compressed form. + * + * This method takes a public key represented as a byte array and compresses it. Public key + * compression is a process that reduces the size of the public key by removing the y-coordinate, + * making it more efficient for storage and transmission. The compressed key retains the same + * level of security as the uncompressed key. + * + * Example usage: + * + * ```ts + * const uncompressedPublicKeyBytes = new Uint8Array([...]); // Replace with actual uncompressed public key bytes + * const compressedPublicKey = await Secp256k1.compressPublicKey({ + * publicKeyBytes: uncompressedPublicKeyBytes + * }); + * ``` + * + * @param options - The options for the public key compression. + * @param options.publicKeyBytes - The public key as a Uint8Array. + * + * @returns A Promise that resolves to the compressed public key as a Uint8Array. */ - public static async convertPublicKey(options: { - publicKey: Uint8Array, - compressedPublicKey: boolean + public static async compressPublicKey(options: { + publicKeyBytes: Uint8Array }): Promise { - let { publicKey, compressedPublicKey } = options; + let { publicKeyBytes } = options; - // Decode Weierstrass points from key bytes. - const point = secp256k1.ProjectivePoint.fromHex(publicKey); + // Decode Weierstrass points from the public key byte array. + const point = secp256k1.ProjectivePoint.fromHex(publicKeyBytes); - // Return either the compressed or uncompressed form of hte public key. - return point.toRawBytes(compressedPublicKey); + // Return the compressed form of the public key. + return point.toRawBytes(true); } /** - * Generates a secp256k1 key pair. + * Converts a public key to its uncompressed form. + * + * This method takes a compressed public key represented as a byte array and decompresses it. + * Public key decompression involves reconstructing the y-coordinate from the x-coordinate, + * resulting in the full public key. This method is used when the uncompressed key format is + * required for certain cryptographic operations or interoperability. + * + * Example usage: * - * @param options - Optional parameters for the key generation. - * @param options.compressedPublicKey - If true, generates a compressed public key. Defaults to true. - * @returns A Promise that resolves to an object containing the private and public keys as Uint8Array. + * ```ts + * const compressedPublicKeyBytes = new Uint8Array([...]); // Replace with actual compressed public key bytes + * const decompressedPublicKey = await Secp256k1.decompressPublicKey({ + * publicKeyBytes: compressedPublicKeyBytes + * }); + * ``` + * + * @param options - The options for the public key decompression. + * @param options.publicKeyBytes - The public key as a Uint8Array. + * + * @returns A Promise that resolves to the uncompressed public key as a Uint8Array. */ - public static async generateKeyPair(options?: { - compressedPublicKey?: boolean - }): Promise { - let { compressedPublicKey } = options ?? { }; + public static async decompressPublicKey(options: { + publicKeyBytes: Uint8Array + }): Promise { + let { publicKeyBytes } = options; - compressedPublicKey ??= true; // Default to compressed public key, matching the default of @noble/secp256k1. + // Decode Weierstrass points from the public key byte array. + const point = secp256k1.ProjectivePoint.fromHex(publicKeyBytes); - // Generate the private key and compute its public key. - const privateKey = secp256k1.utils.randomPrivateKey(); - const publicKey = secp256k1.getPublicKey(privateKey, compressedPublicKey); + // Return the uncompressed form of the public key. + return point.toRawBytes(false); + } - const keyPair = { - privateKey : privateKey, - publicKey : publicKey + /** + * Derives the public key in JWK format from a given private key. + * + * This method takes a private key in JWK format and derives its corresponding public key, + * also in JWK format. The derivation process involves converting the private key to a raw + * byte array, then computing the elliptic curve points (x and y coordinates) from this private + * key. These coordinates are then encoded into base64url format to construct the public key in + * JWK format. + * + * The process ensures that the derived public key correctly corresponds to the given private key, + * adhering to the secp256k1 elliptic curve standards. This method is useful in cryptographic + * operations where a public key is needed for operations like signature verification, but only + * the private key is available. + * + * Example usage: + * + * ```ts + * const privateKeyJwk = { ... }; // A PrivateKeyJwk object representing a secp256k1 private key + * const publicKeyJwk = await Secp256k1.computePublicKey({ privateKey: privateKeyJwk }); + * ``` + * + * @param options - The options for the public key derivation. + * @param options.privateKey - The private key in JWK format from which to derive the public key. + * + * @returns A Promise that resolves to the derived public key in JWK format. + */ + public static async computePublicKey(options: { + privateKey: PrivateKeyJwk + }): Promise { + const { privateKey } = options; + + // Convert the provided private key to a byte array. + const privateKeyBytes = await Secp256k1.privateKeyToBytes({ privateKey }); + + // Get the elliptic curve points (x and y coordinates) for the provided private key. + const points = await Secp256k1.getCurvePoints({ key: privateKeyBytes }); + + // Construct the public key in JWK format. + const publicKey: PublicKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + x : Convert.uint8Array(points.x).toBase64Url(), + y : Convert.uint8Array(points.y).toBase64Url() }; - return keyPair; + // Compute the JWK thumbprint and set as the key ID. + publicKey.kid = await Jose.jwkThumbprint({ key: publicKey }); + + return publicKey; } /** - * Returns the elliptic curve points (x and y coordinates) for a given secp256k1 key. + * Generates a secp256k1 private key in JSON Web Key (JWK) format. * - * In the case of a private key, the public key is first computed from the private key, - * then the x and y coordinates of the public key point on the elliptic curve are returned. + * This method creates a new private key suitable for use with the secp256k1 + * elliptic curve. The key is generated using cryptographically secure random + * number generation to ensure its uniqueness and security. The resulting + * private key adheres to the JWK format, specifically tailored for secp256k1, + * making it compatible with common cryptographic standards and easy to use in + * various cryptographic processes. * - * In the case of a public key, the x and y coordinates of the key point on the elliptic - * curve are returned directly. + * The private key generated by this method includes the following components: + * - `kty`: Key Type, set to 'EC' for Elliptic Curve. + * - `crv`: Curve Name, set to 'secp256k1'. + * - `d`: The private key component, base64url-encoded. + * - `x`: The x-coordinate of the public key point, derived from the private key, base64url-encoded. + * - `y`: The y-coordinate of the public key point, derived from the private key, base64url-encoded. * - * The returned coordinates can be used to perform various operations on the elliptic curve, - * such as addition and multiplication of points, which can be used in various cryptographic - * schemes and protocols. + * The key is returned in a format suitable for direct use in signin and key agreement operations. * - * @param options - The options for the operation. - * @param options.key - The key for which to get the elliptic curve points. - * Can be either a private key or a public key. - * The key should be passed as a Uint8Array. - * @returns A Promise that resolves to an object with properties 'x' and 'y', - * each being a Uint8Array representing the x and y coordinates of the key point on the elliptic curve. + * Example usage: + * + * ```ts + * const privateKey = await Secp256k1.generateKey(); + * ``` + * + * @returns A Promise that resolves to the generated private key in JWK format. */ - public static async getCurvePoints(options: { - key: Uint8Array - }): Promise<{ x: Uint8Array, y: Uint8Array }> { - let { key } = options; + public static async generateKey(): Promise { + // Generate a random private key. + const privateKeyBytes = secp256k1.utils.randomPrivateKey(); - // If key is a private key, first compute the public key. - if (key.byteLength === 32) { - key = await Secp256k1.getPublicKey({ privateKey: key }); - } + // Convert private key from bytes to JWK format. + const privateKey = await Secp256k1.bytesToPrivateKey({ privateKeyBytes }); - // Decode Weierstrass points from key bytes. - const point = secp256k1.ProjectivePoint.fromHex(key); + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); - // Get x- and y-coordinate values and convert to Uint8Array. - const x = numberToBytesBE(point.x, 32); - const y = numberToBytesBE(point.y, 32); + return privateKey; + } - return { x, y }; + /** + * Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method takes a private key in JWK format and extracts its raw byte representation. + * It specifically focuses on the 'd' parameter of the JWK, which represents the private + * key component in base64url encoding. The method decodes this value into a byte array. + * + * This conversion is essential for operations that require the private key in its raw + * binary form, such as certain low-level cryptographic operations or when interfacing + * with systems and libraries that expect keys in a byte array format. + * + * Example usage: + * + * ```ts + * const privateKey = { ... }; // An X25519 private key in JWK format + * const privateKeyBytes = await Secp256k1.privateKeyToBytes({ privateKey }); + * ``` + * + * @param options - The options for the private key conversion. + * @param options.privateKey - The private key in JWK format. + * + * @returns A Promise that resolves to the private key as a Uint8Array. + */ + public static async privateKeyToBytes(options: { + privateKey: PrivateKeyJwk + }): Promise { + const { privateKey } = options; + + // Verify the provided JWK represents a valid EC secp256k1 private key. + if (!Jose.isEcPrivateKeyJwk(privateKey)) { + throw new Error(`Secp256k1: The provided key is not a valid EC private key.`); + } + + // Decode the provided private key to bytes. + const privateKeyBytes = Convert.base64Url(privateKey.d).toUint8Array(); + + return privateKeyBytes; } /** - * Computes the public key from a given private key. - * If compressedPublicKey=true then the output is a 33-byte public key. - * If compressedPublicKey=false then the output is a 65-byte public key. - * - * @param options - The options for the public key computation. - * @param options.privateKey - The 32-byte private key from which to compute the public key. - * @param options.compressedPublicKey - If true, returns a compressed public key. Defaults to true. - * @returns A Promise that resolves to the computed public key as a Uint8Array. + * Converts a public key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method accepts a public key in JWK format and converts it into its raw binary + * form. The conversion process involves decoding the 'x' and 'y' parameters of the JWK + * (which represent the x and y coordinates of the elliptic curve point, respectively) + * from base64url format into a byte array. The method then concatenates these values, + * along with a prefix indicating the key format, to form the full public key. + * + * This function is particularly useful for use cases where the public key is needed + * in its raw byte format, such as for certain cryptographic operations or when + * interfacing with systems that require raw key formats. + * + * Example usage: + * + * ```ts + * const publicKey = { ... }; // A PublicKeyJwk object + * const publicKeyBytes = await Secp256k1.publicKeyToBytes({ publicKey }); + * ``` + * + * @param options - The options for the public key conversion. + * @param options.publicKey - The public key in JWK format. + * + * @returns A Promise that resolves to the public key as a Uint8Array. */ - public static async getPublicKey(options: { - privateKey: Uint8Array, - compressedPublicKey?: boolean + public static async publicKeyToBytes(options: { + publicKey: PublicKeyJwk }): Promise { - let { privateKey, compressedPublicKey } = options; + const { publicKey } = options; + + // Verify the provided JWK represents a valid EC secp256k1 public key, which must have a 'y' value. + if (!(Jose.isEcPublicKeyJwk(publicKey) && publicKey.y)) { + throw new Error(`Secp256k1: The provided key is not a valid EC public key.`); + } - compressedPublicKey ??= true; // Default to compressed public key, matching the default of @noble/secp256k1. + // Decode the provided public key to bytes. + const prefix = new Uint8Array([0x04]); // Designates an uncompressed key. + const x = Convert.base64Url(publicKey.x).toUint8Array(); + const y = Convert.base64Url(publicKey.y).toUint8Array(); - // Compute public key. - const publicKey = secp256k1.getPublicKey(privateKey, compressedPublicKey); + // Concatenate the prefix, x-coordinate, and y-coordinate as a single byte array. + const publicKeyBytes = new Uint8Array([...prefix, ...x, ...y]); - return publicKey; + return publicKeyBytes; } /** - * Generates a RFC6090 ECDH shared secret given the private key of one party - * and the public key another party. + * Computes an RFC6090-compliant Elliptic Curve Diffie-Hellman (ECDH) shared secret + * using secp256k1 private and public keys in JSON Web Key (JWK) format. + * + * This method facilitates the ECDH key agreement protocol, which is a method of securely + * deriving a shared secret between two parties based on their private and public keys. + * It takes the private key of one party (privateKeyA) and the public key of another + * party (publicKeyB) to compute a shared secret. The shared secret is derived from the + * x-coordinate of the elliptic curve point resulting from the multiplication of the + * public key with the private key. * * Note: When performing Elliptic Curve Diffie-Hellman (ECDH) key agreement, * the resulting shared secret is a point on the elliptic curve, which @@ -183,18 +443,43 @@ export class Secp256k1 { * in the ECDH process, it's standard practice to use only the x-coordinate * of the shared secret point as the resulting shared key. This is because * the y-coordinate does not add to the entropy of the key, and both parties - * can independently compute the x-coordinate, so using just the x-coordinate - * simplifies matters. + * can independently compute the x-coordinate. Consquently, this implementation + * omits the y-coordinate for simplicity and standard compliance. + * + * Example usage: + * + * ```ts + * const privateKeyA = { ... }; // A PrivateKeyJwk object for party A + * const publicKeyB = { ... }; // A PublicKeyJwk object for party B + * const sharedSecret = await Secp256k1.sharedSecret({ + * privateKeyA, + * publicKeyB + * }); + * ``` + * + * @param options - The options for the shared secret computation operation. + * @param options.privateKeyA - The private key in JWK format of one party. + * @param options.publicKeyB - The public key in JWK format of the other party. + * + * @returns A Promise that resolves to the computed shared secret as a Uint8Array. */ public static async sharedSecret(options: { - compressedSecret?: boolean, - privateKey: Uint8Array, - publicKey: Uint8Array + privateKeyA: PrivateKeyJwk, + publicKeyB: PublicKeyJwk }): Promise { - let { privateKey, publicKey } = options; + let { privateKeyA, publicKeyB } = options; + + // Ensure that keys from the same key pair are not specified. + if ('x' in privateKeyA && 'x' in publicKeyB && privateKeyA.x === publicKeyB.x) { + throw new Error(`Secp256k1: ECDH shared secret cannot be computed from a single key pair's public and private keys.`); + } + + // Convert the provided private and public keys to bytes. + const privateKeyABytes = await Secp256k1.privateKeyToBytes({ privateKey: privateKeyA }); + const publicKeyBBytes = await Secp256k1.publicKeyToBytes({ publicKey: publicKeyB }); - // Compute the shared secret between the public and private keys. - const sharedSecret = secp256k1.getSharedSecret(privateKey, publicKey); + // Compute the compact representation shared secret between the public and private keys. + const sharedSecret = secp256k1.getSharedSecret(privateKeyABytes, publicKeyBBytes, true); // Remove the leading byte that indicates the sign of the y-coordinate // of the point on the elliptic curve. See note above. @@ -202,49 +487,196 @@ export class Secp256k1 { } /** - * Generates a RFC6979 ECDSA signature of given data with a given private key and hash algorithm. + * Generates an RFC6979-compliant ECDSA signature of given data using a secp256k1 private key. + * + * This method signs the provided data with a specified private key using the ECDSA + * (Elliptic Curve Digital Signature Algorithm) signature algorithm, as defined in RFC6979. + * The data to be signed is first hashed using the SHA-256 algorithm, and this hash is then + * signed using the private key. The output is a digital signature in the form of a + * Uint8Array, which uniquely corresponds to both the data and the private key used for signing. + * + * This method is commonly used in cryptographic applications to ensure data integrity and + * authenticity. The signature can later be verified by parties with access to the corresponding + * public key, ensuring that the data has not been tampered with and was indeed signed by the + * holder of the private key. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); // Data to be signed + * const privateKey = { ... }; // A PrivateKeyJwk object representing a secp256k1 private key + * const signature = await Secp256k1.sign({ + * data, + * key: privateKey + * }); + * ``` * * @param options - The options for the signing operation. - * @param options.data - The data to sign. - * @param options.hash - The hash algorithm to use to generate a digest of the data. - * @param options.key - The private key to use for signing. + * @param options.data - The data to sign, represented as a Uint8Array. + * @param options.key - The private key to use for signing, represented in JWK format. + * * @returns A Promise that resolves to the signature as a Uint8Array. */ public static async sign(options: { data: Uint8Array, - hash: string, - key: Uint8Array + key: PrivateKeyJwk }): Promise { - const { data, hash, key } = options; + const { data, key } = options; + + // Convert the private key from JWK format to bytes. + const privateKeyBytes = await Secp256k1.privateKeyToBytes({ privateKey: key }); - // Generate a digest of the data using the specified hash function. - const hashFunction = this.hashAlgorithms[hash]; - const digest = hashFunction(data); + // Generate a digest of the data using the SHA-256 hash function. + const digest = sha256(data); - // Signature operation returns a Signature instance with { r, s, recovery } properties. - const signatureObject = secp256k1.sign(digest, key); + // Sign the provided data using the ECDSA algorithm. + // The `secp256k1.sign` operation returns a signature object with { r, s, recovery } properties. + const signatureObject = secp256k1.sign(digest, privateKeyBytes); - // Convert Signature object to Uint8Array. + // Convert the signature object to Uint8Array. const signature = signatureObject.toCompactRawBytes(); return signature; } /** - * Validates a given private key to ensure that it's a valid 32-byte number - * that is less than the secp256k1 curve's order. + * Verifies an RFC6979-compliant ECDSA signature against given data and a secp256k1 public key. * - * This method checks the byte length of the key and its numerical validity - * according to the secp256k1 curve's parameters. It doesn't verify whether - * the key corresponds to a known or authorized entity or whether it has - * been compromised. + * This method validates a digital signature to ensure that it was generated by the holder of the + * corresponding private key and that the signed data has not been altered. The signature + * verification is performed using the ECDSA (Elliptic Curve Digital Signature Algorithm) as + * specified in RFC6979. The data to be verified is first hashed using the SHA-256 algorithm, and + * this hash is then used along with the public key to verify the signature. + * + * The method returns a boolean value indicating whether the signature is valid. A valid signature + * proves that the signed data was indeed signed by the owner of the private key corresponding to + * the provided public key and that the data has not been tampered with since it was signed. + * + * Note: The verification process does not consider the malleability of low-s signatures, which + * may be relevant in certain contexts, such as Bitcoin transactions. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); // Data that was signed + * const publicKey = { ... }; // Public key in JWK format corresponding to the private key that signed the data + * const signature = new Uint8Array([...]); // Signature to verify + * const isSignatureValid = await Secp256k1.verify({ + * data, + * key: publicKey, + * signature + * }); + * console.log(isSignatureValid); // true if the signature is valid, false otherwise + * ``` + * + * @param options - The options for the verification operation. + * @param options.data - The data that was signed, represented as a Uint8Array. + * @param options.key - The public key used for verification, represented in JWK format. + * @param options.signature - The signature to verify, represented as a Uint8Array. + * + * @returns A Promise that resolves to a boolean indicating whether the signature is valid. + */ + public static async verify(options: { + data: Uint8Array, + key: PublicKeyJwk, + signature: Uint8Array + }): Promise { + const { data, key, signature } = options; + + // Convert the public key from JWK format to bytes. + const publicKeyBytes = await Secp256k1.publicKeyToBytes({ publicKey: key }); + + // Generate a digest of the data using the SHA-256 hash function. + const digest = sha256(data); + + /** Perform the verification of the signature. + * This verify operation has the malleability check disabled. Guaranteed support + * for low-s signatures across languages is unlikely especially in the context + * of SSI. Notable Cloud KMS providers do not natively support it either. It is + * also worth noting that low-s signatures are a requirement for Bitcoin. */ + const isValid = secp256k1.verify(signature, digest, publicKeyBytes, { lowS: false }); + + return isValid; + } + + /** + * Returns the elliptic curve points (x and y coordinates) for a given secp256k1 key. + * + * This method extracts the elliptic curve points from a given secp256k1 key, whether + * it's a private or a public key. For a private key, the method first computes the + * corresponding public key and then extracts the x and y coordinates. For a public key, + * it directly returns these coordinates. The coordinates are represented as Uint8Array. + * + * The x and y coordinates represent the key's position on the elliptic curve and can be + * used in various cryptographic operations, such as digital signatures or key agreement + * protocols. + * + * Example usage: + * + * ```ts + * // For a private key + * const privateKey = new Uint8Array([...]); // A 32-byte private key + * const { x: xFromPrivateKey, y: yFromPrivateKey } = await Secp256k1.getCurvePoints({ key: privateKey }); + * + * // For a public key + * const publicKey = new Uint8Array([...]); // A 33-byte or 65-byte public key + * const { x: xFromPublicKey, y: yFromPublicKey } = await Secp256k1.getCurvePoints({ key: publicKey }); + * ``` + * + * @param options - The options for the operation. + * @param options.key - The key for which to get the elliptic curve points. + * Can be either a private key or a public key. + * The key should be passed as a Uint8Array. + * + * @returns A Promise that resolves to an object with properties 'x' and 'y', + * each being a Uint8Array representing the x and y coordinates of the key point on the + * elliptic curve. + */ + private static async getCurvePoints(options: { + key: Uint8Array + }): Promise<{ x: Uint8Array, y: Uint8Array }> { + let { key } = options; + + // If key is a private key, first compute the public key. + if (key.byteLength === 32) { + key = secp256k1.getPublicKey(key); + } + + // Decode Weierstrass points from key bytes. + const point = secp256k1.ProjectivePoint.fromHex(key); + + // Get x- and y-coordinate values and convert to Uint8Array. + const x = numberToBytesBE(point.x, 32); + const y = numberToBytesBE(point.y, 32); + + return { x, y }; + } + + /** + * Validates a given private key to ensure its compliance with the secp256k1 curve standards. + * + * This method checks whether a provided private key is a valid 32-byte number and falls within + * the range defined by the secp256k1 curve's order. It is essential for ensuring the private + * key's mathematical correctness in the context of secp256k1-based cryptographic operations. + * + * Note that this validation strictly pertains to the key's format and numerical validity; it does + * not assess whether the key corresponds to a known entity or its security status (e.g., whether + * it has been compromised). + * + * Example usage: + * + * ```ts + * const privateKey = new Uint8Array([...]); // A 32-byte private key + * const isValid = await Secp256k1.validatePrivateKey({ key: privateKey }); + * console.log(isValid); // true or false based on the key's validity + * ``` * * @param options - The options for the key validation. * @param options.key - The private key to validate, represented as a Uint8Array. - * @returns A Promise that resolves to a boolean indicating whether the private - * key is a valid 32-byte number less than the secp256k1 curve's order. + * + * @returns A Promise that resolves to a boolean indicating whether the private key is valid. */ - public static async validatePrivateKey(options: { + private static async validatePrivateKey(options: { key: Uint8Array }): Promise { const { key } = options; @@ -253,24 +685,32 @@ export class Secp256k1 { } /** - * Validates a given public key to ensure that it corresponds to a - * valid point on the secp256k1 elliptic curve. + * Validates a given public key to confirm its mathematical correctness on the secp256k1 curve. * - * This method decodes the Weierstrass points from the key bytes and - * asserts their validity on the curve. If the points are not valid, - * the method returns false. If the points are valid, the method - * returns true. + * This method checks if the provided public key represents a valid point on the secp256k1 curve. + * It decodes the key's Weierstrass points (x and y coordinates) and verifies their validity + * against the curve's parameters. A valid point must lie on the curve and meet specific + * mathematical criteria defined by the curve's equation. * - * Note: This method does not check whether the key corresponds to a - * known or authorized entity, or whether it has been compromised. - * It only checks the mathematical validity of the key. + * It's important to note that this method does not verify the key's ownership or whether it has + * been compromised; it solely focuses on the key's adherence to the curve's mathematical + * principles. * - * @param options - The options for the key validation. - * @param options.key - The key to validate, represented as a Uint8Array. - * @returns A Promise that resolves to a boolean indicating whether the key - * corresponds to a valid point on the secp256k1 elliptic curve. + * Example usage: + * + * ```ts + * const publicKey = new Uint8Array([...]); // A public key in byte format + * const isValid = await Secp256k1.validatePublicKey({ key: publicKey }); + * console.log(isValid); // true if the key is valid on the secp256k1 curve, false otherwise + * ``` + * + * @param options - The options for the public key validation. + * @param options.key - The public key to validate, represented as a Uint8Array. + * + * @returns A Promise that resolves to a boolean indicating the public key's validity on + * the secp256k1 curve. */ - public static async validatePublicKey(options: { + private static async validatePublicKey(options: { key: Uint8Array }): Promise { const { key } = options; @@ -288,35 +728,4 @@ export class Secp256k1 { return true; } - - /** - * Verifies a RFC6979 ECDSA signature of given data with a given public key and hash algorithm. - * - * @param options - The options for the verification operation. - * @param options.data - The data that was signed. - * @param options.hash - The hash algorithm to use to generate a digest of the data. - * @param options.key - The public key to use for verification. - * @param options.signature - The signature to verify. - * @returns A Promise that resolves to a boolean indicating whether the signature is valid. - */ - public static async verify(options: { - data: Uint8Array, - hash: string, - key: Uint8Array, - signature: Uint8Array - }): Promise { - const { data, hash, key, signature } = options; - - // Generate a digest of the data using the specified hash function. - const hashFunction = this.hashAlgorithms[hash]; - const digest = hashFunction(data); - - // Verify operation with malleability check disabled. Guaranteed support for low-s - // signatures across languages is unlikely especially in the context of SSI. - // Notable Cloud KMS providers do not natively support it either. - // low-s signatures are a requirement for Bitcoin - const isValid = secp256k1.verify(signature, digest, key, { lowS: false }); - - return isValid; - } -} +} \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/x25519.ts b/packages/crypto/src/crypto-primitives/x25519.ts index 522b36bcd..63f20c32d 100644 --- a/packages/crypto/src/crypto-primitives/x25519.ts +++ b/packages/crypto/src/crypto-primitives/x25519.ts @@ -1,81 +1,369 @@ -import type { BytesKeyPair } from '../types/crypto-key.js'; - +import { Convert } from '@web5/common'; import { x25519 } from '@noble/curves/ed25519'; +import type { PrivateKeyJwk, PublicKeyJwk } from '../jose.js'; + +import { Jose } from '../jose.js'; + /** - * The `X25519` class provides an interface for X25519 (Curve25519) key pair - * generation, public key computation, and shared secret computation. The class - * uses the '@noble/curves/ed25519' package for the cryptographic operations. + * The `X25519` class provides a comprehensive suite of utilities for working with the X25519 + * elliptic curve, widely used for key agreement protocols and cryptographic applications. It + * provides methods for key generation, conversion, and Elliptic Curve Diffie-Hellman (ECDH) + * key agreement, all aligned with standard cryptographic practices. * - * All methods of this class are asynchronous and return Promises. They all use - * the Uint8Array type for keys and data, providing a consistent - * interface for working with binary data. + * The class supports conversions between raw byte formats and JSON Web Key (JWK) formats, + * making it versatile for various cryptographic tasks. It adheres to RFC6090 for ECDH, ensuring + * secure and effective handling of keys and cryptographic operations. * - * Example usage: + * Key Features: + * - Key Generation: Generate X25519 private keys in JWK format. + * - Key Conversion: Transform keys between raw byte arrays and JWK formats. + * - Public Key Derivation: Derive public keys from private keys. + * - ECDH Shared Secret Computation: Securely derive shared secrets using private and public keys. + * + * The methods in this class are asynchronous, returning Promises to accommodate various + * JavaScript environments. + * + * Usage Examples: * * ```ts - * const ownKeyPair = await X25519.generateKeyPair(); - * const otherPartyKeyPair = await X25519.generateKeyPair(); + * // Key Generation + * const privateKey = await X25519.generateKey(); + * + * // Public Key Derivation + * const publicKey = await X25519.computePublicKey({ privateKey }); + * + * // ECDH Shared Secret Computation * const sharedSecret = await X25519.sharedSecret({ - * privateKey : ownKeyPair.privateKey, - * publicKey : otherPartyKeyPair.publicKey + * privateKeyA: privateKey, + * publicKeyB: anotherPublicKey * }); + * + * // Key Conversion + * const publicKeyBytes = await X25519.publicKeyToBytes({ publicKey }); + * const privateKeyBytes = await X25519.privateKeyToBytes({ privateKey }); * ``` */ export class X25519 { /** - * Generates a key pair for X25519 (private and public key). + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method accepts a private key as a byte array (Uint8Array) for the X25519 elliptic curve + * and transforms it into a JWK object. The process involves first deriving the public key from + * the private key, then encoding both the private and public keys into base64url format. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'OKP' for Octet Key Pair. + * - `crv`: Curve Name, set to 'X25519'. + * - `d`: The private key component, base64url-encoded. + * - `x`: The derived public key, base64url-encoded. + * + * This method is useful for converting raw public keys into a standardized + * JSON format, facilitating their use in cryptographic operations and making + * them easy to share and store. + * + * Example usage: + * + * ```ts + * const privateKeyBytes = new Uint8Array([...]); // Replace with actual private key bytes + * const privateKey = await X25519.bytesToPrivateKey({ privateKeyBytes }); + * ``` + * + * @param options - The options for the private key conversion. + * @param options.privateKeyBytes - The raw private key as a Uint8Array. + * + * @returns A Promise that resolves to the private key in JWK format. + */ + public static async bytesToPrivateKey(options: { + privateKeyBytes: Uint8Array + }): Promise { + const { privateKeyBytes } = options; + + // Derive the public key from the private key. + const publicKeyBytes = x25519.getPublicKey(privateKeyBytes); + + // Construct the private key in JWK format. + const privateKey: PrivateKeyJwk = { + kty : 'OKP', + crv : 'X25519', + d : Convert.uint8Array(privateKeyBytes).toBase64Url(), + x : Convert.uint8Array(publicKeyBytes).toBase64Url(), + }; + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a raw public key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method accepts a public key as a byte array (Uint8Array) for the X25519 elliptic curve + * and transforms it into a JWK object. The conversion process involves encoding the public + * key bytes into base64url format. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'OKP' for Octet Key Pair. + * - `crv`: Curve Name, set to 'X25519'. + * - `x`: The public key, base64url-encoded. + * + * This method is useful for converting raw public keys into a standardized + * JSON format, facilitating their use in cryptographic operations and making + * them easy to share and store. + * + * Example usage: + * + * ```ts + * const publicKeyBytes = new Uint8Array([...]); // Replace with actual public key bytes + * const publicKey = await X25519.bytesToPublicKey({ publicKeyBytes }); + * ``` * - * @returns A Promise that resolves to a BytesKeyPair object. + * @param options - The options for the public key conversion. + * @param options.publicKeyBytes - The raw public key as a Uint8Array. + * + * @returns A Promise that resolves to the public key in JWK format. */ - public static async generateKeyPair(): Promise { - // Generate the private key and compute its public key. - const privateKey = x25519.utils.randomPrivateKey(); - const publicKey = x25519.getPublicKey(privateKey); - - const keyPair = { - privateKey : privateKey, - publicKey : publicKey + public static async bytesToPublicKey(options: { + publicKeyBytes: Uint8Array + }): Promise { + const { publicKeyBytes } = options; + + // Construct the public key in JWK format. + const publicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'X25519', + x : Convert.uint8Array(publicKeyBytes).toBase64Url(), }; - return keyPair; + // Compute the JWK thumbprint and set as the key ID. + publicKey.kid = await Jose.jwkThumbprint({ key: publicKey }); + + return publicKey; } /** - * Computes a public key given a private key. + * Derives the public key in JWK format from a given X25519 private key. + * + * This method takes a private key in JWK format and derives its corresponding public key, + * also in JWK format. The derivation process involves converting the private key to a + * raw byte array and then computing the corresponding public key on the Curve25519 curve. + * The public key is then encoded into base64url format to construct a JWK representation. + * + * The process ensures that the derived public key correctly corresponds to the given private key, + * adhering to the Curve25519 elliptic curve in Twisted Edwards form standards. This method is + * useful in cryptographic operations where a public key is needed for operations like signature + * verification, but only the private key is available. * - * @param options - The options for the public key computation operation. - * @param options.privateKey - The private key used to compute the public key. - * @returns A Promise that resolves to the computed public key as a Uint8Array. + * Example usage: + * + * ```ts + * const privateKey = { ... }; // A PrivateKeyJwk object representing an X25519 private key + * const publicKey = await X25519.computePublicKey({ privateKey }); + * ``` + * + * @param options - The options for the public key derivation. + * @param options.privateKey - The private key in JWK format from which to derive the public key. + * + * @returns A Promise that resolves to the derived public key in JWK format. */ - public static async getPublicKey(options: { - privateKey: Uint8Array - }): Promise { + public static async computePublicKey(options: { + privateKey: PrivateKeyJwk + }): Promise { let { privateKey } = options; - // Compute public key. - const publicKey = x25519.getPublicKey(privateKey); + // Convert the provided private key to a byte array. + const privateKeyBytes = await X25519.privateKeyToBytes({ privateKey }); + + // Derive the public key from the private key. + const publicKeyBytes = x25519.getPublicKey(privateKeyBytes); + + // Construct the public key in JWK format. + const publicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'X25519', + x : Convert.uint8Array(publicKeyBytes).toBase64Url() + }; + + // Compute the JWK thumbprint and set as the key ID. + publicKey.kid = await Jose.jwkThumbprint({ key: publicKey }); return publicKey; } /** - * Generates a RFC6090 ECDH shared secret given the private key of one party - * and the public key of another party. + * Generates an X25519 private key in JSON Web Key (JWK) format. + * + * This method creates a new private key suitable for use with the X25519 elliptic curve. + * The key generation process involves using cryptographically secure random number generation + * to ensure the uniqueness and security of the key. The resulting private key adheres to the + * JWK format making it compatible with common cryptographic standards and easy to use in various + * cryptographic processes. + * + * The generated private key in JWK format includes the following components: + * - `kty`: Key Type, set to 'OKP' for Octet Key Pair. + * - `crv`: Curve Name, set to 'X25519'. + * - `d`: The private key component, base64url-encoded. + * - `x`: The derived public key, base64url-encoded. + * + * The key is returned in a format suitable for direct use in key agreement operations. + * + * Example usage: + * + * ```ts + * const privateKey = await X25519.generateKey(); + * ``` + * + * @returns A Promise that resolves to the generated private key in JWK format. + */ + public static async generateKey(): Promise { + // Generate a random private key. + const privateKeyBytes = x25519.utils.randomPrivateKey(); + + // Convert private key from bytes to JWK format. + const privateKey = await X25519.bytesToPrivateKey({ privateKeyBytes }); + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method accepts a private key in JWK format and extracts its raw byte representation. + * + * This method accepts a public key in JWK format and converts it into its raw binary + * form. The conversion process involves decoding the 'd' parameter of the JWK + * from base64url format into a byte array. + * + * This conversion is essential for operations that require the private key in its raw + * binary form, such as certain low-level cryptographic operations or when interfacing + * with systems and libraries that expect keys in a byte array format. + * + * Example usage: + * + * ```ts + * const privateKey = { ... }; // An X25519 private key in JWK format + * const privateKeyBytes = await X25519.privateKeyToBytes({ privateKey }); + * ``` + * + * @param options - The options for the private key conversion. + * @param options.privateKey - The private key in JWK format. + * + * @returns A Promise that resolves to the private key as a Uint8Array. + */ + public static async privateKeyToBytes(options: { + privateKey: PrivateKeyJwk + }): Promise { + const { privateKey } = options; + + // Verify the provided JWK represents a valid OKP private key. + if (!Jose.isOkpPrivateKeyJwk(privateKey)) { + throw new Error(`X25519: The provided key is not a valid OKP private key.`); + } + + // Decode the provided private key to bytes. + const privateKeyBytes = Convert.base64Url(privateKey.d).toUint8Array(); + + return privateKeyBytes; + } + + /** + * Converts a public key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method accepts a public key in JWK format and converts it into its raw binary form. + * The conversion process involves decoding the 'x' parameter of the JWK (which represent the + * x coordinate of the elliptic curve point) from base64url format into a byte array. + * + * This conversion is essential for operations that require the public key in its raw + * binary form, such as certain low-level cryptographic operations or when interfacing + * with systems and libraries that expect keys in a byte array format. + * + * Example usage: + * + * ```ts + * const publicKey = { ... }; // An X25519 public key in JWK format + * const publicKeyBytes = await X25519.publicKeyToBytes({ publicKey }); + * ``` + * + * @param options - The options for the public key conversion. + * @param options.publicKey - The public key in JWK format. + * + * @returns A Promise that resolves to the public key as a Uint8Array. + */ + public static async publicKeyToBytes(options: { + publicKey: PublicKeyJwk + }): Promise { + const { publicKey } = options; + + // Verify the provided JWK represents a valid OKP public key. + if (!Jose.isOkpPublicKeyJwk(publicKey)) { + throw new Error(`X25519: The provided key is not a valid OKP public key.`); + } + + // Decode the provided public key to bytes. + const publicKeyBytes = Convert.base64Url(publicKey.x).toUint8Array(); + + return publicKeyBytes; + } + + /** + * Computes an RFC6090-compliant Elliptic Curve Diffie-Hellman (ECDH) shared secret + * using secp256k1 private and public keys in JSON Web Key (JWK) format. + * + * This method facilitates the ECDH key agreement protocol, which is a method of securely + * deriving a shared secret between two parties based on their private and public keys. + * It takes the private key of one party (privateKeyA) and the public key of another + * party (publicKeyB) to compute a shared secret. The shared secret is derived from the + * x-coordinate of the elliptic curve point resulting from the multiplication of the + * public key with the private key. + * + * Note: When performing Elliptic Curve Diffie-Hellman (ECDH) key agreement, + * the resulting shared secret is a point on the elliptic curve, which + * consists of an x-coordinate and a y-coordinate. With a 256-bit curve like + * secp256k1, each of these coordinates is 32 bytes (256 bits) long. However, + * in the ECDH process, it's standard practice to use only the x-coordinate + * of the shared secret point as the resulting shared key. This is because + * the y-coordinate does not add to the entropy of the key, and both parties + * can independently compute the x-coordinate. Consquently, this implementation + * omits the y-coordinate for simplicity and standard compliance. + * + * Example usage: + * + * ```ts + * const privateKeyA = { ... }; // A PrivateKeyJwk object for party A + * const publicKeyB = { ... }; // A PublicKeyJwk object for party B + * const sharedSecret = await Secp256k1.sharedSecret({ + * privateKeyA, + * publicKeyB + * }); + * ``` * * @param options - The options for the shared secret computation operation. - * @param options.privateKey - The private key of one party. - * @param options.publicKey - The public key of the other party. + * @param options.privateKeyA - The private key in JWK format of one party. + * @param options.publicKeyB - The public key in JWK format of the other party. + * * @returns A Promise that resolves to the computed shared secret as a Uint8Array. */ public static async sharedSecret(options: { - privateKey: Uint8Array, - publicKey: Uint8Array + privateKeyA: PrivateKeyJwk, + publicKeyB: PublicKeyJwk }): Promise { - let { privateKey, publicKey } = options; + let { privateKeyA, publicKeyB } = options; + // Ensure that keys from the same key pair are not specified. + if ('x' in privateKeyA && 'x' in publicKeyB && privateKeyA.x === publicKeyB.x) { + throw new Error(`X25519: ECDH shared secret cannot be computed from a single key pair's public and private keys.`); + } - const sharedSecret = x25519.getSharedSecret(privateKey, publicKey); + // Convert the provided private and public keys to bytes. + const privateKeyABytes = await X25519.privateKeyToBytes({ privateKey: privateKeyA }); + const publicKeyBBytes = await X25519.publicKeyToBytes({ publicKey: publicKeyB }); + + // Compute the shared secret between the public and private keys. + const sharedSecret = x25519.getSharedSecret(privateKeyABytes, publicKeyBBytes); return sharedSecret; } @@ -89,13 +377,14 @@ export class X25519 { * @param options - The options for the key validation operation. * @param options.key - The key to validate. * @throws {Error} If the method is called because it is not yet implemented. + * * @returns A Promise that resolves to void. */ - public static async validatePublicKey(_options: { + private static async validatePublicKey(_options: { key: Uint8Array }): Promise { // TODO: Implement once/if @noble/curves library implements checking // proper points on the Montgomery curve. - throw new Error(`Not implemented: 'validatePublicKey()'`); + throw new Error(`X25519: Not implemented: 'validatePublicKey()'`); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts b/packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts index 5aeb4c012..264b36478 100644 --- a/packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts +++ b/packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts @@ -1,34 +1,202 @@ -import { xchacha20_poly1305 } from '@noble/ciphers/chacha'; +import { Convert } from '@web5/common'; +import { xchacha20poly1305 } from '@noble/ciphers/chacha'; + +import type { PrivateKeyJwk } from '../jose.js'; + +import { Jose } from '../jose.js'; const TAG_LENGTH = 16; +/** + * The `XChaCha20Poly1305` class provides a suite of utilities for cryptographic operations + * using the XChaCha20-Poly1305 algorithm, a combination of the XChaCha20 stream cipher and the + * Poly1305 message authentication code (MAC). This class encompasses methods for key generation, + * encryption, decryption, and conversions between raw byte arrays and JSON Web Key (JWK) formats. + * + * XChaCha20-Poly1305 is renowned for its high security and efficiency, especially in scenarios + * involving large data volumes or where data integrity and confidentiality are paramount. The + * extended nonce size of XChaCha20 reduces the risks of nonce reuse, while Poly1305 provides + * a strong MAC ensuring data integrity. + * + * Key Features: + * - Key Generation: Generate XChaCha20-Poly1305 symmetric keys in JWK format. + * - Key Conversion: Transform keys between raw byte arrays and JWK formats. + * - Encryption: Encrypt data using XChaCha20-Poly1305, returning both ciphertext and MAC tag. + * - Decryption: Decrypt data and verify integrity using the XChaCha20-Poly1305 algorithm. + * + * The methods in this class are asynchronous, returning Promises to accommodate various + * JavaScript environments. + * + * Usage Examples: + * + * ```ts + * // Key Generation + * const privateKey = await XChaCha20Poly1305.generateKey(); + * + * // Encryption + * const data = new TextEncoder().encode('Hello, world!'); + * const nonce = crypto.getRandomValues(new Uint8Array(24)); // 24-byte nonce + * const additionalData = new TextEncoder().encode('Associated data'); + * const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + * data, + * nonce, + * additionalData, + * key: privateKey + * }); + * + * // Decryption + * const decryptedData = await XChaCha20Poly1305.decrypt({ + * data: ciphertext, + * nonce, + * tag, + * additionalData, + * key: privateKey + * }); + * + * // Key Conversion + * const privateKeyBytes = await XChaCha20Poly1305.privateKeyToBytes({ privateKey }); + * ``` + */ export class XChaCha20Poly1305 { + /** + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method takes a symmetric key represented as a byte array (Uint8Array) and converts it into + * a JWK object for use with the XChaCha20-Poly1305 algorithm. The process involves encoding the + * key into base64url format and setting the appropriate JWK parameters. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'oct' for Octet Sequence (representing a symmetric key). + * - `k`: The symmetric key, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint. + * + * Example usage: + * + * ```ts + * const privateKeyBytes = new Uint8Array([...]); // Replace with actual symmetric key bytes + * const privateKey = await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKeyBytes - The raw symmetric key as a Uint8Array. + * + * @returns A Promise that resolves to the symmetric key in JWK format. + */ + public static async bytesToPrivateKey(options: { + privateKeyBytes: Uint8Array + }): Promise { + const { privateKeyBytes } = options; + + // Construct the private key in JWK format. + const privateKey: PrivateKeyJwk = { + k : Convert.uint8Array(privateKeyBytes).toBase64Url(), + kty : 'oct' + }; + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + return privateKey; + } + + /** + * Decrypts the provided data using XChaCha20-Poly1305. + * + * This method performs XChaCha20-Poly1305 decryption on the given encrypted data using the + * specified key, nonce, and authentication tag. It supports optional additional authenticated + * data (AAD) for enhanced security. The nonce must be 24 bytes long, consistent with XChaCha20's + * specifications. + * + * Example usage: + * + * ```ts + * const encryptedData = new Uint8Array([...]); // Encrypted data + * const nonce = new Uint8Array(24); // 24-byte nonce + * const tag = new Uint8Array([...]); // Authentication tag + * const additionalData = new Uint8Array([...]); // Optional AAD + * const key = { ... }; // A PrivateKeyJwk object representing the XChaCha20-Poly1305 key + * const decryptedData = await XChaCha20Poly1305.decrypt({ + * data: encryptedData, + * nonce, + * tag, + * additionalData, + * key + * }); + * ``` + * + * @param options - The options for the decryption operation. + * @param options.data - The encrypted data to decrypt, represented as a Uint8Array. + * @param options.key - The key to use for decryption, represented in JWK format. + * @param options.nonce - The nonce used during the encryption process. + * @param options.tag - The authentication tag generated during encryption. + * @param options.additionalData - Optional additional authenticated data. + * + * @returns A Promise that resolves to the decrypted data as a Uint8Array. + */ public static async decrypt(options: { additionalData?: Uint8Array, data: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, nonce: Uint8Array, tag: Uint8Array }): Promise { const { additionalData, data, key, nonce, tag } = options; - const xc20p = xchacha20_poly1305(key, nonce, additionalData); + // Convert the private key from JWK format to bytes. + const privateKeyBytes = await XChaCha20Poly1305.privateKeyToBytes({ privateKey: key }); + + const xc20p = xchacha20poly1305(privateKeyBytes, nonce, additionalData); const ciphertext = new Uint8Array([...data, ...tag]); const plaintext = xc20p.decrypt(ciphertext); return plaintext; } + /** + * Encrypts the provided data using XChaCha20-Poly1305. + * + * This method performs XChaCha20-Poly1305 encryption on the given data using the specified key + * and nonce. It supports optional additional authenticated data (AAD) for enhanced security. The + * nonce must be 24 bytes long, as per XChaCha20's specifications. The method returns the + * encrypted data along with an authentication tag as a Uint8Array, ensuring both confidentiality + * and integrity of the data. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); + * const nonce = crypto.getRandomValues(new Uint8Array(24)); // 24-byte nonce + * const additionalData = new TextEncoder().encode('Associated data'); // Optional AAD + * const key = { ... }; // A PrivateKeyJwk object representing an XChaCha20-Poly1305 key + * const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + * data, + * nonce, + * additionalData, + * key + * }); + * ``` + * + * @param options - The options for the encryption operation. + * @param options.data - The data to encrypt, represented as a Uint8Array. + * @param options.key - The key to use for encryption, represented in JWK format. + * @param options.nonce - A 24-byte nonce for the encryption process. + * @param options.additionalData - Optional additional authenticated data. + * + * @returns A Promise that resolves to an object containing the encrypted data (`ciphertext`) and + * the authentication tag (`tag`). + */ public static async encrypt(options: { additionalData?: Uint8Array, data: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, nonce: Uint8Array }): Promise<{ ciphertext: Uint8Array, tag: Uint8Array }> { const { additionalData, data, key, nonce } = options; - const xc20p = xchacha20_poly1305(key, nonce, additionalData); + // Convert the private key from JWK format to bytes. + const privateKeyBytes = await XChaCha20Poly1305.privateKeyToBytes({ privateKey: key }); + + const xc20p = xchacha20poly1305(privateKeyBytes, nonce, additionalData); const cipherOutput = xc20p.encrypt(data); const ciphertext = cipherOutput.subarray(0, -TAG_LENGTH); @@ -37,10 +205,72 @@ export class XChaCha20Poly1305 { return { ciphertext, tag }; } - public static async generateKey(): Promise { - // Generate the secret key. - const secretKey = crypto.getRandomValues(new Uint8Array(32)); + /** + * Generates a symmetric key for XChaCha20-Poly1305 in JSON Web Key (JWK) format. + * + * This method creates a new symmetric key suitable for use with the XChaCha20-Poly1305 algorithm. + * The key is generated using cryptographically secure random number generation to ensure its + * uniqueness and security. The XChaCha20-Poly1305 algorithm requires a 256-bit key (32 bytes), + * and this method adheres to that specification. + * + * Key components included in the JWK: + * - `kty`: Key Type, set to 'oct' for Octet Sequence. + * - `k`: The symmetric key component, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint. + * + * Example usage: + * + * ```ts + * const privateKey = await XChaCha20Poly1305.generateKey(); + * ``` + * + * @returns A Promise that resolves to the generated symmetric key in JWK format. + */ + public static async generateKey(): Promise { + // Generate a random private key. + const privateKeyBytes = crypto.getRandomValues(new Uint8Array(32)); + + // Convert private key from bytes to JWK format. + const privateKey = await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes }); + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method takes a symmetric key in JWK format and extracts its raw byte representation. + * It decodes the 'k' parameter of the JWK value, which represents the symmetric key in base64url + * encoding, into a byte array. + * + * Example usage: + * + * ```ts + * const privateKey = { ... }; // A symmetric key in JWK format + * const privateKeyBytes = await XChaCha20Poly1305.privateKeyToBytes({ privateKey }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKey - The symmetric key in JWK format. + * + * @returns A Promise that resolves to the symmetric key as a Uint8Array. + */ + public static async privateKeyToBytes(options: { + privateKey: PrivateKeyJwk + }): Promise { + const { privateKey } = options; + + // Verify the provided JWK represents a valid oct private key. + if (!Jose.isOctPrivateKeyJwk(privateKey)) { + throw new Error(`XChaCha20Poly1305: The provided key is not a valid oct private key.`); + } + + // Decode the provided private key to bytes. + const privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); - return secretKey; + return privateKeyBytes; } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/xchacha20.ts b/packages/crypto/src/crypto-primitives/xchacha20.ts index f1a9e916f..9d741d9c0 100644 --- a/packages/crypto/src/crypto-primitives/xchacha20.ts +++ b/packages/crypto/src/crypto-primitives/xchacha20.ts @@ -1,34 +1,250 @@ +import { Convert } from '@web5/common'; import { xchacha20 } from '@noble/ciphers/chacha'; +import type { PrivateKeyJwk } from '../jose.js'; + +import { Jose } from '../jose.js'; + +/** + * The `XChaCha20` class provides a comprehensive suite of utilities for cryptographic operations + * using the XChaCha20 symmetric encryption algorithm. This class includes methods for key + * generation, encryption, decryption, and conversions between raw byte arrays and JSON Web Key + * (JWK) formats. XChaCha20 is an extended nonce variant of ChaCha20, a stream cipher designed for + * high-speed encryption with substantial security margins. + * + * The XChaCha20 algorithm is particularly well-suited for encrypting large volumes of data or + * data streams, especially where random access is required. The class adheres to standard + * cryptographic practices, ensuring robustness and security in its implementations. + * + * Key Features: + * - Key Generation: Generate XChaCha20 symmetric keys in JWK format. + * - Key Conversion: Transform keys between raw byte arrays and JWK formats. + * - Encryption: Encrypt data using XChaCha20 with the provided symmetric key. + * - Decryption: Decrypt data encrypted with XChaCha20 using the corresponding symmetric key. + * + * The methods in this class are asynchronous, returning Promises to accommodate various + * JavaScript environments. + * + * Usage Examples: + * + * ```ts + * // Key Generation + * const privateKey = await XChaCha20.generateKey(); + * + * // Encryption + * const data = new TextEncoder().encode('Hello, world!'); + * const nonce = crypto.getRandomValues(new Uint8Array(24)); // 24-byte nonce for XChaCha20 + * const encryptedData = await XChaCha20.encrypt({ + * data, + * nonce, + * key: privateKey + * }); + * + * // Decryption + * const decryptedData = await XChaCha20.decrypt({ + * data: encryptedData, + * nonce, + * key: privateKey + * }); + * + * // Key Conversion + * const privateKeyBytes = await XChaCha20.privateKeyToBytes({ privateKey }); + * ``` + */ export class XChaCha20 { + /** + * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. + * + * This method takes a symmetric key represented as a byte array (Uint8Array) and + * converts it into a JWK object for use with the XChaCha20 symmetric encryption algorithm. The + * conversion process involves encoding the key into base64url format and setting the appropriate + * JWK parameters. + * + * The resulting JWK object includes the following properties: + * - `kty`: Key Type, set to 'oct' for Octet Sequence (representing a symmetric key). + * - `k`: The symmetric key, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint. + * + * Example usage: + * + * ```ts + * const privateKeyBytes = new Uint8Array([...]); // Replace with actual symmetric key bytes + * const privateKey = await XChaCha20.bytesToPrivateKey({ privateKeyBytes }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKeyBytes - The raw symmetric key as a Uint8Array. + * + * @returns A Promise that resolves to the symmetric key in JWK format. + */ + public static async bytesToPrivateKey(options: { + privateKeyBytes: Uint8Array + }): Promise { + const { privateKeyBytes } = options; + + // Construct the private key in JWK format. + const privateKey: PrivateKeyJwk = { + k : Convert.uint8Array(privateKeyBytes).toBase64Url(), + kty : 'oct' + }; + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Decrypts the provided data using XChaCha20. + * + * This method performs XChaCha20 decryption on the given encrypted data using the specified key + * and nonce. The nonce should be the same as used in the encryption process and must be 24 bytes + * long. The method returns the decrypted data as a Uint8Array. + * + * Example usage: + * + * ```ts + * const encryptedData = new Uint8Array([...]); // Encrypted data + * const nonce = new Uint8Array(24); // 24-byte nonce used during encryption + * const key = { ... }; // A PrivateKeyJwk object representing the XChaCha20 key + * const decryptedData = await XChaCha20.decrypt({ + * data: encryptedData, + * nonce, + * key + * }); + * ``` + * + * @param options - The options for the decryption operation. + * @param options.data - The encrypted data to decrypt, represented as a Uint8Array. + * @param options.key - The key to use for decryption, represented in JWK format. + * @param options.nonce - The nonce used during the encryption process. + * + * @returns A Promise that resolves to the decrypted data as a Uint8Array. + */ public static async decrypt(options: { data: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, nonce: Uint8Array }): Promise { const { data, key, nonce } = options; - const ciphertext = xchacha20(key, nonce, data); + // Convert the private key from JWK format to bytes. + const privateKeyBytes = await XChaCha20.privateKeyToBytes({ privateKey: key }); + + const ciphertext = xchacha20(privateKeyBytes, nonce, data); return ciphertext; } + /** + * Encrypts the provided data using XChaCha20. + * + * This method performs XChaCha20 encryption on the given data using the specified key and nonce. + * The nonce must be 24 bytes long, ensuring a high level of security through a vast nonce space, + * reducing the risks associated with nonce reuse. The method returns the encrypted data as a + * Uint8Array. + * + * Example usage: + * + * ```ts + * const data = new TextEncoder().encode('Hello, world!'); + * const nonce = crypto.getRandomValues(new Uint8Array(24)); // 24-byte nonce for XChaCha20 + * const key = { ... }; // A PrivateKeyJwk object representing an XChaCha20 key + * const encryptedData = await XChaCha20.encrypt({ + * data, + * nonce, + * key + * }); + * ``` + * + * @param options - The options for the encryption operation. + * @param options.data - The data to encrypt, represented as a Uint8Array. + * @param options.key - The key to use for encryption, represented in JWK format. + * @param options.nonce - A 24-byte nonce for the encryption process. + * + * @returns A Promise that resolves to the encrypted data as a Uint8Array. + */ public static async encrypt(options: { data: Uint8Array, - key: Uint8Array, + key: PrivateKeyJwk, nonce: Uint8Array }): Promise { const { data, key, nonce } = options; - const plaintext = xchacha20(key, nonce, data); + // Convert the private key from JWK format to bytes. + const privateKeyBytes = await XChaCha20.privateKeyToBytes({ privateKey: key }); + + const plaintext = xchacha20(privateKeyBytes, nonce, data); return plaintext; } - public static async generateKey(): Promise { - // Generate the secret key. - const secretKey = crypto.getRandomValues(new Uint8Array(32)); + /** + * Generates a symmetric key for XChaCha20 in JSON Web Key (JWK) format. + * + * This method creates a new symmetric key suitable for use with the XChaCha20 encryption + * algorithm. The key is generated using cryptographically secure random number generation + * to ensure its uniqueness and security. The XChaCha20 algorithm requires a 256-bit key + * (32 bytes), and this method adheres to that specification. + * + * Key components included in the JWK: + * - `kty`: Key Type, set to 'oct' for Octet Sequence. + * - `k`: The symmetric key component, base64url-encoded. + * - `kid`: Key ID, generated based on the JWK thumbprint. + * + * Example usage: + * + * ```ts + * const privateKey = await XChaCha20.generateKey(); + * ``` + * + * @returns A Promise that resolves to the generated symmetric key in JWK format. + */ + public static async generateKey(): Promise { + // Generate a random private key. + const privateKeyBytes = crypto.getRandomValues(new Uint8Array(32)); + + // Convert private key from bytes to JWK format. + const privateKey = await XChaCha20.bytesToPrivateKey({ privateKeyBytes }); + + // Compute the JWK thumbprint and set as the key ID. + privateKey.kid = await Jose.jwkThumbprint({ key: privateKey }); + + return privateKey; + } + + /** + * Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array). + * + * This method takes a symmetric key in JWK format and extracts its raw byte representation. + * It decodes the 'k' parameter of the JWK value, which represents the symmetric key in base64url + * encoding, into a byte array. + * + * Example usage: + * + * ```ts + * const privateKey = { ... }; // A symmetric key in JWK format + * const privateKeyBytes = await XChaCha20.privateKeyToBytes({ privateKey }); + * ``` + * + * @param options - The options for the symmetric key conversion. + * @param options.privateKey - The symmetric key in JWK format. + * + * @returns A Promise that resolves to the symmetric key as a Uint8Array. + */ + public static async privateKeyToBytes(options: { + privateKey: PrivateKeyJwk + }): Promise { + const { privateKey } = options; + + // Verify the provided JWK represents a valid oct private key. + if (!Jose.isOctPrivateKeyJwk(privateKey)) { + throw new Error(`XChaCha20: The provided key is not a valid oct private key.`); + } + + // Decode the provided private key to bytes. + const privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); - return secretKey; + return privateKeyBytes; } } \ No newline at end of file diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index 2ed684c02..44e4612c4 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -1,4 +1,3 @@ -export type * from './types/crypto-key.js'; export type * from './types/web5-crypto.js'; export * from './algorithms-api/index.js'; diff --git a/packages/crypto/src/jose.ts b/packages/crypto/src/jose.ts index 561bcc459..ff373b3e1 100644 --- a/packages/crypto/src/jose.ts +++ b/packages/crypto/src/jose.ts @@ -1,31 +1,68 @@ import { sha256 } from '@noble/hashes/sha256'; import { Convert, Multicodec, MulticodecCode, MulticodecDefinition, removeUndefinedProperties } from '@web5/common'; -import type { Web5Crypto } from './types/web5-crypto.js'; - import { keyToMultibaseId } from './utils.js'; -import { CryptoKey } from './algorithms-api/index.js'; import { Ed25519, Secp256k1, X25519 } from './crypto-primitives/index.js'; /** * JSON Web Key Operations * - * decrypt : Decrypt content and validate decryption, if applicable - * deriveBits : Derive bits not to be used as a key - * deriveKey : Derive key - * encrypt : Encrypt content - * sign : Compute digital signature or MAC - * unwrapKey : Decrypt key and validate decryption, if applicable - * verify : Verify digital signature or MAC - * wrapKey : Encrypt key + * The "key_ops" (key operations) parameter identifies the operation(s) + * for which the key is intended to be used. The "key_ops" parameter is + * intended for use cases in which public, private, or symmetric keys + * may be present. + * + * Its value is an array of key operation values. Values defined by + * {@link https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3 | RFC 7517 Section 4.3} are: + * + * - "decrypt" : Decrypt content and validate decryption, if applicable + * - "deriveBits" : Derive bits not to be used as a key + * - "deriveKey" : Derive key + * - "encrypt" : Encrypt content + * - "sign" : Compute digital signature or MAC + * - "unwrapKey" : Decrypt key and validate decryption, if applicable + * - "verify" : Verify digital signature or MAC + * - "wrapKey" : Encrypt key + * + * Other values MAY be used. The key operation values are case- + * sensitive strings. Duplicate key operation values MUST NOT be + * present in the array. Use of the "key_ops" member is OPTIONAL, + * unless the application requires its presence. + * + * The "use" and "key_ops" JWK members SHOULD NOT be used together; + * however, if both are used, the information they convey MUST be + * consistent. Applications should specify which of these members they + * use, if either is to be used by the application. */ -export type JwkOperation = Web5Crypto.KeyUsage[] | string[]; +export type JwkOperation = 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'deriveKey' | 'deriveBits' | 'wrapKey' | 'unwrapKey'; /** * JSON Web Key Use * - * sig : Digital Signature or MAC - * enc : Encryption + * The "use" (public key use) parameter identifies the intended use of + * the public key. The "use" parameter is employed to indicate whether + * a public key is used for encrypting data or verifying the signature + * on data. + * + * Values defined by {@link https://datatracker.ietf.org/doc/html/rfc7517#section-4.2 | RFC 7517 Section 4.2} are: + * + * - "sig" (signature) + * - "enc" (encryption) + * + * Other values MAY be used. The "use" value is a case-sensitive + * string. Use of the "use" member is OPTIONAL, unless the application + * requires its presence. + * + * The "use" and "key_ops" JWK members SHOULD NOT be used together; + * however, if both are used, the information they convey MUST be + * consistent. Applications should specify which of these members they + * use, if either is to be used by the application. + * + * When a key is used to wrap another key and a public key use + * designation for the first key is desired, the "enc" (encryption) key + * use value is used, since key wrapping is a kind of encryption. The + * "enc" value is also to be used for public keys used for key agreement + * operations. */ export type JwkUse = 'sig' | 'enc' | string; @@ -90,8 +127,8 @@ export type JwkParamsAnyKeyType = { // Extractable ext?: 'true' | 'false'; // Key Operations - key_ops?: JwkOperation; - // Key ID + key_ops?: JwkOperation[]; + //'encrypt' | 'decrypt' | 'sign' | 'verify' | 'deriveKey' | 'deriveBits' | 'wrapKey' | 'unwrapKey';D kid?: string; // Key Type kty: JwkType; @@ -411,59 +448,13 @@ export interface JweHeaderParams extends JoseHeaderParams { [key: string]: unknown } -const joseToWebCryptoMapping: { [key: string]: Web5Crypto.GenerateKeyOptions } = { - 'Ed25519' : { name: 'EdDSA', namedCurve: 'Ed25519' }, - 'Ed448' : { name: 'EdDSA', namedCurve: 'Ed448' }, - 'X25519' : { name: 'ECDH', namedCurve: 'X25519' }, - 'secp256k1:ES256K' : { name: 'ECDSA', namedCurve: 'secp256k1' }, - 'secp256k1' : { name: 'ECDH', namedCurve: 'secp256k1' }, - 'P-256' : { name: 'ECDSA', namedCurve: 'P-256' }, - 'P-384' : { name: 'ECDSA', namedCurve: 'P-384' }, - 'P-521' : { name: 'ECDSA', namedCurve: 'P-521' }, - 'A128CBC' : { name: 'AES-CBC', length: 128 }, - 'A192CBC' : { name: 'AES-CBC', length: 192 }, - 'A256CBC' : { name: 'AES-CBC', length: 256 }, - 'A128CTR' : { name: 'AES-CTR', length: 128 }, - 'A192CTR' : { name: 'AES-CTR', length: 192 }, - 'A256CTR' : { name: 'AES-CTR', length: 256 }, - 'A128GCM' : { name: 'AES-GCM', length: 128 }, - 'A192GCM' : { name: 'AES-GCM', length: 192 }, - 'A256GCM' : { name: 'AES-GCM', length: 256 }, - 'HS256' : { name: 'HMAC', hash: { name: 'SHA-256' } }, - 'HS384' : { name: 'HMAC', hash: { name: 'SHA-384' } }, - 'HS512' : { name: 'HMAC', hash: { name: 'SHA-512' } }, -}; - -const webCryptoToJoseMapping: { [key: string]: Partial } = { - 'EdDSA:Ed25519' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP' }, - 'EdDSA:Ed448' : { alg: 'EdDSA', crv: 'Ed448', kty: 'OKP' }, - 'ECDH:X25519' : { crv: 'X25519', kty: 'OKP' }, - 'ECDSA:secp256k1' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC' }, - 'ECDH:secp256k1' : { crv: 'secp256k1', kty: 'EC' }, - 'ECDSA:P-256' : { alg: 'ES256', crv: 'P-256', kty: 'EC' }, - 'ECDSA:P-384' : { alg: 'ES384', crv: 'P-384', kty: 'EC' }, - 'ECDSA:P-521' : { alg: 'ES512', crv: 'P-521', kty: 'EC' }, - 'AES-CBC:128' : { alg: 'A128CBC', kty: 'oct' }, - 'AES-CBC:192' : { alg: 'A192CBC', kty: 'oct' }, - 'AES-CBC:256' : { alg: 'A256CBC', kty: 'oct' }, - 'AES-CTR:128' : { alg: 'A128CTR', kty: 'oct' }, - 'AES-CTR:192' : { alg: 'A192CTR', kty: 'oct' }, - 'AES-CTR:256' : { alg: 'A256CTR', kty: 'oct' }, - 'AES-GCM:128' : { alg: 'A128GCM', kty: 'oct' }, - 'AES-GCM:192' : { alg: 'A192GCM', kty: 'oct' }, - 'AES-GCM:256' : { alg: 'A256GCM', kty: 'oct' }, - 'HMAC:SHA-256' : { alg: 'HS256', kty: 'oct' }, - 'HMAC:SHA-384' : { alg: 'HS384', kty: 'oct' }, - 'HMAC:SHA-512' : { alg: 'HS512', kty: 'oct' }, -}; - const multicodecToJoseMapping: { [key: string]: JsonWebKey } = { - 'ed25519-pub' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP', x: '' }, - 'ed25519-priv' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP', x: '', d: '' }, - 'secp256k1-pub' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC', x: '', y: ''}, - 'secp256k1-priv' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC', x: '', y: '', d: '' }, - 'x25519-pub' : { crv: 'X25519', kty: 'OKP', x: '' }, - 'x25519-priv' : { crv: 'X25519', kty: 'OKP', x: '', d: '' }, + 'ed25519-pub' : { crv: 'Ed25519', kty: 'OKP', x: '' }, + 'ed25519-priv' : { crv: 'Ed25519', kty: 'OKP', x: '', d: '' }, + 'secp256k1-pub' : { crv: 'secp256k1', kty: 'EC', x: '', y: ''}, + 'secp256k1-priv' : { crv: 'secp256k1', kty: 'EC', x: '', y: '', d: '' }, + 'x25519-pub' : { crv: 'X25519', kty: 'OKP', x: '' }, + 'x25519-priv' : { crv: 'X25519', kty: 'OKP', x: '', d: '' }, }; const joseToMulticodecMapping: { [key: string]: string } = { @@ -476,43 +467,51 @@ const joseToMulticodecMapping: { [key: string]: string } = { }; export class Jose { + public static isEcPrivateKeyJwk(obj: unknown): obj is JwkParamsEcPrivate { + if (!obj || typeof obj !== 'object') return false; + if (!('kty' in obj && 'crv' in obj && 'x' in obj && 'd' in obj)) return false; + if (obj.kty !== 'EC') return false; + if (typeof obj.d !== 'string') return false; + if (typeof obj.x !== 'string') return false; + + return true; + } - public static async cryptoKeyToJwk(options: { - key: Web5Crypto.CryptoKey, - }): Promise { - const { algorithm, extractable, material, type, usages } = options.key; - - // Translate WebCrypto algorithm to JOSE format. - let jsonWebKey = Jose.webCryptoToJose(algorithm) as JsonWebKey; - - // Set extractable parameter. - jsonWebKey.ext = extractable ? 'true' : 'false'; - - // Set key use parameter. - jsonWebKey.key_ops = usages; + public static isEcPublicKeyJwk(obj: unknown): obj is JwkParamsEcPublic { + if (!obj || typeof obj !== 'object') return false; + if (!('kty' in obj && 'crv' in obj && 'x' in obj)) return false; + if ('d' in obj) return false; + if (obj.kty !== 'EC') return false; + if (typeof obj.x !== 'string') return false; + return true; + } - jsonWebKey = await Jose.keyToJwk({ - keyMaterial : material, - keyType : type, - ...jsonWebKey - }); + public static isOctPrivateKeyJwk(obj: unknown): obj is JwkParamsOctPrivate { + if (!obj || typeof obj !== 'object') return false; + if (!('kty' in obj && 'k' in obj)) return false; + if (obj.kty !== 'oct') return false; + if (typeof obj.k !== 'string') return false; - return { ...jsonWebKey }; + return true; } - public static async cryptoKeyToJwkPair(options: { - keyPair: Web5Crypto.CryptoKeyPair, - }): Promise { - const { keyPair } = options; + public static isOkpPrivateKeyJwk(obj: unknown): obj is JwkParamsOkpPrivate { + if (!obj || typeof obj !== 'object') return false; + if (!('kty' in obj && 'crv' in obj && 'x' in obj && 'd' in obj)) return false; + if (obj.kty !== 'OKP') return false; + if (typeof obj.d !== 'string') return false; + if (typeof obj.x !== 'string') return false; - // Convert public and private keys into JSON Web Key format. - const privateKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.privateKey }) as PrivateKeyJwk; - const publicKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey }) as PublicKeyJwk; - - // Assemble as a JWK key pair - const jwkKeyPair: JwkKeyPair = { privateKeyJwk, publicKeyJwk }; + return true; + } - return { ...jwkKeyPair }; + public static isOkpPublicKeyJwk(obj: unknown): obj is JwkParamsOkpPublic { + if (!obj || typeof obj !== 'object') return false; + if ('d' in obj) return false; + if (!('kty' in obj && 'crv' in obj && 'x' in obj)) return false; + if (obj.kty !== 'OKP') return false; + if (typeof obj.x !== 'string') return false; + return true; } public static async joseToMulticodec(options: { @@ -543,43 +542,6 @@ export class Jose { return { code, name }; } - public static joseToWebCrypto(options: - Partial - ): Web5Crypto.GenerateKeyOptions { - const params: string[] = []; - - /** - * All Elliptic Curve (EC) and Octet Key Pair (OKP) JSON Web Keys - * set a value for the "crv" parameter. - */ - if ('crv' in options && options.crv) { - params.push(options.crv); - // Special case for secp256k1. If alg is "ES256K", then ECDSA. Else ECDH. - if (options.crv === 'secp256k1' && options.alg === 'ES256K') { - params.push(options.alg); - } - - /** - * All Octet Sequence (oct) JSON Web Keys omit "crv" and - * set a value for the "alg" parameter. - */ - } else if (options.alg !== undefined) { - params.push(options.alg); - - } else { - throw new TypeError(`One or more parameters missing: 'alg' or 'crv'`); - } - - const lookupKey = params.join(':'); - const webCrypto = joseToWebCryptoMapping[lookupKey]; - - if (webCrypto === undefined) { - throw new Error(`Unsupported JOSE to WebCrypto conversion: '${lookupKey}'`); - } - - return { ...webCrypto }; - } - /** * Computes the thumbprint of a JSON Web Key (JWK) using the method * specified in RFC 7638. This function accepts RSA, EC, OKP, and oct keys @@ -662,197 +624,62 @@ export class Jose { return thumbprint; } - public static async jwkToCryptoKey(options: { - key: JsonWebKey - }): Promise { - const jsonWebKey = options.key; - - const { keyMaterial, keyType } = await Jose.jwkToKey({ key: jsonWebKey }); - - // Translate JOSE format to WebCrypto algorithm. - let algorithm = Jose.joseToWebCrypto(jsonWebKey) as Web5Crypto.GenerateKeyOptions; - - // Set extractable parameter. - let extractable: boolean; - if ('ext' in jsonWebKey && jsonWebKey.ext !== undefined) { - extractable = jsonWebKey.ext === 'true' ? true : false; - } else { - throw new Error(`Conversion from JWK to CryptoKey failed. Required parameter missing: 'ext'`); - } - - // Set key use parameter. - let keyUsage: Web5Crypto.KeyUsage[]; - if ('key_ops' in jsonWebKey && jsonWebKey.key_ops !== undefined) { - keyUsage = jsonWebKey.key_ops as Web5Crypto.KeyUsage[]; - } else { - throw new Error(`Conversion from JWK to CryptoKey failed. Required parameter missing: 'key_ops'`); - } - - const cryptoKey = new CryptoKey( - algorithm, - extractable, - keyMaterial, - keyType, - keyUsage - ); - - return cryptoKey; - } - - public static async jwkToKey(options: { - key: JsonWebKey - }): Promise<{ keyMaterial: Uint8Array, keyType: Web5Crypto.KeyType }> { - const jsonWebKey = options.key; - - let keyMaterial: Uint8Array; - let keyType: Web5Crypto.KeyType; - - // Asymmetric private key ("EC" or "OKP" - Curve25519 or SECG curves). - if ('d' in jsonWebKey) { - keyMaterial = Convert.base64Url(jsonWebKey.d).toUint8Array(); - keyType = 'private'; - } - - // Asymmetric public key ("EC" - secp256k1, secp256r1, secp384r1, secp521r1). - else if ('y' in jsonWebKey && jsonWebKey.y) { - const prefix = new Uint8Array([0x04]); // Designates an uncompressed key. - const x = Convert.base64Url(jsonWebKey.x).toUint8Array(); - const y = Convert.base64Url(jsonWebKey.y).toUint8Array(); - - const publicKey = new Uint8Array([...prefix, ...x, ...y]); - keyMaterial = publicKey; - keyType = 'public'; - } - - // Asymmetric public key ("OKP" - Ed25519, X25519). - else if ('x' in jsonWebKey) { - keyMaterial = Convert.base64Url(jsonWebKey.x).toUint8Array(); - keyType = 'public'; - } - - // Symmetric encryption or signature key ("oct" - AES, HMAC) - else if ('k' in jsonWebKey) { - keyMaterial = Convert.base64Url(jsonWebKey.k).toUint8Array(); - keyType = 'private'; - } - - else { - throw new Error('Jose: Unknown JSON Web Key format.'); - } - - return { keyMaterial, keyType }; - } - /** - * Note: All secp public keys are converted to compressed point encoding - * before the multibase identifier is computed. - * - * Per {@link https://github.com/multiformats/multicodec/blob/master/table.csv | Multicodec table}: - * public keys for Elliptic Curve cryptography algorithms (e.g., secp256k1, - * secp256k1r1, secp384r1, etc.) are always represented with compressed point - * encoding (e.g., secp256k1-pub, p256-pub, p384-pub, etc.). - * - * Per {@link https://datatracker.ietf.org/doc/html/rfc8812#name-jose-and-cose-secp256k1-cur | RFC 8812}: - * "As a compressed point encoding representation is not defined for JWK - * elliptic curve points, the uncompressed point encoding defined there - * MUST be used. The x and y values represented MUST both be exactly - * 256 bits, with any leading zeros preserved. - * - */ - public static async jwkToMultibaseId(options: { - key: JsonWebKey + * Note: All secp public keys are converted to compressed point encoding + * before the multibase identifier is computed. + * + * Per {@link https://github.com/multiformats/multicodec/blob/master/table.csv | Multicodec table}: + * Public keys for Elliptic Curve cryptography algorithms (e.g., secp256k1, + * secp256k1r1, secp384r1, etc.) are always represented with compressed point + * encoding (e.g., secp256k1-pub, p256-pub, p384-pub, etc.). + * + * Per {@link https://datatracker.ietf.org/doc/html/rfc8812#name-jose-and-cose-secp256k1-cur | RFC 8812}: + * "As a compressed point encoding representation is not defined for JWK + * elliptic curve points, the uncompressed point encoding defined there + * MUST be used. The x and y values represented MUST both be exactly + * 256 bits, with any leading zeros preserved." + */ + public static async publicKeyToMultibaseId(options: { + publicKey: PublicKeyJwk }): Promise { - const jsonWebKey = options.key; + const { publicKey } = options; - // Convert the algorithm into Multicodec name format. - const { name: multicodecName } = await Jose.joseToMulticodec({ key: jsonWebKey }); - - // Decode the key as a raw binary data from the JWK. - let { keyMaterial } = await Jose.jwkToKey({ key: jsonWebKey }); - - // Convert secp256k1 public keys to compressed format. - if ('crv' in jsonWebKey && !('d' in jsonWebKey)) { - switch (jsonWebKey.crv) { - case 'secp256k1': { - keyMaterial = await Secp256k1.convertPublicKey({ - publicKey : keyMaterial, - compressedPublicKey : true - }); - break; - } - } + if (!('crv' in publicKey)) { + throw new Error(`Jose: Unsupported public key type: ${publicKey.kty}`); } - // Compute the multibase identifier based on the provided key. - const multibaseId = keyToMultibaseId({ key: keyMaterial, multicodecName }); + let publicKeyBytes: Uint8Array; - return multibaseId; - } - - public static async keyToJwk(options: - Partial & { - keyMaterial: Uint8Array, - keyType: Web5Crypto.KeyType, - }): Promise { - const { keyMaterial, keyType, ...jsonWebKeyOptions } = options; + switch (publicKey.crv) { + case 'Ed25519': { + publicKeyBytes = await Ed25519.publicKeyToBytes({ publicKey }); + break; + } - let jsonWebKey = { ...jsonWebKeyOptions } as JsonWebKey; + case 'secp256k1': { + publicKeyBytes = await Secp256k1.publicKeyToBytes({ publicKey }); + // Convert secp256k1 public keys to compressed format. + publicKeyBytes = await Secp256k1.compressPublicKey({ publicKeyBytes }); + break; + } - /** - * All Elliptic Curve (EC) and Octet Key Pair (OKP) keys - * specify a "crv" (named curve) parameter. - */ - if ('crv' in jsonWebKey) { - switch (jsonWebKey.crv) { - - case 'Ed25519': { - const publicKey = (keyType === 'private') - ? await Ed25519.getPublicKey({ privateKey: keyMaterial }) - : keyMaterial; - jsonWebKey.x = Convert.uint8Array(publicKey).toBase64Url(); - jsonWebKey.kty ??= 'OKP'; - break; - } - - case 'X25519': { - const publicKey = (keyType === 'private') - ? await X25519.getPublicKey({ privateKey: keyMaterial }) - : keyMaterial; - jsonWebKey.x = Convert.uint8Array(publicKey).toBase64Url(); - jsonWebKey.kty ??= 'OKP'; - break; - } - - case 'secp256k1': { - const points = await Secp256k1.getCurvePoints({ key: keyMaterial }); - jsonWebKey.x = Convert.uint8Array(points.x).toBase64Url(); - jsonWebKey.y = Convert.uint8Array(points.y).toBase64Url(); - jsonWebKey.kty ??= 'EC'; - break; - } - - default: { - throw new Error(`Unsupported key to JWK conversion: ${jsonWebKey.crv}`); - } + case 'X25519': { + publicKeyBytes = await X25519.publicKeyToBytes({ publicKey }); + break; } - if (keyType === 'private') { - jsonWebKey = { - d: Convert.uint8Array(keyMaterial).toBase64Url(), - ...jsonWebKey - }; + default: { + throw new Error(`Jose: Unsupported public key curve: ${publicKey.crv}`); } } - /** - * All Octet Sequence (oct) symmetric encryption and signature keys - * specify only an "alg" parameter. - */ - if (!('crv' in jsonWebKey) && jsonWebKey.kty === 'oct') { - jsonWebKey.k = Convert.uint8Array(keyMaterial).toBase64Url(); - } + // Convert the JSON Web Key (JWK) parameters to a Multicodec name. + const { name: multicodecName } = await Jose.joseToMulticodec({ key: publicKey }); + + // Compute the multibase identifier based on the provided key. + const multibaseId = keyToMultibaseId({ key: publicKeyBytes, multicodecName }); - return { ...jsonWebKey }; + return multibaseId; } public static async multicodecToJose(options: { @@ -879,51 +706,6 @@ export class Jose { return { ...jose }; } - public static webCryptoToJose(options: - Web5Crypto.Algorithm | Web5Crypto.GenerateKeyOptions - ): Partial { - const params: string[] = []; - - /** - * All WebCrypto algorithms have the "named" parameter. - */ - params.push(options.name); - - /** - * All Elliptic Curve (EC) WebCrypto algorithms - * set a value for the "namedCurve" parameter. - */ - if ('namedCurve' in options) { - params.push(options.namedCurve); - - /** - * All symmetric encryption (AES) WebCrypto algorithms - * set a value for the "length" parameter. - */ - } else if ('length' in options && options.length !== undefined) { - params.push(options.length.toString()); - - /** - * All symmetric signature (HMAC) WebCrypto algorithms - * set a value for the "hash" parameter. - */ - } else if ('hash' in options) { - params.push(options.hash.name); - - } else { - throw new TypeError(`One or more parameters missing: 'name', 'namedCurve', 'length', or 'hash'`); - } - - const lookupKey = params.join(':'); - const jose = webCryptoToJoseMapping[lookupKey]; - - if (jose === undefined) { - throw new Error(`Unsupported WebCrypto to JOSE conversion: '${lookupKey}'`); - } - - return { ...jose }; - } - private static canonicalize(obj: { [key: string]: any }): string { const sortedKeys = Object.keys(obj).sort(); const sortedObj = sortedKeys.reduce<{ [key: string]: any }>((acc, key) => { @@ -932,17 +714,4 @@ export class Jose { }, {}); return JSON.stringify(sortedObj); } -} - -type Constructable = new (...args: any[]) => object; - -export function CryptoKeyToJwkMixin(Base: T) { - return class extends Base { - public async toJwk(): Promise { - const jwk = Jose.cryptoKeyToJwk({ key: (this as unknown) as CryptoKey }); - return jwk; - } - }; -} - -export const CryptoKeyWithJwk = CryptoKeyToJwkMixin(CryptoKey); \ No newline at end of file +} \ No newline at end of file diff --git a/packages/crypto/src/types/crypto-key.ts b/packages/crypto/src/types/crypto-key.ts deleted file mode 100644 index 864fc68cf..000000000 --- a/packages/crypto/src/types/crypto-key.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface BytesKeyPair { - privateKey: Uint8Array; - publicKey: Uint8Array; -} \ No newline at end of file diff --git a/packages/crypto/src/types/web5-crypto.ts b/packages/crypto/src/types/web5-crypto.ts index 55d017009..60d789de0 100644 --- a/packages/crypto/src/types/web5-crypto.ts +++ b/packages/crypto/src/types/web5-crypto.ts @@ -1,12 +1,12 @@ +import type { PublicKeyJwk } from '../jose.js'; + export namespace Web5Crypto { export interface AesCtrOptions extends Algorithm { counter: Uint8Array; length: number; } - export interface AesGenerateKeyOptions extends Algorithm { - length: number; - } + export interface AesGenerateKeyOptions extends Algorithm { } export interface AesGcmOptions extends Algorithm { additionalData?: Uint8Array; @@ -20,34 +20,16 @@ export namespace Web5Crypto { export type AlgorithmIdentifier = Algorithm; - export interface CryptoKey { - algorithm: Web5Crypto.Algorithm; - extractable: boolean; - material: Uint8Array; - type: KeyType; - usages: KeyUsage[]; - } - - export interface CryptoKeyPair { - privateKey: CryptoKey; - publicKey: CryptoKey; - } - - export interface EcdsaOptions extends Algorithm { - hash: string; - } + export interface EcdsaOptions extends Algorithm {} export interface EcGenerateKeyOptions extends Algorithm { - namedCurve: NamedCurve; + curve: NamedCurve; } - export interface EcdhDeriveKeyOptions extends Algorithm { - publicKey: CryptoKey; + publicKey: PublicKeyJwk; } - export interface EcdsaGenerateKeyOptions extends EcGenerateKeyOptions { - compressedPublicKey?: boolean; - } + export interface EcdsaGenerateKeyOptions extends EcGenerateKeyOptions { } export type EdDsaGenerateKeyOptions = EcGenerateKeyOptions @@ -66,46 +48,6 @@ export namespace Web5Crypto { export type KeyFormat = 'jwk' | 'pkcs8' | 'raw' | 'spki'; - export interface KeyPairUsage { - privateKey: KeyUsage[]; - publicKey: KeyUsage[]; - } - - /** - * KeyType - * - * The read-only `type` property indicates which kind of key - * is represented by the object. - * - * It can have the following string values: - * - * "secret": This key is a secret key for use with a symmetric algorithm. - * "private": This key is the private half of an asymmetric algorithm's `ManagedKeyPair`. - * "public": This key is the public half of an asymmetric algorithm's `ManagedKeyPair`. - */ - export type KeyType = 'private' | 'public' | 'secret'; - - /** - * KeyUsage - * - * The read-only usage property indicates what can be done with the key. - * - * An Array of strings from the following list: - * - * "encrypt": The key may be used to encrypt messages. - * "decrypt": The key may be used to decrypt messages. - * "sign": The key may be used to sign messages. - * "verify": The key may be used to verify signatures. - * "deriveKey": The key may be used in deriving a new key. - * "deriveBits": The key may be used in deriving bits. - * "wrapKey": The key may be used to wrap a key. - * "unwrapKey": The key may be used to unwrap a key. - * - * Reference: IANA "JSON Web Key Operations" registry - * https://www.iana.org/assignments/jose/jose.xhtml#web-key-operations - */ - export type KeyUsage = 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'deriveKey' | 'deriveBits' | 'wrapKey' | 'unwrapKey'; - export type NamedCurve = string; export interface Pbkdf2Options extends Algorithm { @@ -113,6 +55,4 @@ export namespace Web5Crypto { iterations: number; salt: Uint8Array; } - - export type PrivateKeyType = 'private' | 'secret'; } \ No newline at end of file diff --git a/packages/crypto/src/utils.ts b/packages/crypto/src/utils.ts index 0c448f099..2636f815a 100644 --- a/packages/crypto/src/utils.ts +++ b/packages/crypto/src/utils.ts @@ -1,8 +1,4 @@ -// import type { BytesKeyPair, Web5Crypto } from './types/index.js'; -import type { Web5Crypto } from './types/web5-crypto.js'; -import type { BytesKeyPair } from './types/crypto-key.js'; - -import { Convert, Multicodec, universalTypeOf } from '@web5/common'; +import { Convert, Multicodec } from '@web5/common'; import { bytesToHex, randomBytes as nobleRandomBytes } from '@noble/hashes/utils'; /** @@ -35,7 +31,7 @@ export function checkRequiredProperty(options: { * @throws {SyntaxError} If the property is not a member of the allowedProperties Array, Map, or Set. */ export function checkValidProperty(options: { - property: string, allowedProperties: Array | Map | Set + property: string, allowedProperties: ReadonlyArray | Array | Map | Set }): void { if (!options || options.property === undefined || options.allowedProperties === undefined) { throw new TypeError(`One or more required parameters missing: 'property, allowedProperties'`); @@ -51,30 +47,6 @@ export function checkValidProperty(options: { } } -/** - * Type guard function to check if the given key is a raw key pair - * of Uint8Array typed arrays. - * - * @param key The key to check. - * @returns True if the key is a pair of Uint8Array typed arrays, false otherwise. - */ -export function isBytesKeyPair(key: BytesKeyPair | undefined): key is BytesKeyPair { - return (key && 'privateKey' in key && 'publicKey' in key && - universalTypeOf(key.privateKey) === 'Uint8Array' && - universalTypeOf(key.publicKey) === 'Uint8Array') ? true : false; -} - -/** - * Type guard function to check if the given key is a - * Web5Crypto.CryptoKeyPair. - * - * @param key The key to check. - * @returns True if the key is a CryptoKeyPair, false otherwise. - */ -export function isCryptoKeyPair(key: Web5Crypto.CryptoKey | Web5Crypto.CryptoKeyPair): key is Web5Crypto.CryptoKeyPair { - return key && 'privateKey' in key && 'publicKey' in key; -} - export function keyToMultibaseId(options: { key: Uint8Array, multicodecCode?: number, diff --git a/packages/crypto/tests/algorithms-api.spec.ts b/packages/crypto/tests/algorithms-api.spec.ts index ae0d361c5..6c946f079 100644 --- a/packages/crypto/tests/algorithms-api.spec.ts +++ b/packages/crypto/tests/algorithms-api.spec.ts @@ -1,10 +1,20 @@ -import type { Web5Crypto } from '../src/types/web5-crypto.js'; - import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; import chaiAsPromised from 'chai-as-promised'; +import type { Web5Crypto } from '../src/types/web5-crypto.js'; +import type { + JwkType, + JwkOperation, + PublicKeyJwk, + PrivateKeyJwk, + JwkParamsEcPublic, + JwkParamsEcPrivate, + JwkParamsOkpPublic, + JwkParamsOctPrivate, +} from '../src/jose.js'; + import { - CryptoKey, OperationError, CryptoAlgorithm, BaseAesAlgorithm, @@ -24,8 +34,8 @@ describe('Algorithms API', () => { describe('CryptoAlgorithm', () => { class TestCryptoAlgorithm extends CryptoAlgorithm { - public name = 'TestAlgorithm'; - public keyUsages: KeyUsage[] = ['decrypt', 'deriveBits', 'deriveKey', 'encrypt', 'sign', 'unwrapKey', 'verify', 'wrapKey']; + public names = ['TestAlgorithm'] as const; + public keyOperations: JwkOperation[] = ['decrypt', 'deriveBits', 'deriveKey', 'encrypt', 'sign', 'unwrapKey', 'verify', 'wrapKey']; public async decrypt(): Promise { return null as any; } @@ -35,8 +45,8 @@ describe('Algorithms API', () => { public async encrypt(): Promise { return null as any; } - public async generateKey(): Promise { - return { publicKey: {} as any, privateKey: {} as any }; + public async generateKey(): Promise { + return null as any; } public async sign(): Promise { return null as any; @@ -70,33 +80,6 @@ describe('Algorithms API', () => { }); }); - describe('checkCryptoKey()', () => { - it('does not throw with a valid CryptoKey object', () => { - const mockCryptoKey = { - algorithm : null, - extractable : null, - type : null, - usages : null - }; - expect(() => alg.checkCryptoKey({ - // @ts-expect-error because 'material' property is intentionally omitted to support WebCrypto API CryptoKeys. - key: mockCryptoKey - })).to.not.throw(); - }); - - it('throws an error if the algorithm name does not match', () => { - const mockCryptoKey = { - algorithm : null, - type : null, - usages : null - }; - expect(() => alg.checkCryptoKey({ - // @ts-expect-error because 'extractable' property is intentionally ommitted to trigger check to throw. - key: mockCryptoKey - })).to.throw(TypeError, 'Object is not a CryptoKey'); - }); - }); - describe('checkKeyAlgorithm()', () => { it('throws an error when keyAlgorithmName is undefined', async () => { expect(() => alg.checkKeyAlgorithm({} as any)).to.throw(TypeError, 'Required parameter missing'); @@ -104,11 +87,11 @@ describe('Algorithms API', () => { it('throws an error when keyAlgorithmName does not match', async () => { const wrongName = 'wrongName'; - expect(() => alg.checkKeyAlgorithm({ keyAlgorithmName: wrongName })).to.throw(InvalidAccessError, `Algorithm '${alg.name}' does not match the provided '${wrongName}' key.`); + expect(() => alg.checkKeyAlgorithm({ keyAlgorithmName: wrongName })).to.throw(InvalidAccessError, `Algorithm '${alg.names.join(', ')}' does not match the provided '${wrongName}' key.`); }); it('does not throw an error when keyAlgorithmName matches', async () => { - const correctName = alg.name; + const [ correctName ] = alg.names; expect(() => alg.checkKeyAlgorithm({ keyAlgorithmName: correctName })).not.to.throw(); }); }); @@ -121,112 +104,159 @@ describe('Algorithms API', () => { }); it('throws an error when keyType does not match allowedKeyType', async () => { - const keyType = 'public'; - const allowedKeyType = 'private'; - expect(() => alg.checkKeyType({ keyType, allowedKeyType })).to.throw(InvalidAccessError, 'Requested operation is not valid'); + const keyType: JwkType = 'oct'; + const allowedKeyTypes: JwkType[] = ['OKP']; + expect(() => alg.checkKeyType({ keyType, allowedKeyTypes })).to.throw(InvalidAccessError, 'Key type of the provided key must be'); + }); + + it('throws an error when allowedKeyTypes is not an array', () => { + expect( + () => alg.checkKeyType({ + keyType : 'oct', + allowedKeyTypes : {} as any // Intentionally incorrect type + }) + ).to.throw(TypeError, `'allowedKeyTypes' is not of type Array.`); }); it('does not throw an error when keyType matches allowedKeyType', async () => { - const keyType = 'public'; - const allowedKeyType = 'public'; - expect(() => alg.checkKeyType({ keyType, allowedKeyType })).not.to.throw(); + const keyType: JwkType = 'EC'; + const allowedKeyTypes: JwkType[] = ['EC']; + expect(() => alg.checkKeyType({ keyType, allowedKeyTypes })).not.to.throw(); }); }); - describe('checkKeyUsages()', () => { - it('throws an error when keyUsages is undefined or empty', async () => { - expect(() => alg.checkKeyUsages({ allowedKeyUsages: ['sign'] } as any)).to.throw(TypeError, 'Required parameter missing or empty'); - expect(() => alg.checkKeyUsages({ keyUsages: [], allowedKeyUsages: ['sign'] })).to.throw(TypeError, 'Required parameter missing or empty'); + describe('checkKeyOperations()', () => { + it('does not throw an error when keyOperations are in allowedKeyOperations', async () => { + const keyOperations: JwkOperation[] = ['sign', 'verify']; + const allowedKeyOperations: JwkOperation[] = ['sign', 'verify', 'encrypt', 'decrypt']; + expect(() => alg.checkKeyOperations({ keyOperations, allowedKeyOperations })).not.to.throw(); }); - it('throws an error when keyUsages are not in allowedKeyUsages', async () => { - const keyUsages: Web5Crypto.KeyUsage[] = ['encrypt', 'decrypt']; - const allowedKeyUsages: Web5Crypto.KeyUsage[] = ['sign', 'verify']; - expect(() => alg.checkKeyUsages({ keyUsages, allowedKeyUsages })).to.throw(InvalidAccessError, 'is not valid for the provided key'); - - const keyPairUsages: Web5Crypto.KeyPairUsage = { privateKey: ['sign'], publicKey: ['verify'] }; - expect(() => alg.checkKeyUsages({ keyUsages, allowedKeyUsages: keyPairUsages })).to.throw(InvalidAccessError, 'is not valid for the provided key'); + it('throws an error when keyOperations is undefined or empty', async () => { + expect(() => alg.checkKeyOperations({ allowedKeyOperations: ['sign'] } as any)).to.throw(TypeError, 'Required parameter missing or empty'); + expect(() => alg.checkKeyOperations({ keyOperations: [], allowedKeyOperations: ['sign'] })).to.throw(TypeError, 'Required parameter missing or empty'); }); - it('does not throw an error when keyUsages are in allowedKeyUsages', async () => { - const keyUsages: Web5Crypto.KeyUsage[] = ['sign', 'verify']; - const allowedKeyUsages: Web5Crypto.KeyUsage[] = ['sign', 'verify', 'encrypt', 'decrypt']; - expect(() => alg.checkKeyUsages({ keyUsages, allowedKeyUsages })).not.to.throw(); + it('throws an error when keyOperations are not in allowedKeyOperations', async () => { + const keyOperations: JwkOperation[] = ['encrypt', 'decrypt']; + const allowedKeyOperations: JwkOperation[] = ['sign', 'verify']; + expect(() => alg.checkKeyOperations({ keyOperations, allowedKeyOperations })).to.throw(InvalidAccessError, 'is not valid for the provided key'); + }); - const keyPairUsages: Web5Crypto.KeyPairUsage = { privateKey: ['sign'], publicKey: ['verify'] }; - expect(() => alg.checkKeyUsages({ keyUsages, allowedKeyUsages: keyPairUsages })).to.not.throw(); + it('throws an error when allowedKeyOperations is not an Array', async () => { + const keyOperations: JwkOperation[] = ['encrypt', 'decrypt']; + const allowedKeyOperations = 'sign' as any; // Intentionally incorrect type'; + expect(() => alg.checkKeyOperations({ keyOperations, allowedKeyOperations })).to.throw(TypeError, 'is not of type Array'); }); }); }); describe('BaseAesAlgorithm', () => { class TestAesAlgorithm extends BaseAesAlgorithm { - public name = 'TestAlgorithm'; - public keyUsages: KeyUsage[] = ['decrypt', 'encrypt']; + public names = ['TestAlgorithm'] as const; + public keyOperations: JwkOperation[] = ['decrypt', 'encrypt']; public async decrypt(): Promise { return null as any; } public async encrypt(): Promise { return null as any; } - public async generateKey(): Promise { + public async generateKey(): Promise { return null as any; } } - describe('checkGenerateKey()', () => { - let alg: TestAesAlgorithm; + let alg: TestAesAlgorithm; - beforeEach(() => { - alg = TestAesAlgorithm.create(); - }); + beforeEach(() => { + alg = TestAesAlgorithm.create(); + }); - it('does not throw with supported algorithm, length, and key usage', () => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'TestAlgorithm', length: 128 }, - keyUsages : ['encrypt'] + describe('checkGenerateKeyOptions()', () => { + it('does not throw with supported algorithm and key operation', () => { + expect(() => alg.checkGenerateKeyOptions({ + algorithm : { name: 'TestAlgorithm' }, + keyOperations : ['encrypt'] })).to.not.throw(); }); it('throws an error when unsupported algorithm specified', () => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'ECDSA', length: 128 }, - keyUsages : ['encrypt'] + expect(() => alg.checkGenerateKeyOptions({ + algorithm : { name: 'ECDSA' }, + keyOperations : ['encrypt'] })).to.throw(NotSupportedError, 'Algorithm not supported'); }); - it('throws an error when the length property is missing', () => { - expect(() => alg.checkGenerateKey({ - // @ts-expect-error because length was intentionally omitted. - algorithm : { name: 'TestAlgorithm' }, - keyUsages : ['encrypt'] - })).to.throw(TypeError, 'Required parameter missing'); + it('throws an error when the requested operation is not valid', () => { + ['sign', 'verify'].forEach((operation) => { + expect(() => alg.checkGenerateKeyOptions({ + algorithm : { name: 'TestAlgorithm' }, + keyOperations : [operation as JwkOperation] + })).to.throw(InvalidAccessError, 'Requested operation'); + }); }); + }); + + describe('checkSecretKey()', () => { + let dataEncryptionKey: PrivateKeyJwk; - it('throws an error when the specified length is not a Number', () => { - expect(() => alg.checkGenerateKey({ - // @ts-expect-error because length is intentionally set as a string instead of number. - algorithm : { name: 'TestAlgorithm', length: '256' }, - keyUsages : ['encrypt'] - })).to.throw(TypeError, `is not of type: Number`); + beforeEach(() => { + dataEncryptionKey = { kty: 'oct', k: Convert.uint8Array(new Uint8Array(16)).toBase64Url() }; }); - it('throws an error when the specified length is not valid', () => { - [64, 96, 160, 224, 512].forEach((length) => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'TestAlgorithm', length }, - keyUsages : ['encrypt'] - })).to.throw(OperationError, `Algorithm 'length' must be 128, 192, or 256`); - }); + it('does not throw with a valid secret key', () => { + const key: PrivateKeyJwk = { + kty : 'oct', + k : Convert.uint8Array(new Uint8Array(16)).toBase64Url() + }; + expect(() => alg.checkSecretKey({ key })).to.not.throw(); }); - it('throws an error when the requested operation is not valid', () => { - ['sign', 'verify'].forEach((operation) => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'TestAlgorithm', length: 128 }, - keyUsages : [operation as KeyUsage] - })).to.throw(InvalidAccessError, 'Requested operation'); - }); + it('throws an error when the key is not a JWK', () => { + const key = 'foo' as any; // Intentionally incorrect type. + expect(() => alg.checkSecretKey({ key })).to.throw(TypeError, 'is not a JSON Web Key'); + }); + + it('throws an error if the key property is missing', () => { + // @ts-expect-error because key property was intentionally omitted. + expect(() => alg.checkSecretKey({})).to.throw(TypeError, `Required parameter missing: 'key'`); + }); + + it('throws an error if the given key is not valid', () => { + const { kty, ...keyMissingKeyType } = dataEncryptionKey as JwkParamsOctPrivate; + expect(() => alg.checkSecretKey({ + // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. + key: keyMissingKeyType + })).to.throw(TypeError, 'Object is not a JSON Web Key'); + + const { k, ...keyMissingK } = dataEncryptionKey as JwkParamsOctPrivate; + expect(() => alg.checkSecretKey({ + // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. + key: keyMissingK + })).to.throw(InvalidAccessError, 'Requested operation is only valid for oct private keys'); + }); + + it('if specified, throws an error if the algorithm of the key does not match', () => { + // @ts-expect-error because alg property is intentionally set to an invalid value. + const key: PrivateKeyJwk = { ...dataEncryptionKey, alg: 'invalid-alg' }; + expect(() => alg.checkSecretKey({ + key + })).to.throw(InvalidAccessError, 'does not match'); + }); + + it('throws an error if an EC private key is specified as the key', () => { + const secp256k1PrivateKey: PrivateKeyJwk = { kty: 'EC', crv: 'secp256k1', d: '', x: '', y: '' }; + expect(() => alg.checkSecretKey({ + key: secp256k1PrivateKey + })).to.throw(InvalidAccessError, 'operation is only valid'); + }); + + it('throws an error if a public key is specified as the key', () => { + const secp256k1PublicKey: PublicKeyJwk = { kty: 'EC', crv: 'secp256k1', x: '', y: '' }; + expect(() => alg.checkSecretKey({ + // @ts-expect-error because a public key is being intentionally specified as the key. + key: secp256k1PublicKey + })).to.throw(InvalidAccessError, 'operation is only valid'); }); }); @@ -239,191 +269,196 @@ describe('Algorithms API', () => { describe('sign()', () => { it(`throws an error because 'sign' operation is valid for AES-CTR keys`, async () => { - const alg = TestAesAlgorithm.create(); await expect(alg.sign()).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for'); }); }); describe('verify()', () => { it(`throws an error because 'verify' operation is valid for AES-CTR keys`, async () => { - const alg = TestAesAlgorithm.create(); await expect(alg.verify()).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for'); }); }); + }); - describe('BaseAesCtrAlgorithm', () => { - let alg: BaseAesCtrAlgorithm; + describe('BaseAesCtrAlgorithm', () => { + let alg: BaseAesCtrAlgorithm; - before(() => { - alg = Reflect.construct(BaseAesCtrAlgorithm, []) as BaseAesCtrAlgorithm; + before(() => { + alg = Reflect.construct(BaseAesCtrAlgorithm, []) as BaseAesCtrAlgorithm; + // @ts-expect-error because the `names` property is readonly. + alg.names = ['A128CTR', 'A192CTR', 'A256CTR'] as const; + }); + + describe('checkAlgorithmOptions()', () => { + it('does not throw with matching algorithm name and valid counter and length', () => { + expect(() => alg.checkAlgorithmOptions({ + algorithm: { + name : 'A128CTR', + counter : new Uint8Array(16), + length : 128 + } + })).to.not.throw(); }); - let dataEncryptionKey: Web5Crypto.CryptoKey; + it('throws an error when unsupported algorithm specified', () => { + expect(() => alg.checkAlgorithmOptions({ + algorithm: { + name : 'invalid-name', + counter : new Uint8Array(16), + length : 128 + } + })).to.throw(NotSupportedError, 'Algorithm not supported'); + }); - beforeEach(() => { - dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new Uint8Array(32), 'secret', ['encrypt', 'decrypt']); - }); - - describe('checkAlgorithmOptions()', () => { - it('does not throw with matching algorithm name and valid counter and length', () => { - expect(() => alg.checkAlgorithmOptions({ - algorithm: { - name : 'AES-CTR', - counter : new Uint8Array(16), - length : 128 - }, - key: dataEncryptionKey - })).to.not.throw(); - }); + it('throws an error if the counter property is missing', () => { + // @ts-expect-error because `counter` property is intentionally omitted. + expect(() => alg.checkAlgorithmOptions({ algorithm: { + name : 'A128CTR', + length : 128 + }})).to.throw(TypeError, 'Required parameter missing'); + }); - it('throws an error when unsupported algorithm specified', () => { - expect(() => alg.checkAlgorithmOptions({ - algorithm: { - name : 'invalid-name', - counter : new Uint8Array(16), - length : 128 - }, - key: dataEncryptionKey - })).to.throw(NotSupportedError, 'Algorithm not supported'); - }); + it('accepts counter as Uint8Array', () => { + const data = new Uint8Array(16); + const algorithm: { name?: string, counter?: any, length?: number } = {}; + algorithm.name = 'A128CTR'; + algorithm.length = 128; - it('throws an error if the counter property is missing', () => { - // @ts-expect-error because `counter` property is intentionally omitted. - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name : 'AES-CTR', - length : 128 - }})).to.throw(TypeError, 'Required parameter missing'); - }); + // TypedArray - Uint8Array + algorithm.counter = data; + expect(() => alg.checkAlgorithmOptions({ + algorithm: algorithm as Web5Crypto.AesCtrOptions, + })).to.not.throw(); + }); - it('accepts counter as Uint8Array', () => { - const data = new Uint8Array(16); - const algorithm: { name?: string, counter?: any, length?: number } = {}; - algorithm.name = 'AES-CTR'; - algorithm.length = 128; - - // TypedArray - Uint8Array - algorithm.counter = data; - expect(() => alg.checkAlgorithmOptions({ - algorithm : algorithm as Web5Crypto.AesCtrOptions, - key : dataEncryptionKey - })).to.not.throw(); - }); + it('throws error if counter is not acceptable data type', () => { + expect(() => alg.checkAlgorithmOptions({ + algorithm: { + name : 'A128CTR', + // @ts-expect-error because counter is being intentionally set to the wrong data type to trigger an error. + counter : new Set([...Array(16).keys()].map(n => n.toString(16))), + length : 128 + }, + })).to.throw(TypeError, 'is not of type'); + }); - it('throws error if counter is not acceptable data type', () => { - expect(() => alg.checkAlgorithmOptions({ - algorithm: { - name : 'AES-CTR', - // @ts-expect-error because counter is being intentionally set to the wrong data type to trigger an error. - counter : new Set([...Array(16).keys()].map(n => n.toString(16))), - length : 128 - }, - key: dataEncryptionKey - })).to.throw(TypeError, 'is not of type'); - }); + it('throws error if initial value of the counter block is not 16 bytes', () => { + expect(() => alg.checkAlgorithmOptions({ + algorithm: { + name : 'A128CTR', + counter : new Uint8Array(128), + length : 128 + } + })).to.throw(OperationError, 'must have length'); + }); - it('throws error if initial value of the counter block is not 16 bytes', () => { - expect(() => alg.checkAlgorithmOptions({ - algorithm: { - name : 'AES-CTR', - counter : new Uint8Array(128), - length : 128 - }, - key: dataEncryptionKey - })).to.throw(OperationError, 'must have length'); - }); + it('throws an error if the length property is missing', () => { + // @ts-expect-error because lengthy property was intentionally omitted. + expect(() => alg.checkAlgorithmOptions({ algorithm: { + name : 'A128CTR', + counter : new Uint8Array(16) + }})).to.throw(TypeError, `Required parameter missing: 'length'`); + }); - it('throws an error if the length property is missing', () => { - // @ts-expect-error because lengthy property was intentionally omitted. - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name : 'AES-CTR', - counter : new Uint8Array(16) - }})).to.throw(TypeError, `Required parameter missing: 'length'`); - }); + it('throws an error if length is not a Number', () => { + expect(() => alg.checkAlgorithmOptions({ algorithm: { + name : 'A128CTR', + counter : new Uint8Array(16), + // @ts-expect-error because length is being intentionally specified as a string instead of a number. + length : '128' + }})).to.throw(TypeError, 'is not of type'); + }); - it('throws an error if length is not a Number', () => { - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name : 'AES-CTR', + it('throws an error if length is not between 1 and 128', () => { + expect(() => alg.checkAlgorithmOptions({ + algorithm: { + name : 'A128CTR', counter : new Uint8Array(16), - // @ts-expect-error because length is being intentionally specified as a string instead of a number. - length : '128' - }})).to.throw(TypeError, 'is not of type'); - }); - - it('throws an error if length is not between 1 and 128', () => { - expect(() => alg.checkAlgorithmOptions({ - algorithm: { - name : 'AES-CTR', - counter : new Uint8Array(16), - length : 0 - }, - key: dataEncryptionKey - })).to.throw(OperationError, 'should be in the range'); - - expect(() => alg.checkAlgorithmOptions({ - algorithm: { - name : 'AES-CTR', - counter : new Uint8Array(16), - length : 256 - }, - key: dataEncryptionKey - })).to.throw(OperationError, 'should be in the range'); - }); + length : 0 + } + })).to.throw(OperationError, 'should be in the range'); - it('throws an error if the key property is missing', () => { - // @ts-expect-error because key property was intentionally omitted. - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name : 'AES-CTR', + expect(() => alg.checkAlgorithmOptions({ + algorithm: { + name : 'A128CTR', counter : new Uint8Array(16), - length : 64 - }})).to.throw(TypeError, `Required parameter missing: 'key'`); - }); + length : 256 + } + })).to.throw(OperationError, 'should be in the range'); + }); + }); - it('throws an error if the given key is not valid', () => { - // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. - delete dataEncryptionKey.extractable; - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, - key : dataEncryptionKey - })).to.throw(TypeError, 'Object is not a CryptoKey'); - }); + describe('checkDecryptOptions()', () => { + let algorithm: Web5Crypto.AesCtrOptions; + let dataEncryptionKey: PrivateKeyJwk; - it('throws an error if the algorithm of the key does not match', () => { - const dataEncryptionKey = new CryptoKey({ name: 'non-existent-algorithm', length: 128 }, false, new Uint8Array(32), 'secret', ['encrypt', 'decrypt']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, - key : dataEncryptionKey - })).to.throw(InvalidAccessError, 'does not match'); - }); + beforeEach(() => { + algorithm = { name: 'A128CTR', counter: new Uint8Array(16), length: 128 }; + dataEncryptionKey = { kty: 'oct', k: Convert.uint8Array(new Uint8Array(16)).toBase64Url() }; + }); - it('throws an error if a private key is specified as the key', () => { - const dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new Uint8Array(32), 'private', ['encrypt', 'decrypt']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, - key : dataEncryptionKey - })).to.throw(InvalidAccessError, 'Requested operation is not valid'); - }); + it('validates that data is a Uint8Array', async () => { + expect(() => alg.checkDecryptOptions({ + algorithm, + key : dataEncryptionKey, + // @ts-expect-error because invalid data type intentionally specified. + data : 'baz' + })).to.throw(TypeError, `data must be of type Uint8Array`); + }); - it('throws an error if a public key is specified as the key', () => { - const dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new Uint8Array(32), 'public', ['encrypt', 'decrypt']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, - key : dataEncryptionKey - })).to.throw(InvalidAccessError, 'Requested operation is not valid'); - }); + it(`if specified, validates that 'key_opts' includes 'decrypt'`, async () => { + // Exclude the 'decrypt' operation. + dataEncryptionKey.key_ops = ['encrypt']; + + expect(() => alg.checkDecryptOptions({ + algorithm, + key : dataEncryptionKey, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(InvalidAccessError, 'is not valid for the provided key'); + }); + }); + + describe('checkEncryptOptions()', () => { + let algorithm: Web5Crypto.AesCtrOptions; + let dataEncryptionKey: PrivateKeyJwk; + + beforeEach(() => { + algorithm = { name: 'A128CTR', counter: new Uint8Array(16), length: 128 }; + dataEncryptionKey = { kty: 'oct', k: Convert.uint8Array(new Uint8Array(16)).toBase64Url() }; + }); + + it('validates that data is a Uint8Array', async () => { + expect(() => alg.checkEncryptOptions({ + algorithm, + key : dataEncryptionKey, + // @ts-expect-error because invalid data type intentionally specified. + data : 'baz' + })).to.throw(TypeError, `data must be of type Uint8Array`); + }); + + it(`if specified, validates that 'key_opts' includes 'encrypt'`, async () => { + // Exclude the 'encrypt' operation. + dataEncryptionKey.key_ops = ['decrypt']; + + expect(() => alg.checkEncryptOptions({ + algorithm, + key : dataEncryptionKey, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(InvalidAccessError, 'is not valid for the provided key'); }); }); }); describe('BaseEllipticCurveAlgorithm', () => { class TestEllipticCurveAlgorithm extends BaseEllipticCurveAlgorithm { - public name = 'TestAlgorithm'; - public namedCurves = ['curveA']; - public keyUsages: KeyUsage[] = ['decrypt']; + public names = ['TestAlgorithm'] as const; + public curves = ['secp256k1'] as const; + public keyOperations: JwkOperation[] = ['decrypt']; public async deriveBits(): Promise { return null as any; } - public async generateKey(): Promise { - return { publicKey: {} as any, privateKey: {} as any }; + public async generateKey(): Promise { + return null as any; } public async sign(): Promise { return null as any; @@ -433,252 +468,611 @@ describe('Algorithms API', () => { } } - describe('checkGenerateKey()', () => { + describe('checkGenerateKeyOptions()', () => { let alg: TestEllipticCurveAlgorithm; beforeEach(() => { alg = TestEllipticCurveAlgorithm.create(); }); - it('does not throw with supported algorithm, named curve, and key usage', () => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'TestAlgorithm', namedCurve: 'curveA' }, - keyUsages : ['decrypt'] + it('does not throw with supported algorithm, named curve, and key operation', () => { + expect(() => alg.checkGenerateKeyOptions({ + algorithm : { name: 'TestAlgorithm', curve: 'secp256k1' }, + keyOperations : ['decrypt'] })).to.not.throw(); }); it('throws an error when unsupported algorithm specified', () => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - keyUsages : ['sign'] + expect(() => alg.checkGenerateKeyOptions({ + algorithm : { name: 'invalid-algorithm', curve: 'secp256k1' }, + keyOperations : ['sign'] })).to.throw(NotSupportedError, 'Algorithm not supported'); }); it('throws an error when unsupported named curve specified', () => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'TestAlgorithm', namedCurve: 'X25519' }, - keyUsages : ['sign'] + expect(() => alg.checkGenerateKeyOptions({ + algorithm : { name: 'TestAlgorithm', curve: 'invalid-curve' }, + keyOperations : ['sign'] })).to.throw(TypeError, 'Out of range'); }); it('throws an error when the requested operation is not valid', () => { ['sign', 'verify'].forEach((operation) => { - expect(() => alg.checkGenerateKey({ - algorithm : { name: 'TestAlgorithm', namedCurve: 'curveA' }, - keyUsages : [operation as KeyUsage] + expect(() => alg.checkGenerateKeyOptions({ + algorithm : { name: 'TestAlgorithm', curve: 'secp256k1' }, + keyOperations : [operation as JwkOperation] })).to.throw(InvalidAccessError, 'Requested operation'); }); }); }); + describe('checkSignOptions()', () => { + let alg: TestEllipticCurveAlgorithm; + + beforeEach(() => { + alg = TestEllipticCurveAlgorithm.create(); + }); + + it('validates algorithm name and key algorithm name', async () => { + // Invalid (algorithm name, private key) result in algorithm name check failing first. + expect(() => alg.checkSignOptions({ + algorithm : { name: 'invalid-name' }, + // @ts-expect-error because invalid key intentionally specified. + key : { foo: 'bar '}, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(NotSupportedError, 'Algorithm not supported'); + + // Valid (algorithm name) + Invalid (private key) result in private key check failing first. + expect(() => alg.checkSignOptions({ + algorithm : { name: 'TestAlgorithm' }, + // @ts-expect-error because invalid key intentionally specified. + key : { foo: 'bar '}, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(InvalidAccessError, 'operation is only valid for private keys'); + + // Valid (algorithm name) + Invalid (private key alg) result in private key algorithm check failing first. + expect(() => alg.checkSignOptions({ + algorithm : { name: 'TestAlgorithm' }, + // @ts-expect-error because invalid key algorithm intentionally specified. + key : { kty: 'EC', crv: 'secp256k1', d: '', x: '', y: '', alg: 'invalid-alg' }, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(InvalidAccessError, `does not match the provided 'invalid-alg' key`); + }); + + it('validates that data is a Uint8Array', async () => { + const privateKey: PrivateKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + d : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + x : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + y : Convert.uint8Array(new Uint8Array(32)).toBase64Url() + }; + + // Valid (algorithm name, private key) + Invalid (data) result in the data check failing first. + expect(() => alg.checkSignOptions({ + algorithm : { name: 'TestAlgorithm' }, + key : privateKey, + // @ts-expect-error because invalid data type intentionally specified. + data : 'baz' + })).to.throw(TypeError, `data must be of type Uint8Array`); + }); + + it('validates that key is not a public key', async () => { + const publicKey: PublicKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + x : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + y : Convert.uint8Array(new Uint8Array(32)).toBase64Url() + }; + + // Valid (algorithm name, data) + Invalid (private key) result in key type check failing first. + expect(() => alg.checkSignOptions({ + algorithm : { name: 'TestAlgorithm' }, + // @ts-expect-error because invalid key intentionally specified. + key : publicKey, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(InvalidAccessError, 'operation is only valid for private keys'); + }); + + it(`if specified, validates that 'key_opts' includes 'sign'`, async () => { + // Exclude the 'sign' operation. + const privateKey: PrivateKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + d : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + x : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + y : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + key_ops : ['verify'] + }; + + expect(() => alg.checkSignOptions({ + algorithm : { name: 'TestAlgorithm' }, + key : privateKey, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(InvalidAccessError, 'is not valid for the provided key'); + }); + + it('throws an error when key is an unsupported curve', async () => { + const privateKey: PrivateKeyJwk = { + kty : 'EC', + // @ts-expect-error because an invalid curve is being intentionally specified. + crv : 'invalid-curve', + d : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + x : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + y : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + key_ops : ['verify'] + }; + + expect(() => alg.checkSignOptions({ + algorithm : { name: 'TestAlgorithm' }, + key : privateKey, + data : new Uint8Array([1, 2, 3, 4]) + })).to.throw(TypeError, 'Out of range'); + }); + }); + + describe('checkVerifyOptions()', () => { + let alg: TestEllipticCurveAlgorithm; + let privateKey: PrivateKeyJwk; + let publicKey: PublicKeyJwk; + let signature: Uint8Array; + let data = new Uint8Array([51, 52, 53]); + + beforeEach(() => { + alg = TestEllipticCurveAlgorithm.create(); + + privateKey = { + kty : 'EC', + crv : 'secp256k1', + d : 'XwsSwwmtfxgooR2XsWsvZxeacO1W4koDw3iXxmUivcE', + x : 'Ldwc5EnadPCf-pXe_qWmM7i2-qfYrQXkSCm4aOJ09UQ', + y : 'vL7LbN7q072aRJ5TSpz63cOetIzEDmBR_LwKciPfHZE', + kid : 'ukuZTjeoTyhQk5pScZwj3PDHLUmMffmV5Fey4cS2sMk', + key_ops : [ 'sign' ] + }; + publicKey = { + kty : 'EC', + crv : 'secp256k1', + x : 'Ldwc5EnadPCf-pXe_qWmM7i2-qfYrQXkSCm4aOJ09UQ', + y : 'vL7LbN7q072aRJ5TSpz63cOetIzEDmBR_LwKciPfHZE', + kid : 'ukuZTjeoTyhQk5pScZwj3PDHLUmMffmV5Fey4cS2sMk', + key_ops : [ 'sign' ] + }; + signature = Convert.base64Url('jikTSNWducZQBBDCjonE-OnQaUc3A0oFnCcWWF5N2OV2AYID4iGSTrdPw9jgXISBhojZ1kYeeu4_6YvV26A6GQ').toUint8Array(); + }); + + it('validates algorithm name and key algorithm name', async () => { + // Invalid (algorithm name, public key) result in algorithm name check failing first. + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'invalid-name' }, + // @ts-expect-error because invalid key intentionally specified. + key : { foo: 'bar '}, + signature, + data + })).to.throw(NotSupportedError, 'Algorithm not supported'); + + // Valid (algorithm name) + Invalid (public key) result in public key check failing first. + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'TestAlgorithm' }, + // @ts-expect-error because invalid key intentionally specified. + key : { foo: 'bar '}, + signature, + data + })).to.throw(InvalidAccessError, 'operation is only valid for public keys'); + + // Valid (algorithm name) + Invalid (public key alg) result in public key algorithm check failing first. + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'TestAlgorithm' }, + // @ts-expect-error because invalid key intentionally specified. + key : { ...publicKey, alg: 'invalid-alg' }, + signature, + data + })).to.throw(InvalidAccessError, `does not match the provided 'invalid-alg' key`); + }); + + it('validates that key is not a private key', async () => { + // Valid (algorithm name, hash algorithm, signature, data) + Invalid (public key) result in key type check failing first. + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'TestAlgorithm' }, + // @ts-expect-error because invalid key intentionally specified. + key : privateKey, + signature : signature, + data : data + })).to.throw(InvalidAccessError, 'operation is only valid for public keys'); + }); + + it(`if specified, validates that 'key_ops' includes 'verify'`, async () => { + // Manually specify the public key operations to exclude the 'verify' operation. + const key: PublicKeyJwk = { ...publicKey, key_ops: ['sign'] }; + + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'TestAlgorithm' }, + key, + signature : signature, + data : data + })).to.throw(InvalidAccessError, 'is not valid for the provided key'); + }); + + it('throws an error when key is an unsupported curve', async () => { + // Manually change the key's curve to trigger an error. + // @ts-expect-error because an invalid curve is being intentionally specified. + const key: PublicKeyJwk = { ...publicKey, crv: 'invalid-curve' }; + + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'TestAlgorithm' }, + data : data, + key, + signature + })).to.throw(TypeError, 'Out of range'); + }); + + it('validates that data is a Uint8Array', async () => { + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'TestAlgorithm' }, + key : publicKey, + // @ts-expect-error because invalid data type intentionally specified. + data : 'baz', + signature + })).to.throw(TypeError, `data must be of type Uint8Array`); + }); + + it('validates that signature is a Uint8Array', async () => { + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'TestAlgorithm' }, + key : publicKey, + data, + // @ts-expect-error because invalid data type intentionally specified. + signature : 'baz' + })).to.throw(TypeError, `signature must be of type Uint8Array`); + }); + }); + describe('decrypt()', () => { - it(`throws an error because 'decrypt' operation is valid for AES-CTR keys`, async () => { + it(`throws an error because 'decrypt' operation is valid for Elliptic Curve algorithms`, async () => { const alg = TestEllipticCurveAlgorithm.create(); await expect(alg.decrypt()).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for'); }); }); describe('encrypt()', () => { - it(`throws an error because 'encrypt' operation is valid for AES-CTR keys`, async () => { + it(`throws an error because 'encrypt' operation is valid for Elliptic Curve algorithms`, async () => { const alg = TestEllipticCurveAlgorithm.create(); await expect(alg.encrypt()).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for'); }); }); + }); - describe('BaseEcdhAlgorithm', () => { - let alg: BaseEcdhAlgorithm; + describe('BaseEcdhAlgorithm', () => { + let alg: BaseEcdhAlgorithm; - before(() => { - alg = Reflect.construct(BaseEcdhAlgorithm, []) as BaseEcdhAlgorithm; - }); + before(() => { + alg = Reflect.construct(BaseEcdhAlgorithm, []) as BaseEcdhAlgorithm; + // @ts-expect-error because the `names` property is readonly. + alg.names = ['ECDH'] as const; + }); - describe('checkAlgorithmOptions()', () => { + describe('checkDeriveBitsOptions()', () => { + let otherPartyPublicKey: PublicKeyJwk; + let ownPrivateKey: PrivateKeyJwk; - let otherPartyPublicKey: Web5Crypto.CryptoKey; - let ownPrivateKey: Web5Crypto.CryptoKey; + beforeEach(() => { + otherPartyPublicKey = { + kty : 'OKP', + crv : 'X25519', + x : Convert.uint8Array(new Uint8Array(32)).toBase64Url() + }; + ownPrivateKey = { + kty : 'OKP', + crv : 'X25519', + x : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + d : Convert.uint8Array(new Uint8Array(32)).toBase64Url() + }; + }); - beforeEach(() => { - otherPartyPublicKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); - ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); - }); + it('does not throw with matching algorithm name and valid publicKey and baseKey', () => { + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.not.throw(); + }); - it('does not throw with matching algorithm name and valid publicKey and baseKey', () => { - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.not.throw(); - }); + it('throws an error when unsupported algorithm specified', () => { + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'non-existent-algorithm', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.throw(NotSupportedError, 'Algorithm not supported'); + }); - it('throws an error when unsupported algorithm specified', () => { - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'non-existent-algorithm', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.throw(NotSupportedError, 'Algorithm not supported'); - }); + it('throws an error if the publicKey property is missing', () => { + expect(() => alg.checkDeriveBitsOptions({ + // @ts-expect-error because `publicKey` property is intentionally omitted. + algorithm : { name: 'ECDH' }, + baseKey : ownPrivateKey + })).to.throw(TypeError, `Required parameter missing: 'publicKey'`); + }); - it('throws an error if the publicKey property is missing', () => { - expect(() => alg.checkAlgorithmOptions({ - // @ts-expect-error because `publicKey` property is intentionally omitted. - algorithm : { name: 'ECDH' }, - baseKey : ownPrivateKey - })).to.throw(TypeError, `Required parameter missing: 'publicKey'`); - }); + it('throws an error if the given publicKey is not valid', () => { + const { kty, ...otherPartyPublicKeyMissingKeyType } = otherPartyPublicKey as JwkParamsEcPublic; + expect(() => alg.checkDeriveBitsOptions({ + // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKeyMissingKeyType }, + baseKey : ownPrivateKey + })).to.throw(TypeError, 'Object is not a JSON Web Key'); - it('throws an error if the given publicKey is not valid', () => { + const { crv, ...otherPartyPublicKeyMissingCurve } = otherPartyPublicKey as JwkParamsEcPublic; + expect(() => alg.checkDeriveBitsOptions({ // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. - delete otherPartyPublicKey.extractable; - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.throw(TypeError, 'Object is not a CryptoKey'); - }); + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKeyMissingCurve }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, 'Requested operation is only valid for public keys'); - it('throws an error if the algorithm of the publicKey does not match', () => { - const otherPartyPublicKey = new CryptoKey({ name: 'Nope', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.throw(InvalidAccessError, 'does not match'); - }); + const { x, ...otherPartyPublicKeyMissingX } = otherPartyPublicKey as JwkParamsEcPublic; + expect(() => alg.checkDeriveBitsOptions({ + // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKeyMissingX }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, 'Requested operation is only valid for public keys'); + }); - it('throws an error if a private key is specified as the publicKey', () => { - const ecdhPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: ecdhPrivateKey }, - baseKey : ownPrivateKey - })).to.throw(InvalidAccessError, 'Requested operation is not valid'); - }); + it('throws an error if the key type of the publicKey is not EC or OKP', () => { + otherPartyPublicKey.kty = 'RSA'; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, 'Key type of the provided key must be'); + }); - it('throws an error if the baseKey property is missing', () => { - // @ts-expect-error because `baseKey` property is intentionally omitted. - expect(() => alg.checkAlgorithmOptions({ - algorithm: { name: 'ECDH', publicKey: otherPartyPublicKey } - })).to.throw(TypeError, `Required parameter missing: 'baseKey'`); - }); + it(`does not throw if publicKey 'key_ops' is undefined`, async () => { + delete otherPartyPublicKey.key_ops; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.not.throw(); + }); + + it('throws an error if a private key is specified as the publicKey', () => { + expect(() => alg.checkDeriveBitsOptions({ + // @ts-expect-error since a private key is being intentionally provided to trigger the error. + algorithm : { name: 'ECDH', publicKey: ownPrivateKey }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, 'Requested operation is only valid'); + }); + + it('throws an error if the baseKey property is missing', () => { + // @ts-expect-error because `baseKey` property is intentionally omitted. + expect(() => alg.checkDeriveBitsOptions({ + algorithm: { name: 'ECDH', publicKey: otherPartyPublicKey } + })).to.throw(TypeError, `Required parameter missing: 'baseKey'`); + }); - it('throws an error if the given baseKey is not valid', () => { + it('throws an error if the given baseKey is not valid', () => { + const { kty, ...ownPrivateKeyMissingKeyType } = ownPrivateKey as JwkParamsEcPrivate; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. - delete ownPrivateKey.extractable; - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.throw(TypeError, 'Object is not a CryptoKey'); - }); + baseKey : ownPrivateKeyMissingKeyType + })).to.throw(TypeError, 'Object is not a JSON Web Key'); - it('throws an error if the algorithm of the baseKey does not match', () => { - const ownPrivateKey = new CryptoKey({ name: 'non-existent-algorithm', namedCurve: 'X25519' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.throw(InvalidAccessError, 'does not match'); - }); + const { crv, ...ownPrivateKeyMissingCurve } = ownPrivateKey as JwkParamsEcPrivate; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. + baseKey : ownPrivateKeyMissingCurve + })).to.throw(InvalidAccessError, 'Requested operation is only valid for private keys'); - it('throws an error if a public key is specified as the baseKey', () => { - const ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.throw(InvalidAccessError, 'Requested operation is not valid'); - }); + const { x, ...ownPrivateKeyMissingX } = ownPrivateKey as JwkParamsEcPrivate; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. + baseKey : ownPrivateKeyMissingX + })).to.throw(InvalidAccessError, 'Requested operation is only valid for private keys'); - it('throws an error if the named curve of the public and base keys does not match', () => { - const ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'secp256k1' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); - expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey - })).to.throw(InvalidAccessError, `named curve of the publicKey and baseKey must match`); - }); + const { d, ...ownPrivateKeyMissingD } = ownPrivateKey as JwkParamsEcPrivate; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. + baseKey : ownPrivateKeyMissingD + })).to.throw(InvalidAccessError, 'Requested operation is only valid for private keys'); }); - describe('sign()', () => { - it(`throws an error because 'sign' operation is valid for ECDH keys`, async () => { - await expect(alg.sign()).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for ECDH'); - }); + it('throws an error if the key type of the baseKey is not EC or OKP', () => { + ownPrivateKey.kty = 'RSA'; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, 'Key type of the provided key must be'); + }); + + it(`does not throw if baseKey 'key_ops' is undefined`, async () => { + delete ownPrivateKey.key_ops; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.not.throw(); }); - describe('verify()', () => { - it(`throws an error because 'verify' operation is valid for ECDH keys`, async () => { - await expect(alg.verify()).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for ECDH'); + it('throws an error if a public key is specified as the baseKey', () => { + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + // @ts-expect-error because public key is being provided instead of private key. + baseKey : otherPartyPublicKey + })).to.throw(InvalidAccessError, 'Requested operation is only valid for private keys'); + }); + + it('throws an error if the key type of the public and base keys does not match', () => { + ownPrivateKey.kty = 'EC'; + otherPartyPublicKey.kty = 'OKP'; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, `key type of the publicKey and baseKey must match`); + }); + + it('throws an error if the curve of the public and base keys does not match', () => { + (ownPrivateKey as JwkParamsEcPrivate).crv = 'secp256k1'; + (otherPartyPublicKey as JwkParamsOkpPublic).crv = 'X25519'; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, `curve of the publicKey and baseKey must match`); + }); + + ['baseKey', 'publicKey'].forEach(keyType => { + describe(`if ${keyType} 'key_ops' is specified`, () => { + it(`does not throw if 'key_ops' is valid`, () => { + const key = keyType === 'baseKey' ? ownPrivateKey : otherPartyPublicKey; + key.key_ops = ['deriveBits']; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.not.throw(); + }); + + it(`throws an error if 'key_ops' property is an empty array`, () => { + const key = keyType === 'baseKey' ? ownPrivateKey : otherPartyPublicKey; + key.key_ops = []; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.throw(InvalidAccessError, `is not valid for the provided key`); + }); + + it(`throws an error if the 'key_ops' property is not an array`, () => { + const key = keyType === 'baseKey' ? ownPrivateKey : otherPartyPublicKey; + key.key_ops = 'deriveBits' as any; // Intentionally incorrect type + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey + })).to.throw(TypeError, `is not of type Array.`); + }); + + it(`throws an error if the 'key_ops' property contains an invalid operation`, () => { + const key = keyType === 'baseKey' ? ownPrivateKey : otherPartyPublicKey; + key.key_ops = ['sign']; + expect(() => alg.checkDeriveBitsOptions({ + algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, + baseKey : ownPrivateKey, + })).to.throw(InvalidAccessError, `is not valid for the provided key`); + }); }); }); }); - describe('BaseEcdsaAlgorithm', () => { - let alg: BaseEcdsaAlgorithm; + describe('sign()', () => { + it(`throws an error because 'sign' operation is not valid for ECDH`, async () => { + await expect(alg.sign()).to.eventually.be.rejectedWith(InvalidAccessError, `is not valid for ECDH`); + }); + }); - before(() => { - alg = Reflect.construct(BaseEcdsaAlgorithm, []) as BaseEcdsaAlgorithm; - // @ts-expect-error because `hashAlgorithms` is a read-only property. - alg.hashAlgorithms = ['SHA-256']; + describe('verify()', () => { + it(`throws an error because 'verify' operation is not valid for ECDH`, async () => { + await expect(alg.verify()).to.eventually.be.rejectedWith(InvalidAccessError, `is not valid for ECDH`); }); + }); + }); - describe('checkAlgorithmOptions()', () => { - it('does not throw with matching algorithm name and valid hash algorithm', () => { - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name : 'ECDSA', - hash : 'SHA-256' - }})).to.not.throw(); - }); + describe('BaseEcdsaAlgorithm', () => { + let alg: BaseEcdsaAlgorithm; - it('throws an error when unsupported algorithm specified', () => { - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name : 'Nope', - hash : 'SHA-256' - }})).to.throw(NotSupportedError, 'Algorithm not supported'); - }); + before(() => { + alg = Reflect.construct(BaseEcdsaAlgorithm, []) as BaseEcdsaAlgorithm; + // @ts-expect-error because the `names` property is readonly. + alg.names = ['ES256K'] as const; + // @ts-expect-error because the `curves` property is readonly. + alg.curves = ['secp256k1'] as const; + }); - it('throws an error if the hash property is missing', () => { - // @ts-expect-error because `hash` property is intentionally omitted. - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name: 'ECDSA', - }})).to.throw(TypeError, 'Required parameter missing'); - }); + describe('checkSignOptions()', () => { + it('validates that key is an EC private key', async () => { + const ed25519PrivateKey: PrivateKeyJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : 'k-DgyL6dBSdblokVYrYfJhSAEbf3gx68YSTwtqAaMis', + d : 'VF2v7AbPoDwuuTcV-M6mB_C7SYIDB4E0ImvGM3t0VAE' + }; - it('throws an error if the given hash algorithm is not supported', () => { - const ecdhPublicKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); - // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. - delete ecdhPublicKey.extractable; - expect(() => alg.checkAlgorithmOptions({ algorithm: { - name : 'ECDSA', - hash : 'SHA-1234' - }})).to.throw(TypeError, 'Out of range'); - }); + expect(() => alg.checkSignOptions({ + algorithm : { name: 'ES256K' }, + key : ed25519PrivateKey, + data : new Uint8Array([51, 52, 53]) + })).to.throw(InvalidAccessError, 'operation is only valid for EC private keys'); }); + }); - describe('deriveBits()', () => { - it(`throws an error because 'deriveBits' operation is valid for ECDSA keys`, async () => { - await expect(alg.deriveBits()).to.eventually.be.rejectedWith(InvalidAccessError, `is not valid for ECDSA`); - }); + describe('checkVerifyOptions()', () => { + it('validates that key is an EC public key', async () => { + const ed25519PublicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : 'k-DgyL6dBSdblokVYrYfJhSAEbf3gx68YSTwtqAaMis', + }; + + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'ES256K' }, + key : ed25519PublicKey, + data : new Uint8Array(), + signature : new Uint8Array() + })).to.throw(InvalidAccessError, 'operation is only valid for EC public keys'); }); }); - describe('BaseEdDsaAlgorithm', () => { - let alg: BaseEdDsaAlgorithm; - - before(() => { - alg = Reflect.construct(BaseEdDsaAlgorithm, []) as BaseEdDsaAlgorithm; + describe('deriveBits()', () => { + it(`throws an error because 'deriveBits' operation is not valid for ECDSA algorithm`, async () => { + await expect(alg.deriveBits()).to.eventually.be.rejectedWith(InvalidAccessError, `is not valid for ECDSA`); }); + }); + }); - describe('checkAlgorithmOptions()', () => { - const testEdDsaAlgorithm = Reflect.construct(BaseEdDsaAlgorithm, []) as BaseEdDsaAlgorithm; + describe('BaseEdDsaAlgorithm', () => { + let alg: BaseEdDsaAlgorithm; - it('does not throw with matching algorithm name', () => { - expect(() => testEdDsaAlgorithm.checkAlgorithmOptions({ algorithm: { - name: 'EdDSA' - }})).to.not.throw(); - }); + before(() => { + alg = Reflect.construct(BaseEdDsaAlgorithm, []) as BaseEdDsaAlgorithm; + // @ts-expect-error because the `names` property is readonly. + alg.names = ['EdDSA'] as const; + // @ts-expect-error because the `curves` property is readonly. + alg.curves = ['Ed25519'] as const; + }); - it('throws an error when unsupported algorithm specified', () => { - expect(() => testEdDsaAlgorithm.checkAlgorithmOptions({ algorithm: { - name: 'Nope' - }})).to.throw(NotSupportedError, 'Algorithm not supported'); - }); + describe('checkSignOptions()', () => { + it('validates that key is an OKP private key', async () => { + const secp256k1PrivateKey: PrivateKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + x : 'UxYbeCQo17viyn9Bb5frn80_icQ0dHaRNsjfjZDaxDo', + y : '5vg_APq25qhV1wkbEqT3Z1H8vt57iHDhQqsw9TN0M1E', + d : 'O2-jjd6m16BXjxTp-UudzZNIkRHQwUYN0KJg3i5Ndko' + }; + + expect(() => alg.checkSignOptions({ + algorithm : { name: 'EdDSA' }, + key : secp256k1PrivateKey, + data : new Uint8Array([51, 52, 53]) + })).to.throw(InvalidAccessError, 'operation is only valid for OKP private keys'); }); + }); - describe('deriveBits()', () => { - it(`throws an error because 'deriveBits' operation is valid for EdDSA keys`, async () => { - await expect(alg.deriveBits()).to.eventually.be.rejectedWith(InvalidAccessError, `is not valid for EdDSA`); - }); + describe('checkVerifyOptions()', () => { + it('validates that key is an OKP public key', async () => { + const secp256k1PublicKey: PublicKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + x : 'UxYbeCQo17viyn9Bb5frn80_icQ0dHaRNsjfjZDaxDo', + y : '5vg_APq25qhV1wkbEqT3Z1H8vt57iHDhQqsw9TN0M1E' + }; + + expect(() => alg.checkVerifyOptions({ + algorithm : { name: 'EdDSA' }, + key : secp256k1PublicKey, + data : new Uint8Array(), + signature : new Uint8Array() + })).to.throw(InvalidAccessError, 'operation is only valid for OKP public keys'); + }); + }); + + describe('deriveBits()', () => { + it(`throws an error because 'deriveBits' operation is not valid for EdDSA keys`, async () => { + await expect(alg.deriveBits()).to.eventually.be.rejectedWith(InvalidAccessError, `is not valid for EdDSA`); }); }); }); @@ -688,16 +1082,22 @@ describe('Algorithms API', () => { before(() => { alg = Reflect.construct(BasePbkdf2Algorithm, []) as BasePbkdf2Algorithm; + // @ts-expect-error because the `names` property is readonly. + alg.names = ['PBKDF2' as const]; // @ts-expect-error because `hashAlgorithms` is a read-only property. alg.hashAlgorithms = ['SHA-256']; }); describe('checkAlgorithmOptions()', () => { - let baseKey: Web5Crypto.CryptoKey; + let baseKey: PrivateKeyJwk; beforeEach(() => { - baseKey = new CryptoKey({ name: 'PBKDF2' }, false, new Uint8Array(32), 'secret', ['deriveBits', 'deriveKey']); + baseKey = { + kty : 'oct', + k : Convert.uint8Array(new Uint8Array(32)).toBase64Url(), + key_ops : ['deriveBits', 'deriveKey'] + }; }); it('does not throw with matching algorithm name and valid hash, iterations, and salt', () => { @@ -824,7 +1224,7 @@ describe('Algorithms API', () => { it('throws an error if the given key is not valid', () => { // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. - delete baseKey.extractable; + delete baseKey.kty; expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'PBKDF2', @@ -833,11 +1233,15 @@ describe('Algorithms API', () => { salt : new Uint8Array(16) }, baseKey - })).to.throw(TypeError, 'Object is not a CryptoKey'); + })).to.throw(TypeError, 'Object is not a JSON Web Key'); }); - it('throws an error if the algorithm of the key does not match', () => { - const baseKey = new CryptoKey({ name: 'wrong-algorithm' }, false, new Uint8Array(32), 'secret', ['deriveBits', 'deriveKey']); + it('throws an error if the key type of the key is not valid', () => { + const baseKey: PrivateKeyJwk = { + kty : 'OKP', + // @ts-expect-error because OKP JWKs don't have a k parameter. + k : Convert.uint8Array(new Uint8Array(32)).toBase64Url() + }; expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'PBKDF2', @@ -846,56 +1250,7 @@ describe('Algorithms API', () => { salt : new Uint8Array(16) }, baseKey - })).to.throw(InvalidAccessError, 'does not match'); - }); - }); - - describe('checkImportKey()', () => { - it('should not throw when all options are valid', () => { - expect(() => alg.checkImportKey({ - algorithm : { name: 'PBKDF2' }, - format : 'raw', - extractable : false, - keyUsages : ['deriveBits'] - })).to.not.throw(); - }); - - it('throws an error when unsupported algorithm specified', () => { - expect(() => alg.checkImportKey({ - algorithm : { name: 'ECDH' }, - format : 'raw', - extractable : false, - keyUsages : ['deriveBits'] - })).to.throw(NotSupportedError, 'Algorithm not supported'); - }); - - it('throws an error if the format is not raw', () => { - expect(() => alg.checkImportKey({ - algorithm : { name: 'PBKDF2' }, - format : 'pkcs8', // Invalid, only 'raw' is supported - extractable : false, - keyUsages : ['deriveBits'] - })).to.throw(SyntaxError, `Only 'raw' is supported`); - }); - - it('throws an error if extractable is not false', () => { - expect(() => alg.checkImportKey({ - algorithm : { name: 'PBKDF2' }, - format : 'raw', - extractable : true, - keyUsages : ['deriveBits'] - })).to.throw(SyntaxError, `Only 'false' is supported`); - }); - - it('throws an error when the requested operation is not valid', () => { - ['sign', 'verify'].forEach((operation) => { - expect(() => alg.checkImportKey({ - algorithm : { name: 'PBKDF2' }, - format : 'raw', - extractable : false, - keyUsages : [operation as KeyUsage] - })).to.throw(InvalidAccessError, 'Requested operation'); - }); + })).to.throw(InvalidAccessError, 'Key type of the provided key must be'); }); }); diff --git a/packages/crypto/tests/crypto-algorithms.spec.ts b/packages/crypto/tests/crypto-algorithms.spec.ts index d62c8b200..1c815dc8a 100644 --- a/packages/crypto/tests/crypto-algorithms.spec.ts +++ b/packages/crypto/tests/crypto-algorithms.spec.ts @@ -1,13 +1,13 @@ -import type { Web5Crypto } from '../src/types/web5-crypto.js'; - import sinon from 'sinon'; import chai, { expect } from 'chai'; import { Convert } from '@web5/common'; import chaiAsPromised from 'chai-as-promised'; +import type { JwkParamsOctPrivate, PrivateKeyJwk, PublicKeyJwk } from '../src/jose.js'; + import { aesCtrTestVectors } from './fixtures/test-vectors/aes.js'; import { AesCtr, Ed25519, Secp256k1, X25519 } from '../src/crypto-primitives/index.js'; -import { CryptoKey, InvalidAccessError, NotSupportedError, OperationError } from '../src/algorithms-api/index.js'; +import { InvalidAccessError, NotSupportedError, OperationError } from '../src/algorithms-api/index.js'; import { EcdhAlgorithm, EcdsaAlgorithm, @@ -28,24 +28,23 @@ describe('Default Crypto Algorithm Implementations', () => { }); describe('decrypt()', () => { - let secretCryptoKey: Web5Crypto.CryptoKey; + let dataEncryptionKey: PrivateKeyJwk; - beforeEach(async () => { - secretCryptoKey = await aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 128 }, - extractable : false, - keyUsages : ['encrypt', 'decrypt'] + before(async () => { + dataEncryptionKey = await aesCtr.generateKey({ + algorithm : { name: 'A128CTR' }, + keyOperations : ['encrypt', 'decrypt'] }); }); it('returns plaintext as a Uint8Array', async () => { const plaintext = await aesCtr.decrypt({ algorithm: { - name : 'AES-CTR', + name : 'A128CTR', counter : new Uint8Array(16), length : 128 }, - key : secretCryptoKey, + key : dataEncryptionKey, data : new Uint8Array([1, 2, 3, 4]) }); @@ -53,247 +52,121 @@ describe('Default Crypto Algorithm Implementations', () => { expect(plaintext.byteLength).to.equal(4); }); - it('returns plaintext given ciphertext', async () => { - let secretCryptoKey: Web5Crypto.CryptoKey; + it('returns expected plaintext given ciphertext input', async () => { + let dataEncryptionKey: PrivateKeyJwk; for (const vector of aesCtrTestVectors) { - secretCryptoKey = new CryptoKey( - { name: 'AES-CTR', length: 128 }, - false, - Convert.hex(vector.key).toUint8Array(), - 'secret', - ['encrypt', 'decrypt'] - ); + dataEncryptionKey = await AesCtr.bytesToPrivateKey({ privateKeyBytes: Convert.hex(vector.key).toUint8Array() }); + const plaintext = await aesCtr.decrypt({ algorithm: { - name : 'AES-CTR', + name : 'A128CTR', counter : Convert.hex(vector.counter).toUint8Array(), length : vector.length }, - key : secretCryptoKey, + key : dataEncryptionKey, data : Convert.hex(vector.ciphertext).toUint8Array() }); expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); } }); - it('validates algorithm, counter, and length', async () => { - const secretCryptoKey: Web5Crypto.CryptoKey = new CryptoKey( - { name: 'AES-CTR', length: 128 }, - false, - new Uint8Array(16), - 'secret', - ['encrypt', 'decrypt'] - ); - - // Invalid (algorithm name, counter, length) result in algorithm name check failing first. - await expect(aesCtr.decrypt({ - algorithm : { name: 'foo', counter: new Uint8Array(64), length: 512 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - - // Valid (algorithm name) + Invalid (counter, length) result counter check failing first. - await expect(aesCtr.decrypt({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(64), length: 512 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(OperationError, `'counter' must have length`); - - // Valid (algorithm name, counter) + Invalid (length) result length check failing first. - await expect(aesCtr.decrypt({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 512 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(OperationError, `'length' should be in the range`); - }); - - it(`validates that key usage is 'decrypt'`, async () => { - // Manually specify the secret key usages to exclude the 'decrypt' operation. - secretCryptoKey.usages = ['encrypt']; - - await expect(aesCtr.decrypt({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); - }); - }); - - describe('encrypt()', () => { - let secretCryptoKey: Web5Crypto.CryptoKey; + describe('encrypt()', () => { + let dataEncryptionKey: PrivateKeyJwk; - before(async () => { - secretCryptoKey = await aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 128 }, - extractable : false, - keyUsages : ['encrypt', 'decrypt'] - }); - }); - - it('returns ciphertext as a Uint8Array', async () => { - const ciphertext = await aesCtr.encrypt({ - algorithm: { - name : 'AES-CTR', - counter : new Uint8Array(16), - length : 128 - }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) + before(async () => { + dataEncryptionKey = await aesCtr.generateKey({ + algorithm : { name: 'A128CTR' }, + keyOperations : ['encrypt', 'decrypt'] + }); }); - expect(ciphertext).to.be.instanceOf(Uint8Array); - expect(ciphertext.byteLength).to.equal(4); - }); - - it('returns ciphertext given plaintext', async () => { - let secretCryptoKey: Web5Crypto.CryptoKey; - for (const vector of aesCtrTestVectors) { - secretCryptoKey = new CryptoKey( - { name: 'AES-CTR', length: 128 }, - false, - Convert.hex(vector.key).toUint8Array(), - 'secret', - ['encrypt', 'decrypt'] - ); + it('returns ciphertext as a Uint8Array', async () => { const ciphertext = await aesCtr.encrypt({ algorithm: { - name : 'AES-CTR', - counter : Convert.hex(vector.counter).toUint8Array(), - length : vector.length + name : 'A128CTR', + counter : new Uint8Array(16), + length : 128 }, - key : secretCryptoKey, - data : Convert.hex(vector.data).toUint8Array() + key : dataEncryptionKey, + data : new Uint8Array([1, 2, 3, 4]) }); - expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext); - } - }); - it('validates algorithm, counter, and length', async () => { - const secretCryptoKey: Web5Crypto.CryptoKey = new CryptoKey( - { name: 'AES-CTR', length: 128 }, - false, - new Uint8Array(16), - 'secret', - ['encrypt', 'decrypt'] - ); - - // Invalid (algorithm name, counter, length) result in algorithm name check failing first. - await expect(aesCtr.encrypt({ - algorithm : { name: 'foo', counter: new Uint8Array(64), length: 512 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); + expect(ciphertext).to.be.instanceOf(Uint8Array); + expect(ciphertext.byteLength).to.equal(4); + }); - // Valid (algorithm name) + Invalid (counter, length) result counter check failing first. - await expect(aesCtr.encrypt({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(64), length: 512 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(OperationError, `'counter' must have length`); - - // Valid (algorithm name, counter) + Invalid (length) result length check failing first. - await expect(aesCtr.encrypt({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 512 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(OperationError, `'length' should be in the range`); - }); - - it(`validates that key usage is 'encrypt'`, async () => { - // Manually specify the secret key usages to exclude the 'encrypt' operation. - secretCryptoKey.usages = ['decrypt']; - - await expect(aesCtr.encrypt({ - algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }, - key : secretCryptoKey, - data : new Uint8Array([1, 2, 3, 4]) - })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); + it('returns expected ciphertext given plaintext input', async () => { + for (const vector of aesCtrTestVectors) { + dataEncryptionKey = await AesCtr.bytesToPrivateKey({ privateKeyBytes: Convert.hex(vector.key).toUint8Array() }); + + const ciphertext = await aesCtr.encrypt({ + algorithm: { + name : 'A128CTR', + counter : Convert.hex(vector.counter).toUint8Array(), + length : vector.length + }, + key : dataEncryptionKey, + data : Convert.hex(vector.data).toUint8Array() + }); + expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext); + } + }); }); }); describe('generateKey()', () => { - it('returns a secret key', async () => { - const key = await aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 128 }, - extractable : false, - keyUsages : ['encrypt', 'decrypt'] + it('returns a private key in JWK format', async () => { + const privateKey = await aesCtr.generateKey({ + algorithm : { name: 'A128CTR' }, + keyOperations : ['encrypt', 'decrypt'] }); - expect(key.algorithm.name).to.equal('AES-CTR'); - expect(key.usages).to.deep.equal(['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']); - expect(key.material.byteLength).to.equal(128 / 8); - }); - - it('secret key is selectively extractable', async () => { - let key: CryptoKey; - // key is NOT extractable if generateKey() called with extractable = false - key = await aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 128 }, - extractable : false, - keyUsages : ['encrypt', 'decrypt'] - }); - expect(key.extractable).to.be.false; + expect(privateKey).to.have.property('alg', 'A128CTR'); + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); - // key is extractable if generateKey() called with extractable = true - key = await aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 128 }, - extractable : true, - keyUsages : ['encrypt', 'decrypt'] - }); - expect(key.extractable).to.be.true; + expect(privateKey.key_ops).to.deep.equal(['encrypt', 'decrypt']); }); - it(`supports 'encrypt', 'decrypt', 'wrapKey', and/or 'unWrapKey' key usages`, async () => { - const operations = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; - for (const operation of operations) { + it(`supports 'A128CTR', 'A192CTR', and 'A256CTR' algorithms`, async () => { + const algorithms = ['A128CTR', 'A192CTR', 'A256CTR']; + for (const algorithm of algorithms) { await expect(aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 128 }, - extractable : true, - keyUsages : [operation as KeyUsage] + algorithm : { name: algorithm }, + keyOperations : ['encrypt', 'decrypt'] })).to.eventually.be.fulfilled; } }); - it('validates algorithm, length, and key usages', async () => { - // Invalid (algorithm name, length, and key usages) result in algorithm name check failing first. - await expect(aesCtr.generateKey({ - algorithm : { name: 'foo', length: 512 }, - extractable : false, - keyUsages : ['sign'] - })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - - // Valid (algorithm name) + Invalid (length, key usages) result length check failing first. - await expect(aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 512 }, - extractable : false, - keyUsages : ['sign'] - })).to.eventually.be.rejectedWith(OperationError, `'length' must be 128, 192, or 256`); - - // Valid (algorithm name, length) + Invalid (key usages) result key usages check failing first. - await expect(aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 256 }, - extractable : false, - keyUsages : ['sign'] - })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation'); + it(`returns keys with the correct bit length`, async () => { + const algorithms = ['A128CTR', 'A192CTR', 'A256CTR']; + for (const algorithm of algorithms) { + const privateKey = await aesCtr.generateKey({ + algorithm : { name: algorithm }, + keyOperations : ['encrypt', 'decrypt'] + }) as JwkParamsOctPrivate; + const privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); + expect(privateKeyBytes.byteLength * 8).to.equal(parseInt(algorithm.slice(1, 4))); + } }); - it(`should throw an error if 'AES-CTR' key generation fails`, async function() { - // @ts-ignore because the method is being intentionally stubbed to return null. - const aesCtrStub = sinon.stub(AesCtr, 'generateKey').returns(Promise.resolve(null)); + it(`throws an error if operation fails`, async function() { + const checkGenerateKeyOptionsStub = sinon.stub(aesCtr, 'checkGenerateKeyOptions').returns(undefined); + // @ts-expect-error because method is being intentionally stubbed to return undefined. + const checkGenerateKeyStub = sinon.stub(AesCtr, 'generateKey').returns(Promise.resolve(undefined)); try { - await aesCtr.generateKey({ - algorithm : { name: 'AES-CTR', length: 128 }, - extractable : false, - keyUsages : ['encrypt', 'decrypt'] - }); - aesCtrStub.restore(); - expect.fail('Expect generateKey() to throw an error'); + // @ts-expect-error because no generateKey operations are defined. + await aesCtr.generateKey({ algorithm: {} }); + expect.fail('Expected aesCtr.generateKey() to throw an error'); } catch (error) { - aesCtrStub.restore(); expect(error).to.be.an('error'); - expect((error as Error).message).to.equal('Operation failed to generate key.'); + expect((error as Error).message).to.include('Operation failed: generateKey'); + } finally { + checkGenerateKeyOptionsStub.restore(); + checkGenerateKeyStub.restore(); } }); }); @@ -308,360 +181,286 @@ describe('Default Crypto Algorithm Implementations', () => { describe('deriveBits()', () => { - let otherPartyPublicKey: Web5Crypto.CryptoKey; - let ownPrivateKey: Web5Crypto.CryptoKey; + let secp256k1PrivateKeyA: PrivateKeyJwk; + let secp256k1PublicKeyA: PublicKeyJwk; + let secp256k1PrivateKeyB: PrivateKeyJwk; + let secp256k1PublicKeyB: PublicKeyJwk; - beforeEach(async () => { - const otherPartyKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['deriveBits'] - }); - otherPartyPublicKey = otherPartyKeyPair.publicKey; + let x25519PrivateKeyA: PrivateKeyJwk; + let x25519PublicKeyA: PublicKeyJwk; + let x25519PrivateKeyB: PrivateKeyJwk; + let x25519PublicKeyB: PublicKeyJwk; - const ownKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['deriveBits'] - }); - ownPrivateKey = ownKeyPair.privateKey; + before(async () => { + secp256k1PrivateKeyA = await ecdh.generateKey({ algorithm: { name: 'ECDH', curve: 'secp256k1' } }); + secp256k1PublicKeyA = await Secp256k1.computePublicKey({ privateKey: secp256k1PrivateKeyA }); + secp256k1PrivateKeyB = await ecdh.generateKey({ algorithm: { name: 'ECDH', curve: 'secp256k1' } }); + secp256k1PublicKeyB = await Secp256k1.computePublicKey({ privateKey: secp256k1PrivateKeyB }); + + x25519PrivateKeyA = await ecdh.generateKey({ algorithm: { name: 'ECDH', curve: 'X25519' } }); + x25519PublicKeyA = await X25519.computePublicKey({ privateKey: x25519PrivateKeyA }); + x25519PrivateKeyB = await ecdh.generateKey({ algorithm: { name: 'ECDH', curve: 'X25519' } }); + x25519PublicKeyB = await X25519.computePublicKey({ privateKey: x25519PrivateKeyB }); }); - it('returns shared secrets with maximum bit length when length is null', async () => { - const sharedSecretSecp256k1 = await ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : null + it(`supports 'secp256k1' curve`, async () => { + const sharedSecret = await ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey : secp256k1PrivateKeyA }); + expect(sharedSecret).to.be.instanceOf(Uint8Array); + expect(sharedSecret.byteLength).to.equal(32); + }); - const otherPartyKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits'] + it(`supports 'X25519' curve`, async () => { + const sharedSecret = await ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: x25519PublicKeyB }, + baseKey : x25519PrivateKeyA }); - otherPartyPublicKey = otherPartyKeyPair.publicKey; + expect(sharedSecret).to.be.instanceOf(Uint8Array); + expect(sharedSecret.byteLength).to.equal(32); + }); - const ownKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits'] - }); - ownPrivateKey = ownKeyPair.privateKey; + it('throws an error when base or public key is an unsupported curve', async () => { + // Manually change the key's curve to trigger an error. + // @ts-expect-error because an unknown 'crv' value is manually set. + const baseKey = { ...secp256k1PrivateKeyA, crv: 'non-existent-curve' } as PrivateKeyJwk; + // @ts-expect-error because an unknown 'crv' value is manually set. + const publicKey = { ...secp256k1PublicKeyB, crv: 'non-existent-curve' } as PublicKeyJwk; - const sharedSecretX25519 = await ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : null - }); - expect(sharedSecretSecp256k1.byteLength).to.equal(32); - expect(sharedSecretX25519.byteLength).to.equal(32); + await expect(ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey }, + baseKey, + length : 40 + })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); }); - it('returns shared secrets with specified length, if possible', async () => { - let sharedSecretSecp256k1 = await ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : 16 + it('returns shared secret with maximum bit length when length is null', async () => { + const sharedSecretSecp256k1 = await ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey : secp256k1PrivateKeyA }); - expect(sharedSecretSecp256k1.byteLength).to.equal(16 / 8); + expect(sharedSecretSecp256k1.byteLength).to.equal(32); + }); - sharedSecretSecp256k1 = await ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : 256 + it('returns shared secret with specified length, if possible', async () => { + [16, 32, 256].forEach(async length => { + let sharedSecretSecp256k1 = await ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey : secp256k1PrivateKeyA, + length + }); + expect(sharedSecretSecp256k1.byteLength).to.equal(length / 8); }); - expect(sharedSecretSecp256k1.byteLength).to.equal(256 / 8); + }); - const otherPartyKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits'] + it('is commutative', async () => { + const sharedSecretSecp256k1 = await ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey : secp256k1PrivateKeyA }); - otherPartyPublicKey = otherPartyKeyPair.publicKey; - - const ownKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits'] + const sharedSecretSecp256k1Reversed = await ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyA }, + baseKey : secp256k1PrivateKeyB }); - ownPrivateKey = ownKeyPair.privateKey; + expect(sharedSecretSecp256k1).to.deep.equal(sharedSecretSecp256k1Reversed); const sharedSecretX25519 = await ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : 32 + algorithm : { name: 'ECDH', publicKey: x25519PublicKeyB }, + baseKey : x25519PrivateKeyA + }); + const sharedSecretX25519Reversed = await ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: x25519PublicKeyA }, + baseKey : x25519PrivateKeyB }); - expect(sharedSecretX25519.byteLength).to.equal(32 / 8); + expect(sharedSecretX25519).to.deep.equal(sharedSecretX25519Reversed); }); it('throws error if requested length exceeds that of the generated shared secret', async () => { - await expect(ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : 264 - })).to.eventually.be.rejectedWith(OperationError, `Requested 'length' exceeds the byte length of the derived secret`); + await expect( + ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey : secp256k1PrivateKeyA, + length : 264 + }) + ).to.eventually.be.rejectedWith(OperationError, `Requested 'length' exceeds the byte length of the derived secret`); }); it('throws an error if the given length is not a multiple of 8', async () => { await expect(ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey : secp256k1PrivateKeyA, length : 127 })).to.eventually.be.rejectedWith(OperationError, `'length' must be a multiple of 8`); }); - it(`supports 'secp256k1' curve`, async () => { - const sharedSecret = await ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : null - }); - expect(sharedSecret).to.be.instanceOf(Uint8Array); - expect(sharedSecret.byteLength).to.equal(32); - }); - - it(`supports 'X25519' curve`, async () => { - const otherPartyKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits'] - }); - otherPartyPublicKey = otherPartyKeyPair.publicKey; - - const ownKeyPair = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits'] - }); - ownPrivateKey = ownKeyPair.privateKey; - - const sharedSecret = await ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : null - }); - - expect(sharedSecret).to.be.instanceOf(Uint8Array); - expect(sharedSecret.byteLength).to.equal(32); - }); - - it(`validates that key usage is 'deriveBits'`, async () => { - // Manually specify the private key usages to exclude the 'deriveBits' operation. - otherPartyPublicKey.usages = ['sign']; + it(`accepts base key without 'key_ops' set`, async () => { + const baseKey = { ...secp256k1PrivateKeyA, key_ops: undefined } as PrivateKeyJwk; await expect(ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : null - })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); + algorithm: { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey + })).to.eventually.be.fulfilled; }); - it('throws an error when key(s) is an unsupported curve', async () => { - // Manually change the key's named curve to trigger an error. - // @ts-expect-error because TS can't determine the type of key. - otherPartyPublicKey.algorithm.namedCurve = 'non-existent-curve'; - // @ts-expect-error because TS can't determine the type of key. - ownPrivateKey.algorithm.namedCurve = 'non-existent-curve'; + it(`accepts public key without 'key_ops' set`, async () => { + const publicKey = { ...secp256k1PublicKeyB, key_ops: undefined } as PublicKeyJwk; await expect(ecdh.deriveBits({ - algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, - baseKey : ownPrivateKey, - length : 40 - })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + algorithm : { name: 'ECDH', publicKey }, + baseKey : secp256k1PrivateKeyA + })).to.eventually.be.fulfilled; }); - }); - describe('generateKey()', () => { - it('returns a key pair', async () => { - const keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] - }); + it(`if specified, validates base key operations includes 'deriveBits'`, async () => { + // Manually specify the base key operations array to exclude the 'deriveBits' operation. + const baseKey = { ...secp256k1PrivateKeyA, key_ops: ['sign'] } as PrivateKeyJwk; - expect(keys).to.have.property('privateKey'); - expect(keys.privateKey.type).to.equal('private'); - expect(keys.privateKey.usages).to.deep.equal(['deriveBits', 'deriveKey']); - - expect(keys).to.have.property('publicKey'); - expect(keys.publicKey.type).to.equal('public'); - expect(keys.publicKey.usages).to.deep.equal(['deriveBits', 'deriveKey']); + await expect(ecdh.deriveBits({ + algorithm: { name: 'ECDH', publicKey: secp256k1PublicKeyB }, + baseKey + })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); }); - it('public key is always extractable', async () => { - let keys: CryptoKeyPair; - // publicKey is extractable if generateKey() called with extractable = false - keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] - }); - expect(keys.publicKey.extractable).to.be.true; + it(`if specified, validates public key operations includes 'deriveBits'`, async () => { + // Manually specify the private key operations array to exclude the 'deriveBits' operation. + const publicKey = { ...secp256k1PublicKeyB, key_ops: ['sign'] } as PublicKeyJwk; - // publicKey is extractable if generateKey() called with extractable = true - keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : true, - keyUsages : ['deriveBits', 'deriveKey'] - }); - expect(keys.publicKey.extractable).to.be.true; + await expect( + ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey }, + baseKey : secp256k1PrivateKeyA + }) + ).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); }); - it('private key is selectively extractable', async () => { - let keys: CryptoKeyPair; - // privateKey is NOT extractable if generateKey() called with extractable = false - keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] - }); - expect(keys.privateKey.extractable).to.be.false; - - // privateKey is extractable if generateKey() called with extractable = true - keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : true, - keyUsages : ['deriveBits', 'deriveKey'] - }); - expect(keys.privateKey.extractable).to.be.true; + it('throws an error if the public/private keys from the same key pair are specified', async () => { + await expect( + ecdh.deriveBits({ + algorithm : { name: 'ECDH', publicKey: secp256k1PublicKeyA }, + baseKey : secp256k1PrivateKeyA + }) + ).to.eventually.be.rejectedWith(Error, 'shared secret cannot be computed from a single key pair'); }); + }); - it(`supports 'secp256k1' curve with compressed public keys, by default`, async () => { - const keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await ecdh.generateKey({ + algorithm : { name: 'ECDH', curve: 'X25519' }, + keyOperations : ['deriveBits', 'deriveKey'] }); - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('compressedPublicKey' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.compressedPublicKey).to.be.true; - }); - - it(`supports 'secp256k1' curve with compressed public keys`, async () => { - const keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'secp256k1', compressedPublicKey: true }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] - }); + expect(privateKey).to.have.property('crv', 'X25519'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('x'); - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('compressedPublicKey' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.compressedPublicKey).to.be.true; + expect(privateKey.key_ops).to.deep.equal(['deriveBits', 'deriveKey']); }); - it(`supports 'secp256k1' curve with uncompressed public keys`, async () => { - const keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'secp256k1', compressedPublicKey: false }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] + it(`supports 'secp256k1' curve`, async () => { + const privateKey = await ecdh.generateKey({ + algorithm : { name: 'ECDH', curve: 'secp256k1' }, + keyOperations : ['deriveBits', 'deriveKey'] }); - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('compressedPublicKey' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.compressedPublicKey).to.be.false; + if (!('crv' in privateKey)) throw new Error; // TS type guard + expect(privateKey.crv).to.equal('secp256k1'); }); it(`supports 'X25519' curve`, async () => { - const keys = await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] + const privateKey = await ecdh.generateKey({ + algorithm : { name: 'ECDH', curve: 'X25519' }, + keyOperations : ['deriveBits', 'deriveKey'] }); - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('X25519'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('X25519'); + if (!('crv' in privateKey)) throw new Error; // TS type guard + expect(privateKey.crv).to.equal('X25519'); }); - it(`supports 'deriveBits' and/or 'deriveKey' key usages`, async () => { + it(`supports 'deriveBits' and/or 'deriveKey' key operations`, async () => { await expect(ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits'] + algorithm : { name: 'ECDH', curve: 'X25519' }, + keyOperations : ['deriveBits'] })).to.eventually.be.fulfilled; await expect(ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveKey'] + algorithm : { name: 'ECDH', curve: 'X25519' }, + keyOperations : ['deriveKey'] })).to.eventually.be.fulfilled; await expect(ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] + algorithm : { name: 'ECDH', curve: 'X25519' }, + keyOperations : ['deriveBits', 'deriveKey'] })).to.eventually.be.fulfilled; }); - it('validates algorithm, named curve, and key usages', async () => { - // Invalid (algorithm name, named curve, and key usages) result in algorithm name check failing first. + it(`accepts 'keyOperations' as undefined`, async () => { + const privateKey = await ecdh.generateKey({ + algorithm: { name: 'ECDH', curve: 'X25519' }, + }); + + expect(privateKey).to.exist; + expect(privateKey.key_ops).to.be.undefined; + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('crv', 'X25519'); + }); + + it('validates algorithm, curve, and key operations', async () => { + // Invalid (algorithm name, named curve, and key operations) result in algorithm name check failing first. await expect(ecdh.generateKey({ - algorithm : { name: 'foo', namedCurve: 'bar' }, - extractable : false, - keyUsages : ['encrypt'] + algorithm : { name: 'foo', curve: 'bar' }, + keyOperations : ['encrypt'] })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - // Valid (algorithm name) + Invalid (named curve, key usages) result named curve check failing first. + // Valid (algorithm name) + Invalid (named curve, key operations) result named curve check failing first. await expect(ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'bar' }, - extractable : false, - keyUsages : ['encrypt'] + algorithm : { name: 'ECDH', curve: 'bar' }, + keyOperations : ['encrypt'] })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); - // Valid (algorithm name, named curve) + Invalid (key usages) result key usages check failing first. + // Valid (algorithm name, named curve) + Invalid (key operations) result key operations check failing first. await expect(ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['encrypt'] + algorithm : { name: 'ECDH', curve: 'X25519' }, + keyOperations : ['encrypt'] })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation'); }); - it(`should throw an error if 'secp256k1' key pair generation fails`, async function() { - // @ts-ignore because the method is being intentionally stubbed to return null. - const secp256k1Stub = sinon.stub(Secp256k1, 'generateKeyPair').returns(Promise.resolve(null)); + it(`throws an error if 'secp256k1' key generation fails`, async function() { + // @ts-ignore because the method is being intentionally stubbed to return undefined. + const secp256k1Stub = sinon.stub(Secp256k1, 'generateKey').returns(Promise.resolve(undefined)); try { await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] + algorithm : { name: 'ECDH', curve: 'secp256k1' }, + keyOperations : ['deriveBits', 'deriveKey'] }); - secp256k1Stub.restore(); - expect.fail('Expect generateKey() to throw an error'); + expect.fail('Expected ecdh.generateKey() to throw an error'); } catch (error) { - secp256k1Stub.restore(); expect(error).to.be.an('error'); - expect((error as Error).message).to.equal('Operation failed to generate key pair.'); + expect((error as Error).message).to.include('Operation failed: generateKey'); + } finally { + secp256k1Stub.restore(); } }); - it(`should throw an error if 'X25519' key pair generation fails`, async function() { - // @ts-ignore because the method is being intentionally stubbed to return null. - const x25519Stub = sinon.stub(X25519, 'generateKeyPair').returns(Promise.resolve(null)); + it(`throws an error if 'X25519' key generation fails`, async function() { + // @ts-ignore because the method is being intentionally stubbed to return undefined. + const x25519Stub = sinon.stub(X25519, 'generateKey').returns(Promise.resolve(undefined)); try { await ecdh.generateKey({ - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : false, - keyUsages : ['deriveBits', 'deriveKey'] + algorithm : { name: 'ECDH', curve: 'X25519' }, + keyOperations : ['deriveBits', 'deriveKey'] }); - x25519Stub.restore(); - expect.fail('Expect generateKey() to throw an error'); + expect.fail('Expected ecdh.generateKey() to throw an error'); } catch (error) { - x25519Stub.restore(); expect(error).to.be.an('error'); - expect((error as Error).message).to.equal('Operation failed to generate key pair.'); + expect((error as Error).message).to.include('Operation failed: generateKey'); + } finally { + x25519Stub.restore(); } }); }); @@ -675,185 +474,102 @@ describe('Default Crypto Algorithm Implementations', () => { }); describe('generateKey()', () => { - it('returns a key pair', async () => { - const keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign', 'verify'] + it('returns a private key in JWK format', async () => { + const privateKey = await ecdsa.generateKey({ + algorithm : { name: 'ES256K', curve: 'secp256k1' }, + keyOperations : ['sign'] }); - expect(keys).to.have.property('privateKey'); - expect(keys.privateKey.type).to.equal('private'); - expect(keys.privateKey.usages).to.deep.equal(['sign']); + expect(privateKey).to.have.property('crv', 'secp256k1'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'EC'); + expect(privateKey).to.have.property('x'); - expect(keys).to.have.property('publicKey'); - expect(keys.publicKey.type).to.equal('public'); - expect(keys.publicKey.usages).to.deep.equal(['verify']); - }); - - it('public key is always extractable', async () => { - let keys: CryptoKeyPair; - // publicKey is extractable if generateKey() called with extractable = false - keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign', 'verify'] - }); - expect(keys.publicKey.extractable).to.be.true; - - // publicKey is extractable if generateKey() called with extractable = true - keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : true, - keyUsages : ['sign', 'verify'] - }); - expect(keys.publicKey.extractable).to.be.true; + expect(privateKey.key_ops).to.deep.equal(['sign']); }); - it('private key is selectively extractable', async () => { - let keys: CryptoKeyPair; - // privateKey is NOT extractable if generateKey() called with extractable = false - keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign', 'verify'] - }); - expect(keys.privateKey.extractable).to.be.false; - - // privateKey is extractable if generateKey() called with extractable = true - keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : true, - keyUsages : ['sign', 'verify'] - }); - expect(keys.privateKey.extractable).to.be.true; - }); - - it(`supports 'secp256k1' curve with compressed public keys, by default`, async () => { - const keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign', 'verify'] - }); - - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('compressedPublicKey' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.compressedPublicKey).to.be.true; - }); - - it(`supports 'secp256k1' curve with compressed public keys`, async () => { - const keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1', compressedPublicKey: true }, - extractable : false, - keyUsages : ['sign', 'verify'] - }); - - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('compressedPublicKey' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.compressedPublicKey).to.be.true; - }); - - it(`supports 'secp256k1' curve with uncompressed public keys`, async () => { - const keys = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1', compressedPublicKey: false }, - extractable : false, - keyUsages : ['sign', 'verify'] + it(`supports 'secp256k1' curve`, async () => { + const privateKey = await ecdsa.generateKey({ + algorithm : { name: 'ES256K', curve: 'secp256k1' }, + keyOperations : ['sign'] }); - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('secp256k1'); - if (!('compressedPublicKey' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.compressedPublicKey).to.be.false; + if (!('crv' in privateKey)) throw new Error; // TS type guard + expect(privateKey.crv).to.equal('secp256k1'); }); - it(`supports 'sign' and/or 'verify' key usages`, async () => { + it(`supports 'sign' and/or 'verify' key operations`, async () => { await expect(ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign'] + algorithm : { name: 'ES256K', curve: 'secp256k1' }, + keyOperations : ['sign'] })).to.eventually.be.fulfilled; await expect(ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['verify'] + algorithm : { name: 'ES256K', curve: 'secp256k1' }, + keyOperations : ['verify'] })).to.eventually.be.fulfilled; await expect(ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign', 'verify'] + algorithm : { name: 'ES256K', curve: 'secp256k1' }, + keyOperations : ['sign', 'verify'] })).to.eventually.be.fulfilled; }); - it('validates algorithm, named curve, and key usages', async () => { - // Invalid (algorithm name, named curve, and key usages) result in algorithm name check failing first. + it('validates algorithm name and curve', async () => { + // Invalid (algorithm name, curve) results in algorithm name check failing first. await expect(ecdsa.generateKey({ - algorithm : { name: 'foo', namedCurve: 'bar' }, - extractable : false, - keyUsages : ['encrypt'] + algorithm: { name: 'foo', curve: 'bar' } })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - // Valid (algorithm name) + Invalid (named curve, key usages) result named curve check failing first. + // Valid (algorithm name) + Invalid (curve) results in curve check failing first. await expect(ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'bar' }, - extractable : false, - keyUsages : ['encrypt'] + algorithm: { name: 'ES256K', curve: 'bar' } })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + }); - // Valid (algorithm name, named curve) + Invalid (key usages) result key usages check failing first. - await expect(ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['encrypt'] - })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation'); + it(`accepts 'keyOperations' as undefined`, async () => { + const privateKey = await ecdsa.generateKey({ + algorithm: { name: 'ES256K', curve: 'secp256k1' }, + }); + + expect(privateKey).to.exist; + expect(privateKey.key_ops).to.be.undefined; + expect(privateKey).to.have.property('kty', 'EC'); + expect(privateKey).to.have.property('crv', 'secp256k1'); }); - it(`should throw an error if 'secp256k1' key pair generation fails`, async function() { - // @ts-ignore because the method is being intentionally stubbed to return null. - const secp256k1Stub = sinon.stub(Secp256k1, 'generateKeyPair').returns(Promise.resolve(null)); + it(`throws an error if operation fails`, async function() { + const checkGenerateKeyOptionsStub = sinon.stub(ecdsa, 'checkGenerateKeyOptions').returns(undefined); try { - await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : true, - keyUsages : ['sign'] - }); - secp256k1Stub.restore(); - expect.fail('Expect generateKey() to throw an error'); + // @ts-expect-error because no generateKey operations are defined. + await ecdsa.generateKey({ algorithm: {} }); + expect.fail('Expected ecdsa.generateKey() to throw an error'); } catch (error) { - secp256k1Stub.restore(); expect(error).to.be.an('error'); - expect((error as Error).message).to.equal('Operation failed to generate key pair.'); + expect((error as Error).message).to.include('Operation failed: generateKey'); + } finally { + checkGenerateKeyOptionsStub.restore(); } }); }); describe('sign()', () => { - - let keyPair: Web5Crypto.CryptoKeyPair; + let privateKey: PrivateKeyJwk; let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { - keyPair = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign', 'verify'] + privateKey = await ecdsa.generateKey({ + algorithm : { name: 'ES256K', curve: 'secp256k1' }, + keyOperations : ['sign', 'verify'] }); }); it(`returns a signature for 'secp256k1' keys`, async () => { const signature = await ecdsa.sign({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.privateKey, + algorithm : { name: 'ES256K' }, + key : privateKey, data : data }); @@ -861,167 +577,80 @@ describe('Default Crypto Algorithm Implementations', () => { expect(signature.byteLength).to.equal(64); }); - it('validates algorithm name and key algorithm name', async () => { - // Invalid (algorithm name, hash algorithm, private key, and data) result in algorithm name check failing first. - await expect(ecdsa.sign({ - algorithm : { name: 'Nope', hash: 'nope' }, - // @ts-expect-error because invalid key intentionally specified. - key : { foo: 'bar '}, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - - // Valid (algorithm name) + Invalid (hash algorithm, private key, and data) result in hash algorithm check failing first. - await expect(ecdsa.sign({ - algorithm : { name: 'ECDSA', hash: 'nope' }, - // @ts-expect-error because invalid key intentionally specified. - key : { foo: 'bar '}, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + it(`throws an error if sign operation fails`, async function() { + // @ts-ignore because the method is being intentionally stubbed to return undefined. + const checkSignOptionsStub = sinon.stub(ecdsa, 'checkSignOptions').returns(undefined); - // Valid (algorithm name, hash algorithm) + Invalid (private key, and data) result in key algorithm name check failing first. - await expect(ecdsa.sign({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - // @ts-expect-error because invalid key intentionally specified. - key : { algorithm: { name: 'bar '} }, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(InvalidAccessError, 'does not match'); - }); - - it('validates that key is not a public key', async () => { - // Valid (algorithm name, hash algorithm, data) + Invalid (private key) result in key type check failing first. - await expect(ecdsa.sign({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.publicKey, - data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); - }); - - it(`validates that key usage is 'sign'`, async () => { - // Manually specify the private key usages to exclude the 'sign' operation. - keyPair.privateKey.usages = ['verify']; - - await expect(ecdsa.sign({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.privateKey, - data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); - }); - - it('throws an error when key is an unsupported curve', async () => { - // Manually change the key's named curve to trigger an error. - // @ts-expect-error because TS can't determine the type of key. - keyPair.privateKey.algorithm.namedCurve = 'nope'; - - await expect(ecdsa.sign({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.privateKey, - data : data - })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + try { + // @ts-expect-error because no sign operations are defined. + await ecdsa.sign({ algorithm: {}, key: {}, data: undefined }); + expect.fail('Expected ecdsa.sign() to throw an error'); + } catch (error) { + expect(error).to.be.an('error'); + expect((error as Error).message).to.include('Operation failed: sign'); + } finally { + checkSignOptionsStub.restore(); + } }); }); describe('verify()', () => { - let keyPair: Web5Crypto.CryptoKeyPair; + let privateKey: PrivateKeyJwk; + let publicKey: PublicKeyJwk; let signature: Uint8Array; let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { - keyPair = await ecdsa.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : false, - keyUsages : ['sign', 'verify'] + privateKey = await ecdsa.generateKey({ + algorithm : { name: 'ES256K', curve: 'secp256k1' }, + keyOperations : ['sign'] }); + publicKey = await Secp256k1.computePublicKey({ privateKey }); + signature = await ecdsa.sign({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.privateKey, + algorithm : { name: 'ES256K' }, + key : privateKey, data : data }); }); - it(`returns a verification result for 'secp256k1' keys`, async () => { + it(`returns a boolean verification result`, async () => { const isValid = await ecdsa.verify({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.publicKey, + algorithm : { name: 'ES256K' }, + key : publicKey, signature : signature, data : data }); expect(isValid).to.be.a('boolean'); - expect(isValid).to.be.true; }); - it('validates algorithm name and key algorithm name', async () => { - // Invalid (algorithm name, hash algorithm, public key, signature, and data) result in algorithm name check failing first. - await expect(ecdsa.verify({ - algorithm : { name: 'Nope', hash: 'nope' }, - // @ts-expect-error because invalid key intentionally specified. - key : { foo: 'bar '}, - // @ts-expect-error because invalid signature intentionally specified. - signature : 57, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - - // Valid (algorithm name) + Invalid (hash algorithm, public key, signature and data) result in hash algorithm check failing first. - await expect(ecdsa.verify({ - algorithm : { name: 'ECDSA', hash: 'nope' }, - // @ts-expect-error because invalid key intentionally specified. - key : { foo: 'bar '}, - // @ts-expect-error because invalid signature intentionally specified. - signature : 57, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); - - // Valid (algorithm name, hash algorithm) + Invalid (public key, signature, and data) result in key algorithm name check failing first. - await expect(ecdsa.verify({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - // @ts-expect-error because invalid key intentionally specified. - key : { algorithm: { name: 'bar '} }, - // @ts-expect-error because invalid signature intentionally specified. - signature : 57, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(InvalidAccessError, 'does not match'); - }); - - it('validates that key is not a private key', async () => { - // Valid (algorithm name, hash algorithm, signature, data) + Invalid (public key) result in key type check failing first. - await expect(ecdsa.verify({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.privateKey, + it(`validates 'secp256k1' signatures`, async () => { + const isValid = await ecdsa.verify({ + algorithm : { name: 'ES256K' }, + key : publicKey, signature : signature, data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); - }); - - it(`validates that key usage is 'verify'`, async () => { - // Manually specify the private key usages to exclude the 'verify' operation. - keyPair.publicKey.usages = ['sign']; + }); - await expect(ecdsa.verify({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.publicKey, - signature : signature, - data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); + expect(isValid).to.be.true; }); - it('throws an error when key is an unsupported curve', async () => { - // Manually change the key's named curve to trigger an error. - // @ts-expect-error because TS can't determine the type of key. - keyPair.publicKey.algorithm.namedCurve = 'nope'; + it(`throws an error if verify operation fails`, async function() { + // @ts-ignore because the method is being intentionally stubbed to return undefined. + const checkVerifyOptionsStub = sinon.stub(ecdsa, 'checkVerifyOptions').returns(undefined); - await expect(ecdsa.verify({ - algorithm : { name: 'ECDSA', hash: 'SHA-256' }, - key : keyPair.publicKey, - signature : signature, - data : data - })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + try { + // @ts-expect-error because no verify operations are defined. + await ecdsa.verify({ algorithm: {}, key: {}, data: undefined, signature: undefined }); + expect.fail('Expected ecdsa.verify() to throw an error'); + } catch (error) { + expect(error).to.be.an('error'); + expect((error as Error).message).to.include('Operation failed: verify'); + } finally { + checkVerifyOptionsStub.restore(); + } }); }); }); @@ -1034,153 +663,102 @@ describe('Default Crypto Algorithm Implementations', () => { }); describe('generateKey()', () => { - it('returns a key pair', async () => { - const keys = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] + it('returns a private key in JWK format', async () => { + const privateKey = await eddsa.generateKey({ + algorithm : { name: 'EdDSA', curve: 'Ed25519' }, + keyOperations : ['sign'] }); - expect(keys).to.have.property('privateKey'); - expect(keys.privateKey.type).to.equal('private'); - expect(keys.privateKey.usages).to.deep.equal(['sign']); + expect(privateKey).to.have.property('crv', 'Ed25519'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('x'); - expect(keys).to.have.property('publicKey'); - expect(keys.publicKey.type).to.equal('public'); - expect(keys.publicKey.usages).to.deep.equal(['verify']); - }); - - it('public key is always extractable', async () => { - let keys: CryptoKeyPair; - // publicKey is extractable if generateKey() called with extractable = false - keys = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] - }); - expect(keys.publicKey.extractable).to.be.true; - - // publicKey is extractable if generateKey() called with extractable = true - keys = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : true, - keyUsages : ['sign', 'verify'] - }); - expect(keys.publicKey.extractable).to.be.true; - }); - - it('private key is selectively extractable', async () => { - let keys: CryptoKeyPair; - // privateKey is NOT extractable if generateKey() called with extractable = false - keys = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] - }); - expect(keys.privateKey.extractable).to.be.false; - - // privateKey is extractable if generateKey() called with extractable = true - keys = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : true, - keyUsages : ['sign', 'verify'] - }); - expect(keys.privateKey.extractable).to.be.true; + expect(privateKey.key_ops).to.deep.equal(['sign']); }); it(`supports 'Ed25519' curve`, async () => { - const keys = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] + const privateKey = await eddsa.generateKey({ + algorithm : { name: 'EdDSA', curve: 'Ed25519' }, + keyOperations : ['sign'] }); - if (!('namedCurve' in keys.privateKey.algorithm)) throw new Error; // type guard - expect(keys.privateKey.algorithm.namedCurve).to.equal('Ed25519'); - if (!('namedCurve' in keys.publicKey.algorithm)) throw new Error; // type guard - expect(keys.publicKey.algorithm.namedCurve).to.equal('Ed25519'); + if (!('crv' in privateKey)) throw new Error; // TS type guard + expect(privateKey.crv).to.equal('Ed25519'); }); - it(`supports 'sign' and/or 'verify' key usages`, async () => { + it(`supports 'sign' and/or 'verify' key operations`, async () => { await expect(eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign'] + algorithm : { name: 'EdDSA', curve: 'Ed25519' }, + keyOperations : ['sign'] })).to.eventually.be.fulfilled; await expect(eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['verify'] + algorithm : { name: 'EdDSA', curve: 'Ed25519' }, + keyOperations : ['verify'] })).to.eventually.be.fulfilled; await expect(eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] + algorithm : { name: 'EdDSA', curve: 'Ed25519' }, + keyOperations : ['sign', 'verify'] })).to.eventually.be.fulfilled; }); - it('validates algorithm, named curve, and key usages', async () => { - // Invalid (algorithm name, named curve, and key usages) result in algorithm name check failing first. + it('validates algorithm name and curve', async () => { + // Invalid (algorithm name, curve) results in algorithm name check failing first. await expect(eddsa.generateKey({ - algorithm : { name: 'foo', namedCurve: 'bar' }, - extractable : false, - keyUsages : ['encrypt'] + algorithm: { name: 'foo', curve: 'bar' } })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - // Valid (algorithm name) + Invalid (named curve, key usages) result named curve check failing first. + // Valid (algorithm name) + Invalid (curve) results in curve check failing first. await expect(eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'bar' }, - extractable : false, - keyUsages : ['encrypt'] + algorithm: { name: 'EdDSA', curve: 'bar' } })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + }); - // Valid (algorithm name, named curve) + Invalid (key usages) result key usages check failing first. - await expect(eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['encrypt'] - })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation'); + it(`accepts 'keyOperations' as undefined`, async () => { + const privateKey = await eddsa.generateKey({ + algorithm: { name: 'EdDSA', curve: 'Ed25519' }, + }); + + expect(privateKey).to.exist; + expect(privateKey.key_ops).to.be.undefined; + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('crv', 'Ed25519'); }); - it(`should throw an error if 'Ed25519' key pair generation fails`, async function() { - // @ts-ignore because the method is being intentionally stubbed to return null. - const ed25519Stub = sinon.stub(Ed25519, 'generateKeyPair').returns(Promise.resolve(null)); + it(`throws an error if operation fails`, async function() { + const checkGenerateKeyOptionsStub = sinon.stub(eddsa, 'checkGenerateKeyOptions').returns(undefined); try { - await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] - }); - ed25519Stub.restore(); - expect.fail('Expect generateKey() to throw an error'); + // @ts-expect-error because no generateKey operations are defined. + await eddsa.generateKey({ algorithm: {} }); + expect.fail('Expected eddsa.generateKey() to throw an error'); } catch (error) { - ed25519Stub.restore(); expect(error).to.be.an('error'); - expect((error as Error).message).to.equal('Operation failed to generate key pair.'); + expect((error as Error).message).to.include('Operation failed: generateKey'); + } finally { + checkGenerateKeyOptionsStub.restore(); } }); }); describe('sign()', () => { - - let keyPair: Web5Crypto.CryptoKeyPair; + let privateKey: PrivateKeyJwk; let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { - keyPair = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] + privateKey = await eddsa.generateKey({ + algorithm : { name: 'EdDSA', curve: 'Ed25519' }, + keyOperations : ['sign', 'verify'] }); }); it(`returns a signature for 'Ed25519' keys`, async () => { const signature = await eddsa.sign({ algorithm : { name: 'EdDSA' }, - key : keyPair.privateKey, + key : privateKey, data : data }); @@ -1188,147 +766,80 @@ describe('Default Crypto Algorithm Implementations', () => { expect(signature.byteLength).to.equal(64); }); - it('validates algorithm name and key algorithm name', async () => { - // Invalid (algorithm name, private key, and data) result in algorithm name check failing first. - await expect(eddsa.sign({ - algorithm : { name: 'Nope' }, - // @ts-expect-error because invalid key intentionally specified. - key : { foo: 'bar '}, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - - // Valid (algorithm name) + Invalid (private key, and data) result in key algorithm name check failing first. - await expect(eddsa.sign({ - algorithm : { name: 'EdDSA' }, - // @ts-expect-error because invalid key intentionally specified. - key : { algorithm: { name: 'bar '} }, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(InvalidAccessError, 'does not match'); - }); - - it('validates that key is not a public key', async () => { - // Valid (algorithm name, data) + Invalid (private key) result in key type check failing first. - await expect(eddsa.sign({ - algorithm : { name: 'EdDSA' }, - key : keyPair.publicKey, - data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); - }); - - it(`validates that key usage is 'sign'`, async () => { - // Manually specify the private key usages to exclude the 'sign' operation. - keyPair.privateKey.usages = ['verify']; + it(`throws an error if sign operation fails`, async function() { + // @ts-ignore because the method is being intentionally stubbed to return undefined. + const checkSignOptionsStub = sinon.stub(eddsa, 'checkSignOptions').returns(undefined); - await expect(eddsa.sign({ - algorithm : { name: 'EdDSA' }, - key : keyPair.privateKey, - data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); - }); - - it('throws an error when key is an unsupported curve', async () => { - // Manually change the key's named curve to trigger an error. - // @ts-expect-error because TS can't determine the type of key. - keyPair.privateKey.algorithm.namedCurve = 'nope'; - - await expect(eddsa.sign({ - algorithm : { name: 'EdDSA' }, - key : keyPair.privateKey, - data : data - })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + try { + // @ts-expect-error because no sign operations are defined. + await eddsa.sign({ algorithm: {}, key: {}, data: undefined }); + expect.fail('Expected eddsa.sign() to throw an error'); + } catch (error) { + expect(error).to.be.an('error'); + expect((error as Error).message).to.include('Operation failed: sign'); + } finally { + checkSignOptionsStub.restore(); + } }); }); describe('verify()', () => { - let keyPair: Web5Crypto.CryptoKeyPair; + let privateKey: PrivateKeyJwk; + let publicKey: PublicKeyJwk; let signature: Uint8Array; let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { - keyPair = await eddsa.generateKey({ - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : false, - keyUsages : ['sign', 'verify'] + privateKey = await eddsa.generateKey({ + algorithm : { name: 'EdDSA', curve: 'Ed25519' }, + keyOperations : ['sign'] }); + publicKey = await Ed25519.computePublicKey({ privateKey }); + signature = await eddsa.sign({ algorithm : { name: 'EdDSA' }, - key : keyPair.privateKey, + key : privateKey, data : data }); }); - it(`returns a verification result for 'Ed25519' keys`, async () => { + it(`returns a boolean verification result`, async () => { const isValid = await eddsa.verify({ algorithm : { name: 'EdDSA' }, - key : keyPair.publicKey, + key : publicKey, signature : signature, data : data }); expect(isValid).to.be.a('boolean'); - expect(isValid).to.be.true; }); - it('validates algorithm name and key algorithm name', async () => { - // Invalid (algorithm name, public key, signature, and data) result in algorithm name check failing first. - await expect(eddsa.verify({ - algorithm : { name: 'Nope' }, - // @ts-expect-error because invalid key intentionally specified. - key : { foo: 'bar '}, - // @ts-expect-error because invalid signature intentionally specified. - signature : 57, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); - - // Valid (algorithm name) + Invalid (public key, signature, and data) result in key algorithm name check failing first. - await expect(eddsa.verify({ - algorithm : { name: 'EdDSA' }, - // @ts-expect-error because invalid key intentionally specified. - key : { algorithm: { name: 'bar '} }, - // @ts-expect-error because invalid signature intentionally specified. - signature : 57, - // @ts-expect-error because invalid data type intentionally specified. - data : 'baz' - })).to.eventually.be.rejectedWith(InvalidAccessError, 'does not match'); - }); - - it('validates that key is not a private key', async () => { - // Valid (algorithm name, signature, data) + Invalid (public key) result in key type check failing first. - await expect(eddsa.verify({ + it(`validates 'secp256k1' signatures`, async () => { + const isValid = await eddsa.verify({ algorithm : { name: 'EdDSA' }, - key : keyPair.privateKey, + key : publicKey, signature : signature, data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); - }); - - it(`validates that key usage is 'verify'`, async () => { - // Manually specify the private key usages to exclude the 'verify' operation. - keyPair.publicKey.usages = ['sign']; + }); - await expect(eddsa.verify({ - algorithm : { name: 'EdDSA' }, - key : keyPair.publicKey, - signature : signature, - data : data - })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); + expect(isValid).to.be.true; }); - it('throws an error when key is an unsupported curve', async () => { - // Manually change the key's named curve to trigger an error. - // @ts-expect-error because TS can't determine the type of key. - keyPair.publicKey.algorithm.namedCurve = 'nope'; + it(`throws an error if verify operation fails`, async function() { + // @ts-ignore because the method is being intentionally stubbed to return undefined. + const checkVerifyOptionsStub = sinon.stub(eddsa, 'checkVerifyOptions').returns(undefined); - await expect(eddsa.verify({ - algorithm : { name: 'EdDSA' }, - key : keyPair.publicKey, - signature : signature, - data : data - })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); + try { + // @ts-expect-error because no verify operations are defined. + await eddsa.verify({ algorithm: {}, key: {}, data: undefined, signature: undefined }); + expect.fail('Expected eddsa.verify() to throw an error'); + } catch (error) { + expect(error).to.be.an('error'); + expect((error as Error).message).to.include('Operation failed: verify'); + } finally { + checkVerifyOptionsStub.restore(); + } }); }); }); @@ -1341,16 +852,13 @@ describe('Default Crypto Algorithm Implementations', () => { }); describe('deriveBits()', () => { - let inputKey: Web5Crypto.CryptoKey; + let inputKey: PrivateKeyJwk; beforeEach(async () => { - inputKey = await pbkdf2.importKey({ - format : 'raw', - keyData : new Uint8Array([51, 52, 53]), - algorithm : { name: 'PBKDF2' }, - extractable : false, - keyUsages : ['deriveBits'] - }); + inputKey = { + kty : 'oct', + k : Convert.uint8Array(new Uint8Array([51, 52, 53])).toBase64Url() + }; }); it('returns derived key as a Uint8Array', async () => { @@ -1387,6 +895,15 @@ describe('Default Crypto Algorithm Implementations', () => { expect(derivedKey.byteLength).to.equal(1024 / 8); }); + it('does not throw if the specified key operations are valid', async () => { + inputKey.key_ops = ['deriveBits']; + await expect(pbkdf2.deriveBits({ + algorithm : { name: 'PBKDF2', hash: 'SHA-256', salt: new Uint8Array([54, 55, 56]), iterations: 1 }, + baseKey : inputKey, + length : 256 + })).to.eventually.be.fulfilled; + }); + it('throws error if requested length is 0', async () => { await expect(pbkdf2.deriveBits({ algorithm : { name: 'PBKDF2', hash: 'SHA-256', salt: new Uint8Array([54, 55, 56]), iterations: 1 }, @@ -1403,6 +920,15 @@ describe('Default Crypto Algorithm Implementations', () => { })).to.eventually.be.rejectedWith(OperationError, `'length' must be a multiple of 8`); }); + it('throws an error if the specified key operations are invalid', async () => { + inputKey.key_ops = ['encrypt', 'sign']; + await expect(pbkdf2.deriveBits({ + algorithm : { name: 'PBKDF2', hash: 'SHA-256', salt: new Uint8Array([54, 55, 56]), iterations: 1 }, + baseKey : inputKey, + length : 256 + })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); + }); + it(`supports 'SHA-256' hash function`, async () => { const derivedKey = await pbkdf2.deriveBits({ algorithm : { name: 'PBKDF2', hash: 'SHA-256', salt: new Uint8Array([54, 55, 56]), iterations: 1 }, @@ -1441,37 +967,5 @@ describe('Default Crypto Algorithm Implementations', () => { })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); }); }); - - describe('importKey()', () => { - it('should import a key when all parameters are valid', async () => { - const key = await pbkdf2.importKey({ - format : 'raw', - keyData : new Uint8Array(16), - algorithm : { name: 'PBKDF2' }, - extractable : false, - keyUsages : ['deriveBits'] - }); - - expect(key).to.exist; - }); - - it('should return a Web5Crypto.CryptoKey object', async () => { - const key = await pbkdf2.importKey({ - format : 'raw', - keyData : new Uint8Array(16), - algorithm : { name: 'PBKDF2' }, - extractable : false, - keyUsages : ['deriveBits'] - }); - - expect(key).to.be.an('object'); - expect(key).to.have.property('algorithm').that.is.an('object'); - expect(key.algorithm).to.have.property('name', 'PBKDF2'); - expect(key).to.have.property('extractable', false); - expect(key).to.have.property('type', 'secret'); - expect(key).to.have.property('usages').that.includes.members(['deriveBits']); - expect(key).to.have.property('material').that.is.instanceOf(Uint8Array); - }); - }); }); }); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives.spec.ts b/packages/crypto/tests/crypto-primitives.spec.ts deleted file mode 100644 index 5c3c7d40d..000000000 --- a/packages/crypto/tests/crypto-primitives.spec.ts +++ /dev/null @@ -1,1272 +0,0 @@ -import type { BytesKeyPair } from '../src/types/crypto-key.js'; - -import sinon from 'sinon'; -import chai, { expect } from 'chai'; -import { Convert } from '@web5/common'; -import chaiAsPromised from 'chai-as-promised'; - -import { NotSupportedError } from '../src/algorithms-api/errors.js'; -import { ed25519TestVectors } from './fixtures/test-vectors/ed25519.js'; -import { secp256k1TestVectors } from './fixtures/test-vectors/secp256k1.js'; -import { aesCtrTestVectors, aesGcmTestVectors } from './fixtures/test-vectors/aes.js'; -import { - AesCtr, - AesGcm, - ConcatKdf, - Ed25519, - Pbkdf2, - Secp256k1, - X25519, - XChaCha20, - XChaCha20Poly1305 -} from '../src/crypto-primitives/index.js'; - -chai.use(chaiAsPromised); - -// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage -// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule -import { webcrypto } from 'node:crypto'; -// @ts-ignore -if (!globalThis.crypto) globalThis.crypto = webcrypto; - -describe('Cryptographic Primitive Implementations', () => { - - describe('AesCtr', () => { - describe('decrypt', () => { - for (const vector of aesCtrTestVectors) { - it(`passes test vector ${vector.id}`, async () => { - const plaintext = await AesCtr.decrypt({ - counter : Convert.hex(vector.counter).toUint8Array(), - data : Convert.hex(vector.ciphertext).toUint8Array(), - key : Convert.hex(vector.key).toUint8Array(), - length : vector.length - }); - expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); - }); - } - - it('accepts ciphertext input as Uint8Array', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const secretKey = await AesCtr.generateKey({ length: 256 }); - let ciphertext: Uint8Array; - - // TypedArray - Uint8Array - ciphertext = await AesCtr.decrypt({ counter: new Uint8Array(16), data, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(Uint8Array); - }); - }); - - describe('encrypt', () => { - for (const vector of aesCtrTestVectors) { - it(`passes test vector ${vector.id}`, async () => { - const ciphertext = await AesCtr.encrypt({ - counter : Convert.hex(vector.counter).toUint8Array(), - data : Convert.hex(vector.data).toUint8Array(), - key : Convert.hex(vector.key).toUint8Array(), - length : vector.length - }); - expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext); - }); - } - - it('accepts plaintext input as Uint8Array', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const secretKey = await AesCtr.generateKey({ length: 256 }); - let ciphertext: Uint8Array; - - // Uint8Array - ciphertext = await AesCtr.encrypt({ counter: new Uint8Array(16), data, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(Uint8Array); - }); - }); - - describe('generateKey()', () => { - it('returns a secret key of type Uint8Array', async () => { - const secretKey = await AesCtr.generateKey({ length: 256 }); - expect(secretKey).to.be.instanceOf(Uint8Array); - }); - - it('returns a secret key of the specified length', async () => { - let secretKey: Uint8Array; - - // 128 bits - secretKey= await AesCtr.generateKey({ length: 128 }); - expect(secretKey.byteLength).to.equal(16); - - // 192 bits - secretKey= await AesCtr.generateKey({ length: 192 }); - expect(secretKey.byteLength).to.equal(24); - - // 256 bits - secretKey= await AesCtr.generateKey({ length: 256 }); - expect(secretKey.byteLength).to.equal(32); - }); - }); - }); - - describe('AesGcm', () => { - describe('decrypt', () => { - for (const vector of aesGcmTestVectors) { - it(`passes test vector ${vector.id}`, async () => { - const plaintext = await AesGcm.decrypt({ - additionalData : Convert.hex(vector.aad).toUint8Array(), - iv : Convert.hex(vector.iv).toUint8Array(), - data : Convert.hex(vector.ciphertext + vector.tag).toUint8Array(), - key : Convert.hex(vector.key).toUint8Array(), - tagLength : vector.tagLength - }); - expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); - }); - } - - it('accepts ciphertext input as Uint8Array', async () => { - const secretKey = new Uint8Array([222, 78, 162, 222, 38, 146, 151, 191, 191, 75, 227, 71, 220, 221, 70, 49]); - let plaintext: Uint8Array; - - // TypedArray - Uint8Array - const ciphertext = new Uint8Array([242, 126, 129, 170, 99, 195, 21, 165, 205, 3, 226, 171, 203, 198, 42, 86, 101]); - plaintext = await AesGcm.decrypt({ data: ciphertext, iv: new Uint8Array(12), key: secretKey, tagLength: 128 }); - expect(plaintext).to.be.instanceOf(Uint8Array); - }); - }); - - describe('encrypt', () => { - for (const vector of aesGcmTestVectors) { - it(`passes test vector ${vector.id}`, async () => { - const ciphertext = await AesGcm.encrypt({ - additionalData : Convert.hex(vector.aad).toUint8Array(), - iv : Convert.hex(vector.iv).toUint8Array(), - data : Convert.hex(vector.data).toUint8Array(), - key : Convert.hex(vector.key).toUint8Array(), - tagLength : vector.tagLength - }); - expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext + vector.tag); - }); - } - - it('accepts plaintext input as Uint8Array', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const secretKey = await AesGcm.generateKey({ length: 256 }); - let ciphertext: Uint8Array; - - // TypedArray - Uint8Array - ciphertext = await AesGcm.encrypt({ data, iv: new Uint8Array(12), key: secretKey, tagLength: 128 }); - expect(ciphertext).to.be.instanceOf(Uint8Array); - }); - }); - - describe('generateKey()', () => { - it('returns a secret key of type Uint8Array', async () => { - const secretKey = await AesGcm.generateKey({ length: 256 }); - expect(secretKey).to.be.instanceOf(Uint8Array); - }); - - it('returns a secret key of the specified length', async () => { - let secretKey: Uint8Array; - - // 128 bits - secretKey= await AesGcm.generateKey({ length: 128 }); - expect(secretKey.byteLength).to.equal(16); - - // 192 bits - secretKey= await AesGcm.generateKey({ length: 192 }); - expect(secretKey.byteLength).to.equal(24); - - // 256 bits - secretKey= await AesGcm.generateKey({ length: 256 }); - expect(secretKey.byteLength).to.equal(32); - }); - }); - }); - - describe('ConcatKdf', () => { - describe('deriveKey()', () => { - it('matches RFC 7518 ECDH-ES key agreement computation example', async () => { - // Test vector 1 - const inputSharedSecret = 'nlbZHYFxNdNyg0KDv4QmnPsxbqPagGpI9tqneYz-kMQ'; - const input = { - sharedSecret : Convert.base64Url(inputSharedSecret).toUint8Array(), - keyDataLen : 128, - otherInfo : { - algorithmId : 'A128GCM', - partyUInfo : 'Alice', - partyVInfo : 'Bob', - suppPubInfo : 128 - } - }; - const output = 'VqqN6vgjbSBcIijNcacQGg'; - - const derivedKeyingMaterial = await ConcatKdf.deriveKey(input); - - const expectedResult = Convert.base64Url(output).toUint8Array(); - expect(derivedKeyingMaterial).to.deep.equal(expectedResult); - expect(derivedKeyingMaterial.byteLength).to.equal(16); - }); - - it('accepts other info as String and TypedArray', async () => { - const inputBase = { - sharedSecret : new Uint8Array([1, 2, 3]), - keyDataLen : 256, - otherInfo : {} - }; - - // String input. - const inputString = { ...inputBase, otherInfo: { - algorithmId : 'A128GCM', - partyUInfo : 'Alice', - partyVInfo : 'Bob', - suppPubInfo : 128 - }}; - let derivedKeyingMaterial = await ConcatKdf.deriveKey(inputString); - expect(derivedKeyingMaterial).to.be.an('Uint8Array'); - expect(derivedKeyingMaterial.byteLength).to.equal(32); - - // TypedArray input. - const inputTypedArray = { ...inputBase, otherInfo: { - algorithmId : 'A128GCM', - partyUInfo : Convert.string('Alice').toUint8Array(), - partyVInfo : Convert.string('Bob').toUint8Array(), - suppPubInfo : 128 - }}; - derivedKeyingMaterial = await ConcatKdf.deriveKey(inputTypedArray); - expect(derivedKeyingMaterial).to.be.an('Uint8Array'); - expect(derivedKeyingMaterial.byteLength).to.equal(32); - }); - - it('throws error if multi-round Concat KDF attempted', async () => { - await expect( - // @ts-expect-error because only parameters needed to trigger the error are specified. - ConcatKdf.deriveKey({ keyDataLen: 512 }) - ).to.eventually.be.rejectedWith(NotSupportedError, 'rounds not supported'); - }); - - it('throws an error if suppPubInfo is not a Number', async () => { - await expect( - ConcatKdf.deriveKey({ - sharedSecret : new Uint8Array([1, 2, 3]), - keyDataLen : 128, - otherInfo : { - algorithmId : 'A128GCM', - partyUInfo : 'Alice', - partyVInfo : 'Bob', - // @ts-expect-error because a string is specified to trigger an error. - suppPubInfo : '128', - } - }) - ).to.eventually.be.rejectedWith(TypeError, 'Fixed length input must be a number'); - }); - }); - - describe('computeOtherInfo()', () => { - it('returns concatenated and formatted Uint8Array', () => { - const input = { - algorithmId : 'A128GCM', - partyUInfo : 'Alice', - partyVInfo : 'Bob', - suppPubInfo : 128, - suppPrivInfo : 'gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0' - }; - const output = 'AAAAB0ExMjhHQ00AAAAFQWxpY2UAAAADQm9iAAAAgAAAACtnSTBHQUlMQmR1N1Q1M2FrckZtTXlHY3NGM241ZE83TW13TkJIS1c1U1Yw'; - - // @ts-expect-error because computeOtherInfo() is a private method. - const otherInfo = ConcatKdf.computeOtherInfo(input); - - const expectedResult = Convert.base64Url(output).toUint8Array(); - expect(otherInfo).to.deep.equal(expectedResult); - }); - - it('matches RFC 7518 ECDH-ES key agreement computation example', async () => { - // Test vector 1. - const input = { - algorithmId : 'A128GCM', - partyUInfo : 'Alice', - partyVInfo : 'Bob', - suppPubInfo : 128 - }; - const output = 'AAAAB0ExMjhHQ00AAAAFQWxpY2UAAAADQm9iAAAAgA'; - - // @ts-expect-error because computeOtherInfo() is a private method. - const otherInfo = ConcatKdf.computeOtherInfo(input); - - const expectedResult = Convert.base64Url(output).toUint8Array(); - expect(otherInfo).to.deep.equal(expectedResult); - }); - }); - }); - - describe('Ed25519', () => { - describe('convertPrivateKeyToX25519()', () => { - let validEd25519PrivateKey: Uint8Array; - - // This is a setup step. Before each test, generate a new Ed25519 key pair - // and use the private key in tests. This ensures that we work with fresh keys for every test. - beforeEach(async () => { - const keyPair = await Ed25519.generateKeyPair(); - validEd25519PrivateKey = keyPair.publicKey; - }); - - it('converts a valid Ed25519 private key to X25519 format', async () => { - const x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519({ privateKey: validEd25519PrivateKey }); - - expect(x25519PrivateKey).to.be.instanceOf(Uint8Array); - expect(x25519PrivateKey.length).to.equal(32); - }); - - it('accepts any Uint8Array value as a private key', async () => { - /** For Ed25519 the private key is a random string of bytes that is - * hashed, which means many possible values can serve as a valid - * private key. */ - const key0Bytes = new Uint8Array(0); - const key33Bytes = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); - - let x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519({ privateKey: key0Bytes }); - expect(x25519PrivateKey.length).to.equal(32); - x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519({ privateKey: key33Bytes }); - expect(x25519PrivateKey.length).to.equal(32); - }); - }); - - describe('convertPublicKeyToX25519()', () => { - let validEd25519PublicKey: Uint8Array; - - // This is a setup step. Before each test, generate a new Ed25519 key pair - // and use the public key in tests. This ensures that we work with fresh keys for every test. - beforeEach(async () => { - const keyPair = await Ed25519.generateKeyPair(); - validEd25519PublicKey = keyPair.publicKey; - }); - - it('converts a valid Ed25519 public key to X25519 format', async () => { - const x25519PublicKey = await Ed25519.convertPublicKeyToX25519({ publicKey: validEd25519PublicKey }); - - expect(x25519PublicKey).to.be.instanceOf(Uint8Array); - expect(x25519PublicKey.length).to.equal(32); - }); - - it('throws an error when provided an invalid Ed25519 public key', async () => { - const invalidEd25519PublicKey = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); - - await expect( - Ed25519.convertPublicKeyToX25519({ publicKey: invalidEd25519PublicKey }) - ).to.eventually.be.rejectedWith(Error, 'Invalid public key'); - }); - - it('throws an error when provided an Ed25519 private key', async () => { - for (const vector of ed25519TestVectors) { - const validEd25519PrivateKey = Convert.hex(vector.privateKey.encoded).toUint8Array(); - - await expect( - Ed25519.convertPublicKeyToX25519({ publicKey: validEd25519PrivateKey }) - ).to.eventually.be.rejectedWith(Error, 'Invalid public key'); - } - }); - }); - - describe('generateKeyPair()', () => { - it('returns a pair of keys of type Uint8Array', async () => { - const keyPair = await Ed25519.generateKeyPair(); - expect(keyPair).to.have.property('privateKey'); - expect(keyPair).to.have.property('publicKey'); - expect(keyPair.privateKey).to.be.instanceOf(Uint8Array); - expect(keyPair.publicKey).to.be.instanceOf(Uint8Array); - }); - - it('returns a 32-byte private key', async () => { - const keyPair = await Ed25519.generateKeyPair(); - expect(keyPair.privateKey.byteLength).to.equal(32); - }); - - it('returns a 32-byte compressed public key', async () => { - const keyPair = await Ed25519.generateKeyPair(); - expect(keyPair.publicKey.byteLength).to.equal(32); - }); - }); - - describe('getPublicKey()', () => { - let keyPair: BytesKeyPair; - - before(async () => { - keyPair = await Ed25519.generateKeyPair(); - }); - - it('returns a 32-byte compressed public key', async () => { - const publicKey = await Ed25519.getPublicKey({ privateKey: keyPair.privateKey }); - expect(publicKey).to.be.instanceOf(Uint8Array); - expect(publicKey.byteLength).to.equal(32); - }); - }); - - describe('sign()', () => { - let keyPair: BytesKeyPair; - - before(async () => { - keyPair = await Ed25519.generateKeyPair(); - }); - - it('returns a 64-byte signature of type Uint8Array', async () => { - const data = new Uint8Array([51, 52, 53]); - const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); - expect(signature).to.be.instanceOf(Uint8Array); - expect(signature.byteLength).to.equal(64); - }); - - it('accepts input data as Uint8Array', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const key = keyPair.privateKey; - let signature: Uint8Array; - - // TypedArray - Uint8Array - signature = await Ed25519.sign({ key, data: data }); - expect(signature).to.be.instanceOf(Uint8Array); - }); - }); - - describe('validatePublicKey()', () => { - it('returns true for valid public keys', async () => { - for (const vector of ed25519TestVectors) { - const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); - const isValid = await Ed25519.validatePublicKey({ key }); - expect(isValid).to.be.true; - } - }); - - it('returns false for invalid public keys', async () => { - const key = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); - const isValid = await Ed25519.validatePublicKey({ key }); - expect(isValid).to.be.false; - }); - - it('returns false if a private key is given', async () => { - for (const vector of ed25519TestVectors) { - const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); - const isValid = await Ed25519.validatePublicKey({ key }); - expect(isValid).to.be.false; - } - }); - }); - - describe('verify()', () => { - let keyPair: BytesKeyPair; - - before(async () => { - keyPair = await Ed25519.generateKeyPair(); - }); - - it('returns a boolean result', async () => { - const data = new Uint8Array([51, 52, 53]); - const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); - - const isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data }); - expect(isValid).to.exist; - expect(isValid).to.be.true; - }); - - it('accepts input data as Uint8Array', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let isValid: boolean; - let signature: Uint8Array; - - // TypedArray - Uint8Array - signature = await Ed25519.sign({ key: keyPair.privateKey, data }); - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data }); - expect(isValid).to.be.true; - }); - - it('returns false if the signed data was mutated', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let isValid: boolean; - - // Generate signature using the private key. - const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); - - // Verification should return true with the data used to generate the signature. - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data }); - expect(isValid).to.be.true; - - // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. - const mutatedData = new Uint8Array(data); - mutatedData[0] ^= 1 << 0; - - // Verification should return false if the given data does not match the data used to generate the signature. - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: mutatedData }); - expect(isValid).to.be.false; - }); - - it('returns false if the signature was mutated', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let isValid: boolean; - - // Generate a new key pair. - keyPair = await Ed25519.generateKeyPair(); - - // Generate signature using the private key. - const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); - - // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. - const mutatedSignature = new Uint8Array(signature); - mutatedSignature[0] ^= 1 << 0; - - // Verification should return false if the signature was modified. - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature: signature, data: mutatedSignature }); - expect(isValid).to.be.false; - }); - - it('returns false with a signature generated using a different private key', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const keyPairA = await Ed25519.generateKeyPair(); - const keyPairB = await Ed25519.generateKeyPair(); - let isValid: boolean; - - // Generate a signature using the private key from key pair B. - const signatureB = await Ed25519.sign({ key: keyPairB.privateKey, data }); - - // Verification should return false with the public key from key pair A. - isValid = await Ed25519.verify({ key: keyPairA.publicKey, signature: signatureB, data }); - expect(isValid).to.be.false; - }); - }); - }); - - describe('Pbkdf2', () => { - const password = Convert.string('password').toUint8Array(); - const salt = Convert.string('salt').toUint8Array(); - const iterations = 1; - const length = 256; // 32 bytes - - describe('deriveKey', () => { - it('successfully derives a key using WebCrypto, if available', async () => { - const subtleDeriveBitsSpy = sinon.spy(crypto.subtle, 'deriveBits'); - - const derivedKey = await Pbkdf2.deriveKey({ hash: 'SHA-256', password, salt, iterations, length }); - - expect(derivedKey).to.be.instanceOf(Uint8Array); - expect(derivedKey.byteLength).to.equal(length / 8); - expect(subtleDeriveBitsSpy.called).to.be.true; - - subtleDeriveBitsSpy.restore(); - }); - - it('successfully derives a key using node:crypto when WebCrypto is not supported', async function () { - // Skip test in web browsers since node:crypto is not available. - if (typeof window !== 'undefined') this.skip(); - - // Ensure that WebCrypto is not available for this test. - sinon.stub(crypto, 'subtle').value(null); - - // @ts-expect-error because we're spying on a private method. - const nodeCryptoDeriveKeySpy = sinon.spy(Pbkdf2, 'deriveKeyWithNodeCrypto'); - - const derivedKey = await Pbkdf2.deriveKey({ hash: 'SHA-256', password, salt, iterations, length }); - - expect(derivedKey).to.be.instanceOf(Uint8Array); - expect(derivedKey.byteLength).to.equal(length / 8); - expect(nodeCryptoDeriveKeySpy.called).to.be.true; - - nodeCryptoDeriveKeySpy.restore(); - sinon.restore(); - }); - - it('derives the same value with node:crypto and WebCrypto', async function () { - // Skip test in web browsers since node:crypto is not available. - if (typeof window !== 'undefined') this.skip(); - - const options = { hash: 'SHA-256', password, salt, iterations, length }; - - // @ts-expect-error because we're testing a private method. - const webCryptoDerivedKey = await Pbkdf2.deriveKeyWithNodeCrypto(options); - // @ts-expect-error because we're testing a private method. - const nodeCryptoDerivedKey = await Pbkdf2.deriveKeyWithWebCrypto(options); - - expect(webCryptoDerivedKey).to.deep.equal(nodeCryptoDerivedKey); - }); - - const hashFunctions: ('SHA-256' | 'SHA-384' | 'SHA-512')[] = ['SHA-256', 'SHA-384', 'SHA-512']; - hashFunctions.forEach(hash => { - it(`handles ${hash} hash function`, async () => { - const options = { hash, password, salt, iterations, length }; - - const derivedKey = await Pbkdf2.deriveKey(options); - expect(derivedKey).to.be.instanceOf(Uint8Array); - expect(derivedKey.byteLength).to.equal(length / 8); - }); - }); - - it('throws an error when an invalid hash function is used with WebCrypto', async () => { - const options = { - hash: 'SHA-2' as const, password, salt, iterations, length - }; - - // @ts-expect-error for testing purposes - await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error); - }); - - it('throws an error when an invalid hash function is used with node:crypto', async function () { - // Skip test in web browsers since node:crypto is not available. - if (typeof window !== 'undefined') this.skip(); - - // Ensure that WebCrypto is not available for this test. - sinon.stub(crypto, 'subtle').value(null); - - const options = { - hash: 'SHA-2' as const, password, salt, iterations, length - }; - - // @ts-expect-error for testing purposes - await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error); - - sinon.restore(); - }); - - it('throws an error when iterations count is not a positive number with WebCrypto', async () => { - const options = { - hash : 'SHA-256' as const, password, salt, - iterations : -1, length - }; - - // Every browser throws a different error message so a specific message cannot be checked. - await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error); - }); - - it('throws an error when iterations count is not a positive number with node:crypto', async function () { - // Skip test in web browsers since node:crypto is not available. - if (typeof window !== 'undefined') this.skip(); - - // Ensure that WebCrypto is not available for this test. - sinon.stub(crypto, 'subtle').value(null); - - const options = { - hash : 'SHA-256' as const, password, salt, - iterations : -1, length - }; - - await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error, 'out of range'); - - sinon.restore(); - }); - }); - }); - - describe('Secp256k1', () => { - describe('convertPublicKey method', () => { - it('converts an uncompressed public key to a compressed format', async () => { - // Generate the uncompressed public key. - const keyPair = await Secp256k1.generateKeyPair({ compressedPublicKey: false }); - const uncompressedPublicKey = keyPair.publicKey; - - // Attempt to convert to compressed format. - const compressedKey = await Secp256k1.convertPublicKey({ - publicKey : uncompressedPublicKey, - compressedPublicKey : true - }); - - // Confirm the length of the resulting public key is 33 bytes - expect(compressedKey.byteLength).to.equal(33); - }); - - it('converts a compressed public key to an uncompressed format', async () => { - // Generate the uncompressed public key. - const keyPair = await Secp256k1.generateKeyPair({ compressedPublicKey: true }); - const compressedPublicKey = keyPair.publicKey; - - const uncompressedKey = await Secp256k1.convertPublicKey({ - publicKey : compressedPublicKey, - compressedPublicKey : false - }); - - // Confirm the length of the resulting public key is 65 bytes - expect(uncompressedKey.byteLength).to.equal(65); - }); - - it('throws an error for an invalid compressed public key', async () => { - // Invalid compressed public key. - const invalidPublicKey = Convert.hex('fef0b998921eafb58f49efdeb0adc47123aa28a4042924236f08274d50c72fe7b0').toUint8Array(); - - try { - await Secp256k1.convertPublicKey({ - publicKey : invalidPublicKey, - compressedPublicKey : false - }); - expect.fail('Expected method to throw an error.'); - } catch (error) { - expect(error).to.be.instanceOf(Error); - expect((error as Error).message).to.include('Point of length 33 was invalid'); - } - }); - - it('throws an error for an invalid uncompressed public key', async () => { - // Here, generating a random byte array of size 65. It's unlikely to be a valid public key. - const invalidPublicKey = Convert.hex('dfebc16793a5737ac51f606a43524df8373c063e41d5a99b2f1530afd987284bd1c7cde1658a9a756e71f44a97b4783ea9dee5ccb7f1447eb4836d8de9bd4f81fd').toUint8Array(); - - try { - await Secp256k1.convertPublicKey({ - publicKey : invalidPublicKey, - compressedPublicKey : true - }); - expect.fail('Expected method to throw an error.'); - } catch (error) { - expect(error).to.be.instanceOf(Error); - expect((error as Error).message).to.include('Point of length 65 was invalid'); - } - }); - }); - - describe('generateKeyPair()', () => { - it('returns a pair of keys of type Uint8Array', async () => { - const keyPair = await Secp256k1.generateKeyPair(); - expect(keyPair).to.have.property('privateKey'); - expect(keyPair).to.have.property('publicKey'); - expect(keyPair.privateKey).to.be.instanceOf(Uint8Array); - expect(keyPair.publicKey).to.be.instanceOf(Uint8Array); - }); - - it('returns a 32-byte private key', async () => { - const keyPair = await Secp256k1.generateKeyPair(); - expect(keyPair.privateKey.byteLength).to.equal(32); - }); - - it('returns a 33-byte compressed public key, by default', async () => { - const keyPair = await Secp256k1.generateKeyPair(); - expect(keyPair.publicKey.byteLength).to.equal(33); - }); - - it('returns a 65-byte uncompressed public key, if specified', async () => { - const keyPair = await Secp256k1.generateKeyPair({ compressedPublicKey: false }); - expect(keyPair.publicKey.byteLength).to.equal(65); - }); - }); - - describe('getCurvePoints()', () => { - it('returns public key x and y coordinates given a public key', async () => { - for (const vector of secp256k1TestVectors) { - const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); - const points = await Secp256k1.getCurvePoints({ key }); - expect(points.x).to.deep.equal(Convert.hex(vector.publicKey.x).toUint8Array()); - expect(points.y).to.deep.equal(Convert.hex(vector.publicKey.y).toUint8Array()); - } - }); - - it('returns public key x and y coordinates given a private key', async () => { - for (const vector of secp256k1TestVectors) { - const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); - const points = await Secp256k1.getCurvePoints({ key }); - expect(points.x).to.deep.equal(Convert.hex(vector.publicKey.x).toUint8Array()); - expect(points.y).to.deep.equal(Convert.hex(vector.publicKey.y).toUint8Array()); - } - }); - - it('handles keys that require padded x-coordinate when converting from BigInt to bytes', async () => { - const key = Convert.hex('0206a1f9628c5bcd31f3bbc2f160ec98f99960147e04ea192f56c53a0086c5432d').toUint8Array(); - const points = await Secp256k1.getCurvePoints({ key }); - - const expectedX = Convert.hex('06a1f9628c5bcd31f3bbc2f160ec98f99960147e04ea192f56c53a0086c5432d').toUint8Array(); - const expectedY = Convert.hex('bf2efab7943be51219a283c0979ccba0fbe03f571e75b0eb338cc2ec01e70552').toUint8Array(); - expect(points.x).to.deep.equal(expectedX); - expect(points.y).to.deep.equal(expectedY); - }); - - it('handles keys that require padded y-coordinate when converting from BigInt to bytes', async () => { - const key = Convert.hex('032ff752fb8fc6af85c8682b0ca9d48901b2b9ac130f558bd1a9092240d42c4682').toUint8Array(); - const points = await Secp256k1.getCurvePoints({ key }); - - const expectedX = Convert.hex('2ff752fb8fc6af85c8682b0ca9d48901b2b9ac130f558bd1a9092240d42c4682').toUint8Array(); - const expectedY = Convert.hex('048c39d9ebdc1fd98bda38b7f00b93de1d2af5bb3ba8cb532ad47c1f36e19501').toUint8Array(); - expect(points.x).to.deep.equal(expectedX); - expect(points.y).to.deep.equal(expectedY); - }); - - it('throws error with invalid input key length', async () => { - await expect( - Secp256k1.getCurvePoints({ key: new Uint8Array(16) }) - ).to.eventually.be.rejectedWith(Error, 'Point of length 16 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes'); - }); - }); - - describe('getPublicKey()', () => { - let keyPair: BytesKeyPair; - - before(async () => { - keyPair = await Secp256k1.generateKeyPair(); - }); - - it('returns a 33-byte compressed public key, by default', async () => { - const publicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey }); - expect(publicKey).to.be.instanceOf(Uint8Array); - expect(publicKey.byteLength).to.equal(33); - }); - - it('returns a 65-byte uncompressed public key, if specified', async () => { - const publicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey, compressedPublicKey: false }); - expect(publicKey).to.be.instanceOf(Uint8Array); - expect(publicKey.byteLength).to.equal(65); - }); - }); - - describe('sharedSecret()', () => { - let otherPartyKeyPair: BytesKeyPair; - let ownKeyPair: BytesKeyPair; - - beforeEach(async () => { - otherPartyKeyPair = await Secp256k1.generateKeyPair(); - ownKeyPair = await Secp256k1.generateKeyPair(); - }); - - it('generates a 32-byte shared secret', async () => { - const sharedSecret = await Secp256k1.sharedSecret({ - privateKey : ownKeyPair.privateKey, - publicKey : otherPartyKeyPair.publicKey - }); - expect(sharedSecret).to.be.instanceOf(Uint8Array); - expect(sharedSecret.byteLength).to.equal(32); - }); - - it('generates identical output if keypairs are swapped', async () => { - const sharedSecretOwnOther = await Secp256k1.sharedSecret({ - privateKey : ownKeyPair.privateKey, - publicKey : otherPartyKeyPair.publicKey - }); - - const sharedSecretOtherOwn = await Secp256k1.sharedSecret({ - privateKey : otherPartyKeyPair.privateKey, - publicKey : ownKeyPair.publicKey - }); - - expect(sharedSecretOwnOther).to.deep.equal(sharedSecretOtherOwn); - }); - }); - - describe('sign()', () => { - let keyPair: BytesKeyPair; - - before(async () => { - keyPair = await Secp256k1.generateKeyPair(); - }); - - it('returns a 64-byte signature of type Uint8Array', async () => { - const hash = 'SHA-256'; - const data = new Uint8Array([51, 52, 53]); - const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); - expect(signature).to.be.instanceOf(Uint8Array); - expect(signature.byteLength).to.equal(64); - }); - - it('accepts input data as Uint8Array', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const hash = 'SHA-256'; - const key = keyPair.privateKey; - let signature: Uint8Array; - - // TypedArray - Uint8Array - signature = await Secp256k1.sign({ hash, key, data }); - expect(signature).to.be.instanceOf(Uint8Array); - }); - }); - - describe('validatePrivateKey()', () => { - it('returns true for valid private keys', async () => { - for (const vector of secp256k1TestVectors) { - const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); - const isValid = await Secp256k1.validatePrivateKey({ key }); - expect(isValid).to.be.true; - } - }); - - it('returns false for invalid private keys', async () => { - const key = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); - const isValid = await Secp256k1.validatePrivateKey({ key }); - expect(isValid).to.be.false; - }); - - it('returns false if a public key is given', async () => { - for (const vector of secp256k1TestVectors) { - const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); - const isValid = await Secp256k1.validatePrivateKey({ key }); - expect(isValid).to.be.false; - } - }); - }); - - describe('validatePublicKey()', () => { - it('returns true for valid public keys', async () => { - for (const vector of secp256k1TestVectors) { - const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); - const isValid = await Secp256k1.validatePublicKey({ key }); - expect(isValid).to.be.true; - } - }); - - it('returns false for invalid public keys', async () => { - const key = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); - const isValid = await Secp256k1.validatePublicKey({ key }); - expect(isValid).to.be.false; - }); - - it('returns false if a private key is given', async () => { - for (const vector of secp256k1TestVectors) { - const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); - const isValid = await Secp256k1.validatePublicKey({ key }); - expect(isValid).to.be.false; - } - }); - }); - - describe('verify()', () => { - let keyPair: BytesKeyPair; - - before(async () => { - keyPair = await Secp256k1.generateKeyPair(); - }); - - it('returns a boolean result', async () => { - const data = new Uint8Array([51, 52, 53]); - const signature = await Secp256k1.sign({ hash: 'SHA-256', key: keyPair.privateKey, data }); - - const isValid = await Secp256k1.verify({ hash: 'SHA-256', key: keyPair.publicKey, signature, data }); - expect(isValid).to.exist; - expect(isValid).to.be.true; - }); - - it('accepts input data as Uint8Array', async () => { - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const hash = 'SHA-256'; - let isValid: boolean; - let signature: Uint8Array; - - // TypedArray - Uint8Array - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data }); - expect(isValid).to.be.true; - }); - - it('accepts both compressed and uncompressed public keys', async () => { - let signature: Uint8Array; - let isValid: boolean; - const hash = 'SHA-256'; - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - - // Generate signature using the private key. - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); - - // Attempt to verify the signature using a compressed public key. - const compressedPublicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey, compressedPublicKey: true }); - isValid = await Secp256k1.verify({ hash, key: compressedPublicKey, signature, data }); - expect(isValid).to.be.true; - - // Attempt to verify the signature using an uncompressed public key. - const uncompressedPublicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey, compressedPublicKey: false }); - isValid = await Secp256k1.verify({ hash, key: uncompressedPublicKey, signature, data }); - expect(isValid).to.be.true; - }); - - it('returns false if the signed data was mutated', async () => { - const hash = 'SHA-256'; - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let isValid: boolean; - - // Generate signature using the private key. - const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); - - // Verification should return true with the data used to generate the signature. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data }); - expect(isValid).to.be.true; - - // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. - const mutatedData = new Uint8Array(data); - mutatedData[0] ^= 1 << 0; - - // Verification should return false if the given data does not match the data used to generate the signature. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: mutatedData }); - expect(isValid).to.be.false; - }); - - it('returns false if the signature was mutated', async () => { - const hash = 'SHA-256'; - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let isValid: boolean; - - // Generate signature using the private key. - const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); - - // Verification should return true with the data used to generate the signature. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data }); - expect(isValid).to.be.true; - - // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. - const mutatedSignature = new Uint8Array(signature); - mutatedSignature[0] ^= 1 << 0; - - // Verification should return false if the signature was modified. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature: signature, data: mutatedSignature }); - expect(isValid).to.be.false; - }); - - it('returns false with a signature generated using a different private key', async () => { - const hash = 'SHA-256'; - const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - const keyPairA = await Secp256k1.generateKeyPair(); - const keyPairB = await Secp256k1.generateKeyPair(); - let isValid: boolean; - - // Generate a signature using the private key from key pair B. - const signatureB = await Secp256k1.sign({ hash, key: keyPairB.privateKey, data }); - - // Verification should return false with the public key from key pair A. - isValid = await Secp256k1.verify({ hash, key: keyPairA.publicKey, signature: signatureB, data }); - expect(isValid).to.be.false; - }); - }); - }); - - describe('X25519', () => { - describe('generateKeyPair()', () => { - it('returns a pair of keys of type Uint8Array', async () => { - const keyPair = await X25519.generateKeyPair(); - expect(keyPair).to.have.property('privateKey'); - expect(keyPair).to.have.property('publicKey'); - expect(keyPair.privateKey).to.be.instanceOf(Uint8Array); - expect(keyPair.publicKey).to.be.instanceOf(Uint8Array); - }); - - it('returns a 32-byte private key', async () => { - const keyPair = await X25519.generateKeyPair(); - expect(keyPair.privateKey.byteLength).to.equal(32); - }); - - it('returns a 32-byte compressed public key', async () => { - const keyPair = await X25519.generateKeyPair(); - expect(keyPair.publicKey.byteLength).to.equal(32); - }); - }); - - describe('getPublicKey()', () => { - let keyPair: BytesKeyPair; - - before(async () => { - keyPair = await X25519.generateKeyPair(); - }); - - it('returns a 32-byte compressed public key', async () => { - const publicKey = await X25519.getPublicKey({ privateKey: keyPair.privateKey }); - expect(publicKey).to.be.instanceOf(Uint8Array); - expect(publicKey.byteLength).to.equal(32); - }); - }); - - describe('sharedSecret()', () => { - let otherPartyKeyPair: BytesKeyPair; - let ownKeyPair: BytesKeyPair; - - beforeEach(async () => { - otherPartyKeyPair = await X25519.generateKeyPair(); - ownKeyPair = await X25519.generateKeyPair(); - }); - - it('generates a 32-byte compressed secret', async () => { - const sharedSecret = await X25519.sharedSecret({ - privateKey : ownKeyPair.privateKey, - publicKey : otherPartyKeyPair.publicKey - }); - expect(sharedSecret).to.be.instanceOf(Uint8Array); - expect(sharedSecret.byteLength).to.equal(32); - }); - - it('generates identical output if keypairs are swapped', async () => { - const sharedSecretOwnOther = await X25519.sharedSecret({ - privateKey : ownKeyPair.privateKey, - publicKey : otherPartyKeyPair.publicKey - }); - - const sharedSecretOtherOwn = await X25519.sharedSecret({ - privateKey : otherPartyKeyPair.privateKey, - publicKey : ownKeyPair.publicKey - }); - - expect(sharedSecretOwnOther).to.deep.equal(sharedSecretOtherOwn); - }); - }); - - describe('validatePublicKey()', () => { - it('throws a not implemented error', async () => { - await expect( - X25519.validatePublicKey({ key: new Uint8Array(32) }) - ).to.eventually.be.rejectedWith(Error, 'Not implemented'); - }); - }); - }); - - describe('XChaCha20', () => { - describe('decrypt()', () => { - it('returns Uint8Array plaintext with length matching input', async () => { - const plaintext = await XChaCha20.decrypt({ - data : new Uint8Array(10), - key : new Uint8Array(32), - nonce : new Uint8Array(24) - }); - expect(plaintext).to.be.an('Uint8Array'); - expect(plaintext.byteLength).to.equal(10); - }); - - it('passes test vectors', async () => { - const input = { - data : Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toUint8Array(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() - }; - const output = Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(); - - const ciphertext = await XChaCha20.decrypt({ - data : input.data, - key : input.key, - nonce : input.nonce - }); - - expect(ciphertext).to.deep.equal(output); - }); - }); - - describe('encrypt()', () => { - it('returns Uint8Array ciphertext with length matching input', async () => { - const ciphertext = await XChaCha20.encrypt({ - data : new Uint8Array(10), - key : new Uint8Array(32), - nonce : new Uint8Array(24) - }); - expect(ciphertext).to.be.an('Uint8Array'); - expect(ciphertext.byteLength).to.equal(10); - }); - - it('passes test vectors', async () => { - const input = { - data : Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() - }; - const output = Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toUint8Array(); - - const ciphertext = await XChaCha20.encrypt({ - data : input.data, - key : input.key, - nonce : input.nonce - }); - - expect(ciphertext).to.deep.equal(output); - }); - }); - - describe('generateKey()', () => { - it('returns a 32-byte secret key of type Uint8Array', async () => { - const secretKey = await XChaCha20.generateKey(); - expect(secretKey).to.be.instanceOf(Uint8Array); - expect(secretKey.byteLength).to.equal(32); - }); - }); - }); - - describe('XChaCha20Poly1305', () => { - describe('decrypt()', () => { - it('returns Uint8Array plaintext with length matching input', async () => { - const plaintext = await XChaCha20Poly1305.decrypt({ - data : Convert.hex('789e9689e5208d7fd9e1').toUint8Array(), - key : new Uint8Array(32), - nonce : new Uint8Array(24), - tag : Convert.hex('09701fb9f36ab77a0f136ca539229a34').toUint8Array() - }); - expect(plaintext).to.be.an('Uint8Array'); - expect(plaintext.byteLength).to.equal(10); - }); - - it('passes test vectors', async () => { - const input = { - data : Convert.hex('80246ca517c0fb5860c19090a7e7a2b030dde4882520102cbc64fad937916596ca9d').toUint8Array(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array(), - tag : Convert.hex('9e10a121d990e6a290f6b534516aa32f').toUint8Array() - }; - const output = Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(); - - const plaintext = await XChaCha20Poly1305.decrypt({ - data : input.data, - key : input.key, - nonce : input.nonce, - tag : input.tag - }); - - expect(plaintext).to.deep.equal(output); - }); - - it('throws an error if the wrong tag is given', async () => { - await expect( - XChaCha20Poly1305.decrypt({ - data : new Uint8Array(10), - key : new Uint8Array(32), - nonce : new Uint8Array(24), - tag : new Uint8Array(16) - }) - ).to.eventually.be.rejectedWith(Error, 'Wrong tag'); - }); - }); - - describe('encrypt()', () => { - it('returns Uint8Array ciphertext and tag', async () => { - const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ - data : new Uint8Array(10), - key : new Uint8Array(32), - nonce : new Uint8Array(24) - }); - expect(ciphertext).to.be.an('Uint8Array'); - expect(ciphertext.byteLength).to.equal(10); - expect(tag).to.be.an('Uint8Array'); - expect(tag.byteLength).to.equal(16); - }); - - it('accepts additional authenticated data', async () => { - const { ciphertext: ciphertextAad, tag: tagAad } = await XChaCha20Poly1305.encrypt({ - additionalData : new Uint8Array(64), - data : new Uint8Array(10), - key : new Uint8Array(32), - nonce : new Uint8Array(24) - }); - - const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ - data : new Uint8Array(10), - key : new Uint8Array(32), - nonce : new Uint8Array(24) - }); - - expect(ciphertextAad.byteLength).to.equal(10); - expect(ciphertext.byteLength).to.equal(10); - expect(ciphertextAad).to.deep.equal(ciphertext); - expect(tagAad).to.not.deep.equal(tag); - }); - - it('passes test vectors', async () => { - const input = { - data : Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() - }; - const output = { - ciphertext : Convert.hex('80246ca517c0fb5860c19090a7e7a2b030dde4882520102cbc64fad937916596ca9d').toUint8Array(), - tag : Convert.hex('9e10a121d990e6a290f6b534516aa32f').toUint8Array() - }; - - const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ - data : input.data, - key : input.key, - nonce : input.nonce - }); - - expect(ciphertext).to.deep.equal(output.ciphertext); - expect(tag).to.deep.equal(output.tag); - }); - }); - - describe('generateKey()', () => { - it('returns a 32-byte secret key of type Uint8Array', async () => { - const secretKey = await XChaCha20Poly1305.generateKey(); - expect(secretKey).to.be.instanceOf(Uint8Array); - expect(secretKey.byteLength).to.equal(32); - }); - }); - }); - -}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/aes-ctr.spec.ts b/packages/crypto/tests/crypto-primitives/aes-ctr.spec.ts new file mode 100644 index 000000000..9f8fdf2a2 --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/aes-ctr.spec.ts @@ -0,0 +1,153 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { JwkParamsOctPrivate, PrivateKeyJwk, PublicKeyJwk } from '../../src/jose.js'; + +import { AesCtr } from '../../src/crypto-primitives/aes-ctr.js'; +import { aesCtrTestVectors } from '../fixtures/test-vectors/aes.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('AesCtr', () => { + describe('bytesToPrivateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKeyBytes = Convert.hex('ffbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + const privateKey = await AesCtr.bytesToPrivateKey({ privateKeyBytes }); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + + it('returns the expected JWK given byte array input', async () => { + const privateKeyBytes = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + + const privateKey = await AesCtr.bytesToPrivateKey({ privateKeyBytes }); + + const expectedOutput: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + expect(privateKey).to.deep.equal(expectedOutput); + }); + }); + + describe('decrypt', () => { + for (const vector of aesCtrTestVectors) { + it(`passes test vector ${vector.id}`, async () => { + const plaintext = await AesCtr.decrypt({ + counter : Convert.hex(vector.counter).toUint8Array(), + data : Convert.hex(vector.ciphertext).toUint8Array(), + key : await AesCtr.bytesToPrivateKey({ privateKeyBytes: Convert.hex(vector.key).toUint8Array() }), + length : vector.length + }); + expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); + }); + } + + it('accepts ciphertext input as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const privateKey = await AesCtr.generateKey({ length: 256 }); + + const ciphertext = await AesCtr.decrypt({ counter: new Uint8Array(16), data, key: privateKey, length: 128 }); + expect(ciphertext).to.be.instanceOf(Uint8Array); + }); + }); + + describe('encrypt', () => { + for (const vector of aesCtrTestVectors) { + it(`passes test vector ${vector.id}`, async () => { + const ciphertext = await AesCtr.encrypt({ + counter : Convert.hex(vector.counter).toUint8Array(), + data : Convert.hex(vector.data).toUint8Array(), + key : await AesCtr.bytesToPrivateKey({ privateKeyBytes: Convert.hex(vector.key).toUint8Array() }), + length : vector.length + }); + expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext); + }); + } + + it('accepts plaintext input as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const privateKey = await AesCtr.generateKey({ length: 256 }); + let ciphertext: Uint8Array; + + // Uint8Array + ciphertext = await AesCtr.encrypt({ counter: new Uint8Array(16), data, key: privateKey, length: 128 }); + expect(ciphertext).to.be.instanceOf(Uint8Array); + }); + }); + + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await AesCtr.generateKey({ length: 256 }); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + + it('returns a private key of the specified length', async () => { + let privateKey: JwkParamsOctPrivate; + let privateKeyBytes: Uint8Array; + + // 128 bits + privateKey = await AesCtr.generateKey({ length: 128 }) as JwkParamsOctPrivate; + privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(16); + + // 192 bits + privateKey = await AesCtr.generateKey({ length: 192 }) as JwkParamsOctPrivate; + privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(24); + + // 256 bits + privateKey = await AesCtr.generateKey({ length: 256 }) as JwkParamsOctPrivate; + privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(32); + }); + }); + + describe('privateKeyToBytes()', () => { + it('returns a private key as a byte array', async () => { + const privateKey = await AesCtr.generateKey({ length: 128 }); + const privateKeyBytes = await AesCtr.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + }); + + it('returns the expected byte array for JWK input', async () => { + const privateKey: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + const privateKeyBytes = await AesCtr.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + expect(privateKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an asymmetric public key', async () => { + const publicKey: PublicKeyJwk = { + crv : 'Ed25519', + kty : 'OKP', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + }; + + await expect( + // @ts-expect-error because a public key is being passed to a method that expects a private key. + AesCtr.privateKeyToBytes({ privateKey: publicKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid oct private key'); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/aes-gcm.spec.ts b/packages/crypto/tests/crypto-primitives/aes-gcm.spec.ts new file mode 100644 index 000000000..1f793c7e7 --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/aes-gcm.spec.ts @@ -0,0 +1,156 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { JwkParamsOctPrivate, PrivateKeyJwk, PublicKeyJwk } from '../../src/jose.js'; + +import { AesGcm } from '../../src/crypto-primitives/aes-gcm.js'; +import { aesGcmTestVectors } from '../fixtures/test-vectors/aes.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('AesGcm', () => { + describe('bytesToPrivateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKeyBytes = Convert.hex('ffbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + const privateKey = await AesGcm.bytesToPrivateKey({ privateKeyBytes }); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + + it('returns the expected JWK given byte array input', async () => { + const privateKeyBytes = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + + const privateKey = await AesGcm.bytesToPrivateKey({ privateKeyBytes }); + + const expectedOutput: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + expect(privateKey).to.deep.equal(expectedOutput); + }); + }); + + describe('decrypt', () => { + for (const vector of aesGcmTestVectors) { + it(`passes test vector ${vector.id}`, async () => { + const plaintext = await AesGcm.decrypt({ + additionalData : Convert.hex(vector.aad).toUint8Array(), + iv : Convert.hex(vector.iv).toUint8Array(), + data : Convert.hex(vector.ciphertext + vector.tag).toUint8Array(), + key : await AesGcm.bytesToPrivateKey({ privateKeyBytes: Convert.hex(vector.key).toUint8Array() }), + tagLength : vector.tagLength + }); + expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); + }); + } + + it('accepts ciphertext input as Uint8Array', async () => { + const privateKeyBytes = Convert.hex('de4ea2de269297bfbf4be347dcdd4631').toUint8Array(); + const privateKey = await AesGcm.bytesToPrivateKey({ privateKeyBytes }); + const ciphertext = Convert.hex('f27e81aa63c315a5cd03e2abcbc62a5665').toUint8Array(); + + const plaintext = await AesGcm.decrypt({ data: ciphertext, iv: new Uint8Array(12), key: privateKey, tagLength: 128 }); + expect(plaintext).to.be.instanceOf(Uint8Array); + }); + }); + + describe('encrypt', () => { + for (const vector of aesGcmTestVectors) { + it(`passes test vector ${vector.id}`, async () => { + const ciphertext = await AesGcm.encrypt({ + additionalData : Convert.hex(vector.aad).toUint8Array(), + iv : Convert.hex(vector.iv).toUint8Array(), + data : Convert.hex(vector.data).toUint8Array(), + key : await AesGcm.bytesToPrivateKey({ privateKeyBytes: Convert.hex(vector.key).toUint8Array() }), + tagLength : vector.tagLength + }); + expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext + vector.tag); + }); + } + + it('accepts plaintext input as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const secretKey = await AesGcm.generateKey({ length: 256 }); + let ciphertext: Uint8Array; + + // TypedArray - Uint8Array + ciphertext = await AesGcm.encrypt({ data, iv: new Uint8Array(12), key: secretKey, tagLength: 128 }); + expect(ciphertext).to.be.instanceOf(Uint8Array); + }); + }); + + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await AesGcm.generateKey({ length: 256 }); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + + it('returns a private key of the specified length', async () => { + let privateKey: JwkParamsOctPrivate; + let privateKeyBytes: Uint8Array; + + // 128 bits + privateKey = await AesGcm.generateKey({ length: 128 }) as JwkParamsOctPrivate; + privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(16); + + // 192 bits + privateKey = await AesGcm.generateKey({ length: 192 }) as JwkParamsOctPrivate; + privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(24); + + // 256 bits + privateKey = await AesGcm.generateKey({ length: 256 }) as JwkParamsOctPrivate; + privateKeyBytes = Convert.base64Url(privateKey.k).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(32); + }); + }); + + describe('privateKeyToBytes()', () => { + it('returns a private key as a byte array', async () => { + const privateKey = await AesGcm.generateKey({ length: 128 }); + const privateKeyBytes = await AesGcm.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + }); + + it('returns the expected byte array for JWK input', async () => { + const privateKey: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + const privateKeyBytes = await AesGcm.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + expect(privateKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an asymmetric public key', async () => { + const publicKey: PublicKeyJwk = { + crv : 'Ed25519', + kty : 'OKP', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + }; + + await expect( + // @ts-expect-error because a public key is being passed to a method that expects a private key. + AesGcm.privateKeyToBytes({ privateKey: publicKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid oct private key'); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/concat-kdf.spec.ts b/packages/crypto/tests/crypto-primitives/concat-kdf.spec.ts new file mode 100644 index 000000000..06f5096a9 --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/concat-kdf.spec.ts @@ -0,0 +1,129 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import { NotSupportedError } from '../../src/algorithms-api/errors.js'; +import { ConcatKdf } from '../../src/crypto-primitives/concat-kdf.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('ConcatKdf', () => { + describe('deriveKey()', () => { + it('matches RFC 7518 ECDH-ES key agreement computation example', async () => { + // Test vector 1 + const inputSharedSecret = 'nlbZHYFxNdNyg0KDv4QmnPsxbqPagGpI9tqneYz-kMQ'; + const input = { + sharedSecret : Convert.base64Url(inputSharedSecret).toUint8Array(), + keyDataLen : 128, + otherInfo : { + algorithmId : 'A128GCM', + partyUInfo : 'Alice', + partyVInfo : 'Bob', + suppPubInfo : 128 + } + }; + const output = 'VqqN6vgjbSBcIijNcacQGg'; + + const derivedKeyingMaterial = await ConcatKdf.deriveKey(input); + + const expectedResult = Convert.base64Url(output).toUint8Array(); + expect(derivedKeyingMaterial).to.deep.equal(expectedResult); + expect(derivedKeyingMaterial.byteLength).to.equal(16); + }); + + it('accepts other info as String and TypedArray', async () => { + const inputBase = { + sharedSecret : new Uint8Array([1, 2, 3]), + keyDataLen : 256, + otherInfo : {} + }; + + // String input. + const inputString = { ...inputBase, otherInfo: { + algorithmId : 'A128GCM', + partyUInfo : 'Alice', + partyVInfo : 'Bob', + suppPubInfo : 128 + }}; + let derivedKeyingMaterial = await ConcatKdf.deriveKey(inputString); + expect(derivedKeyingMaterial).to.be.an('Uint8Array'); + expect(derivedKeyingMaterial.byteLength).to.equal(32); + + // TypedArray input. + const inputTypedArray = { ...inputBase, otherInfo: { + algorithmId : 'A128GCM', + partyUInfo : Convert.string('Alice').toUint8Array(), + partyVInfo : Convert.string('Bob').toUint8Array(), + suppPubInfo : 128 + }}; + derivedKeyingMaterial = await ConcatKdf.deriveKey(inputTypedArray); + expect(derivedKeyingMaterial).to.be.an('Uint8Array'); + expect(derivedKeyingMaterial.byteLength).to.equal(32); + }); + + it('throws error if multi-round Concat KDF attempted', async () => { + await expect( + // @ts-expect-error because only parameters needed to trigger the error are specified. + ConcatKdf.deriveKey({ keyDataLen: 512 }) + ).to.eventually.be.rejectedWith(NotSupportedError, 'rounds not supported'); + }); + + it('throws an error if suppPubInfo is not a Number', async () => { + await expect( + ConcatKdf.deriveKey({ + sharedSecret : new Uint8Array([1, 2, 3]), + keyDataLen : 128, + otherInfo : { + algorithmId : 'A128GCM', + partyUInfo : 'Alice', + partyVInfo : 'Bob', + // @ts-expect-error because a string is specified to trigger an error. + suppPubInfo : '128', + } + }) + ).to.eventually.be.rejectedWith(TypeError, 'Fixed length input must be a number'); + }); + }); + + describe('computeOtherInfo()', () => { + it('returns concatenated and formatted Uint8Array', () => { + const input = { + algorithmId : 'A128GCM', + partyUInfo : 'Alice', + partyVInfo : 'Bob', + suppPubInfo : 128, + suppPrivInfo : 'gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0' + }; + const output = 'AAAAB0ExMjhHQ00AAAAFQWxpY2UAAAADQm9iAAAAgAAAACtnSTBHQUlMQmR1N1Q1M2FrckZtTXlHY3NGM241ZE83TW13TkJIS1c1U1Yw'; + + // @ts-expect-error because computeOtherInfo() is a private method. + const otherInfo = ConcatKdf.computeOtherInfo(input); + + const expectedResult = Convert.base64Url(output).toUint8Array(); + expect(otherInfo).to.deep.equal(expectedResult); + }); + + it('matches RFC 7518 ECDH-ES key agreement computation example', async () => { + // Test vector 1. + const input = { + algorithmId : 'A128GCM', + partyUInfo : 'Alice', + partyVInfo : 'Bob', + suppPubInfo : 128 + }; + const output = 'AAAAB0ExMjhHQ00AAAAFQWxpY2UAAAADQm9iAAAAgA'; + + // @ts-expect-error because computeOtherInfo() is a private method. + const otherInfo = ConcatKdf.computeOtherInfo(input); + + const expectedResult = Convert.base64Url(output).toUint8Array(); + expect(otherInfo).to.deep.equal(expectedResult); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/ed25519.spec.ts b/packages/crypto/tests/crypto-primitives/ed25519.spec.ts new file mode 100644 index 000000000..e2c153390 --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/ed25519.spec.ts @@ -0,0 +1,381 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { JwkParamsOkpPrivate, PrivateKeyJwk, PublicKeyJwk } from '../../src/jose.js'; + +import ed25519Sign from '../fixtures/test-vectors/ed25519/sign.json' assert { type: 'json' }; +import ed25519Verify from '../fixtures/test-vectors/ed25519/verify.json' assert { type: 'json' }; +import ed25519ComputePublicKey from '../fixtures/test-vectors/ed25519/compute-public-key.json' assert { type: 'json' }; +import ed25519BytesToPublicKey from '../fixtures/test-vectors/ed25519/bytes-to-public-key.json' assert { type: 'json' }; +import ed25519PublicKeyToBytes from '../fixtures/test-vectors/ed25519/public-key-to-bytes.json' assert { type: 'json' }; +import ed25519BytesToPrivateKey from '../fixtures/test-vectors/ed25519/bytes-to-private-key.json' assert { type: 'json' }; +import ed25519PrivateKeyToBytes from '../fixtures/test-vectors/ed25519/private-key-to-bytes.json' assert { type: 'json' }; +import ed25519ConvertPublicKeyToX25519 from '../fixtures/test-vectors/ed25519/convert-public-key-to-x25519.json' assert { type: 'json' }; +import ed25519ConvertPrivateKeyToX25519 from '../fixtures/test-vectors/ed25519/convert-private-key-to-x25519.json' assert { type: 'json' }; + +import { Ed25519 } from '../../src/crypto-primitives/ed25519.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('Ed25519', () => { + let privateKey: PrivateKeyJwk; + let publicKey: PublicKeyJwk; + + before(async () => { + privateKey = await Ed25519.generateKey(); + publicKey = await Ed25519.computePublicKey({ privateKey }); + }); + + describe('bytesToPrivateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKeyBytes = Convert.hex('4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb').toUint8Array(); + const privateKey = await Ed25519.bytesToPrivateKey({ privateKeyBytes }); + + expect(privateKey).to.have.property('crv', 'Ed25519'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('x'); + }); + + for (const vector of ed25519BytesToPrivateKey.vectors) { + it(vector.description, async () => { + const privateKey = await Ed25519.bytesToPrivateKey({ + privateKeyBytes: Convert.hex(vector.input.privateKeyBytes).toUint8Array() + }); + expect(privateKey).to.deep.equal(vector.output); + }); + } + }); + + describe('bytesToPublicKey()', () => { + it('returns a public key in JWK format', async () => { + const publicKeyBytes = Convert.hex('3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c').toUint8Array(); + const publicKey = await Ed25519.bytesToPublicKey({ publicKeyBytes }); + + expect(publicKey).to.have.property('crv', 'Ed25519'); + expect(publicKey).to.have.property('kid'); + expect(publicKey).to.have.property('kty', 'OKP'); + expect(publicKey).to.have.property('x'); + expect(publicKey).to.not.have.property('d'); + }); + + for (const vector of ed25519BytesToPublicKey.vectors) { + it(vector.description, async () => { + const publicKey = await Ed25519.bytesToPublicKey({ + publicKeyBytes: Convert.hex(vector.input.publicKeyBytes).toUint8Array() + }); + expect(publicKey).to.deep.equal(vector.output); + }); + } + }); + + describe('computePublicKey()', () => { + it('returns a public key in JWK format', async () => { + const publicKey = await Ed25519.computePublicKey({ privateKey }); + + expect(publicKey).to.have.property('kty', 'OKP'); + expect(publicKey).to.have.property('crv', 'Ed25519'); + expect(publicKey).to.have.property('x'); + expect(publicKey).to.not.have.property('d'); + }); + + for (const vector of ed25519ComputePublicKey.vectors) { + it(vector.description, async () => { + const publicKey = await Ed25519.computePublicKey(vector.input as { privateKey: PrivateKeyJwk }); + expect(publicKey).to.deep.equal(vector.output); + }); + } + }); + + describe('convertPrivateKeyToX25519()', () => { + for (const vector of ed25519ConvertPrivateKeyToX25519.vectors) { + it(vector.description, async () => { + const x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519( + vector.input as { privateKey: PrivateKeyJwk } + ); + expect(x25519PrivateKey).to.deep.equal(vector.output); + }); + } + }); + + describe('convertPublicKeyToX25519()', () => { + it('throws an error when provided an invalid Ed25519 public key', async () => { + const invalidEd25519PublicKeyBytes = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); + + const invalidEd25519PublicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : Convert.uint8Array(invalidEd25519PublicKeyBytes).toBase64Url() + }; + + await expect( + Ed25519.convertPublicKeyToX25519({ publicKey: invalidEd25519PublicKey }) + ).to.eventually.be.rejectedWith(Error, 'Invalid public key'); + }); + + it('throws an error when provided an Ed25519 private key', async () => { + const ed25519PrivateKey: PrivateKeyJwk = { + kty : 'OKP', + crv : 'Ed25519', + d : 'dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo', + x : '0KTOwPi1C6HpNuxWFUVKqX37J4ZPXxdgivLLsQVI8bM' + }; + + await expect( + Ed25519.convertPublicKeyToX25519({ publicKey: ed25519PrivateKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid OKP public key'); + }); + + for (const vector of ed25519ConvertPublicKeyToX25519.vectors) { + it(vector.description, async () => { + const x25519PrivateKey = await Ed25519.convertPublicKeyToX25519( + vector.input as { publicKey: PublicKeyJwk } + ); + expect(x25519PrivateKey).to.deep.equal(vector.output); + }); + } + }); + + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await Ed25519.generateKey(); + + expect(privateKey).to.have.property('crv', 'Ed25519'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('x'); + }); + + it('returns a 32-byte private key', async () => { + const privateKey = await Ed25519.generateKey() as JwkParamsOkpPrivate; + + const privateKeyBytes = Convert.base64Url(privateKey.d).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(32); + }); + }); + + describe('privateKeyToBytes()', () => { + it('returns a private key as a byte array', async () => { + const privateKey: PrivateKeyJwk = { + crv : 'Ed25519', + d : 'TM0Imyj_ltqdtsNG7BFOD1uKMZ81q6Yk2oz27U-4pvs', + kty : 'OKP', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + kid : 'FtIu-VbGrfe_KB6CH7GNwODB72MNxj_ml11dEvO-7kk' + }; + const privateKeyBytes = await Ed25519.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb').toUint8Array(); + expect(privateKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an Ed25519 public key', async () => { + const publicKey: PublicKeyJwk = { + crv : 'Ed25519', + kty : 'OKP', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + }; + + await expect( + // @ts-expect-error because a public key is being passed to a method that expects a private key. + Ed25519.privateKeyToBytes({ privateKey: publicKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid OKP private key'); + }); + + for (const vector of ed25519PrivateKeyToBytes.vectors) { + it(vector.description, async () => { + const privateKeyBytes = await Ed25519.privateKeyToBytes({ + privateKey: vector.input.privateKey as PrivateKeyJwk + }); + expect(privateKeyBytes).to.deep.equal(Convert.hex(vector.output).toUint8Array()); + }); + } + }); + + describe('publicKeyToBytes()', () => { + it('returns a public key in JWK format', async () => { + const publicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + kid : 'FtIu-VbGrfe_KB6CH7GNwODB72MNxj_ml11dEvO-7kk' + }; + + const publicKeyBytes = await Ed25519.publicKeyToBytes({ publicKey }); + + expect(publicKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c').toUint8Array(); + expect(publicKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an Ed25519 private key', async () => { + const privateKey: PrivateKeyJwk = { + crv : 'Ed25519', + d : 'TM0Imyj_ltqdtsNG7BFOD1uKMZ81q6Yk2oz27U-4pvs', + kty : 'OKP', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + kid : 'FtIu-VbGrfe_KB6CH7GNwODB72MNxj_ml11dEvO-7kk' + }; + + await expect( + Ed25519.publicKeyToBytes({ publicKey: privateKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid OKP public key'); + }); + + for (const vector of ed25519PublicKeyToBytes.vectors) { + it(vector.description, async () => { + const publicKeyBytes = await Ed25519.publicKeyToBytes({ + publicKey: vector.input.publicKey as PublicKeyJwk + }); + expect(publicKeyBytes).to.deep.equal(Convert.hex(vector.output).toUint8Array()); + }); + } + }); + + describe('sign()', () => { + it('returns a 64-byte signature of type Uint8Array', async () => { + const data = new Uint8Array([51, 52, 53]); + const signature = await Ed25519.sign({ key: privateKey, data }); + expect(signature).to.be.instanceOf(Uint8Array); + expect(signature.byteLength).to.equal(64); + }); + + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + let signature: Uint8Array; + + signature = await Ed25519.sign({ key: privateKey, data: data }); + expect(signature).to.be.instanceOf(Uint8Array); + }); + + for (const vector of ed25519Sign.vectors) { + it(vector.description, async () => { + const signature = await Ed25519.sign({ + data : Convert.hex(vector.input.data).toUint8Array(), + key : vector.input.key as PrivateKeyJwk + }); + + const signatureHex = Convert.uint8Array(signature).toHex(); + expect(signatureHex).to.deep.equal(vector.output); + }); + } + }); + + describe('validatePublicKey()', () => { + it('returns true for valid public keys', async () => { + const key = Convert.hex('a12c2beb77265f2aac953b5009349d94155a03ada416aad451319480e983ca4c').toUint8Array(); + // @ts-expect-error because validatePublicKey() is a private method. + const isValid = await Ed25519.validatePublicKey({ key }); + expect(isValid).to.be.true; + }); + + it('returns false for invalid public keys', async () => { + const key = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); + // @ts-expect-error because validatePublicKey() is a private method. + const isValid = await Ed25519.validatePublicKey({ key }); + expect(isValid).to.be.false; + }); + + it('returns false if a private key is given', async () => { + const key = Convert.hex('0a23a20072891237aa0864b5765139514908787878cd77135a0059881d313f00').toUint8Array(); + // @ts-expect-error because validatePublicKey() is a private method. + const isValid = await Ed25519.validatePublicKey({ key }); + expect(isValid).to.be.false; + }); + }); + + describe('verify()', () => { + it('returns a boolean result', async () => { + const data = new Uint8Array([51, 52, 53]); + const signature = await Ed25519.sign({ key: privateKey, data }); + + const isValid = await Ed25519.verify({ key: publicKey, signature, data }); + expect(isValid).to.exist; + expect(isValid).to.be.a('boolean'); + }); + + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const signature = await Ed25519.sign({ key: privateKey, data }); + + const isValid = await Ed25519.verify({ key: publicKey, signature, data }); + expect(isValid).to.be.true; + }); + + it('returns false if the signed data was mutated', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + let isValid: boolean; + + // Generate signature using the private key. + const signature = await Ed25519.sign({ key: privateKey, data }); + + // Verification should return true with the data used to generate the signature. + isValid = await Ed25519.verify({ key: publicKey, signature, data }); + expect(isValid).to.be.true; + + // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. + const mutatedData = new Uint8Array(data); + mutatedData[0] ^= 1 << 0; + + // Verification should return false if the given data does not match the data used to generate the signature. + isValid = await Ed25519.verify({ key: publicKey, signature, data: mutatedData }); + expect(isValid).to.be.false; + }); + + it('returns false if the signature was mutated', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + let isValid: boolean; + + // Generate a new private key and get its public key. + privateKey = await Ed25519.generateKey(); + publicKey = await Ed25519.computePublicKey({ privateKey }); + + // Generate signature using the private key. + const signature = await Ed25519.sign({ key: privateKey, data }); + + // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. + const mutatedSignature = new Uint8Array(signature); + mutatedSignature[0] ^= 1 << 0; + + // Verification should return false if the signature was modified. + isValid = await Ed25519.verify({ key: publicKey, signature: signature, data: mutatedSignature }); + expect(isValid).to.be.false; + }); + + it('returns false with a signature generated using a different private key', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const privateKeyA = await Ed25519.generateKey(); + const publicKeyA = await Ed25519.computePublicKey({ privateKey: privateKeyA }); + const privateKeyB = await Ed25519.generateKey(); + let isValid: boolean; + + // Generate a signature using private key B. + const signatureB = await Ed25519.sign({ key: privateKeyB, data }); + + // Verification should return false with the public key from private key A. + isValid = await Ed25519.verify({ key: publicKeyA, signature: signatureB, data }); + expect(isValid).to.be.false; + }); + + for (const vector of ed25519Verify.vectors) { + it(vector.description, async () => { + const isValid = await Ed25519.verify({ + data : Convert.hex(vector.input.data).toUint8Array(), + key : vector.input.key as PublicKeyJwk, + signature : Convert.hex(vector.input.signature).toUint8Array() + }); + expect(isValid).to.equal(vector.output); + }); + } + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/pbkdf2.spec.ts b/packages/crypto/tests/crypto-primitives/pbkdf2.spec.ts new file mode 100644 index 000000000..65119a223 --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/pbkdf2.spec.ts @@ -0,0 +1,133 @@ +import sinon from 'sinon'; +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import { Pbkdf2 } from '../../src/crypto-primitives/pbkdf2.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('Pbkdf2', () => { + const password = Convert.string('password').toUint8Array(); + const salt = Convert.string('salt').toUint8Array(); + const iterations = 1; + const length = 256; // 32 bytes + + describe('deriveKey', () => { + it('successfully derives a key using WebCrypto, if available', async () => { + const subtleDeriveBitsSpy = sinon.spy(crypto.subtle, 'deriveBits'); + + const derivedKey = await Pbkdf2.deriveKey({ hash: 'SHA-256', password, salt, iterations, length }); + + expect(derivedKey).to.be.instanceOf(Uint8Array); + expect(derivedKey.byteLength).to.equal(length / 8); + expect(subtleDeriveBitsSpy.called).to.be.true; + + subtleDeriveBitsSpy.restore(); + }); + + it('successfully derives a key using node:crypto when WebCrypto is not supported', async function () { + // Skip test in web browsers since node:crypto is not available. + if (typeof window !== 'undefined') this.skip(); + + // Ensure that WebCrypto is not available for this test. + sinon.stub(crypto, 'subtle').value(null); + + // @ts-expect-error because we're spying on a private method. + const nodeCryptoDeriveKeySpy = sinon.spy(Pbkdf2, 'deriveKeyWithNodeCrypto'); + + const derivedKey = await Pbkdf2.deriveKey({ hash: 'SHA-256', password, salt, iterations, length }); + + expect(derivedKey).to.be.instanceOf(Uint8Array); + expect(derivedKey.byteLength).to.equal(length / 8); + expect(nodeCryptoDeriveKeySpy.called).to.be.true; + + nodeCryptoDeriveKeySpy.restore(); + sinon.restore(); + }); + + it('derives the same value with node:crypto and WebCrypto', async function () { + // Skip test in web browsers since node:crypto is not available. + if (typeof window !== 'undefined') this.skip(); + + const options = { hash: 'SHA-256', password, salt, iterations, length }; + + // @ts-expect-error because we're testing a private method. + const webCryptoDerivedKey = await Pbkdf2.deriveKeyWithNodeCrypto(options); + // @ts-expect-error because we're testing a private method. + const nodeCryptoDerivedKey = await Pbkdf2.deriveKeyWithWebCrypto(options); + + expect(webCryptoDerivedKey).to.deep.equal(nodeCryptoDerivedKey); + }); + + const hashFunctions: ('SHA-256' | 'SHA-384' | 'SHA-512')[] = ['SHA-256', 'SHA-384', 'SHA-512']; + hashFunctions.forEach(hash => { + it(`handles ${hash} hash function`, async () => { + const options = { hash, password, salt, iterations, length }; + + const derivedKey = await Pbkdf2.deriveKey(options); + expect(derivedKey).to.be.instanceOf(Uint8Array); + expect(derivedKey.byteLength).to.equal(length / 8); + }); + }); + + it('throws an error when an invalid hash function is used with WebCrypto', async () => { + const options = { + hash: 'SHA-2' as const, password, salt, iterations, length + }; + + // @ts-expect-error for testing purposes + await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error); + }); + + it('throws an error when an invalid hash function is used with node:crypto', async function () { + // Skip test in web browsers since node:crypto is not available. + if (typeof window !== 'undefined') this.skip(); + + // Ensure that WebCrypto is not available for this test. + sinon.stub(crypto, 'subtle').value(null); + + const options = { + hash: 'SHA-2' as const, password, salt, iterations, length + }; + + // @ts-expect-error for testing purposes + await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error); + + sinon.restore(); + }); + + it('throws an error when iterations count is not a positive number with WebCrypto', async () => { + const options = { + hash : 'SHA-256' as const, password, salt, + iterations : -1, length + }; + + // Every browser throws a different error message so a specific message cannot be checked. + await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error); + }); + + it('throws an error when iterations count is not a positive number with node:crypto', async function () { + // Skip test in web browsers since node:crypto is not available. + if (typeof window !== 'undefined') this.skip(); + + // Ensure that WebCrypto is not available for this test. + sinon.stub(crypto, 'subtle').value(null); + + const options = { + hash : 'SHA-256' as const, password, salt, + iterations : -1, length + }; + + await expect(Pbkdf2.deriveKey(options)).to.eventually.be.rejectedWith(Error, 'out of range'); + + sinon.restore(); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/secp256k1.spec.ts b/packages/crypto/tests/crypto-primitives/secp256k1.spec.ts new file mode 100644 index 000000000..c2a97427e --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/secp256k1.spec.ts @@ -0,0 +1,442 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { JwkParamsEcPrivate, PrivateKeyJwk, PublicKeyJwk } from '../../src/jose.js'; + +import secp256k1GetCurvePoints from '../fixtures/test-vectors/secp256k1/get-curve-points.json' assert { type: 'json' }; +import secp256k1BytesToPublicKey from '../fixtures/test-vectors/secp256k1/bytes-to-public-key.json' assert { type: 'json' }; +import secp256k1PublicKeyToBytes from '../fixtures/test-vectors/secp256k1/public-key-to-bytes.json' assert { type: 'json' }; +import secp256k1ValidatePublicKey from '../fixtures/test-vectors/secp256k1/validate-public-key.json' assert { type: 'json' }; +import secp256k1BytesToPrivateKey from '../fixtures/test-vectors/secp256k1/bytes-to-private-key.json' assert { type: 'json' }; +import secp256k1PrivateKeyToBytes from '../fixtures/test-vectors/secp256k1/private-key-to-bytes.json' assert { type: 'json' }; +import secp256k1ValidatePrivateKey from '../fixtures/test-vectors/secp256k1/validate-private-key.json' assert { type: 'json' }; + +import { Secp256k1 } from '../../src/crypto-primitives/secp256k1.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('Secp256k1', () => { + let privateKey: PrivateKeyJwk; + let publicKey: PublicKeyJwk; + + before(async () => { + privateKey = await Secp256k1.generateKey(); + publicKey = await Secp256k1.computePublicKey({ privateKey }); + }); + + describe('bytesToPrivateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKeyBytes = Convert.hex('740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88').toUint8Array(); + const privateKey = await Secp256k1.bytesToPrivateKey({ privateKeyBytes }); + + expect(privateKey).to.have.property('crv', 'secp256k1'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'EC'); + expect(privateKey).to.have.property('x'); + expect(privateKey).to.have.property('y'); + }); + + for (const vector of secp256k1BytesToPrivateKey.vectors) { + it(vector.description, async () => { + const privateKey = await Secp256k1.bytesToPrivateKey({ + privateKeyBytes: Convert.hex(vector.input.privateKeyBytes).toUint8Array() + }); + + expect(privateKey).to.deep.equal(vector.output); + }); + } + }); + + describe('bytesToPublicKey()', () => { + it('returns a public key in JWK format', async () => { + const publicKeyBytes = Convert.hex('043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553').toUint8Array(); + const publicKey = await Secp256k1.bytesToPublicKey({ publicKeyBytes }); + + expect(publicKey).to.have.property('crv', 'secp256k1'); + expect(publicKey).to.have.property('kid'); + expect(publicKey).to.have.property('kty', 'EC'); + expect(publicKey).to.have.property('x'); + expect(publicKey).to.have.property('y'); + expect(publicKey).to.not.have.property('d'); + }); + + for (const vector of secp256k1BytesToPublicKey.vectors) { + it(vector.description, async () => { + const publicKey = await Secp256k1.bytesToPublicKey({ + publicKeyBytes: Convert.hex(vector.input.publicKeyBytes).toUint8Array() + }); + expect(publicKey).to.deep.equal(vector.output); + }); + } + }); + + describe('computePublicKey()', () => { + it('returns a public key in JWK format', async () => { + publicKey = await Secp256k1.computePublicKey({ privateKey }); + + expect(publicKey).to.have.property('crv', 'secp256k1'); + expect(publicKey).to.not.have.property('d'); + expect(publicKey).to.have.property('kid'); + expect(publicKey).to.have.property('kty', 'EC'); + expect(publicKey).to.have.property('x'); + expect(publicKey).to.have.property('y'); + }); + }); + + describe('compressPublicKey()', () => { + it('converts an uncompressed public key to compressed format', async () => { + const compressedPublicKeyBytes = Convert.hex('026bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce214').toUint8Array(); + const uncompressedPublicKeyBytes = Convert.hex('046bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce21465062296011dd076ae4e8ce5163ccf69d01496d3147656dcc96645b95211f3c6').toUint8Array(); + + const output = await Secp256k1.compressPublicKey({ + publicKeyBytes: uncompressedPublicKeyBytes + }); + + // Confirm the length of the resulting public key is 33 bytes + expect(output.byteLength).to.equal(33); + + // Confirm the output matches the expected compressed public key. + expect(output).to.deep.equal(compressedPublicKeyBytes); + }); + + it('throws an error for an invalid uncompressed public key', async () => { + // Invalid uncompressed public key. + const invalidPublicKey = Convert.hex('dfebc16793a5737ac51f606a43524df8373c063e41d5a99b2f1530afd987284bd1c7cde1658a9a756e71f44a97b4783ea9dee5ccb7f1447eb4836d8de9bd4f81fd').toUint8Array(); + + try { + await Secp256k1.compressPublicKey({ + publicKeyBytes: invalidPublicKey, + }); + expect.fail('Expected method to throw an error.'); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Point of length 65 was invalid'); + } + }); + }); + + describe('decompressPublicKey()', () => { + it('converts a compressed public key to an uncompressed format', async () => { + const compressedPublicKeyBytes = Convert.hex('026bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce214').toUint8Array(); + const uncompressedPublicKeyBytes = Convert.hex('046bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce21465062296011dd076ae4e8ce5163ccf69d01496d3147656dcc96645b95211f3c6').toUint8Array(); + + const output = await Secp256k1.decompressPublicKey({ + publicKeyBytes: compressedPublicKeyBytes + }); + + // Confirm the length of the resulting public key is 65 bytes + expect(output.byteLength).to.equal(65); + + // Confirm the output matches the expected uncompressed public key. + expect(output).to.deep.equal(uncompressedPublicKeyBytes); + }); + + it('throws an error for an invalid compressed public key', async () => { + // Invalid compressed public key. + const invalidPublicKey = Convert.hex('fef0b998921eafb58f49efdeb0adc47123aa28a4042924236f08274d50c72fe7b0').toUint8Array(); + + try { + await Secp256k1.decompressPublicKey({ + publicKeyBytes: invalidPublicKey, + }); + expect.fail('Expected method to throw an error.'); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Point of length 33 was invalid'); + } + }); + }); + + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await Secp256k1.generateKey(); + + expect(privateKey).to.have.property('crv', 'secp256k1'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'EC'); + expect(privateKey).to.have.property('x'); + expect(privateKey).to.have.property('y'); + }); + + it('returns a 32-byte private key', async () => { + const privateKey = await Secp256k1.generateKey() as JwkParamsEcPrivate; + + const privateKeyBytes = Convert.base64Url(privateKey.d).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(32); + }); + }); + + describe('getCurvePoints()', () => { + for (const vector of secp256k1GetCurvePoints.vectors) { + it(vector.description, async () => { + const key = Convert.hex(vector.input.key).toUint8Array(); + // @ts-expect-error because getCurvePoints() is a private method. + const points = await Secp256k1.getCurvePoints({ key }); + expect(points.x).to.deep.equal(Convert.hex(vector.output.x).toUint8Array()); + expect(points.y).to.deep.equal(Convert.hex(vector.output.y).toUint8Array()); + }); + } + + it('throws error with invalid input key length', async () => { + await expect( + // @ts-expect-error because getCurvePoints() is a private method. + Secp256k1.getCurvePoints({ key: new Uint8Array(16) }) + ).to.eventually.be.rejectedWith(Error, 'Point of length 16 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes'); + }); + }); + + describe('privateKeyToBytes()', () => { + it('returns a private key as a byte array', async () => { + const privateKey: PrivateKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + d : 'dA7GmBDemtG48pjx0sDmpS3R6VjcKvyFdkvsFpwiLog', + x : 'N1KVEnQCMpbIp0sP_kL4L_S01LukMmR3QicD92H1klg', + y : 'wmp0ZbmnesDD8c7bE5xCiwsfu1UWhntSdjbzKG9wVVM', + kid : 'iwwOeCqgvREo5xGeBS-obWW9ZGjv0o1M65gUYN6SYh4' + }; + const privateKeyBytes = await Secp256k1.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88').toUint8Array(); + expect(privateKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided a secp256k1 public key', async () => { + const publicKey: PublicKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + x : 'N1KVEnQCMpbIp0sP_kL4L_S01LukMmR3QicD92H1klg', + y : 'wmp0ZbmnesDD8c7bE5xCiwsfu1UWhntSdjbzKG9wVVM' + }; + + await expect( + // @ts-expect-error because a public key is being passed to a method that expects a private key. + Secp256k1.privateKeyToBytes({ privateKey: publicKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid EC private key'); + }); + + for (const vector of secp256k1PrivateKeyToBytes.vectors) { + it(vector.description, async () => { + const privateKeyBytes = await Secp256k1.privateKeyToBytes({ + privateKey: vector.input.privateKey as PrivateKeyJwk + }); + expect(privateKeyBytes).to.deep.equal(Convert.hex(vector.output).toUint8Array()); + }); + } + }); + + describe('publicKeyToBytes()', () => { + it('returns a public key in JWK format', async () => { + const publicKey: PublicKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + x : 'N1KVEnQCMpbIp0sP_kL4L_S01LukMmR3QicD92H1klg', + y : 'wmp0ZbmnesDD8c7bE5xCiwsfu1UWhntSdjbzKG9wVVM', + kid : 'iwwOeCqgvREo5xGeBS-obWW9ZGjv0o1M65gUYN6SYh4' + }; + + const publicKeyBytes = await Secp256k1.publicKeyToBytes({ publicKey }); + + expect(publicKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553').toUint8Array(); + expect(publicKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an Ed25519 private key', async () => { + const privateKey: PrivateKeyJwk = { + kty : 'EC', + crv : 'secp256k1', + d : 'dA7GmBDemtG48pjx0sDmpS3R6VjcKvyFdkvsFpwiLog', + x : 'N1KVEnQCMpbIp0sP_kL4L_S01LukMmR3QicD92H1klg', + y : 'wmp0ZbmnesDD8c7bE5xCiwsfu1UWhntSdjbzKG9wVVM', + kid : 'iwwOeCqgvREo5xGeBS-obWW9ZGjv0o1M65gUYN6SYh4' + }; + + await expect( + Secp256k1.publicKeyToBytes({ publicKey: privateKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid EC public key'); + }); + + for (const vector of secp256k1PublicKeyToBytes.vectors) { + it(vector.description, async () => { + const publicKeyBytes = await Secp256k1.publicKeyToBytes({ + publicKey: vector.input.publicKey as PublicKeyJwk + }); + expect(publicKeyBytes).to.deep.equal(Convert.hex(vector.output).toUint8Array()); + }); + } + }); + + describe('sharedSecret()', () => { + let ownPrivateKey: PrivateKeyJwk; + let ownPublicKey: PublicKeyJwk; + let otherPartyPrivateKey: PrivateKeyJwk; + let otherPartyPublicKey: PublicKeyJwk; + + beforeEach(async () => { + ownPrivateKey = privateKey; + ownPublicKey = publicKey; + + otherPartyPrivateKey = await Secp256k1.generateKey(); + otherPartyPublicKey = await Secp256k1.computePublicKey({ privateKey: otherPartyPrivateKey }); + }); + + it('generates a 32-byte shared secret', async () => { + const sharedSecret = await Secp256k1.sharedSecret({ + privateKeyA : ownPrivateKey, + publicKeyB : otherPartyPublicKey + }); + expect(sharedSecret).to.be.instanceOf(Uint8Array); + expect(sharedSecret.byteLength).to.equal(32); + }); + + it('is commutative', async () => { + const sharedSecretOwnOther = await Secp256k1.sharedSecret({ + privateKeyA : ownPrivateKey, + publicKeyB : otherPartyPublicKey + }); + + const sharedSecretOtherOwn = await Secp256k1.sharedSecret({ + privateKeyA : otherPartyPrivateKey, + publicKeyB : ownPublicKey + }); + + expect(sharedSecretOwnOther).to.deep.equal(sharedSecretOtherOwn); + }); + + it('throws an error if the public/private keys from the same key pair are specified', async () => { + await expect( + Secp256k1.sharedSecret({ + privateKeyA : ownPrivateKey, + publicKeyB : ownPublicKey + }) + ).to.eventually.be.rejectedWith(Error, 'shared secret cannot be computed from a single key pair'); + }); + }); + + describe('sign()', () => { + it('returns a 64-byte signature of type Uint8Array', async () => { + const data = new Uint8Array([51, 52, 53]); + const signature = await Secp256k1.sign({ key: privateKey, data }); + expect(signature).to.be.instanceOf(Uint8Array); + expect(signature.byteLength).to.equal(64); + }); + + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const key = privateKey; + let signature: Uint8Array; + + signature = await Secp256k1.sign({ key, data }); + expect(signature).to.be.instanceOf(Uint8Array); + }); + }); + + describe('validatePrivateKey()', () => { + for (const vector of secp256k1ValidatePrivateKey.vectors) { + it(vector.description, async () => { + const key = Convert.hex(vector.input.key).toUint8Array(); + // @ts-expect-error because validatePrivateKey() is a private method. + const isValid = await Secp256k1.validatePrivateKey({ key }); + expect(isValid).to.equal(vector.output); + }); + } + }); + + describe('validatePublicKey()', () => { + for (const vector of secp256k1ValidatePublicKey.vectors) { + it(vector.description, async () => { + const key = Convert.hex(vector.input.key).toUint8Array(); + // @ts-expect-error because validatePublicKey() is a private method. + const isValid = await Secp256k1.validatePublicKey({ key }); + expect(isValid).to.equal(vector.output); + }); + } + }); + + describe('verify()', () => { + it('returns a boolean result', async () => { + const data = new Uint8Array([51, 52, 53]); + const signature = await Secp256k1.sign({ key: privateKey, data }); + + const isValid = await Secp256k1.verify({ key: publicKey, signature, data }); + expect(isValid).to.exist; + expect(isValid).to.be.true; + }); + + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + let isValid: boolean; + let signature: Uint8Array; + + // TypedArray - Uint8Array + signature = await Secp256k1.sign({ key: privateKey, data }); + isValid = await Secp256k1.verify({ key: publicKey, signature, data }); + expect(isValid).to.be.true; + }); + + it('returns false if the signed data was mutated', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + let isValid: boolean; + + // Generate signature using the private key. + const signature = await Secp256k1.sign({ key: privateKey, data }); + + // Verification should return true with the data used to generate the signature. + isValid = await Secp256k1.verify({ key: publicKey, signature, data }); + expect(isValid).to.be.true; + + // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. + const mutatedData = new Uint8Array(data); + mutatedData[0] ^= 1 << 0; + + // Verification should return false if the given data does not match the data used to generate the signature. + isValid = await Secp256k1.verify({ key: publicKey, signature, data: mutatedData }); + expect(isValid).to.be.false; + }); + + it('returns false if the signature was mutated', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + let isValid: boolean; + + // Generate signature using the private key. + const signature = await Secp256k1.sign({ key: privateKey, data }); + + // Verification should return true with the data used to generate the signature. + isValid = await Secp256k1.verify({ key: publicKey, signature, data }); + expect(isValid).to.be.true; + + // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. + const mutatedSignature = new Uint8Array(signature); + mutatedSignature[0] ^= 1 << 0; + + // Verification should return false if the signature was modified. + isValid = await Secp256k1.verify({ key: publicKey, signature: signature, data: mutatedSignature }); + expect(isValid).to.be.false; + }); + + it('returns false with a signature generated using a different private key', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const publicKeyA = publicKey; + const privateKeyB = await Secp256k1.generateKey(); + let isValid: boolean; + + // Generate a signature using private key B. + const signatureB = await Secp256k1.sign({ key: privateKeyB, data }); + + // Verification should return false with public key A. + isValid = await Secp256k1.verify({ key: publicKeyA, signature: signatureB, data }); + expect(isValid).to.be.false; + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/x25519.spec.ts b/packages/crypto/tests/crypto-primitives/x25519.spec.ts new file mode 100644 index 000000000..8a85d0d8b --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/x25519.spec.ts @@ -0,0 +1,241 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { JwkParamsOkpPrivate, PrivateKeyJwk, PublicKeyJwk } from '../../src/jose.js'; + +import x25519BytesToPublicKey from '../fixtures/test-vectors/x25519/bytes-to-public-key.json' assert { type: 'json' }; +import x25519BytesToPrivateKey from '../fixtures/test-vectors/x25519/bytes-to-private-key.json' assert { type: 'json' }; +import x25519PrivateKeyToBytes from '../fixtures/test-vectors/x25519/private-key-to-bytes.json' assert { type: 'json' }; +import x25519PublicKeyToBytes from '../fixtures/test-vectors/x25519/public-key-to-bytes.json' assert { type: 'json' }; + +import { X25519 } from '../../src/crypto-primitives/x25519.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('X25519', () => { + let privateKey: PrivateKeyJwk; + let publicKey: PublicKeyJwk; + + before(async () => { + privateKey = await X25519.generateKey(); + publicKey = await X25519.computePublicKey({ privateKey }); + }); + + describe('bytesToPrivateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKeyBytes = Convert.hex('c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475').toUint8Array(); + const privateKey = await X25519.bytesToPrivateKey({ privateKeyBytes }); + + expect(privateKey).to.have.property('crv', 'X25519'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('x'); + }); + + for (const vector of x25519BytesToPrivateKey.vectors) { + it(vector.description, async () => { + const privateKey = await X25519.bytesToPrivateKey({ + privateKeyBytes: Convert.hex(vector.input.privateKeyBytes).toUint8Array() + }); + + expect(privateKey).to.deep.equal(vector.output); + }); + } + }); + + describe('bytesToPublicKey()', () => { + it('returns a public key in JWK format', async () => { + const publicKeyBytes = Convert.hex('504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829').toUint8Array(); + const publicKey = await X25519.bytesToPublicKey({ publicKeyBytes }); + + expect(publicKey).to.have.property('crv', 'X25519'); + expect(publicKey).to.have.property('kid'); + expect(publicKey).to.have.property('kty', 'OKP'); + expect(publicKey).to.have.property('x'); + expect(publicKey).to.not.have.property('d'); + }); + + for (const vector of x25519BytesToPublicKey.vectors) { + it(vector.description, async () => { + const publicKey = await X25519.bytesToPublicKey({ + publicKeyBytes: Convert.hex(vector.input.publicKeyBytes).toUint8Array() + }); + + expect(publicKey).to.deep.equal(vector.output); + }); + } + }); + + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await X25519.generateKey(); + + expect(privateKey).to.have.property('crv', 'X25519'); + expect(privateKey).to.have.property('d'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'OKP'); + expect(privateKey).to.have.property('x'); + }); + + it('returns a 32-byte private key', async () => { + const privateKey = await X25519.generateKey() as JwkParamsOkpPrivate; + + const privateKeyBytes = Convert.base64Url(privateKey.d).toUint8Array(); + expect(privateKeyBytes.byteLength).to.equal(32); + }); + }); + + describe('computePublicKey()', () => { + it('returns a public key in JWK format', async () => { + const publicKey = await X25519.computePublicKey({ privateKey }); + + expect(publicKey).to.have.property('kty', 'OKP'); + expect(publicKey).to.have.property('crv', 'X25519'); + expect(publicKey).to.have.property('x'); + expect(publicKey).to.not.have.property('d'); + }); + }); + + describe('privateKeyToBytes()', () => { + it('returns a private key as a byte array', async () => { + const privateKey: PrivateKeyJwk = { + kty : 'OKP', + crv : 'X25519', + d : 'jxSSX_aM49m6E4MaSd-hcizIM33rXzLltuev9oBw1V8', + x : 'U2kX2FckTAoTAjMBUadwOpftdXk-Kx8pZMeyG3QZsy8', + kid : 'PPgSyqA-j9sc9vmsvpSCpy2uLg_CUfGoKHhPzQ5Gkog' + }; + const privateKeyBytes = await X25519.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('8f14925ff68ce3d9ba13831a49dfa1722cc8337deb5f32e5b6e7aff68070d55f').toUint8Array(); + expect(privateKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an X25519 public key', async () => { + const publicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'X25519', + x : 'U2kX2FckTAoTAjMBUadwOpftdXk-Kx8pZMeyG3QZsy8', + kid : 'PPgSyqA-j9sc9vmsvpSCpy2uLg_CUfGoKHhPzQ5Gkog' + }; + + await expect( + // @ts-expect-error because a public key is being passed to a method that expects a private key. + X25519.privateKeyToBytes({ privateKey: publicKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid OKP private key'); + }); + + for (const vector of x25519PrivateKeyToBytes.vectors) { + it(vector.description, async () => { + const privateKeyBytes = await X25519.privateKeyToBytes({ + privateKey: vector.input.privateKey as PrivateKeyJwk + }); + expect(privateKeyBytes).to.deep.equal(Convert.hex(vector.output).toUint8Array()); + }); + } + }); + + describe('publicKeyToBytes()', () => { + it('returns a public key in JWK format', async () => { + const publicKey: PublicKeyJwk = { + kty : 'OKP', + crv : 'X25519', + x : 'U2kX2FckTAoTAjMBUadwOpftdXk-Kx8pZMeyG3QZsy8', + kid : 'PPgSyqA-j9sc9vmsvpSCpy2uLg_CUfGoKHhPzQ5Gkog' + }; + + const publicKeyBytes = await X25519.publicKeyToBytes({ publicKey }); + + expect(publicKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('536917d857244c0a1302330151a7703a97ed75793e2b1f2964c7b21b7419b32f').toUint8Array(); + expect(publicKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an X25519 private key', async () => { + const privateKey: PrivateKeyJwk = { + kty : 'OKP', + crv : 'X25519', + d : 'jxSSX_aM49m6E4MaSd-hcizIM33rXzLltuev9oBw1V8', + x : 'U2kX2FckTAoTAjMBUadwOpftdXk-Kx8pZMeyG3QZsy8', + kid : 'PPgSyqA-j9sc9vmsvpSCpy2uLg_CUfGoKHhPzQ5Gkog' + }; + + await expect( + X25519.publicKeyToBytes({ publicKey: privateKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid OKP public key'); + }); + + for (const vector of x25519PublicKeyToBytes.vectors) { + it(vector.description, async () => { + const publicKeyBytes = await X25519.publicKeyToBytes({ + publicKey: vector.input.publicKey as PublicKeyJwk + }); + expect(publicKeyBytes).to.deep.equal(Convert.hex(vector.output).toUint8Array()); + }); + } + }); + + describe('sharedSecret()', () => { + let ownPrivateKey: PrivateKeyJwk; + let ownPublicKey: PublicKeyJwk; + let otherPartyPrivateKey: PrivateKeyJwk; + let otherPartyPublicKey: PublicKeyJwk; + + before(async () => { + ownPrivateKey = privateKey; + ownPublicKey = publicKey; + otherPartyPrivateKey = await X25519.generateKey(); + otherPartyPublicKey = await X25519.computePublicKey({ privateKey: otherPartyPrivateKey }); + }); + + it('generates a 32-byte compressed secret', async () => { + const sharedSecret = await X25519.sharedSecret({ + privateKeyA : ownPrivateKey, + publicKeyB : otherPartyPublicKey + }); + expect(sharedSecret).to.be.instanceOf(Uint8Array); + expect(sharedSecret.byteLength).to.equal(32); + }); + + it('is commutative', async () => { + const sharedSecretOwnOther = await X25519.sharedSecret({ + privateKeyA : ownPrivateKey, + publicKeyB : otherPartyPublicKey + }); + + const sharedSecretOtherOwn = await X25519.sharedSecret({ + privateKeyA : otherPartyPrivateKey, + publicKeyB : ownPublicKey + }); + + expect(sharedSecretOwnOther).to.deep.equal(sharedSecretOtherOwn); + }); + + it('throws an error if the public/private keys from the same key pair are specified', async () => { + await expect( + X25519.sharedSecret({ + privateKeyA : ownPrivateKey, + publicKeyB : ownPublicKey + }) + ).to.eventually.be.rejectedWith(Error, 'shared secret cannot be computed from a single key pair'); + }); + }); + + describe('validatePublicKey()', () => { + it('throws a not implemented error', async () => { + await expect( + // @ts-expect-error because validatePublicKey is a private method. + X25519.validatePublicKey({ key: new Uint8Array(32) }) + ).to.eventually.be.rejectedWith(Error, 'Not implemented'); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/xchacha20-poly1305.spec.ts b/packages/crypto/tests/crypto-primitives/xchacha20-poly1305.spec.ts new file mode 100644 index 000000000..16b6d7810 --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/xchacha20-poly1305.spec.ts @@ -0,0 +1,190 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { PrivateKeyJwk, PublicKeyJwk } from '../../src/jose.js'; + +import { XChaCha20Poly1305 } from '../../src/crypto-primitives/xchacha20-poly1305.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('XChaCha20Poly1305', () => { + describe('bytesToPrivateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKeyBytes = Convert.hex('ffbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + const privateKey = await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes }); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + + it('returns the expected JWK given byte array input', async () => { + const privateKeyBytes = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + + const privateKey = await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes }); + + const expectedOutput: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + expect(privateKey).to.deep.equal(expectedOutput); + }); + }); + + describe('decrypt()', () => { + it('returns Uint8Array plaintext with length matching input', async () => { + const plaintext = await XChaCha20Poly1305.decrypt({ + data : Convert.hex('789e9689e5208d7fd9e1').toUint8Array(), + key : await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes: new Uint8Array(32) }), + nonce : new Uint8Array(24), + tag : Convert.hex('09701fb9f36ab77a0f136ca539229a34').toUint8Array() + }); + expect(plaintext).to.be.an('Uint8Array'); + expect(plaintext.byteLength).to.equal(10); + }); + + it('passes test vectors', async () => { + const privateKeyBytes = Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(); + const privateKey = await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes }); + + const input = { + data : Convert.hex('80246ca517c0fb5860c19090a7e7a2b030dde4882520102cbc64fad937916596ca9d').toUint8Array(), + key : privateKey, + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array(), + tag : Convert.hex('9e10a121d990e6a290f6b534516aa32f').toUint8Array() + }; + const output = Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(); + + const plaintext = await XChaCha20Poly1305.decrypt({ + data : input.data, + key : input.key, + nonce : input.nonce, + tag : input.tag + }); + + expect(plaintext).to.deep.equal(output); + }); + + it('throws an error if an invalid tag is given', async () => { + await expect( + XChaCha20Poly1305.decrypt({ + data : new Uint8Array(10), + key : await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes: new Uint8Array(32) }), + nonce : new Uint8Array(24), + tag : new Uint8Array(16) + }) + ).to.eventually.be.rejectedWith(Error, 'invalid tag'); + }); + }); + + describe('encrypt()', () => { + it('returns Uint8Array ciphertext and tag', async () => { + const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + data : new Uint8Array(10), + key : await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes: new Uint8Array(32) }), + nonce : new Uint8Array(24) + }); + expect(ciphertext).to.be.an('Uint8Array'); + expect(ciphertext.byteLength).to.equal(10); + expect(tag).to.be.an('Uint8Array'); + expect(tag.byteLength).to.equal(16); + }); + + it('accepts additional authenticated data', async () => { + const { ciphertext: ciphertextAad, tag: tagAad } = await XChaCha20Poly1305.encrypt({ + additionalData : new Uint8Array(64), + data : new Uint8Array(10), + key : await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes: new Uint8Array(32) }), + nonce : new Uint8Array(24) + }); + + const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + data : new Uint8Array(10), + key : await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes: new Uint8Array(32) }), + nonce : new Uint8Array(24) + }); + + expect(ciphertextAad.byteLength).to.equal(10); + expect(ciphertext.byteLength).to.equal(10); + expect(ciphertextAad).to.deep.equal(ciphertext); + expect(tagAad).to.not.deep.equal(tag); + }); + + it('passes test vectors', async () => { + const privateKeyBytes = Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(); + const privateKey = await XChaCha20Poly1305.bytesToPrivateKey({ privateKeyBytes }); + + const input = { + data : Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(), + key : privateKey, + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() + }; + const output = { + ciphertext : Convert.hex('80246ca517c0fb5860c19090a7e7a2b030dde4882520102cbc64fad937916596ca9d').toUint8Array(), + tag : Convert.hex('9e10a121d990e6a290f6b534516aa32f').toUint8Array() + }; + + const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + data : input.data, + key : input.key, + nonce : input.nonce + }); + + expect(ciphertext).to.deep.equal(output.ciphertext); + expect(tag).to.deep.equal(output.tag); + }); + }); + + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await XChaCha20Poly1305.generateKey(); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + }); + + describe('privateKeyToBytes()', () => { + it('returns a private key as a byte array', async () => { + const privateKey = await XChaCha20Poly1305.generateKey(); + const privateKeyBytes = await XChaCha20Poly1305.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + }); + + it('returns the expected byte array for JWK input', async () => { + const privateKey: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + const privateKeyBytes = await XChaCha20Poly1305.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + expect(privateKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an asymmetric public key', async () => { + const publicKey: PublicKeyJwk = { + crv : 'Ed25519', + kty : 'OKP', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + }; + + await expect( + // @ts-expect-error because a public key is being passed to a method that expects a private key. + XChaCha20Poly1305.privateKeyToBytes({ privateKey: publicKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid oct private key'); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/crypto-primitives/xchacha20.spec.ts b/packages/crypto/tests/crypto-primitives/xchacha20.spec.ts new file mode 100644 index 000000000..c9cc85253 --- /dev/null +++ b/packages/crypto/tests/crypto-primitives/xchacha20.spec.ts @@ -0,0 +1,153 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { PrivateKeyJwk, PublicKeyJwk } from '../../src/jose.js'; + +import { XChaCha20 } from '../../src/crypto-primitives/xchacha20.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('XChaCha20', () => { + describe('bytesToPrivateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKeyBytes = Convert.hex('ffbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + const privateKey = await XChaCha20.bytesToPrivateKey({ privateKeyBytes }); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + + it('returns the expected JWK given byte array input', async () => { + const privateKeyBytes = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + const privateKey = await XChaCha20.bytesToPrivateKey({ privateKeyBytes }); + + const expectedOutput: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + expect(privateKey).to.deep.equal(expectedOutput); + }); + }); + + describe('decrypt()', () => { + it('returns Uint8Array plaintext with length matching input', async () => { + const privateKey = await XChaCha20.generateKey(); + + const plaintext = await XChaCha20.decrypt({ + data : new Uint8Array(10), + key : privateKey, + nonce : new Uint8Array(24) + }); + expect(plaintext).to.be.an('Uint8Array'); + expect(plaintext.byteLength).to.equal(10); + }); + + it('passes test vectors', async () => { + const privateKeyBytes = Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(); + const privateKey = await XChaCha20.bytesToPrivateKey({ privateKeyBytes }); + + const input = { + data : Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toUint8Array(), + key : privateKey, + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() + }; + const output = Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(); + + const ciphertext = await XChaCha20.decrypt({ + data : input.data, + key : input.key, + nonce : input.nonce + }); + + expect(ciphertext).to.deep.equal(output); + }); + }); + + describe('encrypt()', () => { + it('returns Uint8Array ciphertext with length matching input', async () => { + const privateKey = await XChaCha20.generateKey(); + + const ciphertext = await XChaCha20.encrypt({ + data : new Uint8Array(10), + key : privateKey, + nonce : new Uint8Array(24) + }); + expect(ciphertext).to.be.an('Uint8Array'); + expect(ciphertext.byteLength).to.equal(10); + }); + + it('passes test vectors', async () => { + const privateKeyBytes = Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(); + const privateKey = await XChaCha20.bytesToPrivateKey({ privateKeyBytes }); + + const input = { + data : Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(), + key : privateKey, + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() + }; + const output = Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toUint8Array(); + + const ciphertext = await XChaCha20.encrypt({ + data : input.data, + key : input.key, + nonce : input.nonce + }); + + expect(ciphertext).to.deep.equal(output); + }); + }); + + describe('generateKey()', () => { + it('returns a private key in JWK format', async () => { + const privateKey = await XChaCha20.generateKey(); + + expect(privateKey).to.have.property('k'); + expect(privateKey).to.have.property('kid'); + expect(privateKey).to.have.property('kty', 'oct'); + }); + }); + + describe('privateKeyToBytes()', () => { + it('returns a private key as a byte array', async () => { + const privateKey = await XChaCha20.generateKey(); + const privateKeyBytes = await XChaCha20.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + }); + + it('returns the expected byte array for JWK input', async () => { + const privateKey: PrivateKeyJwk = { + k : 'L71Sr1mAvThwzcPzY0mArp0VszRA9j95eZ64yiMpEX8', + kty : 'oct', + kid : '6oEQ2tFk2QI4_Lz8uxQpT4_Qce6f9ceS3ZD76nqd_qg' + }; + const privateKeyBytes = await XChaCha20.privateKeyToBytes({ privateKey }); + + expect(privateKeyBytes).to.be.an.instanceOf(Uint8Array); + const expectedOutput = Convert.hex('2fbd52af5980bd3870cdc3f3634980ae9d15b33440f63f79799eb8ca2329117f').toUint8Array(); + expect(privateKeyBytes).to.deep.equal(expectedOutput); + }); + + it('throws an error when provided an asymmetric public key', async () => { + const publicKey: PublicKeyJwk = { + crv : 'Ed25519', + kty : 'OKP', + x : 'PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw', + }; + + await expect( + // @ts-expect-error because a public key is being passed to a method that expects a private key. + XChaCha20.privateKeyToBytes({ privateKey: publicKey }) + ).to.eventually.be.rejectedWith(Error, 'provided key is not a valid oct private key'); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519.ts b/packages/crypto/tests/fixtures/test-vectors/ed25519.ts deleted file mode 100644 index 261a00e56..000000000 --- a/packages/crypto/tests/fixtures/test-vectors/ed25519.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const ed25519TestVectors = [ - { - id : '1', - privateKey : { - encoded: '4f2db3cf791aaa1a6445490117b1a110435394f4bef8e384c64dce9536053c5b' - }, - publicKey: { - encoded: 'b3cf2b4a6852f156ab1536c204ca6f2eed787bd44f4295104dcb9b6df8329386' - } - }, - { - id : '2', - privateKey : { - encoded: '9b65f2e65734d4f8b338e4a4c81457289564056890d3c539143ab292ad31ee87' - }, - publicKey: { - encoded: '20e95ce23a1b76d8538e8404f9ce22f2d5a0daaa327b07741a07f116e0e87e6e' - } - }, -]; \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-private-key.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-private-key.json new file mode 100644 index 000000000..d02b92e56 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-private-key.json @@ -0,0 +1,44 @@ +{ + "description" : "Ed25519 bytesToPrivateKey test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected private key", + "input" : { + "privateKeyBytes": "add4bb8103785baf9ac534258e8aaf65f5f1adb5ef5f3df19bb80ab989c4d64b" + }, + "output": { + "crv" : "Ed25519", + "d" : "rdS7gQN4W6-axTQljoqvZfXxrbXvXz3xm7gKuYnE1ks", + "kid" : "whzXN9WPd2qZyKXCZmDMlU5TGQjzRHZa496Dj1K0ZAs", + "kty" : "OKP", + "x" : "fU0Of2FTpptiQrUiq77mhf2kQg-INLEIw72uNp71Sfo" + } + }, + { + "description" : "converts wycheproof vector 2 to the expected private key", + "input" : { + "privateKeyBytes": "0a23a20072891237aa0864b5765139514908787878cd77135a0059881d313f00" + }, + "output": { + "crv" : "Ed25519", + "d" : "CiOiAHKJEjeqCGS1dlE5UUkIeHh4zXcTWgBZiB0xPwA", + "kid" : "e1A_70UPZow2psBhMg1fT7K5FfRtkrK-PJKgGbWB6Uk", + "kty" : "OKP", + "x" : "oSwr63cmXyqslTtQCTSdlBVaA62kFqrUUTGUgOmDykw" + } + }, + { + "description" : "converts wycheproof vector 3 to the expected private key", + "input" : { + "privateKeyBytes": "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + }, + "output": { + "crv" : "Ed25519", + "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "kid" : "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty" : "OKP", + "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-public-key.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-public-key.json new file mode 100644 index 000000000..758aa76f6 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/bytes-to-public-key.json @@ -0,0 +1,41 @@ +{ + "description" : "Ed25519 bytesToPublicKey test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected public key", + "input" : { + "publicKeyBytes": "7d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa" + }, + "output": { + "crv" : "Ed25519", + "kid" : "whzXN9WPd2qZyKXCZmDMlU5TGQjzRHZa496Dj1K0ZAs", + "kty" : "OKP", + "x" : "fU0Of2FTpptiQrUiq77mhf2kQg-INLEIw72uNp71Sfo" + } + }, + { + "description" : "converts wycheproof vector 2 to the expected public key", + "input" : { + "publicKeyBytes": "a12c2beb77265f2aac953b5009349d94155a03ada416aad451319480e983ca4c" + }, + "output": { + "crv" : "Ed25519", + "kid" : "e1A_70UPZow2psBhMg1fT7K5FfRtkrK-PJKgGbWB6Uk", + "kty" : "OKP", + "x" : "oSwr63cmXyqslTtQCTSdlBVaA62kFqrUUTGUgOmDykw" + } + }, + { + "description" : "converts wycheproof vector 3 to the expected public key", + "input" : { + "publicKeyBytes": "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" + }, + "output": { + "crv" : "Ed25519", + "kid" : "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty" : "OKP", + "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/compute-public-key.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/compute-public-key.json new file mode 100644 index 000000000..81be96591 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/compute-public-key.json @@ -0,0 +1,73 @@ +{ + "description" : "Ed25519 computePublicKey test vectors", + "vectors" : [ + { + "description" : "computes the expected public key from the RFC8037, Appendex A.1 vector", + "input" : { + "privateKey": { + "crv": "Ed25519", + "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + }, + "output": { + "crv": "Ed25519", + "kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + }, + { + "description" : "computes the expected public key from wycheproof vector 1", + "input" : { + "privateKey": { + "crv": "Ed25519", + "d": "rdS7gQN4W6-axTQljoqvZfXxrbXvXz3xm7gKuYnE1ks", + "kty": "OKP", + "x": "fU0Of2FTpptiQrUiq77mhf2kQg-INLEIw72uNp71Sfo" + } + }, + "output": { + "crv": "Ed25519", + "kid": "whzXN9WPd2qZyKXCZmDMlU5TGQjzRHZa496Dj1K0ZAs", + "kty": "OKP", + "x": "fU0Of2FTpptiQrUiq77mhf2kQg-INLEIw72uNp71Sfo" + } + }, + { + "description" : "computes the expected public key from wycheproof vector 2", + "input" : { + "privateKey": { + "crv": "Ed25519", + "d": "CiOiAHKJEjeqCGS1dlE5UUkIeHh4zXcTWgBZiB0xPwA", + "kty": "OKP", + "x": "oSwr63cmXyqslTtQCTSdlBVaA62kFqrUUTGUgOmDykw" + } + }, + "output": { + "crv": "Ed25519", + "kid": "e1A_70UPZow2psBhMg1fT7K5FfRtkrK-PJKgGbWB6Uk", + "kty": "OKP", + "x": "oSwr63cmXyqslTtQCTSdlBVaA62kFqrUUTGUgOmDykw" + } + }, + { + "description" : "computes the expected public key from wycheproof vector 3", + "input" : { + "privateKey": { + "crv": "Ed25519", + "d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + }, + "output": { + "crv": "Ed25519", + "kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/convert-private-key-to-x25519.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/convert-private-key-to-x25519.json new file mode 100644 index 000000000..80f96df57 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/convert-private-key-to-x25519.json @@ -0,0 +1,41 @@ +{ + "description" : "Ed25519 convertPrivateKeyToX25519 test vectors", + "vectors" : [ + { + "description" : "converts Ed25519 private key to expected X25519 private key 1", + "input" : { + "privateKey": { + "kty": "OKP", + "crv": "Ed25519", + "d": "dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo", + "x": "0KTOwPi1C6HpNuxWFUVKqX37J4ZPXxdgivLLsQVI8bM" + } + }, + "output": { + "crv": "X25519", + "d": "qM1E646TMZwFcLwRAFwOAYnTT_AvbBd3NBGtGRKTyU8", + "kid": "xtsuKULPh6VN9fuJMRwj66cDfQyLaxuXHkMlmAe_v6I", + "kty": "OKP", + "x": "7XdJtNmJ9pV_O_3mxWdn6YjiHJ-HhNkdYQARzVU_mwY" + } + }, + { + "description" : "converts Ed25519 private key to expected X25519 private key 2", + "input" : { + "privateKey": { + "kty": "OKP", + "crv": "Ed25519", + "d": "aeJsLIvfKVFAqRpCzldslD-NXtKItqK0jAs2vOxbGcA", + "x": "0nQq-UcELyC07P95hpWOOChIzrWhLC5P8I-EtoyBvwI" + } + }, + "output": { + "crv": "X25519", + "d": "oJofguv460IW5L3-7FmN3MttwqHTN3vbn5kJbbqxmGw", + "kid": "2o1RtaNsesiPxOTA4nqjKi8tuFPK4goGW5geg8R8YYA", + "kty": "OKP", + "x": "2gWwpR90WzM8pF6c2u4hVkisC9Z_vibouROB-bRpVhc" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/convert-public-key-to-x25519.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/convert-public-key-to-x25519.json new file mode 100644 index 000000000..ac57a8243 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/convert-public-key-to-x25519.json @@ -0,0 +1,37 @@ +{ + "description" : "Ed25519 convertPublicKeyToX25519 test vectors", + "vectors" : [ + { + "description" : "converts Ed25519 public key to expected X25519 public key 1", + "input" : { + "publicKey": { + "kty": "OKP", + "crv": "Ed25519", + "x": "0KTOwPi1C6HpNuxWFUVKqX37J4ZPXxdgivLLsQVI8bM" + } + }, + "output": { + "crv": "X25519", + "kid": "xtsuKULPh6VN9fuJMRwj66cDfQyLaxuXHkMlmAe_v6I", + "kty": "OKP", + "x": "7XdJtNmJ9pV_O_3mxWdn6YjiHJ-HhNkdYQARzVU_mwY" + } + }, + { + "description" : "converts Ed25519 public key to expected X25519 public key 2", + "input" : { + "publicKey": { + "kty": "OKP", + "crv": "Ed25519", + "x": "kZoyeYylO0RwCTgPuX-MG0V2YcqXOBVWL5TwCsWhkzU" + } + }, + "output": { + "crv": "X25519", + "kid": "O7xmoK9ptzlG6ewnl1cQ-6DbVgzIeMhP4nHUM8oD_M8", + "kty": "OKP", + "x": "cz3yKnz6Wfc1GchUbCDR-Pp2jZeXLTehnzPTsBJ_J14" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/private-key-to-bytes.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/private-key-to-bytes.json new file mode 100644 index 000000000..d30d2c21b --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/private-key-to-bytes.json @@ -0,0 +1,44 @@ +{ + "description" : "Ed25519 privateKeyToBytes test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "Ed25519", + "d" : "rdS7gQN4W6-axTQljoqvZfXxrbXvXz3xm7gKuYnE1ks", + "kid" : "whzXN9WPd2qZyKXCZmDMlU5TGQjzRHZa496Dj1K0ZAs", + "kty" : "OKP", + "x" : "fU0Of2FTpptiQrUiq77mhf2kQg-INLEIw72uNp71Sfo" + } + }, + "output": "add4bb8103785baf9ac534258e8aaf65f5f1adb5ef5f3df19bb80ab989c4d64b" + }, + { + "description" : "converts wycheproof vector 2 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "Ed25519", + "d" : "CiOiAHKJEjeqCGS1dlE5UUkIeHh4zXcTWgBZiB0xPwA", + "kid" : "e1A_70UPZow2psBhMg1fT7K5FfRtkrK-PJKgGbWB6Uk", + "kty" : "OKP", + "x" : "oSwr63cmXyqslTtQCTSdlBVaA62kFqrUUTGUgOmDykw" + } + }, + "output": "0a23a20072891237aa0864b5765139514908787878cd77135a0059881d313f00" + }, + { + "description" : "converts wycheproof vector 3 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "Ed25519", + "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "kid" : "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty" : "OKP", + "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + }, + "output": "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/public-key-to-bytes.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/public-key-to-bytes.json new file mode 100644 index 000000000..6ddfb0fd4 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/public-key-to-bytes.json @@ -0,0 +1,41 @@ +{ + "description" : "Ed25519 publicKeyToBytes test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "Ed25519", + "kid" : "whzXN9WPd2qZyKXCZmDMlU5TGQjzRHZa496Dj1K0ZAs", + "kty" : "OKP", + "x" : "fU0Of2FTpptiQrUiq77mhf2kQg-INLEIw72uNp71Sfo" + } + }, + "output": "7d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa" + }, + { + "description" : "converts wycheproof vector 2 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "Ed25519", + "kid" : "e1A_70UPZow2psBhMg1fT7K5FfRtkrK-PJKgGbWB6Uk", + "kty" : "OKP", + "x" : "oSwr63cmXyqslTtQCTSdlBVaA62kFqrUUTGUgOmDykw" + } + }, + "output": "a12c2beb77265f2aac953b5009349d94155a03ada416aad451319480e983ca4c" + }, + { + "description" : "converts wycheproof vector 3 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "Ed25519", + "kid" : "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty" : "OKP", + "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + }, + "output": "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/sign.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/sign.json new file mode 100644 index 000000000..89af1f38f --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/sign.json @@ -0,0 +1,61 @@ +{ + "description": "Ed25519 sign test vectors", + "vectors": [ + { + "description": "generates the expected signature given the RFC8032 0x9d... key and empty message", + "input": { + "data": "", + "key": { + "crv": "Ed25519", + "d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + }, + "output": "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" + }, + { + "description": "generates the expected signature given the RFC8032 0x4c... key and 72 message", + "input": { + "data": "72", + "key": { + "crv": "Ed25519", + "d": "TM0Imyj_ltqdtsNG7BFOD1uKMZ81q6Yk2oz27U-4pvs", + "kid": "FtIu-VbGrfe_KB6CH7GNwODB72MNxj_ml11dEvO-7kk", + "kty": "OKP", + "x": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw" + } + }, + "output": "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00" + }, + { + "description": "generates the expected signature given the RFC8032 0x00... key and 5a... message", + "input": { + "data": "5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b", + "key": { + "crv": "Ed25519", + "d": "AC_dH3ZBeTqwZLt6qEj3YufsbjMv_CburNoUGuM7F4M", + "kid": "M7TyrCUM12xZUUArpFOvdxvSN0CKasiRsxOIlVcyEaA", + "kty": "OKP", + "x": "d9HY66zRP04vikDijEpjvJzjv7aXFjNLyyijPrE0CGw" + } + }, + "output": "0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c" + }, + { + "description": "generates the expected signature given the RFC8032 0xf5... key and long message", + "input": { + "data": "08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0", + "key": { + "crv": "Ed25519", + "d": "9eV2fPFTMZUXYw8iaHa4bIFgzFg7wBN0TGvyVfXMDuU", + "kty": "OKP", + "x": "J4EX_BRMcjQPZ9DyMW6Dhs7_vyskKMnFH-98WX8dQm4", + "kid": "lZI1vM7tnlYapaF5-cy86ptx0tT_8Av721hhiNB5ti4" + } + }, + "output": "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03" + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/verify.json b/packages/crypto/tests/fixtures/test-vectors/ed25519/verify.json new file mode 100644 index 000000000..cb23c7532 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519/verify.json @@ -0,0 +1,61 @@ +{ + "description" : "Ed25519 verify test vectors", + "vectors" : [ + { + "description" : "verifies the signature for the RFC8032 0x9d... key and empty message", + "input" : { + "data": "", + "key": { + "crv": "Ed25519", + "kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + }, + "signature": "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" + }, + "output": true + }, + { + "description" : "verifies the signature for the RFC8032 0x4c... key and 72 message", + "input" : { + "data": "72", + "key": { + "crv": "Ed25519", + "kid": "FtIu-VbGrfe_KB6CH7GNwODB72MNxj_ml11dEvO-7kk", + "kty": "OKP", + "x": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw" + }, + "signature": "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00" + }, + "output": true + }, + { + "description" : "verifies the signature for the RFC8032 0x00... key and 5a... message", + "input" : { + "data": "5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b", + "key": { + "crv": "Ed25519", + "kid": "M7TyrCUM12xZUUArpFOvdxvSN0CKasiRsxOIlVcyEaA", + "kty": "OKP", + "x": "d9HY66zRP04vikDijEpjvJzjv7aXFjNLyyijPrE0CGw" + }, + "signature": "0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c" + }, + "output": true + }, + { + "description" : "verifies the signature for the RFC8032 0xf5... key and long message", + "input" : { + "data": "08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0", + "key": { + "crv": "Ed25519", + "kty": "OKP", + "x": "J4EX_BRMcjQPZ9DyMW6Dhs7_vyskKMnFH-98WX8dQm4", + "kid": "lZI1vM7tnlYapaF5-cy86ptx0tT_8Av721hhiNB5ti4" + }, + "signature": "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03" + }, + "output": true + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/jose.ts b/packages/crypto/tests/fixtures/test-vectors/jose.ts index 7aeeca461..c5b0de666 100644 --- a/packages/crypto/tests/fixtures/test-vectors/jose.ts +++ b/packages/crypto/tests/fixtures/test-vectors/jose.ts @@ -1,317 +1,3 @@ -export const cryptoKeyPairToJsonWebKeyTestVectors = [ - { - id : 'ckp.jwk.1', - cryptoKey : { - publicKey: { - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : true, - material : '02c6cf53ccfc13fbdfb25d827636839d9874df3148eba88c07f07601645ca5a006', // Hex, compressed - type : 'public', - usages : ['verify'], - }, - privateKey: { - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : true, - material : '1d70915381c9bcb940752c3892b6c3b4476a6906b6aee839227f3f38eaf91190', // Hex - type : 'private', - usages : ['sign'], - } - }, - jsonWebKey: { - publicKeyJwk: { - 'alg' : 'ES256K', - 'crv' : 'secp256k1', - 'ext' : 'true', - 'key_ops' : ['verify'], - 'kty' : 'EC', - 'x' : 'xs9TzPwT-9-yXYJ2NoOdmHTfMUjrqIwH8HYBZFyloAY', // Base64url - 'y' : 'tMa4vfJC9rR8S87Sx9yEHACYOWOh7_UWLiFal56lObY', // Base64url - }, - privateKeyJwk: { - 'alg' : 'ES256K', - 'crv' : 'secp256k1', - 'd' : 'HXCRU4HJvLlAdSw4krbDtEdqaQa2rug5In8_OOr5EZA', // Base64url - 'ext' : 'true', - 'key_ops' : ['sign'], - 'kty' : 'EC', - 'x' : 'xs9TzPwT-9-yXYJ2NoOdmHTfMUjrqIwH8HYBZFyloAY', // Base64url - 'y' : 'tMa4vfJC9rR8S87Sx9yEHACYOWOh7_UWLiFal56lObY', // Base64url - }, - } - }, - { - id : 'ckp.jwk.2', - cryptoKey : { - publicKey: { - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : true, - material : '045d67b538b1f3dc38326a975b17c4312b7620c39b656b3012dc9205c5804870c7ab53846c0b4c6f6c0267f08b9ac7075fe1f0b617d013630d92a3c760908b71e3', // Hex, uncompressed - type : 'public', - usages : ['verify'], - }, - privateKey: { - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - extractable : true, - material : 'c1f488e4919027f1da827a3f25c8121f9092f5d940c0da9a52cb36e192fa1610', // Hex - type : 'private', - usages : ['sign'], - } - }, - jsonWebKey: { - publicKeyJwk: { - 'alg' : 'ES256K', - 'crv' : 'secp256k1', - 'ext' : 'true', - 'key_ops' : ['verify'], - 'kty' : 'EC', - 'x' : 'XWe1OLHz3DgyapdbF8QxK3Ygw5tlazAS3JIFxYBIcMc', // Base64url - 'y' : 'q1OEbAtMb2wCZ_CLmscHX-HwthfQE2MNkqPHYJCLceM', // Base64url - }, - privateKeyJwk: { - 'alg' : 'ES256K', - 'crv' : 'secp256k1', - 'd' : 'wfSI5JGQJ_Hagno_JcgSH5CS9dlAwNqaUss24ZL6FhA', // Base64url - 'ext' : 'true', - 'key_ops' : ['sign'], - 'kty' : 'EC', - 'x' : 'XWe1OLHz3DgyapdbF8QxK3Ygw5tlazAS3JIFxYBIcMc', // Base64url - 'y' : 'q1OEbAtMb2wCZ_CLmscHX-HwthfQE2MNkqPHYJCLceM', // Base64url - }, - } - }, - { - id : 'ckp.jwk.3', - cryptoKey : { - publicKey: { - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : true, - material : 'ae92a70cff05e3f8f0bd0ef10e492e2b1d7ae4e4b0732ad0be61169767a28085', // Hex - type : 'public', - usages : ['verify'], - }, - privateKey: { - algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, - extractable : true, - material : 'f69e3da1db3fc8b7474224e3271099dab537807212477ad034ae52f3e39d8782', // Hex - type : 'private', - usages : ['sign'], - } - }, - jsonWebKey: { - publicKeyJwk: { - 'alg' : 'EdDSA', - 'crv' : 'Ed25519', - 'ext' : 'true', - 'key_ops' : ['verify'], - 'kty' : 'OKP', - 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU', // Base64url - }, - privateKeyJwk: { - 'alg' : 'EdDSA', - 'crv' : 'Ed25519', - 'd' : '9p49ods_yLdHQiTjJxCZ2rU3gHISR3rQNK5S8-Odh4I', // Base64url - 'ext' : 'true', - 'key_ops' : ['sign'], - 'kty' : 'OKP', - 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU', // Base64url - }, - } - }, - { - id : 'ckp.jwk.4', - cryptoKey : { - publicKey: { - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : true, - material : '796037a1434a9b79d9374bea882fed0a53c2901ce737947463d3687c99286973', // Hex - type : 'public', - usages : ['deriveBits', 'deriveKey'], - }, - privateKey: { - algorithm : { name: 'ECDH', namedCurve: 'X25519' }, - extractable : true, - material : '20a6d2ab343efc5d8718af1afb3157984b63712edc5f5c1c77bcf8f732f8b545', // Hex - type : 'private', - usages : ['deriveBits', 'deriveKey'], - } - }, - jsonWebKey: { - publicKeyJwk: { - 'crv' : 'X25519', - 'ext' : 'true', - 'key_ops' : ['deriveBits', 'deriveKey'], - 'kty' : 'OKP', - 'x' : 'eWA3oUNKm3nZN0vqiC_tClPCkBznN5R0Y9NofJkoaXM', // Base64url - }, - privateKeyJwk: { - 'crv' : 'X25519', - 'd' : 'IKbSqzQ-_F2HGK8a-zFXmEtjcS7cX1wcd7z49zL4tUU', // Base64url - 'ext' : 'true', - 'key_ops' : ['deriveBits', 'deriveKey'], - 'kty' : 'OKP', - 'x' : 'eWA3oUNKm3nZN0vqiC_tClPCkBznN5R0Y9NofJkoaXM', // Base64url - }, - } - }, -]; - -export const cryptoKeyToJwkTestVectors = [ - { - id : 'csk.jwk.1', - cryptoKey : { - algorithm : { name: 'AES-CTR', length: 256 }, - extractable : true, - material : '510b48012fab99607ebe03601b894fae74d2dad36fc033ca97daecd0bf480a75', // Hex - type : 'secret', - usages : ['encrypt', 'decrypt'], - }, - jsonWebKey: { - 'alg' : 'A256CTR', - 'ext' : 'true', - 'key_ops' : ['encrypt', 'decrypt'], - 'k' : 'UQtIAS-rmWB-vgNgG4lPrnTS2tNvwDPKl9rs0L9ICnU', // Base64url - 'kty' : 'oct', - } - }, - { - id : 'csk.jwk.2', - cryptoKey : { - algorithm : { name: 'AES-GCM', length: 256 }, - extractable : true, - material : 'fa919d00b0edc66c73efcc2325073fff8173bd30956174cd50b3381f438a56ac', // Hex - type : 'secret', - usages : ['encrypt', 'decrypt'], - }, - jsonWebKey: { - 'alg' : 'A256GCM', - 'ext' : 'true', - 'key_ops' : ['encrypt', 'decrypt'], - 'k' : '-pGdALDtxmxz78wjJQc__4FzvTCVYXTNULM4H0OKVqw', // Base64url - 'kty' : 'oct', - } - }, - { - id : 'csk.jwk.3', - cryptoKey : { - algorithm : { name: 'HMAC', hash: { name: 'SHA-256' } }, - extractable : true, - material : 'dc739a7be3ffc152af69bc45dfb02d81cfe313c7cb074c643144a9c15588d87468bafa02da20ab7fc8f7498916b184459b84aff27736be9cc8f60e49ca0d01c7', // Hex - type : 'secret', - usages : ['sign', 'verify'], - }, - jsonWebKey: { - 'alg' : 'HS256', - 'ext' : 'true', - 'key_ops' : ['sign', 'verify'], - 'k' : '3HOae-P_wVKvabxF37Atgc_jE8fLB0xkMUSpwVWI2HRouvoC2iCrf8j3SYkWsYRFm4Sv8nc2vpzI9g5Jyg0Bxw', // Base64url - 'kty' : 'oct', - } - }, -]; - -export const joseToWebCryptoTestVectors = [ - { - id : 'jose.wc.1', - jose : { crv: 'Ed25519', alg: 'EdDSA', kty: 'OKP' }, - webCrypto : { namedCurve: 'Ed25519', name: 'EdDSA' } - }, - { - id : 'jose.wc.2', - jose : { crv: 'Ed448', alg: 'EdDSA', kty: 'OKP' }, - webCrypto : { namedCurve: 'Ed448', name: 'EdDSA' } - }, - { - id : 'jose.wc.3', - jose : { crv: 'X25519', kty: 'OKP' }, - webCrypto : { namedCurve: 'X25519', name: 'ECDH' } - }, - { - id : 'jose.wc.4', - jose : { crv: 'secp256k1', alg: 'ES256K', kty: 'EC' }, - webCrypto : { namedCurve: 'secp256k1', name: 'ECDSA' } - }, - { - id : 'jose.wc.5', - jose : { crv: 'secp256k1', kty: 'EC' }, - webCrypto : { namedCurve: 'secp256k1', name: 'ECDH' } - }, - { - id : 'jose.wc.6', - jose : { crv: 'P-256', alg: 'ES256', kty: 'EC' }, - webCrypto : { namedCurve: 'P-256', name: 'ECDSA' } - }, - { - id : 'jose.wc.7', - jose : { crv: 'P-384', alg: 'ES384', kty: 'EC' }, - webCrypto : { namedCurve: 'P-384', name: 'ECDSA' } - }, - { - id : 'jose.wc.8', - jose : { crv: 'P-521', alg: 'ES512', kty: 'EC' }, - webCrypto : { namedCurve: 'P-521', name: 'ECDSA' } - }, - { - id : 'jose.wc.9', - jose : { alg: 'A128CBC', kty: 'oct' }, - webCrypto : { name: 'AES-CBC', length: 128 } - }, - { - id : 'jose.wc.10', - jose : { alg: 'A192CBC', kty: 'oct' }, - webCrypto : { name: 'AES-CBC', length: 192 } - }, - { - id : 'jose.wc.11', - jose : { alg: 'A256CBC', kty: 'oct' }, - webCrypto : { name: 'AES-CBC', length: 256 } - }, - { - id : 'jose.wc.12', - jose : { alg: 'A128CTR', kty: 'oct' }, - webCrypto : { name: 'AES-CTR', length: 128 } - }, - { - id : 'jose.wc.13', - jose : { alg: 'A192CTR', kty: 'oct' }, - webCrypto : { name: 'AES-CTR', length: 192 } - }, - { - id : 'jose.wc.14', - jose : { alg: 'A256CTR', kty: 'oct' }, - webCrypto : { name: 'AES-CTR', length: 256 } - }, - { - id : 'jose.wc.15', - jose : { alg: 'A128GCM', kty: 'oct' }, - webCrypto : { name: 'AES-GCM', length: 128 } - }, - { - id : 'jose.wc.16', - jose : { alg: 'A192GCM', kty: 'oct' }, - webCrypto : { name: 'AES-GCM', length: 192 } - }, - { - id : 'jose.wc.17', - jose : { alg: 'A256GCM', kty: 'oct' }, - webCrypto : { name: 'AES-GCM', length: 256 } - }, - { - id : 'jose.wc.18', - jose : { alg: 'HS256', kty: 'oct' }, - webCrypto : { name: 'HMAC', hash: { name: 'SHA-256' } } - }, - { - id : 'jose.wc.19', - jose : { alg: 'HS384', kty: 'oct' }, - webCrypto : { name: 'HMAC', hash: { name: 'SHA-384' } } - }, - { - id : 'jose.wc.20', - jose : { alg: 'HS512', kty: 'oct' }, - webCrypto : { name: 'HMAC', hash: { name: 'SHA-512' } } - }, -]; - export const joseToMulticodecTestVectors = [ { output : { code: 237, name: 'ed25519-pub' }, @@ -372,258 +58,6 @@ export const joseToMulticodecTestVectors = [ }, ]; -export const keyToJwkTestVectorsKeyMaterial = '72e63e7c4bbf575b386fc1db1b3cbff5539a36dc6250fccb9fa28e013773d24b'; -export const keyToJwkMulticodecTestVectors = [ - { - input : 'ed25519-pub', - output : { - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - input : 'ed25519-priv', - output : { - d : '', - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - x : 'c5UR1q2r1lOT_ygDhSkU3paf5Bmukg-jX-1t4kIKJvA' - } - }, - { - input : 'secp256k1-pub', - output : { - alg : 'ES256K', - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' - } - }, - { - input : 'secp256k1-priv', - output : { - d : '', - alg : 'ES256K', - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' - } - }, - { - input : 'x25519-pub', - output : { - crv : 'X25519', - kty : 'OKP', - x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - input : 'x25519-priv', - output : { - d : '', - crv : 'X25519', - kty : 'OKP', - x : 'MBZd77wAy5932AEP7MHXOevv_MLzzD9OP_fZAOlnIWM' - } - } -]; - -export const keyToJwkWebCryptoTestVectors = [ - { - input : { namedCurve: 'Ed25519', name: 'EdDSA' }, - output : { - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - input : { namedCurve: 'secp256k1', name: 'ECDSA' }, - output : { - alg : 'ES256K', - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA', - } - }, - { - input : { namedCurve: 'X25519', name: 'ECDH' }, - output : { - crv : 'X25519', - kty : 'OKP', - x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - input : { namedCurve: 'secp256k1', name: 'ECDSA' }, - output : { - alg : 'ES256K', - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' - } - }, - { - input : { namedCurve: 'secp256k1', name: 'ECDH' }, - output : { - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' - } - }, - { - input : { name: 'AES-CBC', length: 128 }, - output : { - alg : 'A128CBC', - kty : 'oct', - k : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - input : { name: 'HMAC', hash: { name: 'SHA-256' } }, - output : { - alg : 'HS256', - kty : 'oct', - k : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - } -]; - -export const keyToJwkWebCryptoWithNullKTYTestVectors = [ - { - input : { namedCurve: 'Ed25519', name: 'EdDSA' }, - output : { - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - input : { namedCurve: 'secp256k1', name: 'ECDSA' }, - output : { - alg : 'ES256K', - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA', - } - }, - { - input : { namedCurve: 'X25519', name: 'ECDH' }, - output : { - crv : 'X25519', - kty : 'OKP', - x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - input : { namedCurve: 'secp256k1', name: 'ECDSA' }, - output : { - alg : 'ES256K', - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' - } - }, - { - input : { namedCurve: 'secp256k1', name: 'ECDH' }, - output : { - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' - } - }, - { - input : { name: 'AES-CBC', length: 128 }, - output : { - alg : 'A128CBC', - kty : null, - } - }, - { - input : { name: 'HMAC', hash: { name: 'SHA-256' } }, - output : { - alg : 'HS256', - kty : null, - } - } -]; - -export const jwkToKeyTestVectors = [ - { - output: { - keyMaterial : keyToJwkTestVectorsKeyMaterial, - keyType : 'public', - }, - input: { - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - output: { - keyMaterial : '04fd38a116fe6ddb88635ac45c75905e1096bae61401e5a88e6261ba98cbb5459051f88e19c92126e87d7f7f988bb83e8d320b60feaf11639217576bc2304779b0', - keyType : 'public', - }, - input: { - alg : 'ES256K', - crv : 'secp256k1', - kty : 'EC', - x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', - y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' - } - }, - { - output: { - keyMaterial : keyToJwkTestVectorsKeyMaterial, - keyType : 'private', - }, - input: { - alg : 'A128CBC', - kty : 'oct', - k : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - output: { - keyMaterial : keyToJwkTestVectorsKeyMaterial, - keyType : 'private', - }, - input: { - alg : 'HS256', - kty : 'oct', - k : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' - } - }, - { - output: { - keyMaterial : '', - keyType : 'private', - }, - input: { - d : '', - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - x : 'c5UR1q2r1lOT_ygDhSkU3paf5Bmukg-jX-1t4kIKJvA', - }, - } -]; - export const jwkToThumbprintTestVectors = [ { output : 'NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs', @@ -672,80 +106,30 @@ export const jwkToThumbprintTestVectors = [ }, ]; -export const jwkToCryptoKeyTestVectors = [ - { - cryptoKey: { - algorithm : { name: 'AES-CTR', length: 256 }, - extractable : true, - type : 'private', - usages : ['encrypt', 'decrypt'], - }, - jsonWebKey: { - 'alg' : 'A256CTR', - 'ext' : 'true', - 'key_ops' : ['encrypt', 'decrypt'], - 'k' : 'UQtIAS-rmWB-vgNgG4lPrnTS2tNvwDPKl9rs0L9ICnU', // Base64url - 'kty' : 'oct', - } - }, - { - cryptoKey: { - algorithm : { name: 'AES-GCM', length: 256 }, - extractable : false, - type : 'private', - usages : ['encrypt', 'decrypt'], - }, - jsonWebKey: { - 'alg' : 'A256GCM', - 'ext' : 'false', - 'key_ops' : ['encrypt', 'decrypt'], - 'k' : '-pGdALDtxmxz78wjJQc__4FzvTCVYXTNULM4H0OKVqw', // Base64url - 'kty' : 'oct', - } - }, - { - cryptoKey: { - algorithm : { name: 'HMAC', hash: { name: 'SHA-256' } }, - extractable : true, - type : 'private', - usages : ['sign', 'verify'], - }, - jsonWebKey: { - 'alg' : 'HS256', - 'ext' : 'true', - 'key_ops' : ['sign', 'verify'], - 'k' : '3HOae-P_wVKvabxF37Atgc_jE8fLB0xkMUSpwVWI2HRouvoC2iCrf8j3SYkWsYRFm4Sv8nc2vpzI9g5Jyg0Bxw', // Base64url - 'kty' : 'oct', - } - }, -]; - export const jwkToMultibaseIdTestVectors = [ { - output : 'zQ3sheTFzDvGpXAc9AXtwGF3MW1CusKovnwM4pSsUamqKCyLB', - input : { - alg : 'ES256K', + input: { crv : 'secp256k1', kty : 'EC', x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA', }, + output: 'zQ3sheTFzDvGpXAc9AXtwGF3MW1CusKovnwM4pSsUamqKCyLB', }, { - output : 'z6LSjQhGhqqYgrFsNFoZL9wzuKpS1xQ7YNE6fnLgSyW2hUt2', - input : { + input: { crv : 'X25519', kty : 'OKP', x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks', }, + output: 'z6LSjQhGhqqYgrFsNFoZL9wzuKpS1xQ7YNE6fnLgSyW2hUt2', }, { - output : 'zAuT', - input : { - d : '', - crv : 'X25519', + input: { + crv : 'Ed25519', kty : 'OKP', - x : 'MBZd77wAy5932AEP7MHXOevv_MLzzD9OP_fZAOlnIWM', + x : 'wwk7wOlocpOHDopgc0cZVCnl_7zFrp-JpvZe9vr5500' }, + output: 'z6MksabiHWJ5wQqJGDzxw1EiV5zi6BE6QRENTnHBcKHSqLaQ', }, ]; diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1.ts b/packages/crypto/tests/fixtures/test-vectors/secp256k1.ts deleted file mode 100644 index f6b63dfea..000000000 --- a/packages/crypto/tests/fixtures/test-vectors/secp256k1.ts +++ /dev/null @@ -1,46 +0,0 @@ -export const secp256k1TestVectors = [ - { - id : '1', - privateKey : { - encoded: '740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88' - }, - publicKey: { - encoded : '043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553', - x : '3752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258', - y : 'c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553' - } - }, - { - id : '2', - privateKey : { - encoded: 'b4713736a3562ff9b7b9e56ad2a533f241610e88217536cf2e620967daf91fd4' - }, - publicKey: { - encoded : '042dc60f3eed21d861fe7ccd353fb87e4dba6d0f453a71d4f8a1d2a17fe5486fd72a1d3bb247c3ca44e2a4d94cc616d6ce991fca220262c51d4edfcd3a1f55c9b4', - x : '2dc60f3eed21d861fe7ccd353fb87e4dba6d0f453a71d4f8a1d2a17fe5486fd7', - y : '2a1d3bb247c3ca44e2a4d94cc616d6ce991fca220262c51d4edfcd3a1f55c9b4' - }, - }, - { - id : '3', - privateKey : { - encoded: 'ea3db478f42cdbca7dbfe521167b03f40a5245370cba07142868c21d0082b391' - }, - publicKey: { - encoded : '037a549a3bdc432592ed40f2549e8668172e8bf1c3985066199472477f767b08f3', - x : '7a549a3bdc432592ed40f2549e8668172e8bf1c3985066199472477f767b08f3', - y : 'a5d03261db10f90f42af658c88f56aaf96fb1561f9c70f61ebe2c5bd2870b571' - } - }, - { - id : '4', - privateKey : { - encoded: '1b23fe831540368c37ec150febdaecc3dc47168585f3171a705f919357f9fff7' - }, - publicKey: { - encoded : '02ea7dd6427cdc1bb1b79584cab8e8109bf98e1cfef6c8dc9d8005d8e49ef1c150', - x : 'ea7dd6427cdc1bb1b79584cab8e8109bf98e1cfef6c8dc9d8005d8e49ef1c150', - y : 'e02763fa1504fa357acbb00c6711b8733c0a5938ebdaf228abd6ccbe7dbc6f80' - } - } -]; \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-private-key.json b/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-private-key.json new file mode 100644 index 000000000..1ae1a4d9a --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-private-key.json @@ -0,0 +1,61 @@ +{ + "description" : "Secp256k1 bytesToPrivateKey test vectors", + "vectors" : [ + { + "description" : "converts noble ecdsa vector 1 to the expected private key", + "input" : { + "privateKeyBytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + "output": { + "crv" : "secp256k1", + "d" : "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE", + "kid" : "2JF8vg9etJzjFwZwmkvhBLLZ0bfMVVOPivYR5lFtcec", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg" + } + }, + { + "description" : "converts noble ecdsa vector 2 to the expected private key", + "input" : { + "privateKeyBytes": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140" + }, + "output": { + "crv" : "secp256k1", + "d" : "_____________________rqu3OavSKA7v9JejNA2QUA", + "kid" : "A5kvmZN8g_rnvmmIfgTaV8S8KUnA6plB3cCmCMUZPyQ", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "t8UliNlcO5qiWwQD8e73VwLoS7dZeqvmY7gvbwTvJ3c" + } + }, + { + "description" : "converts noble ecdsa vector 3 to the expected private key", + "input" : { + "privateKeyBytes": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637" + }, + "output": { + "crv" : "secp256k1", + "d" : "AAAAAAAAAAAAAAAAAAByRhdKsekukUnG5Eb-GU0HJjc", + "kid" : "IJxfsScP6p4G8L9Yf8mYI8WIS87ulINKi0oUbS670KQ", + "kty" : "EC", + "x" : "tILZZWpOmaFc_atei0h8sHIG3xy4OvsHbG97qJCkNoE", + "y": "g-vgQCnVwDMAxAHbZapqnb1Hm05gvdGQoZzltVMgE9I" + } + }, + { + "description" : "converts private key bytes to the expected private key JWK", + "input" : { + "privateKeyBytes": "bb42227e72b0f2607c7810a814b6796da369e9dc22b85b739e0eb924b770cd51" + }, + "output": { + "crv" : "secp256k1", + "d" : "u0IifnKw8mB8eBCoFLZ5baNp6dwiuFtzng65JLdwzVE", + "kid" : "ikH9Jgh0U90gMJQ1txhlaST6VOdP_ygPJNN0JPaAwTI", + "kty" : "EC", + "x" : "9zVCTdMxpNyy3W1l0VfLdkpQyFdkXvDA0Jpx3TTn1og", + "y": "lNsXBQzhcrrGrf9XZYri_LLN1Bye4PdfTaoHbC6PIGI" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-public-key.json b/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-public-key.json new file mode 100644 index 000000000..31aa4b4a3 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1/bytes-to-public-key.json @@ -0,0 +1,57 @@ +{ + "description" : "Secp256k1 bytesToPublicKey test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected public key", + "input" : { + "publicKeyBytes": "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" + }, + "output": { + "crv" : "secp256k1", + "kid" : "2JF8vg9etJzjFwZwmkvhBLLZ0bfMVVOPivYR5lFtcec", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg" + } + }, + { + "description" : "converts wycheproof vector 2 to the expected public key", + "input" : { + "publicKeyBytes": "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798b7c52588d95c3b9aa25b0403f1eef75702e84bb7597aabe663b82f6f04ef2777" + }, + "output": { + "crv" : "secp256k1", + "kid" : "A5kvmZN8g_rnvmmIfgTaV8S8KUnA6plB3cCmCMUZPyQ", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "t8UliNlcO5qiWwQD8e73VwLoS7dZeqvmY7gvbwTvJ3c" + } + }, + { + "description" : "converts wycheproof vector 3 to the expected public key", + "input" : { + "publicKeyBytes": "04b482d9656a4e99a15cfdab5e8b487cb07206df1cb83afb076c6f7ba890a4368183ebe04029d5c03300c401db65aa6a9dbd479b4e60bdd190a19ce5b5532013d2" + }, + "output": { + "crv" : "secp256k1", + "kid" : "IJxfsScP6p4G8L9Yf8mYI8WIS87ulINKi0oUbS670KQ", + "kty" : "EC", + "x" : "tILZZWpOmaFc_atei0h8sHIG3xy4OvsHbG97qJCkNoE", + "y": "g-vgQCnVwDMAxAHbZapqnb1Hm05gvdGQoZzltVMgE9I" + } + }, + { + "description" : "converts public key bytes to the expected public key JWK", + "input" : { + "publicKeyBytes": "04f735424dd331a4dcb2dd6d65d157cb764a50c857645ef0c0d09a71dd34e7d68894db17050ce172bac6adff57658ae2fcb2cdd41c9ee0f75f4daa076c2e8f2062" + }, + "output": { + "crv" : "secp256k1", + "kid" : "ikH9Jgh0U90gMJQ1txhlaST6VOdP_ygPJNN0JPaAwTI", + "kty" : "EC", + "x" : "9zVCTdMxpNyy3W1l0VfLdkpQyFdkXvDA0Jpx3TTn1og", + "y": "lNsXBQzhcrrGrf9XZYri_LLN1Bye4PdfTaoHbC6PIGI" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1/get-curve-points.json b/packages/crypto/tests/fixtures/test-vectors/secp256k1/get-curve-points.json new file mode 100644 index 000000000..4ff58c133 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1/get-curve-points.json @@ -0,0 +1,45 @@ +{ + "description" : "Secp256k1 getCurvePoints test vectors", + "vectors" : [ + { + "description" : "returns public key x and y coordinates given a public key", + "input" : { + "key": "043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553" + }, + "output": { + "x": "3752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258", + "y": "c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553" + } + }, + { + "description" : "returns public key x and y coordinates given a private key", + "input" : { + "key": "740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88" + }, + "output": { + "x": "3752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258", + "y": "c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553" + } + }, + { + "description" : "handles private keys that require padded x-coordinate when converting from BigInt to bytes", + "input" : { + "key": "0206a1f9628c5bcd31f3bbc2f160ec98f99960147e04ea192f56c53a0086c5432d" + }, + "output": { + "x": "06a1f9628c5bcd31f3bbc2f160ec98f99960147e04ea192f56c53a0086c5432d", + "y": "bf2efab7943be51219a283c0979ccba0fbe03f571e75b0eb338cc2ec01e70552" + } + }, + { + "description" : "handles private keys that require padded y-coordinate when converting from BigInt to bytes", + "input" : { + "key": "032ff752fb8fc6af85c8682b0ca9d48901b2b9ac130f558bd1a9092240d42c4682" + }, + "output": { + "x": "2ff752fb8fc6af85c8682b0ca9d48901b2b9ac130f558bd1a9092240d42c4682", + "y": "048c39d9ebdc1fd98bda38b7f00b93de1d2af5bb3ba8cb532ad47c1f36e19501" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1/private-key-to-bytes.json b/packages/crypto/tests/fixtures/test-vectors/secp256k1/private-key-to-bytes.json new file mode 100644 index 000000000..746d764ee --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1/private-key-to-bytes.json @@ -0,0 +1,61 @@ +{ + "description" : "Secp256k1 privateKeyToBytes test vectors", + "vectors" : [ + { + "description" : "converts noble ecdsa vector 1 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "secp256k1", + "d" : "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE", + "kid" : "2JF8vg9etJzjFwZwmkvhBLLZ0bfMVVOPivYR5lFtcec", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg" + } + }, + "output": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "description" : "converts noble ecdsa vector 2 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "secp256k1", + "d" : "_____________________rqu3OavSKA7v9JejNA2QUA", + "kid" : "A5kvmZN8g_rnvmmIfgTaV8S8KUnA6plB3cCmCMUZPyQ", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "t8UliNlcO5qiWwQD8e73VwLoS7dZeqvmY7gvbwTvJ3c" + } + }, + "output": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140" + }, + { + "description" : "converts noble ecdsa vector 3 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "secp256k1", + "d" : "AAAAAAAAAAAAAAAAAAByRhdKsekukUnG5Eb-GU0HJjc", + "kid" : "IJxfsScP6p4G8L9Yf8mYI8WIS87ulINKi0oUbS670KQ", + "kty" : "EC", + "x" : "tILZZWpOmaFc_atei0h8sHIG3xy4OvsHbG97qJCkNoE", + "y": "g-vgQCnVwDMAxAHbZapqnb1Hm05gvdGQoZzltVMgE9I" + } + }, + "output": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637" + }, + { + "description" : "converts byte array bytes to the expected byte array JWK", + "input" : { + "privateKey": { + "crv" : "secp256k1", + "d" : "u0IifnKw8mB8eBCoFLZ5baNp6dwiuFtzng65JLdwzVE", + "kid" : "ikH9Jgh0U90gMJQ1txhlaST6VOdP_ygPJNN0JPaAwTI", + "kty" : "EC", + "x" : "9zVCTdMxpNyy3W1l0VfLdkpQyFdkXvDA0Jpx3TTn1og", + "y": "lNsXBQzhcrrGrf9XZYri_LLN1Bye4PdfTaoHbC6PIGI" + } + }, + "output": "bb42227e72b0f2607c7810a814b6796da369e9dc22b85b739e0eb924b770cd51" + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1/public-key-to-bytes.json b/packages/crypto/tests/fixtures/test-vectors/secp256k1/public-key-to-bytes.json new file mode 100644 index 000000000..ee3e0a5a5 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1/public-key-to-bytes.json @@ -0,0 +1,57 @@ +{ + "description" : "Secp256k1 publicKeyToBytes test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "secp256k1", + "kid" : "2JF8vg9etJzjFwZwmkvhBLLZ0bfMVVOPivYR5lFtcec", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg" + } + }, + "output": "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" + }, + { + "description" : "converts wycheproof vector 2 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "secp256k1", + "kid" : "A5kvmZN8g_rnvmmIfgTaV8S8KUnA6plB3cCmCMUZPyQ", + "kty" : "EC", + "x" : "eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g", + "y": "t8UliNlcO5qiWwQD8e73VwLoS7dZeqvmY7gvbwTvJ3c" + } + }, + "output": "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798b7c52588d95c3b9aa25b0403f1eef75702e84bb7597aabe663b82f6f04ef2777" + }, + { + "description" : "converts wycheproof vector 3 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "secp256k1", + "kid" : "IJxfsScP6p4G8L9Yf8mYI8WIS87ulINKi0oUbS670KQ", + "kty" : "EC", + "x" : "tILZZWpOmaFc_atei0h8sHIG3xy4OvsHbG97qJCkNoE", + "y": "g-vgQCnVwDMAxAHbZapqnb1Hm05gvdGQoZzltVMgE9I" + } + }, + "output": "04b482d9656a4e99a15cfdab5e8b487cb07206df1cb83afb076c6f7ba890a4368183ebe04029d5c03300c401db65aa6a9dbd479b4e60bdd190a19ce5b5532013d2" + }, + { + "description" : "converts byte array bytes to the expected byte array JWK", + "input" : { + "publicKey": { + "crv" : "secp256k1", + "kid" : "ikH9Jgh0U90gMJQ1txhlaST6VOdP_ygPJNN0JPaAwTI", + "kty" : "EC", + "x" : "9zVCTdMxpNyy3W1l0VfLdkpQyFdkXvDA0Jpx3TTn1og", + "y": "lNsXBQzhcrrGrf9XZYri_LLN1Bye4PdfTaoHbC6PIGI" + } + }, + "output": "04f735424dd331a4dcb2dd6d65d157cb764a50c857645ef0c0d09a71dd34e7d68894db17050ce172bac6adff57658ae2fcb2cdd41c9ee0f75f4daa076c2e8f2062" + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-private-key.json b/packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-private-key.json new file mode 100644 index 000000000..f704b2f0e --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-private-key.json @@ -0,0 +1,33 @@ +{ + "description" : "Secp256k1 validatePrivateKey test vectors", + "vectors" : [ + { + "description" : "returns true for valid private keys", + "input" : { + "key": "740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88" + }, + "output": true + }, + { + "description" : "returns false for invalid private keys", + "input" : { + "key": "02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" + }, + "output": false + }, + { + "description" : "returns false if an compressed public key is given", + "input" : { + "key": "026bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce214" + }, + "output": false + }, + { + "description" : "returns false if an uncompressed public key is given", + "input" : { + "key": "043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553" + }, + "output": false + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-public-key.json b/packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-public-key.json new file mode 100644 index 000000000..b031c274b --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1/validate-public-key.json @@ -0,0 +1,33 @@ +{ + "description" : "Secp256k1 validatePublicKey test vectors", + "vectors" : [ + { + "description" : "returns true for valid compressed public keys", + "input" : { + "key": "026bcdccc644b309921d3b0c266183a20786650c1634d34e8dfa1ed74cd66ce214" + }, + "output": true + }, + { + "description" : "returns true for valid uncompressed public keys", + "input" : { + "key": "043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553" + }, + "output": true + }, + { + "description" : "returns false for invalid public keys", + "input" : { + "key": "02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" + }, + "output": false + }, + { + "description" : "returns false if a private key is given", + "input" : { + "key": "740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88" + }, + "output": false + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-private-key.json b/packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-private-key.json new file mode 100644 index 000000000..637001642 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-private-key.json @@ -0,0 +1,44 @@ +{ + "description" : "X25519 bytesToPrivateKey test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected private key", + "input" : { + "privateKeyBytes": "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475" + }, + "output": { + "crv" : "X25519", + "d" : "yKnVqRCRrYUcZosHNsHJoCk2wNOtYmcIWAiAR7oFdHU", + "kid" : "tzkgHF2KJ5d1ir5OaJi9VdNtwKrvnDUPUagtedeyyNE", + "kty" : "OKP", + "x" : "X2S0HM6Kaz1qOHYwiPYVpJd9QiKIrkK0mrOlfi_Nb20" + } + }, + { + "description" : "converts wycheproof vector 2 to the expected private key", + "input" : { + "privateKeyBytes": "d85d8c061a50804ac488ad774ac716c3f5ba714b2712e048491379a500211958" + }, + "output": { + "crv" : "X25519", + "d" : "2F2MBhpQgErEiK13SscWw_W6cUsnEuBISRN5pQAhGVg", + "kid" : "0FSNmN_cVErHxsnoRtFHANkAQyklGgA7crsE-HiFPwg", + "kty" : "OKP", + "x" : "qQMfm22ifgIOV-IEKfLitbg0jEkoIQbCpbI2WnI_DSA" + } + }, + { + "description" : "converts wycheproof vector 3 to the expected private key", + "input" : { + "privateKeyBytes": "c8b45bfd32e55325d9fd648cb302848039000b390e44d521e58aab3b29a6964b" + }, + "output": { + "crv" : "X25519", + "d" : "yLRb_TLlUyXZ_WSMswKEgDkACzkORNUh5YqrOymmlks", + "kid" : "OaJSSSZ5RBIGtsE8-ah1lWjcXrk5Fk4TYpuu_AgQKJs", + "kty" : "OKP", + "x" : "_s8qHPERyRymPqNATkQbqkB7-jpudS-30g-VRmkN00M" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-public-key.json b/packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-public-key.json new file mode 100644 index 000000000..380bdb65e --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/x25519/bytes-to-public-key.json @@ -0,0 +1,41 @@ +{ + "description" : "X25519 bytesToPublicKey test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected public key", + "input" : { + "publicKeyBytes": "5f64b41cce8a6b3d6a38763088f615a4977d422288ae42b49ab3a57e2fcd6f6d" + }, + "output": { + "crv" : "X25519", + "kid" : "tzkgHF2KJ5d1ir5OaJi9VdNtwKrvnDUPUagtedeyyNE", + "kty" : "OKP", + "x" : "X2S0HM6Kaz1qOHYwiPYVpJd9QiKIrkK0mrOlfi_Nb20" + } + }, + { + "description" : "converts wycheproof vector 2 to the expected public key", + "input" : { + "publicKeyBytes": "a9031f9b6da27e020e57e20429f2e2b5b8348c49282106c2a5b2365a723f0d20" + }, + "output": { + "crv" : "X25519", + "kid" : "0FSNmN_cVErHxsnoRtFHANkAQyklGgA7crsE-HiFPwg", + "kty" : "OKP", + "x" : "qQMfm22ifgIOV-IEKfLitbg0jEkoIQbCpbI2WnI_DSA" + } + }, + { + "description" : "converts wycheproof vector 3 to the expected public key", + "input" : { + "publicKeyBytes": "fecf2a1cf111c91ca63ea3404e441baa407bfa3a6e752fb7d20f9546690dd343" + }, + "output": { + "crv" : "X25519", + "kid" : "OaJSSSZ5RBIGtsE8-ah1lWjcXrk5Fk4TYpuu_AgQKJs", + "kty" : "OKP", + "x" : "_s8qHPERyRymPqNATkQbqkB7-jpudS-30g-VRmkN00M" + } + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/x25519/private-key-to-bytes.json b/packages/crypto/tests/fixtures/test-vectors/x25519/private-key-to-bytes.json new file mode 100644 index 000000000..d24abc274 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/x25519/private-key-to-bytes.json @@ -0,0 +1,44 @@ +{ + "description" : "X25519 privateKeyToBytes test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "X25519", + "d" : "yKnVqRCRrYUcZosHNsHJoCk2wNOtYmcIWAiAR7oFdHU", + "kid" : "tzkgHF2KJ5d1ir5OaJi9VdNtwKrvnDUPUagtedeyyNE", + "kty" : "OKP", + "x" : "X2S0HM6Kaz1qOHYwiPYVpJd9QiKIrkK0mrOlfi_Nb20" + } + }, + "output": "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475" + }, + { + "description" : "converts wycheproof vector 2 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "X25519", + "d" : "2F2MBhpQgErEiK13SscWw_W6cUsnEuBISRN5pQAhGVg", + "kid" : "0FSNmN_cVErHxsnoRtFHANkAQyklGgA7crsE-HiFPwg", + "kty" : "OKP", + "x" : "qQMfm22ifgIOV-IEKfLitbg0jEkoIQbCpbI2WnI_DSA" + } + }, + "output": "d85d8c061a50804ac488ad774ac716c3f5ba714b2712e048491379a500211958" + }, + { + "description" : "converts wycheproof vector 3 to the expected byte array", + "input" : { + "privateKey": { + "crv" : "X25519", + "d" : "yLRb_TLlUyXZ_WSMswKEgDkACzkORNUh5YqrOymmlks", + "kid" : "OaJSSSZ5RBIGtsE8-ah1lWjcXrk5Fk4TYpuu_AgQKJs", + "kty" : "OKP", + "x" : "_s8qHPERyRymPqNATkQbqkB7-jpudS-30g-VRmkN00M" + } + }, + "output": "c8b45bfd32e55325d9fd648cb302848039000b390e44d521e58aab3b29a6964b" + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/x25519/public-key-to-bytes.json b/packages/crypto/tests/fixtures/test-vectors/x25519/public-key-to-bytes.json new file mode 100644 index 000000000..f6c0fb5a4 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/x25519/public-key-to-bytes.json @@ -0,0 +1,41 @@ +{ + "description" : "X25519 publicKeyToBytes test vectors", + "vectors" : [ + { + "description" : "converts wycheproof vector 1 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "X25519", + "kid" : "tzkgHF2KJ5d1ir5OaJi9VdNtwKrvnDUPUagtedeyyNE", + "kty" : "OKP", + "x" : "X2S0HM6Kaz1qOHYwiPYVpJd9QiKIrkK0mrOlfi_Nb20" + } + }, + "output": "5f64b41cce8a6b3d6a38763088f615a4977d422288ae42b49ab3a57e2fcd6f6d" + }, + { + "description" : "converts wycheproof vector 2 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "X25519", + "kid" : "0FSNmN_cVErHxsnoRtFHANkAQyklGgA7crsE-HiFPwg", + "kty" : "OKP", + "x" : "qQMfm22ifgIOV-IEKfLitbg0jEkoIQbCpbI2WnI_DSA" + } + }, + "output": "a9031f9b6da27e020e57e20429f2e2b5b8348c49282106c2a5b2365a723f0d20" + }, + { + "description" : "converts wycheproof vector 3 to the expected byte array", + "input" : { + "publicKey": { + "crv" : "X25519", + "kid" : "OaJSSSZ5RBIGtsE8-ah1lWjcXrk5Fk4TYpuu_AgQKJs", + "kty" : "OKP", + "x" : "_s8qHPERyRymPqNATkQbqkB7-jpudS-30g-VRmkN00M" + } + }, + "output": "fecf2a1cf111c91ca63ea3404e441baa407bfa3a6e752fb7d20f9546690dd343" + } + ] +} \ No newline at end of file diff --git a/packages/crypto/tests/jose.spec.ts b/packages/crypto/tests/jose.spec.ts index 46df2c703..4a608c316 100644 --- a/packages/crypto/tests/jose.spec.ts +++ b/packages/crypto/tests/jose.spec.ts @@ -1,153 +1,285 @@ import chai, { expect } from 'chai'; -import { Convert, MulticodecCode, MulticodecDefinition } from '@web5/common'; import chaiAsPromised from 'chai-as-promised'; +import { MulticodecCode, MulticodecDefinition } from '@web5/common'; -import type { JsonWebKey } from '../src/jose.js'; -import type { Web5Crypto } from '../src/types/web5-crypto.js'; +import type { JsonWebKey, PublicKeyJwk } from '../src/jose.js'; -import { CryptoKeyWithJwk, Jose } from '../src/jose.js'; +import { Jose } from '../src/jose.js'; import { - cryptoKeyToJwkTestVectors, - cryptoKeyPairToJsonWebKeyTestVectors, - joseToWebCryptoTestVectors, - keyToJwkWebCryptoTestVectors, - keyToJwkMulticodecTestVectors, - keyToJwkTestVectorsKeyMaterial, - joseToMulticodecTestVectors, jwkToThumbprintTestVectors, - jwkToCryptoKeyTestVectors, - jwkToKeyTestVectors, + joseToMulticodecTestVectors, jwkToMultibaseIdTestVectors, - keyToJwkWebCryptoWithNullKTYTestVectors, } from './fixtures/test-vectors/jose.js'; chai.use(chaiAsPromised); -describe('CryptoKeyWithJwk()', () => { - it('converts private CryptoKeys to JWK', async () => { - for (const vector of cryptoKeyPairToJsonWebKeyTestVectors) { - const privateKey = { - ...vector.cryptoKey.privateKey, - material: Convert.hex(vector.cryptoKey.privateKey.material).toUint8Array() - } as Web5Crypto.CryptoKey; - - const cryptoKey = new CryptoKeyWithJwk( - privateKey.algorithm, - privateKey.extractable, - privateKey.material, - privateKey.type, - privateKey.usages - ); - - const jsonWebKey = await cryptoKey.toJwk(); - - expect(jsonWebKey).to.deep.equal(vector.jsonWebKey.privateKeyJwk); - } - }); +describe('Jose', () => { + describe('isEcPrivateKeyJwk', () => { + it('returns true for a valid EC private key JWK', () => { + const validEcJwk = { + kty : 'EC', + crv : 'P-256', + x : 'base64url-encoded-x-value', + d : 'base64url-encoded-private-key' + }; + expect(Jose.isEcPrivateKeyJwk(validEcJwk)).to.be.true; + }); + + it('returns false for non-object inputs', () => { + expect(Jose.isEcPrivateKeyJwk(null)).to.be.false; + expect(Jose.isEcPrivateKeyJwk(undefined)).to.be.false; + expect(Jose.isEcPrivateKeyJwk(123)).to.be.false; + expect(Jose.isEcPrivateKeyJwk('string')).to.be.false; + expect(Jose.isEcPrivateKeyJwk([])).to.be.false; + }); + + it('returns false if any required property is missing', () => { + const missingKty = { crv: 'P-256', x: 'base64url-encoded-x-value', d: 'base64url-encoded-private-key' }; + const missingCrv = { kty: 'EC', x: 'base64url-encoded-x-value', d: 'base64url-encoded-private-key' }; + const missingX = { kty: 'EC', crv: 'P-256', d: 'base64url-encoded-private-key' }; + const missingD = { kty: 'EC', crv: 'P-256', x: 'base64url-encoded-x-value' }; + + expect(Jose.isEcPrivateKeyJwk(missingKty)).to.be.false; + expect(Jose.isEcPrivateKeyJwk(missingCrv)).to.be.false; + expect(Jose.isEcPrivateKeyJwk(missingX)).to.be.false; + expect(Jose.isEcPrivateKeyJwk(missingD)).to.be.false; + }); + + it('returns false if kty is not EC', () => { + const invalidKty = { kty: 'RSA', crv: 'P-256', x: 'base64url-encoded-x-value', d: 'base64url-encoded-private-key' }; + expect(Jose.isEcPrivateKeyJwk(invalidKty)).to.be.false; + }); + + it('returns false if any property is of incorrect type', () => { + const invalidDType = { kty: 'EC', crv: 'P-256', x: 'base64url-encoded-x-value', d: 123 }; + const invalidXType = { kty: 'EC', crv: 'P-256', x: 123, d: 'base64url-encoded-private-key' }; + + expect(Jose.isEcPrivateKeyJwk(invalidDType)).to.be.false; + expect(Jose.isEcPrivateKeyJwk(invalidXType)).to.be.false; + }); - it('converts public CryptoKeys to JWK', async () => { - for (const vector of cryptoKeyPairToJsonWebKeyTestVectors) { - const publicKey = { - ...vector.cryptoKey.publicKey, - material: Convert.hex(vector.cryptoKey.publicKey.material).toUint8Array() - } as Web5Crypto.CryptoKey; - - const cryptoKey = new CryptoKeyWithJwk( - publicKey.algorithm, - publicKey.extractable, - publicKey.material, - publicKey.type, - publicKey.usages - ); - - const jsonWebKey = await cryptoKey.toJwk(); - - expect(jsonWebKey).to.deep.equal(vector.jsonWebKey.publicKeyJwk); - } + it('returns true for valid EC JWK with extra properties', () => { + const validEcJwkExtra = { + kty : 'EC', + crv : 'P-256', + x : 'base64url-encoded-x-value', + d : 'base64url-encoded-private-key', + extra : 'extra-value' + }; + expect(Jose.isEcPrivateKeyJwk(validEcJwkExtra)).to.be.true; + }); }); - it('converts secret CryptoKeys to JWK', async () => { - for (const vector of cryptoKeyToJwkTestVectors) { - const secretKey = { - ...vector.cryptoKey, - material: Convert.hex(vector.cryptoKey.material).toUint8Array() - } as Web5Crypto.CryptoKey; - - const cryptoKey = new CryptoKeyWithJwk( - secretKey.algorithm, - secretKey.extractable, - secretKey.material, - secretKey.type, - secretKey.usages - ); - - const jsonWebKey = await cryptoKey.toJwk(); - - expect(jsonWebKey).to.deep.equal(vector.jsonWebKey); - } + describe('isEcPublicKeyJwk', () => { + it('returns true for a valid EC public key JWK', () => { + const validEcJwk = { + kty : 'EC', + crv : 'P-256', + x : 'base64url-encoded-x-value' + }; + expect(Jose.isEcPublicKeyJwk(validEcJwk)).to.be.true; + }); + + it('returns false for non-object inputs', () => { + expect(Jose.isEcPublicKeyJwk(null)).to.be.false; + expect(Jose.isEcPublicKeyJwk(undefined)).to.be.false; + expect(Jose.isEcPublicKeyJwk(123)).to.be.false; + expect(Jose.isEcPublicKeyJwk('string')).to.be.false; + expect(Jose.isEcPublicKeyJwk([])).to.be.false; + }); + + it('returns false if any required property is missing', () => { + const missingKty = { crv: 'P-256', x: 'base64url-encoded-x-value' }; + const missingCrv = { kty: 'EC', x: 'base64url-encoded-x-value' }; + const missingX = { kty: 'EC', crv: 'P-256' }; + + expect(Jose.isEcPublicKeyJwk(missingKty)).to.be.false; + expect(Jose.isEcPublicKeyJwk(missingCrv)).to.be.false; + expect(Jose.isEcPublicKeyJwk(missingX)).to.be.false; + }); + + it('returns false if kty is not EC', () => { + const invalidKty = { kty: 'RSA', crv: 'P-256', x: 'base64url-encoded-x-value' }; + expect(Jose.isEcPublicKeyJwk(invalidKty)).to.be.false; + }); + + it('returns false if any property is of incorrect type', () => { + const invalidXType = { kty: 'EC', crv: 'P-256', x: 123 }; + + expect(Jose.isEcPublicKeyJwk(invalidXType)).to.be.false; + }); + + it('returns false if the private key parameter \'d\' is present', () => { + const withDParam = { kty: 'EC', crv: 'P-256', x: 'base64url-encoded-x-value', d: 'base64url-encoded-d-value' }; + expect(Jose.isEcPublicKeyJwk(withDParam)).to.be.false; + }); + + it('returns true for valid EC public JWK with extra properties', () => { + const validEcJwkExtra = { + kty : 'EC', + crv : 'P-256', + x : 'base64url-encoded-x-value', + extra : 'extra-value' + }; + expect(Jose.isEcPublicKeyJwk(validEcJwkExtra)).to.be.true; + }); }); - it('converts public CryptoKeys with extractable=false', async () => { - for (const vector of cryptoKeyPairToJsonWebKeyTestVectors) { - const publicKey = { - ...vector.cryptoKey.publicKey, - material: Convert.hex(vector.cryptoKey.publicKey.material).toUint8Array() - } as Web5Crypto.CryptoKey; - - const cryptoKey = new CryptoKeyWithJwk( - publicKey.algorithm, - false, // override extractable to false - publicKey.material, - publicKey.type, - publicKey.usages - ); - - const jsonWebKey = await cryptoKey.toJwk(); - - expect(jsonWebKey).to.deep.equal({ ...vector.jsonWebKey.publicKeyJwk, ext: 'false' }); - } + describe('isOctPrivateKeyJwk()', () => { + it('returns true for a valid OCT private key JWK', () => { + const validOctJwk = { + kty : 'oct', + k : 'base64url-encoded-key' + }; + expect(Jose.isOctPrivateKeyJwk(validOctJwk)).to.be.true; + }); + + it('returns false for non-object inputs', () => { + expect(Jose.isOctPrivateKeyJwk(null)).to.be.false; + expect(Jose.isOctPrivateKeyJwk(undefined)).to.be.false; + expect(Jose.isOctPrivateKeyJwk(123)).to.be.false; + expect(Jose.isOctPrivateKeyJwk('string')).to.be.false; + expect(Jose.isOctPrivateKeyJwk([])).to.be.false; + }); + + it('returns false if any required property is missing', () => { + const missingKty = { k: 'base64url-encoded-key' }; + const missingK = { kty: 'oct' }; + + expect(Jose.isOctPrivateKeyJwk(missingKty)).to.be.false; + expect(Jose.isOctPrivateKeyJwk(missingK)).to.be.false; + }); + + it('returns false if kty is not oct', () => { + const invalidKty = { kty: 'RSA', k: 'base64url-encoded-key' }; + expect(Jose.isOctPrivateKeyJwk(invalidKty)).to.be.false; + }); + + it('returns false if any property is of incorrect type', () => { + const invalidKType = { kty: 'oct', k: 123 }; + + expect(Jose.isOctPrivateKeyJwk(invalidKType)).to.be.false; + }); + + it('returns true for valid OCT private JWK with extra properties', () => { + const validOctJwkExtra = { + kty : 'oct', + k : 'base64url-encoded-key', + extra : 'extra-value' + }; + expect(Jose.isOctPrivateKeyJwk(validOctJwkExtra)).to.be.true; + }); }); - it('throws an error with unsupported algorithms', async () => { - const cryptoKey = new CryptoKeyWithJwk( - { name: 'ECDSA', namedCurve: 'P-256' }, // algorithm identifier - false, // extractable - new Uint8Array(32), // material aka key material - 'private', // key type - ['sign', 'verify'] // key usages - ); - - await expect( - cryptoKey.toJwk() - ).to.eventually.be.rejectedWith(Error, 'Unsupported key to JWK conversion: P-256'); + describe('isOkpPrivateKeyJwk()', () => { + it('returns true for a valid OKP private key JWK', () => { + const validOkpJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : 'base64url-encoded-x-value', + d : 'base64url-encoded-private-key' + }; + expect(Jose.isOkpPrivateKeyJwk(validOkpJwk)).to.be.true; + }); + + it('returns false for non-object inputs', () => { + expect(Jose.isOkpPrivateKeyJwk(null)).to.be.false; + expect(Jose.isOkpPrivateKeyJwk(undefined)).to.be.false; + expect(Jose.isOkpPrivateKeyJwk(123)).to.be.false; + expect(Jose.isOkpPrivateKeyJwk('string')).to.be.false; + expect(Jose.isOkpPrivateKeyJwk([])).to.be.false; + }); + + it('returns false if any required property is missing', () => { + const missingKty = { crv: 'Ed25519', x: 'base64url-encoded-x-value', d: 'base64url-encoded-private-key' }; + const missingCrv = { kty: 'OKP', x: 'base64url-encoded-x-value', d: 'base64url-encoded-private-key' }; + const missingX = { kty: 'OKP', crv: 'Ed25519', d: 'base64url-encoded-private-key' }; + const missingD = { kty: 'OKP', crv: 'Ed25519', x: 'base64url-encoded-x-value' }; + + expect(Jose.isOkpPrivateKeyJwk(missingKty)).to.be.false; + expect(Jose.isOkpPrivateKeyJwk(missingCrv)).to.be.false; + expect(Jose.isOkpPrivateKeyJwk(missingX)).to.be.false; + expect(Jose.isOkpPrivateKeyJwk(missingD)).to.be.false; + }); + + it('returns false if kty is not OKP', () => { + const invalidKty = { kty: 'EC', crv: 'Ed25519', x: 'base64url-encoded-x-value', d: 'base64url-encoded-private-key' }; + expect(Jose.isOkpPrivateKeyJwk(invalidKty)).to.be.false; + }); + + it('returns false if any property is of incorrect type', () => { + const invalidDType = { kty: 'OKP', crv: 'Ed25519', x: 'base64url-encoded-x-value', d: 123 }; + const invalidXType = { kty: 'OKP', crv: 'Ed25519', x: 123, d: 'base64url-encoded-private-key' }; + + expect(Jose.isOkpPrivateKeyJwk(invalidDType)).to.be.false; + expect(Jose.isOkpPrivateKeyJwk(invalidXType)).to.be.false; + }); + + it('returns true for valid OKP private JWK with extra properties', () => { + const validOkpJwkExtra = { + kty : 'OKP', + crv : 'Ed25519', + x : 'base64url-encoded-x-value', + d : 'base64url-encoded-private-key', + extra : 'extra-value' + }; + expect(Jose.isOkpPrivateKeyJwk(validOkpJwkExtra)).to.be.true; + }); }); -}); -describe('Jose', () => { - describe('joseToWebCrypto()', () => { - it('translates algorithm format from JOSE to WebCrypto', () => { - let webCrypto: Web5Crypto.GenerateKeyOptions; - for (const vector of joseToWebCryptoTestVectors) { - webCrypto = Jose.joseToWebCrypto(vector.jose as JsonWebKey); - expect(webCrypto).to.deep.equal(vector.webCrypto); - } + + describe('isOkpPublicKeyJwk()', () => { + it('returns true for a valid OKP public key JWK', () => { + const validOkpJwk = { + kty : 'OKP', + crv : 'Ed25519', + x : 'base64url-encoded-x-value' + }; + expect(Jose.isOkpPublicKeyJwk(validOkpJwk)).to.be.true; + }); + + it('returns false for non-object inputs', () => { + expect(Jose.isOkpPublicKeyJwk(null)).to.be.false; + expect(Jose.isOkpPublicKeyJwk(undefined)).to.be.false; + expect(Jose.isOkpPublicKeyJwk(123)).to.be.false; + expect(Jose.isOkpPublicKeyJwk('string')).to.be.false; + expect(Jose.isOkpPublicKeyJwk([])).to.be.false; }); - it('throws an error if required parameters are missing', () => { - expect( - () => Jose.joseToWebCrypto({}) - ).to.throw(TypeError, 'One or more parameters missing'); + it('returns false if any required property is missing', () => { + const missingKty = { crv: 'Ed25519', x: 'base64url-encoded-x-value' }; + const missingCrv = { kty: 'OKP', x: 'base64url-encoded-x-value' }; + const missingX = { kty: 'OKP', crv: 'Ed25519' }; + + expect(Jose.isOkpPublicKeyJwk(missingKty)).to.be.false; + expect(Jose.isOkpPublicKeyJwk(missingCrv)).to.be.false; + expect(Jose.isOkpPublicKeyJwk(missingX)).to.be.false; + }); + + it('returns false if kty is not OKP', () => { + const invalidKty = { kty: 'EC', crv: 'Ed25519', x: 'base64url-encoded-x-value' }; + expect(Jose.isOkpPublicKeyJwk(invalidKty)).to.be.false; }); - it('throws an error if an unknown JOSE algorithm is specified', () => { - expect( - () => Jose.joseToWebCrypto({ alg: 'non-existent' }) - ).to.throw(Error, `Unsupported JOSE to WebCrypto conversion: 'non-existent'`); + it('returns false if any property is of incorrect type', () => { + const invalidXType = { kty: 'OKP', crv: 'Ed25519', x: 123 }; + + expect(Jose.isOkpPublicKeyJwk(invalidXType)).to.be.false; + }); + + it(`returns false if the private key parameter 'd' is present`, () => { + const withDParam = { kty: 'OKP', crv: 'Ed25519', x: 'base64url-encoded-x-value', d: 'base64url-encoded-d-value' }; + expect(Jose.isOkpPublicKeyJwk(withDParam)).to.be.false; + }); - expect( - // @ts-expect-error because invalid algorithm was intentionally specified to trigger an error. - () => Jose.joseToWebCrypto({ crv: 'non-existent' }) - ).to.throw(Error, `Unsupported JOSE to WebCrypto conversion: 'non-existent'`); + it('returns true for valid OKP public JWK with extra properties', () => { + const validOkpJwkExtra = { + kty : 'OKP', + crv : 'Ed25519', + x : 'base64url-encoded-x-value', + extra : 'extra-value' + }; + expect(Jose.isOkpPublicKeyJwk(validOkpJwkExtra)).to.be.true; }); }); @@ -188,205 +320,137 @@ describe('Jose', () => { }); }); - describe('jwkToCryptoKey()', () => { + describe('publicKeyToMultibaseId()', () => { it('passes all test vectors', async () => { - let cryptoKey: Web5Crypto.CryptoKey; + let multibaseId: string; - for (const vector of jwkToCryptoKeyTestVectors) { - cryptoKey = await Jose.jwkToCryptoKey({ key: vector.jsonWebKey as JsonWebKey}); - expect(cryptoKey).to.deep.equal(vector.cryptoKey); + for (const vector of jwkToMultibaseIdTestVectors) { + multibaseId = await Jose.publicKeyToMultibaseId({ publicKey: vector.input as PublicKeyJwk}); + expect(multibaseId).to.equal(vector.output); } }); - it('throws an error when ext parameter is missing', async () => { + it('throws an error for an unsupported public key type', async () => { await expect( - Jose.jwkToCryptoKey({key: { - 'alg' : 'A256CTR', - 'key_ops' : ['encrypt', 'decrypt'], - 'k' : 'UQtIAS-rmWB-vgNgG4lPrnTS2tNvwDPKl9rs0L9ICnU', - 'kty' : 'oct', - }}) - ).to.eventually.be.rejectedWith(Error, `Conversion from JWK to CryptoKey failed. Required parameter missing: 'ext'`); + Jose.publicKeyToMultibaseId({ + publicKey: { + kty : 'RSA', + n : 'r0YDzIV4GPJ1wFb1Gftdd3C3VE6YeknVq1C7jGypq5WTTmX0yRDBqzL6mBR3_c-mKRuE5Z5VMGniA1lFnFmv8m0A2engKfALXHPJqoL6WzqN1SyjSM2aI6v8JVTj4H0RdYV9R4jxIB-zK5X-ZyL6CwHx-3dKZkCvZSEp8b-5I8c2Fz8E8Hl7qKkD_qEz6ZOmKVhJLGiEag1qUQYJv2TcRdiyZfwwVsV3nI3IcVfMCTjDZTw2jI0YHJgLi7-MkP4DO7OJ4D4AFtL-7CkZ7V2xG0piBz4b02_-ZGnBZ5zHJxGoUZnTY6HX4V9bPQI_ME8qCjFXf-TcwCfDFcwMm70L2Q', + e : 'AQAB', + alg : 'RS256' + } + }) + ).to.eventually.be.rejectedWith(Error, `Unsupported public key type`); }); - it('throws an error when key_ops parameter is missing', async () => { + it('throws an error for an unsupported public key curve', async () => { await expect( - Jose.jwkToCryptoKey({key: { - 'alg' : 'A256CTR', - 'ext' : 'true', - 'k' : 'UQtIAS-rmWB-vgNgG4lPrnTS2tNvwDPKl9rs0L9ICnU', - 'kty' : 'oct', - }}) - ).to.eventually.be.rejectedWith(Error, `Conversion from JWK to CryptoKey failed. Required parameter missing: 'key_ops'`); + Jose.publicKeyToMultibaseId({ + publicKey: { + kty : 'EC', + crv : 'P-256', + x : 'SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74', + y : 'lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI' + } + }) + ).to.eventually.be.rejectedWith(Error, `Unsupported public key curve`); }); }); - describe('jwkToKey()', () => { - it('converts JWK into Jose parameters', async () => { - let jwk: { keyMaterial: Uint8Array; keyType: Web5Crypto.KeyType }; - - for (const vector of jwkToKeyTestVectors) { - jwk = await Jose.jwkToKey({ key: vector.input as JsonWebKey}); - const hexKeyMaterial = Convert.uint8Array(jwk.keyMaterial).toHex(); - - expect({...jwk, keyMaterial: hexKeyMaterial}).to.deep.equal(vector.output); - } + describe('multicodecToJose()', () => { + it('converts ed25519 public key multicodec to JWK', async () => { + const result = await Jose.multicodecToJose({ name: 'ed25519-pub' }); + expect(result).to.deep.equal({ + crv : 'Ed25519', + kty : 'OKP', + x : '' // x value would be populated with actual key material in real use + }); }); - it('throws an error if unsupported JOSE has been passed', async () => { - await expect( - // @ts-expect-error because parameters are intentionally omitted to trigger an error. - Jose.jwkToKey({ key: { alg: 'HS256', kty: 'oct' }}) - ).to.eventually.be.rejectedWith(Error, `Jose: Unknown JSON Web Key format.`); + it('converts ed25519 private key multicodec to JWK', async () => { + const result = await Jose.multicodecToJose({ name: 'ed25519-priv' }); + expect(result).to.deep.equal({ + crv : 'Ed25519', + kty : 'OKP', + x : '', // x value would be populated with actual key material in real use + d : '' // d value would be populated with actual key material in real use + }); }); - }); - describe('jwkToMultibaseId()', () => { - it('passes all test vectors', async () => { - let multibaseId: string; - - for (const vector of jwkToMultibaseIdTestVectors) { - multibaseId = await Jose.jwkToMultibaseId({ key: vector.input as JsonWebKey}); - expect(multibaseId).to.equal(vector.output); - } - }); - - // it('throws an error when ext parameter is missing', async () => { - // await expect( - // Jose.jwkToCryptoKey({key: { - // 'alg' : 'A256CTR', - // 'key_ops' : ['encrypt', 'decrypt'], - // 'k' : 'UQtIAS-rmWB-vgNgG4lPrnTS2tNvwDPKl9rs0L9ICnU', - // 'kty' : 'oct', - // }}) - // ).to.eventually.be.rejectedWith(Error, `Conversion from JWK to CryptoKey failed. Required parameter missing: 'ext'`); - // }); - }); - - describe('keyToJwk()', () => { - it('converts key with Jose parameters (from WebCrypto) into JWK', async () => { - let jwkParams: Partial; - const keyMaterial = Convert.hex(keyToJwkTestVectorsKeyMaterial).toUint8Array(); - - for (const vector of keyToJwkWebCryptoTestVectors) { - jwkParams = Jose.webCryptoToJose(vector.input); - const jwk = await Jose.keyToJwk({ keyMaterial, keyType: 'public', ...jwkParams }); - expect(jwk).to.deep.equal(vector.output); - } + it('converts secp256k1 public key multicodec to JWK', async () => { + const result = await Jose.multicodecToJose({ name: 'secp256k1-pub' }); + expect(result).to.deep.equal({ + crv : 'secp256k1', + kty : 'EC', + x : '', // x value would be populated with actual key material in real use + y : '' // y value would be populated with actual key material in real use + }); }); - it('converts key with Jose parameters (from Multicodec) into JWK', async () => { - let jwkParams: Partial; - const keyMaterial = Convert.hex(keyToJwkTestVectorsKeyMaterial).toUint8Array(); - - for (const vector of keyToJwkMulticodecTestVectors) { - jwkParams = await Jose.multicodecToJose({ name: vector.input }); - const keyType = vector.input.includes('priv') ? 'private' : 'public'; - const jwk = await Jose.keyToJwk({ keyMaterial, keyType, ...jwkParams }); - expect(jwk).to.deep.equal(vector.output); - } + it('converts secp256k1 private key multicodec to JWK', async () => { + const result = await Jose.multicodecToJose({ name: 'secp256k1-priv' }); + expect(result).to.deep.equal({ + crv : 'secp256k1', + kty : 'EC', + x : '', // x value would be populated with actual key material in real use + y : '', // y value would be populated with actual key material in real use + d : '' // d value would be populated with actual key material in real use + }); }); - it('coverts when kty equals to null', async () => { - let jwkParams: Partial; - const keyMaterial = Convert.hex(keyToJwkTestVectorsKeyMaterial).toUint8Array(); - - for (const vector of keyToJwkWebCryptoWithNullKTYTestVectors) { - jwkParams = Jose.webCryptoToJose(vector.input); - // @ts-expect-error because parameters are intentionally omitted to trigger an error. - const jwk = await Jose.keyToJwk({ keyMaterial, keyType: 'public', ...jwkParams, kty: null }); - expect(jwk).to.deep.equal(vector.output); - } + it('converts x25519 public key multicodec to JWK', async () => { + const result = await Jose.multicodecToJose({ name: 'x25519-pub' }); + expect(result).to.deep.equal({ + crv : 'X25519', + kty : 'OKP', + x : '' // x value would be populated with actual key material in real use + }); }); - it('throws an error for wrong arguments', async () => { - await expect( - Jose.multicodecToJose({ name: 'intentionally-wrong-name', code: 12345 }) - ).to.eventually.be.rejectedWith(Error, `Either 'name' or 'code' must be defined, but not both.`); + it('converts x25519 private key multicodec to JWK', async () => { + const result = await Jose.multicodecToJose({ name: 'x25519-priv' }); + expect(result).to.deep.equal({ + crv : 'X25519', + kty : 'OKP', + x : '', // x value would be populated with actual key material in real use + d : '' // d value would be populated with actual key material in real use + }); }); - it('handles undefined name', async () => { - const jwkParams = await Jose.multicodecToJose({ name: undefined, code: 0xed }); - expect(jwkParams).to.deep.equal({ alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP', x: '' }); - }); - - it('throws an error for unsupported multicodec conversion', async () => { - await expect( - Jose.multicodecToJose({ name: 'intentionally-wrong-name' }) - ).to.eventually.be.rejectedWith(Error, `Unsupported Multicodec to JOSE conversion: 'intentionally-wrong-name'`); - }); - - it('throws an error for unsupported conversion', async () => { - let jwkParams: Partial; - const testVectors = [ - { namedCurve: 'Ed448', name: 'EdDSA' }, - { namedCurve: 'P-256', name: 'ECDSA' }, - { namedCurve: 'P-384', name: 'ECDSA' }, - { namedCurve: 'P-521', name: 'ECDSA' } - ]; - const keyMaterial = new Uint8Array(32); - for (const vector of testVectors) { - jwkParams = Jose.webCryptoToJose(vector); - await expect( - Jose.keyToJwk({ keyMaterial, keyType: 'public', ...jwkParams }) - ).to.eventually.be.rejectedWith(Error, 'Unsupported key to JWK conversion'); + it('throws an error when name is undefined and code is not provided', async () => { + try { + await Jose.multicodecToJose({}); + expect.fail('Should have thrown an error for undefined name and code'); + } catch (e: any) { + expect(e.message).to.equal('Either \'name\' or \'code\' must be defined, but not both.'); } }); - }); - describe('webCryptoToJose()', () => { - it('translates algorithm format from WebCrypto to JOSE', () => { - let jose: Partial; - for (const vector of joseToWebCryptoTestVectors) { - jose = Jose.webCryptoToJose(vector.webCrypto); - expect(jose).to.deep.equal(vector.jose); + it('throws an error when both name and code are provided', async () => { + try { + await Jose.multicodecToJose({ name: 'ed25519-pub', code: 0xed }); + expect.fail('Should have thrown an error for both name and code being defined'); + } catch (e: any) { + expect(e.message).to.equal('Either \'name\' or \'code\' must be defined, but not both.'); } }); - it('throws an error if required parameters are missing', () => { - expect( - // @ts-expect-error because parameters are intentionally omitted to trigger an error. - () => Jose.webCryptoToJose({}) - ).to.throw(TypeError, 'One or more parameters missing'); - }); - - it('throws an error if an unknown WebCrypto algorithm is specified', () => { - expect( - () => Jose.webCryptoToJose({ name: 'non-existent', namedCurve: 'non-existent' }) - ).to.throw(Error, `Unsupported WebCrypto to JOSE conversion: 'non-existent:non-existent'`); - - expect( - () => Jose.webCryptoToJose({ name: 'non-existent', length: 64 }) - ).to.throw(Error, `Unsupported WebCrypto to JOSE conversion: 'non-existent:64'`); - - expect( - () => Jose.webCryptoToJose({ name: 'non-existent', hash: { name: 'SHA-1' } }) - ).to.throw(Error, `Unsupported WebCrypto to JOSE conversion: 'non-existent:SHA-1'`); + it('throws an error for unsupported multicodec name', async () => { + try { + await Jose.multicodecToJose({ name: 'unsupported-key-type' }); + expect.fail('Should have thrown an error for unsupported multicodec name'); + } catch (e: any) { + expect(e.message).to.include('Unsupported Multicodec to JOSE conversion'); + } }); - }); - - describe('cryptoKeyToJwkPair()', () => { - it('converts CryptoKeys to JWK Pair', async () => { - for (const vector of cryptoKeyPairToJsonWebKeyTestVectors) { - const privateKey = { - ...vector.cryptoKey.privateKey, - material: Convert.hex( - vector.cryptoKey.privateKey.material - ).toUint8Array(), - } as Web5Crypto.CryptoKey; - const publicKey = { - ...vector.cryptoKey.publicKey, - material: Convert.hex( - vector.cryptoKey.publicKey.material - ).toUint8Array(), - } as Web5Crypto.CryptoKey; - - const jwkKeyPair = await Jose.cryptoKeyToJwkPair({ - keyPair: { publicKey, privateKey }, - }); - expect(jwkKeyPair).to.deep.equal(vector.jsonWebKey); + it('throws an error for unsupported multicodec code', async () => { + try { + await Jose.multicodecToJose({ code: 0x9999 }); + expect.fail('Should have thrown an error for unsupported multicodec code'); + } catch (e: any) { + expect(e.message).to.include('Unsupported multicodec'); } }); }); -}); +}); \ No newline at end of file diff --git a/packages/crypto/tests/tsconfig.json b/packages/crypto/tests/tsconfig.json index 7c6d2c8e7..0cdb42f92 100644 --- a/packages/crypto/tests/tsconfig.json +++ b/packages/crypto/tests/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "outDir": "compiled", "declarationDir": "compiled/types", - "sourceMap": true, + "resolveJsonModule": true, + "sourceMap": true }, "include": [ "../src", diff --git a/packages/crypto/tests/utils.spec.ts b/packages/crypto/tests/utils.spec.ts index be88d1246..50a27e93f 100644 --- a/packages/crypto/tests/utils.spec.ts +++ b/packages/crypto/tests/utils.spec.ts @@ -1,10 +1,8 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; -import { CryptoKey } from '../src/algorithms-api/crypto-key.js'; import { randomUuid, - isCryptoKeyPair, keyToMultibaseId, multibaseIdToKey, checkValidProperty, @@ -54,42 +52,6 @@ describe('Crypto Utils', () => { }); }); - describe('isCryptoKeyPair()', () => { - it('returns true with a CryptoKeyPair object', () => { - const publicKey = new CryptoKey( - { name: 'EdDSA', namedCurve: 'Ed25519' }, - true, - new Uint8Array(32), - 'public', - ['verify'] - ); - const privateKey = new CryptoKey( - { name: 'EdDSA', namedCurve: 'Ed25519' }, - true, - new Uint8Array(32), - 'private', - ['sign'] - ); - const validCryptoKeyPair = { publicKey, privateKey }; - - const result = isCryptoKeyPair(validCryptoKeyPair); - expect(result).to.be.true; - }); - - it('returns false for a CryptoKey', () => { - const cryptoKey = new CryptoKey( - { name: 'EdDSA', namedCurve: 'Ed25519' }, - true, - new Uint8Array(32), - 'secret', - ['decrypt', 'encrypt'] - ); - - const result = isCryptoKeyPair(cryptoKey); - expect(result).to.be.false; - }); - }); - describe('isWebCryptoSupported()', () => { afterEach(() => { // Restore the original state after each test diff --git a/packages/identity-agent/package.json b/packages/identity-agent/package.json index a61cbb5e7..142db6e71 100644 --- a/packages/identity-agent/package.json +++ b/packages/identity-agent/package.json @@ -68,7 +68,9 @@ }, "dependencies": { "@web5/agent": "0.2.4", - "@web5/api": "0.8.3" + "@web5/common": "0.2.1", + "@web5/crypto": "0.2.2", + "@web5/dids": "0.2.2" }, "devDependencies": { "@playwright/test": "1.36.2", From 34b915df95dada8741737f23c83faa9c1f5a0138 Mon Sep 17 00:00:00 2001 From: Timothy Shamilov Date: Tue, 28 Nov 2023 17:38:44 -0500 Subject: [PATCH 05/11] types: fix syncmanager typedefs to use abstractlevel (#309) * fix syncmanager to use abstractlevel * fix type * organize imports --- packages/agent/src/sync-manager.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/agent/src/sync-manager.ts b/packages/agent/src/sync-manager.ts index a6f3ea230..4d1e8d8b6 100644 --- a/packages/agent/src/sync-manager.ts +++ b/packages/agent/src/sync-manager.ts @@ -1,20 +1,17 @@ -import type { BatchOperation } from 'level'; +import { DataStream } from '@tbd54566975/dwn-sdk-js'; +import { Convert } from '@web5/common'; +import { utils as didUtils } from '@web5/dids'; +import { Level } from 'level'; +import { webReadableToIsomorphicNodeReadable } from './utils.js'; import type { EventsGetReply, GenericMessage, MessagesGetReply, RecordsWriteMessage, } from '@tbd54566975/dwn-sdk-js'; - -import { Level } from 'level'; -import { Convert } from '@web5/common'; -import { utils as didUtils } from '@web5/dids'; -import { DataStream } from '@tbd54566975/dwn-sdk-js'; - +import type { AbstractBatchOperation, AbstractLevel } from 'abstract-level'; import type { Web5ManagedAgent } from './types/agent.js'; -import { webReadableToIsomorphicNodeReadable } from './utils.js'; - export interface SyncManager { agent: Web5ManagedAgent; registerIdentity(options: { did: string }): Promise; @@ -24,10 +21,12 @@ export interface SyncManager { pull(): Promise; } +type LevelDatabase = AbstractLevel; + export type SyncManagerOptions = { agent?: Web5ManagedAgent; dataPath?: string; - db?: Level; + db?: LevelDatabase; }; type SyncDirection = 'push' | 'pull'; @@ -43,7 +42,7 @@ type DwnMessage = { data?: Blob; } -type DbBatchOperation = BatchOperation; +type DbBatchOperation = AbstractBatchOperation; const is2xx = (code: number) => code >= 200 && code <= 299; const is4xx = (code: number) => code >= 400 && code <= 499; @@ -57,7 +56,7 @@ export class SyncManagerLevel implements SyncManager { * operations within the broader Web5 agent framework. */ private _agent?: Web5ManagedAgent; - private _db: Level; + private _db: LevelDatabase; private _syncIntervalId?: ReturnType; constructor(options?: SyncManagerOptions) { From 7828b69888ab2ce96e241730773a8ad1e30d77a1 Mon Sep 17 00:00:00 2001 From: phoebe-lew Date: Wed, 29 Nov 2023 10:31:47 +0000 Subject: [PATCH 06/11] resolve did:dht in VCs (#321) --- packages/credentials/src/verifiable-credential.ts | 4 ++-- packages/credentials/tests/verifiable-credential.spec.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/credentials/src/verifiable-credential.ts b/packages/credentials/src/verifiable-credential.ts index 0fc754f5d..4b8ef4a94 100644 --- a/packages/credentials/src/verifiable-credential.ts +++ b/packages/credentials/src/verifiable-credential.ts @@ -8,7 +8,7 @@ import { v4 as uuidv4 } from 'uuid'; import { getCurrentXmlSchema112Timestamp } from './utils.js'; import { Convert } from '@web5/common'; import { verifyJWT } from 'did-jwt'; -import { DidIonMethod, DidKeyMethod, DidResolver } from '@web5/dids'; +import { DidDhtMethod, DidIonMethod, DidKeyMethod, DidResolver } from '@web5/dids'; import { SsiValidator } from './validators.js'; export const DEFAULT_CONTEXT = 'https://www.w3.org/2018/credentials/v1'; @@ -62,7 +62,7 @@ type DecodedVcJwt = { signature: string } -const didResolver = new DidResolver({ didResolvers: [DidIonMethod, DidKeyMethod] }); +const didResolver = new DidResolver({ didResolvers: [DidIonMethod, DidKeyMethod, DidDhtMethod] }); class TbdResolver implements Resolvable { async resolve(didUrl: string): Promise { diff --git a/packages/credentials/tests/verifiable-credential.spec.ts b/packages/credentials/tests/verifiable-credential.spec.ts index d4f9bfbe0..3142e9c5a 100644 --- a/packages/credentials/tests/verifiable-credential.spec.ts +++ b/packages/credentials/tests/verifiable-credential.spec.ts @@ -16,7 +16,6 @@ describe('Verifiable Credential Tests', () => { ) {} } - beforeEach(async () => { const alice = await DidKeyMethod.create(); const [signingKeyPair] = alice.keySet.verificationMethodKeys!; From 9c7c866c296ae282e6fb4946eae91c311daf6efb Mon Sep 17 00:00:00 2001 From: phoebe-lew Date: Wed, 29 Nov 2023 14:49:07 +0000 Subject: [PATCH 07/11] Test for did:dht signed VCs + release (#322) Signed-off-by: Frank Hinek Co-authored-by: Frank Hinek --- package-lock.json | 11 +- packages/api/package.json | 2 +- packages/credentials/package.json | 2 +- .../tests/verifiable-credential.spec.ts | 169 +++++++++++++++++- packages/identity-agent/package.json | 1 + 5 files changed, 177 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index c663b537f..1070b6998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3419,9 +3419,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.595", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.595.tgz", - "integrity": "sha512-+ozvXuamBhDOKvMNUQvecxfbyICmIAwS4GpLmR0bsiSBlGnLaOcs2Cj7J8XSbW+YEaN3Xl3ffgpm+srTUWFwFQ==", + "version": "1.4.596", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.596.tgz", + "integrity": "sha512-zW3zbZ40Icb2BCWjm47nxwcFGYlIgdXkAx85XDO7cyky9J4QQfq8t0W19/TLZqq3JPQXtlv8BPIGmfa9Jb4scg==", "dev": true, "peer": true }, @@ -9992,7 +9992,7 @@ }, "packages/api": { "name": "@web5/api", - "version": "0.8.3", + "version": "0.8.2", "license": "Apache-2.0", "dependencies": { "@tbd54566975/dwn-sdk-js": "0.2.8", @@ -10415,7 +10415,7 @@ }, "packages/credentials": { "name": "@web5/credentials", - "version": "0.3.1", + "version": "0.3.2", "license": "Apache-2.0", "dependencies": { "@sphereon/pex": "2.1.0", @@ -11093,6 +11093,7 @@ "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web5/api": "0.8.2", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", diff --git a/packages/api/package.json b/packages/api/package.json index b64b2d2df..ec1e14fc6 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@web5/api", - "version": "0.8.3", + "version": "0.8.2", "description": "SDK for accessing the features and capabilities of Web5", "type": "module", "main": "./dist/cjs/index.js", diff --git a/packages/credentials/package.json b/packages/credentials/package.json index 98bafcc3e..92920a558 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -1,6 +1,6 @@ { "name": "@web5/credentials", - "version": "0.3.1", + "version": "0.3.2", "description": "Verifiable Credentials", "type": "module", "main": "./dist/cjs/index.js", diff --git a/packages/credentials/tests/verifiable-credential.spec.ts b/packages/credentials/tests/verifiable-credential.spec.ts index 3142e9c5a..6cc2fe02a 100644 --- a/packages/credentials/tests/verifiable-credential.spec.ts +++ b/packages/credentials/tests/verifiable-credential.spec.ts @@ -1,7 +1,8 @@ import { expect } from 'chai'; import { VerifiableCredential, SignOptions } from '../src/verifiable-credential.js'; import { Ed25519, Jose } from '@web5/crypto'; -import { DidKeyMethod } from '@web5/dids'; +import { DidDhtMethod, DidKeyMethod, PortableDid } from '@web5/dids'; +import sinon from 'sinon'; type Signer = (data: Uint8Array) => Promise; @@ -180,6 +181,172 @@ describe('Verifiable Credential Tests', () => { await VerifiableCredential.verify(vcJwt); }); + + it('verify does not throw an exception with vaild vc signed by did:dht', async () => { + const mockDocument: PortableDid = { + keySet: { + identityKey: { + privateKeyJwk: { + d : '_8gihSI-m8aOCCM6jHg33d8kxdImPBN4C5_bZIu10XU', + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + ext : 'true', + key_ops : [ + 'sign' + ], + x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', + kid : '0' + }, + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + ext : 'true', + key_ops : [ + 'verify' + ], + x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', + kid : '0' + } + }, + verificationMethodKeys: [ + { + privateKeyJwk: { + d : '_8gihSI-m8aOCCM6jHg33d8kxdImPBN4C5_bZIu10XU', + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + ext : 'true', + key_ops : [ + 'sign' + ], + x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', + kid : '0' + }, + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + ext : 'true', + key_ops : [ + 'verify' + ], + x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', + kid : '0' + }, + relationships: [ + 'authentication', + 'assertionMethod', + 'capabilityInvocation', + 'capabilityDelegation' + ] + } + ] + + }, + did : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + document : { + id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + verificationMethod : [ + { + id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy#0', + type : 'JsonWebKey2020', + controller : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + publicKeyJwk : { + crv : 'Ed25519', + kty : 'OKP', + alg : 'EdDSA', + kid : '0', + x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo' + } + } + ], + authentication: [ + '#0' + ], + assertionMethod: [ + '#0' + ], + capabilityInvocation: [ + '#0' + ], + capabilityDelegation: [ + '#0' + ] + } + }; + const didDhtCreateSpy = sinon.stub(DidDhtMethod, 'create').resolves(mockDocument); + + const alice = await DidDhtMethod.create({ publish: true }); + + const [signingKeyPair] = alice.keySet.verificationMethodKeys!; + const privateKey = (await Jose.jwkToKey({ key: signingKeyPair.privateKeyJwk! })).keyMaterial; + signer = EdDsaSigner(privateKey); + signOptions = { + issuerDid : alice.did, + subjectDid : alice.did, + kid : alice.did + '#0', + signer : signer + }; + + const vc = VerifiableCredential.create({ + type : 'StreetCred', + issuer : signOptions.issuerDid, + subject : signOptions.subjectDid, + data : new StreetCredibility('high', true), + }); + + const dhtDidResolutionSpy = sinon.stub(DidDhtMethod, 'resolve').resolves({ + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : { + id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + verificationMethod : [ + { + id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy#0', + type : 'JsonWebKey2020', + controller : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + publicKeyJwk : { + crv : 'Ed25519', + kty : 'OKP', + alg : 'EdDSA', + kid : '0', + x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo' + } + } + ], + authentication: [ + '#0' + ], + assertionMethod: [ + '#0' + ], + capabilityInvocation: [ + '#0' + ], + capabilityDelegation: [ + '#0' + ] + }, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + did : { + didString : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + methodSpecificId : 'ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + method : 'dht' + } + } + }); + + const vcJwt = await vc.sign(signOptions); + + await VerifiableCredential.verify(vcJwt); + + sinon.assert.calledOnce(didDhtCreateSpy); + sinon.assert.calledOnce(dhtDidResolutionSpy); + sinon.restore(); + }); }); }); diff --git a/packages/identity-agent/package.json b/packages/identity-agent/package.json index 142db6e71..65fe2606c 100644 --- a/packages/identity-agent/package.json +++ b/packages/identity-agent/package.json @@ -80,6 +80,7 @@ "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web5/api": "0.8.2", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", From dce370dd8dbab6aa31d046a4340cdbf173fff4eb Mon Sep 17 00:00:00 2001 From: LiranCohen Date: Wed, 29 Nov 2023 14:16:43 -0500 Subject: [PATCH 08/11] Support RecordsQuery pagination in Web5 (#268) * Add pagination messageCid to RecordsQueryResponse and tests supporting pagination/sort * cursor instead of paginationsMessagecid --------- Signed-off-by: Frank Hinek Co-authored-by: Frank Hinek --- README.md | 14 ++++ packages/agent/src/sync-manager.ts | 15 ++-- packages/api/README.md | 14 ++++ packages/api/src/dwn-api.ts | 9 +- packages/api/tests/dwn-api.spec.ts | 130 +++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a324672ff..8da44abbc 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,20 @@ The query `request` contains the following properties: - **`recipient`** - _`string`_ (_optional_): the DID in the `recipient` field of the record. - **`schema`** - _`URI string`_ (_optional_): the URI of the schema bucket in which to query. - **`dataFormat`** - _`Media Type string`_ (_optional_): the IANA string corresponding with the format of the data to filter for. See IANA's Media Type list here: https://www.iana.org/assignments/media-types/media-types.xhtml + - **`dateSort`** - _`DateSort`_ (_optional_): the `DateSort` value of the date field and direction to sort records by. Defaults to `CreatedAscending`. + - **`pagination`** - _`object`_ (_optional_): the properties used to paginate results. + - **`limit`** - _`number`_ (_optional_): the number of records that should be returned with this query. `undefined` returns all records. + - **`cursor`** - _`messageCid string`_ (_optional_): the `messageCid` of the records toc continue paginating from. This value is returned as a `cursor` in the response object of a `query` if there are more results beyond the `limit`. + +#### **Response** + +The query `response` contains the following properties: + +- **`status`** - _`object`_: the status of the `request`: + - **`code`** - _`number`_: the `Response Status` code, following the response code patterns for `HTTP Response Status Codes`. + - **`detail`** _`string`_: a detailed message describing the response. +- **`records`** - _`Records array`_ (_optional_): the array of `Records` returned if the request was successful. +- **`cursor`** - _`messageCid string`_ (_optional_): the `messageCid` of the last message returned in the results if there are exist additional records beyond the specified `limit` in the `query`. ### **`web5.dwn.records.create(request)`** diff --git a/packages/agent/src/sync-manager.ts b/packages/agent/src/sync-manager.ts index 4d1e8d8b6..edf5cb5bc 100644 --- a/packages/agent/src/sync-manager.ts +++ b/packages/agent/src/sync-manager.ts @@ -1,17 +1,20 @@ -import { DataStream } from '@tbd54566975/dwn-sdk-js'; -import { Convert } from '@web5/common'; -import { utils as didUtils } from '@web5/dids'; -import { Level } from 'level'; -import { webReadableToIsomorphicNodeReadable } from './utils.js'; +import type { AbstractBatchOperation, AbstractLevel } from 'abstract-level'; import type { EventsGetReply, GenericMessage, MessagesGetReply, RecordsWriteMessage, } from '@tbd54566975/dwn-sdk-js'; -import type { AbstractBatchOperation, AbstractLevel } from 'abstract-level'; + +import { Level } from 'level'; +import { Convert } from '@web5/common'; +import { utils as didUtils } from '@web5/dids'; +import { DataStream } from '@tbd54566975/dwn-sdk-js'; + import type { Web5ManagedAgent } from './types/agent.js'; +import { webReadableToIsomorphicNodeReadable } from './utils.js'; + export interface SyncManager { agent: Web5ManagedAgent; registerIdentity(options: { did: string }): Promise; diff --git a/packages/api/README.md b/packages/api/README.md index f1921e21f..45b122ce9 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -217,6 +217,20 @@ The query `request` contains the following properties: - **`recipient`** - _`string`_ (_optional_): the DID in the `recipient` field of the record. - **`schema`** - _`URI string`_ (_optional_): the URI of the schema bucket in which to query. - **`dataFormat`** - _`Media Type string`_ (_optional_): the IANA string corresponding with the format of the data to filter for. See IANA's Media Type list here: https://www.iana.org/assignments/media-types/media-types.xhtml + - **`dateSort`** - _`DateSort`_ (_optional_): the `DateSort` value of the date field and direction to sort records by. Defaults to `CreatedAscending`. + - **`pagination`** - _`object`_ (_optional_): the properties used to paginate results. + - **`limit`** - _`number`_ (_optional_): the number of records that should be returned with this query. `undefined` returns all records. + - **`cursor`** - _`messageCid string`_ (_optional_): the `messageCid` of the records toc continue paginating from. This value is returned as a `cursor` in the response object of a `query` if there are more results beyond the `limit`. + +#### **Response** + +The query `response` contains the following properties: + +- **`status`** - _`object`_: the status of the `request`: + - **`code`** - _`number`_: the `Response Status` code, following the response code patterns for `HTTP Response Status Codes`. + - **`detail`** _`string`_: a detailed message describing the response. +- **`records`** - _`Records array`_ (_optional_): the array of `Records` returned if the request was successful. +- **`cursor`** - _`messageCid string`_ (_optional_): the `messageCid` of the last message returned in the results if there are exist additional records beyond the specified `limit` in the `query`. ### **`web5.dwn.records.create(request)`** diff --git a/packages/api/src/dwn-api.ts b/packages/api/src/dwn-api.ts index 359b8050e..c28ec50ec 100644 --- a/packages/api/src/dwn-api.ts +++ b/packages/api/src/dwn-api.ts @@ -16,8 +16,8 @@ import { isEmptyObject } from '@web5/common'; import { DwnInterfaceName, DwnMethodName, RecordsWrite } from '@tbd54566975/dwn-sdk-js'; import { Record } from './record.js'; -import { Protocol } from './protocol.js'; import { dataToBlob } from './utils.js'; +import { Protocol } from './protocol.js'; /** * Status code and detailed message for a response. @@ -131,6 +131,9 @@ export type RecordsQueryRequest = { */ export type RecordsQueryResponse = ResponseStatus & { records?: Record[] + + /** If there are additional results, the messageCid of the last record will be returned as a pagination cursor. */ + cursor?: string; }; /** @@ -360,7 +363,7 @@ export class DwnApi { agentResponse = await this.agent.processDwnRequest(agentRequest); } - const { reply: { entries, status } } = agentResponse; + const { reply: { entries, status, cursor } } = agentResponse; const records = entries.map((entry: RecordsQueryReplyEntry) => { const recordOptions = { @@ -381,7 +384,7 @@ export class DwnApi { return record; }); - return { records, status }; + return { records, status, cursor }; }, /** diff --git a/packages/api/tests/dwn-api.spec.ts b/packages/api/tests/dwn-api.spec.ts index 1320f5165..54a514cc6 100644 --- a/packages/api/tests/dwn-api.spec.ts +++ b/packages/api/tests/dwn-api.spec.ts @@ -1,7 +1,9 @@ import type { PortableDid } from '@web5/dids'; +import sinon from 'sinon'; import { expect } from 'chai'; import { TestManagedAgent } from '@web5/agent'; +import { DateSort } from '@tbd54566975/dwn-sdk-js'; import { DwnApi } from '../src/dwn-api.js'; import { testDwnUrl } from './test-config.js'; @@ -528,6 +530,134 @@ describe('DwnApi', () => { expect(result.records!.length).to.equal(1); expect(result.records![0].id).to.equal(writeResult.record!.id); }); + + it('returns cursor when there are additional results', async () => { + for(let i = 0; i < 3; i++ ) { + const writeResult = await dwnAlice.records.write({ + data : `Hello, world ${i + 1}!`, + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.status.detail).to.equal('Accepted'); + expect(writeResult.record).to.exist; + } + + const results = await dwnAlice.records.query({ + message: { + filter: { + schema: 'foo/bar' + }, + pagination: { limit: 2 } // set a limit of 2 + } + }); + + expect(results.status.code).to.equal(200); + expect(results.records).to.exist; + expect(results.records!.length).to.equal(2); + expect(results.cursor).to.exist; + + const additionalResults = await dwnAlice.records.query({ + message: { + filter: { + schema: 'foo/bar' + }, + pagination: { limit: 2, cursor: results.cursor} + } + }); + expect(additionalResults.status.code).to.equal(200); + expect(additionalResults.records).to.exist; + expect(additionalResults.records!.length).to.equal(1); + expect(additionalResults.cursor).to.not.exist; + }); + + it('sorts results based on provided query sort parameter', async () => { + const clock = sinon.useFakeTimers(); + + const items = []; + const publishedItems = []; + for(let i = 0; i < 6; i++ ) { + const writeResult = await dwnAlice.records.write({ + data : `Hello, world ${i + 1}!`, + message : { + published : i % 2 == 0 ? true : false, + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.status.detail).to.equal('Accepted'); + expect(writeResult.record).to.exist; + + items.push(writeResult.record.id); // add id to list in the order it was inserted + if (writeResult.record.published === true) { + publishedItems.push(writeResult.record.id); // add published records separately + } + + clock.tick(1000 * 1); // travel forward one second + } + clock.restore(); + + // query in ascending order by the dateCreated field + const createdAscResults = await dwnAlice.records.query({ + message: { + filter: { + schema: 'foo/bar' + }, + dateSort: DateSort.CreatedAscending // same as default + } + }); + expect(createdAscResults.status.code).to.equal(200); + expect(createdAscResults.records).to.exist; + expect(createdAscResults.records!.length).to.equal(6); + expect(createdAscResults.records.map(r => r.id)).to.eql(items); + + // query in descending order by the dateCreated field + const createdDescResults = await dwnAlice.records.query({ + message: { + filter: { + schema: 'foo/bar' + }, + dateSort: DateSort.CreatedDescending + } + }); + expect(createdDescResults.status.code).to.equal(200); + expect(createdDescResults.records).to.exist; + expect(createdDescResults.records!.length).to.equal(6); + expect(createdDescResults.records.map(r => r.id)).to.eql([...items].reverse()); + + // query in ascending order by the datePublished field, this will only return published records + const publishedAscResults = await dwnAlice.records.query({ + message: { + filter: { + schema: 'foo/bar' + }, + dateSort: DateSort.PublishedAscending + } + }); + expect(publishedAscResults.status.code).to.equal(200); + expect(publishedAscResults.records).to.exist; + expect(publishedAscResults.records!.length).to.equal(3); + expect(publishedAscResults.records.map(r => r.id)).to.eql(publishedItems); + + // query in desscending order by the datePublished field, this will only return published records + const publishedDescResults = await dwnAlice.records.query({ + message: { + filter: { + schema: 'foo/bar' + }, + dateSort: DateSort.PublishedDescending + } + }); + expect(publishedDescResults.status.code).to.equal(200); + expect(publishedDescResults.records).to.exist; + expect(publishedDescResults.records!.length).to.equal(3); + expect(publishedDescResults.records.map(r => r.id)).to.eql([...publishedItems].reverse()); + }); }); describe('from: did', () => { From d4bf741f70fad137e8af6b76013c79be8b4f4482 Mon Sep 17 00:00:00 2001 From: Timothy Shamilov Date: Wed, 29 Nov 2023 20:32:49 -0500 Subject: [PATCH 09/11] fix tsconfig errors (#306) * cleanup tsc settings * add base tsconfig to DRY up tsconfigs. build sourcemaps for all. * DRY up remaining repeated fields * strict false for web5spec * Minor fix to remove trailing commas from JSON --------- Co-authored-by: Frank Hinek --- .web5-spec/tsconfig.json | 17 ++--------------- package-lock.json | 18 +++++++++--------- packages/agent/src/dwn-manager.ts | 3 ++- packages/agent/tsconfig.cjs.json | 2 +- packages/agent/tsconfig.json | 20 +++----------------- packages/api/tsconfig.cjs.json | 2 +- packages/api/tsconfig.json | 21 ++++----------------- packages/common/tsconfig.json | 20 +++----------------- packages/credentials/tsconfig.cjs.json | 2 +- packages/credentials/tsconfig.json | 20 +++----------------- packages/crypto/tsconfig.cjs.json | 2 +- packages/crypto/tsconfig.json | 17 ++--------------- packages/dids/tsconfig.cjs.json | 2 +- packages/dids/tsconfig.json | 18 ++++-------------- packages/identity-agent/tsconfig.cjs.json | 2 +- packages/identity-agent/tsconfig.json | 18 ++---------------- packages/proxy-agent/tsconfig.cjs.json | 2 +- packages/proxy-agent/tsconfig.json | 17 ++--------------- packages/user-agent/tsconfig.cjs.json | 2 +- packages/user-agent/tsconfig.json | 20 +++----------------- tsconfig.json | 20 ++++++++++++++++++++ 21 files changed, 67 insertions(+), 178 deletions(-) create mode 100644 tsconfig.json diff --git a/.web5-spec/tsconfig.json b/.web5-spec/tsconfig.json index 46b6bcd42..c5fdbea07 100644 --- a/.web5-spec/tsconfig.json +++ b/.web5-spec/tsconfig.json @@ -1,22 +1,9 @@ { + "extends": "../tsconfig.json", "compilerOptions": { - // "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "NodeNext", - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", "outDir": "dist", - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true, - "resolveJsonModule": true + "strict": false }, "include": [ "main.ts", diff --git a/package-lock.json b/package-lock.json index 1070b6998..7aa262e3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1118,9 +1118,9 @@ } }, "node_modules/@types/dns-packet": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.3.tgz", - "integrity": "sha512-T7YsGU31kUqAeN5SzfJxapsTAVCXucSb4QsnB7wcbFBpdm7Y77r/5SBrUpdT3Ng22pgxJtLljtQtBvkP5BiSqg==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.4.tgz", + "integrity": "sha512-R0ORTvCCeujG+upKfV4JlvozKLdQWlpsducXGd1L6ezBChwpjSj9K84F+KoMDsZQ9RhOLTR1hnNrwJHWagY24g==", "dev": true, "dependencies": { "@types/node": "*" @@ -1187,9 +1187,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "version": "20.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz", + "integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -3419,9 +3419,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.596", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.596.tgz", - "integrity": "sha512-zW3zbZ40Icb2BCWjm47nxwcFGYlIgdXkAx85XDO7cyky9J4QQfq8t0W19/TLZqq3JPQXtlv8BPIGmfa9Jb4scg==", + "version": "1.4.597", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.597.tgz", + "integrity": "sha512-0XOQNqHhg2YgRVRUrS4M4vWjFCFIP2ETXcXe/0KIQBjXE9Cpy+tgzzYfuq6HGai3hWq0YywtG+5XK8fyG08EjA==", "dev": true, "peer": true }, diff --git a/packages/agent/src/dwn-manager.ts b/packages/agent/src/dwn-manager.ts index 33699674a..d5888c11c 100644 --- a/packages/agent/src/dwn-manager.ts +++ b/packages/agent/src/dwn-manager.ts @@ -1,3 +1,5 @@ +import type { Readable } from 'readable-stream'; + import { Signer, GenericMessage, @@ -10,7 +12,6 @@ import { import { Jose } from '@web5/crypto'; import { Convert } from '@web5/common'; import { DidResolver } from '@web5/dids'; -import { Readable } from 'readable-stream'; import { utils as didUtils } from '@web5/dids'; import { diff --git a/packages/agent/tsconfig.cjs.json b/packages/agent/tsconfig.cjs.json index 7a6f9c0c0..966f80c2b 100644 --- a/packages/agent/tsconfig.cjs.json +++ b/packages/agent/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/agent/tsconfig.json b/packages/agent/tsconfig.json index b29e4e7e1..23ed4ccf6 100644 --- a/packages/agent/tsconfig.json +++ b/packages/agent/tsconfig.json @@ -1,25 +1,11 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", - "outDir": "dist/esm", - "sourceMap": true, - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ - "src", + "src" ], "exclude": [ "node_modules" diff --git a/packages/api/tsconfig.cjs.json b/packages/api/tsconfig.cjs.json index 7a6f9c0c0..966f80c2b 100644 --- a/packages/api/tsconfig.cjs.json +++ b/packages/api/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 334d87aa4..0701f4d02 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,25 +1,12 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - // "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "ESNext", - "declaration": true, - "declarationMap": true, + "strict": false, "declarationDir": "dist/types", - "outDir": "dist/esm", - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true, - "resolveJsonModule": true + "outDir": "dist/esm" }, "include": [ - "src", + "src" ], "exclude": [ "node_modules" diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index b29e4e7e1..23ed4ccf6 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -1,25 +1,11 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", - "outDir": "dist/esm", - "sourceMap": true, - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ - "src", + "src" ], "exclude": [ "node_modules" diff --git a/packages/credentials/tsconfig.cjs.json b/packages/credentials/tsconfig.cjs.json index 0384273d6..70f5f4067 100644 --- a/packages/credentials/tsconfig.cjs.json +++ b/packages/credentials/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/credentials/tsconfig.json b/packages/credentials/tsconfig.json index b29e4e7e1..23ed4ccf6 100644 --- a/packages/credentials/tsconfig.json +++ b/packages/credentials/tsconfig.json @@ -1,25 +1,11 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", - "outDir": "dist/esm", - "sourceMap": true, - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ - "src", + "src" ], "exclude": [ "node_modules" diff --git a/packages/crypto/tsconfig.cjs.json b/packages/crypto/tsconfig.cjs.json index 7a6f9c0c0..966f80c2b 100644 --- a/packages/crypto/tsconfig.cjs.json +++ b/packages/crypto/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/crypto/tsconfig.json b/packages/crypto/tsconfig.json index 7fec77c5f..23ed4ccf6 100644 --- a/packages/crypto/tsconfig.json +++ b/packages/crypto/tsconfig.json @@ -1,21 +1,8 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", - "outDir": "dist/esm", - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ "src" diff --git a/packages/dids/tsconfig.cjs.json b/packages/dids/tsconfig.cjs.json index e34b3674f..63f6f61a4 100644 --- a/packages/dids/tsconfig.cjs.json +++ b/packages/dids/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/dids/tsconfig.json b/packages/dids/tsconfig.json index 35564adee..3226cdc66 100644 --- a/packages/dids/tsconfig.json +++ b/packages/dids/tsconfig.json @@ -1,19 +1,9 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "lib": [ - "DOM", - "ES6" - ], - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, + "strict": false, "declarationDir": "dist/types", - "outDir": "dist/esm", - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ "src", @@ -22,4 +12,4 @@ "exclude": [ "node_modules" ] -} +} \ No newline at end of file diff --git a/packages/identity-agent/tsconfig.cjs.json b/packages/identity-agent/tsconfig.cjs.json index 0384273d6..70f5f4067 100644 --- a/packages/identity-agent/tsconfig.cjs.json +++ b/packages/identity-agent/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/identity-agent/tsconfig.json b/packages/identity-agent/tsconfig.json index b29e4e7e1..711a7a208 100644 --- a/packages/identity-agent/tsconfig.json +++ b/packages/identity-agent/tsconfig.json @@ -1,22 +1,8 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", - "outDir": "dist/esm", - "sourceMap": true, - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ "src", diff --git a/packages/proxy-agent/tsconfig.cjs.json b/packages/proxy-agent/tsconfig.cjs.json index 0384273d6..70f5f4067 100644 --- a/packages/proxy-agent/tsconfig.cjs.json +++ b/packages/proxy-agent/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/proxy-agent/tsconfig.json b/packages/proxy-agent/tsconfig.json index fea2cf4f1..711a7a208 100644 --- a/packages/proxy-agent/tsconfig.json +++ b/packages/proxy-agent/tsconfig.json @@ -1,21 +1,8 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", - "outDir": "dist/esm", - "sourceMap": true, - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ "src", diff --git a/packages/user-agent/tsconfig.cjs.json b/packages/user-agent/tsconfig.cjs.json index 0384273d6..70f5f4067 100644 --- a/packages/user-agent/tsconfig.cjs.json +++ b/packages/user-agent/tsconfig.cjs.json @@ -3,7 +3,7 @@ "compilerOptions": { "lib": [ "DOM", - "ES5", + "ES5" ], "target": "ES5", "module": "CommonJS", diff --git a/packages/user-agent/tsconfig.json b/packages/user-agent/tsconfig.json index b29e4e7e1..23ed4ccf6 100644 --- a/packages/user-agent/tsconfig.json +++ b/packages/user-agent/tsconfig.json @@ -1,25 +1,11 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "strict": true, - "lib": [ - "DOM", - "ES6" - ], - "allowJs": true, - "target": "es6", - "module": "ESNext", // Required for enabling JavaScript import assertion support - "declaration": true, - "declarationMap": true, "declarationDir": "dist/types", - "outDir": "dist/esm", - "sourceMap": true, - // `NodeNext` will throw compilation errors if relative import paths are missing file extension - // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js - "moduleResolution": "NodeNext", - "esModuleInterop": true + "outDir": "dist/esm" }, "include": [ - "src", + "src" ], "exclude": [ "node_modules" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..9bd97dd1d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": [ + "DOM", + "ES6" + ], + "allowJs": true, + "strict": true, + "declaration": true, + "declarationMap": true, + "target": "ES6", + "module": "NodeNext", // Required for enabling JavaScript import assertion support + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "esModuleInterop": true, + "resolveJsonModule": true, + "moduleResolution": "NodeNext" + } +} \ No newline at end of file From a23b49a0754fdf5f0d6baca83639808b11880be9 Mon Sep 17 00:00:00 2001 From: Leo Ribeiro Date: Thu, 30 Nov 2023 15:05:54 -0500 Subject: [PATCH 10/11] decouple tbdocs commenter (#323) * decouple tbdocs commenter * trigger comment * Decouple docs actions --- .github/workflows/docs-ci.yml | 53 ++++++++++++++++++++++++++ .github/workflows/tbdocs-commenter.yml | 52 +++++++++++++++++++++++++ .github/workflows/tests-ci.yml | 33 ---------------- 3 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/docs-ci.yml create mode 100644 .github/workflows/tbdocs-commenter.yml diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml new file mode 100644 index 000000000..5ad51c453 --- /dev/null +++ b/.github/workflows/docs-ci.yml @@ -0,0 +1,53 @@ +name: Docs Continuous Integration + +on: + push: + branches: + - main + pull_request: + branches: + - main + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + tbdocs-reporter: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version: 18 + registry-url: https://registry.npmjs.org/ + + - name: Install latest npm + run: npm install -g npm@latest + + - name: Install dependencies + run: npm ci + + - name: Build all workspace packages + run: npm run build + + - name: TBDocs Reporter + id: tbdocs-reporter-protocol + uses: TBD54566975/tbdocs@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + report_changed_scope_only: false + fail_on_error: false + entry_points: | + - file: packages/api/src/index.ts + docsReporter: api-extractor + docsGenerator: typedoc-markdown + + - name: Save Artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: tbdocs-reporter-output + path: ./.tbdocs diff --git a/.github/workflows/tbdocs-commenter.yml b/.github/workflows/tbdocs-commenter.yml new file mode 100644 index 000000000..83d4b5d3a --- /dev/null +++ b/.github/workflows/tbdocs-commenter.yml @@ -0,0 +1,52 @@ +name: TBDocs Commenter + +on: + workflow_run: + workflows: ["Docs Continuous Integration"] + types: + - completed + +jobs: + comment-action: + name: TBDocs PR Comment + runs-on: ubuntu-latest + # runs only if it's triggered from a PR + if: github.event.workflow_run.pull_requests[0].number != null + + steps: + - name: Download TBDocs Report + uses: dawidd6/action-download-artifact@v2 + with: + run_id: ${{ github.event.workflow_run.id }} + name: tbdocs-reporter-output + path: ./.tbdocs + + - name: Add footer to markdown report + run: | + report_file='.tbdocs/docs-report.md' + head_sha="${{ github.event.workflow_run.pull_requests[0].head.sha }}" + short_sha=${head_sha:0:7} + timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + repo="${{ github.repository }}" + sha_link="https://github.com/$repo/commit/$head_sha" + + footer_line="_TBDocs Report Updated at $timestamp [\`$short_sha\`]($sha_link)_" + + echo "---" >> $report_file + echo $footer_line >> $report_file + + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: ${{ github.event.workflow_run.pull_requests[0].number }} + comment-author: "github-actions[bot]" + body-includes: TBDocs Report + + - name: Comment on PR + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.workflow_run.pull_requests[0].number }} + edit-mode: replace + body-path: .tbdocs/docs-report.md diff --git a/.github/workflows/tests-ci.yml b/.github/workflows/tests-ci.yml index 5c6131f08..afa549f88 100644 --- a/.github/workflows/tests-ci.yml +++ b/.github/workflows/tests-ci.yml @@ -128,39 +128,6 @@ jobs: env: TEST_DWN_URL: http://localhost:3000 - tbdocs-reporter: - runs-on: ubuntu-latest - steps: - - name: Checkout source - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - - - name: Set up Node.js - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 - with: - node-version: 18 - registry-url: https://registry.npmjs.org/ - - - name: Install latest npm - run: npm install -g npm@latest - - - name: Install dependencies - run: npm ci - - - name: Build all workspace packages - run: npm run build - - - name: TBDocs Reporter - id: tbdocs-reporter-protocol - uses: TBD54566975/tbdocs@main - with: - token: ${{ secrets.GITHUB_TOKEN }} - report_changed_scope_only: false - fail_on_error: false - entry_points: | - - file: packages/api/src/index.ts - docsReporter: api-extractor - docsGenerator: typedoc-markdown - web5-spec: runs-on: ubuntu-latest steps: From ae975ea0adb053e4c2aafac38aa84378ff231922 Mon Sep 17 00:00:00 2001 From: Timothy Shamilov Date: Fri, 1 Dec 2023 12:43:41 -0500 Subject: [PATCH 11/11] replace karma with web test runner (#316) * poc: web test runner * add ci test * webkit is hanging * naming * shutoff webkit * unbump playwright * remove manual timeouts * use concurrency 1 * wrap up poc * run CI for poc back to back with previous impl * tie up loose ends * let wtr run to compare * add back webkit. apples to apples with karma. * remove karma * remove karma * fix tbdocs (i assume) * update ci config * better naming of scripts * fix node * revert test config changes * fix env var override * cleanup ci config * cleanup explicit deps * add back playwright --- .github/workflows/tests-ci.yml | 4 +- package-lock.json | 3729 ++++++++++++----- packages/agent/build/esbuild-tests.cjs | 16 + packages/agent/karma.conf.cjs | 87 - packages/agent/package.json | 19 +- packages/agent/tests/did-manager.spec.ts | 4 +- packages/agent/tests/dwn-manager.spec.ts | 2 +- packages/agent/tests/identity-manager.spec.ts | 5 +- packages/agent/tests/key-manager.spec.ts | 6 +- packages/agent/tests/sync-manager.spec.ts | 6 +- packages/agent/tests/test-config.ts | 24 - packages/agent/tests/utils/test-config.ts | 5 + packages/agent/web-test-runner.config.cjs | 30 + packages/api/.mocharc.json | 3 +- packages/api/build/esbuild-tests.cjs | 16 + packages/api/karma.conf.cjs | 87 - packages/api/package.json | 19 +- packages/api/tests/dwn-api.spec.ts | 2 +- packages/api/tests/protocol.spec.ts | 2 +- packages/api/tests/record.spec.ts | 2 +- packages/api/tests/test-config.ts | 24 - packages/api/tests/utils/test-config.ts | 5 + packages/api/tests/web5.spec.ts | 4 +- packages/api/web-test-runner.config.cjs | 30 + packages/common/build/esbuild-tests.cjs | 16 + packages/common/karma.conf.cjs | 87 - packages/common/package.json | 19 +- packages/common/web-test-runner.config.cjs | 30 + packages/credentials/build/esbuild-tests.cjs | 16 + packages/credentials/karma.conf.cjs | 87 - packages/credentials/package.json | 21 +- .../credentials/web-test-runner.config.cjs | 30 + packages/crypto/build/esbuild-tests.cjs | 16 + packages/crypto/karma.conf.cjs | 87 - packages/crypto/package.json | 19 +- packages/crypto/web-test-runner.config.cjs | 30 + packages/dids/build/esbuild-tests.cjs | 16 + packages/dids/karma.conf.cjs | 87 - packages/dids/package.json | 19 +- packages/dids/web-test-runner.config.cjs | 30 + .../identity-agent/build/esbuild-tests.cjs | 16 + packages/identity-agent/karma.conf.cjs | 87 - packages/identity-agent/package.json | 19 +- .../identity-agent/web-test-runner.config.cjs | 30 + packages/proxy-agent/build/esbuild-tests.cjs | 16 + packages/proxy-agent/karma.conf.cjs | 87 - packages/proxy-agent/package.json | 19 +- .../proxy-agent/web-test-runner.config.cjs | 30 + packages/user-agent/build/esbuild-tests.cjs | 16 + packages/user-agent/karma.conf.cjs | 87 - packages/user-agent/package.json | 19 +- .../user-agent/web-test-runner.config.cjs | 30 + web5-js.code-workspace | 5 +- 53 files changed, 3142 insertions(+), 2060 deletions(-) create mode 100644 packages/agent/build/esbuild-tests.cjs delete mode 100644 packages/agent/karma.conf.cjs delete mode 100644 packages/agent/tests/test-config.ts create mode 100644 packages/agent/tests/utils/test-config.ts create mode 100644 packages/agent/web-test-runner.config.cjs create mode 100644 packages/api/build/esbuild-tests.cjs delete mode 100644 packages/api/karma.conf.cjs delete mode 100644 packages/api/tests/test-config.ts create mode 100644 packages/api/tests/utils/test-config.ts create mode 100644 packages/api/web-test-runner.config.cjs create mode 100644 packages/common/build/esbuild-tests.cjs delete mode 100644 packages/common/karma.conf.cjs create mode 100644 packages/common/web-test-runner.config.cjs create mode 100644 packages/credentials/build/esbuild-tests.cjs delete mode 100644 packages/credentials/karma.conf.cjs create mode 100644 packages/credentials/web-test-runner.config.cjs create mode 100644 packages/crypto/build/esbuild-tests.cjs delete mode 100644 packages/crypto/karma.conf.cjs create mode 100644 packages/crypto/web-test-runner.config.cjs create mode 100644 packages/dids/build/esbuild-tests.cjs delete mode 100644 packages/dids/karma.conf.cjs create mode 100644 packages/dids/web-test-runner.config.cjs create mode 100644 packages/identity-agent/build/esbuild-tests.cjs delete mode 100644 packages/identity-agent/karma.conf.cjs create mode 100644 packages/identity-agent/web-test-runner.config.cjs create mode 100644 packages/proxy-agent/build/esbuild-tests.cjs delete mode 100644 packages/proxy-agent/karma.conf.cjs create mode 100644 packages/proxy-agent/web-test-runner.config.cjs create mode 100644 packages/user-agent/build/esbuild-tests.cjs delete mode 100644 packages/user-agent/karma.conf.cjs create mode 100644 packages/user-agent/web-test-runner.config.cjs diff --git a/.github/workflows/tests-ci.yml b/.github/workflows/tests-ci.yml index afa549f88..54d26542b 100644 --- a/.github/workflows/tests-ci.yml +++ b/.github/workflows/tests-ci.yml @@ -124,9 +124,7 @@ jobs: run: until curl -sf http://localhost:3000/health; do echo -n .; sleep .1; done - name: Run tests for all packages - run: npm run test:browser --ws -- --color - env: - TEST_DWN_URL: http://localhost:3000 + run: npm run test:browser --ws web5-spec: runs-on: ubuntu-latest diff --git a/package-lock.json b/package-lock.json index 7aa262e3f..a0312fdb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,28 @@ "eslint-plugin-mocha": "10.1.0" } }, + "node_modules/@75lb/deep-merge": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@75lb/deep-merge/-/deep-merge-1.1.1.tgz", + "integrity": "sha512-xvgv6pkMGBA6GwdyJbNAnDmfAIR/DfWhrj9jgWh3TY7gRm3KO46x/GPjRg6wJ0nOepwqrNxFfojebh0Df4h4Tw==", + "dev": true, + "dependencies": { + "lodash.assignwith": "^4.2.0", + "typical": "^7.1.1" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/@75lb/deep-merge/node_modules/typical": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.1.1.tgz", + "integrity": "sha512-T+tKVNs6Wu7IWiAce5BgMd7OZfNYUndHwc5MknN+UHOudi7sGZzuHdCadllRuqJ3fPtgFtIH9+lt9qRv6lmpfA==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -45,21 +67,190 @@ "static-eval": "2.0.2" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "node_modules/@babel/code-frame": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", + "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, "engines": { - "node": ">=0.1.90" + "node": ">=4" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@decentralized-identity/ion-pow-sdk": { "version": "1.0.17", "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-pow-sdk/-/ion-pow-sdk-1.0.17.tgz", @@ -93,9 +284,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", - "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", + "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", "cpu": [ "arm" ], @@ -109,9 +300,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", - "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", + "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", "cpu": [ "arm64" ], @@ -125,9 +316,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", - "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", + "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", "cpu": [ "x64" ], @@ -141,9 +332,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", - "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", + "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", "cpu": [ "arm64" ], @@ -157,9 +348,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", - "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", + "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", "cpu": [ "x64" ], @@ -173,9 +364,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", - "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", + "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", "cpu": [ "arm64" ], @@ -189,9 +380,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", - "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", + "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", "cpu": [ "x64" ], @@ -205,9 +396,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", - "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", + "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", "cpu": [ "arm" ], @@ -221,9 +412,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", - "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", + "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", "cpu": [ "arm64" ], @@ -237,9 +428,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", - "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", + "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", "cpu": [ "ia32" ], @@ -253,9 +444,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", - "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", + "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", "cpu": [ "loong64" ], @@ -269,9 +460,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", - "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", + "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", "cpu": [ "mips64el" ], @@ -285,9 +476,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", - "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", + "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", "cpu": [ "ppc64" ], @@ -301,9 +492,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", - "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", + "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", "cpu": [ "riscv64" ], @@ -317,9 +508,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", - "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", + "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", "cpu": [ "s390x" ], @@ -333,9 +524,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", - "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", + "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", "cpu": [ "x64" ], @@ -349,9 +540,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", - "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", + "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", "cpu": [ "x64" ], @@ -365,9 +556,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", - "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", + "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", "cpu": [ "x64" ], @@ -381,9 +572,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", - "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", + "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", "cpu": [ "x64" ], @@ -397,9 +588,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", - "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", + "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", "cpu": [ "arm64" ], @@ -413,9 +604,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", - "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", + "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", "cpu": [ "ia32" ], @@ -429,9 +620,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", - "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", + "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", "cpu": [ "x64" ], @@ -903,80 +1094,340 @@ } }, "node_modules/@playwright/test": { - "version": "1.36.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz", - "integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.36.2" + "playwright": "1.40.1" }, "bin": { "playwright": "cli.js" }, "engines": { "node": ">=16" - }, - "optionalDependencies": { - "fsevents": "2.3.2" } }, - "node_modules/@scure/base": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", - "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", - "funding": { - "url": "https://paulmillr.com/funding/" + "node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "node_modules/@puppeteer/browsers/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/@puppeteer/browsers/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "type-detect": "4.0.8" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^3.0.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@sinonjs/samsam": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", - "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "node_modules/@rollup/pluginutils": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", + "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", "dev": true, "dependencies": { - "type-detect": "4.0.8" + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.0.tgz", + "integrity": "sha512-keHkkWAe7OtdALGoutLY3utvthkGF+Y17ws9LYT8pxMBYXaCoH/8dXS2uzo6e8+sEhY7y/zi5RFo22Dy2lFpDw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.0.tgz", + "integrity": "sha512-y3Kt+34smKQNWilicPbBz/MXEY7QwDzMFNgwEWeYiOhUt9MTWKjHqe3EVkXwT2fR7izOvHpDWZ0o2IyD9SWX7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.0.tgz", + "integrity": "sha512-oLzzxcUIHltHxOCmaXl+pkIlU+uhSxef5HfntW7RsLh1eHm+vJzjD9Oo4oUKso4YuP4PpbFJNlZjJuOrxo8dPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.0.tgz", + "integrity": "sha512-+ANnmjkcOBaV25n0+M0Bere3roeVAnwlKW65qagtuAfIxXF9YxUneRyAn/RDcIdRa7QrjRNJL3jR7T43ObGe8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.0.tgz", + "integrity": "sha512-tBTSIkjSVUyrekddpkAqKOosnj1Fc0ZY0rJL2bIEWPKqlEQk0paORL9pUIlt7lcGJi3LzMIlUGXvtNi1Z6MOCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.0.tgz", + "integrity": "sha512-Ed8uJI3kM11de9S0j67wAV07JUNhbAqIrDYhQBrQW42jGopgheyk/cdcshgGO4fW5Wjq97COCY/BHogdGvKVNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.0.tgz", + "integrity": "sha512-mZoNQ/qK4D7SSY8v6kEsAAyDgznzLLuSFCA3aBHZTmf3HP/dW4tNLTtWh9+LfyO0Z1aUn+ecpT7IQ3WtIg3ViQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.0.tgz", + "integrity": "sha512-rouezFHpwCqdEXsqAfNsTgSWO0FoZ5hKv5p+TGO5KFhyN/dvYXNMqMolOb8BkyKcPqjYRBeT+Z6V3aM26rPaYg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.0.tgz", + "integrity": "sha512-Bbm+fyn3S6u51urfj3YnqBXg5vI2jQPncRRELaucmhBVyZkbWClQ1fEsRmdnCPpQOQfkpg9gZArvtMVkOMsh1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.0.tgz", + "integrity": "sha512-+MRMcyx9L2kTrTUzYmR61+XVsliMG4odFb5UmqtiT8xOfEicfYAGEuF/D1Pww1+uZkYhBqAHpvju7VN+GnC3ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.0.tgz", + "integrity": "sha512-rxfeE6K6s/Xl2HGeK6cO8SiQq3k/3BYpw7cfhW5Bk2euXNEpuzi2cc7llxx1si1QgwfjNtdRNTGqdBzGlFZGFw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.0.tgz", + "integrity": "sha512-QqmCsydHS172Y0Kc13bkMXvipbJSvzeglBncJG3LsYJSiPlxYACz7MmJBs4A8l1oU+jfhYEIC/+AUSlvjmiX/g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scure/base": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", + "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, "node_modules/@sphereon/pex": { @@ -1087,6 +1538,37 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/babel__code-frame": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.6.tgz", + "integrity": "sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", @@ -1102,21 +1584,61 @@ "@types/chai": "*" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "node_modules/@types/co-body": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", + "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*" + } + }, + "node_modules/@types/command-line-args": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", + "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==", + "dev": true + }, + "node_modules/@types/convert-source-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/convert-source-map/-/convert-source-map-2.0.3.tgz", + "integrity": "sha512-ag0BfJLZf6CQz8VIuRIEYQ5Ggwk/82uvTQf27RcpyDNbY0Vw49LIPqAxk5tqYfrCs9xDaIMvl4aj7ZopnYL8bA==", "dev": true }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "node_modules/@types/cookies": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.10.tgz", + "integrity": "sha512-hmUCjAk2fwZVPPkkPBcI7jGLIR5mg4OVoNMBwU6aVsMm/iNPY7z9/R+x2fSwLt/ZXoGua6C5Zy2k5xOo9jUyhQ==", "dev": true, "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", "@types/node": "*" } }, + "node_modules/@types/debounce": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz", + "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==", + "dev": true + }, "node_modules/@types/dns-packet": { "version": "5.6.4", "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.4.tgz", @@ -1162,18 +1684,109 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-assert": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz", + "integrity": "sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==", + "dev": true + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "dev": true + }, + "node_modules/@types/koa": { + "version": "2.13.12", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.12.tgz", + "integrity": "sha512-vAo1KuDSYWFDB4Cs80CHvfmzSQWeUb909aQib0C0aFx4sw0K9UZFz2m5jaEP+b3X1+yr904iQiruS0hXi31jbw==", + "dev": true, + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, "node_modules/@types/mocha": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", @@ -1195,6 +1808,24 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "node_modules/@types/readable-stream": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.6.tgz", @@ -1205,12 +1836,39 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/@types/sinon": { "version": "10.0.15", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", @@ -1232,10 +1890,29 @@ "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", "dev": true }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.0.tgz", - "integrity": "sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==", + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.0.tgz", + "integrity": "sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", @@ -1591,6 +2268,378 @@ "dev": true, "peer": true }, + "node_modules/@web/browser-logs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.4.0.tgz", + "integrity": "sha512-/EBiDAUCJ2DzZhaFxTPRIznEPeafdLbXShIL6aTu7x73x7ZoxSDv7DGuTsh2rWNMUa4+AKli4UORrpyv6QBOiA==", + "dev": true, + "dependencies": { + "errorstacks": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/config-loader": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.3.1.tgz", + "integrity": "sha512-IYjHXUgSGGNpO3YJQ9foLcazbJlAWDdJGRe9be7aOhon0Nd6Na5JIOJAej7jsMu76fKHr4b4w2LfIdNQ4fJ8pA==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@web/dev-server/-/dev-server-0.4.1.tgz", + "integrity": "sha512-GHeyH8MBZQpODFiHiXAdX4hOVbeDyD/DUermUinh/nexWAZUcXyXa200RItuAL6b25MQ3D/5hKNDypujSvXxiw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.11", + "@types/command-line-args": "^5.0.0", + "@web/config-loader": "^0.3.0", + "@web/dev-server-core": "^0.7.0", + "@web/dev-server-rollup": "^0.6.1", + "camelcase": "^6.2.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^7.0.1", + "debounce": "^1.2.0", + "deepmerge": "^4.2.2", + "ip": "^1.1.5", + "nanocolors": "^0.2.1", + "open": "^8.0.2", + "portfinder": "^1.0.32" + }, + "bin": { + "wds": "dist/bin.js", + "web-dev-server": "dist/bin.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server-core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.0.tgz", + "integrity": "sha512-1FJe6cJ3r0x0ZmxY/FnXVduQD4lKX7QgYhyS6N+VmIpV+tBU4sGRbcrmeoYeY+nlnPa6p2oNuonk3X5ln/W95g==", + "dev": true, + "dependencies": { + "@types/koa": "^2.11.6", + "@types/ws": "^7.4.0", + "@web/parse5-utils": "^2.1.0", + "chokidar": "^3.4.3", + "clone": "^2.1.2", + "es-module-lexer": "^1.0.0", + "get-stream": "^6.0.0", + "is-stream": "^2.0.0", + "isbinaryfile": "^5.0.0", + "koa": "^2.13.0", + "koa-etag": "^4.0.0", + "koa-send": "^5.0.1", + "koa-static": "^5.0.0", + "lru-cache": "^8.0.4", + "mime-types": "^2.1.27", + "parse5": "^6.0.1", + "picomatch": "^2.2.2", + "ws": "^7.4.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server-core/node_modules/isbinaryfile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.0.tgz", + "integrity": "sha512-UDdnyGvMajJUWCkib7Cei/dvyJrrvo4FIrsvSFWdPpXSUorzXrDJ0S+X5Q4ZlasfPjca4yqCNNsjbCeiy8FFeg==", + "dev": true, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@web/dev-server-core/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@web/dev-server-core/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@web/dev-server-rollup": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.6.1.tgz", + "integrity": "sha512-vhtsQ8qu1pBHailOBOYJwZnYDc1Lmx6ZAd2j+y5PD2ck0R1LmVsZ7dZK8hDCpkvpvlu2ndURjL9tbzdcsBRJmg==", + "dev": true, + "dependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@web/dev-server-core": "^0.7.0", + "nanocolors": "^0.2.1", + "parse5": "^6.0.1", + "rollup": "^4.4.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@web/parse5-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz", + "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==", + "dev": true, + "dependencies": { + "@types/parse5": "^6.0.1", + "parse5": "^6.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@web/test-runner/-/test-runner-0.18.0.tgz", + "integrity": "sha512-aAlQrdSqwCie1mxuSK5kM0RYDJZL4Q0Hd5LeXn1on3OtHLtgztL4dZzzNSuAWablR2/Vuve3ChwDDxmYSTqXRg==", + "dev": true, + "dependencies": { + "@web/browser-logs": "^0.4.0", + "@web/config-loader": "^0.3.0", + "@web/dev-server": "^0.4.0", + "@web/test-runner-chrome": "^0.15.0", + "@web/test-runner-commands": "^0.9.0", + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-mocha": "^0.9.0", + "camelcase": "^6.2.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^7.0.1", + "convert-source-map": "^2.0.0", + "diff": "^5.0.0", + "globby": "^11.0.1", + "nanocolors": "^0.2.1", + "portfinder": "^1.0.32", + "source-map": "^0.7.3" + }, + "bin": { + "web-test-runner": "dist/bin.js", + "wtr": "dist/bin.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-chrome": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-chrome/-/test-runner-chrome-0.15.0.tgz", + "integrity": "sha512-ZqkTJGQ57FDz3lWw+9CKfHuTV64S9GzBy5+0siSQulEVPfGiTzpksx9DohtA3BCLXdbEq4OHg40/XIQJomlc9w==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-coverage-v8": "^0.8.0", + "async-mutex": "0.4.0", + "chrome-launcher": "^0.15.0", + "puppeteer-core": "^20.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-commands": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-commands/-/test-runner-commands-0.9.0.tgz", + "integrity": "sha512-zeLI6QdH0jzzJMDV5O42Pd8WLJtYqovgdt0JdytgHc0d1EpzXDsc7NTCJSImboc2NcayIsWAvvGGeRF69SMMYg==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "mkdirp": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-commands/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@web/test-runner-core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-core/-/test-runner-core-0.13.0.tgz", + "integrity": "sha512-mUrETPg9n4dHWEk+D46BU3xVhQf+ljT4cG7FSpmF7AIOsXWgWHoaXp6ReeVcEmM5fmznXec2O/apTb9hpGrP3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.11", + "@types/babel__code-frame": "^7.0.2", + "@types/co-body": "^6.1.0", + "@types/convert-source-map": "^2.0.0", + "@types/debounce": "^1.2.0", + "@types/istanbul-lib-coverage": "^2.0.3", + "@types/istanbul-reports": "^3.0.0", + "@web/browser-logs": "^0.4.0", + "@web/dev-server-core": "^0.7.0", + "chokidar": "^3.4.3", + "cli-cursor": "^3.1.0", + "co-body": "^6.1.0", + "convert-source-map": "^2.0.0", + "debounce": "^1.2.0", + "dependency-graph": "^0.11.0", + "globby": "^11.0.1", + "ip": "^1.1.5", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.0.2", + "log-update": "^4.0.0", + "nanocolors": "^0.2.1", + "nanoid": "^3.1.25", + "open": "^8.0.2", + "picomatch": "^2.2.2", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-core/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@web/test-runner-coverage-v8": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-coverage-v8/-/test-runner-coverage-v8-0.8.0.tgz", + "integrity": "sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "istanbul-lib-coverage": "^3.0.0", + "lru-cache": "^8.0.4", + "picomatch": "^2.2.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-coverage-v8/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@web/test-runner-mocha": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-mocha/-/test-runner-mocha-0.9.0.tgz", + "integrity": "sha512-ZL9F6FXd0DBQvo/h/+mSfzFTSRVxzV9st/AHhpgABtUtV/AIpVE9to6+xdkpu6827kwjezdpuadPfg+PlrBWqQ==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.11.0.tgz", + "integrity": "sha512-s+f43DSAcssKYVOD9SuzueUcctJdHzq1by45gAnSCKa9FQcaTbuYe8CzmxA21g+NcL5+ayo4z+MA9PO4H+PssQ==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-coverage-v8": "^0.8.0", + "playwright": "^1.22.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/@web5/agent": { "resolved": "packages/agent", "link": true @@ -1881,6 +2930,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -1921,6 +2982,33 @@ "node": ">=6" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1964,6 +3052,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -2045,6 +3142,45 @@ "node": "*" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-mutex": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", + "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -2094,13 +3230,13 @@ } ] }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", "dev": true, "engines": { - "node": "^4.5.0 || >= 5.9" + "node": ">=10.0.0" } }, "node_modules/bencode": { @@ -2228,49 +3364,10 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -2487,6 +3584,15 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2500,6 +3606,18 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -2585,6 +3703,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -2715,6 +3846,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -2797,6 +3943,24 @@ "chrome-net": "^3.3.2" } }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/chrome-net": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/chrome-net/-/chrome-net-3.3.4.tgz", @@ -2829,19 +3993,16 @@ "node": ">=6.0" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" } }, "node_modules/cipher-base": { @@ -2870,6 +4031,18 @@ "node": ">=12" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2921,6 +4094,37 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", + "dev": true, + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2939,47 +4143,65 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", "dev": true, - "peer": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "node_modules/command-line-usage": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.1.tgz", + "integrity": "sha512-NCyznE//MuTjwi3y84QVUGEOT+P5oto1e1Pk/jFPVdPPfsG03qpTIl3yw6etR+v73d0lXsoojRpvbru2sqePxQ==", "dev": true, "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "array-back": "^6.2.2", + "chalk-template": "^0.4.0", + "table-layout": "^3.0.0", + "typical": "^7.1.1" }, "engines": { - "node": ">= 0.10.0" + "node": ">=12.20.0" } }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/command-line-usage/node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", "dev": true, - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=12.17" } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/command-line-usage/node_modules/typical": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.1.1.tgz", + "integrity": "sha512-T+tKVNs6Wu7IWiAce5BgMd7OZfNYUndHwc5MknN+UHOudi7sGZzuHdCadllRuqJ3fPtgFtIH9+lt9qRv6lmpfA==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "node_modules/console-browserify": { @@ -2994,6 +4216,38 @@ "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", "dev": true }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -3009,26 +4263,17 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", "dev": true, "dependencies": { - "object-assign": "^4", - "vary": "^1" + "depd": "~2.0.0", + "keygrip": "~1.1.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.8" } }, "node_modules/create-ecdh": { @@ -3145,21 +4390,21 @@ "node": "*" } }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "node_modules/data-uri-to-buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", + "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">= 14" } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3205,11 +4450,26 @@ "node": ">=6" } }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", @@ -3223,6 +4483,15 @@ "node": ">= 0.4" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -3239,6 +4508,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3248,6 +4558,15 @@ "node": ">= 0.8" } }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -3268,10 +4587,10 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", "dev": true }, "node_modules/did-jwt": { @@ -3361,18 +4680,6 @@ "node": ">=6.0.0" } }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, "node_modules/domain-browser": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz", @@ -3461,34 +4768,13 @@ "node": ">= 0.8" } }, - "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" + "once": "^1.4.0" } }, "node_modules/enhanced-resolve": { @@ -3505,17 +4791,17 @@ "node": ">=10.13.0" } }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true - }, "node_modules/err-code": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" }, + "node_modules/errorstacks": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/errorstacks/-/errorstacks-2.4.1.tgz", + "integrity": "sha512-jE4i0SMYevwu/xxAuzhly/KTwtj0xDhbzB6m1xPImxTkw8wcCbgarOQPfCVMi5JKVyW7in29pNJCCJrry3Ynnw==", + "dev": true + }, "node_modules/es-abstract": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", @@ -3572,8 +4858,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/es-set-tostringtag": { "version": "2.0.2", @@ -3605,9 +4890,9 @@ } }, "node_modules/esbuild": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", - "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", + "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", "dev": true, "hasInstallScript": true, "bin": { @@ -3617,28 +4902,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.16.17", - "@esbuild/android-arm64": "0.16.17", - "@esbuild/android-x64": "0.16.17", - "@esbuild/darwin-arm64": "0.16.17", - "@esbuild/darwin-x64": "0.16.17", - "@esbuild/freebsd-arm64": "0.16.17", - "@esbuild/freebsd-x64": "0.16.17", - "@esbuild/linux-arm": "0.16.17", - "@esbuild/linux-arm64": "0.16.17", - "@esbuild/linux-ia32": "0.16.17", - "@esbuild/linux-loong64": "0.16.17", - "@esbuild/linux-mips64el": "0.16.17", - "@esbuild/linux-ppc64": "0.16.17", - "@esbuild/linux-riscv64": "0.16.17", - "@esbuild/linux-s390x": "0.16.17", - "@esbuild/linux-x64": "0.16.17", - "@esbuild/netbsd-x64": "0.16.17", - "@esbuild/openbsd-x64": "0.16.17", - "@esbuild/sunos-x64": "0.16.17", - "@esbuild/win32-arm64": "0.16.17", - "@esbuild/win32-ia32": "0.16.17", - "@esbuild/win32-x64": "0.16.17" + "@esbuild/android-arm": "0.19.8", + "@esbuild/android-arm64": "0.19.8", + "@esbuild/android-x64": "0.19.8", + "@esbuild/darwin-arm64": "0.19.8", + "@esbuild/darwin-x64": "0.19.8", + "@esbuild/freebsd-arm64": "0.19.8", + "@esbuild/freebsd-x64": "0.19.8", + "@esbuild/linux-arm": "0.19.8", + "@esbuild/linux-arm64": "0.19.8", + "@esbuild/linux-ia32": "0.19.8", + "@esbuild/linux-loong64": "0.19.8", + "@esbuild/linux-mips64el": "0.19.8", + "@esbuild/linux-ppc64": "0.19.8", + "@esbuild/linux-riscv64": "0.19.8", + "@esbuild/linux-s390x": "0.19.8", + "@esbuild/linux-x64": "0.19.8", + "@esbuild/netbsd-x64": "0.19.8", + "@esbuild/openbsd-x64": "0.19.8", + "@esbuild/sunos-x64": "0.19.8", + "@esbuild/win32-arm64": "0.19.8", + "@esbuild/win32-ia32": "0.19.8", + "@esbuild/win32-x64": "0.19.8" } }, "node_modules/escalade": { @@ -3957,6 +5242,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3965,6 +5256,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -3973,12 +5273,6 @@ "node": ">=6" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -3997,17 +5291,52 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -4056,6 +5385,15 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4080,49 +5418,16 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", "dev": true, "dependencies": { - "ee-first": "1.1.1" + "array-back": "^3.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">=4.0.0" } }, "node_modules/find-up": { @@ -4204,26 +5509,6 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4245,6 +5530,15 @@ "node": ">=8.0.0" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -4344,6 +5638,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -4359,6 +5665,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-uri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", + "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.0", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -4738,6 +6059,44 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4763,18 +6122,17 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dev": true, "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=8.0.0" + "node": ">= 14" } }, "node_modules/https-browserify": { @@ -4783,6 +6141,19 @@ "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", "dev": true }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4848,6 +6219,15 @@ "node": ">=0.8.19" } }, + "node_modules/inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4898,6 +6278,12 @@ "node": ">= 0.4" } }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, "node_modules/ipfs-unixfs": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.1.0.tgz", @@ -5090,6 +6476,21 @@ "node": ">=4" } }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -5101,18 +6502,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -5199,6 +6588,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "node_modules/is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", @@ -5293,6 +6688,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -5376,18 +6783,6 @@ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, "node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -5587,6 +6982,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -5684,372 +7085,173 @@ "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==" }, - "node_modules/karma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", - "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", - "dev": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true, - "peerDependencies": { - "chai": "*", - "karma": ">=0.10.9" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", - "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", - "dev": true, - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-chrome-launcher/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/karma-esbuild": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-esbuild/-/karma-esbuild-2.2.5.tgz", - "integrity": "sha512-+NiRmZhUm/MqOsL1cAu8+RmiOMvIxWDaeYDLBB5upxHF9Hh3Og8YH43EAmDan40pxt2FKDcOjupgqIe4Tx2szQ==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.1", - "source-map": "0.6.1" - }, - "peerDependencies": { - "esbuild": ">=0.8.45" - } - }, - "node_modules/karma-firefox-launcher": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", - "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", - "dev": true, - "dependencies": { - "is-wsl": "^2.2.0", - "which": "^2.0.1" - } - }, - "node_modules/karma-firefox-launcher/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/karma-firefox-launcher/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "tsscmp": "1.0.6" }, "engines": { - "node": ">= 8" + "node": ">= 0.6" } }, - "node_modules/karma-mocha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", - "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { - "minimist": "^1.2.3" + "json-buffer": "3.0.1" } }, - "node_modules/karma-mocha-reporter": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", - "integrity": "sha512-Hr6nhkIp0GIJJrvzY8JFeHpQZNseuIakGac4bpw8K1+5F0tLb6l7uvXRa8mt2Z+NVwYgCct4QAfp2R2QP6o00w==", + "node_modules/koa": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.14.2.tgz", + "integrity": "sha512-VFI2bpJaodz6P7x2uyLiX6RLYpZmOJqNmoCst/Yyd7hQlszyPwG/I9CQJ63nOtKSxpt5M7NH67V6nJL2BwCl7g==", "dev": true, "dependencies": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" }, - "peerDependencies": { - "karma": ">=0.13" - } - }, - "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, "engines": { - "node": ">=4" + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" } }, - "node_modules/karma-mocha-reporter/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true }, - "node_modules/karma-mocha-reporter/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "co": "^4.6.0", + "koa-compose": "^4.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/karma-mocha-reporter/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/karma-mocha-reporter/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/karma-mocha-reporter/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" + "node": ">= 10" } }, - "node_modules/karma-mocha-reporter/node_modules/strip-ansi": { + "node_modules/koa-etag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-4.0.0.tgz", + "integrity": "sha512-1cSdezCkBWlyuB9l6c/IFoe1ANCDdPBxkDkRiaIup40xpUub6U/wwRXoKBZw/O5BifX9OlqAjYnDyzM6+l+TAg==", "dev": true, "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" + "etag": "^1.8.1" } }, - "node_modules/karma-mocha-reporter/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/karma-webkit-launcher": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/karma-webkit-launcher/-/karma-webkit-launcher-2.1.0.tgz", - "integrity": "sha512-S5eqhH0DIcuJFi27nC6eBxZ3MTrPnYybPthDU2Q8dfG0yFrXx8FqNDKSbRZsFFvAKJ55QVtYH1bbArd3ddI5Sg==", - "dev": true, - "dependencies": { - "is-ci": "^3.0.1", - "uuid": "^9.0.0" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": true - } - } - }, - "node_modules/karma/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "node": ">= 8" } }, - "node_modules/karma/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/karma/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/koa-send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.6" } }, - "node_modules/karma/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/koa-send/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 0.6" } }, - "node_modules/karma/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "debug": "^3.1.0", + "koa-send": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">= 7.6.0" } }, - "node_modules/karma/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "ms": "^2.1.1" } }, - "node_modules/karma/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/karma/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, "engines": { - "node": ">=10" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" + "node": ">= 0.6" } }, "node_modules/last-one-wins": { @@ -6111,6 +7313,31 @@ "node": ">= 0.8.0" } }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -6141,6 +7368,18 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.assignwith": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -6153,103 +7392,56 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "node": ">=10" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/log-update/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/log-symbols/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/log-update/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8.0" + "node": ">=8" } }, "node_modules/loupe": { @@ -6296,6 +7488,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "dev": true + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -6364,18 +7562,6 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -6397,6 +7583,15 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -6438,6 +7633,12 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -6450,6 +7651,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "node_modules/mocha": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", @@ -6728,6 +7935,12 @@ "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==" }, + "node_modules/nanocolors": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.13.tgz", + "integrity": "sha512-0n3mSAQLPpGLV9ORXT5+C/D4mwew7Ebws69Hx4E2sgz2ZA5+32Q80B9tL8PbL7XHnRDiAxH/pnrUJ9a4fkTNTA==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -6772,6 +7985,15 @@ "dev": true, "peer": true }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/nise": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", @@ -6981,15 +8203,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -7060,6 +8273,44 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -7155,6 +8406,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -7186,6 +8470,12 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7293,6 +8583,12 @@ "node": ">=0.12" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7353,25 +8649,27 @@ } }, "node_modules/playwright": { - "version": "1.36.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.36.2.tgz", - "integrity": "sha512-4Fmlq3KWsl85Bl4InJw1NC21aeQV0iSZuFvTDcy1F8zVmXmgQRe89GxF8zMSRt/KIS+2tUolak7EXVl9aC+JdA==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", "dev": true, - "hasInstallScript": true, "dependencies": { - "playwright-core": "1.36.2" + "playwright-core": "1.40.1" }, "bin": { "playwright": "cli.js" }, "engines": { "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.36.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz", - "integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -7380,6 +8678,29 @@ "node": ">=16" } }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7406,6 +8727,15 @@ "node": ">= 0.6.0" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/progress-events": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.0.tgz", @@ -7466,6 +8796,40 @@ "multiformats": "^12.0.1" } }, + "node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -7486,19 +8850,95 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/puppeteer-core/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "engines": { - "node": ">=0.9" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/qs": { @@ -7544,6 +8984,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/rabin-wasm": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", @@ -7597,15 +9043,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -7717,12 +9154,6 @@ "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -7749,6 +9180,68 @@ "node": ">=4" } }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", + "dev": true, + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -7768,12 +9261,6 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, "node_modules/rimraf": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.0.tgz", @@ -7853,6 +9340,34 @@ "inherits": "^2.0.1" } }, + "node_modules/rollup": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.0.tgz", + "integrity": "sha512-R8i5Her4oO1LiMQ3jKf7MUglYV/mhQ5g5OKeld5CnkmPdIGo79FDDQYqPhq/PCVuTQVuxsWgIbDy9F+zdHn80w==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.6.0", + "@rollup/rollup-android-arm64": "4.6.0", + "@rollup/rollup-darwin-arm64": "4.6.0", + "@rollup/rollup-darwin-x64": "4.6.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.6.0", + "@rollup/rollup-linux-arm64-gnu": "4.6.0", + "@rollup/rollup-linux-arm64-musl": "4.6.0", + "@rollup/rollup-linux-x64-gnu": "4.6.0", + "@rollup/rollup-linux-x64-musl": "4.6.0", + "@rollup/rollup-win32-arm64-msvc": "4.6.0", + "@rollup/rollup-win32-ia32-msvc": "4.6.0", + "@rollup/rollup-win32-x64-msvc": "4.6.0", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8230,46 +9745,67 @@ "node": ">=8" } }, - "node_modules/socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.5.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { - "node": ">=10.2.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", "dev": true, "dependencies": { - "ws": "~8.11.0" + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" } }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", "dev": true, "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" }, "engines": { - "node": ">=10.0.0" + "node": ">= 14" } }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, "node_modules/sodium-javascript": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/sodium-javascript/-/sodium-javascript-0.8.0.tgz", @@ -8475,18 +10011,23 @@ "node": ">= 6" } }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "node_modules/stream-read-all": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/stream-read-all/-/stream-read-all-3.0.1.tgz", + "integrity": "sha512-EWZT9XOceBPlVJRrYcykW8jyRSZYbkb/0ZK36uLEmoWVO5gxBOnntNTseNzfREsqxqdfEGQrD8SXQ3QWbBmq8A==", "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, "engines": { - "node": ">=8.0" + "node": ">=10" + } + }, + "node_modules/streamx": { + "version": "2.15.5", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz", + "integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" } }, "node_modules/string_decoder": { @@ -8703,6 +10244,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/table-layout": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-3.0.2.tgz", + "integrity": "sha512-rpyNZYRw+/C+dYkcQ3Pr+rLxW4CfHpXjPDnG7lYhdRoUcZTUt+KEsX+94RGp/aVp/MQU35JCITv2T/beY4m+hw==", + "dev": true, + "dependencies": { + "@75lb/deep-merge": "^1.1.1", + "array-back": "^6.2.2", + "command-line-args": "^5.2.1", + "command-line-usage": "^7.0.0", + "stream-read-all": "^3.0.1", + "typical": "^7.1.1", + "wordwrapjs": "^5.1.0" + }, + "bin": { + "table-layout": "bin/cli.js" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.1.1.tgz", + "integrity": "sha512-T+tKVNs6Wu7IWiAce5BgMd7OZfNYUndHwc5MknN+UHOudi7sGZzuHdCadllRuqJ3fPtgFtIH9+lt9qRv6lmpfA==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -8713,6 +10293,28 @@ "node": ">=6" } }, + "node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/terser": { "version": "5.24.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", @@ -8817,6 +10419,12 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -8829,53 +10437,6 @@ "node": ">=0.6.0" } }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tmp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8919,6 +10480,15 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -9051,27 +10621,13 @@ "node": ">=14.17" } }, - "node_modules/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], "engines": { - "node": "*" + "node": ">=8" } }, "node_modules/uint8-util": { @@ -9145,6 +10701,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -9259,15 +10849,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -9336,15 +10917,6 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -9518,6 +11090,15 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrapjs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz", + "integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", @@ -9624,27 +11205,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xsalsa20": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/xsalsa20/-/xsalsa20-1.2.0.tgz", @@ -9736,6 +11296,25 @@ "node": ">=8" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/ylru": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.3.2.tgz", + "integrity": "sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -9770,7 +11349,7 @@ "readable-web-to-node-stream": "3.0.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", @@ -9778,23 +11357,17 @@ "@types/readable-stream": "4.0.6", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" }, @@ -10006,7 +11579,7 @@ "readable-web-to-node-stream": "3.0.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", @@ -10016,23 +11589,17 @@ "@types/sinon": "10.0.15", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "sinon": "15.0.2", "source-map-loader": "4.0.1", @@ -10239,29 +11806,23 @@ "multiformats": "11.0.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" }, @@ -10423,30 +11984,24 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "@web5/common": "0.2.1", "@web5/crypto": "0.2.2", "@web5/dids": "0.2.2", "c8": "8.0.1", "chai": "4.3.10", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" }, @@ -10657,7 +12212,7 @@ "@web5/common": "0.2.1" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/ed2curve": "0.2.2", @@ -10667,22 +12222,16 @@ "@types/uuid": "9.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "sinon": "15.0.2", "source-map-loader": "4.0.1", @@ -10853,7 +12402,7 @@ "z32": "1.0.1" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/dns-packet": "^5.6.1", @@ -10862,22 +12411,16 @@ "@types/sinon": "10.0.15", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "sinon": "15.0.2", "source-map-loader": "4.0.1", @@ -11086,31 +12629,25 @@ "@web5/dids": "0.2.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "@web5/api": "0.8.2", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" }, @@ -11317,30 +12854,24 @@ "@web5/dids": "0.2.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" }, @@ -11547,30 +13078,24 @@ "@web5/dids": "0.2.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" }, diff --git a/packages/agent/build/esbuild-tests.cjs b/packages/agent/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/agent/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/agent/karma.conf.cjs b/packages/agent/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/agent/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/agent/package.json b/packages/agent/package.json index 3534fa1f9..b17b76bfb 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -11,11 +11,12 @@ "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/agent#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -76,7 +77,7 @@ "readable-web-to-node-stream": "3.0.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", @@ -84,23 +85,17 @@ "@types/readable-stream": "4.0.6", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" } diff --git a/packages/agent/tests/did-manager.spec.ts b/packages/agent/tests/did-manager.spec.ts index 7d88ad01d..564c134a9 100644 --- a/packages/agent/tests/did-manager.spec.ts +++ b/packages/agent/tests/did-manager.spec.ts @@ -1,7 +1,8 @@ +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import type { PrivateKeyJwk, PublicKeyJwk, Web5Crypto } from '@web5/crypto'; import type { DidKeySet, PortableDid } from '@web5/dids'; -import { expect } from 'chai'; import { DidKeyMethod } from '@web5/dids'; import { Jose, EdDsaAlgorithm } from '@web5/crypto'; @@ -13,6 +14,7 @@ import { DidManager } from '../src/did-manager.js'; import { TestManagedAgent } from '../src/test-managed-agent.js'; import { DidStoreDwn, DidStoreMemory } from '../src/store-managed-did.js'; +chai.use(chaiAsPromised); describe('DidManager', () => { describe('constructor', () => { diff --git a/packages/agent/tests/dwn-manager.spec.ts b/packages/agent/tests/dwn-manager.spec.ts index d695c74d8..cfdab0dce 100644 --- a/packages/agent/tests/dwn-manager.spec.ts +++ b/packages/agent/tests/dwn-manager.spec.ts @@ -17,7 +17,7 @@ import { ProtocolsConfigureMessage, } from '@tbd54566975/dwn-sdk-js'; -import { testDwnUrl } from './test-config.js'; +import { testDwnUrl } from './utils/test-config.js'; import { TestAgent } from './utils/test-agent.js'; import { DwnManager } from '../src/dwn-manager.js'; import { ManagedIdentity } from '../src/identity-manager.js'; diff --git a/packages/agent/tests/identity-manager.spec.ts b/packages/agent/tests/identity-manager.spec.ts index 809e41c80..7babeb4de 100644 --- a/packages/agent/tests/identity-manager.spec.ts +++ b/packages/agent/tests/identity-manager.spec.ts @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import { DidKeyMethod } from '@web5/dids'; import type { ManagedDid } from '../src/did-manager.js'; @@ -10,6 +11,8 @@ import { IdentityManager } from '../src/identity-manager.js'; import { TestManagedAgent } from '../src/test-managed-agent.js'; import { IdentityStoreDwn, IdentityStoreMemory } from '../src/store-managed-identity.js'; +chai.use(chaiAsPromised); + describe('IdentityManager', () => { describe('get agent', () => { it(`returns the 'agent' instance property`, async () => { diff --git a/packages/agent/tests/key-manager.spec.ts b/packages/agent/tests/key-manager.spec.ts index 40fddc221..8f7f9415e 100644 --- a/packages/agent/tests/key-manager.spec.ts +++ b/packages/agent/tests/key-manager.spec.ts @@ -1,8 +1,8 @@ import type { Web5Crypto } from '@web5/crypto'; import sinon from 'sinon'; -import { expect } from 'chai'; - +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import type { KeyManagerOptions } from '../src/key-manager.js'; import type { ManagedKey, ManagedKeyPair } from '../src/types/managed-key.js'; @@ -11,6 +11,8 @@ import { TestAgent } from './utils/test-agent.js'; import { KeyManager } from '../src/key-manager.js'; import { KeyStoreMemory } from '../src/store-managed-key.js'; +chai.use(chaiAsPromised); + describe('KeyManager', () => { let keyManager: KeyManager; let localKms: LocalKms; diff --git a/packages/agent/tests/sync-manager.spec.ts b/packages/agent/tests/sync-manager.spec.ts index 77e2aa50a..e55587a92 100644 --- a/packages/agent/tests/sync-manager.spec.ts +++ b/packages/agent/tests/sync-manager.spec.ts @@ -6,7 +6,7 @@ import { RecordsQueryReply, RecordsWriteMessage } from '@tbd54566975/dwn-sdk-js' import type { ManagedIdentity } from '../src/identity-manager.js'; -import { testDwnUrl } from './test-config.js'; +import { testDwnUrl } from './utils/test-config.js'; import { TestAgent } from './utils/test-agent.js'; import { SyncManagerLevel } from '../src/sync-manager.js'; import { TestManagedAgent } from '../src/test-managed-agent.js'; @@ -268,7 +268,7 @@ describe('SyncManagerLevel', () => { localDwnQueryReply = queryResponse.reply as RecordsQueryReply; expect(localDwnQueryReply.status.code).to.equal(200); // Query was successfully executed. expect(localDwnQueryReply.entries).to.have.length(1); // Record does exist on local DWN. - }).timeout(5000); + }); }); describe('push()', () => { @@ -464,7 +464,7 @@ describe('SyncManagerLevel', () => { remoteDwnQueryReply = queryResponse.reply as RecordsQueryReply; expect(remoteDwnQueryReply.status.code).to.equal(200); // Query was successfully executed. expect(remoteDwnQueryReply.entries).to.have.length(1); // Record does exist on remote DWN. - }).timeout(5000); + }); }); }); }); \ No newline at end of file diff --git a/packages/agent/tests/test-config.ts b/packages/agent/tests/test-config.ts deleted file mode 100644 index 3d613c34d..000000000 --- a/packages/agent/tests/test-config.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare const __karma__: { config?: { testDwnUrl?: string; } }; - -const DEFAULT_TEST_DWN_URL = 'http://localhost:3000'; - -function getTestDwnUrl(): string { - // Check to see if we're running in a Karma browser test environment. - const browserTestEnvironment = typeof __karma__ !== 'undefined' && __karma__?.config?.testDwnUrl !== undefined; - - // Check to see if we're running in a Node environment. - const nodeTestEnvironment = process && process?.env !== undefined; - - // Attempt to use DWN URL defined in Karma config, if running a browser test. - // Otherwise, attempt to use the Node environment variable. - const envTestDwnUrl = (browserTestEnvironment) - ? __karma__.config!.testDwnUrl - : (nodeTestEnvironment) - ? process.env.TEST_DWN_URL - : undefined; - - // If defined, return the test environment DWN URL. Otherwise, return the default. - return envTestDwnUrl || DEFAULT_TEST_DWN_URL; -} - -export const testDwnUrl = getTestDwnUrl(); \ No newline at end of file diff --git a/packages/agent/tests/utils/test-config.ts b/packages/agent/tests/utils/test-config.ts new file mode 100644 index 000000000..11c53fa07 --- /dev/null +++ b/packages/agent/tests/utils/test-config.ts @@ -0,0 +1,5 @@ +const DEFAULT_TEST_DWN_URL = 'http://localhost:3000'; + +const getTestDwnUrl = () => process.env.TEST_DWN_URL || DEFAULT_TEST_DWN_URL; + +export const testDwnUrl = getTestDwnUrl(); \ No newline at end of file diff --git a/packages/agent/web-test-runner.config.cjs b/packages/agent/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/agent/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/api/.mocharc.json b/packages/api/.mocharc.json index 5aa8c5bfe..8303e434d 100644 --- a/packages/api/.mocharc.json +++ b/packages/api/.mocharc.json @@ -1,5 +1,6 @@ { "enable-source-maps": true, "exit": true, - "spec": ["tests/compiled/**/*.spec.js"] + "spec": ["tests/compiled/**/*.spec.js"], + "timeout": 5000 } \ No newline at end of file diff --git a/packages/api/build/esbuild-tests.cjs b/packages/api/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/api/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/api/karma.conf.cjs b/packages/api/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/api/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/api/package.json b/packages/api/package.json index ec1e14fc6..5f6f9caa9 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,11 +12,12 @@ "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/api#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -86,7 +87,7 @@ "readable-web-to-node-stream": "3.0.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", @@ -96,23 +97,17 @@ "@types/sinon": "10.0.15", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "sinon": "15.0.2", "source-map-loader": "4.0.1", diff --git a/packages/api/tests/dwn-api.spec.ts b/packages/api/tests/dwn-api.spec.ts index 54a514cc6..f6a99b234 100644 --- a/packages/api/tests/dwn-api.spec.ts +++ b/packages/api/tests/dwn-api.spec.ts @@ -6,7 +6,7 @@ import { TestManagedAgent } from '@web5/agent'; import { DateSort } from '@tbd54566975/dwn-sdk-js'; import { DwnApi } from '../src/dwn-api.js'; -import { testDwnUrl } from './test-config.js'; +import { testDwnUrl } from './utils/test-config.js'; import { TestUserAgent } from './utils/test-user-agent.js'; import emailProtocolDefinition from './fixtures/protocol-definitions/email.json' assert { type: 'json' }; diff --git a/packages/api/tests/protocol.spec.ts b/packages/api/tests/protocol.spec.ts index ea1cd1061..6f1611a91 100644 --- a/packages/api/tests/protocol.spec.ts +++ b/packages/api/tests/protocol.spec.ts @@ -6,7 +6,7 @@ import chaiAsPromised from 'chai-as-promised'; import { TestManagedAgent } from '@web5/agent'; import { DwnApi } from '../src/dwn-api.js'; -import { testDwnUrl } from './test-config.js'; +import { testDwnUrl } from './utils/test-config.js'; import { TestUserAgent } from './utils/test-user-agent.js'; import emailProtocolDefinition from './fixtures/protocol-definitions/email.json' assert { type: 'json' }; diff --git a/packages/api/tests/record.spec.ts b/packages/api/tests/record.spec.ts index b0bda397a..853375028 100644 --- a/packages/api/tests/record.spec.ts +++ b/packages/api/tests/record.spec.ts @@ -23,7 +23,7 @@ import { import { Record } from '../src/record.js'; import { DwnApi } from '../src/dwn-api.js'; import { dataToBlob } from '../src/utils.js'; -import { testDwnUrl } from './test-config.js'; +import { testDwnUrl } from './utils/test-config.js'; import { TestUserAgent } from './utils/test-user-agent.js'; import { TestDataGenerator } from './utils/test-data-generator.js'; import emailProtocolDefinition from './fixtures/protocol-definitions/email.json' assert { type: 'json' }; diff --git a/packages/api/tests/test-config.ts b/packages/api/tests/test-config.ts deleted file mode 100644 index 3d613c34d..000000000 --- a/packages/api/tests/test-config.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare const __karma__: { config?: { testDwnUrl?: string; } }; - -const DEFAULT_TEST_DWN_URL = 'http://localhost:3000'; - -function getTestDwnUrl(): string { - // Check to see if we're running in a Karma browser test environment. - const browserTestEnvironment = typeof __karma__ !== 'undefined' && __karma__?.config?.testDwnUrl !== undefined; - - // Check to see if we're running in a Node environment. - const nodeTestEnvironment = process && process?.env !== undefined; - - // Attempt to use DWN URL defined in Karma config, if running a browser test. - // Otherwise, attempt to use the Node environment variable. - const envTestDwnUrl = (browserTestEnvironment) - ? __karma__.config!.testDwnUrl - : (nodeTestEnvironment) - ? process.env.TEST_DWN_URL - : undefined; - - // If defined, return the test environment DWN URL. Otherwise, return the default. - return envTestDwnUrl || DEFAULT_TEST_DWN_URL; -} - -export const testDwnUrl = getTestDwnUrl(); \ No newline at end of file diff --git a/packages/api/tests/utils/test-config.ts b/packages/api/tests/utils/test-config.ts new file mode 100644 index 000000000..11c53fa07 --- /dev/null +++ b/packages/api/tests/utils/test-config.ts @@ -0,0 +1,5 @@ +const DEFAULT_TEST_DWN_URL = 'http://localhost:3000'; + +const getTestDwnUrl = () => process.env.TEST_DWN_URL || DEFAULT_TEST_DWN_URL; + +export const testDwnUrl = getTestDwnUrl(); \ No newline at end of file diff --git a/packages/api/tests/web5.spec.ts b/packages/api/tests/web5.spec.ts index eefb62242..85ce074b5 100644 --- a/packages/api/tests/web5.spec.ts +++ b/packages/api/tests/web5.spec.ts @@ -50,7 +50,7 @@ describe('Web5', () => { expect(did).to.exist; expect(web5).to.exist; - }).timeout(5000); + }); }); describe('constructor', () => { @@ -83,6 +83,6 @@ describe('Web5', () => { expect(did).to.exist; expect(web5).to.exist; - }).timeout(10000); + }); }); }); \ No newline at end of file diff --git a/packages/api/web-test-runner.config.cjs b/packages/api/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/api/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/common/build/esbuild-tests.cjs b/packages/common/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/common/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/common/karma.conf.cjs b/packages/common/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/common/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/common/package.json b/packages/common/package.json index 2fe76e88a..8f104b669 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -11,11 +11,12 @@ "build:cjs": "rimraf dist/cjs && node build/cjs-bundle.js && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/common#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -71,29 +72,23 @@ "multiformats": "11.0.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" } diff --git a/packages/common/web-test-runner.config.cjs b/packages/common/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/common/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/credentials/build/esbuild-tests.cjs b/packages/credentials/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/credentials/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/credentials/karma.conf.cjs b/packages/credentials/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/credentials/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/credentials/package.json b/packages/credentials/package.json index 92920a558..a8925d12b 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -10,13 +10,14 @@ "clean": "rimraf dist coverage tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", - "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", + "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/credentials#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -78,30 +79,24 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "@web5/common": "0.2.1", "@web5/crypto": "0.2.2", "@web5/dids": "0.2.2", "c8": "8.0.1", "chai": "4.3.10", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" } diff --git a/packages/credentials/web-test-runner.config.cjs b/packages/credentials/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/credentials/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/crypto/build/esbuild-tests.cjs b/packages/crypto/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/crypto/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/crypto/karma.conf.cjs b/packages/crypto/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/crypto/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 70f35d6a5..b97bcaeff 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -12,11 +12,12 @@ "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/crypto#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -79,7 +80,7 @@ "@web5/common": "0.2.1" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/ed2curve": "0.2.2", @@ -89,22 +90,16 @@ "@types/uuid": "9.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "sinon": "15.0.2", "source-map-loader": "4.0.1", diff --git a/packages/crypto/web-test-runner.config.cjs b/packages/crypto/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/crypto/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/dids/build/esbuild-tests.cjs b/packages/dids/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/dids/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/dids/karma.conf.cjs b/packages/dids/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/dids/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/dids/package.json b/packages/dids/package.json index 9feb2dae8..053f450ab 100644 --- a/packages/dids/package.json +++ b/packages/dids/package.json @@ -12,11 +12,12 @@ "build:cjs": "rimraf dist/cjs && node build/cjs-bundle.js && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/dids#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -86,7 +87,7 @@ "z32": "1.0.1" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/dns-packet": "^5.6.1", @@ -95,22 +96,16 @@ "@types/sinon": "10.0.15", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "sinon": "15.0.2", "source-map-loader": "4.0.1", diff --git a/packages/dids/web-test-runner.config.cjs b/packages/dids/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/dids/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/identity-agent/build/esbuild-tests.cjs b/packages/identity-agent/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/identity-agent/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/identity-agent/karma.conf.cjs b/packages/identity-agent/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/identity-agent/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/identity-agent/package.json b/packages/identity-agent/package.json index 65fe2606c..f21c027b4 100644 --- a/packages/identity-agent/package.json +++ b/packages/identity-agent/package.json @@ -11,11 +11,12 @@ "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/identity-agent#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -73,31 +74,25 @@ "@web5/dids": "0.2.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "@web5/api": "0.8.2", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" } diff --git a/packages/identity-agent/web-test-runner.config.cjs b/packages/identity-agent/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/identity-agent/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/proxy-agent/build/esbuild-tests.cjs b/packages/proxy-agent/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/proxy-agent/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/proxy-agent/karma.conf.cjs b/packages/proxy-agent/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/proxy-agent/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/proxy-agent/package.json b/packages/proxy-agent/package.json index 71c3a6c09..1c9a71fc1 100644 --- a/packages/proxy-agent/package.json +++ b/packages/proxy-agent/package.json @@ -11,11 +11,12 @@ "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/proxy-agent#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -73,30 +74,24 @@ "@web5/dids": "0.2.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" } diff --git a/packages/proxy-agent/web-test-runner.config.cjs b/packages/proxy-agent/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/proxy-agent/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/packages/user-agent/build/esbuild-tests.cjs b/packages/user-agent/build/esbuild-tests.cjs new file mode 100644 index 000000000..1816db8f8 --- /dev/null +++ b/packages/user-agent/build/esbuild-tests.cjs @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const esbuild = require('esbuild'); +const browserConfig = require('./esbuild-browser-config.cjs'); + +esbuild.build({ + ...browserConfig, + format : 'esm', + entryPoints : ['./tests/*.spec.*'], + bundle : true, + minify : false, + outdir : 'tests/compiled', + define : { + ...browserConfig.define, + 'process.env.TEST_DWN_URL': JSON.stringify(process.env.TEST_DWN_URL ?? null), + }, +}); diff --git a/packages/user-agent/karma.conf.cjs b/packages/user-agent/karma.conf.cjs deleted file mode 100644 index 6b7407b2c..000000000 --- a/packages/user-agent/karma.conf.cjs +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -// Karma is what we're using to run our tests in browser environments -// Karma does not support .mjs - -// playwright acts as a safari executable on windows and mac -const playwright = require('@playwright/test'); -const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); - -// use playwright chrome exec path as run target for chromium tests -process.env.CHROME_BIN = playwright.chromium.executablePath(); - -// use playwright webkit exec path as run target for safari tests -process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); - -// use playwright firefox exec path as run target for firefox tests -process.env.FIREFOX_BIN = playwright.firefox.executablePath(); - -module.exports = function (config) { - config.set({ - plugins: [ - 'karma-chrome-launcher', - 'karma-firefox-launcher', - 'karma-webkit-launcher', - 'karma-esbuild', - 'karma-mocha', - 'karma-mocha-reporter', - ], - - // frameworks to use - // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ['mocha'], - - client: { - // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. - mocha: { - timeout: 10000 // 10 seconds - }, - // If an environment variable is defined, override the default test DWN URL. - testDwnUrl: process.env.TEST_DWN_URL, - }, - - // list of files / patterns to load in the browser - files: [ - { pattern: 'tests/**/*.spec.ts', watched: false }, - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor - preprocessors: { - 'tests/**/*.spec.ts': ['esbuild'], - }, - - esbuild: esbuildBrowserConfig, - - // list of files / patterns to exclude - exclude: [], - - // test results reporter to use - // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter - reporters: ['mocha'], - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - concurrency: 1, - - // start these browsers - // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. - browserDisconnectTimeout : 10000, // default 2000 - browserDisconnectTolerance : 1, // default 0 - }); -}; diff --git a/packages/user-agent/package.json b/packages/user-agent/package.json index 1ee5bb9d0..37a142181 100644 --- a/packages/user-agent/package.json +++ b/packages/user-agent/package.json @@ -11,11 +11,12 @@ "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build:tests:browser": "rimraf tests/compiled && node build/esbuild-tests.cjs", "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", "lint": "eslint . --ext .ts --max-warnings 0", "lint:fix": "eslint . --ext .ts --fix", "test:node": "npm run build:tests:node && c8 mocha", - "test:browser": "karma start karma.conf.cjs" + "test:browser": "npm run build:tests:browser && web-test-runner" }, "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/user-agent#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", @@ -73,30 +74,24 @@ "@web5/dids": "0.2.2" }, "devDependencies": { - "@playwright/test": "1.36.2", + "@playwright/test": "1.40.1", "@types/chai": "4.3.6", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.44.2", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "6.4.0", "@typescript-eslint/parser": "6.4.0", + "@web/test-runner": "0.18.0", + "@web/test-runner-playwright": "0.11.0", "c8": "8.0.1", "chai": "4.3.10", "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", + "esbuild": "0.19.8", "eslint": "8.47.0", "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", + "playwright": "1.40.1", "rimraf": "4.4.0", "typescript": "5.1.6" } diff --git a/packages/user-agent/web-test-runner.config.cjs b/packages/user-agent/web-test-runner.config.cjs new file mode 100644 index 000000000..e5adb980d --- /dev/null +++ b/packages/user-agent/web-test-runner.config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const playwrightLauncher = + require('@web/test-runner-playwright').playwrightLauncher; + +/** + * @type {import('@web/test-runner').TestRunnerConfig} + */ +module.exports = { + files : 'tests/compiled/**/*.spec.js', + playwright : true, + nodeResolve : true, + browsers : [ + playwrightLauncher({ + product: 'chromium', + }), + playwrightLauncher({ + product: 'firefox', + }), + playwrightLauncher({ + product: 'webkit', + }), + ], + testsFinishTimeout : 300000, + concurrentBrowsers : 2, + testFramework : { + config: { + timeout: '15000', + }, + }, +}; diff --git a/web5-js.code-workspace b/web5-js.code-workspace index 7a2018698..cdb597cbf 100644 --- a/web5-js.code-workspace +++ b/web5-js.code-workspace @@ -57,7 +57,10 @@ "mode": "auto" } ], - "npm.packageManager": "npm" + "npm.packageManager": "npm", + "editor.codeActionsOnSave": { + "source.fixAll": "always" + } }, "launch": { "version": "0.2.0",