Liking cljdoc? Tell your friends :D

Changelog

0.9.4 (2026-09-26)

Fixes from a review of signet (docs/review-devin-20260926.md), at the seams between the vault, shared keys, key records and the two backends. Each fix has a regression test (test/signet/seams_test.clj) shown to fail before it.

Fixed

  • unbox rejected valid boxes when the vault held a shared key. vault/handles lists shared keys, and unbox tried them as recipients: in about half of all runs (depending on hash-set order) a correct box came back {:valid? false :error :malformed}. Box and unbox now consider identity keys only (Ed25519, X25519). vault/x25519-dh and vault/public-key throw :signet.vault/wrong-algorithm for a shared key, and so does box with a shared-key handle as sender. New vault/identity-key?. Since 0.8.0.
  • (chain/close sealed-token content) threw a raw ArrayStoreException; it now throws ::sealed, like (close token).
  • Closing an exported token left its key alive. export-token copies the proof's seed out; close of that sendable token destroyed nothing, so the vault's copy could still extend the "sealed" chain. close now destroys the vault's copy of that key (in every vault) and wipes the seed array.
  • The backends disagreed on invalid Ed25519 public keys. The JCA backend now validates points as libsodium does (on the curve, not small order, in the prime-order subgroup); before, it threw ArithmeticException for y = 1 and accepted other invalid points. Both throw :signet.impl/invalid-public-key, and low-order X25519 is :signet.impl/low-order-point on both. A parity check covers 455 inputs.
  • key/kid->public-key accepted malformed kids (a 5-byte key, say) that lookup refused; one parser now, throwing ::malformed-kid.
  • sign/verify accepts a vault handle.
  • Typed errors for wrong key types at public entry points: session/initiator/responder (::bad-key-type, e.g. a secp256k1 peer), shared/shared-key! and vault/register-public-key!.
  • key/register! of a secp256k1 private-only key silently stored nothing; it throws ::no-kid.
  • JCA hkdf-sha-256 refuses lengths beyond 8160 bytes (RFC 5869); :signet.impl/bad-length.
  • Sessions pass the vault's own errors (::destroyed-key, ::unknown-vault) through, instead of reporting them as ::authentication-failed.
  • A session entry is recorded only after the provider accepted it.
  • The verify CLI (signet.cli.verify): a flag without a value is a usage error.

Changed

  • unbox's :from accepts key records and handles, not only kids.
  • bb release-check requires a dated ## X.Y.Z (YYYY-MM-DD) heading.
  • uuidv7 0.7.3 (CLI fixes only; the library is unchanged).
  • Docstrings: chain/close (the sendable form), extend-chain (the old proof stays usable), create-third-party-block (impure with a handle).

Deferred to 0.10.0

  • chain/verify errors as keywords and a uniform :blocks shape (a breaking change for consumers that read the strings).
  • verify-edn rejecting a wrong :type early.

0.9.3 (2026-09-26)

Changed

  • nacljc 0.3.2 (fixes from a review of nacljc):
    • X-Wing operations wipe 64 KiB of stack instead of 16 KiB, and X-Wing encapsulation now wipes at all.
    • with-secret keeps the body's exception when destroying the secret also fails.
  • Every function states its purity and errors, public and private, in the fixed wording of the naming convention: Pure., Impure: <what it reads or writes>, and Throws ex-info {:type ::x} when …. Functions that take a key record or a handle say which one reads the vault. Docstrings only, except as follows.
  • encoding/hex->bytes: the odd-length error now carries {:type :signet.encoding/bad-hex}.
  • Private helpers renamed to the convention (! only for writes): ssh/bad-key! is throw-bad-key, shared/meta! is checked-meta.

0.9.2 (2026-09-26)

Changed

  • cedn 1.6.1 (fixes from a review of canonical-edn):
    • #inst values, and so signed envelopes' timestamps, no longer depend on the JVM's default locale. Before, under a locale with non-Latin digits (for example ar-EG), cedn wrote #inst digits in that script, so signatures made on such a machine verified nowhere else.
    • java.sql.Date/Time in a payload now fail with cedn's :cedn/unsupported-type instead of a raw exception.
    • Canonical bytes are otherwise unchanged for signet (JVM and bb): the other byte fix in 1.6.1 is ClojureScript-only.

0.9.1 (2026-09-25)

Changed

  • nacljc 0.3.1: after every operation that reads a secret (all vault operations under the :sodium provider), the stack the C code used is wiped with sodium_stackzero, as libsodium's memory docs recommend. About 0.2 µs per operation. No API change.

0.9.0 (2026-09-25)

Sessions on vault handles (docs/08-sessions-on-handles-plan.md).

