moondsa is a MoonBit implementation of Dilithium, a post-quantum signature scheme.
moon add ruifeng/moondsa// Import the package
let @dilithium = @ruifeng/moondsa
// Set security level (Dilithium3 is the default)
@dilithium.dilithium_context.set_level(SecurityLevel::Dilithium3)
// Generate key pair with random seed
let (pk, sk) = @dilithium.crypto_sign_keypair(Err(@random.Rand::new()))
// Sign a message
let message = "Hello, post-quantum world!".to_bytes()
let signature = @dilithium.crypto_sign_signature(message, sk)
// Verify signature
let result = @dilithium.crypto_sign_verify(signature, message, pk)
match result {
Ok(_) => println("Signature is valid!")
Err(e) => println("Signature verification failed: \(e)")
}// Generate keys from a specific seed (deterministic)
let seed = Array::make(32, 0x42) // 32-byte seed
let (pk, sk) = @dilithium.crypto_sign_keypair(Ok(seed))// Available security levels
@dilithium.dilithium_context.set_level(SecurityLevel::Dilithium2) // 128-bit security
@dilithium.dilithium_context.set_level(SecurityLevel::Dilithium3) // 192-bit security
@dilithium.dilithium_context.set_level(SecurityLevel::Dilithium5) // 256-bit security| Security Level | Public Key | Secret Key | Signature |
|---|---|---|---|
| Dilithium-2 | 1312 bytes | 2528 bytes | 2420 bytes |
| Dilithium-3 | 1952 bytes | 4016 bytes | 3293 bytes |
| Dilithium-5 | 2592 bytes | 4864 bytes | 4595 bytes |
# Run all tests
moon test
# Run with verbose output
moon test -vtype SignErrortype BigRandomtype DilithiumContextpub struct KeccakState {
s : FixedArray[UInt64]
pos : UInt
}impl Default for KeccakStateimpl Show for KeccakStatepub struct ParameterSet {
k : Int
l : Int
eta : Int
tau : Int
beta : Int
gamma1 : Int
gamma2 : Int
omega : Int
publickey_bytes : Int
polyeta_packedbytes : Int
polyw1_packedbytes : Int
polyz_packed_bytes : Int
poly_uniform_gamma1_nblocks : UInt
polyvech_packed_bytes : Int
sign_bytes : Int
level : SecurityLevel
features : Map[String, String]
}let p = @ruifeng/moondsa.Poly::new()
p.coeffs[0] = 42 // Set constant term
p.coeffs[1] = -17 // Set coefficient of x^1
inspect(p.coeffs[0], content="42")
inspect(p.coeffs[1], content="-17")let poly = Poly::new()
// Assume some coefficients are negative after operations
poly.caddq() // All negative coefficients now have Q added to themlet poly1 = Poly::new()
poly1.coeffs[0] = 42
let poly2 = poly1.copy()
poly2.coeffs[0] = 100
// poly1.coeffs[0] is still 42, poly2.coeffs[0] is 100let poly = Poly::new()
// ... initialize polynomial coefficients ...
poly.ntt() // Transform coefficients to NTT domainlet poly = Poly::new()
// Assume poly has been populated with valid coefficients
let packed_bytes = poly.pack_t1()let poly = Poly::new()
// ... populate polynomial with coefficients ...
poly.reduce() // All coefficients are now in range [0, 2*Q]let polyveck = Polyveck::default()
println(polyveck.vec.length()) // prints the value of k from current parameter setlet polyvec = Polyveck::default()
// ... initialize polynomials in polyvec ...
Polyveck::ntt(polyvec)
// All polynomials in polyvec are now in NTT domainlet w = Polyveck::default()
let h = Polyveck::default()
// ... populate w and h with appropriate values ...
w.use_hint(h)
// w now has its high bits corrected based on the hint vector hlet w = Polyvecl::default()
let v = Polyvecl::default()
// Assume both vectors have been initialized with some polynomial values
w.add(v) // w now contains the element-wise sum of the original w and vlet polyvec = Polyvecl::default()
// ... initialize polynomials in polyvec ...
polyvec.ntt() // Transform all polynomials to NTT domainlet v : Polyvecl = Polyvecl::default()
// ... populate v with polynomial data ...
Polyvecl::reduce(v)
// All polynomials in v now have reduced coefficientspub(all) enum SecurityLevel {
Dilithium2
Dilithium3
Dilithium5
}let CRHBYTES : Intlet D : Intlet N : Intlet POLYT0_PACKEDBYTES : Intlet POLYT1_PACKEDBYTES : Intlet POLY_UNIFORM_NBLOCKS : UIntlet Q : Intlet SEEDBYTES : Intlet SHAKE128_RATE : UIntlet SHAKE256_RATE : UIntlet STREAM128_BLOCKBYTES : UIntlet STREAM256_BLOCKBYTES : UIntfn decompose(a : Int) -> (Int, Int)let state = KeccakState::default()
let seed: Array[Byte] = Array::make(SEEDBYTES, 0) // Create seed of appropriate size
@ruifeng/moondsa.dilithium_shake128_stream_init(state, seed, (42: UInt16))let state = KeccakState::default()
let seed : Array[Byte] = Array::make(CRHBYTES, 0) // 64-byte seed
let nonce = (42: UInt16)
dilithium_shake256_stream_init(state, seed, nonce)fn ite(cond : Bool, a : Int, b : Int) -> Intlet result1 = @ruifeng/moondsa.ite(true, 10, 20) // returns 10
let result2 = @ruifeng/moondsa.ite(false, 10, 20) // returns 20fn keccakf1600_statepermute(state : FixedArray[UInt64]) -> Unit| Level | Public Key | Secret Key | Security |
|---|---|---|---|
| 2 | 1312 bytes | 2528 bytes | 128-bit |
| 3 | 1952 bytes | 4016 bytes | 192-bit |
| 5 | 2592 bytes | 4864 bytes | 256-bit |
fn make_hint(a0 : Int, a1 : Int) -> Bytelet t = Polyveck::default()
let mat : Array[Polyvecl] = Array::new()
let v = Polyvecl::default()
// Initialize matrix with appropriate dimensions
let k = dilithium_context.get_params_set().k
for i in 0..<k {
mat.push(Polyvecl::default())
}
polyvec_matrix_pointwise_montgomery(t, mat, v)fn power2round(a : Int) -> (Int, Int)fn reduce32(a : Int) -> Intlet state = KeccakState::default()
let data : Array[Byte] = "Hello, World!".to_bytes().to_array()
shake128_absorb(state, data, data.length().reinterpret_as_uint())
// Continue absorbing more data if needed
let more_data : Array[Byte] = " More data".to_bytes().to_array()
shake128_absorb(state, more_data, more_data.length().reinterpret_as_uint())
// Finally call shake128_finalize(state) to complete the absorption phaselet state = KeccakState::default()
let input : Array[Byte] = "Hello, World!".to_bytes().to_array()
shake128_absorb(state, input, input.length().reinterpret_as_uint())
shake128_finalize(state)
// State is now ready for squeezing outputlet state = KeccakState::default()
// ... after initializing and finalizing the state ...
let output: Array[Byte] = Array::make(336, 0) // 2 blocks * 168 bytes per block
shake128_squeezeblocks(output, 2, state)let state = @ruifeng/moondsa.KeccakState::default()
let chunk1 : Array[Byte] = b"Hello, ".to_array()
let chunk2 : Array[Byte] = b"World!".to_array()
// Absorb data incrementally
@ruifeng/moondsa.shake256_absorb(state, chunk1, 7U)
@ruifeng/moondsa.shake256_absorb(state, chunk2, 6U)
// Finalize before squeezing
@ruifeng/moondsa.shake256_finalize(state)
// Now squeeze output
let output : Array[Byte] = Array::make(32, 0)
@ruifeng/moondsa.shake256_squeeze(output, 32U, state)let state = @ruifeng/moondsa.KeccakState::default()
let data : Array[Byte] = [0, 1, 2, 3]
@ruifeng/moondsa.shake256_absorb_once(state, data, 4)
// State is now ready for squeezing outputlet state = @ruifeng/moondsa.KeccakState::default()
let input : Array[Byte] = b"hello world".to_array()
@ruifeng/moondsa.shake256_absorb(state, input, 11)
@ruifeng/moondsa.shake256_finalize(state)
// State is now ready for squeezing output// Initialize SHAKE256 state and absorb some data
let state = KeccakState::default()
let input : Array[Byte] = "Hello, World!".to_bytes().to_array()
shake256_absorb(state, input, input.length().reinterpret_as_uint())
shake256_finalize(state)
// Squeeze 32 bytes of output
let output : Array[Byte] = Array::make(32, 0)
shake256_squeeze(output[:], 32, state)// Initialize SHAKE256 state and absorb some input
let state = KeccakState::default()
shake256_absorb_once(state, [1, 2, 3, 4], 4)
// Extract 2 full blocks (272 bytes total for SHAKE256)
let output : Array[Byte] = Array::make(272, 0)
shake256_squeezeblocks(output, 2, state)| Level | Structure | Size |
|---|---|---|
| 2 | (c, z, h) | 2420 bytes |
| 3 | (c, z, h) | 3293 bytes |
| 5 | (c, z, h) | 4595 bytes |
let state = @ruifeng/moondsa.KeccakState::default()
let seed: Array[Byte] = Array::make(32, 0) // SEEDBYTES length
let nonce = (42: UInt16)
@ruifeng/moondsa.stream128_init(state, seed, nonce)let state = KeccakState::default()
let seed : Array[Byte] = Array::make(CRHBYTES, b'A') // 64-byte seed
stream256_init(state, seed, (42: UInt16))fn use_hint(a : Int, hint : Byte) -> IntInstall
Download zipmoondsa is a MoonBit implementation of Dilithium, a post-quantum signature scheme.