hqc: wire real PQClean cgo backend, NIST KATs pass byte-for-byte

Replaces the stub backend (ErrBackendNotWired) with a working byte-
for-byte NIST-compatible HQC implementation. Build tag hqc_pqclean
activates the cgo path; default builds remain stub for portability.

Vendored:
  - PQClean HQC-128/192/256 clean reference C (2023-04-30 NIST
    submission), public domain
  - PQClean shared fips202 (SHA-3 / SHAKE)
  - Per-mode/per-source shim TUs (36 total) to keep PQClean's
    static helpers isolated and avoid duplicate-symbol collisions
  - randombytes_shim.c wiring PQClean's randombytes() to a Go-side
    io.Reader callback, mutex-protected for concurrent dispatch

Tests:
  - TestKAT_HQC128/192/256 — all three NIST KAT vectors verified
    byte-for-byte against META.yml nistkat-sha256
  - TestRoundTrip_AllModes — keygen → encap → decap roundtrip,
    shared secrets byte-equal in all 3 modes
  - TestDeterminism_AllModes — same seed produces identical ct/ss
    (load-bearing for the precompile's on-chain determinism)
  - -race clean

Correction: ciphertext sizes updated from 4481/9026/14469 (older
HQC round-3 listing) to PQClean's 4433/8978/14421 (2023-04-30 NIST
submission); the prior numbers would have failed round-trip with
a 48-byte mismatch.
This commit is contained in:
Hanzo AI
2026-05-15 17:43:48 -07:00
parent b33d3b9133
commit 4ace186b12
128 changed files with 9168 additions and 48 deletions
+261
View File
@@ -0,0 +1,261 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build hqc_pqclean
// backend_pqclean.go — cgo binding to the vendored PQClean HQC
// reference implementation (crypto/hqc/pqclean/, public domain).
//
// PQClean ships three independent build sets under
// crypto_kem/hqc-{128,192,256}/clean/. Each set uses namespaced
// functions (PQCLEAN_HQC128_CLEAN_*, etc.) so all three can coexist
// in one Go binary. The per-mode amalgamators hqcN_amalg.c bundle the
// 12 source files of each mode into one translation unit. fips202.c
// is shared across all three (unnamespaced symbols) and lives in its
// own TU — fips202_amalg.c.
//
// PQClean's HQC API takes its randomness from a `randombytes` symbol
// the linker resolves at build time. We provide randombytes_shim.c,
// which forwards to a Go callback (hqcGoRandombytes below). For each
// cgo entry we install a goroutine-local io.Reader (via a global
// guarded by a mutex — HQC operations are not internally parallel so
// the lock is taken for the duration of one keygen/encap/decap call).
//
// Determinism contract: each cgo call consumes EXACTLY the bytes that
// PQClean's HQC reference would consume for that operation. Replaying
// the same `(seed, op)` produces byte-identical outputs. This is
// load-bearing for the on-chain HQC precompile (validators must reach
// consensus on encapsulation outputs).
package hqc
/*
#cgo CFLAGS: -I${SRCDIR} -I${SRCDIR}/pqclean/common -O2 -std=c99 -Wno-unused-function -Wno-unused-variable
#include <stdint.h>
#include <stddef.h>
// HQC-128
extern int PQCLEAN_HQC128_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk);
extern int PQCLEAN_HQC128_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
extern int PQCLEAN_HQC128_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
// HQC-192
extern int PQCLEAN_HQC192_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk);
extern int PQCLEAN_HQC192_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
extern int PQCLEAN_HQC192_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
// HQC-256
extern int PQCLEAN_HQC256_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk);
extern int PQCLEAN_HQC256_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
extern int PQCLEAN_HQC256_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
*/
import "C"
import (
"errors"
"io"
"sync"
"unsafe"
)
// rngState pairs an io.Reader with a sticky read error so that a
// partial read midway through a cgo call is surfaced once the call
// returns. PQClean's HQC reference does not propagate randombytes
// failures; we capture them on the Go side and report after.
type rngState struct {
reader io.Reader
err error
}
var (
rngMu sync.Mutex
activeRNG *rngState
)
// installRNG takes the cgo lock and registers `r` as the entropy
// source consumed by PQClean during the next call. The caller must
// invoke clearRNG (typically via defer) before returning.
func installRNG(r io.Reader) {
rngMu.Lock()
activeRNG = &rngState{reader: r}
}
// clearRNG releases the cgo lock and returns the sticky read error,
// if any.
func clearRNG() error {
s := activeRNG
activeRNG = nil
rngMu.Unlock()
if s == nil {
return nil
}
return s.err
}
//export hqcGoRandombytes
//
// Called from randombytes_shim.c. Reads exactly `n` bytes from the
// installed RNG into the buffer at `buf`. Returns 0 on full read,
// non-zero on partial read or reader error (PQClean checks the
// return value of randombytes() == 0).
func hqcGoRandombytes(buf *C.uint8_t, n C.size_t) C.int {
if activeRNG == nil {
// Misuse — no RNG installed. Fail loudly via non-zero return
// rather than silently zero-fill (which would produce a
// deterministic-but-attacker-controllable keypair).
return C.int(-1)
}
if n == 0 {
return 0
}
dst := unsafe.Slice((*byte)(unsafe.Pointer(buf)), int(n))
_, err := io.ReadFull(activeRNG.reader, dst)
if err != nil {
activeRNG.err = err
return C.int(-1)
}
return 0
}
// errRNGFailed surfaces a randombytes shortfall back to the caller.
var errRNGFailed = errors.New("hqc: PQClean randombytes read failed (underlying reader returned error or short read)")
func backendKeyGen(mode Mode, rng io.Reader) (*PrivateKey, error) {
if rng == nil {
return nil, errors.New("hqc: nil rng")
}
params := MustParamsFor(mode)
pk := make([]byte, params.PublicKeySize)
sk := make([]byte, params.PrivateKeySize)
installRNG(rng)
var rc C.int
switch mode {
case HQC128:
rc = C.PQCLEAN_HQC128_CLEAN_crypto_kem_keypair(
(*C.uint8_t)(unsafe.Pointer(&pk[0])),
(*C.uint8_t)(unsafe.Pointer(&sk[0])),
)
case HQC192:
rc = C.PQCLEAN_HQC192_CLEAN_crypto_kem_keypair(
(*C.uint8_t)(unsafe.Pointer(&pk[0])),
(*C.uint8_t)(unsafe.Pointer(&sk[0])),
)
case HQC256:
rc = C.PQCLEAN_HQC256_CLEAN_crypto_kem_keypair(
(*C.uint8_t)(unsafe.Pointer(&pk[0])),
(*C.uint8_t)(unsafe.Pointer(&sk[0])),
)
default:
clearRNG()
return nil, ErrModeMismatch
}
rngErr := clearRNG()
if rngErr != nil {
return nil, rngErr
}
if rc != 0 {
return nil, errRNGFailed
}
return &PrivateKey{
Mode: mode,
Bytes: sk,
Pub: &PublicKey{Mode: mode, Bytes: pk},
}, nil
}
func backendEncapsulate(pk *PublicKey, rng io.Reader) (*Ciphertext, []byte, error) {
if rng == nil {
return nil, nil, errors.New("hqc: nil rng")
}
params := MustParamsFor(pk.Mode)
if len(pk.Bytes) != params.PublicKeySize {
return nil, nil, ErrInvalidPublicKey
}
ct := make([]byte, params.CiphertextSize)
ss := make([]byte, params.SharedSecretSize)
installRNG(rng)
var rc C.int
switch pk.Mode {
case HQC128:
rc = C.PQCLEAN_HQC128_CLEAN_crypto_kem_enc(
(*C.uint8_t)(unsafe.Pointer(&ct[0])),
(*C.uint8_t)(unsafe.Pointer(&ss[0])),
(*C.uint8_t)(unsafe.Pointer(&pk.Bytes[0])),
)
case HQC192:
rc = C.PQCLEAN_HQC192_CLEAN_crypto_kem_enc(
(*C.uint8_t)(unsafe.Pointer(&ct[0])),
(*C.uint8_t)(unsafe.Pointer(&ss[0])),
(*C.uint8_t)(unsafe.Pointer(&pk.Bytes[0])),
)
case HQC256:
rc = C.PQCLEAN_HQC256_CLEAN_crypto_kem_enc(
(*C.uint8_t)(unsafe.Pointer(&ct[0])),
(*C.uint8_t)(unsafe.Pointer(&ss[0])),
(*C.uint8_t)(unsafe.Pointer(&pk.Bytes[0])),
)
default:
clearRNG()
return nil, nil, ErrModeMismatch
}
rngErr := clearRNG()
if rngErr != nil {
return nil, nil, rngErr
}
if rc != 0 {
return nil, nil, errRNGFailed
}
return &Ciphertext{Mode: pk.Mode, Bytes: ct}, ss, nil
}
func backendDecapsulate(sk *PrivateKey, ct *Ciphertext) ([]byte, error) {
params := MustParamsFor(sk.Mode)
if len(sk.Bytes) != params.PrivateKeySize {
return nil, ErrInvalidPrivateKey
}
if len(ct.Bytes) != params.CiphertextSize {
return nil, ErrInvalidCiphertext
}
ss := make([]byte, params.SharedSecretSize)
// Decapsulation does not consume entropy — HQC's decap is
// deterministic given (sk, ct). We still take the cgo lock so that
// the global `activeRNG` slot is well-defined if any future
// PQClean revision adds an entropy call to decap.
rngMu.Lock()
var rc C.int
switch sk.Mode {
case HQC128:
rc = C.PQCLEAN_HQC128_CLEAN_crypto_kem_dec(
(*C.uint8_t)(unsafe.Pointer(&ss[0])),
(*C.uint8_t)(unsafe.Pointer(&ct.Bytes[0])),
(*C.uint8_t)(unsafe.Pointer(&sk.Bytes[0])),
)
case HQC192:
rc = C.PQCLEAN_HQC192_CLEAN_crypto_kem_dec(
(*C.uint8_t)(unsafe.Pointer(&ss[0])),
(*C.uint8_t)(unsafe.Pointer(&ct.Bytes[0])),
(*C.uint8_t)(unsafe.Pointer(&sk.Bytes[0])),
)
case HQC256:
rc = C.PQCLEAN_HQC256_CLEAN_crypto_kem_dec(
(*C.uint8_t)(unsafe.Pointer(&ss[0])),
(*C.uint8_t)(unsafe.Pointer(&ct.Bytes[0])),
(*C.uint8_t)(unsafe.Pointer(&sk.Bytes[0])),
)
default:
rngMu.Unlock()
return nil, ErrModeMismatch
}
rngMu.Unlock()
// rc != 0 indicates HQC implicit rejection (the FO-transform's
// re-encryption did not match). PQClean still returns a 64-byte
// `ss` derived from the secret key + ciphertext — this is the
// Hofheinz-Hövelmanns-Kiltz construction, where decap of a tampered
// ciphertext yields a pseudorandom secret rather than an error.
// Do not surface as a Go error; let callers compare.
_ = rc
return ss, nil
}
+241
View File
@@ -0,0 +1,241 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build hqc_pqclean
package hqc
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
"testing"
"golang.org/x/crypto/sha3"
)
// hqcKatReader replicates PQClean's hqckatrng.c — a SHAKE-256-based
// PRNG seeded from a 48-byte entropy input with domain separator 0x01.
// The KAT harness installs this reader, runs keypair / enc, and prints
// the outputs; the resulting transcript's sha256 must match the
// upstream META.yml `nistkat-sha256` hash for the parameter set.
type hqcKatReader struct {
shake sha3.ShakeHash
}
func newHQCKatReader(entropy []byte) *hqcKatReader {
const domain = byte(0x01) // PRNG_DOMAIN from hqckatrng.c
r := &hqcKatReader{shake: sha3.NewShake256()}
_, _ = r.shake.Write(entropy)
_, _ = r.shake.Write([]byte{domain})
return r
}
func (r *hqcKatReader) Read(p []byte) (int, error) {
return r.shake.Read(p)
}
// runHQCKAT replicates the body of PQClean's hqckat.c:
// - seed `entropy = 0x00..0x2F`
// - install KAT PRNG → squeeze 48-byte seed
// - re-install KAT PRNG with that seed
// - run keypair + enc + dec
// - emit the same plaintext block hqckat.c prints to stdout
//
// The returned string is fed to sha256 and compared with
// nistkat-sha256 from the upstream META.yml.
func runHQCKAT(t *testing.T, mode Mode) string {
t.Helper()
// Step 1: build the 48-byte entropy input 0x00..0x2F.
entropy := make([]byte, 48)
for i := range entropy {
entropy[i] = byte(i)
}
// Step 2: first install — squeeze 48-byte `seed`.
r1 := newHQCKatReader(entropy)
seed := make([]byte, 48)
if _, err := io.ReadFull(r1, seed); err != nil {
t.Fatalf("kat seed squeeze failed: %v", err)
}
// Step 3: second install with `seed` as entropy. This reader is
// the entropy source for keypair + enc.
r2 := newHQCKatReader(seed)
// Step 4: keypair.
sk, err := KeyGen(mode, r2)
if err != nil {
t.Fatalf("KAT keygen: %v", err)
}
pk := sk.Pub
// Step 5: encapsulate (same reader is consumed by enc).
ct, ssEnc, err := Encapsulate(pk, r2)
if err != nil {
t.Fatalf("KAT encapsulate: %v", err)
}
// Step 6: decapsulate must match.
ssDec, err := Decapsulate(sk, ct)
if err != nil {
t.Fatalf("KAT decapsulate: %v", err)
}
if !bytes.Equal(ssEnc, ssDec) {
t.Fatalf("KAT round-trip ss mismatch")
}
// Step 7: emit the same plaintext hqckat.c produces.
var buf strings.Builder
fmt.Fprintf(&buf, "count = 0\n")
fmt.Fprintf(&buf, "seed = %s\n", strings.ToUpper(hex.EncodeToString(seed)))
fmt.Fprintf(&buf, "pk = %s\n", strings.ToUpper(hex.EncodeToString(pk.Bytes)))
fmt.Fprintf(&buf, "sk = %s\n", strings.ToUpper(hex.EncodeToString(sk.Bytes)))
fmt.Fprintf(&buf, "ct = %s\n", strings.ToUpper(hex.EncodeToString(ct.Bytes)))
fmt.Fprintf(&buf, "ss = %s\n", strings.ToUpper(hex.EncodeToString(ssEnc)))
return buf.String()
}
// TestKAT_HQC128 — sha256 of the KAT transcript must equal the
// nistkat-sha256 field of PQClean's
// crypto_kem/hqc-128/META.yml.
func TestKAT_HQC128(t *testing.T) {
const wantHex = "74290ad7a789c01aa92cd7f72f1b5ba5fa30a58294cdf2df52b9c76c3aafba3b"
got := sha256.Sum256([]byte(runHQCKAT(t, HQC128)))
gotHex := hex.EncodeToString(got[:])
if gotHex != wantHex {
t.Fatalf("HQC-128 KAT sha256 mismatch\nwant %s\n got %s", wantHex, gotHex)
}
}
// TestKAT_HQC192 — sha256 must equal the nistkat-sha256 from
// crypto_kem/hqc-192/META.yml.
func TestKAT_HQC192(t *testing.T) {
const wantHex = "771c5e421c4eea951850d5883e2329c40783a3c0f363d8dc8e1be7c4026d1ab7"
got := sha256.Sum256([]byte(runHQCKAT(t, HQC192)))
gotHex := hex.EncodeToString(got[:])
if gotHex != wantHex {
t.Fatalf("HQC-192 KAT sha256 mismatch\nwant %s\n got %s", wantHex, gotHex)
}
}
// TestKAT_HQC256 — sha256 must equal the nistkat-sha256 from
// crypto_kem/hqc-256/META.yml.
func TestKAT_HQC256(t *testing.T) {
const wantHex = "e89c520e1ac615ecf6923f211569177c536a89bf94bebf85bb263528282092c4"
got := sha256.Sum256([]byte(runHQCKAT(t, HQC256)))
gotHex := hex.EncodeToString(got[:])
if gotHex != wantHex {
t.Fatalf("HQC-256 KAT sha256 mismatch\nwant %s\n got %s", wantHex, gotHex)
}
}
// TestRoundTrip_AllModes confirms encapsulate(pk) → decapsulate(sk) ss
// matches across all three parameter sets, using a non-KAT (random)
// seed.
func TestRoundTrip_AllModes(t *testing.T) {
for _, mode := range []Mode{HQC128, HQC192, HQC256} {
t.Run(MustParamsFor(mode).String(), func(t *testing.T) {
seed := bytes.Repeat([]byte{0x42}, 128)
r := newHQCKatReader(seed)
sk, err := KeyGen(mode, r)
if err != nil {
t.Fatalf("keygen: %v", err)
}
r2 := newHQCKatReader(append([]byte{0xAA}, seed...))
ct, ssEnc, err := Encapsulate(sk.Pub, r2)
if err != nil {
t.Fatalf("encapsulate: %v", err)
}
ssDec, err := Decapsulate(sk, ct)
if err != nil {
t.Fatalf("decapsulate: %v", err)
}
if !bytes.Equal(ssEnc, ssDec) {
t.Fatalf("ss mismatch\nenc %x\ndec %x", ssEnc, ssDec)
}
if len(ssEnc) != MustParamsFor(mode).SharedSecretSize {
t.Fatalf("ss size = %d, want %d", len(ssEnc), MustParamsFor(mode).SharedSecretSize)
}
})
}
}
// TestDeterminism — the precompile correctness depends on this: same
// (seed, op) produces byte-identical outputs.
func TestDeterminism(t *testing.T) {
for _, mode := range []Mode{HQC128, HQC192, HQC256} {
t.Run(MustParamsFor(mode).String(), func(t *testing.T) {
seed := bytes.Repeat([]byte{0x37}, 96)
sk1, err := KeyGen(mode, newHQCKatReader(seed))
if err != nil {
t.Fatalf("keygen1: %v", err)
}
sk2, err := KeyGen(mode, newHQCKatReader(seed))
if err != nil {
t.Fatalf("keygen2: %v", err)
}
if !bytes.Equal(sk1.Pub.Bytes, sk2.Pub.Bytes) {
t.Fatalf("non-deterministic pk:\n pk1 = %x\n pk2 = %x",
sk1.Pub.Bytes[:32], sk2.Pub.Bytes[:32])
}
if !bytes.Equal(sk1.Bytes, sk2.Bytes) {
t.Fatalf("non-deterministic sk:\n sk1 = %x\n sk2 = %x",
sk1.Bytes[:32], sk2.Bytes[:32])
}
encSeed := bytes.Repeat([]byte{0x71}, 128)
ct1, ss1, err := Encapsulate(sk1.Pub, newHQCKatReader(encSeed))
if err != nil {
t.Fatalf("enc1: %v", err)
}
ct2, ss2, err := Encapsulate(sk1.Pub, newHQCKatReader(encSeed))
if err != nil {
t.Fatalf("enc2: %v", err)
}
if !bytes.Equal(ct1.Bytes, ct2.Bytes) {
t.Fatalf("non-deterministic ct (load-bearing for precompile)")
}
if !bytes.Equal(ss1, ss2) {
t.Fatalf("non-deterministic ss")
}
})
}
}
// TestPQCleanDecapsulate_ModeMismatch — sk for one parameter set, ct
// for another, must be refused at the Go layer (we cannot feed a
// truncated/oversized ct to PQClean's HQC-N decap or it would read
// past the buffer).
func TestPQCleanDecapsulate_ModeMismatch(t *testing.T) {
sk, err := KeyGen(HQC128, newHQCKatReader(bytes.Repeat([]byte{0x01}, 96)))
if err != nil {
t.Fatalf("keygen: %v", err)
}
ct, _, err := Encapsulate(sk.Pub, newHQCKatReader(bytes.Repeat([]byte{0x02}, 64)))
if err != nil {
t.Fatalf("encapsulate: %v", err)
}
// Pretend ct is HQC-256 and run decap with HQC-128 sk.
ct256 := &Ciphertext{Mode: HQC256, Bytes: ct.Bytes}
if _, err := Decapsulate(sk, ct256); !errors.Is(err, ErrModeMismatch) {
t.Fatalf("want ErrModeMismatch, got %v", err)
}
}
// String helper for subtests.
func (p *Params) String() string {
switch p.Mode {
case HQC128:
return "HQC-128"
case HQC192:
return "HQC-192"
case HQC256:
return "HQC-256"
}
return "HQC-?"
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build !hqc_circl && !hqc_pqclean
package hqc
import (
"crypto/rand"
"errors"
"testing"
)
// TestBackend_StubReturnsErrBackendNotWired confirms the default
// (no-backend-tag) build cleanly surfaces ErrBackendNotWired rather
// than silently returning garbage or nil-derefing.
//
// Lives in a build-tag-gated test file so it does NOT run when a
// real backend (hqc_pqclean / hqc_circl) is wired in.
func TestBackend_StubReturnsErrBackendNotWired(t *testing.T) {
_, err := KeyGen(HQC128, rand.Reader)
if !errors.Is(err, ErrBackendNotWired) {
t.Fatalf("KeyGen with no backend: want ErrBackendNotWired, got %v", err)
}
_, _, err = Encapsulate(&PublicKey{Mode: HQC128, Bytes: make([]byte, 2249)}, rand.Reader)
if !errors.Is(err, ErrBackendNotWired) {
t.Fatalf("Encapsulate with no backend: want ErrBackendNotWired, got %v", err)
}
_, err = Decapsulate(
&PrivateKey{Mode: HQC128, Bytes: make([]byte, 2305)},
&Ciphertext{Mode: HQC128, Bytes: make([]byte, 4433)},
)
if !errors.Is(err, ErrBackendNotWired) {
t.Fatalf("Decapsulate with no backend: want ErrBackendNotWired, got %v", err)
}
}
+10
View File
@@ -0,0 +1,10 @@
//go:build hqc_pqclean
/*
* fips202_amalg.c — single translation unit holding the SHA-3 / SHAKE
* implementation used by all three HQC modes. fips202.c symbols are
* NOT namespaced per-mode upstream, so compiling fips202.c into each
* mode's amalgamation would create duplicate-symbol linker errors.
* Pull it in exactly once here.
*/
#include "pqclean/common/fips202.c"
+26 -20
View File
@@ -19,19 +19,19 @@
// Post-Quantum Cryptography Standardization Process" (March 2025).
// Reference: https://pqc-hqc.org/.
//
// Backend status: this package declares the KEM interface and parameter
// sets. The compute body is satisfied by either of:
// Backends:
//
// 1. cloudflare/circl HQC support (when CIRCL ships it — actively
// tracked at https://github.com/cloudflare/circl).
// 2. cgo binding to PQClean's HQC reference implementation
// (https://github.com/PQClean/PQClean). Build tag `hqc_pqclean`
// activates the cgo path; default builds use the CIRCL backend
// once available.
//
// Until either backend is wired, every operation returns
// ErrBackendNotWired. The wire format and parameter constants are
// fixed by NIST IR 8528 §4.1 and are stable.
// 1. PQClean cgo (default for non-stub builds). Build with
// `-tags=hqc_pqclean`. Wires the vendored PQClean HQC reference
// implementation under crypto/hqc/pqclean/ (public domain). NIST
// KAT vectors verify byte-for-byte — see
// crypto/hqc/backend_pqclean_test.go's TestKAT_HQC{128,192,256}.
// 2. Cloudflare CIRCL Go (placeholder; build tag `hqc_circl`).
// Activates once CIRCL ships HQC support.
// 3. Stub (default). When no backend tag is set, every operation
// returns ErrBackendNotWired so the package still compiles on
// hosts without cgo. The wire format and parameter constants are
// fixed by NIST IR 8528 §4.1 and remain stable across backends.
package hqc
import (
@@ -44,15 +44,15 @@ type Mode int
const (
// HQC128 — NIST PQ Security Category 1 (≈ AES-128).
// Public key: 2,249 B, ciphertext: 4,481 B, shared secret: 64 B.
// Public key: 2,249 B, ciphertext: 4,433 B, shared secret: 64 B.
HQC128 Mode = iota
// HQC192 — NIST PQ Security Category 3 (≈ AES-192).
// Public key: 4,522 B, ciphertext: 9,026 B, shared secret: 64 B.
// Public key: 4,522 B, ciphertext: 8,978 B, shared secret: 64 B.
HQC192
// HQC256 — NIST PQ Security Category 5 (≈ AES-256).
// Public key: 7,245 B, ciphertext: 14,469 B, shared secret: 64 B.
// Public key: 7,245 B, ciphertext: 14,421 B, shared secret: 64 B.
HQC256
)
@@ -67,8 +67,14 @@ type Params struct {
}
// MustParamsFor returns the canonical Params for `mode` or panics on
// an unrecognised Mode. The values are NIST-fixed (HQC specification
// 2023 round-4 + NIST IR 8528 §4.1).
// an unrecognised Mode. The values are byte-for-byte the PQClean
// HQC reference (https://github.com/PQClean/PQClean,
// hqc-submission_2023-04-30): see crypto/hqc/pqclean/hqc-N/clean/api.h.
//
// The ciphertext is structured as `u || v || salt` where `u` and `v`
// are the IND-CPA ciphertext halves and `salt` is a 16-byte value
// absorbed into the FO-transform hash. NIST IR 8528 §4.1 references
// this exact wire format.
func MustParamsFor(mode Mode) *Params {
switch mode {
case HQC128:
@@ -76,7 +82,7 @@ func MustParamsFor(mode Mode) *Params {
Mode: HQC128,
PublicKeySize: 2249,
PrivateKeySize: 2305,
CiphertextSize: 4481,
CiphertextSize: 4433,
SharedSecretSize: 64,
SecurityLevelBits: 128,
}
@@ -85,7 +91,7 @@ func MustParamsFor(mode Mode) *Params {
Mode: HQC192,
PublicKeySize: 4522,
PrivateKeySize: 4586,
CiphertextSize: 9026,
CiphertextSize: 8978,
SharedSecretSize: 64,
SecurityLevelBits: 192,
}
@@ -94,7 +100,7 @@ func MustParamsFor(mode Mode) *Params {
Mode: HQC256,
PublicKeySize: 7245,
PrivateKeySize: 7317,
CiphertextSize: 14469,
CiphertextSize: 14421,
SharedSecretSize: 64,
SecurityLevelBits: 256,
}
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 code.c as its own TU. */
#include "pqclean/hqc-128/clean/code.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 fft.c as its own TU. */
#include "pqclean/hqc-128/clean/fft.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 gf.c as its own TU. */
#include "pqclean/hqc-128/clean/gf.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 gf2x.c as its own TU. */
#include "pqclean/hqc-128/clean/gf2x.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 hqc.c as its own TU. */
#include "pqclean/hqc-128/clean/hqc.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 kem.c as its own TU. */
#include "pqclean/hqc-128/clean/kem.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 parsing.c as its own TU. */
#include "pqclean/hqc-128/clean/parsing.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 reed_muller.c as its own TU. */
#include "pqclean/hqc-128/clean/reed_muller.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 reed_solomon.c as its own TU. */
#include "pqclean/hqc-128/clean/reed_solomon.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 shake_ds.c as its own TU. */
#include "pqclean/hqc-128/clean/shake_ds.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 shake_prng.c as its own TU. */
#include "pqclean/hqc-128/clean/shake_prng.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-128 vector.c as its own TU. */
#include "pqclean/hqc-128/clean/vector.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 code.c as its own TU. */
#include "pqclean/hqc-192/clean/code.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 fft.c as its own TU. */
#include "pqclean/hqc-192/clean/fft.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 gf.c as its own TU. */
#include "pqclean/hqc-192/clean/gf.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 gf2x.c as its own TU. */
#include "pqclean/hqc-192/clean/gf2x.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 hqc.c as its own TU. */
#include "pqclean/hqc-192/clean/hqc.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 kem.c as its own TU. */
#include "pqclean/hqc-192/clean/kem.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 parsing.c as its own TU. */
#include "pqclean/hqc-192/clean/parsing.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 reed_muller.c as its own TU. */
#include "pqclean/hqc-192/clean/reed_muller.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 reed_solomon.c as its own TU. */
#include "pqclean/hqc-192/clean/reed_solomon.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 shake_ds.c as its own TU. */
#include "pqclean/hqc-192/clean/shake_ds.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 shake_prng.c as its own TU. */
#include "pqclean/hqc-192/clean/shake_prng.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-192 vector.c as its own TU. */
#include "pqclean/hqc-192/clean/vector.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 code.c as its own TU. */
#include "pqclean/hqc-256/clean/code.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 fft.c as its own TU. */
#include "pqclean/hqc-256/clean/fft.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 gf.c as its own TU. */
#include "pqclean/hqc-256/clean/gf.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 gf2x.c as its own TU. */
#include "pqclean/hqc-256/clean/gf2x.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 hqc.c as its own TU. */
#include "pqclean/hqc-256/clean/hqc.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 kem.c as its own TU. */
#include "pqclean/hqc-256/clean/kem.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 parsing.c as its own TU. */
#include "pqclean/hqc-256/clean/parsing.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 reed_muller.c as its own TU. */
#include "pqclean/hqc-256/clean/reed_muller.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 reed_solomon.c as its own TU. */
#include "pqclean/hqc-256/clean/reed_solomon.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 shake_ds.c as its own TU. */
#include "pqclean/hqc-256/clean/shake_ds.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 shake_prng.c as its own TU. */
#include "pqclean/hqc-256/clean/shake_prng.c"
+4
View File
@@ -0,0 +1,4 @@
//go:build hqc_pqclean
/* Auto-shim: HQC-256 vector.c as its own TU. */
#include "pqclean/hqc-256/clean/vector.c"
+7 -28
View File
@@ -4,7 +4,6 @@
package hqc
import (
"crypto/rand"
"errors"
"testing"
)
@@ -19,9 +18,12 @@ func TestParams_FixedSizes(t *testing.T) {
ct, ss int
secBits int
}{
{HQC128, 2249, 2305, 4481, 64, 128},
{HQC192, 4522, 4586, 9026, 64, 192},
{HQC256, 7245, 7317, 14469, 64, 256},
// Sizes match the vendored PQClean reference
// (hqc-submission_2023-04-30): see
// crypto/hqc/pqclean/hqc-N/clean/api.h.
{HQC128, 2249, 2305, 4433, 64, 128},
{HQC192, 4522, 4586, 8978, 64, 192},
{HQC256, 7245, 7317, 14421, 64, 256},
}
for _, c := range cases {
p := MustParamsFor(c.mode)
@@ -40,36 +42,13 @@ func TestParams_FixedSizes(t *testing.T) {
}
}
// TestBackend_StubReturnsErrBackendNotWired confirms the default
// (no-backend-tag) build cleanly surfaces ErrBackendNotWired rather
// than silently returning garbage or nil-derefing.
func TestBackend_StubReturnsErrBackendNotWired(t *testing.T) {
_, err := KeyGen(HQC128, rand.Reader)
if !errors.Is(err, ErrBackendNotWired) {
t.Fatalf("KeyGen with no backend: want ErrBackendNotWired, got %v", err)
}
_, _, err = Encapsulate(&PublicKey{Mode: HQC128, Bytes: make([]byte, 2249)}, rand.Reader)
if !errors.Is(err, ErrBackendNotWired) {
t.Fatalf("Encapsulate with no backend: want ErrBackendNotWired, got %v", err)
}
_, err = Decapsulate(
&PrivateKey{Mode: HQC128, Bytes: make([]byte, 2305)},
&Ciphertext{Mode: HQC128, Bytes: make([]byte, 4481)},
)
if !errors.Is(err, ErrBackendNotWired) {
t.Fatalf("Decapsulate with no backend: want ErrBackendNotWired, got %v", err)
}
}
// TestDecapsulate_ModeMismatch — a ciphertext from one parameter set
// fed to a private key for another must return ErrModeMismatch, not
// silently produce a bogus shared secret.
func TestDecapsulate_ModeMismatch(t *testing.T) {
_, err := Decapsulate(
&PrivateKey{Mode: HQC128, Bytes: make([]byte, 2305)},
&Ciphertext{Mode: HQC256, Bytes: make([]byte, 14469)},
&Ciphertext{Mode: HQC256, Bytes: make([]byte, 14421)},
)
if !errors.Is(err, ErrModeMismatch) {
t.Errorf("mode mismatch: want ErrModeMismatch, got %v", err)
+39
View File
@@ -0,0 +1,39 @@
PQClean HQC vendored sources
============================
The C sources under crypto/hqc/pqclean/hqc-{128,192,256}/clean/ are
copied verbatim from PQClean (https://github.com/PQClean/PQClean),
specifically from crypto_kem/hqc-{128,192,256}/clean/ at the upstream
state `hqc-submission_2023-04-30` (commit referenced in each scheme's
META.yml under `implementations`).
The HQC reference is licensed by its authors as Public Domain; see
hqc-{128,192,256}/clean/LICENSE in each subdirectory.
The shared SHAKE/SHA-3 implementation (pqclean/common/fips202.c,
fips202.h) is taken from PQClean/common/, originally derived from the
public-domain Keccak Team reference (https://keccak.team/).
Modifications by Lux
--------------------
- Added pqclean/common/randombytes.h that declares only `randombytes(
uint8_t*, size_t)`. The HQC sources `#include "randombytes.h"` and
resolve the symbol at link time. The actual implementation lives in
crypto/hqc/randombytes_shim.c and forwards to a Go callback so that
the EVM precompile can drive HQC encapsulation from a caller-
supplied deterministic seed (see crypto/hqc/backend_pqclean.go).
- No changes to algorithmic code in hqc-*/clean/ — the HQC sources
are byte-for-byte upstream so NIST KAT vectors verify unchanged
(crypto/hqc/backend_pqclean_test.go's TestKAT_HQC{128,192,256}
compare sha256 of the KAT transcript against the values from
PQClean's META.yml).
Original PQClean license
------------------------
PQClean and its authors place the HQC reference into the public domain
("Public Domain", see each hqc-*/clean/LICENSE file). The randombytes
infrastructure file from PQClean/common/randombytes.c carries an MIT
license (Daan Sprenkels) — we do not vendor that file; we ship our own
shim instead.
+928
View File
@@ -0,0 +1,928 @@
/* Based on the public domain implementation in
* crypto_hash/keccakc512/simple/ from http://bench.cr.yp.to/supercop.html
* by Ronny Van Keer
* and the public domain "TweetFips202" implementation
* from https://twitter.com/tweetfips202
* by Gilles Van Assche, Daniel J. Bernstein, and Peter Schwabe */
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "fips202.h"
#define NROUNDS 24
#define ROL(a, offset) (((a) << (offset)) ^ ((a) >> (64 - (offset))))
/*************************************************
* Name: load64
*
* Description: Load 8 bytes into uint64_t in little-endian order
*
* Arguments: - const uint8_t *x: pointer to input byte array
*
* Returns the loaded 64-bit unsigned integer
**************************************************/
static uint64_t load64(const uint8_t *x) {
uint64_t r = 0;
for (size_t i = 0; i < 8; ++i) {
r |= (uint64_t)x[i] << 8 * i;
}
return r;
}
/*************************************************
* Name: store64
*
* Description: Store a 64-bit integer to a byte array in little-endian order
*
* Arguments: - uint8_t *x: pointer to the output byte array
* - uint64_t u: input 64-bit unsigned integer
**************************************************/
static void store64(uint8_t *x, uint64_t u) {
for (size_t i = 0; i < 8; ++i) {
x[i] = (uint8_t) (u >> 8 * i);
}
}
/* Keccak round constants */
static const uint64_t KeccakF_RoundConstants[NROUNDS] = {
0x0000000000000001ULL, 0x0000000000008082ULL,
0x800000000000808aULL, 0x8000000080008000ULL,
0x000000000000808bULL, 0x0000000080000001ULL,
0x8000000080008081ULL, 0x8000000000008009ULL,
0x000000000000008aULL, 0x0000000000000088ULL,
0x0000000080008009ULL, 0x000000008000000aULL,
0x000000008000808bULL, 0x800000000000008bULL,
0x8000000000008089ULL, 0x8000000000008003ULL,
0x8000000000008002ULL, 0x8000000000000080ULL,
0x000000000000800aULL, 0x800000008000000aULL,
0x8000000080008081ULL, 0x8000000000008080ULL,
0x0000000080000001ULL, 0x8000000080008008ULL
};
/*************************************************
* Name: KeccakF1600_StatePermute
*
* Description: The Keccak F1600 Permutation
*
* Arguments: - uint64_t *state: pointer to input/output Keccak state
**************************************************/
static void KeccakF1600_StatePermute(uint64_t *state) {
int round;
uint64_t Aba, Abe, Abi, Abo, Abu;
uint64_t Aga, Age, Agi, Ago, Agu;
uint64_t Aka, Ake, Aki, Ako, Aku;
uint64_t Ama, Ame, Ami, Amo, Amu;
uint64_t Asa, Ase, Asi, Aso, Asu;
uint64_t BCa, BCe, BCi, BCo, BCu;
uint64_t Da, De, Di, Do, Du;
uint64_t Eba, Ebe, Ebi, Ebo, Ebu;
uint64_t Ega, Ege, Egi, Ego, Egu;
uint64_t Eka, Eke, Eki, Eko, Eku;
uint64_t Ema, Eme, Emi, Emo, Emu;
uint64_t Esa, Ese, Esi, Eso, Esu;
// copyFromState(A, state)
Aba = state[0];
Abe = state[1];
Abi = state[2];
Abo = state[3];
Abu = state[4];
Aga = state[5];
Age = state[6];
Agi = state[7];
Ago = state[8];
Agu = state[9];
Aka = state[10];
Ake = state[11];
Aki = state[12];
Ako = state[13];
Aku = state[14];
Ama = state[15];
Ame = state[16];
Ami = state[17];
Amo = state[18];
Amu = state[19];
Asa = state[20];
Ase = state[21];
Asi = state[22];
Aso = state[23];
Asu = state[24];
for (round = 0; round < NROUNDS; round += 2) {
// prepareTheta
BCa = Aba ^ Aga ^ Aka ^ Ama ^ Asa;
BCe = Abe ^ Age ^ Ake ^ Ame ^ Ase;
BCi = Abi ^ Agi ^ Aki ^ Ami ^ Asi;
BCo = Abo ^ Ago ^ Ako ^ Amo ^ Aso;
BCu = Abu ^ Agu ^ Aku ^ Amu ^ Asu;
// thetaRhoPiChiIotaPrepareTheta(round , A, E)
Da = BCu ^ ROL(BCe, 1);
De = BCa ^ ROL(BCi, 1);
Di = BCe ^ ROL(BCo, 1);
Do = BCi ^ ROL(BCu, 1);
Du = BCo ^ ROL(BCa, 1);
Aba ^= Da;
BCa = Aba;
Age ^= De;
BCe = ROL(Age, 44);
Aki ^= Di;
BCi = ROL(Aki, 43);
Amo ^= Do;
BCo = ROL(Amo, 21);
Asu ^= Du;
BCu = ROL(Asu, 14);
Eba = BCa ^ ((~BCe) & BCi);
Eba ^= KeccakF_RoundConstants[round];
Ebe = BCe ^ ((~BCi) & BCo);
Ebi = BCi ^ ((~BCo) & BCu);
Ebo = BCo ^ ((~BCu) & BCa);
Ebu = BCu ^ ((~BCa) & BCe);
Abo ^= Do;
BCa = ROL(Abo, 28);
Agu ^= Du;
BCe = ROL(Agu, 20);
Aka ^= Da;
BCi = ROL(Aka, 3);
Ame ^= De;
BCo = ROL(Ame, 45);
Asi ^= Di;
BCu = ROL(Asi, 61);
Ega = BCa ^ ((~BCe) & BCi);
Ege = BCe ^ ((~BCi) & BCo);
Egi = BCi ^ ((~BCo) & BCu);
Ego = BCo ^ ((~BCu) & BCa);
Egu = BCu ^ ((~BCa) & BCe);
Abe ^= De;
BCa = ROL(Abe, 1);
Agi ^= Di;
BCe = ROL(Agi, 6);
Ako ^= Do;
BCi = ROL(Ako, 25);
Amu ^= Du;
BCo = ROL(Amu, 8);
Asa ^= Da;
BCu = ROL(Asa, 18);
Eka = BCa ^ ((~BCe) & BCi);
Eke = BCe ^ ((~BCi) & BCo);
Eki = BCi ^ ((~BCo) & BCu);
Eko = BCo ^ ((~BCu) & BCa);
Eku = BCu ^ ((~BCa) & BCe);
Abu ^= Du;
BCa = ROL(Abu, 27);
Aga ^= Da;
BCe = ROL(Aga, 36);
Ake ^= De;
BCi = ROL(Ake, 10);
Ami ^= Di;
BCo = ROL(Ami, 15);
Aso ^= Do;
BCu = ROL(Aso, 56);
Ema = BCa ^ ((~BCe) & BCi);
Eme = BCe ^ ((~BCi) & BCo);
Emi = BCi ^ ((~BCo) & BCu);
Emo = BCo ^ ((~BCu) & BCa);
Emu = BCu ^ ((~BCa) & BCe);
Abi ^= Di;
BCa = ROL(Abi, 62);
Ago ^= Do;
BCe = ROL(Ago, 55);
Aku ^= Du;
BCi = ROL(Aku, 39);
Ama ^= Da;
BCo = ROL(Ama, 41);
Ase ^= De;
BCu = ROL(Ase, 2);
Esa = BCa ^ ((~BCe) & BCi);
Ese = BCe ^ ((~BCi) & BCo);
Esi = BCi ^ ((~BCo) & BCu);
Eso = BCo ^ ((~BCu) & BCa);
Esu = BCu ^ ((~BCa) & BCe);
// prepareTheta
BCa = Eba ^ Ega ^ Eka ^ Ema ^ Esa;
BCe = Ebe ^ Ege ^ Eke ^ Eme ^ Ese;
BCi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi;
BCo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso;
BCu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu;
// thetaRhoPiChiIotaPrepareTheta(round+1, E, A)
Da = BCu ^ ROL(BCe, 1);
De = BCa ^ ROL(BCi, 1);
Di = BCe ^ ROL(BCo, 1);
Do = BCi ^ ROL(BCu, 1);
Du = BCo ^ ROL(BCa, 1);
Eba ^= Da;
BCa = Eba;
Ege ^= De;
BCe = ROL(Ege, 44);
Eki ^= Di;
BCi = ROL(Eki, 43);
Emo ^= Do;
BCo = ROL(Emo, 21);
Esu ^= Du;
BCu = ROL(Esu, 14);
Aba = BCa ^ ((~BCe) & BCi);
Aba ^= KeccakF_RoundConstants[round + 1];
Abe = BCe ^ ((~BCi) & BCo);
Abi = BCi ^ ((~BCo) & BCu);
Abo = BCo ^ ((~BCu) & BCa);
Abu = BCu ^ ((~BCa) & BCe);
Ebo ^= Do;
BCa = ROL(Ebo, 28);
Egu ^= Du;
BCe = ROL(Egu, 20);
Eka ^= Da;
BCi = ROL(Eka, 3);
Eme ^= De;
BCo = ROL(Eme, 45);
Esi ^= Di;
BCu = ROL(Esi, 61);
Aga = BCa ^ ((~BCe) & BCi);
Age = BCe ^ ((~BCi) & BCo);
Agi = BCi ^ ((~BCo) & BCu);
Ago = BCo ^ ((~BCu) & BCa);
Agu = BCu ^ ((~BCa) & BCe);
Ebe ^= De;
BCa = ROL(Ebe, 1);
Egi ^= Di;
BCe = ROL(Egi, 6);
Eko ^= Do;
BCi = ROL(Eko, 25);
Emu ^= Du;
BCo = ROL(Emu, 8);
Esa ^= Da;
BCu = ROL(Esa, 18);
Aka = BCa ^ ((~BCe) & BCi);
Ake = BCe ^ ((~BCi) & BCo);
Aki = BCi ^ ((~BCo) & BCu);
Ako = BCo ^ ((~BCu) & BCa);
Aku = BCu ^ ((~BCa) & BCe);
Ebu ^= Du;
BCa = ROL(Ebu, 27);
Ega ^= Da;
BCe = ROL(Ega, 36);
Eke ^= De;
BCi = ROL(Eke, 10);
Emi ^= Di;
BCo = ROL(Emi, 15);
Eso ^= Do;
BCu = ROL(Eso, 56);
Ama = BCa ^ ((~BCe) & BCi);
Ame = BCe ^ ((~BCi) & BCo);
Ami = BCi ^ ((~BCo) & BCu);
Amo = BCo ^ ((~BCu) & BCa);
Amu = BCu ^ ((~BCa) & BCe);
Ebi ^= Di;
BCa = ROL(Ebi, 62);
Ego ^= Do;
BCe = ROL(Ego, 55);
Eku ^= Du;
BCi = ROL(Eku, 39);
Ema ^= Da;
BCo = ROL(Ema, 41);
Ese ^= De;
BCu = ROL(Ese, 2);
Asa = BCa ^ ((~BCe) & BCi);
Ase = BCe ^ ((~BCi) & BCo);
Asi = BCi ^ ((~BCo) & BCu);
Aso = BCo ^ ((~BCu) & BCa);
Asu = BCu ^ ((~BCa) & BCe);
}
// copyToState(state, A)
state[0] = Aba;
state[1] = Abe;
state[2] = Abi;
state[3] = Abo;
state[4] = Abu;
state[5] = Aga;
state[6] = Age;
state[7] = Agi;
state[8] = Ago;
state[9] = Agu;
state[10] = Aka;
state[11] = Ake;
state[12] = Aki;
state[13] = Ako;
state[14] = Aku;
state[15] = Ama;
state[16] = Ame;
state[17] = Ami;
state[18] = Amo;
state[19] = Amu;
state[20] = Asa;
state[21] = Ase;
state[22] = Asi;
state[23] = Aso;
state[24] = Asu;
}
/*************************************************
* Name: keccak_absorb
*
* Description: Absorb step of Keccak;
* non-incremental, starts by zeroeing the state.
*
* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
* - const uint8_t *m: pointer to input to be absorbed into s
* - size_t mlen: length of input in bytes
* - uint8_t p: domain-separation byte for different
* Keccak-derived functions
**************************************************/
static void keccak_absorb(uint64_t *s, uint32_t r, const uint8_t *m,
size_t mlen, uint8_t p) {
size_t i;
uint8_t t[200];
/* Zero state */
for (i = 0; i < 25; ++i) {
s[i] = 0;
}
while (mlen >= r) {
for (i = 0; i < r / 8; ++i) {
s[i] ^= load64(m + 8 * i);
}
KeccakF1600_StatePermute(s);
mlen -= r;
m += r;
}
for (i = 0; i < r; ++i) {
t[i] = 0;
}
for (i = 0; i < mlen; ++i) {
t[i] = m[i];
}
t[i] = p;
t[r - 1] |= 128;
for (i = 0; i < r / 8; ++i) {
s[i] ^= load64(t + 8 * i);
}
}
/*************************************************
* Name: keccak_squeezeblocks
*
* Description: Squeeze step of Keccak. Squeezes full blocks of r bytes each.
* Modifies the state. Can be called multiple times to keep
* squeezing, i.e., is incremental.
*
* Arguments: - uint8_t *h: pointer to output blocks
* - size_t nblocks: number of blocks to be
* squeezed (written to h)
* - uint64_t *s: pointer to input/output Keccak state
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
**************************************************/
static void keccak_squeezeblocks(uint8_t *h, size_t nblocks,
uint64_t *s, uint32_t r) {
while (nblocks > 0) {
KeccakF1600_StatePermute(s);
for (size_t i = 0; i < (r >> 3); i++) {
store64(h + 8 * i, s[i]);
}
h += r;
nblocks--;
}
}
/*************************************************
* Name: keccak_inc_init
*
* Description: Initializes the incremental Keccak state to zero.
*
* Arguments: - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
**************************************************/
static void keccak_inc_init(uint64_t *s_inc) {
size_t i;
for (i = 0; i < 25; ++i) {
s_inc[i] = 0;
}
s_inc[25] = 0;
}
/*************************************************
* Name: keccak_inc_absorb
*
* Description: Incremental keccak absorb
* Preceded by keccak_inc_init, succeeded by keccak_inc_finalize
*
* Arguments: - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
* - const uint8_t *m: pointer to input to be absorbed into s
* - size_t mlen: length of input in bytes
**************************************************/
static void keccak_inc_absorb(uint64_t *s_inc, uint32_t r, const uint8_t *m,
size_t mlen) {
size_t i;
/* Recall that s_inc[25] is the non-absorbed bytes xored into the state */
while (mlen + s_inc[25] >= r) {
for (i = 0; i < r - (uint32_t)s_inc[25]; i++) {
/* Take the i'th byte from message
xor with the s_inc[25] + i'th byte of the state; little-endian */
s_inc[(s_inc[25] + i) >> 3] ^= (uint64_t)m[i] << (8 * ((s_inc[25] + i) & 0x07));
}
mlen -= (size_t)(r - s_inc[25]);
m += r - s_inc[25];
s_inc[25] = 0;
KeccakF1600_StatePermute(s_inc);
}
for (i = 0; i < mlen; i++) {
s_inc[(s_inc[25] + i) >> 3] ^= (uint64_t)m[i] << (8 * ((s_inc[25] + i) & 0x07));
}
s_inc[25] += mlen;
}
/*************************************************
* Name: keccak_inc_finalize
*
* Description: Finalizes Keccak absorb phase, prepares for squeezing
*
* Arguments: - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
* - uint8_t p: domain-separation byte for different
* Keccak-derived functions
**************************************************/
static void keccak_inc_finalize(uint64_t *s_inc, uint32_t r, uint8_t p) {
/* After keccak_inc_absorb, we are guaranteed that s_inc[25] < r,
so we can always use one more byte for p in the current state. */
s_inc[s_inc[25] >> 3] ^= (uint64_t)p << (8 * (s_inc[25] & 0x07));
s_inc[(r - 1) >> 3] ^= (uint64_t)128 << (8 * ((r - 1) & 0x07));
s_inc[25] = 0;
}
/*************************************************
* Name: keccak_inc_squeeze
*
* Description: Incremental Keccak squeeze; can be called on byte-level
*
* Arguments: - uint8_t *h: pointer to output bytes
* - size_t outlen: number of bytes to be squeezed
* - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
**************************************************/
static void keccak_inc_squeeze(uint8_t *h, size_t outlen,
uint64_t *s_inc, uint32_t r) {
size_t i;
/* First consume any bytes we still have sitting around */
for (i = 0; i < outlen && i < s_inc[25]; i++) {
/* There are s_inc[25] bytes left, so r - s_inc[25] is the first
available byte. We consume from there, i.e., up to r. */
h[i] = (uint8_t)(s_inc[(r - s_inc[25] + i) >> 3] >> (8 * ((r - s_inc[25] + i) & 0x07)));
}
h += i;
outlen -= i;
s_inc[25] -= i;
/* Then squeeze the remaining necessary blocks */
while (outlen > 0) {
KeccakF1600_StatePermute(s_inc);
for (i = 0; i < outlen && i < r; i++) {
h[i] = (uint8_t)(s_inc[i >> 3] >> (8 * (i & 0x07)));
}
h += i;
outlen -= i;
s_inc[25] = r - i;
}
}
void shake128_inc_init(shake128incctx *state) {
state->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (state->ctx == NULL) {
exit(111);
}
keccak_inc_init(state->ctx);
}
void shake128_inc_absorb(shake128incctx *state, const uint8_t *input, size_t inlen) {
keccak_inc_absorb(state->ctx, SHAKE128_RATE, input, inlen);
}
void shake128_inc_finalize(shake128incctx *state) {
keccak_inc_finalize(state->ctx, SHAKE128_RATE, 0x1F);
}
void shake128_inc_squeeze(uint8_t *output, size_t outlen, shake128incctx *state) {
keccak_inc_squeeze(output, outlen, state->ctx, SHAKE128_RATE);
}
void shake128_inc_ctx_clone(shake128incctx *dest, const shake128incctx *src) {
dest->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (dest->ctx == NULL) {
exit(111);
}
memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
}
void shake128_inc_ctx_release(shake128incctx *state) {
free(state->ctx);
}
void shake256_inc_init(shake256incctx *state) {
state->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (state->ctx == NULL) {
exit(111);
}
keccak_inc_init(state->ctx);
}
void shake256_inc_absorb(shake256incctx *state, const uint8_t *input, size_t inlen) {
keccak_inc_absorb(state->ctx, SHAKE256_RATE, input, inlen);
}
void shake256_inc_finalize(shake256incctx *state) {
keccak_inc_finalize(state->ctx, SHAKE256_RATE, 0x1F);
}
void shake256_inc_squeeze(uint8_t *output, size_t outlen, shake256incctx *state) {
keccak_inc_squeeze(output, outlen, state->ctx, SHAKE256_RATE);
}
void shake256_inc_ctx_clone(shake256incctx *dest, const shake256incctx *src) {
dest->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (dest->ctx == NULL) {
exit(111);
}
memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
}
void shake256_inc_ctx_release(shake256incctx *state) {
free(state->ctx);
}
/*************************************************
* Name: shake128_absorb
*
* Description: Absorb step of the SHAKE128 XOF.
* non-incremental, starts by zeroeing the state.
*
* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state
* - const uint8_t *input: pointer to input to be absorbed
* into s
* - size_t inlen: length of input in bytes
**************************************************/
void shake128_absorb(shake128ctx *state, const uint8_t *input, size_t inlen) {
state->ctx = malloc(PQC_SHAKECTX_BYTES);
if (state->ctx == NULL) {
exit(111);
}
keccak_absorb(state->ctx, SHAKE128_RATE, input, inlen, 0x1F);
}
/*************************************************
* Name: shake128_squeezeblocks
*
* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of
* SHAKE128_RATE bytes each. Modifies the state. Can be called
* multiple times to keep squeezing, i.e., is incremental.
*
* Arguments: - uint8_t *output: pointer to output blocks
* - size_t nblocks: number of blocks to be squeezed
* (written to output)
* - shake128ctx *state: pointer to input/output Keccak state
**************************************************/
void shake128_squeezeblocks(uint8_t *output, size_t nblocks, shake128ctx *state) {
keccak_squeezeblocks(output, nblocks, state->ctx, SHAKE128_RATE);
}
void shake128_ctx_clone(shake128ctx *dest, const shake128ctx *src) {
dest->ctx = malloc(PQC_SHAKECTX_BYTES);
if (dest->ctx == NULL) {
exit(111);
}
memcpy(dest->ctx, src->ctx, PQC_SHAKECTX_BYTES);
}
/** Release the allocated state. Call only once. */
void shake128_ctx_release(shake128ctx *state) {
free(state->ctx);
}
/*************************************************
* Name: shake256_absorb
*
* Description: Absorb step of the SHAKE256 XOF.
* non-incremental, starts by zeroeing the state.
*
* Arguments: - shake256ctx *state: pointer to (uninitialized) output Keccak state
* - const uint8_t *input: pointer to input to be absorbed
* into s
* - size_t inlen: length of input in bytes
**************************************************/
void shake256_absorb(shake256ctx *state, const uint8_t *input, size_t inlen) {
state->ctx = malloc(PQC_SHAKECTX_BYTES);
if (state->ctx == NULL) {
exit(111);
}
keccak_absorb(state->ctx, SHAKE256_RATE, input, inlen, 0x1F);
}
/*************************************************
* Name: shake256_squeezeblocks
*
* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of
* SHAKE256_RATE bytes each. Modifies the state. Can be called
* multiple times to keep squeezing, i.e., is incremental.
*
* Arguments: - uint8_t *output: pointer to output blocks
* - size_t nblocks: number of blocks to be squeezed
* (written to output)
* - shake256ctx *state: pointer to input/output Keccak state
**************************************************/
void shake256_squeezeblocks(uint8_t *output, size_t nblocks, shake256ctx *state) {
keccak_squeezeblocks(output, nblocks, state->ctx, SHAKE256_RATE);
}
void shake256_ctx_clone(shake256ctx *dest, const shake256ctx *src) {
dest->ctx = malloc(PQC_SHAKECTX_BYTES);
if (dest->ctx == NULL) {
exit(111);
}
memcpy(dest->ctx, src->ctx, PQC_SHAKECTX_BYTES);
}
/** Release the allocated state. Call only once. */
void shake256_ctx_release(shake256ctx *state) {
free(state->ctx);
}
/*************************************************
* Name: shake128
*
* Description: SHAKE128 XOF with non-incremental API
*
* Arguments: - uint8_t *output: pointer to output
* - size_t outlen: requested output length in bytes
* - const uint8_t *input: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void shake128(uint8_t *output, size_t outlen,
const uint8_t *input, size_t inlen) {
size_t nblocks = outlen / SHAKE128_RATE;
uint8_t t[SHAKE128_RATE];
shake128ctx s;
shake128_absorb(&s, input, inlen);
shake128_squeezeblocks(output, nblocks, &s);
output += nblocks * SHAKE128_RATE;
outlen -= nblocks * SHAKE128_RATE;
if (outlen) {
shake128_squeezeblocks(t, 1, &s);
for (size_t i = 0; i < outlen; ++i) {
output[i] = t[i];
}
}
shake128_ctx_release(&s);
}
/*************************************************
* Name: shake256
*
* Description: SHAKE256 XOF with non-incremental API
*
* Arguments: - uint8_t *output: pointer to output
* - size_t outlen: requested output length in bytes
* - const uint8_t *input: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void shake256(uint8_t *output, size_t outlen,
const uint8_t *input, size_t inlen) {
size_t nblocks = outlen / SHAKE256_RATE;
uint8_t t[SHAKE256_RATE];
shake256ctx s;
shake256_absorb(&s, input, inlen);
shake256_squeezeblocks(output, nblocks, &s);
output += nblocks * SHAKE256_RATE;
outlen -= nblocks * SHAKE256_RATE;
if (outlen) {
shake256_squeezeblocks(t, 1, &s);
for (size_t i = 0; i < outlen; ++i) {
output[i] = t[i];
}
}
shake256_ctx_release(&s);
}
void sha3_256_inc_init(sha3_256incctx *state) {
state->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (state->ctx == NULL) {
exit(111);
}
keccak_inc_init(state->ctx);
}
void sha3_256_inc_ctx_clone(sha3_256incctx *dest, const sha3_256incctx *src) {
dest->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (dest->ctx == NULL) {
exit(111);
}
memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
}
void sha3_256_inc_ctx_release(sha3_256incctx *state) {
free(state->ctx);
}
void sha3_256_inc_absorb(sha3_256incctx *state, const uint8_t *input, size_t inlen) {
keccak_inc_absorb(state->ctx, SHA3_256_RATE, input, inlen);
}
void sha3_256_inc_finalize(uint8_t *output, sha3_256incctx *state) {
uint8_t t[SHA3_256_RATE];
keccak_inc_finalize(state->ctx, SHA3_256_RATE, 0x06);
keccak_squeezeblocks(t, 1, state->ctx, SHA3_256_RATE);
sha3_256_inc_ctx_release(state);
for (size_t i = 0; i < 32; i++) {
output[i] = t[i];
}
}
/*************************************************
* Name: sha3_256
*
* Description: SHA3-256 with non-incremental API
*
* Arguments: - uint8_t *output: pointer to output
* - const uint8_t *input: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void sha3_256(uint8_t *output, const uint8_t *input, size_t inlen) {
uint64_t s[25];
uint8_t t[SHA3_256_RATE];
/* Absorb input */
keccak_absorb(s, SHA3_256_RATE, input, inlen, 0x06);
/* Squeeze output */
keccak_squeezeblocks(t, 1, s, SHA3_256_RATE);
for (size_t i = 0; i < 32; i++) {
output[i] = t[i];
}
}
void sha3_384_inc_init(sha3_384incctx *state) {
state->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (state->ctx == NULL) {
exit(111);
}
keccak_inc_init(state->ctx);
}
void sha3_384_inc_ctx_clone(sha3_384incctx *dest, const sha3_384incctx *src) {
dest->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (dest->ctx == NULL) {
exit(111);
}
memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
}
void sha3_384_inc_absorb(sha3_384incctx *state, const uint8_t *input, size_t inlen) {
keccak_inc_absorb(state->ctx, SHA3_384_RATE, input, inlen);
}
void sha3_384_inc_ctx_release(sha3_384incctx *state) {
free(state->ctx);
}
void sha3_384_inc_finalize(uint8_t *output, sha3_384incctx *state) {
uint8_t t[SHA3_384_RATE];
keccak_inc_finalize(state->ctx, SHA3_384_RATE, 0x06);
keccak_squeezeblocks(t, 1, state->ctx, SHA3_384_RATE);
sha3_384_inc_ctx_release(state);
for (size_t i = 0; i < 48; i++) {
output[i] = t[i];
}
}
/*************************************************
* Name: sha3_384
*
* Description: SHA3-256 with non-incremental API
*
* Arguments: - uint8_t *output: pointer to output
* - const uint8_t *input: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void sha3_384(uint8_t *output, const uint8_t *input, size_t inlen) {
uint64_t s[25];
uint8_t t[SHA3_384_RATE];
/* Absorb input */
keccak_absorb(s, SHA3_384_RATE, input, inlen, 0x06);
/* Squeeze output */
keccak_squeezeblocks(t, 1, s, SHA3_384_RATE);
for (size_t i = 0; i < 48; i++) {
output[i] = t[i];
}
}
void sha3_512_inc_init(sha3_512incctx *state) {
state->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (state->ctx == NULL) {
exit(111);
}
keccak_inc_init(state->ctx);
}
void sha3_512_inc_ctx_clone(sha3_512incctx *dest, const sha3_512incctx *src) {
dest->ctx = malloc(PQC_SHAKEINCCTX_BYTES);
if (dest->ctx == NULL) {
exit(111);
}
memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
}
void sha3_512_inc_absorb(sha3_512incctx *state, const uint8_t *input, size_t inlen) {
keccak_inc_absorb(state->ctx, SHA3_512_RATE, input, inlen);
}
void sha3_512_inc_ctx_release(sha3_512incctx *state) {
free(state->ctx);
}
void sha3_512_inc_finalize(uint8_t *output, sha3_512incctx *state) {
uint8_t t[SHA3_512_RATE];
keccak_inc_finalize(state->ctx, SHA3_512_RATE, 0x06);
keccak_squeezeblocks(t, 1, state->ctx, SHA3_512_RATE);
sha3_512_inc_ctx_release(state);
for (size_t i = 0; i < 64; i++) {
output[i] = t[i];
}
}
/*************************************************
* Name: sha3_512
*
* Description: SHA3-512 with non-incremental API
*
* Arguments: - uint8_t *output: pointer to output
* - const uint8_t *input: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void sha3_512(uint8_t *output, const uint8_t *input, size_t inlen) {
uint64_t s[25];
uint8_t t[SHA3_512_RATE];
/* Absorb input */
keccak_absorb(s, SHA3_512_RATE, input, inlen, 0x06);
/* Squeeze output */
keccak_squeezeblocks(t, 1, s, SHA3_512_RATE);
for (size_t i = 0; i < 64; i++) {
output[i] = t[i];
}
}
+166
View File
@@ -0,0 +1,166 @@
#ifndef FIPS202_H
#define FIPS202_H
#include <stddef.h>
#include <stdint.h>
#define SHAKE128_RATE 168
#define SHAKE256_RATE 136
#define SHA3_256_RATE 136
#define SHA3_384_RATE 104
#define SHA3_512_RATE 72
#define PQC_SHAKEINCCTX_BYTES (sizeof(uint64_t)*26)
#define PQC_SHAKECTX_BYTES (sizeof(uint64_t)*25)
// Context for incremental API
typedef struct {
uint64_t *ctx;
} shake128incctx;
// Context for non-incremental API
typedef struct {
uint64_t *ctx;
} shake128ctx;
// Context for incremental API
typedef struct {
uint64_t *ctx;
} shake256incctx;
// Context for non-incremental API
typedef struct {
uint64_t *ctx;
} shake256ctx;
// Context for incremental API
typedef struct {
uint64_t *ctx;
} sha3_256incctx;
// Context for incremental API
typedef struct {
uint64_t *ctx;
} sha3_384incctx;
// Context for incremental API
typedef struct {
uint64_t *ctx;
} sha3_512incctx;
/* Initialize the state and absorb the provided input.
*
* This function does not support being called multiple times
* with the same state.
*/
void shake128_absorb(shake128ctx *state, const uint8_t *input, size_t inlen);
/* Squeeze output out of the sponge.
*
* Supports being called multiple times
*/
void shake128_squeezeblocks(uint8_t *output, size_t nblocks, shake128ctx *state);
/* Free the state */
void shake128_ctx_release(shake128ctx *state);
/* Copy the state. */
void shake128_ctx_clone(shake128ctx *dest, const shake128ctx *src);
/* Initialize incremental hashing API */
void shake128_inc_init(shake128incctx *state);
/* Absorb more information into the XOF.
*
* Can be called multiple times.
*/
void shake128_inc_absorb(shake128incctx *state, const uint8_t *input, size_t inlen);
/* Finalize the XOF for squeezing */
void shake128_inc_finalize(shake128incctx *state);
/* Squeeze output out of the sponge.
*
* Supports being called multiple times
*/
void shake128_inc_squeeze(uint8_t *output, size_t outlen, shake128incctx *state);
/* Copy the context of the SHAKE128 XOF */
void shake128_inc_ctx_clone(shake128incctx *dest, const shake128incctx *src);
/* Free the context of the SHAKE128 XOF */
void shake128_inc_ctx_release(shake128incctx *state);
/* Initialize the state and absorb the provided input.
*
* This function does not support being called multiple times
* with the same state.
*/
void shake256_absorb(shake256ctx *state, const uint8_t *input, size_t inlen);
/* Squeeze output out of the sponge.
*
* Supports being called multiple times
*/
void shake256_squeezeblocks(uint8_t *output, size_t nblocks, shake256ctx *state);
/* Free the context held by this XOF */
void shake256_ctx_release(shake256ctx *state);
/* Copy the context held by this XOF */
void shake256_ctx_clone(shake256ctx *dest, const shake256ctx *src);
/* Initialize incremental hashing API */
void shake256_inc_init(shake256incctx *state);
void shake256_inc_absorb(shake256incctx *state, const uint8_t *input, size_t inlen);
/* Prepares for squeeze phase */
void shake256_inc_finalize(shake256incctx *state);
/* Squeeze output out of the sponge.
*
* Supports being called multiple times
*/
void shake256_inc_squeeze(uint8_t *output, size_t outlen, shake256incctx *state);
/* Copy the state */
void shake256_inc_ctx_clone(shake256incctx *dest, const shake256incctx *src);
/* Free the state */
void shake256_inc_ctx_release(shake256incctx *state);
/* One-stop SHAKE128 call */
void shake128(uint8_t *output, size_t outlen,
const uint8_t *input, size_t inlen);
/* One-stop SHAKE256 call */
void shake256(uint8_t *output, size_t outlen,
const uint8_t *input, size_t inlen);
/* Initialize the incremental hashing state */
void sha3_256_inc_init(sha3_256incctx *state);
/* Absorb blocks into SHA3 */
void sha3_256_inc_absorb(sha3_256incctx *state, const uint8_t *input, size_t inlen);
/* Obtain the output of the function and free `state` */
void sha3_256_inc_finalize(uint8_t *output, sha3_256incctx *state);
/* Copy the context */
void sha3_256_inc_ctx_clone(sha3_256incctx *dest, const sha3_256incctx *src);
/* Release the state, don't use if `_finalize` has been used */
void sha3_256_inc_ctx_release(sha3_256incctx *state);
void sha3_256(uint8_t *output, const uint8_t *input, size_t inlen);
/* Initialize the incremental hashing state */
void sha3_384_inc_init(sha3_384incctx *state);
/* Absorb blocks into SHA3 */
void sha3_384_inc_absorb(sha3_384incctx *state, const uint8_t *input, size_t inlen);
/* Obtain the output of the function and free `state` */
void sha3_384_inc_finalize(uint8_t *output, sha3_384incctx *state);
/* Copy the context */
void sha3_384_inc_ctx_clone(sha3_384incctx *dest, const sha3_384incctx *src);
/* Release the state, don't use if `_finalize` has been used */
void sha3_384_inc_ctx_release(sha3_384incctx *state);
/* One-stop SHA3-384 shop */
void sha3_384(uint8_t *output, const uint8_t *input, size_t inlen);
/* Initialize the incremental hashing state */
void sha3_512_inc_init(sha3_512incctx *state);
/* Absorb blocks into SHA3 */
void sha3_512_inc_absorb(sha3_512incctx *state, const uint8_t *input, size_t inlen);
/* Obtain the output of the function and free `state` */
void sha3_512_inc_finalize(uint8_t *output, sha3_512incctx *state);
/* Copy the context */
void sha3_512_inc_ctx_clone(sha3_512incctx *dest, const sha3_512incctx *src);
/* Release the state, don't use if `_finalize` has been used */
void sha3_512_inc_ctx_release(sha3_512incctx *state);
/* One-stop SHA3-512 shop */
void sha3_512(uint8_t *output, const uint8_t *input, size_t inlen);
#endif
+27
View File
@@ -0,0 +1,27 @@
/*
* randombytes.h — header used by PQClean HQC sources to declare the
* randombytes() entry point. The actual implementation lives in
* crypto/hqc/randombytes_shim.c and reads from a Go-supplied buffer
* via a cgo callback (see backend_pqclean.go).
*
* This vendored header replaces PQClean's common/randombytes.h, which
* pulls in OS entropy sources we don't want for the deterministic
* KEM precompile.
*/
#ifndef PQCLEAN_HQC_RANDOMBYTES_H
#define PQCLEAN_HQC_RANDOMBYTES_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
int randombytes(uint8_t *output, size_t n);
#ifdef __cplusplus
}
#endif
#endif /* PQCLEAN_HQC_RANDOMBYTES_H */
+1
View File
@@ -0,0 +1 @@
Public Domain
+27
View File
@@ -0,0 +1,27 @@
#ifndef PQCLEAN_HQC128_CLEAN_API_H
#define PQCLEAN_HQC128_CLEAN_API_H
/**
* @file api.h
* @brief NIST KEM API used by the HQC_KEM IND-CCA2 scheme
*/
#include <stdint.h>
#define PQCLEAN_HQC128_CLEAN_CRYPTO_ALGNAME "HQC-128"
#define PQCLEAN_HQC128_CLEAN_CRYPTO_SECRETKEYBYTES 2305
#define PQCLEAN_HQC128_CLEAN_CRYPTO_PUBLICKEYBYTES 2249
#define PQCLEAN_HQC128_CLEAN_CRYPTO_BYTES 64
#define PQCLEAN_HQC128_CLEAN_CRYPTO_CIPHERTEXTBYTES 4433
// As a technicality, the public key is appended to the secret key in order to respect the NIST API.
// Without this constraint, PQCLEAN_HQC128_CLEAN_CRYPTO_SECRETKEYBYTES would be defined as 32
int PQCLEAN_HQC128_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk);
int PQCLEAN_HQC128_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
int PQCLEAN_HQC128_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
#endif
+46
View File
@@ -0,0 +1,46 @@
#include "code.h"
#include "parameters.h"
#include "reed_muller.h"
#include "reed_solomon.h"
#include <stdint.h>
/**
* @file code.c
* @brief Implementation of concatenated code
*/
/**
*
* @brief Encoding the message m to a code word em using the concatenated code
*
* First we encode the message using the Reed-Solomon code, then with the duplicated Reed-Muller code we obtain
* a concatenated code word.
*
* @param[out] em Pointer to an array that is the tensor code word
* @param[in] m Pointer to an array that is the message
*/
void PQCLEAN_HQC128_CLEAN_code_encode(uint64_t *em, const uint8_t *m) {
uint8_t tmp[VEC_N1_SIZE_BYTES] = {0};
PQCLEAN_HQC128_CLEAN_reed_solomon_encode(tmp, m);
PQCLEAN_HQC128_CLEAN_reed_muller_encode(em, tmp);
}
/**
* @brief Decoding the code word em to a message m using the concatenated code
*
* @param[out] m Pointer to an array that is the message
* @param[in] em Pointer to an array that is the code word
*/
void PQCLEAN_HQC128_CLEAN_code_decode(uint8_t *m, const uint64_t *em) {
uint8_t tmp[VEC_N1_SIZE_BYTES] = {0};
PQCLEAN_HQC128_CLEAN_reed_muller_decode(tmp, em);
PQCLEAN_HQC128_CLEAN_reed_solomon_decode(m, tmp);
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef CODE_H
#define CODE_H
/**
* @file code.h
* @brief Header file of code.c
*/
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_code_encode(uint64_t *em, const uint8_t *message);
void PQCLEAN_HQC128_CLEAN_code_decode(uint8_t *m, const uint64_t *em);
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef DOMAINS_H
#define DOMAINS_H
/**
* @file domains.h
* @brief SHAKE-256 domains separation header grouping all domains to avoid collisions
*/
#define PRNG_DOMAIN 1
#define SEEDEXPANDER_DOMAIN 2
#define G_FCT_DOMAIN 3
#define K_FCT_DOMAIN 4
#endif
+346
View File
@@ -0,0 +1,346 @@
#include "fft.h"
#include "gf.h"
#include "parameters.h"
#include <stdint.h>
#include <string.h>
/**
* @file fft.c
* @brief Implementation of the additive FFT and its transpose.
* This implementation is based on the paper from Gao and Mateer: <br>
* Shuhong Gao and Todd Mateer, Additive Fast Fourier Transforms over Finite Fields,
* IEEE Transactions on Information Theory 56 (2010), 6265--6272.
* http://www.math.clemson.edu/~sgao/papers/GM10.pdf <br>
* and includes improvements proposed by Bernstein, Chou and Schwabe here:
* https://binary.cr.yp.to/mcbits-20130616.pdf
*/
static void radix_big(uint16_t *f0, uint16_t *f1, const uint16_t *f, uint32_t m_f);
/**
* @brief Computes the basis of betas (omitting 1) used in the additive FFT and its transpose
*
* @param[out] betas Array of size PARAM_M-1
*/
static void compute_fft_betas(uint16_t *betas) {
size_t i;
for (i = 0; i < PARAM_M - 1; ++i) {
betas[i] = 1 << (PARAM_M - 1 - i);
}
}
/**
* @brief Computes the subset sums of the given set
*
* The array subset_sums is such that its ith element is
* the subset sum of the set elements given by the binary form of i.
*
* @param[out] subset_sums Array of size 2^set_size receiving the subset sums
* @param[in] set Array of set_size elements
* @param[in] set_size Size of the array set
*/
static void compute_subset_sums(uint16_t *subset_sums, const uint16_t *set, uint16_t set_size) {
uint16_t i, j;
subset_sums[0] = 0;
for (i = 0; i < set_size; ++i) {
for (j = 0; j < (1 << i); ++j) {
subset_sums[(1 << i) + j] = set[i] ^ subset_sums[j];
}
}
}
/**
* @brief Computes the radix conversion of a polynomial f in GF(2^m)[x]
*
* Computes f0 and f1 such that f(x) = f0(x^2-x) + x.f1(x^2-x)
* as proposed by Bernstein, Chou and Schwabe:
* https://binary.cr.yp.to/mcbits-20130616.pdf
*
* @param[out] f0 Array half the size of f
* @param[out] f1 Array half the size of f
* @param[in] f Array of size a power of 2
* @param[in] m_f 2^{m_f} is the smallest power of 2 greater or equal to the number of coefficients of f
*/
static void radix(uint16_t *f0, uint16_t *f1, const uint16_t *f, uint32_t m_f) {
switch (m_f) {
case 4:
f0[4] = f[8] ^ f[12];
f0[6] = f[12] ^ f[14];
f0[7] = f[14] ^ f[15];
f1[5] = f[11] ^ f[13];
f1[6] = f[13] ^ f[14];
f1[7] = f[15];
f0[5] = f[10] ^ f[12] ^ f1[5];
f1[4] = f[9] ^ f[13] ^ f0[5];
f0[0] = f[0];
f1[3] = f[7] ^ f[11] ^ f[15];
f0[3] = f[6] ^ f[10] ^ f[14] ^ f1[3];
f0[2] = f[4] ^ f0[4] ^ f0[3] ^ f1[3];
f1[1] = f[3] ^ f[5] ^ f[9] ^ f[13] ^ f1[3];
f1[2] = f[3] ^ f1[1] ^ f0[3];
f0[1] = f[2] ^ f0[2] ^ f1[1];
f1[0] = f[1] ^ f0[1];
break;
case 3:
f0[0] = f[0];
f0[2] = f[4] ^ f[6];
f0[3] = f[6] ^ f[7];
f1[1] = f[3] ^ f[5] ^ f[7];
f1[2] = f[5] ^ f[6];
f1[3] = f[7];
f0[1] = f[2] ^ f0[2] ^ f1[1];
f1[0] = f[1] ^ f0[1];
break;
case 2:
f0[0] = f[0];
f0[1] = f[2] ^ f[3];
f1[0] = f[1] ^ f0[1];
f1[1] = f[3];
break;
case 1:
f0[0] = f[0];
f1[0] = f[1];
break;
default:
radix_big(f0, f1, f, m_f);
break;
}
}
static void radix_big(uint16_t *f0, uint16_t *f1, const uint16_t *f, uint32_t m_f) {
uint16_t Q[2 * (1 << (PARAM_FFT - 2)) + 1] = {0};
uint16_t R[2 * (1 << (PARAM_FFT - 2)) + 1] = {0};
uint16_t Q0[1 << (PARAM_FFT - 2)] = {0};
uint16_t Q1[1 << (PARAM_FFT - 2)] = {0};
uint16_t R0[1 << (PARAM_FFT - 2)] = {0};
uint16_t R1[1 << (PARAM_FFT - 2)] = {0};
size_t i, n;
n = 1;
n <<= (m_f - 2);
memcpy(Q, f + 3 * n, 2 * n);
memcpy(Q + n, f + 3 * n, 2 * n);
memcpy(R, f, 4 * n);
for (i = 0; i < n; ++i) {
Q[i] ^= f[2 * n + i];
R[n + i] ^= Q[i];
}
radix(Q0, Q1, Q, m_f - 1);
radix(R0, R1, R, m_f - 1);
memcpy(f0, R0, 2 * n);
memcpy(f0 + n, Q0, 2 * n);
memcpy(f1, R1, 2 * n);
memcpy(f1 + n, Q1, 2 * n);
}
/**
* @brief Evaluates f at all subset sums of a given set
*
* This function is a subroutine of the function PQCLEAN_HQC128_CLEAN_fft.
*
* @param[out] w Array
* @param[in] f Array
* @param[in] f_coeffs Number of coefficients of f
* @param[in] m Number of betas
* @param[in] m_f Number of coefficients of f (one more than its degree)
* @param[in] betas FFT constants
*/
static void fft_rec(uint16_t *w, uint16_t *f, size_t f_coeffs, uint8_t m, uint32_t m_f, const uint16_t *betas) {
uint16_t f0[1 << (PARAM_FFT - 2)] = {0};
uint16_t f1[1 << (PARAM_FFT - 2)] = {0};
uint16_t gammas[PARAM_M - 2] = {0};
uint16_t deltas[PARAM_M - 2] = {0};
uint16_t gammas_sums[1 << (PARAM_M - 2)] = {0};
uint16_t u[1 << (PARAM_M - 2)] = {0};
uint16_t v[1 << (PARAM_M - 2)] = {0};
uint16_t tmp[PARAM_M - (PARAM_FFT - 1)] = {0};
uint16_t beta_m_pow;
size_t i, j, k;
size_t x;
// Step 1
if (m_f == 1) {
for (i = 0; i < m; ++i) {
tmp[i] = PQCLEAN_HQC128_CLEAN_gf_mul(betas[i], f[1]);
}
w[0] = f[0];
x = 1;
for (j = 0; j < m; ++j) {
for (k = 0; k < x; ++k) {
w[x + k] = w[k] ^ tmp[j];
}
x <<= 1;
}
return;
}
// Step 2: compute g
if (betas[m - 1] != 1) {
beta_m_pow = 1;
x = 1;
x <<= m_f;
for (i = 1; i < x; ++i) {
beta_m_pow = PQCLEAN_HQC128_CLEAN_gf_mul(beta_m_pow, betas[m - 1]);
f[i] = PQCLEAN_HQC128_CLEAN_gf_mul(beta_m_pow, f[i]);
}
}
// Step 3
radix(f0, f1, f, m_f);
// Step 4: compute gammas and deltas
for (i = 0; i + 1 < m; ++i) {
gammas[i] = PQCLEAN_HQC128_CLEAN_gf_mul(betas[i], PQCLEAN_HQC128_CLEAN_gf_inverse(betas[m - 1]));
deltas[i] = PQCLEAN_HQC128_CLEAN_gf_square(gammas[i]) ^ gammas[i];
}
// Compute gammas sums
compute_subset_sums(gammas_sums, gammas, m - 1);
// Step 5
fft_rec(u, f0, (f_coeffs + 1) / 2, m - 1, m_f - 1, deltas);
k = 1;
k <<= ((m - 1) & 0xf); // &0xf is to let the compiler know that m-1 is small.
if (f_coeffs <= 3) { // 3-coefficient polynomial f case: f1 is constant
w[0] = u[0];
w[k] = u[0] ^ f1[0];
for (i = 1; i < k; ++i) {
w[i] = u[i] ^ PQCLEAN_HQC128_CLEAN_gf_mul(gammas_sums[i], f1[0]);
w[k + i] = w[i] ^ f1[0];
}
} else {
fft_rec(v, f1, f_coeffs / 2, m - 1, m_f - 1, deltas);
// Step 6
memcpy(w + k, v, 2 * k);
w[0] = u[0];
w[k] ^= u[0];
for (i = 1; i < k; ++i) {
w[i] = u[i] ^ PQCLEAN_HQC128_CLEAN_gf_mul(gammas_sums[i], v[i]);
w[k + i] ^= w[i];
}
}
}
/**
* @brief Evaluates f on all fields elements using an additive FFT algorithm
*
* f_coeffs is the number of coefficients of f (one less than its degree). <br>
* The FFT proceeds recursively to evaluate f at all subset sums of a basis B. <br>
* This implementation is based on the paper from Gao and Mateer: <br>
* Shuhong Gao and Todd Mateer, Additive Fast Fourier Transforms over Finite Fields,
* IEEE Transactions on Information Theory 56 (2010), 6265--6272.
* http://www.math.clemson.edu/~sgao/papers/GM10.pdf <br>
* and includes improvements proposed by Bernstein, Chou and Schwabe here:
* https://binary.cr.yp.to/mcbits-20130616.pdf <br>
* Note that on this first call (as opposed to the recursive calls to fft_rec), gammas are equal to betas,
* meaning the first gammas subset sums are actually the subset sums of betas (except 1). <br>
* Also note that f is altered during computation (twisted at each level).
*
* @param[out] w Array
* @param[in] f Array of 2^PARAM_FFT elements
* @param[in] f_coeffs Number coefficients of f (i.e. deg(f)+1)
*/
void PQCLEAN_HQC128_CLEAN_fft(uint16_t *w, const uint16_t *f, size_t f_coeffs) {
uint16_t betas[PARAM_M - 1] = {0};
uint16_t betas_sums[1 << (PARAM_M - 1)] = {0};
uint16_t f0[1 << (PARAM_FFT - 1)] = {0};
uint16_t f1[1 << (PARAM_FFT - 1)] = {0};
uint16_t deltas[PARAM_M - 1] = {0};
uint16_t u[1 << (PARAM_M - 1)] = {0};
uint16_t v[1 << (PARAM_M - 1)] = {0};
size_t i, k;
// Follows Gao and Mateer algorithm
compute_fft_betas(betas);
// Step 1: PARAM_FFT > 1, nothing to do
// Compute gammas sums
compute_subset_sums(betas_sums, betas, PARAM_M - 1);
// Step 2: beta_m = 1, nothing to do
// Step 3
radix(f0, f1, f, PARAM_FFT);
// Step 4: Compute deltas
for (i = 0; i < PARAM_M - 1; ++i) {
deltas[i] = PQCLEAN_HQC128_CLEAN_gf_square(betas[i]) ^ betas[i];
}
// Step 5
fft_rec(u, f0, (f_coeffs + 1) / 2, PARAM_M - 1, PARAM_FFT - 1, deltas);
fft_rec(v, f1, f_coeffs / 2, PARAM_M - 1, PARAM_FFT - 1, deltas);
k = 1 << (PARAM_M - 1);
// Step 6, 7 and error polynomial computation
memcpy(w + k, v, 2 * k);
// Check if 0 is root
w[0] = u[0];
// Check if 1 is root
w[k] ^= u[0];
// Find other roots
for (i = 1; i < k; ++i) {
w[i] = u[i] ^ PQCLEAN_HQC128_CLEAN_gf_mul(betas_sums[i], v[i]);
w[k + i] ^= w[i];
}
}
/**
* @brief Retrieves the error polynomial error from the evaluations w of the ELP (Error Locator Polynomial) on all field elements.
*
* @param[out] error Array with the error
* @param[out] error_compact Array with the error in a compact form
* @param[in] w Array of size 2^PARAM_M
*/
void PQCLEAN_HQC128_CLEAN_fft_retrieve_error_poly(uint8_t *error, const uint16_t *w) {
uint16_t gammas[PARAM_M - 1] = {0};
uint16_t gammas_sums[1 << (PARAM_M - 1)] = {0};
uint16_t k;
size_t i, index;
compute_fft_betas(gammas);
compute_subset_sums(gammas_sums, gammas, PARAM_M - 1);
k = 1 << (PARAM_M - 1);
error[0] ^= 1 ^ ((uint16_t) - w[0] >> 15);
error[0] ^= 1 ^ ((uint16_t) - w[k] >> 15);
for (i = 1; i < k; ++i) {
index = PARAM_GF_MUL_ORDER - gf_log[gammas_sums[i]];
error[index] ^= 1 ^ ((uint16_t) - w[i] >> 15);
index = PARAM_GF_MUL_ORDER - gf_log[gammas_sums[i] ^ 1];
error[index] ^= 1 ^ ((uint16_t) - w[k + i] >> 15);
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef FFT_H
#define FFT_H
/**
* @file fft.h
* @brief Header file of fft.c
*/
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_fft(uint16_t *w, const uint16_t *f, size_t f_coeffs);
void PQCLEAN_HQC128_CLEAN_fft_retrieve_error_poly(uint8_t *error, const uint16_t *w);
#endif
+170
View File
@@ -0,0 +1,170 @@
#include "gf.h"
#include "parameters.h"
#include <stddef.h>
#include <stdint.h>
/**
* @file gf.c
* @brief Galois field implementation
*/
/**
* @brief Computes the number of trailing zero bits.
*
* @returns The number of trailing zero bits in a.
* @param[in] a An operand
*/
static uint16_t trailing_zero_bits_count(uint16_t a) {
uint16_t tmp = 0;
uint16_t mask = 0xFFFF;
for (size_t i = 0; i < 14; ++i) {
tmp += ((1 - ((a >> i) & 0x0001)) & mask);
mask &= - (1 - ((a >> i) & 0x0001));
}
return tmp;
}
/**
* Reduces polynomial x modulo primitive polynomial GF_POLY.
* @returns x mod GF_POLY
* @param[in] x Polynomial of degree less than 64
* @param[in] deg_x The degree of polynomial x
*/
static uint16_t gf_reduce(uint64_t x, size_t deg_x) {
uint16_t z1, z2, rmdr, dist;
uint64_t mod;
// Deduce the number of steps of reduction
size_t steps = CEIL_DIVIDE(deg_x - (PARAM_M - 1), PARAM_GF_POLY_M2);
// Reduce
for (size_t i = 0; i < steps; ++i) {
mod = x >> PARAM_M;
x &= (1 << PARAM_M) - 1;
x ^= mod;
z1 = 0;
rmdr = PARAM_GF_POLY ^ 1;
for (size_t j = PARAM_GF_POLY_WT - 2; j; --j) {
z2 = trailing_zero_bits_count(rmdr);
dist = z2 - z1;
mod <<= dist;
x ^= mod;
rmdr ^= 1 << z2;
z1 = z2;
}
}
return (uint16_t)x;
}
/**
* Carryless multiplication of two polynomials a and b.
*
* Implementation of the algorithm mul1 in https://hal.inria.fr/inria-00188261v4/document
* with s = 2 and w = 8
*
* @param[out] The polynomial c = a * b
* @param[in] a The first polynomial
* @param[in] b The second polynomial
*/
static void gf_carryless_mul(uint8_t c[2], uint8_t a, uint8_t b) {
uint16_t h = 0, l = 0, g = 0, u[4];
uint32_t tmp1, tmp2;
uint16_t mask;
u[0] = 0;
u[1] = b & 0x7F;
u[2] = u[1] << 1;
u[3] = u[2] ^ u[1];
tmp1 = a & 3;
for (size_t i = 0; i < 4; i++) {
tmp2 = (uint32_t)(tmp1 - i);
g ^= (u[i] & (uint32_t)(0 - (1 - ((uint32_t)(tmp2 | (0 - tmp2)) >> 31))));
}
l = g;
h = 0;
for (size_t i = 2; i < 8; i += 2) {
g = 0;
tmp1 = (a >> i) & 3;
for (size_t j = 0; j < 4; ++j) {
tmp2 = (uint32_t)(tmp1 - j);
g ^= (u[j] & (uint32_t)(0 - (1 - ((uint32_t)(tmp2 | (0 - tmp2)) >> 31))));
}
l ^= g << i;
h ^= g >> (8 - i);
}
mask = (-((b >> 7) & 1));
l ^= ((a << 7) & mask);
h ^= ((a >> 1) & mask);
c[0] = (uint8_t)l;
c[1] = (uint8_t)h;
}
/**
* Multiplies two elements of GF(2^GF_M).
* @returns the product a*b
* @param[in] a Element of GF(2^GF_M)
* @param[in] b Element of GF(2^GF_M)
*/
uint16_t PQCLEAN_HQC128_CLEAN_gf_mul(uint16_t a, uint16_t b) {
uint8_t c[2] = {0};
gf_carryless_mul(c, (uint8_t) a, (uint8_t) b);
uint16_t tmp = c[0] ^ (c[1] << 8);
return gf_reduce(tmp, 2 * (PARAM_M - 1));
}
/**
* @brief Squares an element of GF(2^PARAM_M).
* @returns a^2
* @param[in] a Element of GF(2^PARAM_M)
*/
uint16_t PQCLEAN_HQC128_CLEAN_gf_square(uint16_t a) {
uint32_t b = a;
uint32_t s = b & 1;
for (size_t i = 1; i < PARAM_M; ++i) {
b <<= 1;
s ^= b & (1 << 2 * i);
}
return gf_reduce(s, 2 * (PARAM_M - 1));
}
/**
* @brief Computes the inverse of an element of GF(2^PARAM_M),
* using the addition chain 1 2 3 4 7 11 15 30 60 120 127 254
* @returns the inverse of a if a != 0 or 0 if a = 0
* @param[in] a Element of GF(2^PARAM_M)
*/
uint16_t PQCLEAN_HQC128_CLEAN_gf_inverse(uint16_t a) {
uint16_t inv = a;
uint16_t tmp1, tmp2;
inv = PQCLEAN_HQC128_CLEAN_gf_square(a); /* a^2 */
tmp1 = PQCLEAN_HQC128_CLEAN_gf_mul(inv, a); /* a^3 */
inv = PQCLEAN_HQC128_CLEAN_gf_square(inv); /* a^4 */
tmp2 = PQCLEAN_HQC128_CLEAN_gf_mul(inv, tmp1); /* a^7 */
tmp1 = PQCLEAN_HQC128_CLEAN_gf_mul(inv, tmp2); /* a^11 */
inv = PQCLEAN_HQC128_CLEAN_gf_mul(tmp1, inv); /* a^15 */
inv = PQCLEAN_HQC128_CLEAN_gf_square(inv); /* a^30 */
inv = PQCLEAN_HQC128_CLEAN_gf_square(inv); /* a^60 */
inv = PQCLEAN_HQC128_CLEAN_gf_square(inv); /* a^120 */
inv = PQCLEAN_HQC128_CLEAN_gf_mul(inv, tmp2); /* a^127 */
inv = PQCLEAN_HQC128_CLEAN_gf_square(inv); /* a^254 */
return inv;
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef GF_H
#define GF_H
/**
* @file gf.h
* @brief Header file of gf.c
*/
#include <stdint.h>
/**
* Powers of the root alpha of 1 + x^2 + x^3 + x^4 + x^8.
* The last two elements are needed by the PQCLEAN_HQC128_CLEAN_gf_mul function
* (for example if both elements to multiply are zero).
*/
static const uint16_t gf_exp [258] = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1, 2, 4 };
/**
* Logarithm of elements of GF(2^8) to the base alpha (root of 1 + x^2 + x^3 + x^4 + x^8).
* The logarithm of 0 is set to 0 by convention.
*/
static const uint16_t gf_log [256] = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 };
uint16_t PQCLEAN_HQC128_CLEAN_gf_mul(uint16_t a, uint16_t b);
uint16_t PQCLEAN_HQC128_CLEAN_gf_square(uint16_t a);
uint16_t PQCLEAN_HQC128_CLEAN_gf_inverse(uint16_t a);
#endif
+198
View File
@@ -0,0 +1,198 @@
#include "gf2x.h"
#include "parameters.h"
#include <stddef.h>
#include <stdint.h>
/**
* @file gf2x.c
* @brief Implementation of multiplication of two polynomials
*/
/**
* @brief Caryless multiplication of two words of 64 bits
*
* Implemntation of the algorithm mul1 in https://hal.inria.fr/inria-00188261v4/document.
* With w = 64 and s = 4
*
* @param[out] c The result c = a * b
* @param[in] a The first value a
* @param[in] b The second value b
*/
static void base_mul(uint64_t *c, uint64_t a, uint64_t b) {
uint64_t h = 0;
uint64_t l = 0;
uint64_t g;
uint64_t u[16] = {0};
uint64_t mask_tab[4] = {0};
uint64_t tmp1, tmp2;
// Step 1
u[0] = 0;
u[1] = b & (((uint64_t)1 << (64 - 4)) - 1);
u[2] = u[1] << 1;
u[3] = u[2] ^ u[1];
u[4] = u[2] << 1;
u[5] = u[4] ^ u[1];
u[6] = u[3] << 1;
u[7] = u[6] ^ u[1];
u[8] = u[4] << 1;
u[9] = u[8] ^ u[1];
u[10] = u[5] << 1;
u[11] = u[10] ^ u[1];
u[12] = u[6] << 1;
u[13] = u[12] ^ u[1];
u[14] = u[7] << 1;
u[15] = u[14] ^ u[1];
g = 0;
tmp1 = a & 0x0f;
for (size_t i = 0; i < 16; ++i) {
tmp2 = tmp1 - i;
g ^= (u[i] & (uint64_t)(0 - (1 - ((uint64_t)(tmp2 | (0 - tmp2)) >> 63))));
}
l = g;
h = 0;
// Step 2
for (size_t i = 4; i < 64; i += 4) {
g = 0;
tmp1 = (a >> i) & 0x0f;
for (size_t j = 0; j < 16; ++j) {
tmp2 = tmp1 - j;
g ^= (u[j] & (uint64_t)(0 - (1 - ((uint64_t)(tmp2 | (0 - tmp2)) >> 63))));
}
l ^= g << i;
h ^= g >> (64 - i);
}
// Step 3
mask_tab [0] = 0 - ((b >> 60) & 1);
mask_tab [1] = 0 - ((b >> 61) & 1);
mask_tab [2] = 0 - ((b >> 62) & 1);
mask_tab [3] = 0 - ((b >> 63) & 1);
l ^= ((a << 60) & mask_tab[0]);
h ^= ((a >> 4) & mask_tab[0]);
l ^= ((a << 61) & mask_tab[1]);
h ^= ((a >> 3) & mask_tab[1]);
l ^= ((a << 62) & mask_tab[2]);
h ^= ((a >> 2) & mask_tab[2]);
l ^= ((a << 63) & mask_tab[3]);
h ^= ((a >> 1) & mask_tab[3]);
c[0] = l;
c[1] = h;
}
static void karatsuba_add1(uint64_t *alh, uint64_t *blh, const uint64_t *a, const uint64_t *b, size_t size_l, size_t size_h) {
for (size_t i = 0; i < size_h; ++i) {
alh[i] = a[i] ^ a[i + size_l];
blh[i] = b[i] ^ b[i + size_l];
}
if (size_h < size_l) {
alh[size_h] = a[size_h];
blh[size_h] = b[size_h];
}
}
static void karatsuba_add2(uint64_t *o, uint64_t *tmp1, const uint64_t *tmp2, size_t size_l, size_t size_h) {
for (size_t i = 0; i < (2 * size_l); ++i) {
tmp1[i] = tmp1[i] ^ o[i];
}
for (size_t i = 0; i < ( 2 * size_h); ++i) {
tmp1[i] = tmp1[i] ^ tmp2[i];
}
for (size_t i = 0; i < (2 * size_l); ++i) {
o[i + size_l] = o[i + size_l] ^ tmp1[i];
}
}
/**
* Karatsuba multiplication of a and b, Implementation inspired from the NTL library.
*
* @param[out] o Polynomial
* @param[in] a Polynomial
* @param[in] b Polynomial
* @param[in] size Length of polynomial
* @param[in] stack Length of polynomial
*/
static void karatsuba(uint64_t *o, const uint64_t *a, const uint64_t *b, size_t size, uint64_t *stack) {
size_t size_l, size_h;
const uint64_t *ah, *bh;
if (size == 1) {
base_mul(o, a[0], b[0]);
return;
}
size_h = size / 2;
size_l = (size + 1) / 2;
uint64_t *alh = stack;
uint64_t *blh = alh + size_l;
uint64_t *tmp1 = blh + size_l;
uint64_t *tmp2 = o + 2 * size_l;
stack += 4 * size_l;
ah = a + size_l;
bh = b + size_l;
karatsuba(o, a, b, size_l, stack);
karatsuba(tmp2, ah, bh, size_h, stack);
karatsuba_add1(alh, blh, a, b, size_l, size_h);
karatsuba(tmp1, alh, blh, size_l, stack);
karatsuba_add2(o, tmp1, tmp2, size_l, size_h);
}
/**
* @brief Compute o(x) = a(x) mod \f$ X^n - 1\f$
*
* This function computes the modular reduction of the polynomial a(x)
*
* @param[in] a Pointer to the polynomial a(x)
* @param[out] o Pointer to the result
*/
static void reduce(uint64_t *o, const uint64_t *a) {
uint64_t r;
uint64_t carry;
for (size_t i = 0; i < VEC_N_SIZE_64; ++i) {
r = a[i + VEC_N_SIZE_64 - 1] >> (PARAM_N & 0x3F);
carry = a[i + VEC_N_SIZE_64] << (64 - (PARAM_N & 0x3F));
o[i] = a[i] ^ r ^ carry;
}
o[VEC_N_SIZE_64 - 1] &= RED_MASK;
}
/**
* @brief Multiply two polynomials modulo \f$ X^n - 1\f$.
*
* This functions multiplies polynomials <b>v1</b> and <b>v2</b>.
* The multiplication is done modulo \f$ X^n - 1\f$.
*
* @param[out] o Product of <b>v1</b> and <b>v2</b>
* @param[in] v1 Pointer to the first polynomial
* @param[in] v2 Pointer to the second polynomial
*/
void PQCLEAN_HQC128_CLEAN_vect_mul(uint64_t *o, const uint64_t *v1, const uint64_t *v2) {
uint64_t stack[VEC_N_SIZE_64 << 3] = {0};
uint64_t o_karat[VEC_N_SIZE_64 << 1] = {0};
karatsuba(o_karat, v1, v2, VEC_N_SIZE_64, stack);
reduce(o, o_karat);
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef GF2X_H
#define GF2X_H
/**
* @file gf2x.h
* @brief Header file for gf2x.c
*/
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_vect_mul(uint64_t *o, const uint64_t *v1, const uint64_t *v2);
#endif
+146
View File
@@ -0,0 +1,146 @@
#include "code.h"
#include "gf2x.h"
#include "hqc.h"
#include "parameters.h"
#include "parsing.h"
#include "randombytes.h"
#include "shake_prng.h"
#include "vector.h"
#include <stdint.h>
/**
* @file hqc.c
* @brief Implementation of hqc.h
*/
/**
* @brief Keygen of the HQC_PKE IND_CPA scheme
*
* The public key is composed of the syndrome <b>s</b> as well as the <b>seed</b> used to generate the vector <b>h</b>.
*
* The secret key is composed of the <b>seed</b> used to generate vectors <b>x</b> and <b>y</b>.
* As a technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] pk String containing the public key
* @param[out] sk String containing the secret key
*/
void PQCLEAN_HQC128_CLEAN_hqc_pke_keygen(uint8_t *pk, uint8_t *sk) {
seedexpander_state sk_seedexpander;
seedexpander_state pk_seedexpander;
uint8_t sk_seed[SEED_BYTES] = {0};
uint8_t sigma[VEC_K_SIZE_BYTES] = {0};
uint8_t pk_seed[SEED_BYTES] = {0};
uint64_t x[VEC_N_SIZE_64] = {0};
uint64_t y[VEC_N_SIZE_64] = {0};
uint64_t h[VEC_N_SIZE_64] = {0};
uint64_t s[VEC_N_SIZE_64] = {0};
// Create seed_expanders for public key and secret key
randombytes(sk_seed, SEED_BYTES);
randombytes(sigma, VEC_K_SIZE_BYTES);
PQCLEAN_HQC128_CLEAN_seedexpander_init(&sk_seedexpander, sk_seed, SEED_BYTES);
randombytes(pk_seed, SEED_BYTES);
PQCLEAN_HQC128_CLEAN_seedexpander_init(&pk_seedexpander, pk_seed, SEED_BYTES);
// Compute secret key
PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, x, PARAM_OMEGA);
PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, y, PARAM_OMEGA);
// Compute public key
PQCLEAN_HQC128_CLEAN_vect_set_random(&pk_seedexpander, h);
PQCLEAN_HQC128_CLEAN_vect_mul(s, y, h);
PQCLEAN_HQC128_CLEAN_vect_add(s, x, s, VEC_N_SIZE_64);
// Parse keys to string
PQCLEAN_HQC128_CLEAN_hqc_public_key_to_string(pk, pk_seed, s);
PQCLEAN_HQC128_CLEAN_hqc_secret_key_to_string(sk, sk_seed, sigma, pk);
PQCLEAN_HQC128_CLEAN_seedexpander_release(&pk_seedexpander);
PQCLEAN_HQC128_CLEAN_seedexpander_release(&sk_seedexpander);
}
/**
* @brief Encryption of the HQC_PKE IND_CPA scheme
*
* The cihertext is composed of vectors <b>u</b> and <b>v</b>.
*
* @param[out] u Vector u (first part of the ciphertext)
* @param[out] v Vector v (second part of the ciphertext)
* @param[in] m Vector representing the message to encrypt
* @param[in] theta Seed used to derive randomness required for encryption
* @param[in] pk String containing the public key
*/
void PQCLEAN_HQC128_CLEAN_hqc_pke_encrypt(uint64_t *u, uint64_t *v, uint8_t *m, uint8_t *theta, const uint8_t *pk) {
seedexpander_state vec_seedexpander;
uint64_t h[VEC_N_SIZE_64] = {0};
uint64_t s[VEC_N_SIZE_64] = {0};
uint64_t r1[VEC_N_SIZE_64] = {0};
uint64_t r2[VEC_N_SIZE_64] = {0};
uint64_t e[VEC_N_SIZE_64] = {0};
uint64_t tmp1[VEC_N_SIZE_64] = {0};
uint64_t tmp2[VEC_N_SIZE_64] = {0};
// Create seed_expander from theta
PQCLEAN_HQC128_CLEAN_seedexpander_init(&vec_seedexpander, theta, SEED_BYTES);
// Retrieve h and s from public key
PQCLEAN_HQC128_CLEAN_hqc_public_key_from_string(h, s, pk);
// Generate r1, r2 and e
PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(&vec_seedexpander, r1, PARAM_OMEGA_R);
PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(&vec_seedexpander, r2, PARAM_OMEGA_R);
PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(&vec_seedexpander, e, PARAM_OMEGA_E);
// Compute u = r1 + r2.h
PQCLEAN_HQC128_CLEAN_vect_mul(u, r2, h);
PQCLEAN_HQC128_CLEAN_vect_add(u, r1, u, VEC_N_SIZE_64);
// Compute v = m.G by encoding the message
PQCLEAN_HQC128_CLEAN_code_encode(v, m);
PQCLEAN_HQC128_CLEAN_vect_resize(tmp1, PARAM_N, v, PARAM_N1N2);
// Compute v = m.G + s.r2 + e
PQCLEAN_HQC128_CLEAN_vect_mul(tmp2, r2, s);
PQCLEAN_HQC128_CLEAN_vect_add(tmp2, e, tmp2, VEC_N_SIZE_64);
PQCLEAN_HQC128_CLEAN_vect_add(tmp2, tmp1, tmp2, VEC_N_SIZE_64);
PQCLEAN_HQC128_CLEAN_vect_resize(v, PARAM_N1N2, tmp2, PARAM_N);
PQCLEAN_HQC128_CLEAN_seedexpander_release(&vec_seedexpander);
}
/**
* @brief Decryption of the HQC_PKE IND_CPA scheme
*
* @param[out] m Vector representing the decrypted message
* @param[in] u Vector u (first part of the ciphertext)
* @param[in] v Vector v (second part of the ciphertext)
* @param[in] sk String containing the secret key
* @returns 0
*/
uint8_t PQCLEAN_HQC128_CLEAN_hqc_pke_decrypt(uint8_t *m, uint8_t *sigma, const uint64_t *u, const uint64_t *v, const uint8_t *sk) {
uint64_t x[VEC_N_SIZE_64] = {0};
uint64_t y[VEC_N_SIZE_64] = {0};
uint8_t pk[PUBLIC_KEY_BYTES] = {0};
uint64_t tmp1[VEC_N_SIZE_64] = {0};
uint64_t tmp2[VEC_N_SIZE_64] = {0};
// Retrieve x, y, pk from secret key
PQCLEAN_HQC128_CLEAN_hqc_secret_key_from_string(x, y, sigma, pk, sk);
// Compute v - u.y
PQCLEAN_HQC128_CLEAN_vect_resize(tmp1, PARAM_N, v, PARAM_N1N2);
PQCLEAN_HQC128_CLEAN_vect_mul(tmp2, y, u);
PQCLEAN_HQC128_CLEAN_vect_add(tmp2, tmp1, tmp2, VEC_N_SIZE_64);
// Compute m by decoding v - u.y
PQCLEAN_HQC128_CLEAN_code_decode(m, tmp2);
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef HQC_H
#define HQC_H
/**
* @file hqc.h
* @brief Functions of the HQC_PKE IND_CPA scheme
*/
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_hqc_pke_keygen(uint8_t *pk, uint8_t *sk);
void PQCLEAN_HQC128_CLEAN_hqc_pke_encrypt(uint64_t *u, uint64_t *v, uint8_t *m, unsigned char *theta, const unsigned char *pk);
uint8_t PQCLEAN_HQC128_CLEAN_hqc_pke_decrypt(uint8_t *m, uint8_t *sigma, const uint64_t *u, const uint64_t *v, const unsigned char *sk);
#endif
+138
View File
@@ -0,0 +1,138 @@
#include "api.h"
#include "domains.h"
#include "fips202.h"
#include "hqc.h"
#include "parameters.h"
#include "parsing.h"
#include "randombytes.h"
#include "shake_ds.h"
#include "vector.h"
#include <stdint.h>
#include <string.h>
/**
* @file kem.c
* @brief Implementation of api.h
*/
/**
* @brief Keygen of the HQC_KEM IND_CAA2 scheme
*
* The public key is composed of the syndrome <b>s</b> as well as the seed used to generate the vector <b>h</b>.
*
* The secret key is composed of the seed used to generate vectors <b>x</b> and <b>y</b>.
* As a technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] pk String containing the public key
* @param[out] sk String containing the secret key
* @returns 0 if keygen is successful
*/
int PQCLEAN_HQC128_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk) {
PQCLEAN_HQC128_CLEAN_hqc_pke_keygen(pk, sk);
return 0;
}
/**
* @brief Encapsulation of the HQC_KEM IND_CAA2 scheme
*
* @param[out] ct String containing the ciphertext
* @param[out] ss String containing the shared secret
* @param[in] pk String containing the public key
* @returns 0 if encapsulation is successful
*/
int PQCLEAN_HQC128_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk) {
uint8_t theta[SHAKE256_512_BYTES] = {0};
uint64_t u[VEC_N_SIZE_64] = {0};
uint64_t v[VEC_N1N2_SIZE_64] = {0};
uint8_t mc[VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES] = {0};
uint8_t tmp[VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES] = {0};
uint8_t *m = tmp;
uint8_t *salt = tmp + VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES;
shake256incctx shake256state;
// Computing m
randombytes(m, VEC_K_SIZE_BYTES);
// Computing theta
randombytes(salt, SALT_SIZE_BYTES);
memcpy(tmp + VEC_K_SIZE_BYTES, pk, PUBLIC_KEY_BYTES);
PQCLEAN_HQC128_CLEAN_shake256_512_ds(&shake256state, theta, tmp, VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES, G_FCT_DOMAIN);
// Encrypting m
PQCLEAN_HQC128_CLEAN_hqc_pke_encrypt(u, v, m, theta, pk);
// Computing shared secret
memcpy(mc, m, VEC_K_SIZE_BYTES);
PQCLEAN_HQC128_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES, VEC_N_SIZE_BYTES, u, VEC_N_SIZE_64);
PQCLEAN_HQC128_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES, v, VEC_N1N2_SIZE_64);
PQCLEAN_HQC128_CLEAN_shake256_512_ds(&shake256state, ss, mc, VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, K_FCT_DOMAIN);
// Computing ciphertext
PQCLEAN_HQC128_CLEAN_hqc_ciphertext_to_string(ct, u, v, salt);
return 0;
}
/**
* @brief Decapsulation of the HQC_KEM IND_CAA2 scheme
*
* @param[out] ss String containing the shared secret
* @param[in] ct String containing the cipĥertext
* @param[in] sk String containing the secret key
* @returns 0 if decapsulation is successful, -1 otherwise
*/
int PQCLEAN_HQC128_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk) {
uint8_t result;
uint64_t u[VEC_N_SIZE_64] = {0};
uint64_t v[VEC_N1N2_SIZE_64] = {0};
const uint8_t *pk = sk + SEED_BYTES + VEC_K_SIZE_BYTES;
uint8_t sigma[VEC_K_SIZE_BYTES] = {0};
uint8_t theta[SHAKE256_512_BYTES] = {0};
uint64_t u2[VEC_N_SIZE_64] = {0};
uint64_t v2[VEC_N1N2_SIZE_64] = {0};
uint8_t mc[VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES] = {0};
uint8_t tmp[VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES] = {0};
uint8_t *m = tmp;
uint8_t *salt = tmp + VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES;
shake256incctx shake256state;
// Retrieving u, v and d from ciphertext
PQCLEAN_HQC128_CLEAN_hqc_ciphertext_from_string(u, v, salt, ct);
// Decrypting
result = PQCLEAN_HQC128_CLEAN_hqc_pke_decrypt(m, sigma, u, v, sk);
// Computing theta
memcpy(tmp + VEC_K_SIZE_BYTES, pk, PUBLIC_KEY_BYTES);
PQCLEAN_HQC128_CLEAN_shake256_512_ds(&shake256state, theta, tmp, VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES, G_FCT_DOMAIN);
// Encrypting m'
PQCLEAN_HQC128_CLEAN_hqc_pke_encrypt(u2, v2, m, theta, pk);
// Check if c != c'
result |= PQCLEAN_HQC128_CLEAN_vect_compare((uint8_t *)u, (uint8_t *)u2, VEC_N_SIZE_BYTES);
result |= PQCLEAN_HQC128_CLEAN_vect_compare((uint8_t *)v, (uint8_t *)v2, VEC_N1N2_SIZE_BYTES);
result -= 1;
for (size_t i = 0; i < VEC_K_SIZE_BYTES; ++i) {
mc[i] = (m[i] & result) ^ (sigma[i] & ~result);
}
// Computing shared secret
PQCLEAN_HQC128_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES, VEC_N_SIZE_BYTES, u, VEC_N_SIZE_64);
PQCLEAN_HQC128_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES, v, VEC_N1N2_SIZE_64);
PQCLEAN_HQC128_CLEAN_shake256_512_ds(&shake256state, ss, mc, VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, K_FCT_DOMAIN);
return (result & 1) - 1;
}
+96
View File
@@ -0,0 +1,96 @@
#ifndef HQC_PARAMETERS_H
#define HQC_PARAMETERS_H
/**
* @file parameters.h
* @brief Parameters of the HQC_KEM IND-CCA2 scheme
*/
#include "api.h"
#define CEIL_DIVIDE(a, b) (((a)+(b)-1)/(b)) /*!< Divide a by b and ceil the result*/
/*
#define PARAM_N Define the parameter n of the scheme
#define PARAM_N1 Define the parameter n1 of the scheme (length of Reed-Solomon code)
#define PARAM_N2 Define the parameter n2 of the scheme (length of Duplicated Reed-Muller code)
#define PARAM_N1N2 Define the length in bits of the Concatenated code
#define PARAM_OMEGA Define the parameter omega of the scheme
#define PARAM_OMEGA_E Define the parameter omega_e of the scheme
#define PARAM_OMEGA_R Define the parameter omega_r of the scheme
#define SECRET_KEY_BYTES Define the size of the secret key in bytes
#define PUBLIC_KEY_BYTES Define the size of the public key in bytes
#define SHARED_SECRET_BYTES Define the size of the shared secret in bytes
#define CIPHERTEXT_BYTES Define the size of the ciphertext in bytes
#define VEC_N_SIZE_BYTES Define the size of the array used to store a PARAM_N sized vector in bytes
#define VEC_K_SIZE_BYTES Define the size of the array used to store a PARAM_K sized vector in bytes
#define VEC_N1_SIZE_BYTES Define the size of the array used to store a PARAM_N1 sized vector in bytes
#define VEC_N1N2_SIZE_BYTES Define the size of the array used to store a PARAM_N1N2 sized vector in bytes
#define VEC_N_SIZE_64 Define the size of the array used to store a PARAM_N sized vector in 64 bits
#define VEC_K_SIZE_64 Define the size of the array used to store a PARAM_K sized vector in 64 bits
#define VEC_N1_SIZE_64 Define the size of the array used to store a PARAM_N1 sized vector in 64 bits
#define VEC_N1N2_SIZE_64 Define the size of the array used to store a PARAM_N1N2 sized vector in 64 bits
#define PARAM_DELTA Define the parameter delta of the scheme (correcting capacity of the Reed-Solomon code)
#define PARAM_M Define a positive integer
#define PARAM_GF_POLY Generator polynomial of galois field GF(2^PARAM_M), represented in hexadecimial form
#define PARAM_GF_POLY_WT Hamming weight of PARAM_GF_POLY
#define PARAM_GF_POLY_M2 Distance between the primitive polynomial first two set bits
#define PARAM_GF_MUL_ORDER Define the size of the multiplicative group of GF(2^PARAM_M), i.e 2^PARAM_M -1
#define PARAM_K Define the size of the information bits of the Reed-Solomon code
#define PARAM_G Define the size of the generator polynomial of Reed-Solomon code
#define PARAM_FFT The additive FFT takes a 2^PARAM_FFT polynomial as input
We use the FFT to compute the roots of sigma, whose degree if PARAM_DELTA=24
The smallest power of 2 greater than 24+1 is 32=2^5
#define RS_POLY_COEFS Coefficients of the generator polynomial of the Reed-Solomon code
#define RED_MASK A mask for the higher bits of a vector
#define SHAKE256_512_BYTES Define the size of SHAKE-256 output in bytes
#define SEED_BYTES Define the size of the seed in bytes
#define SALT_SIZE_BYTES Define the size of a salt in bytes
*/
#define PARAM_N 17669
#define PARAM_N1 46
#define PARAM_N2 384
#define PARAM_N1N2 17664
#define PARAM_OMEGA 66
#define PARAM_OMEGA_E 75
#define PARAM_OMEGA_R 75
#define SECRET_KEY_BYTES PQCLEAN_HQC128_CLEAN_CRYPTO_SECRETKEYBYTES
#define PUBLIC_KEY_BYTES PQCLEAN_HQC128_CLEAN_CRYPTO_PUBLICKEYBYTES
#define SHARED_SECRET_BYTES PQCLEAN_HQC128_CLEAN_CRYPTO_BYTES
#define CIPHERTEXT_BYTES PQCLEAN_HQC128_CLEAN_CRYPTO_CIPHERTEXTBYTES
#define VEC_N_SIZE_BYTES CEIL_DIVIDE(PARAM_N, 8)
#define VEC_K_SIZE_BYTES PARAM_K
#define VEC_N1_SIZE_BYTES PARAM_N1
#define VEC_N1N2_SIZE_BYTES CEIL_DIVIDE(PARAM_N1N2, 8)
#define VEC_N_SIZE_64 CEIL_DIVIDE(PARAM_N, 64)
#define VEC_K_SIZE_64 CEIL_DIVIDE(PARAM_K, 8)
#define VEC_N1_SIZE_64 CEIL_DIVIDE(PARAM_N1, 8)
#define VEC_N1N2_SIZE_64 CEIL_DIVIDE(PARAM_N1N2, 64)
#define PARAM_DELTA 15
#define PARAM_M 8
#define PARAM_GF_POLY 0x11D
#define PARAM_GF_POLY_WT 5
#define PARAM_GF_POLY_M2 4
#define PARAM_GF_MUL_ORDER 255
#define PARAM_K 16
#define PARAM_G 31
#define PARAM_FFT 4
#define RS_POLY_COEFS 89,69,153,116,176,117,111,75,73,233,242,233,65,210,21,139,103,173,67,118,105,210,174,110,74,69,228,82,255,181,1
#define RED_MASK 0x1f
#define SHAKE256_512_BYTES 64
#define SEED_BYTES 40
#define SALT_SIZE_BYTES 16
#endif
+173
View File
@@ -0,0 +1,173 @@
#include "parameters.h"
#include "parsing.h"
#include "vector.h"
#include <stdint.h>
#include <string.h>
/**
* @file parsing.c
* @brief Functions to parse secret key, public key and ciphertext of the HQC scheme
*/
static uint64_t load8(const uint8_t *in) {
uint64_t ret = in[7];
for (int8_t i = 6; i >= 0; --i) {
ret <<= 8;
ret |= in[i];
}
return ret;
}
void PQCLEAN_HQC128_CLEAN_load8_arr(uint64_t *out64, size_t outlen, const uint8_t *in8, size_t inlen) {
size_t index_in = 0;
size_t index_out = 0;
// first copy by 8 bytes
if (inlen >= 8 && outlen >= 1) {
while (index_out < outlen && index_in + 8 <= inlen) {
out64[index_out] = load8(in8 + index_in);
index_in += 8;
index_out += 1;
}
}
// we now need to do the last 7 bytes if necessary
if (index_in >= inlen || index_out >= outlen) {
return;
}
out64[index_out] = in8[inlen - 1];
for (int8_t i = (int8_t)(inlen - index_in) - 2; i >= 0; --i) {
out64[index_out] <<= 8;
out64[index_out] |= in8[index_in + i];
}
}
void PQCLEAN_HQC128_CLEAN_store8_arr(uint8_t *out8, size_t outlen, const uint64_t *in64, size_t inlen) {
for (size_t index_out = 0, index_in = 0; index_out < outlen && index_in < inlen;) {
out8[index_out] = (in64[index_in] >> ((index_out % 8) * 8)) & 0xFF;
++index_out;
if (index_out % 8 == 0) {
++index_in;
}
}
}
/**
* @brief Parse a secret key into a string
*
* The secret key is composed of the seed used to generate vectors <b>x</b> and <b>y</b>.
* As technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] sk String containing the secret key
* @param[in] sk_seed Seed used to generate the secret key
* @param[in] sigma String used in HHK transform
* @param[in] pk String containing the public key
*/
void PQCLEAN_HQC128_CLEAN_hqc_secret_key_to_string(uint8_t *sk, const uint8_t *sk_seed, const uint8_t *sigma, const uint8_t *pk) {
memcpy(sk, sk_seed, SEED_BYTES);
memcpy(sk + SEED_BYTES, sigma, VEC_K_SIZE_BYTES);
memcpy(sk + SEED_BYTES + VEC_K_SIZE_BYTES, pk, PUBLIC_KEY_BYTES);
}
/**
* @brief Parse a secret key from a string
*
* The secret key is composed of the seed used to generate vectors <b>x</b> and <b>y</b>.
* As technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] x uint64_t representation of vector x
* @param[out] y uint64_t representation of vector y
* @param[out] pk String containing the public key
* @param[in] sk String containing the secret key
*/
void PQCLEAN_HQC128_CLEAN_hqc_secret_key_from_string(uint64_t *x, uint64_t *y, uint8_t *sigma, uint8_t *pk, const uint8_t *sk) {
seedexpander_state sk_seedexpander;
memcpy(sigma, sk + SEED_BYTES, VEC_K_SIZE_BYTES);
PQCLEAN_HQC128_CLEAN_seedexpander_init(&sk_seedexpander, sk, SEED_BYTES);
PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, x, PARAM_OMEGA);
PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, y, PARAM_OMEGA);
memcpy(pk, sk + SEED_BYTES + VEC_K_SIZE_BYTES, PUBLIC_KEY_BYTES);
PQCLEAN_HQC128_CLEAN_seedexpander_release(&sk_seedexpander);
}
/**
* @brief Parse a public key into a string
*
* The public key is composed of the syndrome <b>s</b> as well as the seed used to generate the vector <b>h</b>
*
* @param[out] pk String containing the public key
* @param[in] pk_seed Seed used to generate the public key
* @param[in] s uint64_t representation of vector s
*/
void PQCLEAN_HQC128_CLEAN_hqc_public_key_to_string(uint8_t *pk, const uint8_t *pk_seed, const uint64_t *s) {
memcpy(pk, pk_seed, SEED_BYTES);
PQCLEAN_HQC128_CLEAN_store8_arr(pk + SEED_BYTES, VEC_N_SIZE_BYTES, s, VEC_N_SIZE_64);
}
/**
* @brief Parse a public key from a string
*
* The public key is composed of the syndrome <b>s</b> as well as the seed used to generate the vector <b>h</b>
*
* @param[out] h uint64_t representation of vector h
* @param[out] s uint64_t representation of vector s
* @param[in] pk String containing the public key
*/
void PQCLEAN_HQC128_CLEAN_hqc_public_key_from_string(uint64_t *h, uint64_t *s, const uint8_t *pk) {
seedexpander_state pk_seedexpander;
PQCLEAN_HQC128_CLEAN_seedexpander_init(&pk_seedexpander, pk, SEED_BYTES);
PQCLEAN_HQC128_CLEAN_vect_set_random(&pk_seedexpander, h);
PQCLEAN_HQC128_CLEAN_load8_arr(s, VEC_N_SIZE_64, pk + SEED_BYTES, VEC_N_SIZE_BYTES);
PQCLEAN_HQC128_CLEAN_seedexpander_release(&pk_seedexpander);
}
/**
* @brief Parse a ciphertext into a string
*
* The ciphertext is composed of vectors <b>u</b>, <b>v</b> and salt.
*
* @param[out] ct String containing the ciphertext
* @param[in] u uint64_t representation of vector u
* @param[in] v uint64_t representation of vector v
* @param[in] salt String containing a salt
*/
void PQCLEAN_HQC128_CLEAN_hqc_ciphertext_to_string(uint8_t *ct, const uint64_t *u, const uint64_t *v, const uint8_t *salt) {
PQCLEAN_HQC128_CLEAN_store8_arr(ct, VEC_N_SIZE_BYTES, u, VEC_N_SIZE_64);
PQCLEAN_HQC128_CLEAN_store8_arr(ct + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES, v, VEC_N1N2_SIZE_64);
memcpy(ct + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, salt, SALT_SIZE_BYTES);
}
/**
* @brief Parse a ciphertext from a string
*
* The ciphertext is composed of vectors <b>u</b>, <b>v</b> and salt.
*
* @param[out] u uint64_t representation of vector u
* @param[out] v uint64_t representation of vector v
* @param[out] d String containing the hash d
* @param[in] ct String containing the ciphertext
*/
void PQCLEAN_HQC128_CLEAN_hqc_ciphertext_from_string(uint64_t *u, uint64_t *v, uint8_t *salt, const uint8_t *ct) {
PQCLEAN_HQC128_CLEAN_load8_arr(u, VEC_N_SIZE_64, ct, VEC_N_SIZE_BYTES);
PQCLEAN_HQC128_CLEAN_load8_arr(v, VEC_N1N2_SIZE_64, ct + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES);
memcpy(salt, ct + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, SALT_SIZE_BYTES);
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef PARSING_H
#define PARSING_H
/**
* @file parsing.h
* @brief Header file for parsing.c
*/
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_load8_arr(uint64_t *out64, size_t outlen, const uint8_t *in8, size_t inlen);
void PQCLEAN_HQC128_CLEAN_store8_arr(uint8_t *out8, size_t outlen, const uint64_t *in64, size_t inlen);
void PQCLEAN_HQC128_CLEAN_hqc_secret_key_to_string(uint8_t *sk, const uint8_t *sk_seed, const uint8_t *sigma, const uint8_t *pk);
void PQCLEAN_HQC128_CLEAN_hqc_secret_key_from_string(uint64_t *x, uint64_t *y, uint8_t *sigma, uint8_t *pk, const uint8_t *sk);
void PQCLEAN_HQC128_CLEAN_hqc_public_key_to_string(uint8_t *pk, const uint8_t *pk_seed, const uint64_t *s);
void PQCLEAN_HQC128_CLEAN_hqc_public_key_from_string(uint64_t *h, uint64_t *s, const uint8_t *pk);
void PQCLEAN_HQC128_CLEAN_hqc_ciphertext_to_string(uint8_t *ct, const uint64_t *u, const uint64_t *v, const uint8_t *salt);
void PQCLEAN_HQC128_CLEAN_hqc_ciphertext_from_string(uint64_t *u, uint64_t *v, uint8_t *salt, const uint8_t *ct);
#endif
+193
View File
@@ -0,0 +1,193 @@
#include "parameters.h"
#include "reed_muller.h"
#include <stdint.h>
#include <string.h>
/**
* @file reed_muller.c
* @brief Constant time implementation of Reed-Muller code RM(1,7)
*/
// number of repeated code words
#define MULTIPLICITY CEIL_DIVIDE(PARAM_N2, 128)
// copy bit 0 into all bits of a 32 bit value
#define BIT0MASK(x) (uint32_t)(-((x) & 1))
/**
* @brief Encode a single byte into a single codeword using RM(1,7)
*
* Encoding matrix of this code:
* bit pattern (note that bits are numbered big endian)
* 0 aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa
* 1 cccccccc cccccccc cccccccc cccccccc
* 2 f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
* 3 ff00ff00 ff00ff00 ff00ff00 ff00ff00
* 4 ffff0000 ffff0000 ffff0000 ffff0000
* 5 ffffffff 00000000 ffffffff 00000000
* 6 ffffffff ffffffff 00000000 00000000
* 7 ffffffff ffffffff ffffffff ffffffff
*
* @param[out] word An RM(1,7) codeword
* @param[in] message A message
*/
static void encode(uint64_t *cword, uint8_t message) {
uint32_t first_word;
// bit 7 flips all the bits, do that first to save work
first_word = BIT0MASK(message >> 7);
// bits 0, 1, 2, 3, 4 are the same for all four longs
// (Warning: in the bit matrix above, low bits are at the left!)
first_word ^= BIT0MASK(message >> 0) & 0xaaaaaaaa;
first_word ^= BIT0MASK(message >> 1) & 0xcccccccc;
first_word ^= BIT0MASK(message >> 2) & 0xf0f0f0f0;
first_word ^= BIT0MASK(message >> 3) & 0xff00ff00;
first_word ^= BIT0MASK(message >> 4) & 0xffff0000;
// we can store this in the first quarter
cword[0] = first_word;
// bit 5 flips entries 1 and 3; bit 6 flips 2 and 3
first_word ^= BIT0MASK(message >> 5);
cword[0] |= (uint64_t)first_word << 32;
first_word ^= BIT0MASK(message >> 6);
cword[1] = (uint64_t)first_word << 32;
first_word ^= BIT0MASK(message >> 5);
cword[1] |= first_word;
}
/**
* @brief Hadamard transform
*
* Perform hadamard transform of src and store result in dst
* src is overwritten
*
* @param[out] src Structure that contain the expanded codeword
* @param[out] dst Structure that contain the expanded codeword
*/
static void hadamard(uint16_t src[128], uint16_t dst[128]) {
// the passes move data:
// src -> dst -> src -> dst -> src -> dst -> src -> dst
// using p1 and p2 alternately
uint16_t *p1 = src;
uint16_t *p2 = dst;
uint16_t *p3;
for (size_t pass = 0; pass < 7; ++pass) {
for (size_t i = 0; i < 64; ++i) {
p2[i] = p1[2 * i] + p1[2 * i + 1];
p2[i + 64] = p1[2 * i] - p1[2 * i + 1];
}
// swap p1, p2 for next round
p3 = p1;
p1 = p2;
p2 = p3;
}
}
/**
* @brief Add multiple codewords into expanded codeword
*
* Accesses memory in order
* Note: this does not write the codewords as -1 or +1 as the green machine does
* instead, just 0 and 1 is used.
* The resulting hadamard transform has:
* all values are halved
* the first entry is 64 too high
*
* @param[out] dest Structure that contain the expanded codeword
* @param[in] src Structure that contain the codeword
*/
static void expand_and_sum(uint16_t dest[128], const uint64_t src[2 * MULTIPLICITY]) {
// start with the first copy
for (size_t part = 0; part < 2; ++part) {
for (size_t bit = 0; bit < 64; ++bit) {
dest[part * 64 + bit] = ((src[part] >> bit) & 1);
}
}
// sum the rest of the copies
for (size_t copy = 1; copy < MULTIPLICITY; ++copy) {
for (size_t part = 0; part < 2; ++part) {
for (size_t bit = 0; bit < 64; ++bit) {
dest[part * 64 + bit] += (uint16_t) ((src[2 * copy + part] >> bit) & 1);
}
}
}
}
/**
* @brief Finding the location of the highest value
*
* This is the final step of the green machine: find the location of the highest value,
* and add 128 if the peak is positive
* if there are two identical peaks, the peak with smallest value
* in the lowest 7 bits it taken
* @param[in] transform Structure that contain the expanded codeword
*/
static uint8_t find_peaks(const uint16_t transform[128]) {
uint16_t peak_abs = 0;
uint16_t peak = 0;
uint16_t pos = 0;
uint16_t t, abs, mask;
for (uint16_t i = 0; i < 128; ++i) {
t = transform[i];
abs = t ^ ((uint16_t)(-(t >> 15)) & (t ^ -t)); // t = abs(t)
mask = -(((uint16_t)(peak_abs - abs)) >> 15);
peak ^= mask & (peak ^ t);
pos ^= mask & (pos ^ i);
peak_abs ^= mask & (peak_abs ^ abs);
}
// set bit 7
pos |= 128 & (uint16_t)((peak >> 15) - 1);
return (uint8_t) pos;
}
/**
* @brief Encodes the received word
*
* The message consists of N1 bytes each byte is encoded into PARAM_N2 bits,
* or MULTIPLICITY repeats of 128 bits
*
* @param[out] cdw Array of size VEC_N1N2_SIZE_64 receiving the encoded message
* @param[in] msg Array of size VEC_N1_SIZE_64 storing the message
*/
void PQCLEAN_HQC128_CLEAN_reed_muller_encode(uint64_t *cdw, const uint8_t *msg) {
for (size_t i = 0; i < VEC_N1_SIZE_BYTES; ++i) {
// encode first word
encode(&cdw[2 * i * MULTIPLICITY], msg[i]);
// copy to other identical codewords
for (size_t copy = 1; copy < MULTIPLICITY; ++copy) {
memcpy(&cdw[2 * i * MULTIPLICITY + 2 * copy], &cdw[2 * i * MULTIPLICITY], 16);
}
}
}
/**
* @brief Decodes the received word
*
* Decoding uses fast hadamard transform, for a more complete picture on Reed-Muller decoding, see MacWilliams, Florence Jessie, and Neil James Alexander Sloane.
* The theory of error-correcting codes codes @cite macwilliams1977theory
*
* @param[out] msg Array of size VEC_N1_SIZE_64 receiving the decoded message
* @param[in] cdw Array of size VEC_N1N2_SIZE_64 storing the received word
*/
void PQCLEAN_HQC128_CLEAN_reed_muller_decode(uint8_t *msg, const uint64_t *cdw) {
uint16_t expanded[128];
uint16_t transform[128];
for (size_t i = 0; i < VEC_N1_SIZE_BYTES; ++i) {
// collect the codewords
expand_and_sum(expanded, &cdw[2 * i * MULTIPLICITY]);
// apply hadamard transform
hadamard(expanded, transform);
// fix the first entry to get the half Hadamard transform
transform[0] -= 64 * MULTIPLICITY;
// finish the decoding
msg[i] = find_peaks(transform);
}
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef REED_MULLER_H
#define REED_MULLER_H
/**
* @file reed_muller.h
* @brief Header file of reed_muller.c
*/
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_reed_muller_encode(uint64_t *cdw, const uint8_t *msg);
void PQCLEAN_HQC128_CLEAN_reed_muller_decode(uint8_t *msg, const uint64_t *cdw);
#endif
+335
View File
@@ -0,0 +1,335 @@
#include "fft.h"
#include "gf.h"
#include "parameters.h"
#include "reed_solomon.h"
#include <stdint.h>
#include <string.h>
/**
* @file reed_solomon.c
* @brief Constant time implementation of Reed-Solomon codes
*/
/**
* @brief Encodes a message message of PARAM_K bits to a Reed-Solomon codeword codeword of PARAM_N1 bytes
*
* Following @cite lin1983error (Chapter 4 - Cyclic Codes),
* We perform a systematic encoding using a linear (PARAM_N1 - PARAM_K)-stage shift register
* with feedback connections based on the generator polynomial PARAM_RS_POLY of the Reed-Solomon code.
*
* @param[out] cdw Array of size VEC_N1_SIZE_64 receiving the encoded message
* @param[in] msg Array of size VEC_K_SIZE_64 storing the message
*/
void PQCLEAN_HQC128_CLEAN_reed_solomon_encode(uint8_t *cdw, const uint8_t *msg) {
uint8_t gate_value = 0;
uint16_t tmp[PARAM_G] = {0};
uint16_t PARAM_RS_POLY [] = {RS_POLY_COEFS};
memset(cdw, 0, PARAM_N1);
for (size_t i = 0; i < PARAM_K; ++i) {
gate_value = msg[PARAM_K - 1 - i] ^ cdw[PARAM_N1 - PARAM_K - 1];
for (size_t j = 0; j < PARAM_G; ++j) {
tmp[j] = PQCLEAN_HQC128_CLEAN_gf_mul(gate_value, PARAM_RS_POLY[j]);
}
for (size_t k = PARAM_N1 - PARAM_K - 1; k; --k) {
cdw[k] = (uint8_t)(cdw[k - 1] ^ tmp[k]);
}
cdw[0] = (uint8_t)tmp[0];
}
memcpy(cdw + PARAM_N1 - PARAM_K, msg, PARAM_K);
}
/**
* @brief Computes 2 * PARAM_DELTA syndromes
*
* @param[out] syndromes Array of size 2 * PARAM_DELTA receiving the computed syndromes
* @param[in] cdw Array of size PARAM_N1 storing the received vector
*/
static void compute_syndromes(uint16_t *syndromes, uint8_t *cdw) {
for (size_t i = 0; i < 2 * PARAM_DELTA; ++i) {
for (size_t j = 1; j < PARAM_N1; ++j) {
syndromes[i] ^= PQCLEAN_HQC128_CLEAN_gf_mul(cdw[j], alpha_ij_pow[i][j - 1]);
}
syndromes[i] ^= cdw[0];
}
}
/**
* @brief Computes the error locator polynomial (ELP) sigma
*
* This is a constant time implementation of Berlekamp's simplified algorithm (see @cite lin1983error (Chapter 6 - BCH Codes). <br>
* We use the letter p for rho which is initialized at -1. <br>
* The array X_sigma_p represents the polynomial X^(mu-rho)*sigma_p(X). <br>
* Instead of maintaining a list of sigmas, we update in place both sigma and X_sigma_p. <br>
* sigma_copy serves as a temporary save of sigma in case X_sigma_p needs to be updated. <br>
* We can properly correct only if the degree of sigma does not exceed PARAM_DELTA.
* This means only the first PARAM_DELTA + 1 coefficients of sigma are of value
* and we only need to save its first PARAM_DELTA - 1 coefficients.
*
* @returns the degree of the ELP sigma
* @param[out] sigma Array of size (at least) PARAM_DELTA receiving the ELP
* @param[in] syndromes Array of size (at least) 2*PARAM_DELTA storing the syndromes
*/
static uint16_t compute_elp(uint16_t *sigma, const uint16_t *syndromes) {
uint16_t deg_sigma = 0;
uint16_t deg_sigma_p = 0;
uint16_t deg_sigma_copy = 0;
uint16_t sigma_copy[PARAM_DELTA + 1] = {0};
uint16_t X_sigma_p[PARAM_DELTA + 1] = {0, 1};
uint16_t pp = (uint16_t) -1; // 2*rho
uint16_t d_p = 1;
uint16_t d = syndromes[0];
uint16_t mask1, mask2, mask12;
uint16_t deg_X, deg_X_sigma_p;
uint16_t dd;
uint16_t mu;
uint16_t i;
sigma[0] = 1;
for (mu = 0; (mu < (2 * PARAM_DELTA)); ++mu) {
// Save sigma in case we need it to update X_sigma_p
memcpy(sigma_copy, sigma, 2 * (PARAM_DELTA));
deg_sigma_copy = deg_sigma;
dd = PQCLEAN_HQC128_CLEAN_gf_mul(d, PQCLEAN_HQC128_CLEAN_gf_inverse(d_p));
for (i = 1; (i <= mu + 1) && (i <= PARAM_DELTA); ++i) {
sigma[i] ^= PQCLEAN_HQC128_CLEAN_gf_mul(dd, X_sigma_p[i]);
}
deg_X = mu - pp;
deg_X_sigma_p = deg_X + deg_sigma_p;
// mask1 = 0xffff if(d != 0) and 0 otherwise
mask1 = -((uint16_t) - d >> 15);
// mask2 = 0xffff if(deg_X_sigma_p > deg_sigma) and 0 otherwise
mask2 = -((uint16_t) (deg_sigma - deg_X_sigma_p) >> 15);
// mask12 = 0xffff if the deg_sigma increased and 0 otherwise
mask12 = mask1 & mask2;
deg_sigma ^= mask12 & (deg_X_sigma_p ^ deg_sigma);
if (mu == (2 * PARAM_DELTA - 1)) {
break;
}
pp ^= mask12 & (mu ^ pp);
d_p ^= mask12 & (d ^ d_p);
for (i = PARAM_DELTA; i; --i) {
X_sigma_p[i] = (mask12 & sigma_copy[i - 1]) ^ (~mask12 & X_sigma_p[i - 1]);
}
deg_sigma_p ^= mask12 & (deg_sigma_copy ^ deg_sigma_p);
d = syndromes[mu + 1];
for (i = 1; (i <= mu + 1) && (i <= PARAM_DELTA); ++i) {
d ^= PQCLEAN_HQC128_CLEAN_gf_mul(sigma[i], syndromes[mu + 1 - i]);
}
}
return deg_sigma;
}
/**
* @brief Computes the error polynomial error from the error locator polynomial sigma
*
* See function PQCLEAN_HQC128_CLEAN_fft for more details.
*
* @param[out] error Array of 2^PARAM_M elements receiving the error polynomial
* @param[out] error_compact Array of PARAM_DELTA + PARAM_N1 elements receiving a compact representation of the vector error
* @param[in] sigma Array of 2^PARAM_FFT elements storing the error locator polynomial
*/
static void compute_roots(uint8_t *error, uint16_t *sigma) {
uint16_t w[1 << PARAM_M] = {0};
PQCLEAN_HQC128_CLEAN_fft(w, sigma, PARAM_DELTA + 1);
PQCLEAN_HQC128_CLEAN_fft_retrieve_error_poly(error, w);
}
/**
* @brief Computes the polynomial z(x)
*
* See @cite lin1983error (Chapter 6 - BCH Codes) for more details.
*
* @param[out] z Array of PARAM_DELTA + 1 elements receiving the polynomial z(x)
* @param[in] sigma Array of 2^PARAM_FFT elements storing the error locator polynomial
* @param[in] degree Integer that is the degree of polynomial sigma
* @param[in] syndromes Array of 2 * PARAM_DELTA storing the syndromes
*/
static void compute_z_poly(uint16_t *z, const uint16_t *sigma, uint16_t degree, const uint16_t *syndromes) {
size_t i, j;
uint16_t mask;
z[0] = 1;
for (i = 1; i < PARAM_DELTA + 1; ++i) {
mask = -((uint16_t) (i - degree - 1) >> 15);
z[i] = mask & sigma[i];
}
z[1] ^= syndromes[0];
for (i = 2; i <= PARAM_DELTA; ++i) {
mask = -((uint16_t) (i - degree - 1) >> 15);
z[i] ^= mask & syndromes[i - 1];
for (j = 1; j < i; ++j) {
z[i] ^= mask & PQCLEAN_HQC128_CLEAN_gf_mul(sigma[j], syndromes[i - j - 1]);
}
}
}
/**
* @brief Computes the error values
*
* See @cite lin1983error (Chapter 6 - BCH Codes) for more details.
*
* @param[out] error_values Array of PARAM_DELTA elements receiving the error values
* @param[in] z Array of PARAM_DELTA + 1 elements storing the polynomial z(x)
* @param[in] z_degree Integer that is the degree of polynomial z(x)
* @param[in] error_compact Array of PARAM_DELTA + PARAM_N1 storing compact representation of the error
*/
static void compute_error_values(uint16_t *error_values, const uint16_t *z, const uint8_t *error) {
uint16_t beta_j[PARAM_DELTA] = {0};
uint16_t e_j[PARAM_DELTA] = {0};
uint16_t delta_counter;
uint16_t delta_real_value;
uint16_t found;
uint16_t mask1;
uint16_t mask2;
uint16_t tmp1;
uint16_t tmp2;
uint16_t inverse;
uint16_t inverse_power_j;
// Compute the beta_{j_i} page 31 of the documentation
delta_counter = 0;
for (size_t i = 0; i < PARAM_N1; i++) {
found = 0;
mask1 = (uint16_t) (-((int32_t)error[i]) >> 31); // error[i] != 0
for (size_t j = 0; j < PARAM_DELTA; j++) {
mask2 = ~((uint16_t) (-((int32_t) j ^ delta_counter) >> 31)); // j == delta_counter
beta_j[j] += mask1 & mask2 & gf_exp[i];
found += mask1 & mask2 & 1;
}
delta_counter += found;
}
delta_real_value = delta_counter;
// Compute the e_{j_i} page 31 of the documentation
for (size_t i = 0; i < PARAM_DELTA; ++i) {
tmp1 = 1;
tmp2 = 1;
inverse = PQCLEAN_HQC128_CLEAN_gf_inverse(beta_j[i]);
inverse_power_j = 1;
for (size_t j = 1; j <= PARAM_DELTA; ++j) {
inverse_power_j = PQCLEAN_HQC128_CLEAN_gf_mul(inverse_power_j, inverse);
tmp1 ^= PQCLEAN_HQC128_CLEAN_gf_mul(inverse_power_j, z[j]);
}
for (size_t k = 1; k < PARAM_DELTA; ++k) {
tmp2 = PQCLEAN_HQC128_CLEAN_gf_mul(tmp2, (1 ^ PQCLEAN_HQC128_CLEAN_gf_mul(inverse, beta_j[(i + k) % PARAM_DELTA])));
}
mask1 = (uint16_t) (((int16_t) i - delta_real_value) >> 15); // i < delta_real_value
e_j[i] = mask1 & PQCLEAN_HQC128_CLEAN_gf_mul(tmp1, PQCLEAN_HQC128_CLEAN_gf_inverse(tmp2));
}
// Place the delta e_{j_i} values at the right coordinates of the output vector
delta_counter = 0;
for (size_t i = 0; i < PARAM_N1; ++i) {
found = 0;
mask1 = (uint16_t) (-((int32_t)error[i]) >> 31); // error[i] != 0
for (size_t j = 0; j < PARAM_DELTA; j++) {
mask2 = ~((uint16_t) (-((int32_t) j ^ delta_counter) >> 31)); // j == delta_counter
error_values[i] += mask1 & mask2 & e_j[j];
found += mask1 & mask2 & 1;
}
delta_counter += found;
}
}
/**
* @brief Correct the errors
*
* @param[out] cdw Array of PARAM_N1 elements receiving the corrected vector
* @param[in] error Array of the error vector
* @param[in] error_values Array of PARAM_DELTA elements storing the error values
*/
static void correct_errors(uint8_t *cdw, const uint16_t *error_values) {
for (size_t i = 0; i < PARAM_N1; ++i) {
cdw[i] ^= error_values[i];
}
}
/**
* @brief Decodes the received word
*
* This function relies on six steps:
* <ol>
* <li> The first step, is the computation of the 2*PARAM_DELTA syndromes.
* <li> The second step is the computation of the error-locator polynomial sigma.
* <li> The third step, done by additive FFT, is finding the error-locator numbers by calculating the roots of the polynomial sigma and takings their inverses.
* <li> The fourth step, is the polynomial z(x).
* <li> The fifth step, is the computation of the error values.
* <li> The sixth step is the correction of the errors in the received polynomial.
* </ol>
* For a more complete picture on Reed-Solomon decoding, see Shu. Lin and Daniel J. Costello in Error Control Coding: Fundamentals and Applications @cite lin1983error
*
* @param[out] msg Array of size VEC_K_SIZE_64 receiving the decoded message
* @param[in] cdw Array of size VEC_N1_SIZE_64 storing the received word
*/
void PQCLEAN_HQC128_CLEAN_reed_solomon_decode(uint8_t *msg, uint8_t *cdw) {
uint16_t syndromes[2 * PARAM_DELTA] = {0};
uint16_t sigma[1 << PARAM_FFT] = {0};
uint8_t error[1 << PARAM_M] = {0};
uint16_t z[PARAM_N1] = {0};
uint16_t error_values[PARAM_N1] = {0};
uint16_t deg;
// Calculate the 2*PARAM_DELTA syndromes
compute_syndromes(syndromes, cdw);
// Compute the error locator polynomial sigma
// Sigma's degree is at most PARAM_DELTA but the FFT requires the extra room
deg = compute_elp(sigma, syndromes);
// Compute the error polynomial error
compute_roots(error, sigma);
// Compute the polynomial z(x)
compute_z_poly(z, sigma, deg, syndromes);
// Compute the error values
compute_error_values(error_values, z, error);
// Correct the errors
correct_errors(cdw, error_values);
// Retrieve the message from the decoded codeword
memcpy(msg, cdw + (PARAM_G - 1), PARAM_K);
}
File diff suppressed because one or more lines are too long
+40
View File
@@ -0,0 +1,40 @@
#include "shake_ds.h"
/**
* @file shake_ds.c
* @brief Implementation SHAKE-256 with incremental API and domain separation
*/
/**
* @brief SHAKE-256 with incremental API and domain separation
*
* Derived from function SHAKE_256 in fips202.c
*
* @param[out] state Internal state of SHAKE
* @param[in] output Pointer to output
* @param[in] input Pointer to input
* @param[in] inlen length of input in bytes
* @param[in] domain byte for domain separation
*/
void PQCLEAN_HQC128_CLEAN_shake256_512_ds(shake256incctx *state, uint8_t *output, const uint8_t *input, size_t inlen, uint8_t domain) {
/* Init state */
shake256_inc_init(state);
/* Absorb input */
shake256_inc_absorb(state, input, inlen);
/* Absorb domain separation byte */
shake256_inc_absorb(state, &domain, 1);
/* Finalize */
shake256_inc_finalize(state);
/* Squeeze output */
shake256_inc_squeeze(output, 512 / 8, state);
/* Release ctx */
shake256_inc_ctx_release(state);
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef SHAKE_DS_H
#define SHAKE_DS_H
/**
* @file shake_ds.h
* @brief Header file of shake_ds.c
*/
#include "fips202.h"
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_shake256_512_ds(shake256incctx *state, uint8_t *output, const uint8_t *input, size_t inlen, uint8_t domain);
#endif
+60
View File
@@ -0,0 +1,60 @@
#include "domains.h"
#include "fips202.h"
#include "shake_prng.h"
/**
* @file shake_prng.c
* @brief Implementation of SHAKE-256 based seed expander
*/
/**
* @brief Initialise a SHAKE-256 based seed expander
*
* Derived from function SHAKE_256 in fips202.c
*
* @param[out] state Keccak internal state and a counter
* @param[in] seed A seed
* @param[in] seedlen The seed bytes length
*/
void PQCLEAN_HQC128_CLEAN_seedexpander_init(seedexpander_state *state, const uint8_t *seed, size_t seedlen) {
uint8_t domain = SEEDEXPANDER_DOMAIN;
shake256_inc_init(state);
shake256_inc_absorb(state, seed, seedlen);
shake256_inc_absorb(state, &domain, 1);
shake256_inc_finalize(state);
}
/**
* @brief A SHAKE-256 based seed expander
*
* Derived from function SHAKE_256 in fips202.c
* Squeezes Keccak state by 64-bit blocks (hardware version compatibility)
*
* @param[out] state Internal state of SHAKE
* @param[out] output The XOF data
* @param[in] outlen Number of bytes to return
*/
void PQCLEAN_HQC128_CLEAN_seedexpander(seedexpander_state *state, uint8_t *output, size_t outlen) {
const size_t bsize = sizeof(uint64_t);
const size_t remainder = outlen % bsize;
uint8_t tmp[sizeof(uint64_t)];
shake256_inc_squeeze(output, outlen - remainder, state);
if (remainder != 0) {
shake256_inc_squeeze(tmp, bsize, state);
output += outlen - remainder;
for (size_t i = 0; i < remainder; ++i) {
output[i] = tmp[i];
}
}
}
/**
* @brief Release the seed expander context
* @param[in] state Internal state of the seed expander
*/
void PQCLEAN_HQC128_CLEAN_seedexpander_release(seedexpander_state *state) {
shake256_inc_ctx_release(state);
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef SHAKE_PRNG_H
#define SHAKE_PRNG_H
/**
* @file shake_prng.h
* @brief Header file of shake_prng.c
*/
#include "fips202.h"
#include <stddef.h>
#include <stdint.h>
typedef shake256incctx seedexpander_state;
void PQCLEAN_HQC128_CLEAN_seedexpander_init(seedexpander_state *state, const uint8_t *seed, size_t seedlen);
void PQCLEAN_HQC128_CLEAN_seedexpander(seedexpander_state *state, uint8_t *output, size_t outlen);
void PQCLEAN_HQC128_CLEAN_seedexpander_release(seedexpander_state *state);
#endif
+197
View File
@@ -0,0 +1,197 @@
#include "parameters.h"
#include "parsing.h"
#include "randombytes.h"
#include "vector.h"
#include <stdint.h>
#include <string.h>
/**
* @file vector.c
* @brief Implementation of vectors sampling and some utilities for the HQC scheme
*/
static uint32_t m_val[75] = { 243079, 243093, 243106, 243120, 243134, 243148, 243161, 243175, 243189, 243203, 243216, 243230, 243244, 243258, 243272, 243285, 243299, 243313, 243327, 243340, 243354, 243368, 243382, 243396, 243409, 243423, 243437, 243451, 243465, 243478, 243492, 243506, 243520, 243534, 243547, 243561, 243575, 243589, 243603, 243616, 243630, 243644, 243658, 243672, 243686, 243699, 243713, 243727, 243741, 243755, 243769, 243782, 243796, 243810, 243824, 243838, 243852, 243865, 243879, 243893, 243907, 243921, 243935, 243949, 243962, 243976, 243990, 244004, 244018, 244032, 244046, 244059, 244073, 244087, 244101 };
/**
* @brief Constant-time comparison of two integers v1 and v2
*
* Returns 1 if v1 is equal to v2 and 0 otherwise
* https://gist.github.com/sneves/10845247
*
* @param[in] v1
* @param[in] v2
*/
static inline uint32_t compare_u32(uint32_t v1, uint32_t v2) {
return 1 ^ ((uint32_t)((v1 - v2) | (v2 - v1)) >> 31);
}
static uint64_t single_bit_mask(uint32_t pos) {
uint64_t ret = 0;
uint64_t mask = 1;
uint64_t tmp;
for (size_t i = 0; i < 64; ++i) {
tmp = pos - i;
tmp = 0 - (1 - ((uint64_t)(tmp | (0 - tmp)) >> 63));
ret |= mask & tmp;
mask <<= 1;
}
return ret;
}
static inline uint32_t cond_sub(uint32_t r, uint32_t n) {
uint32_t mask;
r -= n;
mask = 0 - (r >> 31);
return r + (n & mask);
}
static inline uint32_t reduce(uint32_t a, size_t i) {
uint32_t q, n, r;
q = ((uint64_t) a * m_val[i]) >> 32;
n = (uint32_t)(PARAM_N - i);
r = a - q * n;
return cond_sub(r, n);
}
/**
* @brief Generates a vector of a given Hamming weight
*
* Implementation of Algorithm 5 in https://eprint.iacr.org/2021/1631.pdf
*
* @param[in] ctx Pointer to the context of the seed expander
* @param[in] v Pointer to an array
* @param[in] weight Integer that is the Hamming weight
*/
void PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(seedexpander_state *ctx, uint64_t *v, uint16_t weight) {
uint8_t rand_bytes[4 * PARAM_OMEGA_R] = {0}; // to be interpreted as PARAM_OMEGA_R 32-bit unsigned ints
uint32_t support[PARAM_OMEGA_R] = {0};
uint32_t index_tab [PARAM_OMEGA_R] = {0};
uint64_t bit_tab [PARAM_OMEGA_R] = {0};
uint32_t pos, found, mask32, tmp;
uint64_t mask64, val;
PQCLEAN_HQC128_CLEAN_seedexpander(ctx, rand_bytes, 4 * weight);
for (size_t i = 0; i < weight; ++i) {
support[i] = rand_bytes[4 * i];
support[i] |= rand_bytes[4 * i + 1] << 8;
support[i] |= (uint32_t)rand_bytes[4 * i + 2] << 16;
support[i] |= (uint32_t)rand_bytes[4 * i + 3] << 24;
support[i] = (uint32_t)(i + reduce(support[i], i)); // use constant-tme reduction
}
for (size_t i = (weight - 1); i-- > 0;) {
found = 0;
for (size_t j = i + 1; j < weight; ++j) {
found |= compare_u32(support[j], support[i]);
}
mask32 = 0 - found;
support[i] = (mask32 & i) ^ (~mask32 & support[i]);
}
for (size_t i = 0; i < weight; ++i) {
index_tab[i] = support[i] >> 6;
pos = support[i] & 0x3f;
bit_tab[i] = single_bit_mask(pos); // avoid secret shift
}
for (size_t i = 0; i < VEC_N_SIZE_64; ++i) {
val = 0;
for (size_t j = 0; j < weight; ++j) {
tmp = (uint32_t)(i - index_tab[j]);
tmp = 1 ^ ((uint32_t)(tmp | (0 - tmp)) >> 31);
mask64 = 0 - (uint64_t)tmp;
val |= (bit_tab[j] & mask64);
}
v[i] |= val;
}
}
/**
* @brief Generates a random vector of dimension <b>PARAM_N</b>
*
* This function generates a random binary vector of dimension <b>PARAM_N</b>. It generates a random
* array of bytes using the PQCLEAN_HQC128_CLEAN_seedexpander function, and drop the extra bits using a mask.
*
* @param[in] v Pointer to an array
* @param[in] ctx Pointer to the context of the seed expander
*/
void PQCLEAN_HQC128_CLEAN_vect_set_random(seedexpander_state *ctx, uint64_t *v) {
uint8_t rand_bytes[VEC_N_SIZE_BYTES] = {0};
PQCLEAN_HQC128_CLEAN_seedexpander(ctx, rand_bytes, VEC_N_SIZE_BYTES);
PQCLEAN_HQC128_CLEAN_load8_arr(v, VEC_N_SIZE_64, rand_bytes, VEC_N_SIZE_BYTES);
v[VEC_N_SIZE_64 - 1] &= RED_MASK;
}
/**
* @brief Adds two vectors
*
* @param[out] o Pointer to an array that is the result
* @param[in] v1 Pointer to an array that is the first vector
* @param[in] v2 Pointer to an array that is the second vector
* @param[in] size Integer that is the size of the vectors
*/
void PQCLEAN_HQC128_CLEAN_vect_add(uint64_t *o, const uint64_t *v1, const uint64_t *v2, size_t size) {
for (size_t i = 0; i < size; ++i) {
o[i] = v1[i] ^ v2[i];
}
}
/**
* @brief Compares two vectors
*
* @param[in] v1 Pointer to an array that is first vector
* @param[in] v2 Pointer to an array that is second vector
* @param[in] size Integer that is the size of the vectors
* @returns 0 if the vectors are equal and 1 otherwise
*/
uint8_t PQCLEAN_HQC128_CLEAN_vect_compare(const uint8_t *v1, const uint8_t *v2, size_t size) {
uint16_t r = 0x0100;
for (size_t i = 0; i < size; i++) {
r |= v1[i] ^ v2[i];
}
return (r - 1) >> 8;
}
/**
* @brief Resize a vector so that it contains <b>size_o</b> bits
*
* @param[out] o Pointer to the output vector
* @param[in] size_o Integer that is the size of the output vector in bits
* @param[in] v Pointer to the input vector
* @param[in] size_v Integer that is the size of the input vector in bits
*/
void PQCLEAN_HQC128_CLEAN_vect_resize(uint64_t *o, uint32_t size_o, const uint64_t *v, uint32_t size_v) {
uint64_t mask = 0x7FFFFFFFFFFFFFFF;
size_t val = 0;
if (size_o < size_v) {
if (size_o % 64) {
val = 64 - (size_o % 64);
}
memcpy(o, v, VEC_N1N2_SIZE_BYTES);
for (size_t i = 0; i < val; ++i) {
o[VEC_N1N2_SIZE_64 - 1] &= (mask >> i);
}
} else {
memcpy(o, v, 8 * CEIL_DIVIDE(size_v, 64));
}
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef VECTOR_H
#define VECTOR_H
/**
* @file vector.h
* @brief Header file for vector.c
*/
#include "shake_prng.h"
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC128_CLEAN_vect_set_random_fixed_weight(seedexpander_state *ctx, uint64_t *v, uint16_t weight);
void PQCLEAN_HQC128_CLEAN_vect_set_random(seedexpander_state *ctx, uint64_t *v);
void PQCLEAN_HQC128_CLEAN_vect_add(uint64_t *o, const uint64_t *v1, const uint64_t *v2, size_t size);
uint8_t PQCLEAN_HQC128_CLEAN_vect_compare(const uint8_t *v1, const uint8_t *v2, size_t size);
void PQCLEAN_HQC128_CLEAN_vect_resize(uint64_t *o, uint32_t size_o, const uint64_t *v, uint32_t size_v);
#endif
+1
View File
@@ -0,0 +1 @@
Public Domain
+27
View File
@@ -0,0 +1,27 @@
#ifndef PQCLEAN_HQC192_CLEAN_API_H
#define PQCLEAN_HQC192_CLEAN_API_H
/**
* @file api.h
* @brief NIST KEM API used by the HQC_KEM IND-CCA2 scheme
*/
#include <stdint.h>
#define PQCLEAN_HQC192_CLEAN_CRYPTO_ALGNAME "HQC-192"
#define PQCLEAN_HQC192_CLEAN_CRYPTO_SECRETKEYBYTES 4586
#define PQCLEAN_HQC192_CLEAN_CRYPTO_PUBLICKEYBYTES 4522
#define PQCLEAN_HQC192_CLEAN_CRYPTO_BYTES 64
#define PQCLEAN_HQC192_CLEAN_CRYPTO_CIPHERTEXTBYTES 8978
// As a technicality, the public key is appended to the secret key in order to respect the NIST API.
// Without this constraint, PQCLEAN_HQC192_CLEAN_CRYPTO_SECRETKEYBYTES would be defined as 32
int PQCLEAN_HQC192_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk);
int PQCLEAN_HQC192_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
int PQCLEAN_HQC192_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
#endif
+46
View File
@@ -0,0 +1,46 @@
#include "code.h"
#include "parameters.h"
#include "reed_muller.h"
#include "reed_solomon.h"
#include <stdint.h>
/**
* @file code.c
* @brief Implementation of concatenated code
*/
/**
*
* @brief Encoding the message m to a code word em using the concatenated code
*
* First we encode the message using the Reed-Solomon code, then with the duplicated Reed-Muller code we obtain
* a concatenated code word.
*
* @param[out] em Pointer to an array that is the tensor code word
* @param[in] m Pointer to an array that is the message
*/
void PQCLEAN_HQC192_CLEAN_code_encode(uint64_t *em, const uint8_t *m) {
uint8_t tmp[VEC_N1_SIZE_BYTES] = {0};
PQCLEAN_HQC192_CLEAN_reed_solomon_encode(tmp, m);
PQCLEAN_HQC192_CLEAN_reed_muller_encode(em, tmp);
}
/**
* @brief Decoding the code word em to a message m using the concatenated code
*
* @param[out] m Pointer to an array that is the message
* @param[in] em Pointer to an array that is the code word
*/
void PQCLEAN_HQC192_CLEAN_code_decode(uint8_t *m, const uint64_t *em) {
uint8_t tmp[VEC_N1_SIZE_BYTES] = {0};
PQCLEAN_HQC192_CLEAN_reed_muller_decode(tmp, em);
PQCLEAN_HQC192_CLEAN_reed_solomon_decode(m, tmp);
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef CODE_H
#define CODE_H
/**
* @file code.h
* @brief Header file of code.c
*/
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_code_encode(uint64_t *em, const uint8_t *message);
void PQCLEAN_HQC192_CLEAN_code_decode(uint8_t *m, const uint64_t *em);
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef DOMAINS_H
#define DOMAINS_H
/**
* @file domains.h
* @brief SHAKE-256 domains separation header grouping all domains to avoid collisions
*/
#define PRNG_DOMAIN 1
#define SEEDEXPANDER_DOMAIN 2
#define G_FCT_DOMAIN 3
#define K_FCT_DOMAIN 4
#endif
+346
View File
@@ -0,0 +1,346 @@
#include "fft.h"
#include "gf.h"
#include "parameters.h"
#include <stdint.h>
#include <string.h>
/**
* @file fft.c
* @brief Implementation of the additive FFT and its transpose.
* This implementation is based on the paper from Gao and Mateer: <br>
* Shuhong Gao and Todd Mateer, Additive Fast Fourier Transforms over Finite Fields,
* IEEE Transactions on Information Theory 56 (2010), 6265--6272.
* http://www.math.clemson.edu/~sgao/papers/GM10.pdf <br>
* and includes improvements proposed by Bernstein, Chou and Schwabe here:
* https://binary.cr.yp.to/mcbits-20130616.pdf
*/
static void radix_big(uint16_t *f0, uint16_t *f1, const uint16_t *f, uint32_t m_f);
/**
* @brief Computes the basis of betas (omitting 1) used in the additive FFT and its transpose
*
* @param[out] betas Array of size PARAM_M-1
*/
static void compute_fft_betas(uint16_t *betas) {
size_t i;
for (i = 0; i < PARAM_M - 1; ++i) {
betas[i] = 1 << (PARAM_M - 1 - i);
}
}
/**
* @brief Computes the subset sums of the given set
*
* The array subset_sums is such that its ith element is
* the subset sum of the set elements given by the binary form of i.
*
* @param[out] subset_sums Array of size 2^set_size receiving the subset sums
* @param[in] set Array of set_size elements
* @param[in] set_size Size of the array set
*/
static void compute_subset_sums(uint16_t *subset_sums, const uint16_t *set, uint16_t set_size) {
uint16_t i, j;
subset_sums[0] = 0;
for (i = 0; i < set_size; ++i) {
for (j = 0; j < (1 << i); ++j) {
subset_sums[(1 << i) + j] = set[i] ^ subset_sums[j];
}
}
}
/**
* @brief Computes the radix conversion of a polynomial f in GF(2^m)[x]
*
* Computes f0 and f1 such that f(x) = f0(x^2-x) + x.f1(x^2-x)
* as proposed by Bernstein, Chou and Schwabe:
* https://binary.cr.yp.to/mcbits-20130616.pdf
*
* @param[out] f0 Array half the size of f
* @param[out] f1 Array half the size of f
* @param[in] f Array of size a power of 2
* @param[in] m_f 2^{m_f} is the smallest power of 2 greater or equal to the number of coefficients of f
*/
static void radix(uint16_t *f0, uint16_t *f1, const uint16_t *f, uint32_t m_f) {
switch (m_f) {
case 4:
f0[4] = f[8] ^ f[12];
f0[6] = f[12] ^ f[14];
f0[7] = f[14] ^ f[15];
f1[5] = f[11] ^ f[13];
f1[6] = f[13] ^ f[14];
f1[7] = f[15];
f0[5] = f[10] ^ f[12] ^ f1[5];
f1[4] = f[9] ^ f[13] ^ f0[5];
f0[0] = f[0];
f1[3] = f[7] ^ f[11] ^ f[15];
f0[3] = f[6] ^ f[10] ^ f[14] ^ f1[3];
f0[2] = f[4] ^ f0[4] ^ f0[3] ^ f1[3];
f1[1] = f[3] ^ f[5] ^ f[9] ^ f[13] ^ f1[3];
f1[2] = f[3] ^ f1[1] ^ f0[3];
f0[1] = f[2] ^ f0[2] ^ f1[1];
f1[0] = f[1] ^ f0[1];
break;
case 3:
f0[0] = f[0];
f0[2] = f[4] ^ f[6];
f0[3] = f[6] ^ f[7];
f1[1] = f[3] ^ f[5] ^ f[7];
f1[2] = f[5] ^ f[6];
f1[3] = f[7];
f0[1] = f[2] ^ f0[2] ^ f1[1];
f1[0] = f[1] ^ f0[1];
break;
case 2:
f0[0] = f[0];
f0[1] = f[2] ^ f[3];
f1[0] = f[1] ^ f0[1];
f1[1] = f[3];
break;
case 1:
f0[0] = f[0];
f1[0] = f[1];
break;
default:
radix_big(f0, f1, f, m_f);
break;
}
}
static void radix_big(uint16_t *f0, uint16_t *f1, const uint16_t *f, uint32_t m_f) {
uint16_t Q[2 * (1 << (PARAM_FFT - 2)) + 1] = {0};
uint16_t R[2 * (1 << (PARAM_FFT - 2)) + 1] = {0};
uint16_t Q0[1 << (PARAM_FFT - 2)] = {0};
uint16_t Q1[1 << (PARAM_FFT - 2)] = {0};
uint16_t R0[1 << (PARAM_FFT - 2)] = {0};
uint16_t R1[1 << (PARAM_FFT - 2)] = {0};
size_t i, n;
n = 1;
n <<= (m_f - 2);
memcpy(Q, f + 3 * n, 2 * n);
memcpy(Q + n, f + 3 * n, 2 * n);
memcpy(R, f, 4 * n);
for (i = 0; i < n; ++i) {
Q[i] ^= f[2 * n + i];
R[n + i] ^= Q[i];
}
radix(Q0, Q1, Q, m_f - 1);
radix(R0, R1, R, m_f - 1);
memcpy(f0, R0, 2 * n);
memcpy(f0 + n, Q0, 2 * n);
memcpy(f1, R1, 2 * n);
memcpy(f1 + n, Q1, 2 * n);
}
/**
* @brief Evaluates f at all subset sums of a given set
*
* This function is a subroutine of the function PQCLEAN_HQC192_CLEAN_fft.
*
* @param[out] w Array
* @param[in] f Array
* @param[in] f_coeffs Number of coefficients of f
* @param[in] m Number of betas
* @param[in] m_f Number of coefficients of f (one more than its degree)
* @param[in] betas FFT constants
*/
static void fft_rec(uint16_t *w, uint16_t *f, size_t f_coeffs, uint8_t m, uint32_t m_f, const uint16_t *betas) {
uint16_t f0[1 << (PARAM_FFT - 2)] = {0};
uint16_t f1[1 << (PARAM_FFT - 2)] = {0};
uint16_t gammas[PARAM_M - 2] = {0};
uint16_t deltas[PARAM_M - 2] = {0};
uint16_t gammas_sums[1 << (PARAM_M - 2)] = {0};
uint16_t u[1 << (PARAM_M - 2)] = {0};
uint16_t v[1 << (PARAM_M - 2)] = {0};
uint16_t tmp[PARAM_M - (PARAM_FFT - 1)] = {0};
uint16_t beta_m_pow;
size_t i, j, k;
size_t x;
// Step 1
if (m_f == 1) {
for (i = 0; i < m; ++i) {
tmp[i] = PQCLEAN_HQC192_CLEAN_gf_mul(betas[i], f[1]);
}
w[0] = f[0];
x = 1;
for (j = 0; j < m; ++j) {
for (k = 0; k < x; ++k) {
w[x + k] = w[k] ^ tmp[j];
}
x <<= 1;
}
return;
}
// Step 2: compute g
if (betas[m - 1] != 1) {
beta_m_pow = 1;
x = 1;
x <<= m_f;
for (i = 1; i < x; ++i) {
beta_m_pow = PQCLEAN_HQC192_CLEAN_gf_mul(beta_m_pow, betas[m - 1]);
f[i] = PQCLEAN_HQC192_CLEAN_gf_mul(beta_m_pow, f[i]);
}
}
// Step 3
radix(f0, f1, f, m_f);
// Step 4: compute gammas and deltas
for (i = 0; i + 1 < m; ++i) {
gammas[i] = PQCLEAN_HQC192_CLEAN_gf_mul(betas[i], PQCLEAN_HQC192_CLEAN_gf_inverse(betas[m - 1]));
deltas[i] = PQCLEAN_HQC192_CLEAN_gf_square(gammas[i]) ^ gammas[i];
}
// Compute gammas sums
compute_subset_sums(gammas_sums, gammas, m - 1);
// Step 5
fft_rec(u, f0, (f_coeffs + 1) / 2, m - 1, m_f - 1, deltas);
k = 1;
k <<= ((m - 1) & 0xf); // &0xf is to let the compiler know that m-1 is small.
if (f_coeffs <= 3) { // 3-coefficient polynomial f case: f1 is constant
w[0] = u[0];
w[k] = u[0] ^ f1[0];
for (i = 1; i < k; ++i) {
w[i] = u[i] ^ PQCLEAN_HQC192_CLEAN_gf_mul(gammas_sums[i], f1[0]);
w[k + i] = w[i] ^ f1[0];
}
} else {
fft_rec(v, f1, f_coeffs / 2, m - 1, m_f - 1, deltas);
// Step 6
memcpy(w + k, v, 2 * k);
w[0] = u[0];
w[k] ^= u[0];
for (i = 1; i < k; ++i) {
w[i] = u[i] ^ PQCLEAN_HQC192_CLEAN_gf_mul(gammas_sums[i], v[i]);
w[k + i] ^= w[i];
}
}
}
/**
* @brief Evaluates f on all fields elements using an additive FFT algorithm
*
* f_coeffs is the number of coefficients of f (one less than its degree). <br>
* The FFT proceeds recursively to evaluate f at all subset sums of a basis B. <br>
* This implementation is based on the paper from Gao and Mateer: <br>
* Shuhong Gao and Todd Mateer, Additive Fast Fourier Transforms over Finite Fields,
* IEEE Transactions on Information Theory 56 (2010), 6265--6272.
* http://www.math.clemson.edu/~sgao/papers/GM10.pdf <br>
* and includes improvements proposed by Bernstein, Chou and Schwabe here:
* https://binary.cr.yp.to/mcbits-20130616.pdf <br>
* Note that on this first call (as opposed to the recursive calls to fft_rec), gammas are equal to betas,
* meaning the first gammas subset sums are actually the subset sums of betas (except 1). <br>
* Also note that f is altered during computation (twisted at each level).
*
* @param[out] w Array
* @param[in] f Array of 2^PARAM_FFT elements
* @param[in] f_coeffs Number coefficients of f (i.e. deg(f)+1)
*/
void PQCLEAN_HQC192_CLEAN_fft(uint16_t *w, const uint16_t *f, size_t f_coeffs) {
uint16_t betas[PARAM_M - 1] = {0};
uint16_t betas_sums[1 << (PARAM_M - 1)] = {0};
uint16_t f0[1 << (PARAM_FFT - 1)] = {0};
uint16_t f1[1 << (PARAM_FFT - 1)] = {0};
uint16_t deltas[PARAM_M - 1] = {0};
uint16_t u[1 << (PARAM_M - 1)] = {0};
uint16_t v[1 << (PARAM_M - 1)] = {0};
size_t i, k;
// Follows Gao and Mateer algorithm
compute_fft_betas(betas);
// Step 1: PARAM_FFT > 1, nothing to do
// Compute gammas sums
compute_subset_sums(betas_sums, betas, PARAM_M - 1);
// Step 2: beta_m = 1, nothing to do
// Step 3
radix(f0, f1, f, PARAM_FFT);
// Step 4: Compute deltas
for (i = 0; i < PARAM_M - 1; ++i) {
deltas[i] = PQCLEAN_HQC192_CLEAN_gf_square(betas[i]) ^ betas[i];
}
// Step 5
fft_rec(u, f0, (f_coeffs + 1) / 2, PARAM_M - 1, PARAM_FFT - 1, deltas);
fft_rec(v, f1, f_coeffs / 2, PARAM_M - 1, PARAM_FFT - 1, deltas);
k = 1 << (PARAM_M - 1);
// Step 6, 7 and error polynomial computation
memcpy(w + k, v, 2 * k);
// Check if 0 is root
w[0] = u[0];
// Check if 1 is root
w[k] ^= u[0];
// Find other roots
for (i = 1; i < k; ++i) {
w[i] = u[i] ^ PQCLEAN_HQC192_CLEAN_gf_mul(betas_sums[i], v[i]);
w[k + i] ^= w[i];
}
}
/**
* @brief Retrieves the error polynomial error from the evaluations w of the ELP (Error Locator Polynomial) on all field elements.
*
* @param[out] error Array with the error
* @param[out] error_compact Array with the error in a compact form
* @param[in] w Array of size 2^PARAM_M
*/
void PQCLEAN_HQC192_CLEAN_fft_retrieve_error_poly(uint8_t *error, const uint16_t *w) {
uint16_t gammas[PARAM_M - 1] = {0};
uint16_t gammas_sums[1 << (PARAM_M - 1)] = {0};
uint16_t k;
size_t i, index;
compute_fft_betas(gammas);
compute_subset_sums(gammas_sums, gammas, PARAM_M - 1);
k = 1 << (PARAM_M - 1);
error[0] ^= 1 ^ ((uint16_t) - w[0] >> 15);
error[0] ^= 1 ^ ((uint16_t) - w[k] >> 15);
for (i = 1; i < k; ++i) {
index = PARAM_GF_MUL_ORDER - gf_log[gammas_sums[i]];
error[index] ^= 1 ^ ((uint16_t) - w[i] >> 15);
index = PARAM_GF_MUL_ORDER - gf_log[gammas_sums[i] ^ 1];
error[index] ^= 1 ^ ((uint16_t) - w[k + i] >> 15);
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef FFT_H
#define FFT_H
/**
* @file fft.h
* @brief Header file of fft.c
*/
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_fft(uint16_t *w, const uint16_t *f, size_t f_coeffs);
void PQCLEAN_HQC192_CLEAN_fft_retrieve_error_poly(uint8_t *error, const uint16_t *w);
#endif
+170
View File
@@ -0,0 +1,170 @@
#include "gf.h"
#include "parameters.h"
#include <stddef.h>
#include <stdint.h>
/**
* @file gf.c
* @brief Galois field implementation
*/
/**
* @brief Computes the number of trailing zero bits.
*
* @returns The number of trailing zero bits in a.
* @param[in] a An operand
*/
static uint16_t trailing_zero_bits_count(uint16_t a) {
uint16_t tmp = 0;
uint16_t mask = 0xFFFF;
for (size_t i = 0; i < 14; ++i) {
tmp += ((1 - ((a >> i) & 0x0001)) & mask);
mask &= - (1 - ((a >> i) & 0x0001));
}
return tmp;
}
/**
* Reduces polynomial x modulo primitive polynomial GF_POLY.
* @returns x mod GF_POLY
* @param[in] x Polynomial of degree less than 64
* @param[in] deg_x The degree of polynomial x
*/
static uint16_t gf_reduce(uint64_t x, size_t deg_x) {
uint16_t z1, z2, rmdr, dist;
uint64_t mod;
// Deduce the number of steps of reduction
size_t steps = CEIL_DIVIDE(deg_x - (PARAM_M - 1), PARAM_GF_POLY_M2);
// Reduce
for (size_t i = 0; i < steps; ++i) {
mod = x >> PARAM_M;
x &= (1 << PARAM_M) - 1;
x ^= mod;
z1 = 0;
rmdr = PARAM_GF_POLY ^ 1;
for (size_t j = PARAM_GF_POLY_WT - 2; j; --j) {
z2 = trailing_zero_bits_count(rmdr);
dist = z2 - z1;
mod <<= dist;
x ^= mod;
rmdr ^= 1 << z2;
z1 = z2;
}
}
return (uint16_t)x;
}
/**
* Carryless multiplication of two polynomials a and b.
*
* Implementation of the algorithm mul1 in https://hal.inria.fr/inria-00188261v4/document
* with s = 2 and w = 8
*
* @param[out] The polynomial c = a * b
* @param[in] a The first polynomial
* @param[in] b The second polynomial
*/
static void gf_carryless_mul(uint8_t c[2], uint8_t a, uint8_t b) {
uint16_t h = 0, l = 0, g = 0, u[4];
uint32_t tmp1, tmp2;
uint16_t mask;
u[0] = 0;
u[1] = b & 0x7F;
u[2] = u[1] << 1;
u[3] = u[2] ^ u[1];
tmp1 = a & 3;
for (size_t i = 0; i < 4; i++) {
tmp2 = (uint32_t)(tmp1 - i);
g ^= (u[i] & (uint32_t)(0 - (1 - ((uint32_t)(tmp2 | (0 - tmp2)) >> 31))));
}
l = g;
h = 0;
for (size_t i = 2; i < 8; i += 2) {
g = 0;
tmp1 = (a >> i) & 3;
for (size_t j = 0; j < 4; ++j) {
tmp2 = (uint32_t)(tmp1 - j);
g ^= (u[j] & (uint32_t)(0 - (1 - ((uint32_t)(tmp2 | (0 - tmp2)) >> 31))));
}
l ^= g << i;
h ^= g >> (8 - i);
}
mask = (-((b >> 7) & 1));
l ^= ((a << 7) & mask);
h ^= ((a >> 1) & mask);
c[0] = (uint8_t)l;
c[1] = (uint8_t)h;
}
/**
* Multiplies two elements of GF(2^GF_M).
* @returns the product a*b
* @param[in] a Element of GF(2^GF_M)
* @param[in] b Element of GF(2^GF_M)
*/
uint16_t PQCLEAN_HQC192_CLEAN_gf_mul(uint16_t a, uint16_t b) {
uint8_t c[2] = {0};
gf_carryless_mul(c, (uint8_t) a, (uint8_t) b);
uint16_t tmp = c[0] ^ (c[1] << 8);
return gf_reduce(tmp, 2 * (PARAM_M - 1));
}
/**
* @brief Squares an element of GF(2^PARAM_M).
* @returns a^2
* @param[in] a Element of GF(2^PARAM_M)
*/
uint16_t PQCLEAN_HQC192_CLEAN_gf_square(uint16_t a) {
uint32_t b = a;
uint32_t s = b & 1;
for (size_t i = 1; i < PARAM_M; ++i) {
b <<= 1;
s ^= b & (1 << 2 * i);
}
return gf_reduce(s, 2 * (PARAM_M - 1));
}
/**
* @brief Computes the inverse of an element of GF(2^PARAM_M),
* using the addition chain 1 2 3 4 7 11 15 30 60 120 127 254
* @returns the inverse of a if a != 0 or 0 if a = 0
* @param[in] a Element of GF(2^PARAM_M)
*/
uint16_t PQCLEAN_HQC192_CLEAN_gf_inverse(uint16_t a) {
uint16_t inv = a;
uint16_t tmp1, tmp2;
inv = PQCLEAN_HQC192_CLEAN_gf_square(a); /* a^2 */
tmp1 = PQCLEAN_HQC192_CLEAN_gf_mul(inv, a); /* a^3 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^4 */
tmp2 = PQCLEAN_HQC192_CLEAN_gf_mul(inv, tmp1); /* a^7 */
tmp1 = PQCLEAN_HQC192_CLEAN_gf_mul(inv, tmp2); /* a^11 */
inv = PQCLEAN_HQC192_CLEAN_gf_mul(tmp1, inv); /* a^15 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^30 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^60 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^120 */
inv = PQCLEAN_HQC192_CLEAN_gf_mul(inv, tmp2); /* a^127 */
inv = PQCLEAN_HQC192_CLEAN_gf_square(inv); /* a^254 */
return inv;
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef GF_H
#define GF_H
/**
* @file gf.h
* @brief Header file of gf.c
*/
#include <stdint.h>
/**
* Powers of the root alpha of 1 + x^2 + x^3 + x^4 + x^8.
* The last two elements are needed by the PQCLEAN_HQC192_CLEAN_gf_mul function
* (for example if both elements to multiply are zero).
*/
static const uint16_t gf_exp [258] = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1, 2, 4 };
/**
* Logarithm of elements of GF(2^8) to the base alpha (root of 1 + x^2 + x^3 + x^4 + x^8).
* The logarithm of 0 is set to 0 by convention.
*/
static const uint16_t gf_log [256] = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 };
uint16_t PQCLEAN_HQC192_CLEAN_gf_mul(uint16_t a, uint16_t b);
uint16_t PQCLEAN_HQC192_CLEAN_gf_square(uint16_t a);
uint16_t PQCLEAN_HQC192_CLEAN_gf_inverse(uint16_t a);
#endif
+198
View File
@@ -0,0 +1,198 @@
#include "gf2x.h"
#include "parameters.h"
#include <stddef.h>
#include <stdint.h>
/**
* @file gf2x.c
* @brief Implementation of multiplication of two polynomials
*/
/**
* @brief Caryless multiplication of two words of 64 bits
*
* Implemntation of the algorithm mul1 in https://hal.inria.fr/inria-00188261v4/document.
* With w = 64 and s = 4
*
* @param[out] c The result c = a * b
* @param[in] a The first value a
* @param[in] b The second value b
*/
static void base_mul(uint64_t *c, uint64_t a, uint64_t b) {
uint64_t h = 0;
uint64_t l = 0;
uint64_t g;
uint64_t u[16] = {0};
uint64_t mask_tab[4] = {0};
uint64_t tmp1, tmp2;
// Step 1
u[0] = 0;
u[1] = b & (((uint64_t)1 << (64 - 4)) - 1);
u[2] = u[1] << 1;
u[3] = u[2] ^ u[1];
u[4] = u[2] << 1;
u[5] = u[4] ^ u[1];
u[6] = u[3] << 1;
u[7] = u[6] ^ u[1];
u[8] = u[4] << 1;
u[9] = u[8] ^ u[1];
u[10] = u[5] << 1;
u[11] = u[10] ^ u[1];
u[12] = u[6] << 1;
u[13] = u[12] ^ u[1];
u[14] = u[7] << 1;
u[15] = u[14] ^ u[1];
g = 0;
tmp1 = a & 0x0f;
for (size_t i = 0; i < 16; ++i) {
tmp2 = tmp1 - i;
g ^= (u[i] & (uint64_t)(0 - (1 - ((uint64_t)(tmp2 | (0 - tmp2)) >> 63))));
}
l = g;
h = 0;
// Step 2
for (size_t i = 4; i < 64; i += 4) {
g = 0;
tmp1 = (a >> i) & 0x0f;
for (size_t j = 0; j < 16; ++j) {
tmp2 = tmp1 - j;
g ^= (u[j] & (uint64_t)(0 - (1 - ((uint64_t)(tmp2 | (0 - tmp2)) >> 63))));
}
l ^= g << i;
h ^= g >> (64 - i);
}
// Step 3
mask_tab [0] = 0 - ((b >> 60) & 1);
mask_tab [1] = 0 - ((b >> 61) & 1);
mask_tab [2] = 0 - ((b >> 62) & 1);
mask_tab [3] = 0 - ((b >> 63) & 1);
l ^= ((a << 60) & mask_tab[0]);
h ^= ((a >> 4) & mask_tab[0]);
l ^= ((a << 61) & mask_tab[1]);
h ^= ((a >> 3) & mask_tab[1]);
l ^= ((a << 62) & mask_tab[2]);
h ^= ((a >> 2) & mask_tab[2]);
l ^= ((a << 63) & mask_tab[3]);
h ^= ((a >> 1) & mask_tab[3]);
c[0] = l;
c[1] = h;
}
static void karatsuba_add1(uint64_t *alh, uint64_t *blh, const uint64_t *a, const uint64_t *b, size_t size_l, size_t size_h) {
for (size_t i = 0; i < size_h; ++i) {
alh[i] = a[i] ^ a[i + size_l];
blh[i] = b[i] ^ b[i + size_l];
}
if (size_h < size_l) {
alh[size_h] = a[size_h];
blh[size_h] = b[size_h];
}
}
static void karatsuba_add2(uint64_t *o, uint64_t *tmp1, const uint64_t *tmp2, size_t size_l, size_t size_h) {
for (size_t i = 0; i < (2 * size_l); ++i) {
tmp1[i] = tmp1[i] ^ o[i];
}
for (size_t i = 0; i < ( 2 * size_h); ++i) {
tmp1[i] = tmp1[i] ^ tmp2[i];
}
for (size_t i = 0; i < (2 * size_l); ++i) {
o[i + size_l] = o[i + size_l] ^ tmp1[i];
}
}
/**
* Karatsuba multiplication of a and b, Implementation inspired from the NTL library.
*
* @param[out] o Polynomial
* @param[in] a Polynomial
* @param[in] b Polynomial
* @param[in] size Length of polynomial
* @param[in] stack Length of polynomial
*/
static void karatsuba(uint64_t *o, const uint64_t *a, const uint64_t *b, size_t size, uint64_t *stack) {
size_t size_l, size_h;
const uint64_t *ah, *bh;
if (size == 1) {
base_mul(o, a[0], b[0]);
return;
}
size_h = size / 2;
size_l = (size + 1) / 2;
uint64_t *alh = stack;
uint64_t *blh = alh + size_l;
uint64_t *tmp1 = blh + size_l;
uint64_t *tmp2 = o + 2 * size_l;
stack += 4 * size_l;
ah = a + size_l;
bh = b + size_l;
karatsuba(o, a, b, size_l, stack);
karatsuba(tmp2, ah, bh, size_h, stack);
karatsuba_add1(alh, blh, a, b, size_l, size_h);
karatsuba(tmp1, alh, blh, size_l, stack);
karatsuba_add2(o, tmp1, tmp2, size_l, size_h);
}
/**
* @brief Compute o(x) = a(x) mod \f$ X^n - 1\f$
*
* This function computes the modular reduction of the polynomial a(x)
*
* @param[in] a Pointer to the polynomial a(x)
* @param[out] o Pointer to the result
*/
static void reduce(uint64_t *o, const uint64_t *a) {
uint64_t r;
uint64_t carry;
for (size_t i = 0; i < VEC_N_SIZE_64; ++i) {
r = a[i + VEC_N_SIZE_64 - 1] >> (PARAM_N & 0x3F);
carry = a[i + VEC_N_SIZE_64] << (64 - (PARAM_N & 0x3F));
o[i] = a[i] ^ r ^ carry;
}
o[VEC_N_SIZE_64 - 1] &= RED_MASK;
}
/**
* @brief Multiply two polynomials modulo \f$ X^n - 1\f$.
*
* This functions multiplies polynomials <b>v1</b> and <b>v2</b>.
* The multiplication is done modulo \f$ X^n - 1\f$.
*
* @param[out] o Product of <b>v1</b> and <b>v2</b>
* @param[in] v1 Pointer to the first polynomial
* @param[in] v2 Pointer to the second polynomial
*/
void PQCLEAN_HQC192_CLEAN_vect_mul(uint64_t *o, const uint64_t *v1, const uint64_t *v2) {
uint64_t stack[VEC_N_SIZE_64 << 3] = {0};
uint64_t o_karat[VEC_N_SIZE_64 << 1] = {0};
karatsuba(o_karat, v1, v2, VEC_N_SIZE_64, stack);
reduce(o, o_karat);
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef GF2X_H
#define GF2X_H
/**
* @file gf2x.h
* @brief Header file for gf2x.c
*/
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_vect_mul(uint64_t *o, const uint64_t *v1, const uint64_t *v2);
#endif
+146
View File
@@ -0,0 +1,146 @@
#include "code.h"
#include "gf2x.h"
#include "hqc.h"
#include "parameters.h"
#include "parsing.h"
#include "randombytes.h"
#include "shake_prng.h"
#include "vector.h"
#include <stdint.h>
/**
* @file hqc.c
* @brief Implementation of hqc.h
*/
/**
* @brief Keygen of the HQC_PKE IND_CPA scheme
*
* The public key is composed of the syndrome <b>s</b> as well as the <b>seed</b> used to generate the vector <b>h</b>.
*
* The secret key is composed of the <b>seed</b> used to generate vectors <b>x</b> and <b>y</b>.
* As a technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] pk String containing the public key
* @param[out] sk String containing the secret key
*/
void PQCLEAN_HQC192_CLEAN_hqc_pke_keygen(uint8_t *pk, uint8_t *sk) {
seedexpander_state sk_seedexpander;
seedexpander_state pk_seedexpander;
uint8_t sk_seed[SEED_BYTES] = {0};
uint8_t sigma[VEC_K_SIZE_BYTES] = {0};
uint8_t pk_seed[SEED_BYTES] = {0};
uint64_t x[VEC_N_SIZE_64] = {0};
uint64_t y[VEC_N_SIZE_64] = {0};
uint64_t h[VEC_N_SIZE_64] = {0};
uint64_t s[VEC_N_SIZE_64] = {0};
// Create seed_expanders for public key and secret key
randombytes(sk_seed, SEED_BYTES);
randombytes(sigma, VEC_K_SIZE_BYTES);
PQCLEAN_HQC192_CLEAN_seedexpander_init(&sk_seedexpander, sk_seed, SEED_BYTES);
randombytes(pk_seed, SEED_BYTES);
PQCLEAN_HQC192_CLEAN_seedexpander_init(&pk_seedexpander, pk_seed, SEED_BYTES);
// Compute secret key
PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, x, PARAM_OMEGA);
PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, y, PARAM_OMEGA);
// Compute public key
PQCLEAN_HQC192_CLEAN_vect_set_random(&pk_seedexpander, h);
PQCLEAN_HQC192_CLEAN_vect_mul(s, y, h);
PQCLEAN_HQC192_CLEAN_vect_add(s, x, s, VEC_N_SIZE_64);
// Parse keys to string
PQCLEAN_HQC192_CLEAN_hqc_public_key_to_string(pk, pk_seed, s);
PQCLEAN_HQC192_CLEAN_hqc_secret_key_to_string(sk, sk_seed, sigma, pk);
PQCLEAN_HQC192_CLEAN_seedexpander_release(&pk_seedexpander);
PQCLEAN_HQC192_CLEAN_seedexpander_release(&sk_seedexpander);
}
/**
* @brief Encryption of the HQC_PKE IND_CPA scheme
*
* The cihertext is composed of vectors <b>u</b> and <b>v</b>.
*
* @param[out] u Vector u (first part of the ciphertext)
* @param[out] v Vector v (second part of the ciphertext)
* @param[in] m Vector representing the message to encrypt
* @param[in] theta Seed used to derive randomness required for encryption
* @param[in] pk String containing the public key
*/
void PQCLEAN_HQC192_CLEAN_hqc_pke_encrypt(uint64_t *u, uint64_t *v, uint8_t *m, uint8_t *theta, const uint8_t *pk) {
seedexpander_state vec_seedexpander;
uint64_t h[VEC_N_SIZE_64] = {0};
uint64_t s[VEC_N_SIZE_64] = {0};
uint64_t r1[VEC_N_SIZE_64] = {0};
uint64_t r2[VEC_N_SIZE_64] = {0};
uint64_t e[VEC_N_SIZE_64] = {0};
uint64_t tmp1[VEC_N_SIZE_64] = {0};
uint64_t tmp2[VEC_N_SIZE_64] = {0};
// Create seed_expander from theta
PQCLEAN_HQC192_CLEAN_seedexpander_init(&vec_seedexpander, theta, SEED_BYTES);
// Retrieve h and s from public key
PQCLEAN_HQC192_CLEAN_hqc_public_key_from_string(h, s, pk);
// Generate r1, r2 and e
PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(&vec_seedexpander, r1, PARAM_OMEGA_R);
PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(&vec_seedexpander, r2, PARAM_OMEGA_R);
PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(&vec_seedexpander, e, PARAM_OMEGA_E);
// Compute u = r1 + r2.h
PQCLEAN_HQC192_CLEAN_vect_mul(u, r2, h);
PQCLEAN_HQC192_CLEAN_vect_add(u, r1, u, VEC_N_SIZE_64);
// Compute v = m.G by encoding the message
PQCLEAN_HQC192_CLEAN_code_encode(v, m);
PQCLEAN_HQC192_CLEAN_vect_resize(tmp1, PARAM_N, v, PARAM_N1N2);
// Compute v = m.G + s.r2 + e
PQCLEAN_HQC192_CLEAN_vect_mul(tmp2, r2, s);
PQCLEAN_HQC192_CLEAN_vect_add(tmp2, e, tmp2, VEC_N_SIZE_64);
PQCLEAN_HQC192_CLEAN_vect_add(tmp2, tmp1, tmp2, VEC_N_SIZE_64);
PQCLEAN_HQC192_CLEAN_vect_resize(v, PARAM_N1N2, tmp2, PARAM_N);
PQCLEAN_HQC192_CLEAN_seedexpander_release(&vec_seedexpander);
}
/**
* @brief Decryption of the HQC_PKE IND_CPA scheme
*
* @param[out] m Vector representing the decrypted message
* @param[in] u Vector u (first part of the ciphertext)
* @param[in] v Vector v (second part of the ciphertext)
* @param[in] sk String containing the secret key
* @returns 0
*/
uint8_t PQCLEAN_HQC192_CLEAN_hqc_pke_decrypt(uint8_t *m, uint8_t *sigma, const uint64_t *u, const uint64_t *v, const uint8_t *sk) {
uint64_t x[VEC_N_SIZE_64] = {0};
uint64_t y[VEC_N_SIZE_64] = {0};
uint8_t pk[PUBLIC_KEY_BYTES] = {0};
uint64_t tmp1[VEC_N_SIZE_64] = {0};
uint64_t tmp2[VEC_N_SIZE_64] = {0};
// Retrieve x, y, pk from secret key
PQCLEAN_HQC192_CLEAN_hqc_secret_key_from_string(x, y, sigma, pk, sk);
// Compute v - u.y
PQCLEAN_HQC192_CLEAN_vect_resize(tmp1, PARAM_N, v, PARAM_N1N2);
PQCLEAN_HQC192_CLEAN_vect_mul(tmp2, y, u);
PQCLEAN_HQC192_CLEAN_vect_add(tmp2, tmp1, tmp2, VEC_N_SIZE_64);
// Compute m by decoding v - u.y
PQCLEAN_HQC192_CLEAN_code_decode(m, tmp2);
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef HQC_H
#define HQC_H
/**
* @file hqc.h
* @brief Functions of the HQC_PKE IND_CPA scheme
*/
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_hqc_pke_keygen(uint8_t *pk, uint8_t *sk);
void PQCLEAN_HQC192_CLEAN_hqc_pke_encrypt(uint64_t *u, uint64_t *v, uint8_t *m, unsigned char *theta, const unsigned char *pk);
uint8_t PQCLEAN_HQC192_CLEAN_hqc_pke_decrypt(uint8_t *m, uint8_t *sigma, const uint64_t *u, const uint64_t *v, const unsigned char *sk);
#endif
+138
View File
@@ -0,0 +1,138 @@
#include "api.h"
#include "domains.h"
#include "fips202.h"
#include "hqc.h"
#include "parameters.h"
#include "parsing.h"
#include "randombytes.h"
#include "shake_ds.h"
#include "vector.h"
#include <stdint.h>
#include <string.h>
/**
* @file kem.c
* @brief Implementation of api.h
*/
/**
* @brief Keygen of the HQC_KEM IND_CAA2 scheme
*
* The public key is composed of the syndrome <b>s</b> as well as the seed used to generate the vector <b>h</b>.
*
* The secret key is composed of the seed used to generate vectors <b>x</b> and <b>y</b>.
* As a technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] pk String containing the public key
* @param[out] sk String containing the secret key
* @returns 0 if keygen is successful
*/
int PQCLEAN_HQC192_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk) {
PQCLEAN_HQC192_CLEAN_hqc_pke_keygen(pk, sk);
return 0;
}
/**
* @brief Encapsulation of the HQC_KEM IND_CAA2 scheme
*
* @param[out] ct String containing the ciphertext
* @param[out] ss String containing the shared secret
* @param[in] pk String containing the public key
* @returns 0 if encapsulation is successful
*/
int PQCLEAN_HQC192_CLEAN_crypto_kem_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk) {
uint8_t theta[SHAKE256_512_BYTES] = {0};
uint64_t u[VEC_N_SIZE_64] = {0};
uint64_t v[VEC_N1N2_SIZE_64] = {0};
uint8_t mc[VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES] = {0};
uint8_t tmp[VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES] = {0};
uint8_t *m = tmp;
uint8_t *salt = tmp + VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES;
shake256incctx shake256state;
// Computing m
randombytes(m, VEC_K_SIZE_BYTES);
// Computing theta
randombytes(salt, SALT_SIZE_BYTES);
memcpy(tmp + VEC_K_SIZE_BYTES, pk, PUBLIC_KEY_BYTES);
PQCLEAN_HQC192_CLEAN_shake256_512_ds(&shake256state, theta, tmp, VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES, G_FCT_DOMAIN);
// Encrypting m
PQCLEAN_HQC192_CLEAN_hqc_pke_encrypt(u, v, m, theta, pk);
// Computing shared secret
memcpy(mc, m, VEC_K_SIZE_BYTES);
PQCLEAN_HQC192_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES, VEC_N_SIZE_BYTES, u, VEC_N_SIZE_64);
PQCLEAN_HQC192_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES, v, VEC_N1N2_SIZE_64);
PQCLEAN_HQC192_CLEAN_shake256_512_ds(&shake256state, ss, mc, VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, K_FCT_DOMAIN);
// Computing ciphertext
PQCLEAN_HQC192_CLEAN_hqc_ciphertext_to_string(ct, u, v, salt);
return 0;
}
/**
* @brief Decapsulation of the HQC_KEM IND_CAA2 scheme
*
* @param[out] ss String containing the shared secret
* @param[in] ct String containing the cipĥertext
* @param[in] sk String containing the secret key
* @returns 0 if decapsulation is successful, -1 otherwise
*/
int PQCLEAN_HQC192_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk) {
uint8_t result;
uint64_t u[VEC_N_SIZE_64] = {0};
uint64_t v[VEC_N1N2_SIZE_64] = {0};
const uint8_t *pk = sk + SEED_BYTES + VEC_K_SIZE_BYTES;
uint8_t sigma[VEC_K_SIZE_BYTES] = {0};
uint8_t theta[SHAKE256_512_BYTES] = {0};
uint64_t u2[VEC_N_SIZE_64] = {0};
uint64_t v2[VEC_N1N2_SIZE_64] = {0};
uint8_t mc[VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES] = {0};
uint8_t tmp[VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES] = {0};
uint8_t *m = tmp;
uint8_t *salt = tmp + VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES;
shake256incctx shake256state;
// Retrieving u, v and d from ciphertext
PQCLEAN_HQC192_CLEAN_hqc_ciphertext_from_string(u, v, salt, ct);
// Decrypting
result = PQCLEAN_HQC192_CLEAN_hqc_pke_decrypt(m, sigma, u, v, sk);
// Computing theta
memcpy(tmp + VEC_K_SIZE_BYTES, pk, PUBLIC_KEY_BYTES);
PQCLEAN_HQC192_CLEAN_shake256_512_ds(&shake256state, theta, tmp, VEC_K_SIZE_BYTES + PUBLIC_KEY_BYTES + SALT_SIZE_BYTES, G_FCT_DOMAIN);
// Encrypting m'
PQCLEAN_HQC192_CLEAN_hqc_pke_encrypt(u2, v2, m, theta, pk);
// Check if c != c'
result |= PQCLEAN_HQC192_CLEAN_vect_compare((uint8_t *)u, (uint8_t *)u2, VEC_N_SIZE_BYTES);
result |= PQCLEAN_HQC192_CLEAN_vect_compare((uint8_t *)v, (uint8_t *)v2, VEC_N1N2_SIZE_BYTES);
result -= 1;
for (size_t i = 0; i < VEC_K_SIZE_BYTES; ++i) {
mc[i] = (m[i] & result) ^ (sigma[i] & ~result);
}
// Computing shared secret
PQCLEAN_HQC192_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES, VEC_N_SIZE_BYTES, u, VEC_N_SIZE_64);
PQCLEAN_HQC192_CLEAN_store8_arr(mc + VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES, v, VEC_N1N2_SIZE_64);
PQCLEAN_HQC192_CLEAN_shake256_512_ds(&shake256state, ss, mc, VEC_K_SIZE_BYTES + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, K_FCT_DOMAIN);
return (result & 1) - 1;
}
+96
View File
@@ -0,0 +1,96 @@
#ifndef HQC_PARAMETERS_H
#define HQC_PARAMETERS_H
/**
* @file parameters.h
* @brief Parameters of the HQC_KEM IND-CCA2 scheme
*/
#include "api.h"
#define CEIL_DIVIDE(a, b) (((a)+(b)-1)/(b)) /*!< Divide a by b and ceil the result*/
/*
#define PARAM_N Define the parameter n of the scheme
#define PARAM_N1 Define the parameter n1 of the scheme (length of Reed-Solomon code)
#define PARAM_N2 Define the parameter n2 of the scheme (length of Duplicated Reed-Muller code)
#define PARAM_N1N2 Define the length in bits of the Concatenated code
#define PARAM_OMEGA Define the parameter omega of the scheme
#define PARAM_OMEGA_E Define the parameter omega_e of the scheme
#define PARAM_OMEGA_R Define the parameter omega_r of the scheme
#define SECRET_KEY_BYTES Define the size of the secret key in bytes
#define PUBLIC_KEY_BYTES Define the size of the public key in bytes
#define SHARED_SECRET_BYTES Define the size of the shared secret in bytes
#define CIPHERTEXT_BYTES Define the size of the ciphertext in bytes
#define VEC_N_SIZE_BYTES Define the size of the array used to store a PARAM_N sized vector in bytes
#define VEC_K_SIZE_BYTES Define the size of the array used to store a PARAM_K sized vector in bytes
#define VEC_N1_SIZE_BYTES Define the size of the array used to store a PARAM_N1 sized vector in bytes
#define VEC_N1N2_SIZE_BYTES Define the size of the array used to store a PARAM_N1N2 sized vector in bytes
#define VEC_N_SIZE_64 Define the size of the array used to store a PARAM_N sized vector in 64 bits
#define VEC_K_SIZE_64 Define the size of the array used to store a PARAM_K sized vector in 64 bits
#define VEC_N1_SIZE_64 Define the size of the array used to store a PARAM_N1 sized vector in 64 bits
#define VEC_N1N2_SIZE_64 Define the size of the array used to store a PARAM_N1N2 sized vector in 64 bits
#define PARAM_DELTA Define the parameter delta of the scheme (correcting capacity of the Reed-Solomon code)
#define PARAM_M Define a positive integer
#define PARAM_GF_POLY Generator polynomial of galois field GF(2^PARAM_M), represented in hexadecimial form
#define PARAM_GF_POLY_WT Hamming weight of PARAM_GF_POLY
#define PARAM_GF_POLY_M2 Distance between the primitive polynomial first two set bits
#define PARAM_GF_MUL_ORDER Define the size of the multiplicative group of GF(2^PARAM_M), i.e 2^PARAM_M -1
#define PARAM_K Define the size of the information bits of the Reed-Solomon code
#define PARAM_G Define the size of the generator polynomial of Reed-Solomon code
#define PARAM_FFT The additive FFT takes a 2^PARAM_FFT polynomial as input
We use the FFT to compute the roots of sigma, whose degree if PARAM_DELTA=24
The smallest power of 2 greater than 24+1 is 32=2^5
#define RS_POLY_COEFS Coefficients of the generator polynomial of the Reed-Solomon code
#define RED_MASK A mask for the higher bits of a vector
#define SHAKE256_512_BYTES Define the size of SHAKE-256 output in bytes
#define SEED_BYTES Define the size of the seed in bytes
#define SALT_SIZE_BYTES Define the size of a salt in bytes
*/
#define PARAM_N 35851
#define PARAM_N1 56
#define PARAM_N2 640
#define PARAM_N1N2 35840
#define PARAM_OMEGA 100
#define PARAM_OMEGA_E 114
#define PARAM_OMEGA_R 114
#define SECRET_KEY_BYTES PQCLEAN_HQC192_CLEAN_CRYPTO_SECRETKEYBYTES
#define PUBLIC_KEY_BYTES PQCLEAN_HQC192_CLEAN_CRYPTO_PUBLICKEYBYTES
#define SHARED_SECRET_BYTES PQCLEAN_HQC192_CLEAN_CRYPTO_BYTES
#define CIPHERTEXT_BYTES PQCLEAN_HQC192_CLEAN_CRYPTO_CIPHERTEXTBYTES
#define VEC_N_SIZE_BYTES CEIL_DIVIDE(PARAM_N, 8)
#define VEC_K_SIZE_BYTES PARAM_K
#define VEC_N1_SIZE_BYTES PARAM_N1
#define VEC_N1N2_SIZE_BYTES CEIL_DIVIDE(PARAM_N1N2, 8)
#define VEC_N_SIZE_64 CEIL_DIVIDE(PARAM_N, 64)
#define VEC_K_SIZE_64 CEIL_DIVIDE(PARAM_K, 8)
#define VEC_N1_SIZE_64 CEIL_DIVIDE(PARAM_N1, 8)
#define VEC_N1N2_SIZE_64 CEIL_DIVIDE(PARAM_N1N2, 64)
#define PARAM_DELTA 16
#define PARAM_M 8
#define PARAM_GF_POLY 0x11D
#define PARAM_GF_POLY_WT 5
#define PARAM_GF_POLY_M2 4
#define PARAM_GF_MUL_ORDER 255
#define PARAM_K 24
#define PARAM_G 33
#define PARAM_FFT 5
#define RS_POLY_COEFS 45,216,239,24,253,104,27,40,107,50,163,210,227,134,224,158,119,13,158,1,238,164,82,43,15,232,246,142,50,189,29,232,1
#define RED_MASK 0x7ff
#define SHAKE256_512_BYTES 64
#define SEED_BYTES 40
#define SALT_SIZE_BYTES 16
#endif
+173
View File
@@ -0,0 +1,173 @@
#include "parameters.h"
#include "parsing.h"
#include "vector.h"
#include <stdint.h>
#include <string.h>
/**
* @file parsing.c
* @brief Functions to parse secret key, public key and ciphertext of the HQC scheme
*/
static uint64_t load8(const uint8_t *in) {
uint64_t ret = in[7];
for (int8_t i = 6; i >= 0; --i) {
ret <<= 8;
ret |= in[i];
}
return ret;
}
void PQCLEAN_HQC192_CLEAN_load8_arr(uint64_t *out64, size_t outlen, const uint8_t *in8, size_t inlen) {
size_t index_in = 0;
size_t index_out = 0;
// first copy by 8 bytes
if (inlen >= 8 && outlen >= 1) {
while (index_out < outlen && index_in + 8 <= inlen) {
out64[index_out] = load8(in8 + index_in);
index_in += 8;
index_out += 1;
}
}
// we now need to do the last 7 bytes if necessary
if (index_in >= inlen || index_out >= outlen) {
return;
}
out64[index_out] = in8[inlen - 1];
for (int8_t i = (int8_t)(inlen - index_in) - 2; i >= 0; --i) {
out64[index_out] <<= 8;
out64[index_out] |= in8[index_in + i];
}
}
void PQCLEAN_HQC192_CLEAN_store8_arr(uint8_t *out8, size_t outlen, const uint64_t *in64, size_t inlen) {
for (size_t index_out = 0, index_in = 0; index_out < outlen && index_in < inlen;) {
out8[index_out] = (in64[index_in] >> ((index_out % 8) * 8)) & 0xFF;
++index_out;
if (index_out % 8 == 0) {
++index_in;
}
}
}
/**
* @brief Parse a secret key into a string
*
* The secret key is composed of the seed used to generate vectors <b>x</b> and <b>y</b>.
* As technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] sk String containing the secret key
* @param[in] sk_seed Seed used to generate the secret key
* @param[in] sigma String used in HHK transform
* @param[in] pk String containing the public key
*/
void PQCLEAN_HQC192_CLEAN_hqc_secret_key_to_string(uint8_t *sk, const uint8_t *sk_seed, const uint8_t *sigma, const uint8_t *pk) {
memcpy(sk, sk_seed, SEED_BYTES);
memcpy(sk + SEED_BYTES, sigma, VEC_K_SIZE_BYTES);
memcpy(sk + SEED_BYTES + VEC_K_SIZE_BYTES, pk, PUBLIC_KEY_BYTES);
}
/**
* @brief Parse a secret key from a string
*
* The secret key is composed of the seed used to generate vectors <b>x</b> and <b>y</b>.
* As technicality, the public key is appended to the secret key in order to respect NIST API.
*
* @param[out] x uint64_t representation of vector x
* @param[out] y uint64_t representation of vector y
* @param[out] pk String containing the public key
* @param[in] sk String containing the secret key
*/
void PQCLEAN_HQC192_CLEAN_hqc_secret_key_from_string(uint64_t *x, uint64_t *y, uint8_t *sigma, uint8_t *pk, const uint8_t *sk) {
seedexpander_state sk_seedexpander;
memcpy(sigma, sk + SEED_BYTES, VEC_K_SIZE_BYTES);
PQCLEAN_HQC192_CLEAN_seedexpander_init(&sk_seedexpander, sk, SEED_BYTES);
PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, x, PARAM_OMEGA);
PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(&sk_seedexpander, y, PARAM_OMEGA);
memcpy(pk, sk + SEED_BYTES + VEC_K_SIZE_BYTES, PUBLIC_KEY_BYTES);
PQCLEAN_HQC192_CLEAN_seedexpander_release(&sk_seedexpander);
}
/**
* @brief Parse a public key into a string
*
* The public key is composed of the syndrome <b>s</b> as well as the seed used to generate the vector <b>h</b>
*
* @param[out] pk String containing the public key
* @param[in] pk_seed Seed used to generate the public key
* @param[in] s uint64_t representation of vector s
*/
void PQCLEAN_HQC192_CLEAN_hqc_public_key_to_string(uint8_t *pk, const uint8_t *pk_seed, const uint64_t *s) {
memcpy(pk, pk_seed, SEED_BYTES);
PQCLEAN_HQC192_CLEAN_store8_arr(pk + SEED_BYTES, VEC_N_SIZE_BYTES, s, VEC_N_SIZE_64);
}
/**
* @brief Parse a public key from a string
*
* The public key is composed of the syndrome <b>s</b> as well as the seed used to generate the vector <b>h</b>
*
* @param[out] h uint64_t representation of vector h
* @param[out] s uint64_t representation of vector s
* @param[in] pk String containing the public key
*/
void PQCLEAN_HQC192_CLEAN_hqc_public_key_from_string(uint64_t *h, uint64_t *s, const uint8_t *pk) {
seedexpander_state pk_seedexpander;
PQCLEAN_HQC192_CLEAN_seedexpander_init(&pk_seedexpander, pk, SEED_BYTES);
PQCLEAN_HQC192_CLEAN_vect_set_random(&pk_seedexpander, h);
PQCLEAN_HQC192_CLEAN_load8_arr(s, VEC_N_SIZE_64, pk + SEED_BYTES, VEC_N_SIZE_BYTES);
PQCLEAN_HQC192_CLEAN_seedexpander_release(&pk_seedexpander);
}
/**
* @brief Parse a ciphertext into a string
*
* The ciphertext is composed of vectors <b>u</b>, <b>v</b> and salt.
*
* @param[out] ct String containing the ciphertext
* @param[in] u uint64_t representation of vector u
* @param[in] v uint64_t representation of vector v
* @param[in] salt String containing a salt
*/
void PQCLEAN_HQC192_CLEAN_hqc_ciphertext_to_string(uint8_t *ct, const uint64_t *u, const uint64_t *v, const uint8_t *salt) {
PQCLEAN_HQC192_CLEAN_store8_arr(ct, VEC_N_SIZE_BYTES, u, VEC_N_SIZE_64);
PQCLEAN_HQC192_CLEAN_store8_arr(ct + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES, v, VEC_N1N2_SIZE_64);
memcpy(ct + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, salt, SALT_SIZE_BYTES);
}
/**
* @brief Parse a ciphertext from a string
*
* The ciphertext is composed of vectors <b>u</b>, <b>v</b> and salt.
*
* @param[out] u uint64_t representation of vector u
* @param[out] v uint64_t representation of vector v
* @param[out] d String containing the hash d
* @param[in] ct String containing the ciphertext
*/
void PQCLEAN_HQC192_CLEAN_hqc_ciphertext_from_string(uint64_t *u, uint64_t *v, uint8_t *salt, const uint8_t *ct) {
PQCLEAN_HQC192_CLEAN_load8_arr(u, VEC_N_SIZE_64, ct, VEC_N_SIZE_BYTES);
PQCLEAN_HQC192_CLEAN_load8_arr(v, VEC_N1N2_SIZE_64, ct + VEC_N_SIZE_BYTES, VEC_N1N2_SIZE_BYTES);
memcpy(salt, ct + VEC_N_SIZE_BYTES + VEC_N1N2_SIZE_BYTES, SALT_SIZE_BYTES);
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef PARSING_H
#define PARSING_H
/**
* @file parsing.h
* @brief Header file for parsing.c
*/
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_load8_arr(uint64_t *out64, size_t outlen, const uint8_t *in8, size_t inlen);
void PQCLEAN_HQC192_CLEAN_store8_arr(uint8_t *out8, size_t outlen, const uint64_t *in64, size_t inlen);
void PQCLEAN_HQC192_CLEAN_hqc_secret_key_to_string(uint8_t *sk, const uint8_t *sk_seed, const uint8_t *sigma, const uint8_t *pk);
void PQCLEAN_HQC192_CLEAN_hqc_secret_key_from_string(uint64_t *x, uint64_t *y, uint8_t *sigma, uint8_t *pk, const uint8_t *sk);
void PQCLEAN_HQC192_CLEAN_hqc_public_key_to_string(uint8_t *pk, const uint8_t *pk_seed, const uint64_t *s);
void PQCLEAN_HQC192_CLEAN_hqc_public_key_from_string(uint64_t *h, uint64_t *s, const uint8_t *pk);
void PQCLEAN_HQC192_CLEAN_hqc_ciphertext_to_string(uint8_t *ct, const uint64_t *u, const uint64_t *v, const uint8_t *salt);
void PQCLEAN_HQC192_CLEAN_hqc_ciphertext_from_string(uint64_t *u, uint64_t *v, uint8_t *salt, const uint8_t *ct);
#endif
+193
View File
@@ -0,0 +1,193 @@
#include "parameters.h"
#include "reed_muller.h"
#include <stdint.h>
#include <string.h>
/**
* @file reed_muller.c
* @brief Constant time implementation of Reed-Muller code RM(1,7)
*/
// number of repeated code words
#define MULTIPLICITY CEIL_DIVIDE(PARAM_N2, 128)
// copy bit 0 into all bits of a 32 bit value
#define BIT0MASK(x) (uint32_t)(-((x) & 1))
/**
* @brief Encode a single byte into a single codeword using RM(1,7)
*
* Encoding matrix of this code:
* bit pattern (note that bits are numbered big endian)
* 0 aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa
* 1 cccccccc cccccccc cccccccc cccccccc
* 2 f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
* 3 ff00ff00 ff00ff00 ff00ff00 ff00ff00
* 4 ffff0000 ffff0000 ffff0000 ffff0000
* 5 ffffffff 00000000 ffffffff 00000000
* 6 ffffffff ffffffff 00000000 00000000
* 7 ffffffff ffffffff ffffffff ffffffff
*
* @param[out] word An RM(1,7) codeword
* @param[in] message A message
*/
static void encode(uint64_t *cword, uint8_t message) {
uint32_t first_word;
// bit 7 flips all the bits, do that first to save work
first_word = BIT0MASK(message >> 7);
// bits 0, 1, 2, 3, 4 are the same for all four longs
// (Warning: in the bit matrix above, low bits are at the left!)
first_word ^= BIT0MASK(message >> 0) & 0xaaaaaaaa;
first_word ^= BIT0MASK(message >> 1) & 0xcccccccc;
first_word ^= BIT0MASK(message >> 2) & 0xf0f0f0f0;
first_word ^= BIT0MASK(message >> 3) & 0xff00ff00;
first_word ^= BIT0MASK(message >> 4) & 0xffff0000;
// we can store this in the first quarter
cword[0] = first_word;
// bit 5 flips entries 1 and 3; bit 6 flips 2 and 3
first_word ^= BIT0MASK(message >> 5);
cword[0] |= (uint64_t)first_word << 32;
first_word ^= BIT0MASK(message >> 6);
cword[1] = (uint64_t)first_word << 32;
first_word ^= BIT0MASK(message >> 5);
cword[1] |= first_word;
}
/**
* @brief Hadamard transform
*
* Perform hadamard transform of src and store result in dst
* src is overwritten
*
* @param[out] src Structure that contain the expanded codeword
* @param[out] dst Structure that contain the expanded codeword
*/
static void hadamard(uint16_t src[128], uint16_t dst[128]) {
// the passes move data:
// src -> dst -> src -> dst -> src -> dst -> src -> dst
// using p1 and p2 alternately
uint16_t *p1 = src;
uint16_t *p2 = dst;
uint16_t *p3;
for (size_t pass = 0; pass < 7; ++pass) {
for (size_t i = 0; i < 64; ++i) {
p2[i] = p1[2 * i] + p1[2 * i + 1];
p2[i + 64] = p1[2 * i] - p1[2 * i + 1];
}
// swap p1, p2 for next round
p3 = p1;
p1 = p2;
p2 = p3;
}
}
/**
* @brief Add multiple codewords into expanded codeword
*
* Accesses memory in order
* Note: this does not write the codewords as -1 or +1 as the green machine does
* instead, just 0 and 1 is used.
* The resulting hadamard transform has:
* all values are halved
* the first entry is 64 too high
*
* @param[out] dest Structure that contain the expanded codeword
* @param[in] src Structure that contain the codeword
*/
static void expand_and_sum(uint16_t dest[128], const uint64_t src[2 * MULTIPLICITY]) {
// start with the first copy
for (size_t part = 0; part < 2; ++part) {
for (size_t bit = 0; bit < 64; ++bit) {
dest[part * 64 + bit] = ((src[part] >> bit) & 1);
}
}
// sum the rest of the copies
for (size_t copy = 1; copy < MULTIPLICITY; ++copy) {
for (size_t part = 0; part < 2; ++part) {
for (size_t bit = 0; bit < 64; ++bit) {
dest[part * 64 + bit] += (uint16_t) ((src[2 * copy + part] >> bit) & 1);
}
}
}
}
/**
* @brief Finding the location of the highest value
*
* This is the final step of the green machine: find the location of the highest value,
* and add 128 if the peak is positive
* if there are two identical peaks, the peak with smallest value
* in the lowest 7 bits it taken
* @param[in] transform Structure that contain the expanded codeword
*/
static uint8_t find_peaks(const uint16_t transform[128]) {
uint16_t peak_abs = 0;
uint16_t peak = 0;
uint16_t pos = 0;
uint16_t t, abs, mask;
for (uint16_t i = 0; i < 128; ++i) {
t = transform[i];
abs = t ^ ((uint16_t)(-(t >> 15)) & (t ^ -t)); // t = abs(t)
mask = -(((uint16_t)(peak_abs - abs)) >> 15);
peak ^= mask & (peak ^ t);
pos ^= mask & (pos ^ i);
peak_abs ^= mask & (peak_abs ^ abs);
}
// set bit 7
pos |= 128 & (uint16_t)((peak >> 15) - 1);
return (uint8_t) pos;
}
/**
* @brief Encodes the received word
*
* The message consists of N1 bytes each byte is encoded into PARAM_N2 bits,
* or MULTIPLICITY repeats of 128 bits
*
* @param[out] cdw Array of size VEC_N1N2_SIZE_64 receiving the encoded message
* @param[in] msg Array of size VEC_N1_SIZE_64 storing the message
*/
void PQCLEAN_HQC192_CLEAN_reed_muller_encode(uint64_t *cdw, const uint8_t *msg) {
for (size_t i = 0; i < VEC_N1_SIZE_BYTES; ++i) {
// encode first word
encode(&cdw[2 * i * MULTIPLICITY], msg[i]);
// copy to other identical codewords
for (size_t copy = 1; copy < MULTIPLICITY; ++copy) {
memcpy(&cdw[2 * i * MULTIPLICITY + 2 * copy], &cdw[2 * i * MULTIPLICITY], 16);
}
}
}
/**
* @brief Decodes the received word
*
* Decoding uses fast hadamard transform, for a more complete picture on Reed-Muller decoding, see MacWilliams, Florence Jessie, and Neil James Alexander Sloane.
* The theory of error-correcting codes codes @cite macwilliams1977theory
*
* @param[out] msg Array of size VEC_N1_SIZE_64 receiving the decoded message
* @param[in] cdw Array of size VEC_N1N2_SIZE_64 storing the received word
*/
void PQCLEAN_HQC192_CLEAN_reed_muller_decode(uint8_t *msg, const uint64_t *cdw) {
uint16_t expanded[128];
uint16_t transform[128];
for (size_t i = 0; i < VEC_N1_SIZE_BYTES; ++i) {
// collect the codewords
expand_and_sum(expanded, &cdw[2 * i * MULTIPLICITY]);
// apply hadamard transform
hadamard(expanded, transform);
// fix the first entry to get the half Hadamard transform
transform[0] -= 64 * MULTIPLICITY;
// finish the decoding
msg[i] = find_peaks(transform);
}
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef REED_MULLER_H
#define REED_MULLER_H
/**
* @file reed_muller.h
* @brief Header file of reed_muller.c
*/
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_reed_muller_encode(uint64_t *cdw, const uint8_t *msg);
void PQCLEAN_HQC192_CLEAN_reed_muller_decode(uint8_t *msg, const uint64_t *cdw);
#endif
+335
View File
@@ -0,0 +1,335 @@
#include "fft.h"
#include "gf.h"
#include "parameters.h"
#include "reed_solomon.h"
#include <stdint.h>
#include <string.h>
/**
* @file reed_solomon.c
* @brief Constant time implementation of Reed-Solomon codes
*/
/**
* @brief Encodes a message message of PARAM_K bits to a Reed-Solomon codeword codeword of PARAM_N1 bytes
*
* Following @cite lin1983error (Chapter 4 - Cyclic Codes),
* We perform a systematic encoding using a linear (PARAM_N1 - PARAM_K)-stage shift register
* with feedback connections based on the generator polynomial PARAM_RS_POLY of the Reed-Solomon code.
*
* @param[out] cdw Array of size VEC_N1_SIZE_64 receiving the encoded message
* @param[in] msg Array of size VEC_K_SIZE_64 storing the message
*/
void PQCLEAN_HQC192_CLEAN_reed_solomon_encode(uint8_t *cdw, const uint8_t *msg) {
uint8_t gate_value = 0;
uint16_t tmp[PARAM_G] = {0};
uint16_t PARAM_RS_POLY [] = {RS_POLY_COEFS};
memset(cdw, 0, PARAM_N1);
for (size_t i = 0; i < PARAM_K; ++i) {
gate_value = msg[PARAM_K - 1 - i] ^ cdw[PARAM_N1 - PARAM_K - 1];
for (size_t j = 0; j < PARAM_G; ++j) {
tmp[j] = PQCLEAN_HQC192_CLEAN_gf_mul(gate_value, PARAM_RS_POLY[j]);
}
for (size_t k = PARAM_N1 - PARAM_K - 1; k; --k) {
cdw[k] = (uint8_t)(cdw[k - 1] ^ tmp[k]);
}
cdw[0] = (uint8_t)tmp[0];
}
memcpy(cdw + PARAM_N1 - PARAM_K, msg, PARAM_K);
}
/**
* @brief Computes 2 * PARAM_DELTA syndromes
*
* @param[out] syndromes Array of size 2 * PARAM_DELTA receiving the computed syndromes
* @param[in] cdw Array of size PARAM_N1 storing the received vector
*/
static void compute_syndromes(uint16_t *syndromes, uint8_t *cdw) {
for (size_t i = 0; i < 2 * PARAM_DELTA; ++i) {
for (size_t j = 1; j < PARAM_N1; ++j) {
syndromes[i] ^= PQCLEAN_HQC192_CLEAN_gf_mul(cdw[j], alpha_ij_pow[i][j - 1]);
}
syndromes[i] ^= cdw[0];
}
}
/**
* @brief Computes the error locator polynomial (ELP) sigma
*
* This is a constant time implementation of Berlekamp's simplified algorithm (see @cite lin1983error (Chapter 6 - BCH Codes). <br>
* We use the letter p for rho which is initialized at -1. <br>
* The array X_sigma_p represents the polynomial X^(mu-rho)*sigma_p(X). <br>
* Instead of maintaining a list of sigmas, we update in place both sigma and X_sigma_p. <br>
* sigma_copy serves as a temporary save of sigma in case X_sigma_p needs to be updated. <br>
* We can properly correct only if the degree of sigma does not exceed PARAM_DELTA.
* This means only the first PARAM_DELTA + 1 coefficients of sigma are of value
* and we only need to save its first PARAM_DELTA - 1 coefficients.
*
* @returns the degree of the ELP sigma
* @param[out] sigma Array of size (at least) PARAM_DELTA receiving the ELP
* @param[in] syndromes Array of size (at least) 2*PARAM_DELTA storing the syndromes
*/
static uint16_t compute_elp(uint16_t *sigma, const uint16_t *syndromes) {
uint16_t deg_sigma = 0;
uint16_t deg_sigma_p = 0;
uint16_t deg_sigma_copy = 0;
uint16_t sigma_copy[PARAM_DELTA + 1] = {0};
uint16_t X_sigma_p[PARAM_DELTA + 1] = {0, 1};
uint16_t pp = (uint16_t) -1; // 2*rho
uint16_t d_p = 1;
uint16_t d = syndromes[0];
uint16_t mask1, mask2, mask12;
uint16_t deg_X, deg_X_sigma_p;
uint16_t dd;
uint16_t mu;
uint16_t i;
sigma[0] = 1;
for (mu = 0; (mu < (2 * PARAM_DELTA)); ++mu) {
// Save sigma in case we need it to update X_sigma_p
memcpy(sigma_copy, sigma, 2 * (PARAM_DELTA));
deg_sigma_copy = deg_sigma;
dd = PQCLEAN_HQC192_CLEAN_gf_mul(d, PQCLEAN_HQC192_CLEAN_gf_inverse(d_p));
for (i = 1; (i <= mu + 1) && (i <= PARAM_DELTA); ++i) {
sigma[i] ^= PQCLEAN_HQC192_CLEAN_gf_mul(dd, X_sigma_p[i]);
}
deg_X = mu - pp;
deg_X_sigma_p = deg_X + deg_sigma_p;
// mask1 = 0xffff if(d != 0) and 0 otherwise
mask1 = -((uint16_t) - d >> 15);
// mask2 = 0xffff if(deg_X_sigma_p > deg_sigma) and 0 otherwise
mask2 = -((uint16_t) (deg_sigma - deg_X_sigma_p) >> 15);
// mask12 = 0xffff if the deg_sigma increased and 0 otherwise
mask12 = mask1 & mask2;
deg_sigma ^= mask12 & (deg_X_sigma_p ^ deg_sigma);
if (mu == (2 * PARAM_DELTA - 1)) {
break;
}
pp ^= mask12 & (mu ^ pp);
d_p ^= mask12 & (d ^ d_p);
for (i = PARAM_DELTA; i; --i) {
X_sigma_p[i] = (mask12 & sigma_copy[i - 1]) ^ (~mask12 & X_sigma_p[i - 1]);
}
deg_sigma_p ^= mask12 & (deg_sigma_copy ^ deg_sigma_p);
d = syndromes[mu + 1];
for (i = 1; (i <= mu + 1) && (i <= PARAM_DELTA); ++i) {
d ^= PQCLEAN_HQC192_CLEAN_gf_mul(sigma[i], syndromes[mu + 1 - i]);
}
}
return deg_sigma;
}
/**
* @brief Computes the error polynomial error from the error locator polynomial sigma
*
* See function PQCLEAN_HQC192_CLEAN_fft for more details.
*
* @param[out] error Array of 2^PARAM_M elements receiving the error polynomial
* @param[out] error_compact Array of PARAM_DELTA + PARAM_N1 elements receiving a compact representation of the vector error
* @param[in] sigma Array of 2^PARAM_FFT elements storing the error locator polynomial
*/
static void compute_roots(uint8_t *error, uint16_t *sigma) {
uint16_t w[1 << PARAM_M] = {0};
PQCLEAN_HQC192_CLEAN_fft(w, sigma, PARAM_DELTA + 1);
PQCLEAN_HQC192_CLEAN_fft_retrieve_error_poly(error, w);
}
/**
* @brief Computes the polynomial z(x)
*
* See @cite lin1983error (Chapter 6 - BCH Codes) for more details.
*
* @param[out] z Array of PARAM_DELTA + 1 elements receiving the polynomial z(x)
* @param[in] sigma Array of 2^PARAM_FFT elements storing the error locator polynomial
* @param[in] degree Integer that is the degree of polynomial sigma
* @param[in] syndromes Array of 2 * PARAM_DELTA storing the syndromes
*/
static void compute_z_poly(uint16_t *z, const uint16_t *sigma, uint16_t degree, const uint16_t *syndromes) {
size_t i, j;
uint16_t mask;
z[0] = 1;
for (i = 1; i < PARAM_DELTA + 1; ++i) {
mask = -((uint16_t) (i - degree - 1) >> 15);
z[i] = mask & sigma[i];
}
z[1] ^= syndromes[0];
for (i = 2; i <= PARAM_DELTA; ++i) {
mask = -((uint16_t) (i - degree - 1) >> 15);
z[i] ^= mask & syndromes[i - 1];
for (j = 1; j < i; ++j) {
z[i] ^= mask & PQCLEAN_HQC192_CLEAN_gf_mul(sigma[j], syndromes[i - j - 1]);
}
}
}
/**
* @brief Computes the error values
*
* See @cite lin1983error (Chapter 6 - BCH Codes) for more details.
*
* @param[out] error_values Array of PARAM_DELTA elements receiving the error values
* @param[in] z Array of PARAM_DELTA + 1 elements storing the polynomial z(x)
* @param[in] z_degree Integer that is the degree of polynomial z(x)
* @param[in] error_compact Array of PARAM_DELTA + PARAM_N1 storing compact representation of the error
*/
static void compute_error_values(uint16_t *error_values, const uint16_t *z, const uint8_t *error) {
uint16_t beta_j[PARAM_DELTA] = {0};
uint16_t e_j[PARAM_DELTA] = {0};
uint16_t delta_counter;
uint16_t delta_real_value;
uint16_t found;
uint16_t mask1;
uint16_t mask2;
uint16_t tmp1;
uint16_t tmp2;
uint16_t inverse;
uint16_t inverse_power_j;
// Compute the beta_{j_i} page 31 of the documentation
delta_counter = 0;
for (size_t i = 0; i < PARAM_N1; i++) {
found = 0;
mask1 = (uint16_t) (-((int32_t)error[i]) >> 31); // error[i] != 0
for (size_t j = 0; j < PARAM_DELTA; j++) {
mask2 = ~((uint16_t) (-((int32_t) j ^ delta_counter) >> 31)); // j == delta_counter
beta_j[j] += mask1 & mask2 & gf_exp[i];
found += mask1 & mask2 & 1;
}
delta_counter += found;
}
delta_real_value = delta_counter;
// Compute the e_{j_i} page 31 of the documentation
for (size_t i = 0; i < PARAM_DELTA; ++i) {
tmp1 = 1;
tmp2 = 1;
inverse = PQCLEAN_HQC192_CLEAN_gf_inverse(beta_j[i]);
inverse_power_j = 1;
for (size_t j = 1; j <= PARAM_DELTA; ++j) {
inverse_power_j = PQCLEAN_HQC192_CLEAN_gf_mul(inverse_power_j, inverse);
tmp1 ^= PQCLEAN_HQC192_CLEAN_gf_mul(inverse_power_j, z[j]);
}
for (size_t k = 1; k < PARAM_DELTA; ++k) {
tmp2 = PQCLEAN_HQC192_CLEAN_gf_mul(tmp2, (1 ^ PQCLEAN_HQC192_CLEAN_gf_mul(inverse, beta_j[(i + k) % PARAM_DELTA])));
}
mask1 = (uint16_t) (((int16_t) i - delta_real_value) >> 15); // i < delta_real_value
e_j[i] = mask1 & PQCLEAN_HQC192_CLEAN_gf_mul(tmp1, PQCLEAN_HQC192_CLEAN_gf_inverse(tmp2));
}
// Place the delta e_{j_i} values at the right coordinates of the output vector
delta_counter = 0;
for (size_t i = 0; i < PARAM_N1; ++i) {
found = 0;
mask1 = (uint16_t) (-((int32_t)error[i]) >> 31); // error[i] != 0
for (size_t j = 0; j < PARAM_DELTA; j++) {
mask2 = ~((uint16_t) (-((int32_t) j ^ delta_counter) >> 31)); // j == delta_counter
error_values[i] += mask1 & mask2 & e_j[j];
found += mask1 & mask2 & 1;
}
delta_counter += found;
}
}
/**
* @brief Correct the errors
*
* @param[out] cdw Array of PARAM_N1 elements receiving the corrected vector
* @param[in] error Array of the error vector
* @param[in] error_values Array of PARAM_DELTA elements storing the error values
*/
static void correct_errors(uint8_t *cdw, const uint16_t *error_values) {
for (size_t i = 0; i < PARAM_N1; ++i) {
cdw[i] ^= error_values[i];
}
}
/**
* @brief Decodes the received word
*
* This function relies on six steps:
* <ol>
* <li> The first step, is the computation of the 2*PARAM_DELTA syndromes.
* <li> The second step is the computation of the error-locator polynomial sigma.
* <li> The third step, done by additive FFT, is finding the error-locator numbers by calculating the roots of the polynomial sigma and takings their inverses.
* <li> The fourth step, is the polynomial z(x).
* <li> The fifth step, is the computation of the error values.
* <li> The sixth step is the correction of the errors in the received polynomial.
* </ol>
* For a more complete picture on Reed-Solomon decoding, see Shu. Lin and Daniel J. Costello in Error Control Coding: Fundamentals and Applications @cite lin1983error
*
* @param[out] msg Array of size VEC_K_SIZE_64 receiving the decoded message
* @param[in] cdw Array of size VEC_N1_SIZE_64 storing the received word
*/
void PQCLEAN_HQC192_CLEAN_reed_solomon_decode(uint8_t *msg, uint8_t *cdw) {
uint16_t syndromes[2 * PARAM_DELTA] = {0};
uint16_t sigma[1 << PARAM_FFT] = {0};
uint8_t error[1 << PARAM_M] = {0};
uint16_t z[PARAM_N1] = {0};
uint16_t error_values[PARAM_N1] = {0};
uint16_t deg;
// Calculate the 2*PARAM_DELTA syndromes
compute_syndromes(syndromes, cdw);
// Compute the error locator polynomial sigma
// Sigma's degree is at most PARAM_DELTA but the FFT requires the extra room
deg = compute_elp(sigma, syndromes);
// Compute the error polynomial error
compute_roots(error, sigma);
// Compute the polynomial z(x)
compute_z_poly(z, sigma, deg, syndromes);
// Compute the error values
compute_error_values(error_values, z, error);
// Correct the errors
correct_errors(cdw, error_values);
// Retrieve the message from the decoded codeword
memcpy(msg, cdw + (PARAM_G - 1), PARAM_K);
}
File diff suppressed because one or more lines are too long
+40
View File
@@ -0,0 +1,40 @@
#include "shake_ds.h"
/**
* @file shake_ds.c
* @brief Implementation SHAKE-256 with incremental API and domain separation
*/
/**
* @brief SHAKE-256 with incremental API and domain separation
*
* Derived from function SHAKE_256 in fips202.c
*
* @param[out] state Internal state of SHAKE
* @param[in] output Pointer to output
* @param[in] input Pointer to input
* @param[in] inlen length of input in bytes
* @param[in] domain byte for domain separation
*/
void PQCLEAN_HQC192_CLEAN_shake256_512_ds(shake256incctx *state, uint8_t *output, const uint8_t *input, size_t inlen, uint8_t domain) {
/* Init state */
shake256_inc_init(state);
/* Absorb input */
shake256_inc_absorb(state, input, inlen);
/* Absorb domain separation byte */
shake256_inc_absorb(state, &domain, 1);
/* Finalize */
shake256_inc_finalize(state);
/* Squeeze output */
shake256_inc_squeeze(output, 512 / 8, state);
/* Release ctx */
shake256_inc_ctx_release(state);
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef SHAKE_DS_H
#define SHAKE_DS_H
/**
* @file shake_ds.h
* @brief Header file of shake_ds.c
*/
#include "fips202.h"
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_shake256_512_ds(shake256incctx *state, uint8_t *output, const uint8_t *input, size_t inlen, uint8_t domain);
#endif
+60
View File
@@ -0,0 +1,60 @@
#include "domains.h"
#include "fips202.h"
#include "shake_prng.h"
/**
* @file shake_prng.c
* @brief Implementation of SHAKE-256 based seed expander
*/
/**
* @brief Initialise a SHAKE-256 based seed expander
*
* Derived from function SHAKE_256 in fips202.c
*
* @param[out] state Keccak internal state and a counter
* @param[in] seed A seed
* @param[in] seedlen The seed bytes length
*/
void PQCLEAN_HQC192_CLEAN_seedexpander_init(seedexpander_state *state, const uint8_t *seed, size_t seedlen) {
uint8_t domain = SEEDEXPANDER_DOMAIN;
shake256_inc_init(state);
shake256_inc_absorb(state, seed, seedlen);
shake256_inc_absorb(state, &domain, 1);
shake256_inc_finalize(state);
}
/**
* @brief A SHAKE-256 based seed expander
*
* Derived from function SHAKE_256 in fips202.c
* Squeezes Keccak state by 64-bit blocks (hardware version compatibility)
*
* @param[out] state Internal state of SHAKE
* @param[out] output The XOF data
* @param[in] outlen Number of bytes to return
*/
void PQCLEAN_HQC192_CLEAN_seedexpander(seedexpander_state *state, uint8_t *output, size_t outlen) {
const size_t bsize = sizeof(uint64_t);
const size_t remainder = outlen % bsize;
uint8_t tmp[sizeof(uint64_t)];
shake256_inc_squeeze(output, outlen - remainder, state);
if (remainder != 0) {
shake256_inc_squeeze(tmp, bsize, state);
output += outlen - remainder;
for (size_t i = 0; i < remainder; ++i) {
output[i] = tmp[i];
}
}
}
/**
* @brief Release the seed expander context
* @param[in] state Internal state of the seed expander
*/
void PQCLEAN_HQC192_CLEAN_seedexpander_release(seedexpander_state *state) {
shake256_inc_ctx_release(state);
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef SHAKE_PRNG_H
#define SHAKE_PRNG_H
/**
* @file shake_prng.h
* @brief Header file of shake_prng.c
*/
#include "fips202.h"
#include <stddef.h>
#include <stdint.h>
typedef shake256incctx seedexpander_state;
void PQCLEAN_HQC192_CLEAN_seedexpander_init(seedexpander_state *state, const uint8_t *seed, size_t seedlen);
void PQCLEAN_HQC192_CLEAN_seedexpander(seedexpander_state *state, uint8_t *output, size_t outlen);
void PQCLEAN_HQC192_CLEAN_seedexpander_release(seedexpander_state *state);
#endif
+197
View File
@@ -0,0 +1,197 @@
#include "parameters.h"
#include "parsing.h"
#include "randombytes.h"
#include "vector.h"
#include <stdint.h>
#include <string.h>
/**
* @file vector.c
* @brief Implementation of vectors sampling and some utilities for the HQC scheme
*/
static uint32_t m_val[114] = { 119800, 119803, 119807, 119810, 119813, 119817, 119820, 119823, 119827, 119830, 119833, 119837, 119840, 119843, 119847, 119850, 119853, 119857, 119860, 119864, 119867, 119870, 119874, 119877, 119880, 119884, 119887, 119890, 119894, 119897, 119900, 119904, 119907, 119910, 119914, 119917, 119920, 119924, 119927, 119930, 119934, 119937, 119941, 119944, 119947, 119951, 119954, 119957, 119961, 119964, 119967, 119971, 119974, 119977, 119981, 119984, 119987, 119991, 119994, 119997, 120001, 120004, 120008, 120011, 120014, 120018, 120021, 120024, 120028, 120031, 120034, 120038, 120041, 120044, 120048, 120051, 120054, 120058, 120061, 120065, 120068, 120071, 120075, 120078, 120081, 120085, 120088, 120091, 120095, 120098, 120101, 120105, 120108, 120112, 120115, 120118, 120122, 120125, 120128, 120132, 120135, 120138, 120142, 120145, 120149, 120152, 120155, 120159, 120162, 120165, 120169, 120172, 120175, 120179 };
/**
* @brief Constant-time comparison of two integers v1 and v2
*
* Returns 1 if v1 is equal to v2 and 0 otherwise
* https://gist.github.com/sneves/10845247
*
* @param[in] v1
* @param[in] v2
*/
static inline uint32_t compare_u32(uint32_t v1, uint32_t v2) {
return 1 ^ ((uint32_t)((v1 - v2) | (v2 - v1)) >> 31);
}
static uint64_t single_bit_mask(uint32_t pos) {
uint64_t ret = 0;
uint64_t mask = 1;
uint64_t tmp;
for (size_t i = 0; i < 64; ++i) {
tmp = pos - i;
tmp = 0 - (1 - ((uint64_t)(tmp | (0 - tmp)) >> 63));
ret |= mask & tmp;
mask <<= 1;
}
return ret;
}
static inline uint32_t cond_sub(uint32_t r, uint32_t n) {
uint32_t mask;
r -= n;
mask = 0 - (r >> 31);
return r + (n & mask);
}
static inline uint32_t reduce(uint32_t a, size_t i) {
uint32_t q, n, r;
q = ((uint64_t) a * m_val[i]) >> 32;
n = (uint32_t)(PARAM_N - i);
r = a - q * n;
return cond_sub(r, n);
}
/**
* @brief Generates a vector of a given Hamming weight
*
* Implementation of Algorithm 5 in https://eprint.iacr.org/2021/1631.pdf
*
* @param[in] ctx Pointer to the context of the seed expander
* @param[in] v Pointer to an array
* @param[in] weight Integer that is the Hamming weight
*/
void PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(seedexpander_state *ctx, uint64_t *v, uint16_t weight) {
uint8_t rand_bytes[4 * PARAM_OMEGA_R] = {0}; // to be interpreted as PARAM_OMEGA_R 32-bit unsigned ints
uint32_t support[PARAM_OMEGA_R] = {0};
uint32_t index_tab [PARAM_OMEGA_R] = {0};
uint64_t bit_tab [PARAM_OMEGA_R] = {0};
uint32_t pos, found, mask32, tmp;
uint64_t mask64, val;
PQCLEAN_HQC192_CLEAN_seedexpander(ctx, rand_bytes, 4 * weight);
for (size_t i = 0; i < weight; ++i) {
support[i] = rand_bytes[4 * i];
support[i] |= rand_bytes[4 * i + 1] << 8;
support[i] |= (uint32_t)rand_bytes[4 * i + 2] << 16;
support[i] |= (uint32_t)rand_bytes[4 * i + 3] << 24;
support[i] = (uint32_t)(i + reduce(support[i], i)); // use constant-tme reduction
}
for (size_t i = (weight - 1); i-- > 0;) {
found = 0;
for (size_t j = i + 1; j < weight; ++j) {
found |= compare_u32(support[j], support[i]);
}
mask32 = 0 - found;
support[i] = (mask32 & i) ^ (~mask32 & support[i]);
}
for (size_t i = 0; i < weight; ++i) {
index_tab[i] = support[i] >> 6;
pos = support[i] & 0x3f;
bit_tab[i] = single_bit_mask(pos); // avoid secret shift
}
for (size_t i = 0; i < VEC_N_SIZE_64; ++i) {
val = 0;
for (size_t j = 0; j < weight; ++j) {
tmp = (uint32_t)(i - index_tab[j]);
tmp = 1 ^ ((uint32_t)(tmp | (0 - tmp)) >> 31);
mask64 = 0 - (uint64_t)tmp;
val |= (bit_tab[j] & mask64);
}
v[i] |= val;
}
}
/**
* @brief Generates a random vector of dimension <b>PARAM_N</b>
*
* This function generates a random binary vector of dimension <b>PARAM_N</b>. It generates a random
* array of bytes using the PQCLEAN_HQC192_CLEAN_seedexpander function, and drop the extra bits using a mask.
*
* @param[in] v Pointer to an array
* @param[in] ctx Pointer to the context of the seed expander
*/
void PQCLEAN_HQC192_CLEAN_vect_set_random(seedexpander_state *ctx, uint64_t *v) {
uint8_t rand_bytes[VEC_N_SIZE_BYTES] = {0};
PQCLEAN_HQC192_CLEAN_seedexpander(ctx, rand_bytes, VEC_N_SIZE_BYTES);
PQCLEAN_HQC192_CLEAN_load8_arr(v, VEC_N_SIZE_64, rand_bytes, VEC_N_SIZE_BYTES);
v[VEC_N_SIZE_64 - 1] &= RED_MASK;
}
/**
* @brief Adds two vectors
*
* @param[out] o Pointer to an array that is the result
* @param[in] v1 Pointer to an array that is the first vector
* @param[in] v2 Pointer to an array that is the second vector
* @param[in] size Integer that is the size of the vectors
*/
void PQCLEAN_HQC192_CLEAN_vect_add(uint64_t *o, const uint64_t *v1, const uint64_t *v2, size_t size) {
for (size_t i = 0; i < size; ++i) {
o[i] = v1[i] ^ v2[i];
}
}
/**
* @brief Compares two vectors
*
* @param[in] v1 Pointer to an array that is first vector
* @param[in] v2 Pointer to an array that is second vector
* @param[in] size Integer that is the size of the vectors
* @returns 0 if the vectors are equal and 1 otherwise
*/
uint8_t PQCLEAN_HQC192_CLEAN_vect_compare(const uint8_t *v1, const uint8_t *v2, size_t size) {
uint16_t r = 0x0100;
for (size_t i = 0; i < size; i++) {
r |= v1[i] ^ v2[i];
}
return (r - 1) >> 8;
}
/**
* @brief Resize a vector so that it contains <b>size_o</b> bits
*
* @param[out] o Pointer to the output vector
* @param[in] size_o Integer that is the size of the output vector in bits
* @param[in] v Pointer to the input vector
* @param[in] size_v Integer that is the size of the input vector in bits
*/
void PQCLEAN_HQC192_CLEAN_vect_resize(uint64_t *o, uint32_t size_o, const uint64_t *v, uint32_t size_v) {
uint64_t mask = 0x7FFFFFFFFFFFFFFF;
size_t val = 0;
if (size_o < size_v) {
if (size_o % 64) {
val = 64 - (size_o % 64);
}
memcpy(o, v, VEC_N1N2_SIZE_BYTES);
for (size_t i = 0; i < val; ++i) {
o[VEC_N1N2_SIZE_64 - 1] &= (mask >> i);
}
} else {
memcpy(o, v, 8 * CEIL_DIVIDE(size_v, 64));
}
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef VECTOR_H
#define VECTOR_H
/**
* @file vector.h
* @brief Header file for vector.c
*/
#include "shake_prng.h"
#include <stddef.h>
#include <stdint.h>
void PQCLEAN_HQC192_CLEAN_vect_set_random_fixed_weight(seedexpander_state *ctx, uint64_t *v, uint16_t weight);
void PQCLEAN_HQC192_CLEAN_vect_set_random(seedexpander_state *ctx, uint64_t *v);
void PQCLEAN_HQC192_CLEAN_vect_add(uint64_t *o, const uint64_t *v1, const uint64_t *v2, size_t size);
uint8_t PQCLEAN_HQC192_CLEAN_vect_compare(const uint8_t *v1, const uint8_t *v2, size_t size);
void PQCLEAN_HQC192_CLEAN_vect_resize(uint64_t *o, uint32_t size_o, const uint64_t *v, uint32_t size_v);
#endif

Some files were not shown because too many files have changed in this diff Show More