Skip to content

node:crypto: implement stream-backed crypto transform semantics #2479

@andrewtdiz

Description

@andrewtdiz

Summary

Perry supports many node:crypto method-style operations (hash.update().digest(), cipher.update()/final(), sign.update().sign(), etc.), but the returned crypto handles are not real Node stream objects. Node packages can use Hash, Hmac, Cipher, Decipher, Sign, and Verify objects in stream pipelines; those workloads currently lack compatible write/end/pipe/event behavior in Perry.

Affected API families include stream-backed behavior for:

  • crypto.createHash() / crypto.Hash
  • crypto.createHmac() / crypto.Hmac
  • crypto.createCipheriv() / crypto.Cipheriv
  • crypto.createDecipheriv() / crypto.Decipheriv
  • crypto.createSign() / crypto.Sign
  • crypto.createVerify() / crypto.Verify

Evidence

The crypto parity suite documents this as a deliberate follow-up:

  • test-parity/node-suite/crypto/README.md says current coverage includes hash/HMAC/cipher/sign method-style handles, but lists “Full Node stream-backed crypto transform semantics” under known gaps.
  • Existing crypto parity fixtures under test-parity/node-suite/crypto/hash, hmac, cipher, and asymmetric exercise update(), digest(), final(), sign(), and verify(), but do not cover write(), end(), pipe(), data, readable, finish, error, or backpressure.

Runtime/source shape also points to method-only handles:

  • crates/perry-stdlib/src/crypto/hash_handles.rs registers Hash/Hmac handles and dispatches methods such as update, digest, and copy.
  • Hash property reads bind only update, digest, and copy.
  • crates/perry-stdlib/src/crypto/cipher.rs dispatches Cipher/Decipher methods such as update, final, setAAD, getAuthTag, setAuthTag, and setAutoPadding, but there is no stream/EventEmitter method surface.
  • crates/perry-stdlib/src/crypto/ecdh.rs implements Sign/Verify as data-accumulating handles with method dispatch, not writable stream behavior.

Expected Node behavior

Node crypto handles are stream-compatible. For example:

import { Readable } from "node:stream";
import { createHash, createCipheriv, randomBytes } from "node:crypto";

const hash = createHash("sha256");
hash.setEncoding("hex");
Readable.from(["abc", "def"]).pipe(hash).on("data", (digest) => {
  console.log("digest:", digest);
});

const key = Buffer.alloc(32, 1);
const iv = Buffer.alloc(16, 2);
const cipher = createCipheriv("aes-256-cbc", key, iv);
Readable.from([Buffer.from("0123456789abcdef")])
  .pipe(cipher)
  .on("data", (chunk) => console.log(chunk.length));

Hash/Hmac should act as transform streams that consume written chunks and emit the digest at the end. Cipher/Decipher should transform chunks through the stream interface and participate in normal stream lifecycle/backpressure/error behavior. Sign/Verify should accept streamed writes before sign()/verify() or stream finalization.

Current Perry behavior

The crypto objects work for direct method calls, but they are handle values with method dispatch for crypto-specific methods only. Stream-oriented calls such as .pipe(...), .write(...), .end(...), .on("data", ...), or Readable.from(...).pipe(createHash(...)) are not modeled, so packages that compose crypto with Node streams cannot run against Perry's current crypto compatibility layer.

Suggested test surface

Add focused parity cases under test-parity/node-suite/crypto/ for:

  • Hash/Hmac as writable/transform streams: write, end, setEncoding, data, finish.
  • Readable.from([...]).pipe(createHash("sha256")) digest output.
  • Cipher/Decipher streaming round-trips through pipe() with multiple chunks.
  • Error propagation for bad keys/tags during streamed cipher/decipher finalization.
  • Backpressure/lifecycle basics once the stream integration is wired.

Scope / non-goals

This issue is about Node stream semantics for already-supported crypto algorithms and handles. It does not require expanding algorithm coverage, exact OpenSSL error text, or encrypted DER/PEM import/export variants.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions