java.security.KeyPairGenerator / Signature (Java 15+, JEP 339)javax.crypto.KeyAgreement with XDH (Java 11+, JEP 324)java.security.MessageDigestjava.security.SecureRandomcrypto.subtle) — Ed25519 + X25519 now in all browserscrypto.subtle.digestcrypto.getRandomValuesWebCrypto is Promise-based (async). Options:
Recommendation: Start with sync-on-JVM, promise-on-JS. Provide a unified macro/helper that abstracts the difference for common patterns. Consider promesa if a unified API is strongly desired.
signet/
├── src/
│ └── signet/
│ ├── core.cljc ;; Re-exports main API
│ ├── key.cljc ;; Key generation, encoding, decoding
│ ├── sign.cljc ;; Ed25519 signing and verification
│ ├── box.cljc ;; X25519 key agreement + authenticated encryption
│ ├── hash.cljc ;; SHA-256, SHA-512
│ ├── random.cljc ;; Secure random bytes
│ ├── envelope.cljc ;; EDN signed envelope format
│ ├── encoding.cljc ;; hex, base64url, bytes conversions
│ └── impl/
│ ├── jvm.clj ;; JCA implementations
│ └── js.cljs ;; WebCrypto / noble implementations
{:public <bytes>
:secret <bytes>
:algorithm :ed25519}
Pro: Simple, easy to destructure. Con: No behavior, just data.
(defprotocol IKeyPair
(public-key [kp])
(private-key [kp])
(algorithm [kp]))
(defrecord Ed25519KeyPair [public secret])
Pro: Extensible, type-safe. Con: More complex.
;; defrecord with :type field — is a map, has protocol dispatch
(defrecord Ed25519KeyPair [type public secret kid])
;; Construct with type tag
(->Ed25519KeyPair :signet/ed25519-keypair pub-bytes sec-bytes kid-str)
;; After cedn serialization, becomes a plain map with :type preserved
;; {:type :signet/ed25519-keypair :public #bytes "..." :secret #bytes "..." :kid "..."}
;; Reconstruct via multimethod dispatching on :type
(defmulti from-map (fn [m] (or (:type m) (type m))))
(defmethod from-map :signet/ed25519-keypair [m]
(map->Ed25519KeyPair m))
;; Use derive to route the record's class to the same defmethod
(derive Ed25519KeyPair :signet/ed25519-keypair)
;; Now from-map works identically for plain maps AND existing records
Pro: Protocol dispatch, cedn-compatible, self-describing in EDN, idempotent round-trip.
The :type keyword is the single source of truth for identity — derive bridges
the class hierarchy to the keyword hierarchy so one defmethod handles both.
Recommendation: Option C — records with :type convention. Gets the benefits of
all three approaches: simple maps (EDN-native), protocol dispatch (extensible),
and self-describing type tags (serialization-friendly).
{:type :signet/signed
:payload <any-edn-value>
:signer {:kid "base64url-public-key"
:alg :ed25519}
:request-id #uuid "01966..." ;; UUIDv7
:signature #bytes "hex-of-signature"}
{:payload value :signer {:kid kid :alg alg} :request-id (uuidv7)}(cedn/canonical-bytes envelope)(hash/sha256 canonical-bytes) (optional — Ed25519 hashes internally)(sign/sign secret-key hash-or-canonical-bytes)(assoc envelope :type :signet/signed :signature sig-bytes){:type :signet/encrypted
:sender {:kid "base64url-sender-public-key"}
:recipient {:kid "base64url-recipient-public-key"}
:nonce #bytes "hex-of-24-byte-nonce"
:ciphertext #bytes "hex-of-ciphertext"
:alg :x25519-xsalsa20-poly1305}
{:type :signet/sealed
:recipient {:kid "base64url-recipient-public-key"}
:ephemeral-pk #bytes "hex-of-ephemeral-public-key"
:ciphertext #bytes "hex-of-ciphertext"
:alg :x25519-xsalsa20-poly1305}
(require '[signet.key :as key])
;; Generate keypairs
(key/generate-signing-keypair) ;; → {:type :signet/ed25519-keypair ...}
(key/generate-encryption-keypair) ;; → {:type :signet/x25519-keypair ...}
;; Encode/decode
(key/encode-public-key kp :base64url)
(key/decode-public-key bytes :ed25519)
;; Key identity
(key/kid kp) ;; → "base64url-of-public-key"
(require '[signet.sign :as sign])
;; Low-level
(sign/sign secret-key message-bytes) ;; → signature-bytes
(sign/verify public-key message-bytes signature-bytes) ;; → boolean
;; High-level (EDN envelope)
(sign/sign-edn keypair payload) ;; → signed envelope map
(sign/verify-edn signed-envelope) ;; → {:valid? true :payload ... :signer ...}
;; Close: sign-and-discard — proves possession of a key, then destroys
;; the ability to extend/delegate further (cf. Biscuit "seal")
(sign/close ephemeral-secret-key message-bytes) ;; → {:type :signet/closed :signature sig-bytes}
(sign/closed? proof) ;; → boolean
(require '[signet.box :as box])
;; Authenticated encryption
(box/encrypt sender-kp recipient-pk plaintext-bytes) ;; → encrypted box map
(box/decrypt recipient-kp encrypted-box) ;; → plaintext-bytes
;; High-level (EDN)
(box/encrypt-edn sender-kp recipient-pk edn-value) ;; → encrypted box map
(box/decrypt-edn recipient-kp encrypted-box) ;; → edn-value
org.clojure/clojure (1.12+)com.github.franks42/cedn (canonical EDN)com.github.franks42/uuidv7 (request IDs)org.clojure/clojurescript (for JS target)@noble/curves (JS polyfill, may become unnecessary as WebCrypto matures)Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |