The Secp256r1 class provides a comprehensive suite of utilities for working with the secp256r1 (aka P-256) 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.

The class supports conversions between raw byte formats and JSON Web Key (JWK) formats. It adheres to RFC6979 for ECDSA signing and verification and RFC6090 for ECDH.

Key Features:

  • Key Generation: Generate secp256r1 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 secp256r1 keys.
  • Key Validation: Validate the mathematical correctness of secp256r1 keys.

The methods in this class are asynchronous, returning Promises to accommodate various JavaScript environments, and use Uint8Array for binary data handling.

Example

// Key Generation
const privateKey = await Secp256r1.generateKey();

// Public Key Derivation
const publicKey = await Secp256r1.computePublicKey({ key: privateKey });
console.log(publicKey === await Secp256r1.getPublicKey({ key: privateKey })); // Output: true

// ECDH Shared Secret Computation
const sharedSecret = await Secp256r1.sharedSecret({
privateKeyA: privateKey,
publicKeyB: anotherPublicKey
});

// ECDSA Signing
const signature = await Secp256r1.sign({
key: privateKey,
data: new TextEncoder().encode('Message')
});

// ECDSA Signature Verification
const isValid = await Secp256r1.verify({
key: publicKey,
signature: signature,
data: new TextEncoder().encode('Message')
});

// Key Conversion
const publicKeyBytes = await Secp256r1.publicKeyToBytes({ publicKey });
const privateKeyBytes = await Secp256r1.privateKeyToBytes({ privateKey });
const compressedPublicKey = await Secp256r1.compressPublicKey({ publicKeyBytes });
const uncompressedPublicKey = await Secp256r1.decompressPublicKey({ publicKeyBytes });

// Key Validation
const isPrivateKeyValid = await Secp256r1.validatePrivateKey({ privateKeyBytes });
const isPublicKeyValid = await Secp256r1.validatePublicKey({ publicKeyBytes });

Constructors

