npm.io
2.0.0 • Published 2 months ago

ec-compression

Licence
—
Version
2.0.0
Deps
0
Size
70 kB
Vulns
0
Weekly
0

ec-compression

Elliptic curve public key point compression and decompression, with no runtime dependencies.

A public key on a short Weierstrass curve is a point (x, y). Because y is determined by x up to its parity, the point can be stored in compressed form (a parity prefix plus the x coordinate) instead of its full uncompressed form. This library converts between the two forms and validates that a given point lies on the curve.

Supported curves: secp256r1 (P-256), secp384r1 (P-384), secp521r1 (P-521), and secp256k1.

Installation

npm install ec-compression

Point encoding

Keys are represented as Uint8Array using the SEC1 encoding:

  • Compressed: 0x02 || x when y is even, 0x03 || x when y is odd.
  • Uncompressed: 0x04 || x || y.

Inputs whose coordinates lost leading zero bytes (as some JWK producers emit for P-521) are accepted; output is always canonical SEC1, with coordinates padded to the full curve width.

Usage

Compress and decompress

The curve may be a CurveParams instance or a curve name (see Curve names).

import { compressPublicKey, decompressPublicKey } from 'ec-compression'

const compressed = compressPublicKey(uncompressedKey, 'p256')
const uncompressed = decompressPublicKey(compressed, 'p256')

Both functions throw if the input is not a valid point for the given curve.

Best-effort variants

The *IfPossible variants return the input unchanged instead of throwing when it cannot be converted. This is useful when a key may already be in the desired form.

import {
  compressPublicKeyIfPossible,
  decompressPublicKeyIfPossible,
} from 'ec-compression'

const compressed = compressPublicKeyIfPossible(key, 'p256')
Validation
import {
  isValidCompressedPublicKeyFormat,
  isValidDecompressedPublicKeyFormat,
  isValidPublicKeyFormat,
} from 'ec-compression'

isValidCompressedPublicKeyFormat(key, 'p256')
isValidDecompressedPublicKeyFormat(key, 'p256')
isValidPublicKeyFormat(key, 'p256') // either form

Each check verifies the encoding prefix and length, and that the decoded point lies on the curve.

Working with points directly

AffinePoint exposes the underlying conversion.

import { AffinePoint, Secp256r1 } from 'ec-compression'

const point = AffinePoint.fromCompressedPoint(compressedKey, Secp256r1)

point.x // bigint
point.y // bigint
point.compressedForm // Uint8Array
point.decompressedForm // Uint8Array
point.isValidPoint(Secp256r1) // boolean

const other = new AffinePoint(xBigintOrBytes, yBigintOrBytes, Secp256r1)
AffinePoint.fromDecompressedPoint(uncompressedKey, Secp256r1)

API

Compression functions
  • compressPublicKey(uncompressed: Uint8Array, curve: CurveParams | string): Uint8Array
  • decompressPublicKey(compressed: Uint8Array, curve: CurveParams | string): Uint8Array
  • compressPublicKeyIfPossible(key: Uint8Array, curve: CurveParams | string): Uint8Array
  • decompressPublicKeyIfPossible(key: Uint8Array, curve: CurveParams | string): Uint8Array
Validation functions
  • isValidCompressedPublicKeyFormat(key: Uint8Array, curve: CurveParams | string): boolean
  • isValidDecompressedPublicKeyFormat(key: Uint8Array, curve: CurveParams | string): boolean
  • isValidPublicKeyFormat(key: Uint8Array, curve: CurveParams | string): boolean
AffinePoint
  • new AffinePoint(x: Uint8Array | bigint, y: Uint8Array | bigint, curve: CurveParams | string)
  • AffinePoint.fromCompressedPoint(form: Uint8Array | bigint, curve: CurveParams | string): AffinePoint
  • AffinePoint.fromDecompressedPoint(form: Uint8Array | bigint, curve: CurveParams | string): AffinePoint
  • point.x / point.y — coordinates as bigint
  • point.xBytes / point.yBytes — coordinates as full-width Uint8Array
  • point.compressedForm / point.decompressedForm — encoded key as Uint8Array
  • point.isValidPoint(curve: CurveParams | string): boolean
Curves
  • Secp256r1, Secp384r1, Secp521r1, Secp256k1 — CurveParams instances.
  • getCurveParamsByName(name: string): CurveParams | undefined
  • resolveCurveParams(curve: CurveParams | string): CurveParams — like getCurveParamsByName, but accepts an instance and throws on unknown names.
  • CurveParams — class holding the curve parameters p, a, b, pointBitLength, and names.
Curve names

Name lookups are case-insensitive. The following aliases are recognised:

Curve Aliases
Secp256r1 secp256r1, p256, p-256
Secp384r1 secp384r1, p384, p-384
Secp521r1 secp521r1, p521, p-521
Secp256k1 secp256k1, k256, k-256
Conversion helpers
  • bigintToBytes(value: bigint, length?: number): Uint8Array — big-endian, zero-padded to length when given; throws on negative input.
  • bytesToBigint(bytes: Uint8Array): bigint — big-endian; throws on empty input.

Development

pnpm install
pnpm test
pnpm lint
pnpm build

The number of randomized round-trip cases per curve can be set with the TESTS_PER_CURVE environment variable (default 100).

License

MIT