@enc-protocol/plugin-ecdh-envelope
v0.9.0
Published
EcdhEnvelopeCryptoFn: spec-compliant one-shot ECDH envelope for Personal-enclave notices (group/DM invites) + group-invite handoff. secp256k1 ECDH + HKDF-SHA256 + XChaCha20-Poly1305 (24-byte nonce), sourced from the verified @enc-protocol/core codegen. Im
Readme
@enc-protocol/plugin-ecdh-envelope
The spec-compliant ecdh-envelope implementation (EcdhEnvelopeCryptoFn), per
spec/app/plugins/ecdh-envelope.md — the
complete wire-and-key contract for one-shot sender→recipient confidentiality on
Personal-enclave notice content (group/DM invites), plus the inner handoff
sub-encryption that carries a group's bootstrap root_secret.
Source = codegen
The crypto is generated from the Lean spec (Enc.DSL.Modules.EcdhEnvelope →
@enc-protocol/core/ecdh-envelope.js) and composes only the verified core
primitives — secp256k1 ecdh, HKDF-SHA256 deriveKey, XChaCha20-Poly1305
(24-byte nonce). There is no hand-rolled @noble. This package is a thin
adapter: it re-exports the codegen API and declares the plugin-contract slot.
Primitives (spec §1, fixed)
| | |
|---|---|
| AEAD | XChaCha20-Poly1305 — 24-byte nonce, 16-byte tag |
| KDF | HKDF-SHA-256 — empty salt, info = ASCII domain separator, L = 32 |
| ECDH | secp256k1 — 32-byte x-coordinate, x-only inputs 0x02-extended |
| Domain separators | enc:personal:notice (outer), enc:personal:notice:epoch (handoff) |
API
import {
ecdhEnvelopeSeal, ecdhEnvelopeOpen,
ecdhEnvelopeWrapHandoff, ecdhEnvelopeUnwrapHandoff,
} from '@enc-protocol/plugin-ecdh-envelope'
// Sender seals a notice to the recipient's operating pubkey (hex, x-only):
const envelope = ecdhEnvelopeSeal(senderOpPriv, recipientOpPubHex, payload)
// → { ciphertext, nonce, sender_pub, scheme: 'personal:notice', encrypted: true }
// Recipient opens it under their operating priv (tries each op-key):
const payload = ecdhEnvelopeOpen(myOpPriv, envelope)
// Group invite: wrap the 32-byte root_secret as a handoff entry:
const handoff = ecdhEnvelopeWrapHandoff(committerPriv, recipientOpPubHex, rootSecret)
const secret = ecdhEnvelopeUnwrapHandoff(myOpPriv, myOpPubHex, handoff) // 32 bytes | null*WithNonce variants take an explicit 24-byte nonce for deterministic
(byte-for-byte) output — used by the §9 compliance vectors.
Compliance
test/ecdh-envelope.test.mjs runs the spec's §9 KAT vectors (byte-for-byte
reference envelope + handoff parent/sub distinctness + wrong-sender reject +
wrong-recipient skip) and the §7 rejection rules. Vectors fixture:
test/ecdh-envelope.vectors.json.
Not to be confused with
plugin-envelope-dm— a non-canonical X25519 + ChaCha20-Poly1305(12) demo for DM content; a different construction and wire format, not this spec.plugin-dm-ratchet(ratchet-pair) — the canonical forward-secret DM plugin.