Changed

  • signet.session keeps its secrets in the vault. The chaining key, handshake key, transport keys and ephemerals are vault session entries, in the vault of the local static key; a state holds only handles and public values, so printing it shows nothing secret. Under the :sodium provider they stay in libsodium's guarded memory.
  • initiator / responder take a vault handle as the local static key, and the peer as a public key or a kid (::unknown-peer if it cannot be resolved). Key records still work, deprecated; their session secrets go to the :vault option's vault (default :default).
  • Each successful write-message! / read-message! destroys the secrets only the consumed state needed; a failed or racing call destroys what it created, so a forged message leaves nothing behind.
  • State keys renamed: :local-static, :local-ephemeral.
  • initiator / responder draw a random session id (documented as impure: a read of the random generator).
  • nacljc 0.3.0 (secret-split, secret HKDF salt).

Added

  • session/close!: destroys every secret of a session from any of its states (even the first); any later use throws ::session-closed.
  • session/with-conclave: closes the session when the block exits, also on an exception. A clj-kondo lint-as export ships with the jar.
  • vault/session-entry-count: how many session secrets a vault holds, to monitor sessions that were never closed.
  • Internal: vault session entries (never on the public side, never in handles, not exportable: ::not-exportable), impl/split-material.
  • The :sodium provider moves byte material it adopts into guarded memory, so it only ever holds secrets.
  • ssh/import-keypair!: imports an OpenSSH Ed25519 private key file into a vault and returns a handle; the seed must give the file's public key (:public-key-mismatch otherwise).

Deprecated (decision 17)

Secret-carrying key records are deprecated in favour of vault handles. Nothing is removed; the functions below carry ^{:deprecated "0.9.0"} (clj-kondo warns) and a docstring pointer to the replacement:

  • key/signing-keypair, key/signing-keypair! → vault/generate-signing-key! / vault/import-signing-key! (secp256k1 has no vault equivalent yet)
  • key/encryption-keypair, key/encryption-keypair! → vault/generate-encryption-key! / vault/import-encryption-key!
  • key/signing-private-key, key/encryption-private-key, key/private-key → a vault handle
  • key/raw-shared-secret → signet.shared/shared-key!
  • ssh/read-private-key, ssh/load-keypair, ssh/load-keypair! → ssh/import-keypair!

Key-record inputs to sign, box, chain and session keep working. Public-key records and functions are not deprecated.

Fixed

  • signet.session is now wire-compatible with Noise. The handshake mixed the prologue into the transcript hash after the static public keys; the Noise spec (§5.3) mixes it first. Handshake messages therefore matched no other Noise_KK_25519_ChaChaPoly_SHA256 implementation. Breaking: a 0.9.0 peer cannot complete a handshake with a 0.8.0 or earlier peer (the first message fails authentication). Transport messages were not affected.

Added

  • Known-answer tests against the published Noise_KK vectors of cacophony and snow (test/signet/noise_vectors_test.clj), on every backend.

0.8.0 (2026-09-24)

Secrets by reference: code holds vault handles, never secret bytes (docs/07-secret-handles-design.md, decisions 7–16).

