3Elliptic curve cryptography and authenticated encryption for Elm on LynxJS.
5Embeds [@noble/curves](https://github.com/paulmillr/noble-curves) 2.0.1 and [@noble/ciphers](https://github.com/paulmillr/noble-ciphers) as kernel JS. All byte data is represented as hex-encoded strings.
15 "lynxjs-elm/crypto": "1.0.0"
23- [`Crypto.Secp256k1`](#cryptosecp256k1) — ECDSA signing, ECDH key exchange (Bitcoin, Ethereum)
24- [`Crypto.Ed25519`](#cryptoed25519) — EdDSA signing (SSH, Signal)
25- [`Crypto.X25519`](#cryptox25519) — ECDH key exchange (WireGuard, Signal, Noise)
26- [`Crypto.P256`](#cryptop256) — ECDSA signing, ECDH key exchange (TLS, WebAuthn)
27- [`Crypto.Cipher`](#cryptocipher) — AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305
34import Crypto.Secp256k1 as Secp256k1
45All are opaque. Construct with `fromHex`, extract with `toHex`.
50privateKeyFromHex : String -> Maybe PrivateKey
53Parse a hex-encoded private key (32 bytes / 64 hex chars). Returns `Nothing` if invalid or not a valid scalar on the curve.
56privateKeyToHex : PrivateKey -> String
60publicKeyFromHex : String -> Maybe PublicKey
63Parse a hex-encoded public key (compressed 33 bytes or uncompressed 65 bytes). Returns `Nothing` if the point is not on the curve.
66publicKeyToHex : PublicKey -> String
69Returns the compressed representation.
71### Signature Conversion
74signatureFromCompactHex : String -> Maybe Signature
77Parse a compact signature (r‖s, 64 bytes / 128 hex chars).
80signatureFromDerHex : String -> Maybe Signature
83Parse a DER-encoded signature.
86signatureToCompactHex : Signature -> String
87signatureToDerHex : Signature -> String
93getPublicKey : PrivateKey -> PublicKey
96Derive the compressed public key from a private key.
99sign : String -> PrivateKey -> Signature
102Sign a message hash. The first argument is the **hex-encoded hash** of the message (e.g. a SHA-256 hash). Uses deterministic RFC 6979 nonces.
105verify : Signature -> String -> PublicKey -> Bool
108Verify a signature against a message hash and public key.
111getSharedSecret : PrivateKey -> PublicKey -> String
114ECDH: compute a shared secret from your private key and their public key. Returns the hex-encoded shared point. Both parties derive the same secret.
119import Crypto.Secp256k1 as Secp256k1
121case Secp256k1.privateKeyFromHex "a665a459...7a27ae3" of
124 pub = Secp256k1.getPublicKey priv
125 sig = Secp256k1.sign msgHashHex priv
127 Secp256k1.verify sig msgHashHex pub -- True
138import Crypto.Ed25519 as Ed25519
152privateKeyFromHex : String -> Maybe PrivateKey
155Parse a hex-encoded private key (32 bytes / 64 hex chars).
158privateKeyToHex : PrivateKey -> String
162publicKeyFromHex : String -> Maybe PublicKey
165Parse a hex-encoded public key (32 bytes / 64 hex chars). Returns `Nothing` if the point is not on the curve.
168publicKeyToHex : PublicKey -> String
171### Signature Conversion
174signatureFromHex : String -> Maybe Signature
177Parse a hex-encoded signature (64 bytes / 128 hex chars).
180signatureToHex : Signature -> String
186getPublicKey : PrivateKey -> PublicKey
190sign : String -> PrivateKey -> Signature
193Sign a message. Unlike secp256k1/P-256, Ed25519 takes the **raw message bytes** (hex-encoded), not a pre-hash. Ed25519 hashes internally with SHA-512.
196verify : Signature -> String -> PublicKey -> Bool
202import Crypto.Ed25519 as Ed25519
204case Ed25519.privateKeyFromHex "9d61b19d...cae7f60" of
207 pub = Ed25519.getPublicKey priv
208 sig = Ed25519.sign messageHex priv
210 Ed25519.verify sig messageHex pub -- True
221import Crypto.X25519 as X25519
224X25519 Diffie-Hellman key exchange on Curve25519 (Montgomery form). Used in WireGuard, Signal, Noise Protocol, TLS 1.3.
236privateKeyFromHex : String -> Maybe PrivateKey
237privateKeyToHex : PrivateKey -> String
238publicKeyFromHex : String -> Maybe PublicKey
239publicKeyToHex : PublicKey -> String
242Parse hex-encoded keys (32 bytes / 64 hex chars).
247getPublicKey : PrivateKey -> PublicKey
250Derive the public key from a private key.
253getSharedSecret : PrivateKey -> PublicKey -> String
256ECDH: compute a shared secret from your private key and their public key. Returns the hex-encoded shared point (32 bytes / 64 hex chars). Both parties derive the same secret.
261import Crypto.X25519 as X25519
263case ( X25519.privateKeyFromHex alicePrivHex, X25519.privateKeyFromHex bobPrivHex ) of
264 ( Just alicePriv, Just bobPriv ) ->
266 alicePub = X25519.getPublicKey alicePriv
267 bobPub = X25519.getPublicKey bobPriv
268 secret1 = X25519.getSharedSecret alicePriv bobPub
269 secret2 = X25519.getSharedSecret bobPriv alicePub
271 secret1 == secret2 -- True
282import Crypto.P256 as P256
285Identical API to `Crypto.Secp256k1` — same types and functions, different curve (NIST P-256 / secp256r1 / prime256v1).
298privateKeyFromHex : String -> Maybe PrivateKey
299privateKeyToHex : PrivateKey -> String
300publicKeyFromHex : String -> Maybe PublicKey
301publicKeyToHex : PublicKey -> String
304### Signature Conversion
307signatureFromCompactHex : String -> Maybe Signature
308signatureFromDerHex : String -> Maybe Signature
309signatureToCompactHex : Signature -> String
310signatureToDerHex : Signature -> String
316getPublicKey : PrivateKey -> PublicKey
317sign : String -> PrivateKey -> Signature
318verify : Signature -> String -> PublicKey -> Bool
319getSharedSecret : PrivateKey -> PublicKey -> String
327import Crypto.Cipher as Cipher
330Authenticated encryption (AEAD). Ciphertext includes a 16-byte authentication tag appended automatically. Decryption returns `Nothing` if authentication fails (tampered data or wrong key/nonce).
338A 256-bit (32-byte) symmetric key. Used by all three ciphers.
344A 96-bit (12-byte) nonce. Used by AES-GCM and ChaCha20-Poly1305.
350A 192-bit (24-byte) nonce. Used by XChaCha20-Poly1305. The larger nonce space makes random nonce generation safe (negligible collision probability).
352### Key & Nonce Construction
355keyFromHex : String -> Maybe Key -- 32 bytes / 64 hex chars
356keyToHex : Key -> String
357nonce12FromHex : String -> Maybe Nonce12 -- 12 bytes / 24 hex chars
358nonce12ToHex : Nonce12 -> String
359nonce24FromHex : String -> Maybe Nonce24 -- 24 bytes / 48 hex chars
360nonce24ToHex : Nonce24 -> String
366aesGcmEncrypt : Key -> Nonce12 -> String -> String
369Encrypt plaintext (hex). Returns ciphertext + 16-byte auth tag (hex).
372aesGcmDecrypt : Key -> Nonce12 -> String -> Maybe String
375Decrypt ciphertext + tag (hex). Returns `Nothing` if authentication fails.
380chacha20Encrypt : Key -> Nonce12 -> String -> String
381chacha20Decrypt : Key -> Nonce12 -> String -> Maybe String
384### XChaCha20-Poly1305
387xchacha20Encrypt : Key -> Nonce24 -> String -> String
388xchacha20Decrypt : Key -> Nonce24 -> String -> Maybe String
391### Example: Symmetric Encryption
394import Crypto.Cipher as Cipher
396case ( Cipher.keyFromHex keyHex, Cipher.nonce12FromHex nonceHex ) of
397 ( Just key, Just nonce ) ->
399 ciphertext = Cipher.aesGcmEncrypt key nonce plaintextHex
401 case Cipher.aesGcmDecrypt key nonce ciphertext of
403 decrypted == plaintextHex -- True
406 False -- authentication failed
409 False -- invalid key or nonce
412### Example: ECDH + Encryption (Public-Key Encryption)
414Combine `Crypto.Secp256k1.getSharedSecret` with `Crypto.Cipher` for public-key encryption:
417import Crypto.Secp256k1 as Secp256k1
418import Crypto.Cipher as Cipher
420-- Alice encrypts for Bob
421encryptForBob : Secp256k1.PrivateKey -> Secp256k1.PublicKey -> String -> String -> Maybe String
422encryptForBob alicePriv bobPub nonceHex plaintextHex =
424 shared = Secp256k1.getSharedSecret alicePriv bobPub
425 sharedKeyHex = String.left 64 shared -- first 32 bytes
427 case ( Cipher.keyFromHex sharedKeyHex, Cipher.nonce12FromHex nonceHex ) of
428 ( Just key, Just nonce ) ->
429 Just (Cipher.aesGcmEncrypt key nonce plaintextHex)
434-- Bob decrypts from Alice
435decryptFromAlice : Secp256k1.PrivateKey -> Secp256k1.PublicKey -> String -> String -> Maybe String
436decryptFromAlice bobPriv alicePub nonceHex ciphertextHex =
438 shared = Secp256k1.getSharedSecret bobPriv alicePub
439 sharedKeyHex = String.left 64 shared
441 case ( Cipher.keyFromHex sharedKeyHex, Cipher.nonce12FromHex nonceHex ) of
442 ( Just key, Just nonce ) ->
443 Cipher.aesGcmDecrypt key nonce ciphertextHex
449Both sides derive the same shared secret, so Alice's encryption can be decrypted by Bob and vice versa.
455**Never reuse a (key, nonce) pair.** Reusing a nonce with the same key completely breaks the security of all three ciphers.
457- For **AES-GCM** and **ChaCha20-Poly1305** (12-byte nonce): use a counter or derive nonces deterministically. Random 12-byte nonces have a non-negligible collision risk after ~2^32 messages.
458- For **XChaCha20-Poly1305** (24-byte nonce): random nonces are safe. The 24-byte nonce space makes collision negligible even after billions of messages.
462All functions accept and return **lowercase hex strings**. One byte = two hex characters.
466| Private key (32 bytes) | 64 chars |
467| Public key, compressed (33 bytes) | 66 chars |
468| Public key, uncompressed (65 bytes) | 130 chars |
469| Ed25519 public key (32 bytes) | 64 chars |
470| X25519 public key (32 bytes) | 64 chars |
471| Signature, compact (64 bytes) | 128 chars |
472| Cipher key (32 bytes) | 64 chars |
473| Nonce12 (12 bytes) | 24 chars |
474| Nonce24 (24 bytes) | 48 chars |
478MIT (noble-curves, noble-ciphers), BSD-3-Clause (lynxjs-elm/crypto)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list