// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Every corner of mooncrypt in one run, in nine sections: digests, MACs,
/// key derivations, block ciphers and their modes, AEAD, signatures, key
/// agreement, one-time passwords and key files.
///
/// moon run src/examples/tour
///
/// The point is not the output — most of these are vectors already in the test
/// suite — but that every one is reached the same way: through `@spec`'s traits
/// where the shape fits, and through a plain function where it does not.
#coverage.skip
fn main raise {
let sha256 = fn() -> &@spec.Hash { @sha2.Hasher::new() }
let msg = b"the quick brown fox"
// ------------------------------------------------------------- digests
//
// A digest, one shot and streamed. The streamed one is how a large body is
// hashed without holding it in memory.
println("--- digests")
println("sha256 -> \{@base16.encode(@sha2.hash(msg[:]))[0:32]}…")
let running = @sha2.Hasher::new(kind=Sha512)
running.write(b"the quick "[:])
running.write(b"brown fox"[:])
println("sha512 -> \{@base16.encode(running.finish()[:])[0:32]}…")
// The four other families, same trait, so HMAC below would take any of them.
println("sha3-256 -> \{@base16.encode(@sha3.hash(msg[:]))[0:32]}…")
println(
"shake256 -> \{@base16.encode(@sha3.shake256(msg[:], 32)[:])[0:32]}…",
)
println("blake3 -> \{@base16.encode(@blake3.hash(msg[:]))[0:32]}…")
println("blake2b -> \{@base16.encode(@blake2.hash(msg[:])[:])[0:32]}…")
println("sm3 -> \{@base16.encode(@sm3.hash(msg[:]))[0:32]}…")
println("ripemd160 -> \{@base16.encode(@ripemd.hash(msg[:]))[0:32]}…")
// A non-cryptographic digest is still a `Hash`, which is what makes it usable
// as a checksum or as a key in a table without a second interface.
println("crc32 -> \{@crc32.crc32(msg[:])}")
println("xxhash64 -> \{@xxhash.xxhash(msg[:])}")
// ---------------------------------------------------------------- MACs
//
// A MAC. The hash goes in as a factory, so the same HMAC serves every digest.
println("--- macs")
let tag = @hmac.mac(b"shared secret"[:], msg[:], sha256)
println("hmac-sha256 -> \{@base16.encode(tag[:])[0:32]}…")
println("constant-time compare -> \{@spec.eq(tag[:], tag[:])}")
// A MAC built on a block cipher: CMAC over AES, GMAC over the same, and KMAC
// over SHA-3. Three keyed hashes from three different primitives.
let aes_key = Bytes::make(16, b'\x2b')
let aes = @aes.Cipher::new(aes_key[:])
println("aes-cmac -> \{@base16.encode(@cmac.mac(aes, msg[:])[:])}")
let nonce16 = Bytes::make(12, b'\x00')
println(
"aes-gmac -> \{@base16.encode(@gmac.mac(aes, nonce16[:], msg[:])[:])}",
)
println(
"kmac256 -> \{@base16.encode(@kmac.mac(aes_key[:], msg[:], kind=Kmac256, len=32)[:])[0:32]}…",
)
let siphash_key = Bytes::make(16, b'\x07')
println(
"siphash-2-4 -> \{@base16.encode(@siphash.siphash(siphash_key[:], msg[:])[:])}",
)
// -------------------------------------------------------- key derivation
println("--- key derivation")
let key = @hkdf.key(
b"input keying material"[:],
sha256,
salt=b"salt"[:],
info=b"client write key"[:],
len=32,
)
println("hkdf -> \{@base16.encode(key[:])[0:32]}…")
let stretched = @pbkdf2.key(
b"correct horse"[:],
b"battery staple"[:],
sha256,
rounds=4096,
len=32,
)
println("pbkdf2 -> \{@base16.encode(stretched[:])[0:32]}…")
// The three password hashes, each with the smallest parameters that are still
// that algorithm — a tour should not take a minute to print a line.
let salt = b"battery staple"[:]
let scrypt = @scrypt.key(
b"correct horse"[:],
salt,
params=@scrypt.Params::new(n=1024, r=8, p=1),
len=32,
)
println("scrypt -> \{@base16.encode(scrypt[:])[0:32]}…")
let argon = @argon2.key(
b"correct horse"[:],
salt,
params=@argon2.minimum,
len=32,
)
println("argon2id -> \{@base16.encode(argon[:])[0:32]}…")
let salt16 = Bytes::make(16, b'\x09')
let bc = @bcrypt.encode(b"correct horse"[:], salt16[:], cost=4)
println("bcrypt -> \{bc}")
println("bcrypt ok -> \{@bcrypt.verify(b"correct horse"[:], bc)}")
// A Concat KDF, which is what a NIST key-agreement scheme derives its keys
// with — the same input material HKDF above started from.
let concat = @concat.key(
b"shared secret z"[:],
sha256,
other_info=b"info"[:],
len=32,
)
println("concat kdf -> \{@base16.encode(concat[:])[0:32]}…")
// -------------------------------------------------- ciphers and modes
println("--- ciphers")
let block = Bytes::make(16, b'\x00')
let sm4 = @sm4.Cipher::new(Bytes::make(16, b'\x01')[:])
println("sm4 -> \{@base16.encode(sm4.encrypt(block[:])[:])}")
let camellia = @camellia.Cipher::new(Bytes::make(32, b'\x01')[:])
println("camellia -> \{@base16.encode(camellia.encrypt(block[:])[:])}")
let des = @des.Cipher::new(Bytes::make(8, b'\x01')[:])
println("des -> \{@base16.encode(des.encrypt(block[0:8])[:])}")
// A mode of operation turns a block cipher into something that takes a
// message. CBC pads, so the ciphertext is longer than the plaintext.
let iv = Bytes::make(16, b'\x00')
let cbc = @mode.Cbc::new(aes, iv[:])
let padded = cbc.encrypt(@mode.pad(16, msg[:])[:])
println("aes-cbc -> \{padded.length()} bytes from 19")
// The context is stateful — it carries the chain from one call to the next, so
// that a long message can be written in pieces. Starting over means `reset`.
cbc.reset()
println("aes-cbc ok -> \{@mode.unpad(16, cbc.decrypt(padded[:])[:]) == msg}")
// The stream ciphers, legacy ones included: same shape, no padding.
let stream_key = Bytes::make(32, b'\x03')
let nonce8 = Bytes::make(8, b'\x00')
println(
"salsa20 -> \{@base16.encode(@salsa20.salsa20(stream_key[:], nonce8[:], msg[:])[:])[0:32]}…",
)
let x = @xchacha20.Cipher::new(stream_key[:])
let nonce24 = Bytes::make(24, b'\x00')
println(
"xchacha20 -> \{@base16.encode(x.xor(nonce=nonce24[:], msg[:])[:])[0:32]}…",
)
println(
"rc4 -> \{@base16.encode(@rc4.rc4(b"Key"[:], msg[:])[:])[0:32]}…",
)
// ---------------------------------------------------------------- AEAD
//
// Four constructions. Each seals with a nonce and associated data, and each
// refuses to open a ciphertext that has been touched.
println("--- aead")
let boxed = @chachapoly.ChaChaPoly::new(stream_key[:])
let nonce12 = Bytes::make(12, b'\x00')
let sealed_box = boxed.seal(nonce=nonce12[:], plain=msg[:])
println("chachapoly -> \{@base16.encode(sealed_box[:])[0:32]}…")
println(
"opened -> \{boxed.open(nonce=nonce12[:], cipher=sealed_box[:]) == msg}",
)
let gcm = @sm4gcm.new(Bytes::make(16, b'\x04')[:])
let g = gcm.seal(nonce=nonce12[:], plain=msg[:], aad=b"header"[:])
println("sm4-gcm -> \{@base16.encode(g[:])[0:32]}…")
println(
"sm4-gcm ok -> \{gcm.open(nonce=nonce12[:], cipher=g[:], aad=b"header"[:]) == msg}",
)
let ccm = @ccm.Ccm::new(aes)
let c = ccm.seal(nonce=nonce12[:], plain=msg[:], aad=b"header"[:])
println("aes-ccm -> \{@base16.encode(c[:])[0:32]}…")
println(
"aes-ccm ok -> \{ccm.open(nonce=nonce12[:], cipher=c[:], aad=b"header"[:]) == msg}",
)
let siv_cmac = @aes.Cipher::new(Bytes::make(16, b'\x05')[:])
let siv = @siv.Siv::new(siv_cmac, aes)
let s = siv.seal(nonce=b"header"[:], plain=msg[:])
println("aes-siv -> \{@base16.encode(s[:])[0:32]}…")
println("aes-siv ok -> \{siv.open(nonce=b"header"[:], cipher=s[:]) == msg}")
// ---------------------------------------------------------- signatures
//
// Five schemes behind the same two traits, so a verifier written against
// `@spec.Verifier` takes any of them.
println("--- signatures")
let seed = @base16.decode_lossy(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
)
let ed = @ed25519.PrivateKey::new(seed[:])
let ed_sig = ed.sign(msg[:])
println("ed25519 -> \{@base16.encode(ed.public().bytes()[:])[0:32]}…")
println("ed25519 ok -> \{ed.public().verify(msg[:], ed_sig[:])}")
// Ed448, with the same API and a 57-byte seed.
let ed448 = @ed448.PrivateKey::new(Bytes::make(57, b'\x11')[:])
let ed448_sig = ed448.sign(msg[:])
println("ed448 ok -> \{ed448.public().verify(msg[:], ed448_sig[:])}")
// ECDSA over P-256, with the RFC 6979 nonce — also deterministic.
let scalar = @base16.decode_lossy(
"c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721",
)
let ec = @ecdsa.PrivateKey::new(scalar[:])
let ec_sig = ec.sign(msg[:])
println("ecdsa p-256 -> \{@base16.encode(ec_sig[:])[0:32]}…")
println("ecdsa ok -> \{ec.public().verify(msg[:], ec_sig[:])}")
println(
"ecdsa bent -> \{ec.public().verify(b"the quick brown fix"[:], ec_sig[:])}",
)
// SM2, which is ECDSA's shape over the Chinese curve and with the signer's
// identity mixed into the hash.
let sm2 = @sm2.PrivateKey::new(Bytes::make(32, b'\x06')[:])
let sm2_sig = sm2.sign(msg[:])
println("sm2 ok -> \{sm2.public().verify(msg[:], sm2_sig[:])}")
// BIP 340 Schnorr over secp256k1, x-only keys and all.
let schnorr = @schnorr.PrivateKey::new(Bytes::make(32, b'\x07')[:])
let schnorr_sig = schnorr.sign(msg[:])
println("schnorr ok -> \{schnorr.public().verify(msg[:], schnorr_sig[:])}")
// ---------------------------------------------------- key agreement
println("--- key agreement")
let alice = Bytes::make(32, b'\x11')
let bob = Bytes::make(32, b'\x22')
let secret = @ecdh.shared(alice[:], @ecdh.public(bob[:])[:])
let agreed = @spec.eq(
secret[:],
@ecdh.shared(bob[:], @ecdh.public(alice[:])[:])[:],
)
println("ecdh agreed -> \{agreed}")
// X448, the same idea over a different curve and a 56-byte scalar.
let a448 = Bytes::make(56, b'\x33')
let b448 = Bytes::make(56, b'\x44')
let s448 = @x448.shared(a448[:], @x448.public(b448[:])[:])
println(
"x448 agreed -> \{@spec.eq(s448[:], @x448.shared(b448[:], @x448.public(a448[:])[:])[:])}",
)
// Finite-field Diffie-Hellman over an RFC 7919 group, which is the only place
// here where a "public key" is 256 bytes rather than 32.
let dh_a = Bytes::make(32, b'\x55')
let dh_b = Bytes::make(32, b'\x66')
let dh_s = @dh.shared_secret(
@dh.Ffdhe2048,
dh_a[:],
@dh.public_key(@dh.Ffdhe2048, dh_b[:])[:],
)
println(
"ffdhe2048 -> \{@dh.Group::size(Ffdhe2048)} bytes, \{dh_s.length()} shared",
)
// ------------------------------------------------- one-time passwords
println("--- one-time passwords")
let otp_secret = b"12345678901234567890"
let codes = @otp.Otp::new(otp_secret[:])
println("hotp 0 -> \{codes.hotp(0UL)}")
println("hotp 1 -> \{codes.hotp(1UL)}")
println("totp t=59 -> \{codes.totp(59UL)}")
println("totp ok -> \{codes.confirm(codes.totp(59UL)[:], 59UL)}")
// ------------------------------------------------------- key files
//
// PEM is base64 inside a header, which is all a `.pem` file is; DER is what
// the base64 decodes to, and that is what a key parser reads.
println("--- key files")
let pem = @key.armor("EC PRIVATE KEY", Bytes::make(48, b'\x42'))
let (label, back) = @key.dearmor(pem[:])
println("pem -> \{label}, \{back.length()} bytes back")
println("pem lines -> \{pem.split("\n").collect().length()}")
}