diff --git a/packages/crypto-aws-kms/src/ecdsa.ts b/packages/crypto-aws-kms/src/ecdsa.ts index 15fe7803b..29ce68a24 100644 --- a/packages/crypto-aws-kms/src/ecdsa.ts +++ b/packages/crypto-aws-kms/src/ecdsa.ts @@ -157,6 +157,11 @@ export class EcdsaAlgorithm implements * Hashing the data first ensures that the input to the signing operation is within this limit, * regardless of the original data size. * + * Note: The signature returned is normalized to low-S to prevent signature malleability. This + * ensures that the signature can be verified by other libraries that enforce strict verification. + * More information on signature malleability can be found + * {@link @web5/crypto#Secp256k1.adjustSignatureToLowS | here}. + * * @example * ```ts * const ecdsa = new EcdsaAlgorithm({ crypto, kmsClient }); @@ -214,7 +219,10 @@ export class EcdsaAlgorithm implements const derSignature = response.Signature; // Convert the DER encoded signature to a compact R+S signature. - const signature = await Secp256k1.convertDerToCompactSignature({ derSignature }); + let signature = await Secp256k1.convertDerToCompactSignature({ derSignature }); + + // Ensure the signature is in low-S, normalized form to prevent signature malleability. + signature = await Secp256k1.adjustSignatureToLowS({ signature }); return signature; } diff --git a/packages/crypto-aws-kms/tests/ecdsa.spec.ts b/packages/crypto-aws-kms/tests/ecdsa.spec.ts index ad4fae24f..11da2495c 100644 --- a/packages/crypto-aws-kms/tests/ecdsa.spec.ts +++ b/packages/crypto-aws-kms/tests/ecdsa.spec.ts @@ -2,11 +2,12 @@ import type { Jwk } from '@web5/crypto'; import sinon from 'sinon'; import { expect } from 'chai'; +import { Convert } from '@web5/common'; import { CreateKeyCommand, DescribeKeyCommand, KMSClient, SignCommand } from '@aws-sdk/client-kms'; import { AwsKmsCrypto } from '../src/api.js'; import { EcdsaAlgorithm } from '../src/ecdsa.js'; -import { mockEcdsaSecp256k1 } from './fixtures/mock-ecdsa-secp256k1.js'; +import { mockEcdsaSecp256k1, mockSignCommandOutput } from './fixtures/mock-ecdsa-secp256k1.js'; describe('EcdsaAlgorithm', () => { let crypto: AwsKmsCrypto; @@ -85,6 +86,29 @@ describe('EcdsaAlgorithm', () => { expect(kmsClientStub.send.calledTwice).to.be.true; }); + it('returns normalized, low-s form signatures', async () => { + // Setup. + const mockHighSSignCommandOutput = { + ...mockSignCommandOutput, + // Return the DER encoded signature from Wycheproof test case 1, which has a high-s value. + signature: Convert.hex('3046022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc9832365022100900e75ad233fcc908509dbff5922647db37c21f4afd3203ae8dc4ae7794b0f87').toUint8Array() + }; + kmsClientStub.send.withArgs(sinon.match.instanceOf(SignCommand)).resolves(mockHighSSignCommandOutput); + kmsClientStub.send.withArgs(sinon.match.instanceOf(DescribeKeyCommand)).resolves(mockEcdsaSecp256k1.getKeySpec.output); + + // Test the method. + const signature = await ecdsa.sign({ + algorithm : 'ES256K', + data : new Uint8Array([0, 1, 2, 3, 4]), + keyUri : 'urn:jwk:U01_M3_A9vMLOWixG-rlfC-_f3LLdurttn7c7d3_upU' + }); + + // Validate the signature returned by EcdsaAlgorithm.sign() has been adjust to low-s form. + expect(signature).to.deep.equal( + Convert.hex('8891914c431baae682defc57fe074c8cb700f790d72e2a51474cca0ee00faa8451e462395b70b51ae6b98d3fadc233ae4db15583e9b75ac32d94a697c71aa426').toUint8Array() + ); + }); + it('throws an error if an unsupported algorithm is specified', async () => { // Setup. const keyUri = 'urn:jwk:U01_M3_A9vMLOWixG-rlfC-_f3LLdurttn7c7d3_upU'; diff --git a/packages/crypto/.c8rc.json b/packages/crypto/.c8rc.json index ab680f663..c44b67aba 100644 --- a/packages/crypto/.c8rc.json +++ b/packages/crypto/.c8rc.json @@ -5,12 +5,12 @@ ".js" ], "include": [ - "tests/compiled/src/**" + "tests/compiled/**/src/**" ], "exclude": [ - "tests/compiled/src/index.js", - "tests/compiled/src/types.js", - "tests/compiled/src/types/**" + "tests/compiled/**/src/index.js", + "tests/compiled/**/src/types.js", + "tests/compiled/**/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/crypto/src/primitives/secp256k1.ts b/packages/crypto/src/primitives/secp256k1.ts index 0b62981bd..add7da40f 100644 --- a/packages/crypto/src/primitives/secp256k1.ts +++ b/packages/crypto/src/primitives/secp256k1.ts @@ -68,6 +68,76 @@ import { computeJwkThumbprint, isEcPrivateJwk, isEcPublicJwk } from '../jose/jwk * ``` */ export class Secp256k1 { + /** + * Adjusts an ECDSA signature to a normalized, low-S form. + * + * @remarks + * All ECDSA signatures, regardless of the curve, consist of two components, `r` and `s`, both of + * which are integers. The curve's order (the total number of points on the curve) is denoted by + * `n`. In a valid ECDSA signature, both `r` and `s` must be in the range [1, n-1]. However, due + * to the mathematical properties of ECDSA, if `(r, s)` is a valid signature, then `(r, n - s)` is + * also a valid signature for the same message and public key. In other words, for every + * signature, there's a "mirror" signature that's equally valid. For these elliptic curves: + * + * - Low S Signature: A signature where the `s` component is in the lower half of the range, + * specifically less than or equal to `n/2`. + * + * - High S Signature: This is where the `s` component is in the upper half of the range, greater + * than `n/2`. + * + * The practical implication is that a third-party can forge a second valid signature for the same + * message by negating the `s` component of the original signature, without any knowledge of the + * private key. This is known as a "signature malleability" attack. + * + * This type of forgery is not a problem in all systems, but it can be an issue in systems that + * rely on digital signature uniqueness to ensure transaction integrity. For example, in Bitcoin, + * transaction malleability is an issue because it allows for the modification of transaction + * identifiers (and potentially, transactions themselves) after they're signed but before they're + * confirmed in a block. By enforcing low `s` values, the Bitcoin network reduces the likelihood of + * this occurring, making the system more secure and predictable. + * + * For this reason, it's common practice to normalize ECDSA signatures to a low-S form. This + * form is considered standard and preferable in some systems and is known as the "normalized" + * form of the signature. + * + * This method takes a signature, and if it's high-S, returns the normalized low-S form. If the + * signature is already low-S, it's returned unmodified. It's important to note that this + * method does not change the validity of the signature but makes it compliant with systems that + * enforce low-S signatures. + * + * @example + * ```ts + * const signature = new Uint8Array([...]); // Your ECDSA signature + * const adjustedSignature = await Secp256k1.adjustSignatureToLowS({ signature }); + * // Now 'adjustedSignature' is in the low-S form. + * ``` + * + * @param params - The parameters for the signature adjustment. + * @param params.signature - The ECDSA signature as a `Uint8Array`. + * + * @returns A Promise that resolves to the adjusted signature in low-S form as a `Uint8Array`. + */ + public static async adjustSignatureToLowS({ signature }: { + signature: Uint8Array; + }): Promise { + // Convert the signature to a `secp256k1.Signature` object. + const signatureObject = secp256k1.Signature.fromCompact(signature); + + if (signatureObject.hasHighS()) { + // Adjust the signature to low-S format if it's high-S. + const adjustedSignatureObject = signatureObject.normalizeS(); + + // Convert the adjusted signature object back to a byte array. + const adjustedSignature = adjustedSignatureObject.toCompactRawBytes(); + + return adjustedSignature; + + } else { + // Return the unmodified signature if it is already in low-S format. + return signature; + } + } + /** * Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format. * diff --git a/packages/crypto/tests/primitives/ed25519.spec.ts b/packages/crypto/tests/primitives/ed25519.spec.ts index 5e257735c..e857d596e 100644 --- a/packages/crypto/tests/primitives/ed25519.spec.ts +++ b/packages/crypto/tests/primitives/ed25519.spec.ts @@ -4,9 +4,9 @@ import chaiAsPromised from 'chai-as-promised'; import type { Jwk, JwkParamsOkpPrivate } from '../../src/jose/jwk.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 CryptoEd25519SignTestVector from '../../../../test-vectors/crypto_ed25519/sign.json' assert { type: 'json' }; import ed25519ComputePublicKey from '../fixtures/test-vectors/ed25519/compute-public-key.json' assert { type: 'json' }; +import CryptoEd25519VerifyTestVector from '../../../../test-vectors/crypto_ed25519/verify.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' }; @@ -315,17 +315,24 @@ describe('Ed25519', () => { expect(signature).to.be.instanceOf(Uint8Array); }); - for (const vector of ed25519Sign.vectors) { - it(vector.description, async () => { - const signature = await Ed25519.sign({ - key : vector.input.key as Jwk, - data : Convert.hex(vector.input.data).toUint8Array() - }); - - const signatureHex = Convert.uint8Array(signature).toHex(); - expect(signatureHex).to.deep.equal(vector.output); + describe('Web5TestVectorsCryptoEd25519', () => { + it('sign', async () => { + for (const vector of CryptoEd25519SignTestVector.vectors) { + let errorOccurred = false; + try { + const signature = await Ed25519.sign({ + key : vector.input.key as Jwk, + data : Convert.hex(vector.input.data).toUint8Array() + }); + + const signatureHex = Convert.uint8Array(signature).toHex(); + expect(signatureHex).to.deep.equal(vector.output, vector.description); + + } catch { errorOccurred = true; } + expect(errorOccurred).to.equal(vector.errors, `Expected '${vector.description}' to${vector.errors ? ' ' : ' not '}throw an error`); + } }); - } + }); }); describe('validatePublicKey()', () => { @@ -368,70 +375,23 @@ describe('Ed25519', () => { 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({ key: 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({ key: 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({ - key : vector.input.key as Jwk, - signature : Convert.hex(vector.input.signature).toUint8Array(), - data : Convert.hex(vector.input.data).toUint8Array() - }); - expect(isValid).to.equal(vector.output); + describe('Web5TestVectorsCryptoEd25519', () => { + it('verify', async () => { + for (const vector of CryptoEd25519VerifyTestVector.vectors) { + let errorOccurred = false; + try { + const isValid = await Ed25519.verify({ + key : vector.input.key as Jwk, + signature : Convert.hex(vector.input.signature).toUint8Array(), + data : Convert.hex(vector.input.data).toUint8Array() + }); + + expect(isValid).to.equal(vector.output); + + } catch { errorOccurred = true; } + expect(errorOccurred).to.equal(vector.errors, `Expected '${vector.description}' to${vector.errors ? ' ' : ' not '}throw an error`); + } }); - } + }); }); }); \ No newline at end of file diff --git a/packages/crypto/tests/primitives/secp256k1.spec.ts b/packages/crypto/tests/primitives/secp256k1.spec.ts index bede59104..1c86996ae 100644 --- a/packages/crypto/tests/primitives/secp256k1.spec.ts +++ b/packages/crypto/tests/primitives/secp256k1.spec.ts @@ -4,6 +4,8 @@ import chaiAsPromised from 'chai-as-promised'; import type { Jwk, JwkParamsEcPrivate } from '../../src/jose/jwk.js'; +import CryptoEs256kSignTestVector from '../../../../test-vectors/crypto_es256k/sign.json' assert { type: 'json' }; +import CryptoEs256kVerifyTestVector from '../../../../test-vectors/crypto_es256k/verify.json' assert { type: 'json' }; 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' }; @@ -25,6 +27,59 @@ describe('Secp256k1', () => { publicKey = await Secp256k1.computePublicKey({ key: privateKey }); }); + describe('adjustSignatureToLowS()', () => { + 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 }); + + const adjustedSignature = await Secp256k1.adjustSignatureToLowS({ signature }); + + expect(adjustedSignature).to.be.instanceOf(Uint8Array); + expect(adjustedSignature.byteLength).to.equal(64); + }); + + it('returns the low-S form given a high-S signature', async () => { + const signatureHighS = Convert.hex('351757c538d0a13fa9473dabc259be82dba1bd8f44dcba71a7f222655429b470f9f78c954682f4ce451e5f3d353b4c9fcfbb7d702fe9e28bdfe21be648fc618d').toUint8Array(); + + const adjustedSignature = await Secp256k1.adjustSignatureToLowS({ signature: signatureHighS }); + + expect(adjustedSignature).to.not.deep.equal(signatureHighS); + }); + + it('returns the signature unmodified if already in low-S form', async () => { + const signatureLowS = Convert.hex('351757c538d0a13fa9473dabc259be82dba1bd8f44dcba71a7f222655429b4700608736ab97d0b31bae1a0c2cac4b35eeaf35f767f5ebdafdff042a68739dfb4').toUint8Array(); + + const adjustedSignature = await Secp256k1.adjustSignatureToLowS({ signature: signatureLowS }); + + expect(adjustedSignature).to.deep.equal(signatureLowS); + }); + + it('returns signatures that can be verified regardless of low- or high-S form', async () => { + const data = new Uint8Array([51, 52, 53]); + + const publicKey: Jwk = { + kty : 'EC', + crv : 'secp256k1', + x : 'A2ZbCLhod3ltBQ4Mw0zjkcQZ7h7B1FQ3s56ZtWavonQ', + y : 'JBerPwkut8tONfAfcXhNEBERj7jejohqMfbbs2aMMZA', + kid : '9l2x1L-iUvyCy4RuqJdoqe7h0IPnCVXPjTHhVYCuLAc' + }; + + const signatureLowS = Convert.hex('351757c538d0a13fa9473dabc259be82dba1bd8f44dcba71a7f222655429b4700608736ab97d0b31bae1a0c2cac4b35eeaf35f767f5ebdafdff042a68739dfb4').toUint8Array(); + const signatureHighS = Convert.hex('351757c538d0a13fa9473dabc259be82dba1bd8f44dcba71a7f222655429b470f9f78c954682f4ce451e5f3d353b4c9fcfbb7d702fe9e28bdfe21be648fc618d').toUint8Array(); + + // Verify that the returned signature is valid when input in low-S form. + let adjustedSignature = await Secp256k1.adjustSignatureToLowS({ signature: signatureLowS }); + let isValid = await Secp256k1.verify({ key: publicKey, signature: adjustedSignature, data }); + expect(isValid).to.be.true; + + // Verify that the returned signature is valid when input in high-S form. + adjustedSignature = await Secp256k1.adjustSignatureToLowS({ signature: signatureHighS }); + isValid = await Secp256k1.verify({ key: publicKey, signature: adjustedSignature, data }); + expect(isValid).to.be.true; + }); + }); + describe('bytesToPrivateKey()', () => { it('returns a private key in JWK format', async () => { const privateKeyBytes = Convert.hex('740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88').toUint8Array(); @@ -507,6 +562,25 @@ describe('Secp256k1', () => { signature = await Secp256k1.sign({ key, data }); expect(signature).to.be.instanceOf(Uint8Array); }); + + describe('Web5TestVectorsCryptoEs256k', () => { + it('sign', async () => { + for (const vector of CryptoEs256kSignTestVector.vectors) { + let errorOccurred = false; + try { + const signature = await Secp256k1.sign({ + key : vector.input.key as Jwk, + data : Convert.hex(vector.input.data).toUint8Array() + }); + + const signatureHex = Convert.uint8Array(signature).toHex(); + expect(signatureHex).to.deep.equal(vector.output); + + } catch { errorOccurred = true; } + expect(errorOccurred).to.equal(vector.errors, `Expected '${vector.description}' to${vector.errors ? ' ' : ' not '}throw an error`); + } + }); + }); }); describe('validatePrivateKey()', () => { @@ -550,58 +624,23 @@ describe('Secp256k1', () => { 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; + describe('Web5TestVectorsCryptoEs256k', () => { + it('verify', async () => { + for (const vector of CryptoEs256kVerifyTestVector.vectors) { + let errorOccurred = false; + try { + const isValid = await Secp256k1.verify({ + key : vector.input.key as Jwk, + signature : Convert.hex(vector.input.signature).toUint8Array(), + data : Convert.hex(vector.input.data).toUint8Array() + }); - // Generate signature using the private key. - const signature = await Secp256k1.sign({ key: privateKey, data }); + expect(isValid).to.equal(vector.output); - // 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; + } catch { errorOccurred = true; } + expect(errorOccurred).to.equal(vector.errors, `Expected '${vector.description}' to${vector.errors ? ' ' : ' not '}throw an error`); + } + }); }); }); }); \ No newline at end of file diff --git a/test-vectors/crypto_ed25519/README.md b/test-vectors/crypto_ed25519/README.md new file mode 100644 index 000000000..7a16443c2 --- /dev/null +++ b/test-vectors/crypto_ed25519/README.md @@ -0,0 +1,58 @@ +# `Ed25519` Test Vectors + +This directory contains test vectors for the `Ed25519` signature scheme, which is a part of the +Edwards-curve Digital Signature Algorithm (EdDSA) family of signature algorithms as detailed in +[RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032). + +## `sign` + +Sign test vectors are detailed in a [JSON file](./sign.json). It includes cases for testing the +signing operation with the Ed25519 curve. + +### Input + +The `input` for the sign operation is an object with the following properties: + +| Property | Description | +| -------- | -------------------------------------------------------------------- | +| `key` | A JSON Web Key ([JWK][RFC7517]) object representing the private key. | +| `data` | The data to be signed, as a byte array in hexadecimal string format. | + +### Output + +The `output` is a hexadecimal string representing the signature byte array produced by the signing +operation. + +### Reference Implementations + +Reference implementations for the sign operation can be found in the following SDK repositories: + +- TypeScript: [`Ed25519.sign()`](https://github.com/TBD54566975/web5-js/blob/44c38a116dec0b357ca15d807eb513f819341e50/packages/crypto/src/primitives/ed25519.ts#L434-L468) + +## `verify` + +Verify test vectors are outlined in a [JSON file](./verify.json), encompassing both successful and unsuccessful signature verification cases. + +### Input + +The `input` for the verify operation includes: + +| Property | Description | +| ----------- | -------------------------------------------------------------------------------- | +| `key` | An JSON Web Key ([JWK][RFC7517]) object representing the public key. | +| `signature` | The signature to verify, as a byte array in hexadecimal string format. | +| `data` | The original data that was signed, as a byte array in hexadecimal string format. | + +### Output + +The `output` is a boolean value indicating whether the signature verification was successful +(`true`) or not (`false`). + +### Reference Implementations + +Reference implementations for the verify operation can also be found in the following SDK +repositories: + +- TypeScript: [`Ed25519.verify()`](https://github.com/TBD54566975/web5-js/blob/44c38a116dec0b357ca15d807eb513f819341e50/packages/crypto/src/primitives/ed25519.ts#L512-L547) + +[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517 diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/sign.json b/test-vectors/crypto_ed25519/sign.json similarity index 90% rename from packages/crypto/tests/fixtures/test-vectors/ed25519/sign.json rename to test-vectors/crypto_ed25519/sign.json index 89af1f38f..0cb220074 100644 --- a/packages/crypto/tests/fixtures/test-vectors/ed25519/sign.json +++ b/test-vectors/crypto_ed25519/sign.json @@ -13,7 +13,8 @@ "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" } }, - "output": "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" + "output": "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b", + "errors": false }, { "description": "generates the expected signature given the RFC8032 0x4c... key and 72 message", @@ -27,7 +28,8 @@ "x": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw" } }, - "output": "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00" + "output": "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00", + "errors": false }, { "description": "generates the expected signature given the RFC8032 0x00... key and 5a... message", @@ -41,7 +43,8 @@ "x": "d9HY66zRP04vikDijEpjvJzjv7aXFjNLyyijPrE0CGw" } }, - "output": "0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c" + "output": "0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c", + "errors": false }, { "description": "generates the expected signature given the RFC8032 0xf5... key and long message", @@ -55,7 +58,21 @@ "kid": "lZI1vM7tnlYapaF5-cy86ptx0tT_8Av721hhiNB5ti4" } }, - "output": "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03" + "output": "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03", + "errors": false + }, + { + "description": "error when given a public key", + "input": { + "data": "", + "key": { + "crv": "Ed25519", + "kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + }, + "errors": true } ] } \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519/verify.json b/test-vectors/crypto_ed25519/verify.json similarity index 59% rename from packages/crypto/tests/fixtures/test-vectors/ed25519/verify.json rename to test-vectors/crypto_ed25519/verify.json index cb23c7532..bfc026b70 100644 --- a/packages/crypto/tests/fixtures/test-vectors/ed25519/verify.json +++ b/test-vectors/crypto_ed25519/verify.json @@ -1,9 +1,9 @@ { - "description" : "Ed25519 verify test vectors", - "vectors" : [ + "description": "Ed25519 verify test vectors", + "vectors": [ { - "description" : "verifies the signature for the RFC8032 0x9d... key and empty message", - "input" : { + "description": "verifies the signature for the RFC8032 0x9d... key and empty message", + "input": { "data": "", "key": { "crv": "Ed25519", @@ -13,13 +13,14 @@ }, "signature": "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" }, - "output": true + "output": true, + "errors": false }, { - "description" : "verifies the signature for the RFC8032 0x4c... key and 72 message", - "input" : { + "description": "verifies the signature for the RFC8032 0x4c... key and 72 message", + "input": { "data": "72", - "key": { + "key": { "crv": "Ed25519", "kid": "FtIu-VbGrfe_KB6CH7GNwODB72MNxj_ml11dEvO-7kk", "kty": "OKP", @@ -27,13 +28,14 @@ }, "signature": "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00" }, - "output": true + "output": true, + "errors": false }, { - "description" : "verifies the signature for the RFC8032 0x00... key and 5a... message", - "input" : { + "description": "verifies the signature for the RFC8032 0x00... key and 5a... message", + "input": { "data": "5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b", - "key": { + "key": { "crv": "Ed25519", "kid": "M7TyrCUM12xZUUArpFOvdxvSN0CKasiRsxOIlVcyEaA", "kty": "OKP", @@ -41,11 +43,12 @@ }, "signature": "0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c" }, - "output": true + "output": true, + "errors": false }, { - "description" : "verifies the signature for the RFC8032 0xf5... key and long message", - "input" : { + "description": "verifies the signature for the RFC8032 0xf5... key and long message", + "input": { "data": "08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0", "key": { "crv": "Ed25519", @@ -55,7 +58,68 @@ }, "signature": "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03" }, - "output": true + "output": true, + "errors": false + }, + { + "description": "verification fails if the data was tampered with", + "input": { + "data": "0002030405060708", + "key": { + "kty": "OKP", + "crv": "Ed25519", + "x": "XVXPU41VtJuEN0m1WTB-9-AqmIr4shYrsycDu05WmRs", + "kid": "QVq_liaHGqnWD1xzm3VCmZG7ibO_aZSEK7gZ4I9rEok" + }, + "signature": "6a38583c45ffa51c99cf621fd19219fcc80c39bfa64fba884b27ed90ca46bd4122d8c5c6c87b6757787716c37497948204aae42442023765e9c0bc70e3e7a600" + }, + "output": false, + "errors": false + }, + { + "description": "verification fails if the signature was tampered with", + "input": { + "data": "0102030405060708", + "key": { + "kty": "OKP", + "crv": "Ed25519", + "x": "5bXaMJzFcrB8638un6ccrzTQ3Mh-49mPZT9yN10FZJ8", + "kid": "_6WIXMTzaw5V0JTPQCluQF58MREJeBSVLCmG7EVCorE" + }, + "signature": "7b7f4334f3df755dc2085dbc9be69588f4e86289c5be22b860f09ee354e5368724c9d96895d20c1b7cf8b723f0191073e0cf9b7d90c0a88fcfbbcdbe8a2df108" + }, + "output": false, + "errors": false + }, + { + "description": "verification fails if the public key is not associated with the signing key", + "input": { + "data": "0102030405060708", + "key": { + "kty": "OKP", + "crv": "Ed25519", + "x": "Q7SbAMR1c3ZefhGMU1cSsyfVqSQ4JFShScvO4C4WleY", + "kid": "SSMbPCacyDpbL3emWwOY5ESkBpkgtzw4dseWZcFfqjc" + }, + "signature": "50b20a14a64942d3211621c1b8be110f0f5a35b3ff4da123ab2c2d38e98f24548e0727539d0a98cf653b7c4e7732b103ebc5ee0456acf4a601285c6ecedf8e0b" + }, + "output": false, + "errors": false + }, + { + "description": "error when given a private key", + "input": { + "data": "", + "key": { + "crv": "Ed25519", + "d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k", + "kty": "OKP", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + }, + "signature": "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" + }, + "errors": true } ] } \ No newline at end of file diff --git a/test-vectors/crypto_es256k/README.md b/test-vectors/crypto_es256k/README.md new file mode 100644 index 000000000..4c91ed1be --- /dev/null +++ b/test-vectors/crypto_es256k/README.md @@ -0,0 +1,70 @@ +# `ES256K` Test Vectors + +This directory contains test vectors for the secp256k1 with SHA-256 signature scheme, which is a +part of the Elliptic Curve Digital Signature Algorithm (ECDSA) family of signature algorithms as +detailed in the Standards for Efficient Cryptography Group (SECG) publication +[SEC1](https://www.secg.org/sec1-v2.pdf). + +The `ES256K` algorithm identifier is defined in +[RFC8812](https://datatracker.ietf.org/doc/html/rfc8812), which specifies the use of ECDSA with the +secp256k1 curve and the SHA-256 cryptographic hash function. + +> [!IMPORTANT] +> All ECDSA signatures, regardless of the curve, are subject to signature malleability such that +> for every valid signature there is a "mirror" signature that's equally valid for the same message +> and public key. Read more +> [here]() +> about the practical implications and mitigation techniques. + +## `sign` + +Sign test vectors are detailed in a [JSON file](./sign.json). It includes cases for testing the +signing operation with the secp256k1 curve and SHA-256 hash function. + +### Input + +The `input` for the sign operation is an object with the following properties: + +| Property | Description | +| -------- | -------------------------------------------------------------------- | +| `key` | A JSON Web Key ([JWK][RFC7517]) object representing the private key. | +| `data` | The data to be signed, as a byte array in hexadecimal string format. | + +### Output + +The `output` is a hexadecimal string representing the signature byte array produced by the signing +operation. + +### Reference Implementations + +Reference implementations for the sign operation can be found in the following SDK repositories: + +- TypeScript: [`Secp256k1.sign()`](https://github.com/TBD54566975/web5-js/blob/44c38a116dec0b357ca15d807eb513f819341e50/packages/crypto/src/primitives/secp256k1.ts#L547-L595) + +## `verify` + +Verify test vectors are outlined in a [JSON file](./verify.json), encompassing both successful and unsuccessful signature verification cases. + +### Input + +The `input` for the verify operation includes: + +| Property | Description | +| ----------- | -------------------------------------------------------------------------------- | +| `key` | An JSON Web Key ([JWK][RFC7517]) object representing the public key. | +| `signature` | The signature to verify, as a byte array in hexadecimal string format. | +| `data` | The original data that was signed, as a byte array in hexadecimal string format. | + +### Output + +The `output` is a boolean value indicating whether the signature verification was successful +(`true`) or not (`false`). + +### Reference Implementations + +Reference implementations for the verify operation can also be found in the following SDK +repositories: + +- TypeScript: [`Secp256k1.verify()`](https://github.com/TBD54566975/web5-js/blob/44c38a116dec0b357ca15d807eb513f819341e50/packages/crypto/src/primitives/secp256k1.ts#L670-L724) + +[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517 diff --git a/test-vectors/crypto_es256k/sign.json b/test-vectors/crypto_es256k/sign.json new file mode 100644 index 000000000..7ac86c624 --- /dev/null +++ b/test-vectors/crypto_es256k/sign.json @@ -0,0 +1,65 @@ +{ + "description": "ES256K sign test vectors", + "vectors": [ + { + "description": "always generates low-S form signatures", + "input": { + "data": "333435", + "key": { + "crv": "secp256k1", + "d": "lZqISvM7R1S7zBgZ5JjUuOppZuYKXuCbYWBkqgwX88c", + "kid": "JOeO0oJDLMaXibhJBpsHVvskK47qq0k8uaLozxTtNhk", + "kty": "EC", + "x": "npaD6WyM4AZIxwPmieND_gdnYuROitnyDfskXwpv-J0", + "y": "y5_uOFRRNOCWAJPD-Ly1ENJd908lWJ0-0KGnTwxWzNM" + } + }, + "output": "95b9c99642a5765b4f5f4648671dbad2ad107f7507f1e538eb4ad365caf76a4d321db3e3682f5124d37c597b6f2b489171c6b7d90e82f67a87a7e4d8783f4d63", + "errors": false + }, + { + "description": "error when given a public key", + "input": { + "data": "", + "key": { + "crv": "secp256k1", + "kid": "JOeO0oJDLMaXibhJBpsHVvskK47qq0k8uaLozxTtNhk", + "kty": "EC", + "x": "npaD6WyM4AZIxwPmieND_gdnYuROitnyDfskXwpv-J0", + "y": "y5_uOFRRNOCWAJPD-Ly1ENJd908lWJ0-0KGnTwxWzNM" + } + }, + "errors": true + }, + { + "description": "error with invalid private key == 0 (not on curve)", + "input": { + "data": "0000000000000000000000000000000000000000000000000000000000000001", + "key": { + "kty": "EC", + "crv": "secp256k1", + "d": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "x": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "y": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "kid": "bBw8BkYm7Aeo-e8Xzbc76irs8TtXtPxvCIZiUuuU-PY" + } + }, + "errors": true + }, + { + "description": "error with invalid private key >= G (not on curve)", + "input": { + "data": "0000000000000000000000000000000000000000000000000000000000000001", + "key": { + "kty": "EC", + "crv": "secp256k1", + "d": "__________________________________________8", + "x": "__________________________________________8", + "y": "__________________________________________8", + "kid": "W-Oix7HogMrpbP0tj98DA8McTn2MLUEo9LYlbfk3-lA" + } + }, + "errors": true + } + ] +} \ No newline at end of file diff --git a/test-vectors/crypto_es256k/verify.json b/test-vectors/crypto_es256k/verify.json new file mode 100644 index 000000000..6bafb8824 --- /dev/null +++ b/test-vectors/crypto_es256k/verify.json @@ -0,0 +1,132 @@ +{ + "description": "ES256K verify test vectors", + "vectors": [ + { + "description": "verifies the signature from Wycheproof test case 3", + "input": { + "data": "313233343030", + "key": { + "crv": "secp256k1", + "kid": "i8L_MOOCkkDoHKY1a8cXtZ2BSTLWzD29eiCUiR555ts", + "kty": "EC", + "x": "uDj_ROW8F3vyEYnQdmCC_J2EMiaIf8l2A3EQC37iCm8", + "y": "8MnXW_unsxpryhl0SW7rVt41cHGVXYPEsbraoLIYMuk" + }, + "signature": "813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc98323656ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba" + }, + "output": true, + "errors": false + }, + { + "description": "verifies low-S form signatures", + "input": { + "data": "333435", + "key": { + "crv": "secp256k1", + "kid": "9l2x1L-iUvyCy4RuqJdoqe7h0IPnCVXPjTHhVYCuLAc", + "kty": "EC", + "x": "A2ZbCLhod3ltBQ4Mw0zjkcQZ7h7B1FQ3s56ZtWavonQ", + "y": "JBerPwkut8tONfAfcXhNEBERj7jejohqMfbbs2aMMZA" + }, + "signature": "351757c538d0a13fa9473dabc259be82dba1bd8f44dcba71a7f222655429b4700608736ab97d0b31bae1a0c2cac4b35eeaf35f767f5ebdafdff042a68739dfb4" + }, + "output": true, + "errors": false + }, + { + "description": "verifies high-S form signatures", + "input": { + "data": "333435", + "key": { + "crv": "secp256k1", + "kid": "9l2x1L-iUvyCy4RuqJdoqe7h0IPnCVXPjTHhVYCuLAc", + "kty": "EC", + "x": "A2ZbCLhod3ltBQ4Mw0zjkcQZ7h7B1FQ3s56ZtWavonQ", + "y": "JBerPwkut8tONfAfcXhNEBERj7jejohqMfbbs2aMMZA" + }, + "signature": "351757c538d0a13fa9473dabc259be82dba1bd8f44dcba71a7f222655429b470f9f78c954682f4ce451e5f3d353b4c9fcfbb7d702fe9e28bdfe21be648fc618d" + }, + "output": true, + "errors": false + }, + { + "description": "verification fails if the data was tampered with", + "input": { + "data": "0002030405060708", + "key": { + "kty": "EC", + "crv": "secp256k1", + "x": "fmCdLkmSfkAW0sKwrDegDsCcIKVUC_S6RBSGqrqNDzw", + "y": "qG4iddPl2ddQS4QRGloxXJDMwqT6cwHEFr9o0_aXp0s", + "kid": "yF4nEQmfgPjaZSudWp55n0oD486mWw2S0tG6G0Vs9ds" + }, + "signature": "efcd2eb0df4137bf3993149b8dc0956aea9858c83c270ea0fcbf6fb8da77573d1e49798da017740b5e948a099cdc2abcda43421bc872c4ae1370de4661f9d879" + }, + "output": false, + "errors": false + }, + { + "description": "verification fails if the signature was tampered with", + "input": { + "data": "0102030405060708", + "key": { + "kty": "EC", + "crv": "secp256k1", + "x": "oFYWfw35gaUsuUKXTEfq9i0Rg8bJI8aautX7uUy-BlI", + "y": "CXnzACqBqCFvP5zEmolhFiuQJ7MFY6yiMDHKxiLv8SM", + "kid": "AkWUHqaYZCNM06UeEGCDKwYJD1fXNFqB4JOzmqFDTCQ" + }, + "signature": "3ce28829b29db2fce5ab3fbc1dd6822dc29787e806573ded683003a80e4bca85221b4c5e39c43117bbadb63dccd3649223729c5b5847f74935cfd6d810584de6" + }, + "output": false, + "errors": false + }, + { + "description": "verification fails if the public key is not associated with the signing key", + "input": { + "data": "0102030405060708", + "key": { + "kty": "EC", + "crv": "secp256k1", + "x": "rZumJRfoU39x5arLh3g6geDFnikLRpCsTneNOvWeAXw", + "y": "ACJk2iPQZinwFT6MeGEwu29jFxuvqjlEXA7jbaSYNx8", + "kid": "J15CEGRafTv4gR3jr3zaWqsO5txEzcxICDBhJO-bkRw" + }, + "signature": "006b365af98e60c9dd89884391bc2d41aa078586a899e7fff07104683a3195ec323589cf5050a4d485a2e6c281561f378dd0a9663954236b5d20fd64519bcbe7" + }, + "output": false, + "errors": false + }, + { + "description": "error when given a private key", + "input": { + "data": "", + "key": { + "crv": "secp256k1", + "d": "lZqISvM7R1S7zBgZ5JjUuOppZuYKXuCbYWBkqgwX88c", + "kid": "JOeO0oJDLMaXibhJBpsHVvskK47qq0k8uaLozxTtNhk", + "kty": "EC", + "x": "npaD6WyM4AZIxwPmieND_gdnYuROitnyDfskXwpv-J0", + "y": "y5_uOFRRNOCWAJPD-Ly1ENJd908lWJ0-0KGnTwxWzNM" + }, + "signature": "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" + }, + "errors": true + }, + { + "description": "error with invalid public key X > P (not on curve)", + "input": { + "data": "", + "key": { + "crv": "secp256k1", + "kid": "zrExdhAYVSioQSqh8uTqzc1GEpEKGBax6Q7J8UdBt0s", + "kty": "EC", + "x": "_____________________________________v___DA", + "y": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE" + }, + "signature": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" + }, + "errors": true + } + ] +} \ No newline at end of file