Added

  • signet.vault:
    • KeyHandle records naming a key and its vault; a vault registry with :default.
    • Two sides indexed by one kid: lookup and register-public-key!, handle and handles.
    • Keys born in the vault: generate-signing-key!, generate-encryption-key!.
    • import-signing-key! and import-encryption-key! (they wipe the caller's array); export-secret (requires {:i-understand :exposes-secret}); destroy!.
    • sign, public-key, algorithm.
    • The default identity: default-signing-key, set-default-signing-key!, ensure-default-signing-key! (race-safe).
  • Providers: :memory (heap, inside the vault, lent copies wiped) and :sodium (signet.vault.sodium, on nacljc 0.2.0 secrets in libsodium guarded memory; derived secrets stay there). default-provider picks :sodium on the libsodium backend.
  • Handles everywhere: sign/sign and sign-edn take handles. box takes a handle sender; unbox takes a handle, a set of handles, or a vault id. chain/extend takes a handle root.
  • Chains: an open token's :proof is a vault handle; chain/export-token (sendable form, needs the acknowledgement), chain/import-token! (refuses a mismatched proof), chain/discard!; close destroys the proof.
  • signet.shared:
    • shared-key!: both parties derive the same key and kid with no exchange.
    • seal and open: directional, key-committing, with a per-message salt.
    • mac and verify-mac?: directional.

Changed (breaking)

  • The default identity is the vault's. Removed: key/set-default-signing-keypair!, key/set-default-encryption-keypair!, key/default-signing-keypair, key/default-encryption-keypair, key/clear-defaults!, key/ensure-default-signing-keypair!. sign/sign-edn! and key-less chain/extend use vault/ensure-default-signing-key! / vault/default-signing-key.
  • An open chain token's :proof is a handle, not the seed. Use export-token to send one.
  • nacljc 0.2.0.

Tooling

  • bb check-not-released, run by test:jar, refuses to install a version already on Clojars. It prevents a local build shadowing a published jar in ~/.m2.

0.7.1 (2026-09-23)

Dependency update only; no API or behaviour change.

  • cedn 1.6.0 (was 1.5.2). It only renames functions (cedn/check, cedn.error/throw-*, with the old names deprecated), so the canonical bytes, and therefore signatures, are unchanged. signet uses none of the renamed functions.
  • uuidv7 0.7.2 (was 0.7.1). Its generator step is now a pure function; the UUID format and distribution are unchanged.

These two should have been released before signet 0.7.0; 0.7.1 catches up.

0.7.0 (2026-09-23)

The first release on Clojars (com.github.franks42/signet). Earlier versions were git tags only. There are breaking changes from 0.6.0; see "Changed" below and the README's "Compatibility".

Added

  • libsodium backend (signet.impl.sodium, through nacljc 0.1.0), selected with -Dsignet.backend=sodium or SIGNET_BACKEND=sodium. It is byte-identical to the JCA backend (a parity check runs in CI), and the full suite also passes on babashka.
  • box v2 (signet.encryption): self-describing EDN boxes with directional keys (a box cannot be reflected), a per-message HKDF salt (safe random nonces at any volume), optional kid slots and an authenticated :aad slot. unbox never throws on malformed input. Design: docs/06-box-v2-design.md.
  • Registering twins: key/signing-keypair!, key/encryption-keypair!, ssh/load-keypair!. Also key/ensure-default-signing-keypair! and sign/sign-edn!.
  • Redacted printing: key records print their kid and :d "<redacted>", never secret bytes.
  • CI on JDK 21 and 25 (JCA), plus the libsodium backend on macOS and Linux, all through bb tasks.

Changed (breaking)

  • Pure by default. Constructors and conversions (signing-keypair, encryption-keypair, signing-public-key, signing-private-key, encryption-public-key, encryption-private-key, public-key, private-key, hex->kid, raw-shared-secret, ssh/load-keypair) no longer register keys. Only functions ending in ! write the key store or the defaults.
  • Registering never sets a default. The first-one-wins rule is gone. Defaults are set with set-default-signing-keypair! or ensure-default-signing-keypair!.
  • sign/sign-edn always takes a key. The key-less arity is now sign/sign-edn!.
  • session/write-message and session/read-message are now write-message! and read-message!, since they consume their state.
  • Session states are single-use. Reusing one throws ::stale-session-state, so a nonce is never reused and a replayed message is refused.
  • Trust vocabulary. verify-edn and chain/verify never throw, count expiry against :valid?, and take :signer or :root for :verified?, and :now for deterministic expiry. The raw signature check is now :signature-valid?.
  • key/lookup and key/kid no longer register anything.
  • dh is for static keys and edh for ephemeral ones; mixing them up throws.
  • Removed key/as-public-key, as-encryption-public-key and as-encryption-private-key (added in 0.7.0 snapshots; use the now-pure public-key, encryption-public-key, encryption-private-key).
  • Dependencies: cedn 1.5.2 (canonical bytes changed for some inputs; see the README), uuidv7 0.7.1 (secure randomness on ClojureScript).

Fixed

  • SSH import is strict.
    • read-public-key and read-private-key refuse anything but an unencrypted Ed25519 key, with ::bad-ssh-key and a :reason.
    • Before, an ssh-rsa line or a passphrase-protected key was parsed into a wrong key without error.
    • The OpenSSH check-ints and the public-key copies are now verified.
    • assert, which can be compiled out, is replaced by ex-info.
  • Typed errors.
    • Every error is ex-info with a namespaced :type: for example ::no-private-key, ::sealed, ::no-default-signing-keypair, ::authentication-failed, ::wrong-message-phase.
    • A session's decryption failure is the same ::authentication-failed on every backend.
    • A local static key without its private part is refused when the session is created.
  • Error data carries no key bytes. Before, a seed passed by mistake could end up in ex-data.
  • Ephemeral keys are never registered, and are wiped after use, along with every DH output.

Naming convention

! means the call writes state that outlives it. Impure reads are documented, not banged, and ! never means "may throw". Docstrings state Impure: …, Throws … and Never throws …. See CLAUDE.md.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close