///| return (public key, secret key)
pub fn keypair_gen(
  seed : Result[Array[Byte], Rand],
) -> (Array[Byte], Array[Byte]) {
  let init_seed : Array[Byte] = Array::make(SEEDBYTES, 0)
  let k = dilithium_context.get_params_set().k
  let l = dilithium_context.get_params_set().l
  let publickey_bytes = dilithium_context.get_params_set().publickey_bytes
  match seed {
    Ok(x) => {
      init_seed.clear()
      init_seed.append(x)
    }
    Err(rand) => {
      let x = randombytes(rand, SEEDBYTES)
      init_seed.clear()
      init_seed.append(x)
    }
  }
  let seedbuf : Array[Byte] = Array::make(2 * SEEDBYTES + CRHBYTES, 0)
  let tr : Array[Byte] = Array::make(SEEDBYTES, 0)
  let rho : Array[Byte] = Array::make(SEEDBYTES, 0)
  let rhoprime : Array[Byte] = Array::make(CRHBYTES, 0)
  let key : Array[Byte] = Array::make(SEEDBYTES, 0)
  let mat : Array[Polyvecl] = Array::new()
  let s1 : Polyvecl = Polyvecl::default()
  let s2 : Polyveck = Polyveck::default()
  let t1 : Polyveck = Polyveck::default()
  let t0 : Polyveck = Polyveck::default()
  for i in 0.. sig
pub fn sign(m : Array[Byte], sk : Array[Byte]) -> Array[Byte] {
  let k = dilithium_context.get_params_set().k
  let polyw1_packedbytes = dilithium_context.get_params_set().polyw1_packedbytes
  let gamma1 = dilithium_context.get_params_set().gamma1
  let gamma2 = dilithium_context.get_params_set().gamma2
  let beta = dilithium_context.get_params_set().beta
  let omega = dilithium_context.get_params_set().omega
  let sign_bytes = dilithium_context.get_params_set().sign_bytes
  // `key` and `mu` are concatenated
  let keymu : Array[Byte] = Array::make(SEEDBYTES + CRHBYTES, 0)
  let mat : Array[Polyvecl] = Array::new()
  let y : Polyvecl = Polyvecl::default()
  let w1 : Polyveck = Polyveck::default()
  let w0 : Polyveck = Polyveck::default()
  let h = Polyveck::default()
  let cp = Poly::default()
  let state = KeccakState::default() //shake256_init()
  let rhoprime : Array[Byte] = Array::make(CRHBYTES, 0)
  let (rho, key, tr, t0, s1, s2) = unpack_sk(sk)
  for i in 0.. 0 {
      continue
    }

    // Check that subtracting cs2 does not change high bits of w and low bits
    // do not reveal secret information 
    Polyveck::pointwise_poly_montgomery(h, cp, s2)
    h.invntt_tomont()
    w0.sub(h)
    w0.reduce()
    if w0.chknorm(gamma2 - beta) > 0 {
      continue
    }

    // Compute hints for w1
    Polyveck::pointwise_poly_montgomery(h, cp, t0)
    h.invntt_tomont()
    h.reduce()
    if h.chknorm(gamma2) > 0 {
      continue
    }
    w0.add(h)
    let n = Polyveck::make_hint(h, w0, w1)
    if n > omega {
      continue
    }
    // Write signature
    let _ = pack_sig(sig, None, z, h)
    return sig
  }
  panic() // unreachable
}

///|
pub fn verify(
  sig : Array[Byte],
  m : Array[Byte],
  pk : Array[Byte],
) -> Result[Unit, SignError] {
  let sign_bytes = dilithium_context.get_params_set().sign_bytes
  let beta = dilithium_context.get_params_set().beta
  let gamma1 = dilithium_context.get_params_set().gamma1
  let k = dilithium_context.get_params_set().k
  let polyw1_packedbytes = dilithium_context.get_params_set().polyw1_packedbytes
  let publickey_bytes = dilithium_context.get_params_set().publickey_bytes
  let buf : Array[Byte] = Array::make(k * polyw1_packedbytes, 0)
  let mu : Array[Byte] = Array::make(CRHBYTES, 0)
  let mut c : Array[Byte] = Array::make(SEEDBYTES, 0)
  let c2 : Array[Byte] = Array::make(SEEDBYTES, 0)
  let cp = Poly::default()
  let mat : Array[Polyvecl] = Array::new()
  let mut z = Polyvecl::default()
  let w1 = Polyveck::default()
  let mut h = Polyveck::default()
  let state = KeccakState::default() // shake256_init()
  if sig.length() != sign_bytes {
    return Err(SignError("Wrong signature length"))
  }
  for i in 0.. return Err(e)
    Ok((sig_z, sig_h, sig_c)) => {
      c = sig_c
      z = sig_z
      h = sig_h
    }
  }
  if z.chknorm(gamma1 - beta) > 0 {
    return Err(SignError("Invalid signature"))
  }

  // Compute CRH(CRH(rho, t1), msg)
  shake256(
    output=mu,
    SEEDBYTES.reinterpret_as_uint(),
    input=pk,
    publickey_bytes.reinterpret_as_uint(),
  )
  shake256_absorb(state, mu, SEEDBYTES.reinterpret_as_uint())
  shake256_absorb(state, m, m.length().reinterpret_as_uint())
  shake256_finalize(state)
  shake256_squeeze(mu, CRHBYTES.reinterpret_as_uint(), state)

  // Matrix-vector multiplication; compute Az - c2^dt1
  cp.challenge(c)
  Polyvecl::matrix_expand(mat, rho)
  z.ntt()
  polyvec_matrix_pointwise_montgomery(w1, mat, z)
  cp.ntt()
  t1.shiftl()
  t1.ntt()
  let t1_2 = t1.copy()
  Polyveck::pointwise_poly_montgomery(t1, cp, t1_2)
  w1.sub(t1)
  w1.reduce()
  w1.invntt_tomont()

  // Reconstruct w1
  w1.caddq()
  w1.use_hint(h)
  w1.pack_w1(buf)

  // Call random oracle and verify challenge
  state.init()
  shake256_absorb(state, mu, CRHBYTES.reinterpret_as_uint())
  shake256_absorb(state, buf, (k * polyw1_packedbytes).reinterpret_as_uint())
  shake256_finalize(state)
  shake256_squeeze(c2, SEEDBYTES.reinterpret_as_uint(), state)
  // Doesn't require constant time equality check
  if c != c2 {
    Err(SignError("verify failed"))
  } else {
    Ok(())
  }
}