denoland/deno · error · Error

Private key is not valid for specified curve

Error message

Private key is not valid for specified curve

What it means

setPrivateKey() validates the supplied bytes with op_node_ecdh_validate_private_key and throws this Error when they are not a legal private scalar for the curve: wrong byte length, an all-zero value, or a value at or above the curve order. The polyfill matches Node's behavior of rejecting the import rather than storing a unusable key.

Source

Thrown at ext/node/polyfills/internal/crypto/diffiehellman.ts:1557

      pubbuf[0] = compressedBuf[0] + 4;
    }
    return ecdhEncode(pubbuf, encoding ?? "buffer");
  }

  setPrivateKey(
    privateKey: ArrayBufferView | string,
    encoding?: any,
  ): Buffer | string {
    let privbuf: Buffer;
    if (typeof privateKey === "string") {
      privbuf = Buffer.from(privateKey, encoding);
    } else {
      const parts = getViewParts(privateKey);
      privbuf = Buffer.from(parts.ab, parts.off, parts.len);
    }

    if (!op_node_ecdh_validate_private_key(this.#curve.name, privbuf)) {
      throw new Error("Private key is not valid for specified curve");
    }

    const pubbuf = Buffer.alloc(this.#curve.publicKeySize);
    op_node_ecdh_compute_public_key(this.#curve.name, privbuf, pubbuf);

    this.#privbuf = privbuf;
    this.#pubbuf = pubbuf;

    return pubbuf;
  }

  setPublicKey(
    publicKey: ArrayBufferView | string,
    encoding?: any,
  ): void {
    let pubbuf: Buffer;
    if (typeof publicKey === "string") {
      pubbuf = Buffer.from(publicKey, encoding);

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Match the byte length to the curve: P-256/X25519/secp256k1: 32; P-384: 48; P-521: 66
  2. Decode hex/base64 strings with the explicit encoding: Buffer.from(privHex, 'hex')
  3. Left-pad or right-trim with 0x00 to the exact field size if the key came from a system that drops leading zeros
  4. Confirm you are importing the private scalar, not the public key or a DER wrapper (unwrap DER first)

Example fix

// before
ecdh.setPrivateKey(Buffer.from(privHex)); // utf8 bytes of hex text -> wrong length

// after
ecdh.setPrivateKey(Buffer.from(privHex, 'hex')); // 32 bytes for P-256
Defensive patterns

Strategy: validation

Validate before calling

const PRIV_LEN = { prime256v1: 32, secp384r1: 48, secp521r1: 66, X25519: 32 }[curve]!;
if (privBuf.length !== PRIV_LEN || privBuf.every((b) => b === 0)) {
  throw new RangeError(`private key must be ${PRIV_LEN} non-zero bytes for ${curve}`);
}
ecdh.setPrivateKey(privBuf);

Type guard

const isCurvePrivateScalar = (b: Uint8Array, curve: string): boolean => {
  const sizes: Record<string, number> = { prime256v1: 32, secp384r1: 48, secp521r1: 66, X25519: 32 };
  return b.length === sizes[curve] && b.some((x) => x !== 0);
};

Try / catch

catch (e) { if (e.message === 'Private key is not valid for specified curve') { /* check encoding + length of stored key */ } throw e; }

Prevention

When it happens

Trigger: ecdh.setPrivateKey(buf) where buf has the wrong size for the curve (e.g. 30 or 64 bytes for P-256, which expects 32), is zero, or >= n; passing the public key by mistake; decoding a hex private key string without the 'hex' encoding so it becomes 64 utf8 bytes.

Common situations: Reading a stored hex key with Buffer.from(key) instead of Buffer.from(key, 'hex'); interoperating with systems that pad or strip leading zero bytes inconsistently; copying a secp256k1 key into a P-256 instance (both 32 bytes, but the value can exceed P-256's order).

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/1113fc69b7c19e72. Report an issue: GitHub.