Methods

  • Adjusts an ECDSA signature to a normalized, low-S form.

    Parameters

    • params: {
          signature: Uint8Array;
      }

      The parameters for the signature adjustment.

      • signature: Uint8Array

        The ECDSA signature as a Uint8Array.

    Returns Promise<Uint8Array>

    A Promise that resolves to the adjusted signature in low-S form as a Uint8Array.

    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

    const signature = new Uint8Array([...]); // Your ECDSA signature
    const adjustedSignature = await Secp256r1.adjustSignatureToLowS({ signature });
    // Now 'adjustedSignature' is in the low-S form.
  • Converts a raw private key in bytes to its corresponding JSON Web Key (JWK) format.

    Parameters

    • params: {
          privateKeyBytes: Uint8Array;
      }

      The parameters for the private key conversion.

      • privateKeyBytes: Uint8Array

        The raw private key as a Uint8Array.

    Returns Promise<Jwk>

    A Promise that resolves to the private key in JWK format.

    Remarks

    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 point (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 'P-256'.
    • 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

    const privateKeyBytes = new Uint8Array([...]); // Replace with actual private key bytes
    const privateKey = await Secp256r1.bytesToPrivateKey({ privateKeyBytes });
  • Converts a raw public key in bytes to its corresponding JSON Web Key (JWK) format.

    Parameters

    • params: {
          publicKeyBytes: Uint8Array;
      }

      The parameters for the public key conversion.

      • publicKeyBytes: Uint8Array

        The raw public key as a Uint8Array.

    Returns Promise<Jwk>

    A Promise that resolves to the public key in JWK format.

    Remarks

    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 point (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 'P-256'.
    • 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

    const publicKeyBytes = new Uint8Array([...]); // Replace with actual public key bytes
    const publicKey = await Secp256r1.bytesToPublicKey({ publicKeyBytes });
  • Converts a public key to its compressed form.

    Parameters

    • params: {
          publicKeyBytes: Uint8Array;
      }

      The parameters for the public key compression.

      • publicKeyBytes: Uint8Array

        The public key as a Uint8Array.

    Returns Promise<Uint8Array>

    A Promise that resolves to the compressed public key as a Uint8Array.

    Remarks

    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

    const uncompressedPublicKeyBytes = new Uint8Array([...]); // Replace with actual uncompressed public key bytes
    const compressedPublicKey = await Secp256r1.compressPublicKey({
    publicKeyBytes: uncompressedPublicKeyBytes
    });
  • Derives the public key in JWK format from a given private key.

    Parameters

    Returns Promise<Jwk>

    A Promise that resolves to the derived public key in JWK format.

    Remarks

    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 point (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 secp256r1 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

    const privateKey = { ... }; // A Jwk object representing a secp256r1 private key
    const publicKey = await Secp256r1.computePublicKey({ key: privateKey });
  • Converts an ASN.1 DER encoded ECDSA signature to a compact R+S format.

    Parameters

    • params: {
          derSignature: Uint8Array;
      }

      The parameters for the signature conversion.

      • derSignature: Uint8Array

        The signature in ASN.1 DER format as a Uint8Array.

    Returns Promise<Uint8Array>

    A Promise that resolves to the signature in compact R+S format as a Uint8Array.

    Remarks

    This method is used for converting an ECDSA signature from the ASN.1 DER encoding to the more compact R+S format. This conversion is often required when dealing with ECDSA signatures in certain cryptographic standards such as JWS (JSON Web Signature).

    The method decodes the DER-encoded signature, extracts the R and S values, and concatenates them into a single byte array. This process involves handling the ASN.1 structure to correctly parse the R and S values, considering padding and integer encoding specifics of DER.

    Example

    const derSignature = new Uint8Array([...]); // Replace with your DER-encoded signature
    const signature = await Secp256r1.convertDerToCompactSignature({ derSignature });
  • Converts a public key to its uncompressed form.

    Parameters

    • params: {
          publicKeyBytes: Uint8Array;
      }

      The parameters for the public key decompression.

      • publicKeyBytes: Uint8Array

        The public key as a Uint8Array.

    Returns Promise<Uint8Array>

    A Promise that resolves to the uncompressed public key as a Uint8Array.

    Remarks

    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

    const compressedPublicKeyBytes = new Uint8Array([...]); // Replace with actual compressed public key bytes
    const decompressedPublicKey = await Secp256r1.decompressPublicKey({
    publicKeyBytes: compressedPublicKeyBytes
    });
  • Generates a secp256r1 private key in JSON Web Key (JWK) format.

    Returns Promise<Jwk>

    A Promise that resolves to the generated private key in JWK format.

    Remarks

    This method creates a new private key suitable for use with the secp256r1 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 secp256r1, making it compatible with common cryptographic standards and easy to use in various cryptographic processes.

    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 'P-256'.
    • 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 key is returned in a format suitable for direct use in signin and key agreement operations.

    Example

    const privateKey = await Secp256r1.generateKey();
    
  • Returns the elliptic curve point (x and y coordinates) for a given secp256r1 key.

    Parameters

    • params: {
          keyBytes: Uint8Array;
      }

      The parameters for the curve point decoding operation.

      • keyBytes: Uint8Array

        The key for which to get the elliptic curve point. Can be either a private key or a public key. The key should be passed as a Uint8Array.

    Returns Promise<AffinePoint<Uint8Array>>

    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.

    Remarks

    This method extracts the elliptic curve point from a given secp256r1 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

    // For a private key
    const privateKey = new Uint8Array([...]); // A 32-byte private key
    const { x: xFromPrivateKey, y: yFromPrivateKey } = await Secp256r1.getCurvePoint({ keyBytes: privateKey });

    // For a public key
    const publicKey = new Uint8Array([...]); // A 33-byte or 65-byte public key
    const { x: xFromPublicKey, y: yFromPublicKey } = await Secp256r1.getCurvePoint({ keyBytes: publicKey });
  • Retrieves the public key properties from a given private key in JWK format.

    Parameters

    Returns Promise<Jwk>

    A Promise that resolves to the public key in JWK format.

    Remarks

    This method extracts the public key portion from a secp256r1 private key in JWK format. It does so by removing the private key property 'd' and making a shallow copy, effectively yielding the public key. The method sets the 'kid' (key ID) property using the JWK thumbprint if it is not already defined. This approach is used under the assumption that a private key in JWK format always contains the corresponding public key properties.

    Note: This method offers a significant performance advantage, being about 200 times faster than computePublicKey(). However, it does not mathematically validate the private key, nor does it derive the public key from the private key. It simply extracts existing public key properties from the private key object. This makes it suitable for scenarios where speed is critical and the private key's integrity is already assured.

    Example

    const privateKey = { ... }; // A Jwk object representing a secp256r1 private key
    const publicKey = await Secp256r1.getPublicKey({ key: privateKey });
  • Converts a private key from JSON Web Key (JWK) format to a raw byte array (Uint8Array).

    Parameters

    • params: {
          privateKey: Jwk;
      }

      The parameters for the private key conversion.

      • privateKey: Jwk

        The private key in JWK format.

    Returns Promise<Uint8Array>

    A Promise that resolves to the private key as a Uint8Array.

    Remarks

    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

    const privateKey = { ... }; // An X25519 private key in JWK format
    const privateKeyBytes = await Secp256r1.privateKeyToBytes({ privateKey });
  • Converts a public key from JSON Web Key (JWK) format to a raw byte array (Uint8Array).

    Parameters

    • params: {
          publicKey: Jwk;
      }

      The parameters for the public key conversion.

      • publicKey: Jwk

        The public key in JWK format.

    Returns Promise<Uint8Array>

    A Promise that resolves to the public key as a Uint8Array.

    Remarks

    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

    const publicKey = { ... }; // A Jwk public key object
    const publicKeyBytes = await Secp256r1.publicKeyToBytes({ publicKey });
  • Computes an RFC6090-compliant Elliptic Curve Diffie-Hellman (ECDH) shared secret using secp256r1 private and public keys in JSON Web Key (JWK) format.

    Parameters

    • params: {
          privateKeyA: Jwk;
          publicKeyB: Jwk;
      }

      The parameters for the shared secret computation.

      • privateKeyA: Jwk

        The private key in JWK format of one party.

      • publicKeyB: Jwk

        The public key in JWK format of the other party.

    Returns Promise<Uint8Array>

    A Promise that resolves to the computed shared secret as a Uint8Array.

    Remarks

    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 secp256r1, 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

    const privateKeyA = { ... }; // A Jwk private key object for party A
    const publicKeyB = { ... }; // A Jwk public key object for party B
    const sharedSecret = await Secp256r1.sharedSecret({
    privateKeyA,
    publicKeyB
    });
  • Generates an RFC6979-compliant ECDSA signature of given data using a secp256r1 private key.

    Parameters

    • params: SignParams

      The parameters for the signing operation.

    Returns Promise<Uint8Array>

    A Promise that resolves to the signature as a Uint8Array.

    Remarks

    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

    const data = new TextEncoder().encode('Messsage'); // Data to be signed
    const privateKey = { ... }; // A Jwk object representing a secp256r1 private key
    const signature = await Secp256r1.sign({
    key: privateKey,
    data
    });
  • Validates a given private key to ensure its compliance with the secp256r1 curve standards.

    Parameters

    • params: {
          privateKeyBytes: Uint8Array;
      }

      The parameters for the key validation.

      • privateKeyBytes: Uint8Array

        The private key to validate, represented as a Uint8Array.

    Returns Promise<boolean>

    A Promise that resolves to a boolean indicating whether the private key is valid.

    Remarks

    This method checks whether a provided private key is a valid 32-byte number and falls within the range defined by the secp256r1 curve's order. It is essential for ensuring the private key's mathematical correctness in the context of secp256r1-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

    const privateKeyBytes = new Uint8Array([...]); // A 32-byte private key
    const isValid = await Secp256r1.validatePrivateKey({ privateKeyBytes });
    console.log(isValid); // true or false based on the key's validity
  • Validates a given public key to confirm its mathematical correctness on the secp256r1 curve.

    Parameters

    • params: {
          publicKeyBytes: Uint8Array;
      }

      The parameters for the key validation.

      • publicKeyBytes: Uint8Array

        The public key to validate, represented as a Uint8Array.

    Returns Promise<boolean>

    A Promise that resolves to a boolean indicating the public key's validity on the secp256r1 curve.

    Remarks

    This method checks if the provided public key represents a valid point on the secp256r1 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.

    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.

    Example

    const publicKeyBytes = new Uint8Array([...]); // A public key in byte format
    const isValid = await Secp256r1.validatePublicKey({ publicKeyBytes });
    console.log(isValid); // true if the key is valid on the secp256r1 curve, false otherwise
  • Verifies an RFC6979-compliant ECDSA signature against given data and a secp256r1 public key.

    Parameters

    • params: VerifyParams

      The parameters for the signature verification.

    Returns Promise<boolean>

    A Promise that resolves to a boolean indicating whether the signature is valid.

    Remarks

    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

    const data = new TextEncoder().encode('Messsage'); // 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 Secp256r1.verify({
    key: publicKey,
    signature,
    data
    });
    console.log(isSignatureValid); // true if the signature is valid, false otherwise