Small Go library for Matrix-adjacent cryptographic primitives.
This repository currently provides:
lthash: LtHash16-style homomorphic state hashing for Matrix state setsfndsa512: a thin Go wrapper for Falconfn-dsa-512keyid: canonicalfn-dsa-512key-ID fingerprint and short-ID helpermatrixjson: Matrix Canonical JSON encoding for signed objectsmerkle: MSC4511 SHA3-256 root derivation and event ID primitivesserverkey: FN-DSA server-key object construction and self-signingcuckoo: Cuckoo Cycle proof generation and verification helperscmd/merkle-vectors: draft MSC4511 Merkle metadata vector generatorcmd/cuckoo-scan: helper command that scans graph nonces until the reference mean-miner finds a proof
Early library code, not a finished application or protocol implementation.
The fn-dsa-512 package currently wraps github.com/pornin/go-fn-dsa.
That upstream implementation explicitly tracks a pre-final FN-DSA standard, so
wire compatibility may need adjustment when NIST finalizes the specification.
- Go 1.25+
lthash/: reference lattice hash andBLAKEchecksumfndsa512/: key gen, signing, and verification helperskeyid/: key-ID digest and pseudo-unique short-ID derivationmatrixjson/: canonical JSON encodermerkle/: draft MSC4511 field roots, event roots, and event ID helpersserverkey/: FN-DSA helpers for/_matrix/key/v2/servercuckoo/: PoW edge miner and proof verificationres/: reference notes and external materials
The lthash package implements a 2048-byte lattice state hash. Each state
entry is expanded with SHAKE256 and accumulated with wrapping uint16
addition, so inserts and removals are incremental and order-independent.
Example:
var h lthash.Hash
h.Insert("m.room.create", "", "$event0:example.org")
h.Insert("m.room.member", "@alice:example.org", "$event1:example.org")
checksum := h.Checksum()The fndsa512 package exposes a narrow API over the underlying Falcon
implementation for:
- key generation
- raw-message signing and verification
- prehashed signing and verification
Example:
priv, pub, err := fndsa512.GenerateKey(nil)
if err != nil {
panic(err)
}
msg := []byte("matrix canonical json payload")
sig, err := fndsa512.Sign(nil, priv, msg)
if err != nil {
panic(err)
}
ok := fndsa512.Verify(pub, msg, sig)The serverkey package builds and self-signs an FN-DSA Matrix server-key
object. The signature covers the Matrix Canonical JSON form of the object after
removing signatures and unsigned.
Example:
priv, pub, err := fndsa512.GenerateKey(nil)
if err != nil {
panic(err)
}
// In production this proof is found by iterating the minting nonce until the
// SHA3-256-derived graph seed has a valid Cuckoo Cycle solution. The final
// key_id is derived from that graph seed and the sorted proof solution.
proof := serverkey.FNDSAMintingProof{
Algorithm: serverkey.ProductionPoW,
Nonce: 8137226,
Solution: []uint32{ /* 42 sorted edge nonces */ },
}
obj, keyName, err := serverkey.NewSignedFNDSA(
nil,
"example.com",
priv,
pub,
1798848000000,
serverkey.FNDSAMetadata{
FIPS206Revision: serverkey.DefaultFIPSRevision,
Claims: []string{
"constant-time-keygen",
"constant-time-signing",
},
},
proof,
)
if err != nil {
panic(err)
}
verifiedKeyName, err := serverkey.VerifyFNDSASelfSignature(obj, "example.com")
if err != nil || verifiedKeyName != keyName {
panic("invalid self-signature")
}Demo command, including a low-difficulty Cuckoo proof embedded in the generated FN-DSA verify key object:
go run ./cmd/serverkey-demo -server example.com -valid-days 7To encrypt the generated private key before it is printed, provide the passphrase through an environment variable or a file:
SERVERKEY_PASSPHRASE='replace with a secret phrase' go run ./cmd/serverkey-demo -server example.com -private-key-passphrase-env SERVERKEY_PASSPHRASEExisting plaintext private-key exports can be encrypted, and encrypted exports can be re-wrapped with a new passphrase:
SERVERKEY_PASSPHRASE='replace with a secret phrase' go run ./cmd/serverkey-keytool -mode encrypt -in private-key.b64 -passphrase-env SERVERKEY_PASSPHRASE
OLD_SERVERKEY_PASSPHRASE='old secret phrase' NEW_SERVERKEY_PASSPHRASE='new secret phrase' go run ./cmd/serverkey-keytool -mode reencrypt -in encrypted-private-key.json -old-passphrase-env OLD_SERVERKEY_PASSPHRASE -new-passphrase-env NEW_SERVERKEY_PASSPHRASEEmpty passphrases are rejected. Removing encryption from an encrypted private key is intentionally not provided by this tool.
The demo uses -pow-edge-bits 8 -pow-proof-size 4 and searches 1<<12
edge nonces per minting nonce by default, so it is intentionally easy: it looks
for a 4-cycle in a tiny graph. The live production profile described in
res/ is 42-29 with a SHA3-256 co-generation seed and is intentionally
much more expensive.
PoW profile examples:
# Default fast demo profile.
go run ./cmd/serverkey-demo -server example.com
# Custom profile and algorithm label.
go run ./cmd/serverkey-demo -pow-profile custom -pow-algorithm local.cuckoo-cycle-6-12-sha3-256-cogen -pow-edge-bits 12 -pow-proof-size 6 -pow-max-nonce 65536
# Production-style nutra.tk bundle using the reference mean-miner.
(cd cuckoo/meanminer/csrc && make)
go run ./cmd/serverkey-demo -server nutra.tk -valid-days 365 -pow-profile production -pow-max-graph-nonce 1024
# Production parameter labels. This is expected to be expensive with the Go helper.
go run ./cmd/serverkey-demo -pow-profile production -pow-max-nonce 536870912 -pow-max-graph-nonce 1024Draft MSC4511 Merkleized metadata vectors can be regenerated with:
go run ./cmd/merkle-vectorsThese are implementation regression vectors, not official Matrix specification vectors.
The cuckoo package provides deterministic edge derivation from a seed,
proof verification, and a bounded search helper suitable for tests or
low-difficulty experiments.
Example:
cfg := cuckoo.Config{EdgeBits: 8, ProofSize: 4}
seed := []byte("example-seed")
proof, err := cuckoo.FindProof(cfg, seed, 1<<12)
if err != nil {
panic(err)
}
err = cuckoo.Verify(cfg, seed, proof)Reference mean-miner scan helper:
# Build the reference solver first.
(cd cuckoo/meanminer/csrc && make)
# Scan graph nonces until the first solvable graph is found.
go run ./cmd/cuckoo-scan -prefix manual-test -start 0 -limit 200 -threads 6The helper hashes <prefix> + little-endian uint64(graph_nonce) with
SHA-256 to derive the 32-byte graph seed for each attempt. It is useful
when you want to reproduce the shell loop used during solver testing without
retyping the loop each time.
Run the full test suite with:
make testRun the main verification path with:
make format lint cov