diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Changelog
+
+## 0.1.0 - 2026-10-15
+
+- Initial release with synchronous Haskell APIs for AIMer, HAETAE, NTRU+, and
+  SMAUG-T.
+- Added scrubbed storage for secret keys and shared secrets.
+- Added tests for all supported parameter sets and 1,600 known-answer vectors.
diff --git a/KpqC.cabal b/KpqC.cabal
new file mode 100644
--- /dev/null
+++ b/KpqC.cabal
@@ -0,0 +1,148 @@
+cabal-version:      3.4
+name:               KpqC
+version:            0.1.0
+synopsis:           Haskell bindings for KpqC post-quantum algorithms
+description:
+  Synchronous Haskell APIs for the AIMer and HAETAE signature schemes and the
+  NTRU+ and SMAUG-T key encapsulation mechanisms (KEMs). The package bundles
+  the native algorithm sources and does not download code during installation.
+homepage:            https://github.com/KpqC/kpqc-haskell
+bug-reports:         https://github.com/KpqC/kpqc-haskell/issues
+license:             MIT
+license-file:        LICENSE
+author:              O. Suolfou
+maintainer:          osuolfou@gmail.com
+category:            Cryptography
+build-type:          Simple
+tested-with:
+  GHC ==8.10.7 || ==9.10.3 || ==9.14.1
+extra-doc-files:
+  CHANGELOG.md
+extra-source-files:
+  README.md
+  THIRD_PARTY_NOTICES.md
+  cbits/**/*.c
+  cbits/**/*.h
+  cbits/third_party/AIMer/LICENSE
+  cbits/third_party/HAETAE/LICENSE
+  cbits/third_party/NTRUplus/LICENSE
+  cbits/third_party/SMAUG-T/LICENSE
+
+flag kat
+  description: Build deterministic entropy hooks and known-answer tests
+  default:     False
+  manual:      True
+
+common native
+  default-language: Haskell2010
+  ghc-options:      -Wall
+  build-depends:    base >=4.14 && <5
+  hs-source-dirs:   native
+  cc-options:       -std=c11
+  extra-libraries:  m
+  if flag(kat)
+    cpp-options: -DKPQC_TEST_ENTROPY
+    cc-options:  -DKPQC_TEST_ENTROPY
+
+library native-aimer
+  import:          native
+  exposed-modules: Crypto.KpqC.Internal.Native.AIMer
+  c-sources:
+    cbits/core_aimer128f.c
+    cbits/core_aimer128s.c
+    cbits/core_aimer192f.c
+    cbits/core_aimer192s.c
+    cbits/core_aimer256f.c
+    cbits/core_aimer256s.c
+
+library native-haetae
+  import:          native
+  exposed-modules: Crypto.KpqC.Internal.Native.HAETAE
+  include-dirs:    cbits/third_party/HAETAE/include
+  c-sources:
+    cbits/core_haetae2.c
+    cbits/core_haetae3.c
+    cbits/core_haetae5.c
+
+library native-ntruplus
+  import:          native
+  exposed-modules: Crypto.KpqC.Internal.Native.NTRUPlus
+  include-dirs:
+    cbits/third_party/NTRUplus/NTRU+768
+    cbits/third_party/NTRUplus/NTRU+864
+    cbits/third_party/NTRUplus/NTRU+1152
+  c-sources:
+    cbits/core_ntruplus768.c
+    cbits/core_ntruplus864.c
+    cbits/core_ntruplus1152.c
+
+library native-smaugt
+  import:          native
+  exposed-modules: Crypto.KpqC.Internal.Native.SMAUGT
+  include-dirs:    cbits/third_party/SMAUG-T/include
+  c-sources:
+    cbits/core_smaugt128.c
+    cbits/core_smaugt192.c
+    cbits/core_smaugt256.c
+    cbits/core_timer.c
+
+library
+  exposed-modules:  Crypto.KpqC
+  hs-source-dirs:    src
+  default-language: Haskell2010
+  ghc-options:       -Wall
+  build-depends:
+    base >=4.14 && <5,
+    bytestring >=0.10.12 && <0.13,
+    memory >=0.16 && <0.19,
+    KpqC:native-aimer,
+    KpqC:native-haetae,
+    KpqC:native-ntruplus,
+    KpqC:native-smaugt
+
+test-suite spec
+  type:             exitcode-stdio-1.0
+  main-is:          Spec.hs
+  hs-source-dirs:   test
+  default-language: Haskell2010
+  ghc-options:      -Wall -threaded
+  build-depends:
+    base >=4.14 && <5,
+    bytestring >=0.10.12 && <0.13,
+    memory >=0.16 && <0.19,
+    KpqC
+
+test-suite kat
+  type:             exitcode-stdio-1.0
+  main-is:          KAT.hs
+  hs-source-dirs:   test
+  default-language: Haskell2010
+  ghc-options:      -Wall -threaded
+  if flag(kat)
+    buildable: True
+    build-depends:
+      base >=4.14 && <5,
+      bytestring >=0.10.12 && <0.13,
+      containers >=0.6 && <0.9,
+      filepath >=1.4 && <1.6,
+      memory >=0.16 && <0.19,
+      KpqC,
+      KpqC:native-aimer,
+      KpqC:native-haetae,
+      KpqC:native-ntruplus,
+      KpqC:native-smaugt
+    c-sources:
+      cbits/test/aes.c
+      cbits/test/aes_bridge.c
+    include-dirs: cbits/test
+  else
+    buildable: False
+
+source-repository head
+  type:     git
+  location: https://github.com/KpqC/kpqc-haskell.git
+
+source-repository this
+  type:     git
+  location: https://github.com/KpqC/kpqc-haskell.git
+  tag:      v0.1.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 O. Suolfou
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,144 @@
+# KpqC
+
+KpqC provides synchronous Haskell APIs for the AIMer and HAETAE signature
+schemes and the NTRU+ and SMAUG-T key encapsulation mechanisms (KEMs). Native
+operations use safe FFI calls so other Haskell threads can continue running.
+
+This Haskell package is independently maintained and is not an official
+distribution from the algorithm authors.
+
+## Runtime support
+
+- GHC 8.10 or newer and Cabal 3.4 or newer
+- A C11 compiler
+- macOS or Linux
+
+The package bundles its native algorithm sources and builds them locally. It
+does not download native libraries during compilation.
+
+## Install
+
+Add `KpqC` to your package's dependencies:
+
+```cabal
+build-depends: KpqC ^>=0.1.0
+```
+
+## Available schemes
+
+| Algorithm&nbsp;&nbsp; | Type&nbsp;&nbsp; | Constructors |
+| --- | --- | --- |
+| **AIMer**&nbsp;&nbsp; | Signature&nbsp;&nbsp; | `AIMer128f`, `AIMer128s`, `AIMer192f`, `AIMer192s`, `AIMer256f`, `AIMer256s` |
+| **HAETAE**&nbsp;&nbsp; | Signature&nbsp;&nbsp; | `HAETAE2`, `HAETAE3`, `HAETAE5` |
+| **NTRU+**&nbsp;&nbsp; | KEM&nbsp;&nbsp; | `NTRUPlus768`, `NTRUPlus864`, `NTRUPlus1152` |
+| **SMAUG&#8209;T**&nbsp;&nbsp; | KEM&nbsp;&nbsp; | `SMAUGT128`, `SMAUGT192`, `SMAUGT256`, `TiMER` |
+
+### Signatures
+
+```haskell
+import Crypto.KpqC
+import qualified Data.ByteString.Char8 as BS
+
+main :: IO ()
+main = do
+  Right keys <- generateKeyPair AIMer128f
+  let message = BS.pack "release-manifest:v3"
+  Right signature <- sign AIMer128f message (secretKey keys)
+  Right True <- verify AIMer128f message signature (publicKey keys)
+  pure ()
+```
+
+`signWithContext` and `verifyWithContext` accept a domain-separation context
+of at most 255 bytes. Verification fails when the supplied context differs
+from the signing context.
+
+### KEM
+
+A KEM creates a shared secret for a sender and a recipient. The public key may
+be distributed; the secret key and resulting shared secret must remain private.
+
+```haskell
+import Crypto.KpqC
+
+main :: IO ()
+main = do
+  Right recipient <- generateKeyPair SMAUGT192
+  Right outbound <- encapsulate SMAUGT192 (publicKey recipient)
+  Right inbound <-
+    decapsulate SMAUGT192 (ciphertext outbound) (secretKey recipient)
+  print (inbound == sharedSecret outbound)
+```
+
+## Data and failures
+
+Public keys, signatures, ciphertexts, messages, and contexts are strict
+`ByteString` values. Secret keys and shared secrets use `SecretBytes`, backed
+by `ScrubbedBytes` so their memory is scrubbed when it is no longer referenced
+and equality checks are constant-time. Signing and decapsulation also accept
+other `ByteArrayAccess` types, including strict `ByteString`. Operations return
+`IO (Either KpqCError value)`. Invalid signatures return `Right False`.
+
+NTRU+ rejects non-canonical public keys and invalid ciphertexts. SMAUG-T uses
+implicit rejection and returns a replacement secret for an invalid
+ciphertext; it will not equal the sender's shared secret.
+
+### Parameter sizes
+
+All sizes are in bytes.
+
+#### Signatures
+
+| Constructor&nbsp;&nbsp; | Public key&nbsp;&nbsp; | Secret key&nbsp;&nbsp; | Signature |
+| --- | ---: | ---: | ---: |
+| `AIMer128f`&nbsp;&nbsp; | 32&nbsp;&nbsp; | 48&nbsp;&nbsp; | 6,944 |
+| `AIMer128s`&nbsp;&nbsp; | 32&nbsp;&nbsp; | 48&nbsp;&nbsp; | 4,704 |
+| `AIMer192f`&nbsp;&nbsp; | 48&nbsp;&nbsp; | 72&nbsp;&nbsp; | 15,408 |
+| `AIMer192s`&nbsp;&nbsp; | 48&nbsp;&nbsp; | 72&nbsp;&nbsp; | 10,320 |
+| `AIMer256f`&nbsp;&nbsp; | 64&nbsp;&nbsp; | 96&nbsp;&nbsp; | 31,360 |
+| `AIMer256s`&nbsp;&nbsp; | 64&nbsp;&nbsp; | 96&nbsp;&nbsp; | 20,224 |
+| `HAETAE2`&nbsp;&nbsp; | 992&nbsp;&nbsp; | 1,408&nbsp;&nbsp; | 1,474 |
+| `HAETAE3`&nbsp;&nbsp; | 1,472&nbsp;&nbsp; | 2,112&nbsp;&nbsp; | 2,349 |
+| `HAETAE5`&nbsp;&nbsp; | 2,080&nbsp;&nbsp; | 2,752&nbsp;&nbsp; | 2,948 |
+
+#### KEMs
+
+| Constructor&nbsp;&nbsp; | Public key&nbsp;&nbsp; | Secret key&nbsp;&nbsp; | Ciphertext&nbsp;&nbsp; | Shared secret |
+| --- | ---: | ---: | ---: | ---: |
+| `NTRUPlus768`&nbsp;&nbsp; | 1,152&nbsp;&nbsp; | 2,336&nbsp;&nbsp; | 1,152&nbsp;&nbsp; | 32 |
+| `NTRUPlus864`&nbsp;&nbsp; | 1,296&nbsp;&nbsp; | 2,624&nbsp;&nbsp; | 1,296&nbsp;&nbsp; | 32 |
+| `NTRUPlus1152`&nbsp;&nbsp; | 1,728&nbsp;&nbsp; | 3,488&nbsp;&nbsp; | 1,728&nbsp;&nbsp; | 32 |
+| `SMAUGT128`&nbsp;&nbsp; | 672&nbsp;&nbsp; | 832&nbsp;&nbsp; | 672&nbsp;&nbsp; | 32 |
+| `SMAUGT192`&nbsp;&nbsp; | 1,088&nbsp;&nbsp; | 1,312&nbsp;&nbsp; | 992&nbsp;&nbsp; | 32 |
+| `SMAUGT256`&nbsp;&nbsp; | 1,440&nbsp;&nbsp; | 1,728&nbsp;&nbsp; | 1,376&nbsp;&nbsp; | 32 |
+| `TiMER`&nbsp;&nbsp; | 672&nbsp;&nbsp; | 832&nbsp;&nbsp; | 608&nbsp;&nbsp; | 32 |
+
+## Tests
+
+```sh
+cabal test spec
+```
+
+The optional known-answer suite validates all 1,600 records from the pinned
+[KpqC/kpqc-test-vectors at commit d75490bf824f](https://github.com/KpqC/kpqc-test-vectors/tree/d75490bf824faa4b148cd0b901a2eb13198fe0da):
+
+```sh
+cabal clean
+KPQC_TEST_VECTORS=../kpqc-test-vectors cabal test kat -fkat --enable-tests
+```
+
+Deterministic entropy hooks are compiled only when the manual `kat` flag is
+enabled. Run the suite from a clean build so Cabal recompiles the bundled C
+sources with those hooks enabled.
+
+## Security
+
+The native cores are compiled from the upstream reference implementations.
+This package has not received an independent security audit and does not
+guarantee constant-time execution. Assess those constraints before using it
+with sensitive production keys.
+
+Algorithm encodings and parameter sizes follow the exact upstream revisions
+listed below and may differ from other revisions of the same algorithms.
+
+Third-party licenses and attributions are listed in
+[THIRD_PARTY_NOTICES.md](https://github.com/KpqC/kpqc-haskell/blob/main/THIRD_PARTY_NOTICES.md).
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
new file mode 100644
--- /dev/null
+++ b/THIRD_PARTY_NOTICES.md
@@ -0,0 +1,24 @@
+# Third-party notices
+
+This package includes code from the following upstream reference
+implementations:
+
+- [AIMer](https://github.com/samsungsds-opensource/AIMer/tree/734cc76fa4695ca8c8eaa070508db8ff62205e68), Copyright (c) 2022–2026 SAMSUNG SDS
+- [HAETAE](https://github.com/CryptoLabInc/HAETAE/tree/743c31df48183fc8c8a39a4f50a634da1c4af03a), Copyright (c) 2026 Team HAETAE
+- [NTRU+](https://github.com/ntruplus/ntruplus/tree/3991b2ae08d6f0008d37e41b8aceaaab27b4ec89), Copyright (c) 2024–2026 NTRU+ TEAM
+- [SMAUG-T](https://github.com/CryptoLabInc/SMAUG-T/tree/bbc463cd788eae36c6d155cef667ac9dbd9054cd), Copyright (c) 2026 Team SMAUG-T
+
+These components are distributed under the MIT License. Their original license
+texts are included in their source headers or under `cbits/third_party/`.
+
+## Local portability changes
+
+The bundled SMAUG-T sources use unsigned intermediate arithmetic in
+`cbits/third_party/SMAUG-T/src/dg.c`,
+`cbits/third_party/SMAUG-T/src/indcpa.c`, and
+`cbits/third_party/SMAUG-T/src/poly.c` to preserve the reference
+implementation's 16-bit results without relying on undefined signed left
+shifts. All SMAUG-T and TiMER outputs remain byte-for-byte validated against
+the referenced known-answer test vectors. The SMAUG-T fixed-weight sampler
+also uses size-correct loop indices to avoid signed/unsigned comparison
+warnings without changing algorithm outputs.
diff --git a/cbits/core_aimer128f.c b/cbits/core_aimer128f.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_aimer128f.c
@@ -0,0 +1,1 @@
+#include "native/aimer128f/core.c"
diff --git a/cbits/core_aimer128s.c b/cbits/core_aimer128s.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_aimer128s.c
@@ -0,0 +1,1 @@
+#include "native/aimer128s/core.c"
diff --git a/cbits/core_aimer192f.c b/cbits/core_aimer192f.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_aimer192f.c
@@ -0,0 +1,1 @@
+#include "native/aimer192f/core.c"
diff --git a/cbits/core_aimer192s.c b/cbits/core_aimer192s.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_aimer192s.c
@@ -0,0 +1,1 @@
+#include "native/aimer192s/core.c"
diff --git a/cbits/core_aimer256f.c b/cbits/core_aimer256f.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_aimer256f.c
@@ -0,0 +1,1 @@
+#include "native/aimer256f/core.c"
diff --git a/cbits/core_aimer256s.c b/cbits/core_aimer256s.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_aimer256s.c
@@ -0,0 +1,1 @@
+#include "native/aimer256s/core.c"
diff --git a/cbits/core_haetae2.c b/cbits/core_haetae2.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_haetae2.c
@@ -0,0 +1,1 @@
+#include "native/haetae2/core.c"
diff --git a/cbits/core_haetae3.c b/cbits/core_haetae3.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_haetae3.c
@@ -0,0 +1,1 @@
+#include "native/haetae3/core.c"
diff --git a/cbits/core_haetae5.c b/cbits/core_haetae5.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_haetae5.c
@@ -0,0 +1,1 @@
+#include "native/haetae5/core.c"
diff --git a/cbits/core_ntruplus1152.c b/cbits/core_ntruplus1152.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_ntruplus1152.c
@@ -0,0 +1,1 @@
+#include "native/ntruplus1152/core.c"
diff --git a/cbits/core_ntruplus768.c b/cbits/core_ntruplus768.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_ntruplus768.c
@@ -0,0 +1,1 @@
+#include "native/ntruplus768/core.c"
diff --git a/cbits/core_ntruplus864.c b/cbits/core_ntruplus864.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_ntruplus864.c
@@ -0,0 +1,1 @@
+#include "native/ntruplus864/core.c"
diff --git a/cbits/core_smaugt128.c b/cbits/core_smaugt128.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_smaugt128.c
@@ -0,0 +1,1 @@
+#include "native/smaugt128/core.c"
diff --git a/cbits/core_smaugt192.c b/cbits/core_smaugt192.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_smaugt192.c
@@ -0,0 +1,1 @@
+#include "native/smaugt192/core.c"
diff --git a/cbits/core_smaugt256.c b/cbits/core_smaugt256.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_smaugt256.c
@@ -0,0 +1,1 @@
+#include "native/smaugt256/core.c"
diff --git a/cbits/core_timer.c b/cbits/core_timer.c
new file mode 100644
--- /dev/null
+++ b/cbits/core_timer.c
@@ -0,0 +1,1 @@
+#include "native/timer/core.c"
diff --git a/cbits/native/aimer128f/core.c b/cbits/native/aimer128f/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/aimer128f/core.c
@@ -0,0 +1,18 @@
+#define KPQC_VARIANT aimer128f
+#include "../common/pqc_fips202.h"
+#define crypto_sign_keypair_internal KPQC_SYMBOL(crypto_sign_keypair_internal)
+#define crypto_sign_signature_internal KPQC_SYMBOL(crypto_sign_signature_internal)
+#define crypto_sign_verify_internal KPQC_SYMBOL(crypto_sign_verify_internal)
+#define randombytes KPQC_SYMBOL(randombytes)
+#define KPQC_AIMER 1
+#define PARAMS 128f
+#include "../common/randombytes.h"
+#include "../../third_party/AIMer/aim3.c"
+#include "../../third_party/AIMer/field_common.c"
+#include "../../third_party/AIMer/hash.c"
+#include "../../third_party/AIMer/sign.c"
+#include "../../third_party/AIMer/tree.c"
+#include "../../third_party/AIMer/field128.c"
+#include "../../third_party/AIMer/common/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_aimer128f_
+#include "../common/native_signature.h"
diff --git a/cbits/native/aimer128s/core.c b/cbits/native/aimer128s/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/aimer128s/core.c
@@ -0,0 +1,18 @@
+#define KPQC_VARIANT aimer128s
+#include "../common/pqc_fips202.h"
+#define crypto_sign_keypair_internal KPQC_SYMBOL(crypto_sign_keypair_internal)
+#define crypto_sign_signature_internal KPQC_SYMBOL(crypto_sign_signature_internal)
+#define crypto_sign_verify_internal KPQC_SYMBOL(crypto_sign_verify_internal)
+#define randombytes KPQC_SYMBOL(randombytes)
+#define KPQC_AIMER 1
+#define PARAMS 128s
+#include "../common/randombytes.h"
+#include "../../third_party/AIMer/aim3.c"
+#include "../../third_party/AIMer/field_common.c"
+#include "../../third_party/AIMer/hash.c"
+#include "../../third_party/AIMer/sign.c"
+#include "../../third_party/AIMer/tree.c"
+#include "../../third_party/AIMer/field128.c"
+#include "../../third_party/AIMer/common/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_aimer128s_
+#include "../common/native_signature.h"
diff --git a/cbits/native/aimer192f/core.c b/cbits/native/aimer192f/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/aimer192f/core.c
@@ -0,0 +1,18 @@
+#define KPQC_VARIANT aimer192f
+#include "../common/pqc_fips202.h"
+#define crypto_sign_keypair_internal KPQC_SYMBOL(crypto_sign_keypair_internal)
+#define crypto_sign_signature_internal KPQC_SYMBOL(crypto_sign_signature_internal)
+#define crypto_sign_verify_internal KPQC_SYMBOL(crypto_sign_verify_internal)
+#define randombytes KPQC_SYMBOL(randombytes)
+#define KPQC_AIMER 1
+#define PARAMS 192f
+#include "../common/randombytes.h"
+#include "../../third_party/AIMer/aim3.c"
+#include "../../third_party/AIMer/field_common.c"
+#include "../../third_party/AIMer/hash.c"
+#include "../../third_party/AIMer/sign.c"
+#include "../../third_party/AIMer/tree.c"
+#include "../../third_party/AIMer/field192.c"
+#include "../../third_party/AIMer/common/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_aimer192f_
+#include "../common/native_signature.h"
diff --git a/cbits/native/aimer192s/core.c b/cbits/native/aimer192s/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/aimer192s/core.c
@@ -0,0 +1,18 @@
+#define KPQC_VARIANT aimer192s
+#include "../common/pqc_fips202.h"
+#define crypto_sign_keypair_internal KPQC_SYMBOL(crypto_sign_keypair_internal)
+#define crypto_sign_signature_internal KPQC_SYMBOL(crypto_sign_signature_internal)
+#define crypto_sign_verify_internal KPQC_SYMBOL(crypto_sign_verify_internal)
+#define randombytes KPQC_SYMBOL(randombytes)
+#define KPQC_AIMER 1
+#define PARAMS 192s
+#include "../common/randombytes.h"
+#include "../../third_party/AIMer/aim3.c"
+#include "../../third_party/AIMer/field_common.c"
+#include "../../third_party/AIMer/hash.c"
+#include "../../third_party/AIMer/sign.c"
+#include "../../third_party/AIMer/tree.c"
+#include "../../third_party/AIMer/field192.c"
+#include "../../third_party/AIMer/common/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_aimer192s_
+#include "../common/native_signature.h"
diff --git a/cbits/native/aimer256f/core.c b/cbits/native/aimer256f/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/aimer256f/core.c
@@ -0,0 +1,18 @@
+#define KPQC_VARIANT aimer256f
+#include "../common/pqc_fips202.h"
+#define crypto_sign_keypair_internal KPQC_SYMBOL(crypto_sign_keypair_internal)
+#define crypto_sign_signature_internal KPQC_SYMBOL(crypto_sign_signature_internal)
+#define crypto_sign_verify_internal KPQC_SYMBOL(crypto_sign_verify_internal)
+#define randombytes KPQC_SYMBOL(randombytes)
+#define KPQC_AIMER 1
+#define PARAMS 256f
+#include "../common/randombytes.h"
+#include "../../third_party/AIMer/aim3.c"
+#include "../../third_party/AIMer/field_common.c"
+#include "../../third_party/AIMer/hash.c"
+#include "../../third_party/AIMer/sign.c"
+#include "../../third_party/AIMer/tree.c"
+#include "../../third_party/AIMer/field256.c"
+#include "../../third_party/AIMer/common/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_aimer256f_
+#include "../common/native_signature.h"
diff --git a/cbits/native/aimer256s/core.c b/cbits/native/aimer256s/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/aimer256s/core.c
@@ -0,0 +1,18 @@
+#define KPQC_VARIANT aimer256s
+#include "../common/pqc_fips202.h"
+#define crypto_sign_keypair_internal KPQC_SYMBOL(crypto_sign_keypair_internal)
+#define crypto_sign_signature_internal KPQC_SYMBOL(crypto_sign_signature_internal)
+#define crypto_sign_verify_internal KPQC_SYMBOL(crypto_sign_verify_internal)
+#define randombytes KPQC_SYMBOL(randombytes)
+#define KPQC_AIMER 1
+#define PARAMS 256s
+#include "../common/randombytes.h"
+#include "../../third_party/AIMer/aim3.c"
+#include "../../third_party/AIMer/field_common.c"
+#include "../../third_party/AIMer/hash.c"
+#include "../../third_party/AIMer/sign.c"
+#include "../../third_party/AIMer/tree.c"
+#include "../../third_party/AIMer/field256.c"
+#include "../../third_party/AIMer/common/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_aimer256s_
+#include "../common/native_signature.h"
diff --git a/cbits/native/common/compact_fips202.h b/cbits/native/common/compact_fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/compact_fips202.h
@@ -0,0 +1,19 @@
+#include "prefix.h"
+
+#define KeccakF_RoundConstants KPQC_SYMBOL(KeccakF_RoundConstants)
+#define sha3_256 KPQC_SYMBOL(sha3_256)
+#define sha3_512 KPQC_SYMBOL(sha3_512)
+#define shake128 KPQC_SYMBOL(shake128)
+#define shake128_absorb KPQC_SYMBOL(shake128_absorb)
+#define shake128_absorb_once KPQC_SYMBOL(shake128_absorb_once)
+#define shake128_finalize KPQC_SYMBOL(shake128_finalize)
+#define shake128_init KPQC_SYMBOL(shake128_init)
+#define shake128_squeeze KPQC_SYMBOL(shake128_squeeze)
+#define shake128_squeezeblocks KPQC_SYMBOL(shake128_squeezeblocks)
+#define shake256 KPQC_SYMBOL(shake256)
+#define shake256_absorb KPQC_SYMBOL(shake256_absorb)
+#define shake256_absorb_once KPQC_SYMBOL(shake256_absorb_once)
+#define shake256_finalize KPQC_SYMBOL(shake256_finalize)
+#define shake256_init KPQC_SYMBOL(shake256_init)
+#define shake256_squeeze KPQC_SYMBOL(shake256_squeeze)
+#define shake256_squeezeblocks KPQC_SYMBOL(shake256_squeezeblocks)
diff --git a/cbits/native/common/haetae.h b/cbits/native/common/haetae.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/haetae.h
@@ -0,0 +1,14 @@
+#include "compact_fips202.h"
+
+#define brv8 KPQC_SYMBOL(brv8)
+#define fix_round KPQC_SYMBOL(fix_round)
+#define hammingWeight_8 KPQC_SYMBOL(hammingWeight_8)
+#define polyfixfix_sub KPQC_SYMBOL(polyfixfix_sub)
+#define sample_gauss KPQC_SYMBOL(sample_gauss)
+#define shake128_stream_init KPQC_SYMBOL(shake128_stream_init)
+#define shake256_absorb_thrice KPQC_SYMBOL(shake256_absorb_thrice)
+#define shake256_absorb_twice KPQC_SYMBOL(shake256_absorb_twice)
+#define shake256_stream_init KPQC_SYMBOL(shake256_stream_init)
+#define start_cube KPQC_SYMBOL(start_cube)
+#define start_times_threehalves KPQC_SYMBOL(start_times_threehalves)
+#define randombytes KPQC_SYMBOL(randombytes)
diff --git a/cbits/native/common/haetae_sources.h b/cbits/native/common/haetae_sources.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/haetae_sources.h
@@ -0,0 +1,15 @@
+#include "../../third_party/HAETAE/src/decompose.c"
+#include "../../third_party/HAETAE/src/encoding.c"
+#include "../../third_party/HAETAE/src/fft.c"
+#include "../../third_party/HAETAE/src/fixpoint.c"
+#include "../../third_party/HAETAE/src/ntt.c"
+#include "../../third_party/HAETAE/src/packing.c"
+#include "../../third_party/HAETAE/src/poly.c"
+#include "../../third_party/HAETAE/src/polyfix.c"
+#include "../../third_party/HAETAE/src/polymat.c"
+#include "../../third_party/HAETAE/src/polyvec.c"
+#include "../../third_party/HAETAE/src/reduce.c"
+#include "../../third_party/HAETAE/src/sampler.c"
+#include "../../third_party/HAETAE/src/sign.c"
+#include "../../third_party/HAETAE/src/fips202.c"
+#include "../../third_party/HAETAE/src/symmetric-shake.c"
diff --git a/cbits/native/common/native_kem.h b/cbits/native/common/native_kem.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/native_kem.h
@@ -0,0 +1,33 @@
+#include <stdint.h>
+
+#define KPQC_JOIN_(left, right) left##right
+#define KPQC_JOIN(left, right) KPQC_JOIN_(left, right)
+#define KPQC_API(name) KPQC_JOIN(KPQC_NATIVE_PREFIX, name)
+
+int KPQC_API(keypair)(uint8_t *public_key, uint8_t *secret_key) {
+    return crypto_kem_keypair(public_key, secret_key);
+}
+
+int KPQC_API(encapsulate)(uint8_t *ciphertext, uint8_t *shared_secret,
+                          const uint8_t *public_key) {
+    return crypto_kem_enc(ciphertext, shared_secret, public_key);
+}
+
+int KPQC_API(decapsulate)(uint8_t *shared_secret, const uint8_t *ciphertext,
+                          const uint8_t *secret_key) {
+    return crypto_kem_dec(shared_secret, ciphertext, secret_key);
+}
+
+#if defined(KPQC_TEST_ENTROPY)
+int KPQC_API(set_test_entropy)(const uint8_t *input, size_t length) {
+    return kpqc_set_test_entropy(input, length);
+}
+
+size_t KPQC_API(test_entropy_remaining)(void) {
+    return kpqc_test_entropy_remaining();
+}
+
+void KPQC_API(clear_test_entropy)(void) {
+    kpqc_clear_test_entropy();
+}
+#endif
diff --git a/cbits/native/common/native_signature.h b/cbits/native/common/native_signature.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/native_signature.h
@@ -0,0 +1,43 @@
+#include <stddef.h>
+#include <stdint.h>
+
+#define KPQC_JOIN_(left, right) left##right
+#define KPQC_JOIN(left, right) KPQC_JOIN_(left, right)
+#define KPQC_API(name) KPQC_JOIN(KPQC_NATIVE_PREFIX, name)
+
+int KPQC_API(keypair)(uint8_t *public_key, uint8_t *secret_key) {
+    return crypto_sign_keypair(public_key, secret_key);
+}
+
+int KPQC_API(sign)(uint8_t *signature, uint64_t *signature_len,
+                   const uint8_t *message, size_t message_len,
+                   const uint8_t *context, size_t context_len,
+                   const uint8_t *secret_key) {
+    size_t length = 0;
+    int status = crypto_sign_signature(signature, &length, message, message_len,
+                                       context, context_len, secret_key);
+    *signature_len = (uint64_t)length;
+    return status;
+}
+
+int KPQC_API(verify)(const uint8_t *signature, size_t signature_len,
+                     const uint8_t *message, size_t message_len,
+                     const uint8_t *context, size_t context_len,
+                     const uint8_t *public_key) {
+    return crypto_sign_verify(signature, signature_len, message, message_len,
+                              context, context_len, public_key);
+}
+
+#if defined(KPQC_TEST_ENTROPY)
+int KPQC_API(set_test_entropy)(const uint8_t *input, size_t length) {
+    return kpqc_set_test_entropy(input, length);
+}
+
+size_t KPQC_API(test_entropy_remaining)(void) {
+    return kpqc_test_entropy_remaining();
+}
+
+void KPQC_API(clear_test_entropy)(void) {
+    kpqc_clear_test_entropy();
+}
+#endif
diff --git a/cbits/native/common/ntruplus.h b/cbits/native/common/ntruplus.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/ntruplus.h
@@ -0,0 +1,34 @@
+#include "pqc_fips202.h"
+
+/* Keep NTRU+ entry points internal to the bundled native component. */
+#define NTRUPLUS_ABI_H
+#define NTRUPLUS_INTERNAL
+#define NTRUPLUS_EXTERNAL
+#define NTRUPLUS_SYSV
+
+#define baseinv KPQC_SYMBOL(baseinv)
+#define basemul KPQC_SYMBOL(basemul)
+#define basemul_add KPQC_SYMBOL(basemul_add)
+#define crypto_kem_dec KPQC_SYMBOL(crypto_kem_dec)
+#define crypto_kem_enc KPQC_SYMBOL(crypto_kem_enc)
+#define crypto_kem_keypair KPQC_SYMBOL(crypto_kem_keypair)
+#define hash_f KPQC_SYMBOL(hash_f)
+#define hash_g KPQC_SYMBOL(hash_g)
+#define hash_h KPQC_SYMBOL(hash_h)
+#define invntt KPQC_SYMBOL(invntt)
+#define ntt KPQC_SYMBOL(ntt)
+#define poly_baseinv KPQC_SYMBOL(poly_baseinv)
+#define poly_basemul KPQC_SYMBOL(poly_basemul)
+#define poly_basemul_add KPQC_SYMBOL(poly_basemul_add)
+#define poly_cbd1 KPQC_SYMBOL(poly_cbd1)
+#define poly_crepmod3 KPQC_SYMBOL(poly_crepmod3)
+#define poly_frombytes KPQC_SYMBOL(poly_frombytes)
+#define poly_invntt KPQC_SYMBOL(poly_invntt)
+#define poly_ntt KPQC_SYMBOL(poly_ntt)
+#define poly_sotp_decode KPQC_SYMBOL(poly_sotp_decode)
+#define poly_sotp_encode KPQC_SYMBOL(poly_sotp_encode)
+#define poly_sub KPQC_SYMBOL(poly_sub)
+#define poly_tobytes KPQC_SYMBOL(poly_tobytes)
+#define poly_triple KPQC_SYMBOL(poly_triple)
+#define zetas KPQC_SYMBOL(zetas)
+#define randombytes KPQC_SYMBOL(randombytes)
diff --git a/cbits/native/common/pqc_fips202.h b/cbits/native/common/pqc_fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/pqc_fips202.h
@@ -0,0 +1,42 @@
+#include "prefix.h"
+
+#define sha3_256 KPQC_SYMBOL(sha3_256)
+#define sha3_256_inc_absorb KPQC_SYMBOL(sha3_256_inc_absorb)
+#define sha3_256_inc_ctx_clone KPQC_SYMBOL(sha3_256_inc_ctx_clone)
+#define sha3_256_inc_ctx_release KPQC_SYMBOL(sha3_256_inc_ctx_release)
+#define sha3_256_inc_finalize KPQC_SYMBOL(sha3_256_inc_finalize)
+#define sha3_256_inc_init KPQC_SYMBOL(sha3_256_inc_init)
+#define sha3_384 KPQC_SYMBOL(sha3_384)
+#define sha3_384_inc_absorb KPQC_SYMBOL(sha3_384_inc_absorb)
+#define sha3_384_inc_ctx_clone KPQC_SYMBOL(sha3_384_inc_ctx_clone)
+#define sha3_384_inc_ctx_release KPQC_SYMBOL(sha3_384_inc_ctx_release)
+#define sha3_384_inc_finalize KPQC_SYMBOL(sha3_384_inc_finalize)
+#define sha3_384_inc_init KPQC_SYMBOL(sha3_384_inc_init)
+#define sha3_512 KPQC_SYMBOL(sha3_512)
+#define sha3_512_inc_absorb KPQC_SYMBOL(sha3_512_inc_absorb)
+#define sha3_512_inc_ctx_clone KPQC_SYMBOL(sha3_512_inc_ctx_clone)
+#define sha3_512_inc_ctx_release KPQC_SYMBOL(sha3_512_inc_ctx_release)
+#define sha3_512_inc_finalize KPQC_SYMBOL(sha3_512_inc_finalize)
+#define sha3_512_inc_init KPQC_SYMBOL(sha3_512_inc_init)
+#define shake128 KPQC_SYMBOL(shake128)
+#define shake128_absorb KPQC_SYMBOL(shake128_absorb)
+#define shake128_ctx_clone KPQC_SYMBOL(shake128_ctx_clone)
+#define shake128_ctx_release KPQC_SYMBOL(shake128_ctx_release)
+#define shake128_inc_absorb KPQC_SYMBOL(shake128_inc_absorb)
+#define shake128_inc_ctx_clone KPQC_SYMBOL(shake128_inc_ctx_clone)
+#define shake128_inc_ctx_release KPQC_SYMBOL(shake128_inc_ctx_release)
+#define shake128_inc_finalize KPQC_SYMBOL(shake128_inc_finalize)
+#define shake128_inc_init KPQC_SYMBOL(shake128_inc_init)
+#define shake128_inc_squeeze KPQC_SYMBOL(shake128_inc_squeeze)
+#define shake128_squeezeblocks KPQC_SYMBOL(shake128_squeezeblocks)
+#define shake256 KPQC_SYMBOL(shake256)
+#define shake256_absorb KPQC_SYMBOL(shake256_absorb)
+#define shake256_ctx_clone KPQC_SYMBOL(shake256_ctx_clone)
+#define shake256_ctx_release KPQC_SYMBOL(shake256_ctx_release)
+#define shake256_inc_absorb KPQC_SYMBOL(shake256_inc_absorb)
+#define shake256_inc_ctx_clone KPQC_SYMBOL(shake256_inc_ctx_clone)
+#define shake256_inc_ctx_release KPQC_SYMBOL(shake256_inc_ctx_release)
+#define shake256_inc_finalize KPQC_SYMBOL(shake256_inc_finalize)
+#define shake256_inc_init KPQC_SYMBOL(shake256_inc_init)
+#define shake256_inc_squeeze KPQC_SYMBOL(shake256_inc_squeeze)
+#define shake256_squeezeblocks KPQC_SYMBOL(shake256_squeezeblocks)
diff --git a/cbits/native/common/prefix.h b/cbits/native/common/prefix.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/prefix.h
@@ -0,0 +1,8 @@
+#ifndef KPQC_NATIVE_PREFIX_H
+#define KPQC_NATIVE_PREFIX_H
+
+#define KPQC_PREFIXED_(variant, name) kpqc_##variant##_##name
+#define KPQC_PREFIXED(variant, name) KPQC_PREFIXED_(variant, name)
+#define KPQC_SYMBOL(name) KPQC_PREFIXED(KPQC_VARIANT, name)
+
+#endif
diff --git a/cbits/native/common/randombytes.h b/cbits/native/common/randombytes.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/randombytes.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: MIT */
+#include <stddef.h>
+#include <stdint.h>
+#if defined(KPQC_TEST_ENTROPY)
+#include <string.h>
+#include <stdlib.h>
+#endif
+
+#if defined(_WIN32)
+#include <limits.h>
+#include <windows.h>
+#include <bcrypt.h>
+#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
+    defined(__NetBSD__) || defined(__DragonFly__)
+#include <stdlib.h>
+#elif defined(__linux__) || defined(__ANDROID__)
+#include <errno.h>
+#include <sys/random.h>
+#else
+#error "kpqc supports operating-system entropy on Windows, Linux, Android, macOS, and BSD"
+#endif
+
+#if defined(KPQC_TEST_ENTROPY)
+/*
+ * This override is compiled only for kpqc_kat builds and is exposed through
+ * internal/native. It lets the KAT tests reproduce the NIST CTR-DRBG stream
+ * without changing the public API or the operating-system entropy used in
+ * normal builds.
+ */
+static uint8_t *kpqc_test_entropy = NULL;
+static size_t kpqc_test_entropy_length = 0;
+static size_t kpqc_test_entropy_offset = 0;
+static int kpqc_test_entropy_active = 0;
+
+static void kpqc_clear_test_entropy(void) {
+    if (kpqc_test_entropy != NULL) {
+        volatile uint8_t *bytes = kpqc_test_entropy;
+        for (size_t i = 0; i < kpqc_test_entropy_length; ++i) bytes[i] = 0;
+        free(kpqc_test_entropy);
+    }
+    kpqc_test_entropy = NULL;
+    kpqc_test_entropy_length = 0;
+    kpqc_test_entropy_offset = 0;
+    kpqc_test_entropy_active = 0;
+}
+
+static int kpqc_set_test_entropy(const uint8_t *input, size_t length) {
+    kpqc_clear_test_entropy();
+    if (length != 0) {
+        kpqc_test_entropy = (uint8_t *)malloc(length);
+        if (kpqc_test_entropy == NULL) return -1;
+        memcpy(kpqc_test_entropy, input, length);
+    }
+    kpqc_test_entropy_length = length;
+    kpqc_test_entropy_active = 1;
+    return 0;
+}
+
+static size_t kpqc_test_entropy_remaining(void) {
+    return kpqc_test_entropy_length - kpqc_test_entropy_offset;
+}
+#endif
+
+static int kpqc_fill_random(uint8_t *output, size_t length) {
+#if defined(KPQC_TEST_ENTROPY)
+    if (kpqc_test_entropy_active) {
+        if (length > kpqc_test_entropy_remaining()) return -1;
+        if (length != 0) {
+            memcpy(output, kpqc_test_entropy + kpqc_test_entropy_offset, length);
+            kpqc_test_entropy_offset += length;
+        }
+        return 0;
+    }
+#endif
+#if defined(_WIN32)
+    while (length != 0) {
+        ULONG chunk = length > (size_t)ULONG_MAX ? ULONG_MAX : (ULONG)length;
+        if (BCryptGenRandom(NULL, output, chunk,
+                            BCRYPT_USE_SYSTEM_PREFERRED_RNG) != 0) {
+            return -1;
+        }
+        output += chunk;
+        length -= chunk;
+    }
+    return 0;
+#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
+    defined(__NetBSD__) || defined(__DragonFly__)
+    arc4random_buf(output, length);
+    return 0;
+#else
+    while (length != 0) {
+        ssize_t count = getrandom(output, length, 0);
+        if (count < 0) {
+            if (errno == EINTR) continue;
+            return -1;
+        }
+        if (count == 0) return -1;
+        output += (size_t)count;
+        length -= (size_t)count;
+    }
+    return 0;
+#endif
+}
+
+#if defined(KPQC_AIMER)
+int randombytes(unsigned char *output, unsigned long long length) {
+    if (length > (unsigned long long)SIZE_MAX) return -1;
+    return kpqc_fill_random(output, (size_t)length);
+}
+#else
+int randombytes(uint8_t *output, size_t length) {
+    return kpqc_fill_random(output, length);
+}
+#endif
diff --git a/cbits/native/common/smaugt.h b/cbits/native/common/smaugt.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/smaugt.h
@@ -0,0 +1,4 @@
+#include "compact_fips202.h"
+
+#define flipabs KPQC_SYMBOL(flipabs)
+#define randombytes KPQC_SYMBOL(randombytes)
diff --git a/cbits/native/common/smaugt_sources.h b/cbits/native/common/smaugt_sources.h
new file mode 100644
--- /dev/null
+++ b/cbits/native/common/smaugt_sources.h
@@ -0,0 +1,14 @@
+#include "../../third_party/SMAUG-T/src/cbd.c"
+#include "../../third_party/SMAUG-T/src/ciphertext.c"
+#include "../../third_party/SMAUG-T/src/dg.c"
+#include "../../third_party/SMAUG-T/src/hash.c"
+#include "../../third_party/SMAUG-T/src/hwt.c"
+#include "../../third_party/SMAUG-T/src/indcpa.c"
+#include "../../third_party/SMAUG-T/src/kem.c"
+#include "../../third_party/SMAUG-T/src/key.c"
+#include "../../third_party/SMAUG-T/src/pack.c"
+#include "../../third_party/SMAUG-T/src/packring.c"
+#include "../../third_party/SMAUG-T/src/poly.c"
+#include "../../third_party/SMAUG-T/src/toomcook.c"
+#include "../../third_party/SMAUG-T/src/verify.c"
+#include "../../third_party/SMAUG-T/src/fips202.c"
diff --git a/cbits/native/haetae2/core.c b/cbits/native/haetae2/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/haetae2/core.c
@@ -0,0 +1,7 @@
+#define KPQC_VARIANT haetae2
+#include "../common/haetae.h"
+#define HAETAE_CONFIG_MODE 0
+#include "../common/randombytes.h"
+#include "../common/haetae_sources.h"
+#define KPQC_NATIVE_PREFIX kpqc_haetae2_
+#include "../common/native_signature.h"
diff --git a/cbits/native/haetae3/core.c b/cbits/native/haetae3/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/haetae3/core.c
@@ -0,0 +1,7 @@
+#define KPQC_VARIANT haetae3
+#include "../common/haetae.h"
+#define HAETAE_CONFIG_MODE 1
+#include "../common/randombytes.h"
+#include "../common/haetae_sources.h"
+#define KPQC_NATIVE_PREFIX kpqc_haetae3_
+#include "../common/native_signature.h"
diff --git a/cbits/native/haetae5/core.c b/cbits/native/haetae5/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/haetae5/core.c
@@ -0,0 +1,7 @@
+#define KPQC_VARIANT haetae5
+#include "../common/haetae.h"
+#define HAETAE_CONFIG_MODE 2
+#include "../common/randombytes.h"
+#include "../common/haetae_sources.h"
+#define KPQC_NATIVE_PREFIX kpqc_haetae5_
+#include "../common/native_signature.h"
diff --git a/cbits/native/ntruplus1152/core.c b/cbits/native/ntruplus1152/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/ntruplus1152/core.c
@@ -0,0 +1,10 @@
+#define KPQC_VARIANT ntruplus1152
+#include "../common/ntruplus.h"
+#include "../common/randombytes.h"
+#include "../../third_party/NTRUplus/NTRU+1152/kem.c"
+#include "../../third_party/NTRUplus/NTRU+1152/ntt.c"
+#include "../../third_party/NTRUplus/NTRU+1152/poly.c"
+#include "../../third_party/NTRUplus/NTRU+1152/symmetric.c"
+#include "../../third_party/NTRUplus/NTRU+1152/fips202/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_ntruplus1152_
+#include "../common/native_kem.h"
diff --git a/cbits/native/ntruplus768/core.c b/cbits/native/ntruplus768/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/ntruplus768/core.c
@@ -0,0 +1,10 @@
+#define KPQC_VARIANT ntruplus768
+#include "../common/ntruplus.h"
+#include "../common/randombytes.h"
+#include "../../third_party/NTRUplus/NTRU+768/kem.c"
+#include "../../third_party/NTRUplus/NTRU+768/ntt.c"
+#include "../../third_party/NTRUplus/NTRU+768/poly.c"
+#include "../../third_party/NTRUplus/NTRU+768/symmetric.c"
+#include "../../third_party/NTRUplus/NTRU+768/fips202/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_ntruplus768_
+#include "../common/native_kem.h"
diff --git a/cbits/native/ntruplus864/core.c b/cbits/native/ntruplus864/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/ntruplus864/core.c
@@ -0,0 +1,10 @@
+#define KPQC_VARIANT ntruplus864
+#include "../common/ntruplus.h"
+#include "../common/randombytes.h"
+#include "../../third_party/NTRUplus/NTRU+864/kem.c"
+#include "../../third_party/NTRUplus/NTRU+864/ntt.c"
+#include "../../third_party/NTRUplus/NTRU+864/poly.c"
+#include "../../third_party/NTRUplus/NTRU+864/symmetric.c"
+#include "../../third_party/NTRUplus/NTRU+864/fips202/fips202.c"
+#define KPQC_NATIVE_PREFIX kpqc_ntruplus864_
+#include "../common/native_kem.h"
diff --git a/cbits/native/smaugt128/core.c b/cbits/native/smaugt128/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/smaugt128/core.c
@@ -0,0 +1,7 @@
+#define KPQC_VARIANT smaugt128
+#include "../common/smaugt.h"
+#define SMAUGT_CONFIG_MODE 0
+#include "../common/randombytes.h"
+#include "../common/smaugt_sources.h"
+#define KPQC_NATIVE_PREFIX kpqc_smaugt128_
+#include "../common/native_kem.h"
diff --git a/cbits/native/smaugt192/core.c b/cbits/native/smaugt192/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/smaugt192/core.c
@@ -0,0 +1,7 @@
+#define KPQC_VARIANT smaugt192
+#include "../common/smaugt.h"
+#define SMAUGT_CONFIG_MODE 1
+#include "../common/randombytes.h"
+#include "../common/smaugt_sources.h"
+#define KPQC_NATIVE_PREFIX kpqc_smaugt192_
+#include "../common/native_kem.h"
diff --git a/cbits/native/smaugt256/core.c b/cbits/native/smaugt256/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/smaugt256/core.c
@@ -0,0 +1,7 @@
+#define KPQC_VARIANT smaugt256
+#include "../common/smaugt.h"
+#define SMAUGT_CONFIG_MODE 2
+#include "../common/randombytes.h"
+#include "../common/smaugt_sources.h"
+#define KPQC_NATIVE_PREFIX kpqc_smaugt256_
+#include "../common/native_kem.h"
diff --git a/cbits/native/timer/core.c b/cbits/native/timer/core.c
new file mode 100644
--- /dev/null
+++ b/cbits/native/timer/core.c
@@ -0,0 +1,7 @@
+#define KPQC_VARIANT timer
+#include "../common/smaugt.h"
+#define SMAUGT_CONFIG_MODE 3
+#include "../common/randombytes.h"
+#include "../common/smaugt_sources.h"
+#define KPQC_NATIVE_PREFIX kpqc_timer_
+#include "../common/native_kem.h"
diff --git a/cbits/test/aes.c b/cbits/test/aes.c
new file mode 100644
--- /dev/null
+++ b/cbits/test/aes.c
@@ -0,0 +1,176 @@
+/* Test-only AES-256 implementation for the CTR-DRBG known-answer-test hook. */
+#include "aes.h"
+
+#include <string.h>
+
+#define AES256_ROUNDS 14
+
+static uint8_t xtime(uint8_t value) {
+    return (uint8_t)((uint8_t)(value << 1) ^
+                     (uint8_t)(((uint8_t)(value >> 7)) * 0x1bu));
+}
+
+static uint8_t gf_multiply(uint8_t left, uint8_t right) {
+    uint8_t product = 0;
+    for (unsigned int bit = 0; bit < 8; ++bit) {
+        if ((right & 1u) != 0) product ^= left;
+        left = xtime(left);
+        right >>= 1;
+    }
+    return product;
+}
+
+static uint8_t gf_inverse(uint8_t value) {
+    uint8_t result = 1;
+    uint8_t power = value;
+    unsigned int exponent = 254;
+
+    if (value == 0) return 0;
+    while (exponent != 0) {
+        if ((exponent & 1u) != 0) result = gf_multiply(result, power);
+        power = gf_multiply(power, power);
+        exponent >>= 1;
+    }
+    return result;
+}
+
+static uint8_t rotate_left(uint8_t value, unsigned int count) {
+    return (uint8_t)((uint8_t)(value << count) |
+                     (uint8_t)(value >> (8u - count)));
+}
+
+static uint8_t substitute(uint8_t value) {
+    uint8_t inverse = gf_inverse(value);
+    return (uint8_t)(inverse ^ rotate_left(inverse, 1) ^
+                     rotate_left(inverse, 2) ^ rotate_left(inverse, 3) ^
+                     rotate_left(inverse, 4) ^ 0x63u);
+}
+
+static void add_round_key(uint8_t state[AES_BLOCKBYTES],
+                          const uint8_t *round_key) {
+    for (size_t index = 0; index < AES_BLOCKBYTES; ++index) {
+        state[index] ^= round_key[index];
+    }
+}
+
+static void substitute_bytes(uint8_t state[AES_BLOCKBYTES]) {
+    for (size_t index = 0; index < AES_BLOCKBYTES; ++index) {
+        state[index] = substitute(state[index]);
+    }
+}
+
+static void shift_rows(uint8_t state[AES_BLOCKBYTES]) {
+    uint8_t shifted[AES_BLOCKBYTES];
+
+    shifted[0] = state[0];
+    shifted[1] = state[5];
+    shifted[2] = state[10];
+    shifted[3] = state[15];
+    shifted[4] = state[4];
+    shifted[5] = state[9];
+    shifted[6] = state[14];
+    shifted[7] = state[3];
+    shifted[8] = state[8];
+    shifted[9] = state[13];
+    shifted[10] = state[2];
+    shifted[11] = state[7];
+    shifted[12] = state[12];
+    shifted[13] = state[1];
+    shifted[14] = state[6];
+    shifted[15] = state[11];
+
+    memcpy(state, shifted, sizeof(shifted));
+}
+
+static void mix_columns(uint8_t state[AES_BLOCKBYTES]) {
+    for (size_t column = 0; column < AES_BLOCKBYTES; column += 4) {
+        uint8_t first = state[column];
+        uint8_t combined = (uint8_t)(state[column] ^ state[column + 1] ^
+                                     state[column + 2] ^ state[column + 3]);
+
+        state[column] ^= (uint8_t)(combined ^
+                                   xtime((uint8_t)(state[column] ^
+                                                   state[column + 1])));
+        state[column + 1] ^=
+            (uint8_t)(combined ^ xtime((uint8_t)(state[column + 1] ^
+                                                  state[column + 2])));
+        state[column + 2] ^=
+            (uint8_t)(combined ^ xtime((uint8_t)(state[column + 2] ^
+                                                  state[column + 3])));
+        state[column + 3] ^=
+            (uint8_t)(combined ^ xtime((uint8_t)(state[column + 3] ^ first)));
+    }
+}
+
+static void aes_secure_zero(void *value, size_t length) {
+    volatile uint8_t *bytes = (volatile uint8_t *)value;
+    while (length-- != 0) *bytes++ = 0;
+}
+
+void aes256_ecb_keyexp(aes256ctx *ctx, const uint8_t key[AES256_KEYBYTES]) {
+    uint8_t temporary[4];
+    size_t generated = AES256_KEYBYTES;
+    uint8_t round_constant = 1;
+
+    memcpy(ctx->round_keys, key, AES256_KEYBYTES);
+    while (generated < AES256_ROUND_KEYBYTES) {
+        memcpy(temporary, ctx->round_keys + generated - sizeof(temporary),
+               sizeof(temporary));
+
+        if (generated % AES256_KEYBYTES == 0) {
+            uint8_t first = temporary[0];
+            temporary[0] = substitute(temporary[1]);
+            temporary[1] = substitute(temporary[2]);
+            temporary[2] = substitute(temporary[3]);
+            temporary[3] = substitute(first);
+            temporary[0] ^= round_constant;
+            round_constant = xtime(round_constant);
+        } else if (generated % AES256_KEYBYTES == 16) {
+            for (size_t index = 0; index < sizeof(temporary); ++index) {
+                temporary[index] = substitute(temporary[index]);
+            }
+        }
+
+        for (size_t index = 0;
+             index < sizeof(temporary) && generated < AES256_ROUND_KEYBYTES;
+             ++index, ++generated) {
+            ctx->round_keys[generated] =
+                (uint8_t)(ctx->round_keys[generated - AES256_KEYBYTES] ^
+                          temporary[index]);
+        }
+    }
+    aes_secure_zero(temporary, sizeof(temporary));
+}
+
+static void encrypt_block(uint8_t output[AES_BLOCKBYTES],
+                          const uint8_t input[AES_BLOCKBYTES],
+                          const aes256ctx *ctx) {
+    uint8_t state[AES_BLOCKBYTES];
+
+    memcpy(state, input, sizeof(state));
+    add_round_key(state, ctx->round_keys);
+    for (size_t round = 1; round < AES256_ROUNDS; ++round) {
+        substitute_bytes(state);
+        shift_rows(state);
+        mix_columns(state);
+        add_round_key(state, ctx->round_keys + round * AES_BLOCKBYTES);
+    }
+    substitute_bytes(state);
+    shift_rows(state);
+    add_round_key(state,
+                  ctx->round_keys + AES256_ROUNDS * AES_BLOCKBYTES);
+    memcpy(output, state, sizeof(state));
+    aes_secure_zero(state, sizeof(state));
+}
+
+void aes256_ecb(uint8_t *output, const uint8_t *input, size_t blocks,
+                const aes256ctx *ctx) {
+    for (size_t block = 0; block < blocks; ++block) {
+        encrypt_block(output + block * AES_BLOCKBYTES,
+                      input + block * AES_BLOCKBYTES, ctx);
+    }
+}
+
+void aes256_ctx_release(aes256ctx *ctx) {
+    aes_secure_zero(ctx, sizeof(*ctx));
+}
diff --git a/cbits/test/aes.h b/cbits/test/aes.h
new file mode 100644
--- /dev/null
+++ b/cbits/test/aes.h
@@ -0,0 +1,20 @@
+#ifndef KPQC_KAT_AES_H
+#define KPQC_KAT_AES_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define AES256_KEYBYTES 32
+#define AES_BLOCKBYTES 16
+#define AES256_ROUND_KEYBYTES 240
+
+typedef struct {
+    uint8_t round_keys[AES256_ROUND_KEYBYTES];
+} aes256ctx;
+
+void aes256_ecb_keyexp(aes256ctx *ctx, const uint8_t key[AES256_KEYBYTES]);
+void aes256_ecb(uint8_t *output, const uint8_t *input, size_t blocks,
+                const aes256ctx *ctx);
+void aes256_ctx_release(aes256ctx *ctx);
+
+#endif
diff --git a/cbits/test/aes_bridge.c b/cbits/test/aes_bridge.c
new file mode 100644
--- /dev/null
+++ b/cbits/test/aes_bridge.c
@@ -0,0 +1,11 @@
+#include <stdint.h>
+
+#include "aes.h"
+
+void kpqc_test_aes256_ecb(uint8_t *output, const uint8_t *key,
+                          const uint8_t *input) {
+    aes256ctx context;
+    aes256_ecb_keyexp(&context, key);
+    aes256_ecb(output, input, 1, &context);
+    aes256_ctx_release(&context);
+}
diff --git a/cbits/third_party/AIMer/LICENSE b/cbits/third_party/AIMer/LICENSE
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022-2026 SAMSUNG SDS
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/cbits/third_party/AIMer/aim3.c b/cbits/third_party/AIMer/aim3.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/aim3.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: MIT
+
+#include "aim3.h"
+#include "field.h"
+#include "hash.h"
+#include "params.h"
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+static void squeeze_invertible_matrix(hash_instance *ctx,
+                                      gf mat[AIM3_NUM_BITS_FIELD])
+{
+  uint8_t buf[AIM3_NUM_BYTES_FIELD];
+  uint64_t ormask, lmask, umask;
+
+  gf matrix_L[AIM3_NUM_BITS_FIELD];
+  gf matrix_U[AIM3_NUM_BITS_FIELD];
+  gf temp = {0,};
+
+  for (size_t row = 0; row < AIM3_NUM_BITS_FIELD; row++)
+  {
+    hash_squeeze(ctx, buf, AIM3_NUM_BYTES_FIELD);
+    gf_from_bytes(temp, buf);
+
+    ormask = ((uint64_t)1) << (row % 64);
+    lmask = ((uint64_t)-1) << (row % 64);
+    umask = ~lmask;
+
+    size_t inter = row / 64;
+    size_t col_word;
+    for (col_word = 0; col_word < inter; col_word++)
+    {
+      // L is zero, U is full
+      matrix_L[row][col_word] = 0;
+      matrix_U[row][col_word] = temp[col_word];
+    }
+    matrix_L[row][inter] = (temp[inter] & lmask) | ormask;
+    matrix_U[row][inter] = (temp[inter] & umask) | ormask;
+    for (col_word = inter + 1; col_word < AIM3_NUM_WORDS_FIELD; col_word++)
+    {
+      // L is full, U is zero
+      matrix_L[row][col_word] = temp[col_word];
+      matrix_U[row][col_word] = 0;
+    }
+  }
+
+  for (size_t i = 0; i < AIM3_NUM_BITS_FIELD; i++)
+  {
+    gf_mat_vec_mul(mat[i], matrix_U[i], (const gf *)matrix_L);
+  }
+}
+
+void aim3_generate_linear(aim_lin_t *lin, const uint8_t iv[AIM3_IV_SIZE])
+{
+  hash_instance ctx;
+
+  hash_init(&ctx);
+  hash_update(&ctx, iv, AIM3_IV_SIZE);
+  hash_final(&ctx);
+
+  for (size_t ell = 0; ell < 2 * AIM3_NUM_INPUT_SBOX; ell++)
+  {
+    squeeze_invertible_matrix(&ctx, lin->mat_A[ell]);
+  }
+
+  for (size_t ell = 0; ell < AIM3_NUM_INPUT_SBOX + 1; ell++)
+  {
+    hash_squeeze(&ctx, (uint8_t *)lin->vec_b[ell], AIM3_NUM_BYTES_FIELD);
+  }
+  hash_ctx_release(&ctx);
+}
+
+int aim3_nozeroinpsb(uint8_t ct[AIM3_NUM_BYTES_FIELD],
+                     const uint8_t pt[AIM3_NUM_BYTES_FIELD],
+                     const uint8_t iv[AIM3_IV_SIZE])
+{
+  aim_lin_t *lin = (aim_lin_t *)malloc(sizeof(aim_lin_t));
+  if (lin == NULL)
+  {
+    // allocation failure
+    return -2;
+  }
+
+  gf state[AIM3_NUM_INPUT_SBOX];
+  gf pt_gf = {0,}, ct_gf = {0,}, tmp = {0,};
+  gf_from_bytes(pt_gf, pt);
+
+  // generate random matrix
+  aim3_generate_linear(lin, iv);
+
+  for (size_t i = 0; i < AIM3_NUM_INPUT_SBOX; i++)
+  {
+    // linear component: affine layer
+    gf_mat_vec_mul(state[i], pt_gf, lin->mat_A[i]);
+    gf_add(state[i], state[i], lin->vec_b[i]);
+
+    // non-linear component: y = x^{2^e-1} + x^{-1} = x^{-1} * (x^2^e + 1)
+    if (gf_is0(state[i]))
+    {
+      free(lin);
+      return -1;
+    }
+    gf_inv(tmp, state[i]);
+    for (size_t e = 0; e < aim3_exponents[i]; e++)
+    {
+      gf_sqr(state[i], state[i]);
+    }
+    state[i][0] ^= 1;
+    gf_mul(state[i], state[i], tmp);
+
+    // linear component: affine layer
+    gf_mat_vec_mul(state[i], state[i],
+                   (const gf *)lin->mat_A[i + AIM3_NUM_INPUT_SBOX]);
+  }
+
+  for (size_t i = 1; i < AIM3_NUM_INPUT_SBOX; i++)
+  {
+    gf_add(state[0], state[0], state[i]);
+  }
+  gf_add(state[0], state[0], lin->vec_b[AIM3_NUM_INPUT_SBOX]);
+
+  // non-linear component: y = x^{2^e-1} + x^{-1} = x^{-1} * (x^2^e + 1)
+  if (gf_is0(state[0]))
+  {
+    free(lin);
+    return -1;
+  }
+  gf_inv(tmp, state[0]);
+  for (size_t e = 0; e < aim3_exponents[AIM3_NUM_INPUT_SBOX]; e++)
+  {
+    gf_sqr(state[0], state[0]);
+  }
+  state[0][0] ^= 1;
+  gf_mul(state[0], state[0], tmp);
+
+  // linear component: feed-forward
+  gf_add(ct_gf, pt_gf, state[0]);
+
+  gf_to_bytes(ct, ct_gf);
+
+  free(lin);
+  return 0;
+}
+
+void aim3_sbox_outputs(gf sbox_outputs[AIM3_NUM_INPUT_SBOX+1],
+                       const aim_lin_t* lin,
+                       const gf pt, const gf ct)
+{
+  gf tmp = {0,};
+  for (size_t i = 0; i < AIM3_NUM_INPUT_SBOX; i ++)
+  {
+    // linear component: affine layer
+    gf_mat_vec_mul(sbox_outputs[i], pt, lin->mat_A[i]);
+    gf_add(sbox_outputs[i], sbox_outputs[i], lin->vec_b[i]);
+
+    // non-linear component: y = x^{2^e-1} + x^{-1} = x^{-1} * (x^2^e + 1)
+    gf_inv(tmp, sbox_outputs[i]);
+    for (size_t e = 0; e < aim3_exponents[i]; e++)
+    {
+      gf_sqr(sbox_outputs[i], sbox_outputs[i]);
+    }
+    sbox_outputs[i][0] ^= 1;
+    gf_mul(sbox_outputs[i], sbox_outputs[i], tmp);
+  }
+  gf_add(sbox_outputs[AIMER_L], pt, ct);
+}
+
+void aim3_mpc(mult_chk_t *mult_chk, const aim_lin_t *lin,
+              const tape_t *tape, const gf ct_gf,
+              size_t party)
+{
+  for (size_t i = 0; i < AIMER_L + 1; i++)
+  {
+    gf_copy(mult_chk->a_shares[i], tape->a_shares[i]);
+  }
+
+  // b_shares[i] = share of sbox outputs
+  for (size_t i = 0; i < AIMER_L; i++)
+  {
+    gf_copy(mult_chk->b_shares[i], tape->y_shares[i]);
+  }
+  gf_copy(mult_chk->b_shares[AIMER_L], tape->pt_share);
+  if (party == AIMER_N - 1)
+  {
+    gf_add(mult_chk->b_shares[AIMER_L], mult_chk->b_shares[AIMER_L], ct_gf);
+  }
+
+  gf_copy(mult_chk->c_share, tape->c_share);
+
+  // Set x_shares (= share of sbox inputs)
+  for (size_t i = 0; i < AIMER_L; i++)
+  {
+    gf_mat_vec_mul(mult_chk->x_shares[i], tape->pt_share, lin->mat_A[i]);
+    if (party == AIMER_N - 1)
+    {
+      gf_add(mult_chk->x_shares[i], mult_chk->x_shares[i], lin->vec_b[i]);
+    }
+  }
+
+  gf_set0(mult_chk->x_shares[AIMER_L]);
+  for (size_t ell = 0; ell < AIMER_L; ell++)
+  {
+    gf_mat_vec_mul_add(mult_chk->x_shares[AIMER_L], tape->y_shares[ell],
+                       lin->mat_A[ell+AIMER_L]);
+  }
+  if (party == AIMER_N - 1)
+  {
+    gf_add(mult_chk->x_shares[AIMER_L], mult_chk->x_shares[AIMER_L],
+           lin->vec_b[AIMER_L]);
+  }
+
+  // Set z_shares: z[i] = x[i]^{2^e} + 1
+  // y = x^{-1} + x^{2^e-1} -> xy = x^{2^e} + 1
+  for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+  {
+    gf_sqr(mult_chk->z_shares[ell], mult_chk->x_shares[ell]);
+    for (size_t i = 1; i < aim3_exponents[ell]; i++)
+    {
+      gf_sqr(mult_chk->z_shares[ell], mult_chk->z_shares[ell]);
+    }
+    if (party == AIMER_N - 1)
+    {
+      mult_chk->z_shares[ell][0] ^= 1;
+    }
+  }
+}
diff --git a/cbits/third_party/AIMer/aim3.h b/cbits/third_party/AIMer/aim3.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/aim3.h
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef AIM3_H
+#define AIM3_H
+
+#include "field.h"
+#include "params.h"
+#include "sign.h"
+#include <stdint.h>
+
+typedef struct mult_chk_t
+{
+  gf x_shares[AIMER_L + 1];
+  gf z_shares[AIMER_L + 1];
+  gf a_shares[AIMER_L + 1];
+  gf b_shares[AIMER_L + 1]; // b_shares[i] = y_shares[i]
+  gf c_share;
+} mult_chk_t;
+
+typedef struct aim_lin_t
+{
+  gf mat_A[2 * AIM3_NUM_INPUT_SBOX][AIM3_NUM_BITS_FIELD];
+  gf vec_b[AIM3_NUM_INPUT_SBOX + 1];
+} aim_lin_t;
+
+#if SECURITY_BITS == 128
+static const size_t aim3_exponents[AIM3_NUM_INPUT_SBOX + 1] =
+{
+  3, 7, 11
+};
+
+#elif SECURITY_BITS == 192
+static const size_t aim3_exponents[AIM3_NUM_INPUT_SBOX + 1] =
+{
+  11, 23, 47
+};
+
+#else
+static const size_t aim3_exponents[AIM3_NUM_INPUT_SBOX + 1] =
+{
+  3, 7, 11, 19
+};
+#endif
+
+#define aim3_generate_linear AIMER_NAMESPACE(aim3_generate_linear)
+void aim3_generate_linear(aim_lin_t *lin, const uint8_t iv[AIM3_IV_SIZE]);
+
+#define aim3_sbox_outputs AIMER_NAMESPACE(aim3_sbox_outputs)
+void aim3_sbox_outputs(gf sbox_outputs[AIM3_NUM_INPUT_SBOX+1],
+                       const aim_lin_t *lin,
+                       const gf pt, const gf ct);
+
+#define aim3_nozeroinpsb AIMER_NAMESPACE(aim3_nozeroinpsb)
+int aim3_nozeroinpsb(uint8_t ct[AIM3_NUM_BYTES_FIELD],
+                     const uint8_t pt[AIM3_NUM_BYTES_FIELD],
+                     const uint8_t iv[AIM3_IV_SIZE]);
+
+#define aim3_mpc AIMER_NAMESPACE(aim3_mpc)
+void aim3_mpc(mult_chk_t *mult_chk, const aim_lin_t *lin,
+              const tape_t *tape, const gf ct_gf,
+              size_t party);
+
+#endif // AIM3_H
diff --git a/cbits/third_party/AIMer/api.h b/cbits/third_party/AIMer/api.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/api.h
@@ -0,0 +1,42 @@
+/*
+NIST-developed software is provided by NIST as a public service. You may use, copy, and distribute copies of the software in any medium, provided that you keep intact this entire notice. You may improve, modify, and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the source of the software.
+
+NIST-developed software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE.
+
+You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. The software developed by NIST employees is not subject to copyright protection within the United States.
+*/
+
+#ifndef API_H
+#define API_H
+
+#include "params.h"
+#include <stddef.h>
+#include <stdint.h>
+
+#define CRYPTO_PUBLICKEYBYTES AIMER_PK_BYTES
+#define CRYPTO_SECRETKEYBYTES AIMER_SK_BYTES
+#define CRYPTO_BYTES AIMER_SIG_BYTES
+#define CRYPTO_ALGNAME "aimer-" xstr(PARAMS)
+
+#define crypto_sign_keypair AIMER_NAMESPACE(crypto_sign_keypair)
+int crypto_sign_keypair(uint8_t *pk, uint8_t *sk);
+
+#define crypto_sign_signature AIMER_NAMESPACE(crypto_sign_signature)
+int crypto_sign_signature(uint8_t *sig, size_t *siglen,
+                          const uint8_t *m, size_t mlen,
+                          const uint8_t *ctx, size_t ctxlen, const uint8_t *sk);
+
+#define crypto_sign AIMER_NAMESPACE(crypto_sign)
+int crypto_sign(uint8_t *sm, size_t *smlen, const uint8_t *m, size_t mlen,
+                const uint8_t *ctx, size_t ctxlen, const uint8_t *sk);
+
+#define crypto_sign_verify AIMER_NAMESPACE(crypto_sign_verify)
+int crypto_sign_verify(const uint8_t *sig, size_t siglen,
+                       const uint8_t *m, size_t mlen,
+                       const uint8_t *ctx, size_t ctxlen, const uint8_t *pk);
+
+#define crypto_sign_open AIMER_NAMESPACE(crypto_sign_open)
+int crypto_sign_open(uint8_t *m, size_t *mlen, const uint8_t *sm, size_t smlen,
+                     const uint8_t *ctx, size_t ctxlen, const uint8_t *pk);
+
+#endif
diff --git a/cbits/third_party/AIMer/common/crypto_declassify.h b/cbits/third_party/AIMer/common/crypto_declassify.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/common/crypto_declassify.h
@@ -0,0 +1,7 @@
+#ifndef PQCLEAN_CRYPTO_DECLASSIFY
+#define PQCLEAN_CRYPTO_DECLASSIFY
+
+/* EMPTY MACRO DEFINITION */
+#define crypto_declassify(x, y)
+
+#endif
diff --git a/cbits/third_party/AIMer/common/fips202.c b/cbits/third_party/AIMer/common/fips202.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/common/fips202.c
@@ -0,0 +1,878 @@
+/* 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 <string.h>
+
+#include "fips202.h"
+
+#define NROUNDS 24
+#define ROL(a, offset) (((a) << (offset)) ^ ((a) >> (64 - (offset))))
+
+static void clear_state(uint64_t *state, size_t words) {
+    volatile uint64_t *p = state;
+    while (words-- > 0) {
+        *p++ = 0;
+    }
+}
+
+/*************************************************
+ * 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) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+void shake128_inc_ctx_release(shake128incctx *state) {
+    clear_state(state->ctx, 26);
+}
+
+void shake256_inc_init(shake256incctx *state) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+void shake256_inc_ctx_release(shake256incctx *state) {
+    clear_state(state->ctx, 26);
+}
+
+/*************************************************
+ * 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) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/** Release the allocated state. Call only once. */
+void shake128_ctx_release(shake128ctx *state) {
+    clear_state(state->ctx, 25);
+}
+
+/*************************************************
+ * 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) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/** Release the allocated state. Call only once. */
+void shake256_ctx_release(shake256ctx *state) {
+    clear_state(state->ctx, 25);
+}
+
+/*************************************************
+ * 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) {
+    keccak_inc_init(state->ctx);
+}
+
+void sha3_256_inc_ctx_clone(sha3_256incctx *dest, const sha3_256incctx *src) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+void sha3_256_inc_ctx_release(sha3_256incctx *state) {
+    clear_state(state->ctx, 26);
+}
+
+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) {
+    keccak_inc_init(state->ctx);
+}
+
+void sha3_384_inc_ctx_clone(sha3_384incctx *dest, const sha3_384incctx *src) {
+    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) {
+    clear_state(state->ctx, 26);
+}
+
+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) {
+    keccak_inc_init(state->ctx);
+}
+
+void sha3_512_inc_ctx_clone(sha3_512incctx *dest, const sha3_512incctx *src) {
+    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) {
+    clear_state(state->ctx, 26);
+}
+
+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];
+    }
+}
diff --git a/cbits/third_party/AIMer/common/fips202.h b/cbits/third_party/AIMer/common/fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/common/fips202.h
@@ -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[26];
+} shake128incctx;
+
+// Context for non-incremental API
+typedef struct {
+    uint64_t ctx[25];
+} shake128ctx;
+
+// Context for incremental API
+typedef struct {
+    uint64_t ctx[26];
+} shake256incctx;
+
+// Context for non-incremental API
+typedef struct {
+    uint64_t ctx[25];
+} shake256ctx;
+
+// Context for incremental API
+typedef struct {
+    uint64_t ctx[26];
+} sha3_256incctx;
+
+// Context for incremental API
+typedef struct {
+    uint64_t ctx[26];
+} sha3_384incctx;
+
+// Context for incremental API
+typedef struct {
+    uint64_t ctx[26];
+} 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
diff --git a/cbits/third_party/AIMer/common/rng.h b/cbits/third_party/AIMer/common/rng.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/common/rng.h
@@ -0,0 +1,56 @@
+/*
+NIST-developed software is provided by NIST as a public service. You may use, copy, and distribute copies of the software in any medium, provided that you keep intact this entire notice. You may improve, modify, and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the source of the software.
+
+NIST-developed software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE.
+
+You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. The software developed by NIST employees is not subject to copyright protection within the United States.
+*/
+
+#ifndef rng_h
+#define rng_h
+
+#include <stdio.h>
+
+#define RNG_SUCCESS      0
+#define RNG_BAD_MAXLEN  -1
+#define RNG_BAD_OUTBUF  -2
+#define RNG_BAD_REQ_LEN -3
+
+typedef struct {
+    unsigned char   buffer[16];
+    int             buffer_pos;
+    unsigned long   length_remaining;
+    unsigned char   key[32];
+    unsigned char   ctr[16];
+} AES_XOF_struct;
+
+typedef struct {
+    unsigned char   Key[32];
+    unsigned char   V[16];
+    int             reseed_counter;
+} AES256_CTR_DRBG_struct;
+
+
+void
+AES256_CTR_DRBG_Update(unsigned char *provided_data,
+                       unsigned char *Key,
+                       unsigned char *V);
+
+int
+seedexpander_init(AES_XOF_struct *ctx,
+                  unsigned char *seed,
+                  unsigned char *diversifier,
+                  unsigned long maxlen);
+
+int
+seedexpander(AES_XOF_struct *ctx, unsigned char *x, unsigned long xlen);
+
+void
+randombytes_init(unsigned char *entropy_input,
+                 unsigned char *personalization_string,
+                 int security_strength);
+
+int
+randombytes(unsigned char *x, unsigned long long xlen);
+
+#endif /* rng_h */
diff --git a/cbits/third_party/AIMer/field.h b/cbits/third_party/AIMer/field.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/field.h
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef FIELD_H
+#define FIELD_H
+
+#include "params.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
+    __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#error "AIMer reference implementation assumes a little-endian target"
+#endif
+
+#if SECURITY_BITS == 128
+typedef uint64_t gf[2];
+#elif SECURITY_BITS == 192
+typedef uint64_t gf[3];
+#else
+typedef uint64_t gf[4];
+#endif
+
+#define gf_set0 AIMER_NAMESPACE(gf_set0)
+void gf_set0(gf a);
+#define gf_is0 AIMER_NAMESPACE(gf_is0)
+bool gf_is0(const gf a);
+#define gf_copy AIMER_NAMESPACE(gf_copy)
+void gf_copy(gf out, const gf in);
+#define gf_to_bytes AIMER_NAMESPACE(gf_to_bytes)
+void gf_to_bytes(uint8_t *out, const gf in);
+#define gf_from_bytes AIMER_NAMESPACE(gf_from_bytes)
+void gf_from_bytes(gf out, const uint8_t *in);
+
+#define gf_add AIMER_NAMESPACE(gf_add)
+void gf_add(gf c, const gf a, const gf b);
+#define gf_mul AIMER_NAMESPACE(gf_mul)
+void gf_mul(gf c, const gf a, const gf b);
+#define gf_mul_add AIMER_NAMESPACE(gf_mul_add)
+void gf_mul_add(gf c, const gf a, const gf b);
+#define gf_sqr AIMER_NAMESPACE(gf_sqr)
+void gf_sqr(gf c, const gf a);
+#define gf_inv AIMER_NAMESPACE(gf_inv)
+void gf_inv(gf c, const gf a);
+
+#define gf_mat_vec_mul AIMER_NAMESPACE(gf_mat_vec_mul)
+void gf_mat_vec_mul(gf c, const gf a, const gf b[AIM3_NUM_BITS_FIELD]);
+#define gf_mat_vec_mul_add AIMER_NAMESPACE(gf_mat_vec_mul_add)
+void gf_mat_vec_mul_add(gf c, const gf a, const gf b[AIM3_NUM_BITS_FIELD]);
+
+#endif // FIELD_H
diff --git a/cbits/third_party/AIMer/field128.c b/cbits/third_party/AIMer/field128.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/field128.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: MIT
+
+#include "field.h"
+#include "field_internal.h"
+#include <stddef.h>
+#include <stdint.h>
+
+static void gf_mod(gf c, const uint64_t a[2 * AIM3_NUM_WORDS_FIELD])
+{
+  uint64_t t = a[2] ^ ((a[3] >> 57) ^ (a[3] >> 62) ^ (a[3] >> 63));
+
+  c[1] = a[1] ^ a[3];
+  c[1] ^= (a[3] << 7) | (t >> 57);
+  c[1] ^= (a[3] << 2) | (t >> 62);
+  c[1] ^= (a[3] << 1) | (t >> 63);
+
+  c[0] = a[0] ^ t;
+  c[0] ^= (t << 7);
+  c[0] ^= (t << 2);
+  c[0] ^= (t << 1);
+}
+
+void gf_mul(gf c, const gf a, const gf b)
+{
+  uint64_t t[2] = {0,};
+  uint64_t temp[4] = {0,};
+
+  poly64_mul(&temp[3], &temp[2], a[1], b[1]);
+  poly64_mul(&temp[1], &temp[0], a[0], b[0]);
+
+  poly64_mul(&t[1], &t[0], (a[0] ^ a[1]), (b[0] ^ b[1]));
+  temp[1] ^= t[0] ^ temp[0] ^ temp[2];
+  temp[2] = t[0] ^ t[1] ^ temp[0] ^ temp[1] ^ temp[3];
+
+  gf_mod(c, temp);
+}
+
+void gf_sqr(gf c, const gf a)
+{
+  uint64_t temp[4] = {0,};
+
+  poly64_sqr(&temp[1], &temp[0], a[0]);
+  poly64_sqr(&temp[3], &temp[2], a[1]);
+
+  gf_mod(c, temp);
+}
+
+// c = sum_i a[i] * b[i]
+void gf_mat_vec_mul(gf c, const gf a, const gf b[AIM3_NUM_BITS_FIELD])
+{
+  const uint64_t *a_ptr = a;
+  const gf *b_ptr = b;
+
+  uint64_t temp_c0 = 0;
+  uint64_t temp_c1 = 0;
+  uint64_t mask;
+  for (size_t i = AIM3_NUM_WORDS_FIELD; i; --i, ++a_ptr)
+  {
+    uint64_t index = *a_ptr;
+    for (size_t j = AIM3_NUM_BITS_WORD; j; j -= 8, index >>= 8, b_ptr += 8)
+    {
+      mask = 0U - (index & 1);
+      temp_c0 ^= (b_ptr[0][0] & mask);
+      temp_c1 ^= (b_ptr[0][1] & mask);
+
+      mask = 0U - ((index >> 1) & 1);
+      temp_c0 ^= (b_ptr[1][0] & mask);
+      temp_c1 ^= (b_ptr[1][1] & mask);
+
+      mask = 0U - ((index >> 2) & 1);
+      temp_c0 ^= (b_ptr[2][0] & mask);
+      temp_c1 ^= (b_ptr[2][1] & mask);
+
+      mask = 0U - ((index >> 3) & 1);
+      temp_c0 ^= (b_ptr[3][0] & mask);
+      temp_c1 ^= (b_ptr[3][1] & mask);
+
+      mask = 0U - ((index >> 4) & 1);
+      temp_c0 ^= (b_ptr[4][0] & mask);
+      temp_c1 ^= (b_ptr[4][1] & mask);
+
+      mask = 0U - ((index >> 5) & 1);
+      temp_c0 ^= (b_ptr[5][0] & mask);
+      temp_c1 ^= (b_ptr[5][1] & mask);
+
+      mask = 0U - ((index >> 6) & 1);
+      temp_c0 ^= (b_ptr[6][0] & mask);
+      temp_c1 ^= (b_ptr[6][1] & mask);
+
+      mask = 0U - ((index >> 7) & 1);
+      temp_c0 ^= (b_ptr[7][0] & mask);
+      temp_c1 ^= (b_ptr[7][1] & mask);
+    }
+  }
+  c[0] = temp_c0;
+  c[1] = temp_c1;
+}
diff --git a/cbits/third_party/AIMer/field192.c b/cbits/third_party/AIMer/field192.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/field192.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: MIT
+
+#include "field.h"
+#include "field_internal.h"
+#include <stddef.h>
+#include <stdint.h>
+
+static void gf_mod(gf c, const uint64_t a[2 * AIM3_NUM_WORDS_FIELD])
+{
+  uint64_t t = a[3] ^ ((a[5] >> 57) ^ (a[5] >> 62) ^ (a[5] >> 63));
+
+  c[2] = a[2] ^ a[5];
+  c[2] ^= (a[5] << 7) | (a[4] >> 57);
+  c[2] ^= (a[5] << 2) | (a[4] >> 62);
+  c[2] ^= (a[5] << 1) | (a[4] >> 63);
+
+  c[1] = a[1] ^ a[4];
+  c[1] ^= (a[4] << 7) | (t >> 57);
+  c[1] ^= (a[4] << 2) | (t >> 62);
+  c[1] ^= (a[4] << 1) | (t >> 63);
+
+  c[0] = a[0] ^ t;
+  c[0] ^= (t << 7);
+  c[0] ^= (t << 2);
+  c[0] ^= (t << 1);
+}
+
+void gf_mul(gf c, const gf a, const gf b)
+{
+  uint64_t t[3] = {0,};
+  uint64_t temp[6] = {0,};
+
+  poly64_mul(&t[0], &temp[0], a[0], b[0]);
+  poly64_mul(&t[2], &t[1], a[1], b[1]);
+  t[0] ^= t[1];
+
+  poly64_mul(&temp[5], &t[1], a[2], b[2]);
+  t[1] ^= t[2];
+
+  temp[1] = t[0] ^ temp[0];
+  temp[2] = t[1] ^ temp[1];
+  temp[4] = temp[5] ^ t[1];
+  temp[3] = temp[4] ^ t[0];
+
+  poly64_mul(&t[1], &t[0], (a[0] ^ a[1]), (b[0] ^ b[1]));
+  temp[1] ^= t[0];
+  temp[2] ^= t[1];
+
+  poly64_mul(&t[1], &t[0], (a[0] ^ a[2]), (b[0] ^ b[2]));
+  temp[2] ^= t[0];
+  temp[3] ^= t[1];
+
+  poly64_mul(&t[1], &t[0], (a[1] ^ a[2]), (b[1] ^ b[2]));
+  temp[3] ^= t[0];
+  temp[4] ^= t[1];
+
+  gf_mod(c, temp);
+}
+
+void gf_sqr(gf c, const gf a)
+{
+  uint64_t temp[6] = {0,};
+
+  poly64_sqr(&temp[1], &temp[0], a[0]);
+  poly64_sqr(&temp[3], &temp[2], a[1]);
+  poly64_sqr(&temp[5], &temp[4], a[2]);
+
+  gf_mod(c, temp);
+}
+
+void gf_mat_vec_mul(gf c, const gf a, const gf b[AIM3_NUM_BITS_FIELD])
+{
+  const uint64_t *a_ptr = a;
+  const gf *b_ptr = b;
+
+  uint64_t temp_c0 = 0;
+  uint64_t temp_c1 = 0;
+  uint64_t temp_c2 = 0;
+  uint64_t mask;
+  for (size_t i = AIM3_NUM_WORDS_FIELD; i; --i, ++a_ptr)
+  {
+    uint64_t index = *a_ptr;
+    for (size_t j = AIM3_NUM_BITS_WORD; j; j -= 4, index >>= 4, b_ptr += 4)
+    {
+      mask = 0U - (index & 1);
+      temp_c0 ^= (b_ptr[0][0] & mask);
+      temp_c1 ^= (b_ptr[0][1] & mask);
+      temp_c2 ^= (b_ptr[0][2] & mask);
+
+      mask = 0U - ((index >> 1) & 1);
+      temp_c0 ^= (b_ptr[1][0] & mask);
+      temp_c1 ^= (b_ptr[1][1] & mask);
+      temp_c2 ^= (b_ptr[1][2] & mask);
+
+      mask = 0U - ((index >> 2) & 1);
+      temp_c0 ^= (b_ptr[2][0] & mask);
+      temp_c1 ^= (b_ptr[2][1] & mask);
+      temp_c2 ^= (b_ptr[2][2] & mask);
+
+      mask = 0U - ((index >> 3) & 1);
+      temp_c0 ^= (b_ptr[3][0] & mask);
+      temp_c1 ^= (b_ptr[3][1] & mask);
+      temp_c2 ^= (b_ptr[3][2] & mask);
+    }
+  }
+  c[0] = temp_c0;
+  c[1] = temp_c1;
+  c[2] = temp_c2;
+}
diff --git a/cbits/third_party/AIMer/field256.c b/cbits/third_party/AIMer/field256.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/field256.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: MIT
+
+#include "field.h"
+#include "field_internal.h"
+#include <stddef.h>
+#include <stdint.h>
+
+static void gf_mod(gf c, const uint64_t a[2 * AIM3_NUM_WORDS_FIELD])
+{
+  uint64_t t = a[4] ^ ((a[7] >> 54) ^ (a[7] >> 59) ^ (a[7] >> 62));
+
+  c[3] = a[3] ^ a[7];
+  c[3] ^= (a[7] << 10) | (a[6] >> 54);
+  c[3] ^= (a[7] <<  5) | (a[6] >> 59);
+  c[3] ^= (a[7] <<  2) | (a[6] >> 62);
+
+  c[2] = a[2] ^ a[6];
+  c[2] ^= (a[6] << 10) | (a[5] >> 54);
+  c[2] ^= (a[6] <<  5) | (a[5] >> 59);
+  c[2] ^= (a[6] <<  2) | (a[5] >> 62);
+
+  c[1] = a[1] ^ a[5];
+  c[1] ^= (a[5] << 10) | (t >> 54);
+  c[1] ^= (a[5] <<  5) | (t >> 59);
+  c[1] ^= (a[5] <<  2) | (t >> 62);
+
+  c[0] = a[0] ^ t;
+  c[0] ^= (t << 10);
+  c[0] ^= (t <<  5);
+  c[0] ^= (t <<  2);
+}
+
+void gf_mul(gf c, const gf a, const gf b)
+{
+  uint64_t t[4] = {0,};
+  uint64_t add[4] = {0,};
+  uint64_t temp[8] = {0,};
+
+  poly64_mul(&t[0], &temp[0], a[0], b[0]);
+  poly64_mul(&t[2], &t[1], a[1], b[1]);
+  t[0] ^= t[1];
+
+  poly64_mul(&t[3], &t[1], a[2], b[2]);
+  t[1] ^= t[2];
+
+  poly64_mul(&temp[7], &t[2], a[3], b[3]);
+  t[2] ^= t[3];
+
+  temp[6] = temp[7] ^ t[2];
+  temp[3] = t[2] ^ t[1];
+  temp[2] = t[1] ^ t[0];
+  temp[1] = t[0] ^ temp[0];
+
+  poly64_mul(&t[1], &t[0], (a[0] ^ a[1]), (b[0] ^ b[1]));
+  temp[1] ^= t[0];
+  temp[2] ^= t[1];
+
+  poly64_mul(&t[1], &t[0], (a[2] ^ a[3]), (b[2] ^ b[3]));
+  temp[3] ^= t[0];
+  temp[6] ^= t[1];
+
+  temp[5] = temp[7] ^ temp[3];
+  temp[4] = temp[6] ^ temp[2];
+  temp[3] ^= temp[1];
+  temp[2] ^= temp[0];
+
+  add[0] = a[0] ^ a[2];
+  add[1] = a[1] ^ a[3];
+  add[2] = b[0] ^ b[2];
+  add[3] = b[1] ^ b[3];
+  poly64_mul(&t[1], &t[0], add[0], add[2]);
+  poly64_mul(&t[3], &t[2], add[1], add[3]);
+  t[1] ^= t[2];
+  t[2] = t[1] ^ t[3];
+  t[1] ^= t[0];
+
+  temp[2] ^= t[0];
+  temp[3] ^= t[1];
+  temp[4] ^= t[2];
+  temp[5] ^= t[3];
+
+  poly64_mul(&t[1], &t[0], (add[0] ^ add[1]), (add[2] ^ add[3]));
+  temp[3] ^= t[0];
+  temp[4] ^= t[1];
+
+  gf_mod(c, temp);
+}
+
+void gf_sqr(gf c, const gf a)
+{
+  uint64_t temp[8] = {0,};
+
+  poly64_sqr(&temp[1], &temp[0], a[0]);
+  poly64_sqr(&temp[3], &temp[2], a[1]);
+  poly64_sqr(&temp[5], &temp[4], a[2]);
+  poly64_sqr(&temp[7], &temp[6], a[3]);
+
+  gf_mod(c, temp);
+}
+
+void gf_mat_vec_mul(gf c, const gf a, const gf b[AIM3_NUM_BITS_FIELD])
+{
+  const uint64_t *a_ptr = a;
+  const gf *b_ptr = b;
+
+  uint64_t temp_c0 = 0;
+  uint64_t temp_c1 = 0;
+  uint64_t temp_c2 = 0;
+  uint64_t temp_c3 = 0;
+  uint64_t mask;
+  for (size_t i = AIM3_NUM_WORDS_FIELD; i; --i, ++a_ptr)
+  {
+    uint64_t index = *a_ptr;
+    for (size_t j = AIM3_NUM_BITS_WORD; j; j -= 4, index >>= 4, b_ptr += 4)
+    {
+      mask = 0U - (index & 1);
+      temp_c0 ^= (b_ptr[0][0] & mask);
+      temp_c1 ^= (b_ptr[0][1] & mask);
+      temp_c2 ^= (b_ptr[0][2] & mask);
+      temp_c3 ^= (b_ptr[0][3] & mask);
+
+      mask = 0U - ((index >> 1) & 1);
+      temp_c0 ^= (b_ptr[1][0] & mask);
+      temp_c1 ^= (b_ptr[1][1] & mask);
+      temp_c2 ^= (b_ptr[1][2] & mask);
+      temp_c3 ^= (b_ptr[1][3] & mask);
+
+      mask = 0U - ((index >> 2) & 1);
+      temp_c0 ^= (b_ptr[2][0] & mask);
+      temp_c1 ^= (b_ptr[2][1] & mask);
+      temp_c2 ^= (b_ptr[2][2] & mask);
+      temp_c3 ^= (b_ptr[2][3] & mask);
+
+      mask = 0U - ((index >> 3) & 1);
+      temp_c0 ^= (b_ptr[3][0] & mask);
+      temp_c1 ^= (b_ptr[3][1] & mask);
+      temp_c2 ^= (b_ptr[3][2] & mask);
+      temp_c3 ^= (b_ptr[3][3] & mask);
+    }
+  }
+  c[0] = temp_c0;
+  c[1] = temp_c1;
+  c[2] = temp_c2;
+  c[3] = temp_c3;
+}
diff --git a/cbits/third_party/AIMer/field_common.c b/cbits/third_party/AIMer/field_common.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/field_common.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: MIT
+
+#include "field.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+void gf_to_bytes(uint8_t *out, const gf in)
+{
+  for (size_t i = 0; i < AIM3_NUM_WORDS_FIELD; i++)
+  {
+    uint64_t u = in[i];
+    for (size_t j = 0; j < 8; j++)
+    {
+      out[8 * i + j] = (uint8_t)u;
+      u >>= 8;
+    }
+  }
+}
+
+void gf_from_bytes(gf out, const uint8_t *in)
+{
+  for (size_t i = 0; i < AIM3_NUM_WORDS_FIELD; i++)
+  {
+    uint64_t u = 0;
+    for (int j = 7; j >= 0; j--)
+    {
+      u = (u << 8) | in[8 * i + j];
+    }
+    out[i] = u;
+  }
+}
+
+void gf_set0(gf a)
+{
+  for (size_t i = 0; i < AIM3_NUM_WORDS_FIELD; i++)
+  {
+    a[i] = 0;
+  }
+}
+
+bool gf_is0(const gf a)
+{
+  uint64_t result = 0;
+  for (size_t i = 0; i < AIM3_NUM_WORDS_FIELD; i++)
+  {
+    result |= a[i];
+  }
+  return result == 0;
+}
+
+void gf_copy(gf out, const gf in)
+{
+  for (size_t i = 0; i < AIM3_NUM_WORDS_FIELD; i++)
+  {
+    out[i] = in[i];
+  }
+}
+
+void gf_add(gf c, const gf a, const gf b)
+{
+  for (size_t i = 0; i < AIM3_NUM_WORDS_FIELD; i++)
+  {
+    c[i] = a[i] ^ b[i];
+  }
+}
+
+void gf_inv(gf c, const gf a)
+{
+  gf temp;
+
+  gf_copy(temp, a);
+  gf_set0(c);
+  c[0] = 1;
+
+  // c = a^{2^n - 2} = prod_{i = 1}^{n-1} a^{2^i}
+  for (size_t i = 1; i < AIM3_NUM_BITS_FIELD; i++)
+  {
+    gf_sqr(temp, temp); // temp = a^{2^i}
+    gf_mul(c, c, temp);
+  }
+}
+
+// c += a * b
+void gf_mul_add(gf c, const gf a, const gf b)
+{
+  gf r;
+  gf_mul(r, a, b);
+  gf_add(c, c, r);
+}
+
+// c += sum_i a[i] * b[i]
+void gf_mat_vec_mul_add(gf c, const gf a, const gf b[AIM3_NUM_BITS_FIELD])
+{
+  gf r;
+  gf_mat_vec_mul(r, a, b);
+  gf_add(c, c, r);
+}
diff --git a/cbits/third_party/AIMer/field_internal.h b/cbits/third_party/AIMer/field_internal.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/field_internal.h
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef FIELD_INTERNAL_H
+#define FIELD_INTERNAL_H
+
+#include <stdint.h>
+
+static inline void poly64_mul(uint64_t *z1, uint64_t *z0,
+                              uint64_t x0, uint64_t y0)
+{
+  const uint64_t C0 = 0x5555555555555555;
+  const uint64_t C1 = 0x3333333333333333;
+  const uint64_t C2 = 0x0f0f0f0f0f0f0f0f;
+  const uint64_t C3 = 0x00ff00ff00ff00ff;
+  const uint64_t C4 = 0x0000ffff0000ffff;
+  const uint64_t C5 = 0x00000000ffffffff;
+  uint64_t x1, x2, x3, x4, x5, x6, x7;
+  uint64_t y1, y2, y3, y4, y5, y6, y7;
+  uint64_t f0, f1, f2, f3, f4, f5, f6, f7;
+  uint64_t g0, g1, g2, g3, g4, g5, g6, g7;
+  uint64_t s, t;
+
+  x1 = x0 ^ (x0 >> 32); // will ignore outside C5
+  x2 = ((x0 ^ (x0 >> 16)) & C4) | ((x1 ^ (x1 << 16)) & (C4 << 16));
+  x3 = ((x0 ^ (x0 >> 8)) & C3) | ((x1 ^ (x1 <<  8)) & (C3 <<  8));
+  x4 = x2 ^ (x2 >> 8);  // will ignore outside C3
+  x5 = ((x0 ^ (x0 >> 4)) & C2) | ((x1 ^ (x1 <<  4)) & (C2 <<  4));
+  x6 = ((x2 ^ (x2 >> 4)) & C2) | ((x3 ^ (x3 <<  4)) & (C2 <<  4));
+  x7 = x4 ^ (x4 >> 4);  // will ignore outside C2
+  s = ((x0 >> 2) ^ x2) & C1;
+  x0 ^= s << 2;
+  x2 ^= s;
+  s = ((x1 >> 2) ^ x3) & C1;
+  x1 ^= s << 2;
+  x3 ^= s;
+  s = ((x4 >> 2) ^ x6) & C1;
+  x4 ^= s << 2;
+  x6 ^= s;
+  s = ((x5 >> 2) ^ x7) & C1;
+  x5 ^= s << 2;
+  x7 ^= s;
+  s = ((x0 >> 1) ^ x1) & C0;
+  x0 ^= s << 1;
+  x1 ^= s;
+  s = ((x2 >> 1) ^ x3) & C0;
+  x2 ^= s << 1;
+  x3 ^= s;
+  s = ((x4 >> 1) ^ x5) & C0;
+  x4 ^= s << 1;
+  x5 ^= s;
+  s = ((x6 >> 1) ^ x7) & C0;
+  x6 ^= s << 1;
+  x7 ^= s;
+
+  y1 = y0 ^ (y0 >> 32);
+  y2 = ((y0 ^ (y0 >> 16)) & C4) | ((y1 ^ (y1 << 16)) & (C4 << 16));
+  y3 = ((y0 ^ (y0 >> 8)) & C3) | ((y1 ^ (y1 << 8)) & (C3 << 8));
+  y4 = y2 ^ (y2 >> 8);
+  y5 = ((y0 ^ (y0 >> 4)) & C2) | ((y1 ^ (y1 << 4)) & (C2 << 4));
+  y6 = ((y2 ^ (y2 >> 4)) & C2) | ((y3 ^ (y3 << 4)) & (C2 << 4));
+  y7 = y4 ^ (y4 >> 4);
+  s = ((y0 >> 2) ^ y2) & C1;
+  y0 ^= s << 2;
+  y2 ^= s;
+  s = ((y1 >> 2) ^ y3) & C1;
+  y1 ^= s << 2;
+  y3 ^= s;
+  s = ((y4 >> 2) ^ y6) & C1;
+  y4 ^= s << 2;
+  y6 ^= s;
+  s = ((y5 >> 2) ^ y7) & C1;
+  y5 ^= s << 2;
+  y7 ^= s;
+  s = ((y0 >> 1) ^ y1) & C0;
+  y0 ^= s << 1;
+  y1 ^= s;
+  s = ((y2 >> 1) ^ y3) & C0;
+  y2 ^= s << 1;
+  y3 ^= s;
+  s = ((y4 >> 1) ^ y5) & C0;
+  y4 ^= s << 1;
+  y5 ^= s;
+  s = ((y6 >> 1) ^ y7) & C0;
+  y6 ^= s << 1;
+  y7 ^= s;
+
+  f0 = x0 & y0;
+  f1 = (x0 & y1) ^ (x1 & y0);
+  f2 = (x0 & y2) ^ (x1 & y1) ^ (x2 & y0);
+  f3 = (x0 & y3) ^ (x1 & y2) ^ (x2 & y1) ^ (x3 & y0);
+  g0 = (x1 & y3) ^ (x2 & y2) ^ (x3 & y1);
+  g1 = (x2 & y3) ^ (x3 & y2);
+  g2 = x3 & y3;
+
+  f4 = x4 & y4;
+  f5 = (x4 & y5) ^ (x5 & y4);
+  f6 = (x4 & y6) ^ (x5 & y5) ^ (x6 & y4);
+  f7 = (x4 & y7) ^ (x5 & y6) ^ (x6 & y5) ^ (x7 & y4);
+  g4 = (x5 & y7) ^ (x6 & y6) ^ (x7 & y5);
+  g5 = (x6 & y7) ^ (x7 & y6);
+  g6 = x7 & y7;
+
+  s = ((f0 >> 1) ^ f1) & C0;
+  f0 ^= s << 1;
+  f1 ^= s;
+  s = ((f2 >> 1) ^ f3) & C0;
+  f2 ^= s << 1;
+  f3 ^= s;
+  s = ((f0 >> 2) ^ f2) & C1;
+  f0 ^= s << 2;
+  f2 ^= s;
+  s = ((f1 >> 2) ^ f3) & C1;
+  f1 ^= s << 2;
+  f3 ^= s;
+  s = ((g0 >> 1) ^ g1) & C0;
+  g0 ^= s << 1;
+  g1 ^= s;
+  s = (g2 >> 1) & C0;
+  g2 ^= s << 1;
+  g3 = s;
+  s = ((g0 >> 2) ^ g2) & C1;
+  g0 ^= s << 2;
+  g2 ^= s;
+  s = ((g1 >> 2) ^ g3) & C1;
+  g1 ^= s << 2;
+  g3 ^= s;
+
+  s = ((f4 >> 1) ^ f5) & C0;
+  f4 ^= s << 1;
+  f5 ^= s;
+  s = ((f6 >> 1) ^ f7) & C0;
+  f6 ^= s << 1;
+  f7 ^= s;
+  s = ((f4 >> 2) ^ f6) & C1;
+  f4 ^= s << 2;
+  f6 ^= s;
+  s = ((f5 >> 2) ^ f7) & C1;
+  f5 ^= s << 2;
+  f7 ^= s;
+  s = ((g4 >> 1) ^ g5) & C0;
+  g4 ^= s << 1;
+  g5 ^= s;
+  s = (g6 >> 1) & C0;
+  g6 ^= s << 1;
+  g7 = s;
+  s = ((g4 >> 2) ^ g6) & C1;
+  g4 ^= s << 2;
+  g6 ^= s;
+  s = ((g5 >> 2) ^ g7) & C1;
+  g5 ^= s << 2;
+  g7 ^= s;
+
+  t = f0 ^ g0;
+  f0 ^= ((f5 ^ t) & C2) << 4;
+  g0 ^= (g5 ^ (t >> 4)) & C2;
+  t = f1 ^ g1;
+  f1 ^= (f5 ^ (t << 4)) & (C2 << 4);
+  g1 ^= ((g5 ^ t) >> 4) & C2;
+  t = f2 ^ g2;
+  f2 ^= ((f6 ^ t) & C2) << 4;
+  g2 ^= (g6 ^ (t >> 4)) & C2;
+  t = f3 ^ g3;
+  f3 ^= (f6 ^ (t << 4)) & (C2 << 4);
+  g3 ^= ((g6 ^ t) >> 4) & C2;
+  t = f4 ^ g4;
+  f4 ^= ((f7 ^ t) & C2) << 4;
+  g4 ^= (g7 ^ (t >> 4)) & C2;
+
+  t = f0 ^ g0;
+  f0 ^= ((f3 ^ t) & C3) << 8;
+  g0 ^= (g3 ^ (t >> 8)) & C3;
+  t = f1 ^ g1;
+  f1 ^= (f3 ^ (t << 8)) & (C3 << 8);
+  g1 ^= ((g3 ^ t) >> 8) & C3;
+  t = f2 ^ g2;
+  f2 ^= ((f4 ^ t) & C3) << 8;
+  g2 ^= (g4 ^ (t >> 8)) & C3;
+
+  t = f0 ^ g0;
+  f0 ^= ((f2 ^ t) & C4) << 16;
+  g0 ^= (g2 ^ (t >> 16)) & C4;
+  t = f1 ^ g1;
+  f1 ^= (f2 ^ (t << 16)) & (C4 << 16);
+  g1 ^= ((g2 ^ t) >> 16) & C4;
+
+  t = f0 ^ g0;
+  f0 ^= ((t ^ f1) & C5) << 32;
+  g0 ^= ((t >> 32) ^ g1) & C5;
+
+  *z0 = f0;
+  *z1 = g0;
+}
+
+static inline void poly64_sqr(uint64_t *z1, uint64_t *z0, uint64_t x)
+{
+  const uint64_t C0 = 0x5555555555555555;
+  const uint64_t C1 = 0x3333333333333333;
+  const uint64_t C2 = 0x0f0f0f0f0f0f0f0f;
+  const uint64_t C3 = 0x00ff00ff00ff00ff;
+  const uint64_t C4 = 0x0000ffff0000ffff;
+  const uint64_t C5 = 0x00000000ffffffff;
+  uint64_t y = x >> 32;
+  x &= C5;
+  x = (x | (x << 16)) & C4;
+  y = (y | (y << 16)) & C4;
+  x = (x | (x << 8)) & C3;
+  y = (y | (y << 8)) & C3;
+  x = (x | (x << 4)) & C2;
+  y = (y | (y << 4)) & C2;
+  x = (x | (x << 2)) & C1;
+  y = (y | (y << 2)) & C1;
+  x = (x | (x << 1)) & C0;
+  y = (y | (y << 1)) & C0;
+  *z0 = x;
+  *z1 = y;
+}
+
+#endif // FIELD_INTERNAL_H
diff --git a/cbits/third_party/AIMer/hash.c b/cbits/third_party/AIMer/hash.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/hash.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: MIT
+
+#include "hash.h"
+#include <stddef.h>
+#include <stdint.h>
+
+#if SECURITY_BITS == 128
+#define shake_inc_init shake128_inc_init
+#define shake_inc_absorb shake128_inc_absorb
+#define shake_inc_final shake128_inc_finalize
+#define shake_inc_squeeze shake128_inc_squeeze
+#define shake_inc_ctx_release shake128_inc_ctx_release
+#else
+#define shake_inc_init shake256_inc_init
+#define shake_inc_absorb shake256_inc_absorb
+#define shake_inc_final shake256_inc_finalize
+#define shake_inc_squeeze shake256_inc_squeeze
+#define shake_inc_ctx_release shake256_inc_ctx_release
+#endif
+
+void hash_init(hash_instance *ctx)
+{
+  shake_inc_init(ctx);
+}
+
+void hash_init_prefix(hash_instance *ctx, uint8_t prefix)
+{
+  shake_inc_init(ctx);
+  shake_inc_absorb(ctx, &prefix, sizeof(prefix));
+}
+
+void hash_update(hash_instance *ctx, const uint8_t *data, size_t data_len)
+{
+  shake_inc_absorb(ctx, data, data_len);
+}
+
+void hash_final(hash_instance *ctx)
+{
+  shake_inc_final(ctx);
+}
+
+void hash_squeeze(hash_instance *ctx, uint8_t *buffer, size_t buffer_len)
+{
+  shake_inc_squeeze(buffer, buffer_len, ctx);
+}
+
+void hash_ctx_release(hash_instance *ctx)
+{
+  shake_inc_ctx_release(ctx);
+}
diff --git a/cbits/third_party/AIMer/hash.h b/cbits/third_party/AIMer/hash.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/hash.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HASH_H
+#define HASH_H
+
+#include "params.h"
+#include "common/fips202.h"
+#include <stddef.h>
+#include <stdint.h>
+
+static const uint8_t HASH_PREFIX_0 = (uint8_t)AIMER_HASH_PREFIX_0;
+static const uint8_t HASH_PREFIX_1 = 1;
+static const uint8_t HASH_PREFIX_2 = 2;
+static const uint8_t HASH_PREFIX_3 = 3;
+static const uint8_t HASH_PREFIX_4 = 4;
+static const uint8_t HASH_PREFIX_5 = 5;
+
+#if SECURITY_BITS == 128
+typedef shake128incctx hash_instance;
+#else
+typedef shake256incctx hash_instance;
+#endif
+
+#define hash_init AIMER_NAMESPACE(hash_init)
+void hash_init(hash_instance *ctx);
+#define hash_init_prefix AIMER_NAMESPACE(hash_init_prefix)
+void hash_init_prefix(hash_instance *ctx, uint8_t prefix);
+#define hash_update AIMER_NAMESPACE(hash_update)
+void hash_update(hash_instance *ctx, const uint8_t *data, size_t data_len);
+#define hash_final AIMER_NAMESPACE(hash_final)
+void hash_final(hash_instance *ctx);
+#define hash_squeeze AIMER_NAMESPACE(hash_squeeze)
+void hash_squeeze(hash_instance *ctx, uint8_t *buffer, size_t buffer_len);
+#define hash_ctx_release AIMER_NAMESPACE(hash_ctx_release)
+void hash_ctx_release(hash_instance *ctx);
+
+#endif // HASH_H
diff --git a/cbits/third_party/AIMer/params.h b/cbits/third_party/AIMer/params.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/params.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT
+#ifndef AIMER_PARAMS_H
+#define AIMER_PARAMS_H
+
+#define str(s) #s
+#define xstr(s) str(s)
+
+#include xstr(params/params-aimer-PARAMS.h)
+
+#endif // AIMER_PARAMS_H
diff --git a/cbits/third_party/AIMer/params/params-aimer-128f.h b/cbits/third_party/AIMer/params/params-aimer-128f.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/params/params-aimer-128f.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define AIMER_NAMESPACE(s)          samsungsds_aimer_128f_ref_##s
+
+#define SECURITY_BITS               128                  // security parameter
+#define SECURITY_BYTES              (SECURITY_BITS / 8)  // byte size of security parameter
+
+#define AIM3_NUM_BITS_FIELD         SECURITY_BITS        // number of bits in field element
+#define AIM3_NUM_BYTES_FIELD        SECURITY_BYTES       // number of bytes in field element
+#define AIM3_NUM_WORDS_FIELD        (SECURITY_BITS / 64) // number of 64-bit words in element
+#define AIM3_NUM_BITS_WORD          64                   // number of bits in word
+#define AIM3_IV_SIZE                SECURITY_BYTES       // byte size of AIM3 initial vector
+
+#define AIM3_NUM_INPUT_SBOX         2                    // number of AIM3 input S-boxes
+
+#define AIMER_SALT_SIZE             SECURITY_BYTES       // byte size of salt
+#define AIMER_SEED_SIZE             SECURITY_BYTES       // byte size of seed
+#define AIMER_COMMIT_SIZE           (SECURITY_BYTES * 2) // byte size of commitment
+
+#define AIMER_L                     AIM3_NUM_INPUT_SBOX
+#define AIMER_T                     33                   // number of parallel repetitions (Tau)
+#define AIMER_N                     16                   // number of MPC parties (N)
+#define AIMER_LOGN                  4                    // log_2(N)
+
+#define AIMER_PK_BYTES              32                   // byte size of public key
+#define AIMER_SK_BYTES              48                   // byte size of secret key
+#define AIMER_SIG_BYTES             6944                 // byte size of signature
+
+#define AIMER_HASH_PREFIX_0         0x00
+
+#endif // PARAMS_H
diff --git a/cbits/third_party/AIMer/params/params-aimer-128s.h b/cbits/third_party/AIMer/params/params-aimer-128s.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/params/params-aimer-128s.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define AIMER_NAMESPACE(s)          samsungsds_aimer_128s_ref_##s
+
+#define SECURITY_BITS               128                  // security parameter
+#define SECURITY_BYTES              (SECURITY_BITS / 8)  // byte size of security parameter
+
+#define AIM3_NUM_BITS_FIELD         SECURITY_BITS        // number of bits in field element
+#define AIM3_NUM_BYTES_FIELD        SECURITY_BYTES       // number of bytes in field element
+#define AIM3_NUM_WORDS_FIELD        (SECURITY_BITS / 64) // number of 64-bit words in element
+#define AIM3_NUM_BITS_WORD          64                   // number of bits in word
+#define AIM3_IV_SIZE                SECURITY_BYTES       // byte size of AIM3 initial vector
+
+#define AIM3_NUM_INPUT_SBOX         2                    // number of AIM3 input S-boxes
+
+#define AIMER_SALT_SIZE             SECURITY_BYTES       // byte size of salt
+#define AIMER_SEED_SIZE             SECURITY_BYTES       // byte size of seed
+#define AIMER_COMMIT_SIZE           (SECURITY_BYTES * 2) // byte size of commitment
+
+#define AIMER_L                     AIM3_NUM_INPUT_SBOX
+#define AIMER_T                     17                   // number of parallel repetitions (Tau)
+#define AIMER_N                     256                  // number of MPC parties (N)
+#define AIMER_LOGN                  8                    // log_2(N)
+
+#define AIMER_PK_BYTES              32                   // byte size of public key
+#define AIMER_SK_BYTES              48                   // byte size of secret key
+#define AIMER_SIG_BYTES             4704                 // byte size of signature
+
+#define AIMER_HASH_PREFIX_0         0x10
+
+#endif // PARAMS_H
diff --git a/cbits/third_party/AIMer/params/params-aimer-192f.h b/cbits/third_party/AIMer/params/params-aimer-192f.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/params/params-aimer-192f.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define AIMER_NAMESPACE(s)          samsungsds_aimer_192f_ref_##s
+
+#define SECURITY_BITS               192                  // security parameter
+#define SECURITY_BYTES              (SECURITY_BITS / 8)  // byte size of security parameter
+
+#define AIM3_NUM_BITS_FIELD         SECURITY_BITS        // number of bits in field element
+#define AIM3_NUM_BYTES_FIELD        SECURITY_BYTES       // number of bytes in field element
+#define AIM3_NUM_WORDS_FIELD        (SECURITY_BITS / 64) // number of 64-bit words in element
+#define AIM3_NUM_BITS_WORD          64                   // number of bits in word
+#define AIM3_IV_SIZE                SECURITY_BYTES       // byte size of AIM3 initial vector
+
+#define AIM3_NUM_INPUT_SBOX         2                    // number of AIM3 input S-boxes
+
+#define AIMER_SALT_SIZE             SECURITY_BYTES       // byte size of salt
+#define AIMER_SEED_SIZE             SECURITY_BYTES       // byte size of seed
+#define AIMER_COMMIT_SIZE           (SECURITY_BYTES * 2) // byte size of commitment
+
+#define AIMER_L                     AIM3_NUM_INPUT_SBOX
+#define AIMER_T                     49                   // number of parallel repetitions (Tau)
+#define AIMER_N                     16                   // number of MPC parties (N)
+#define AIMER_LOGN                  4                    // log_2(N)
+
+#define AIMER_PK_BYTES              48                   // byte size of public key
+#define AIMER_SK_BYTES              72                   // byte size of secret key
+#define AIMER_SIG_BYTES             15408                // byte size of signature
+
+#define AIMER_HASH_PREFIX_0         0x20
+
+#endif // PARAMS_H
diff --git a/cbits/third_party/AIMer/params/params-aimer-192s.h b/cbits/third_party/AIMer/params/params-aimer-192s.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/params/params-aimer-192s.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define AIMER_NAMESPACE(s)          samsungsds_aimer_192s_ref_##s
+
+#define SECURITY_BITS               192                  // security parameter
+#define SECURITY_BYTES              (SECURITY_BITS / 8)  // byte size of security parameter
+
+#define AIM3_NUM_BITS_FIELD         SECURITY_BITS        // number of bits in field element
+#define AIM3_NUM_BYTES_FIELD        SECURITY_BYTES       // number of bytes in field element
+#define AIM3_NUM_WORDS_FIELD        (SECURITY_BITS / 64) // number of 64-bit words in element
+#define AIM3_NUM_BITS_WORD          64                   // number of bits in word
+#define AIM3_IV_SIZE                SECURITY_BYTES       // byte size of AIM3 initial vector
+
+#define AIM3_NUM_INPUT_SBOX         2                    // number of AIM3 input S-boxes
+
+#define AIMER_SALT_SIZE             SECURITY_BYTES       // byte size of salt
+#define AIMER_SEED_SIZE             SECURITY_BYTES       // byte size of seed
+#define AIMER_COMMIT_SIZE           (SECURITY_BYTES * 2) // byte size of commitment
+
+#define AIMER_L                     AIM3_NUM_INPUT_SBOX
+#define AIMER_T                     25                   // number of parallel repetitions (Tau)
+#define AIMER_N                     256                  // number of MPC parties (N)
+#define AIMER_LOGN                  8                    // log_2(N)
+
+#define AIMER_PK_BYTES              48                   // byte size of public key
+#define AIMER_SK_BYTES              72                   // byte size of secret key
+#define AIMER_SIG_BYTES             10320                // byte size of signature
+
+#define AIMER_HASH_PREFIX_0         0x30
+
+#endif // PARAMS_H
diff --git a/cbits/third_party/AIMer/params/params-aimer-256f.h b/cbits/third_party/AIMer/params/params-aimer-256f.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/params/params-aimer-256f.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define AIMER_NAMESPACE(s)          samsungsds_aimer_256f_ref_##s
+
+#define SECURITY_BITS               256                  // security parameter
+#define SECURITY_BYTES              (SECURITY_BITS / 8)  // byte size of security parameter
+
+#define AIM3_NUM_BITS_FIELD         SECURITY_BITS        // number of bits in field element
+#define AIM3_NUM_BYTES_FIELD        SECURITY_BYTES       // number of bytes in field element
+#define AIM3_NUM_WORDS_FIELD        (SECURITY_BITS / 64) // number of 64-bit words in element
+#define AIM3_NUM_BITS_WORD          64                   // number of bits in word
+#define AIM3_IV_SIZE                SECURITY_BYTES       // byte size of AIM3 initial vector
+
+#define AIM3_NUM_INPUT_SBOX         3                    // number of AIM3 input S-boxes
+
+#define AIMER_SALT_SIZE             SECURITY_BYTES       // byte size of salt
+#define AIMER_SEED_SIZE             SECURITY_BYTES       // byte size of seed
+#define AIMER_COMMIT_SIZE           (SECURITY_BYTES * 2) // byte size of commitment
+
+#define AIMER_L                     AIM3_NUM_INPUT_SBOX
+#define AIMER_T                     65                   // number of parallel repetitions (Tau)
+#define AIMER_N                     16                   // number of MPC parties (N)
+#define AIMER_LOGN                  4                    // log_2(N)
+
+#define AIMER_PK_BYTES              64                   // byte size of public key
+#define AIMER_SK_BYTES              96                   // byte size of secret key
+#define AIMER_SIG_BYTES             31360                // byte size of signature
+
+#define AIMER_HASH_PREFIX_0         0x40
+
+#endif // PARAMS_H
diff --git a/cbits/third_party/AIMer/params/params-aimer-256s.h b/cbits/third_party/AIMer/params/params-aimer-256s.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/params/params-aimer-256s.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define AIMER_NAMESPACE(s)          samsungsds_aimer_256s_ref_##s
+
+#define SECURITY_BITS               256                  // security parameter
+#define SECURITY_BYTES              (SECURITY_BITS / 8)  // byte size of security parameter
+
+#define AIM3_NUM_BITS_FIELD         SECURITY_BITS        // number of bits in field element
+#define AIM3_NUM_BYTES_FIELD        SECURITY_BYTES       // number of bytes in field element
+#define AIM3_NUM_WORDS_FIELD        (SECURITY_BITS / 64) // number of 64-bit words in element
+#define AIM3_NUM_BITS_WORD          64                   // number of bits in word
+#define AIM3_IV_SIZE                SECURITY_BYTES       // byte size of AIM3 initial vector
+
+#define AIM3_NUM_INPUT_SBOX         3                    // number of AIM3 input S-boxes
+
+#define AIMER_SALT_SIZE             SECURITY_BYTES       // byte size of salt
+#define AIMER_SEED_SIZE             SECURITY_BYTES       // byte size of seed
+#define AIMER_COMMIT_SIZE           (SECURITY_BYTES * 2) // byte size of commitment
+
+#define AIMER_L                     AIM3_NUM_INPUT_SBOX
+#define AIMER_T                     33                   // number of parallel repetitions (Tau)
+#define AIMER_N                     256                  // number of MPC parties (N)
+#define AIMER_LOGN                  8                    // log_2(N)
+
+#define AIMER_PK_BYTES              64                   // byte size of public key
+#define AIMER_SK_BYTES              96                   // byte size of secret key
+#define AIMER_SIG_BYTES             20224                // byte size of signature
+
+#define AIMER_HASH_PREFIX_0         0x50
+
+#endif // PARAMS_H
diff --git a/cbits/third_party/AIMer/sign.c b/cbits/third_party/AIMer/sign.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/sign.c
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: MIT
+
+#include "api.h"
+#include "aim3.h"
+#include "field.h"
+#include "hash.h"
+#include "params.h"
+#include "sign.h"
+#include "tree.h"
+#include "common/crypto_declassify.h"
+#include "common/rng.h"
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void commit_and_expand_tape(tape_t *tape, uint8_t *commit,
+                                   const uint8_t *salt,
+                                   size_t rep, size_t party,
+                                   const uint8_t *seed)
+{
+  hash_instance ctx;
+
+  uint8_t rep_byte = (uint8_t)rep;
+  uint8_t party_byte = (uint8_t)party;
+
+  hash_init_prefix(&ctx, HASH_PREFIX_5);
+  hash_update(&ctx, salt, AIMER_SALT_SIZE);
+  hash_update(&ctx, &rep_byte, sizeof(rep_byte));
+  hash_update(&ctx, &party_byte, sizeof(party_byte));
+  hash_update(&ctx, seed, AIMER_SEED_SIZE);
+  hash_final(&ctx);
+
+  hash_squeeze(&ctx, commit, AIMER_COMMIT_SIZE);
+  hash_squeeze(&ctx, (uint8_t *)tape, sizeof(tape_t));
+  hash_ctx_release(&ctx);
+}
+
+static inline void adjust_last_share(
+  proof_t *proof, tape_t *delta, tape_t *tape,
+  const gf pt_gf, const gf *sbox_outputs)
+{
+  gf_add(delta->pt_share, delta->pt_share, pt_gf);
+  gf_to_bytes(proof->delta_pt_bytes, delta->pt_share);
+  gf_add(tape->pt_share, delta->pt_share, tape->pt_share);
+
+  for (size_t i = 0; i < AIMER_L; i++)
+  {
+    gf_add(delta->y_shares[i], delta->y_shares[i], sbox_outputs[i]);
+    gf_to_bytes(proof->delta_ys_bytes[i], delta->y_shares[i]);
+    gf_add(tape->y_shares[i], delta->y_shares[i], tape->y_shares[i]);
+  }
+
+  // c = sum a[i] * y[i] where y = sbox outs
+  for (size_t i = 0; i < AIMER_L + 1; i++)
+  {
+    gf_mul_add(delta->c_share, delta->a_shares[i], sbox_outputs[i]);
+  }
+  gf_to_bytes(proof->delta_c_bytes, delta->c_share);
+  gf_add(tape->c_share, delta->c_share, tape->c_share);
+}
+
+// committing to the seeds and the execution views of the parties
+// Returns 0 on success or -1 on allocation failure.
+static int run_phase_1(signature_t *sign,
+                       uint8_t commits[AIMER_T][AIMER_N][AIMER_COMMIT_SIZE],
+                       uint8_t nodes[AIMER_T][2 * AIMER_N - 1][AIMER_SEED_SIZE],
+                       mult_chk_t mult_chk[AIMER_T][AIMER_N],
+                       const uint8_t *sk, const uint8_t *rnd,
+                       const uint8_t *m, size_t mlen,
+                       const uint8_t *pre, size_t prelen)
+{
+  gf pt_gf = {0,}, ct_gf = {0,};
+  gf_from_bytes(pt_gf, sk);
+  gf_from_bytes(ct_gf, sk + AIM3_NUM_BYTES_FIELD + AIM3_IV_SIZE);
+
+  // message pre-hashing
+  hash_instance ctx;
+  hash_init_prefix(&ctx, HASH_PREFIX_0);
+  hash_update(&ctx, sk + AIM3_NUM_BYTES_FIELD,
+              AIM3_IV_SIZE + AIM3_NUM_BYTES_FIELD);
+  hash_update(&ctx, pre, prelen);
+  hash_update(&ctx, m, mlen);
+  hash_final(&ctx);
+
+  uint8_t mu[AIMER_COMMIT_SIZE];
+  hash_squeeze(&ctx, mu, AIMER_COMMIT_SIZE);
+  hash_ctx_release(&ctx);
+
+  // derive the binary matrix and the vector from the initial vector
+  aim_lin_t *lin = malloc(sizeof(aim_lin_t));
+  if (lin == NULL)
+  {
+    return -2;
+  }
+  aim3_generate_linear(lin, sk + AIM3_NUM_BYTES_FIELD);
+
+  // compute sboxes' outputs
+  gf sbox_outputs[AIMER_L + 1];
+  aim3_sbox_outputs(sbox_outputs, lin, pt_gf, ct_gf);
+
+  // generate salt
+  hash_init_prefix(&ctx, HASH_PREFIX_3);
+  hash_update(&ctx, sk, AIM3_NUM_BYTES_FIELD);
+  hash_update(&ctx, mu, AIMER_COMMIT_SIZE);
+  hash_update(&ctx, rnd, SECURITY_BYTES);
+  hash_final(&ctx);
+  hash_squeeze(&ctx, sign->salt, AIMER_SALT_SIZE);
+
+  // generate root seeds
+  uint8_t root_seeds[AIMER_T][AIMER_SEED_SIZE];
+  hash_squeeze(&ctx, root_seeds[0], sizeof(root_seeds));
+  hash_ctx_release(&ctx);
+
+  // hash_instance for h_1
+  hash_init_prefix(&ctx, HASH_PREFIX_1);
+  hash_update(&ctx, mu, AIMER_COMMIT_SIZE);
+  hash_update(&ctx, sign->salt, AIMER_SALT_SIZE);
+
+  // ct_gf is part of the public key, so it is public throughout the MPC
+  crypto_declassify(ct_gf, sizeof(ct_gf));
+
+  for (size_t rep = 0; rep < AIMER_T; rep++)
+  {
+    // compute parties' seeds using binary tree
+    expand_tree(nodes[rep], sign->salt, rep, root_seeds[rep]);
+
+    // initialize adjustment values
+    tape_t delta, tape;
+    memset(&delta, 0, sizeof(tape_t));
+
+    for (size_t party = 0; party < AIMER_N; party++)
+    {
+      // generate execution views and commitments
+      commit_and_expand_tape(&tape, commits[rep][party], sign->salt, rep, party,
+                             nodes[rep][party + AIMER_N - 1]);
+
+      // compute offsets
+      gf_add(delta.pt_share, delta.pt_share, tape.pt_share);
+      for (size_t i = 0; i < AIMER_L; i++)
+      {
+        gf_add(delta.y_shares[i], delta.y_shares[i], tape.y_shares[i]);
+      }
+      for (size_t i = 0; i < AIMER_L + 1; i++)
+      {
+        gf_add(delta.a_shares[i], delta.a_shares[i], tape.a_shares[i]);
+      }
+      gf_add(delta.c_share, delta.c_share, tape.c_share);
+
+      // adjust the last share and prepare the proof and h_1
+      if (party == AIMER_N - 1)
+      {
+        adjust_last_share(&sign->proofs[rep], &delta, &tape, pt_gf,
+                          sbox_outputs);
+      }
+
+      // run the MPC simulation and prepare the mult check inputs
+      aim3_mpc(&mult_chk[rep][party], lin, &tape, ct_gf, party);
+    }
+    hash_update(&ctx, (const uint8_t*)commits[rep], AIMER_COMMIT_SIZE * AIMER_N);
+
+    // NOTE: depend on the order of values in proof_t
+    hash_update(&ctx, sign->proofs[rep].delta_pt_bytes,
+                AIM3_NUM_BYTES_FIELD * (AIMER_L + 2));
+  }
+
+  // commit to salt, (all commitments of parties' seeds,
+  // delta_pt, delta_t, delta_c) for all repetitions
+  hash_final(&ctx);
+  hash_squeeze(&ctx, sign->h_1, AIMER_COMMIT_SIZE);
+  hash_ctx_release(&ctx);
+
+  free(lin);
+  return 0;
+}
+
+static void run_phase_2_and_3(signature_t *sign,
+                              gf alpha_shares[AIMER_T][AIMER_N][AIMER_L + 1],
+                              gf v_share[AIMER_T][AIMER_N],
+                              const mult_chk_t mult_chk[AIMER_T][AIMER_N])
+{
+  gf epsilons[AIMER_L + 1];
+  gf alpha[AIMER_L + 1];
+
+  hash_instance ctx_e;
+  hash_init(&ctx_e);
+  hash_update(&ctx_e, sign->h_1, AIMER_COMMIT_SIZE);
+  hash_final(&ctx_e);
+
+  hash_instance ctx;
+  hash_init_prefix(&ctx, HASH_PREFIX_2);
+  hash_update(&ctx, sign->h_1, AIMER_COMMIT_SIZE);
+  hash_update(&ctx, sign->salt, AIMER_SALT_SIZE);
+
+  for (size_t rep = 0; rep < AIMER_T; rep++)
+  {
+    hash_squeeze(&ctx_e, (uint8_t *)epsilons, sizeof(epsilons));
+
+    crypto_declassify(epsilons, sizeof(epsilons));
+
+    for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+    {
+      gf_set0(alpha[ell]);
+    }
+
+    for (size_t party = 0; party < AIMER_N; party++)
+    {
+      // alpha_share[ell] = a_share[ell] + x_share[ell] * eps[ell]
+      // v_share = c_share - sum alpha[ell] * b_share[ell] + sum z_share[ell] * eps[ell]
+      gf_copy(v_share[rep][party], mult_chk[rep][party].c_share);
+      for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+      {
+        gf_copy(alpha_shares[rep][party][ell],
+                mult_chk[rep][party].a_shares[ell]);
+        gf_mul_add(alpha_shares[rep][party][ell],
+                   mult_chk[rep][party].x_shares[ell], epsilons[ell]);
+
+        gf_add(alpha[ell], alpha[ell], alpha_shares[rep][party][ell]);
+
+        gf_mul_add(v_share[rep][party],
+                   mult_chk[rep][party].z_shares[ell], epsilons[ell]);
+      }
+    }
+
+    // alpha is opened, so we can finish calculating v_share
+    crypto_declassify(alpha, sizeof(alpha));
+    for (size_t party = 0; party < AIMER_N; party++)
+    {
+      for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+      {
+        gf_mul_add(v_share[rep][party],
+                   mult_chk[rep][party].b_shares[ell], alpha[ell]);
+      }
+    }
+    hash_update(&ctx, (const uint8_t *)alpha_shares[rep],
+                AIM3_NUM_BYTES_FIELD * AIMER_N * (AIMER_L + 1));
+    hash_update(&ctx, (const uint8_t *)v_share[rep],
+                AIM3_NUM_BYTES_FIELD * AIMER_N);
+  }
+  hash_final(&ctx);
+  hash_squeeze(&ctx, sign->h_2, AIMER_COMMIT_SIZE);
+  hash_ctx_release(&ctx);
+  hash_ctx_release(&ctx_e);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Returns 0 on success, -1 when a zero S-box input requires a retry with fresh
+// randomness, and a value < -1 for a fatal error such as an allocation failure.
+static int crypto_sign_keypair_internal(uint8_t *ct,
+                                        const uint8_t *pt, const uint8_t *iv)
+{
+  return aim3_nozeroinpsb(ct, pt, iv);
+}
+
+int crypto_sign_keypair(uint8_t *pk, uint8_t *sk)
+{
+  if (!pk || !sk)
+  {
+    return -1;
+  }
+
+  uint8_t pt[AIM3_NUM_BYTES_FIELD];
+  uint8_t iv[AIM3_IV_SIZE];
+  uint8_t ct[AIM3_NUM_BYTES_FIELD];
+
+  while (true)
+  {
+    if (randombytes(pt, AIM3_NUM_BYTES_FIELD) != 0 ||
+        randombytes(iv, AIM3_IV_SIZE) != 0)
+    {
+      return -1;
+    }
+    int ret = crypto_sign_keypair_internal(ct, pt, iv);
+    if (ret == 0)
+    {
+      break;
+    }
+    if (ret != -1)
+    {
+      // allocation failure: do not retry
+      return ret;
+    }
+  }
+
+  memcpy(pk, iv, AIM3_IV_SIZE);
+  memcpy(pk + AIM3_IV_SIZE, ct, AIM3_NUM_BYTES_FIELD);
+  memcpy(sk, pt, AIM3_NUM_BYTES_FIELD);
+  memcpy(sk + AIM3_NUM_BYTES_FIELD, pk, AIM3_IV_SIZE + AIM3_NUM_BYTES_FIELD);
+
+  return 0;
+}
+
+static int crypto_sign_signature_internal(uint8_t *sig, size_t *siglen,
+                                          const uint8_t *m, size_t mlen,
+                                          const uint8_t *pre, size_t prelen,
+                                          const uint8_t *rnd, const uint8_t *sk)
+{
+  signature_t *sign = (signature_t *)sig;
+
+  //////////////////////////////////////////////////////////////////////////
+  // Phase 1: Committing to the seeds and the execution views of parties. //
+  //////////////////////////////////////////////////////////////////////////
+
+  // nodes for seed trees
+  uint8_t (*nodes)[2 * AIMER_N - 1][AIMER_SEED_SIZE] =
+    malloc(sizeof(*nodes) * AIMER_T);
+
+  // commitments for seeds
+  uint8_t (*commits)[AIMER_N][AIMER_COMMIT_SIZE] =
+    malloc(sizeof(*commits) * AIMER_T);
+
+  // multiplication check inputs
+  mult_chk_t (*mult_chk)[AIMER_N] = malloc(sizeof(*mult_chk) * AIMER_T);
+
+  // multiplication check outputs
+  gf (*alpha_shares)[AIMER_N][AIMER_L + 1] =
+    malloc(sizeof(*alpha_shares) * AIMER_T);
+  gf (*v_share)[AIMER_N] = malloc(sizeof(*v_share) * AIMER_T);
+
+  if (!nodes || !commits || !mult_chk || !alpha_shares || !v_share)
+  {
+    free(nodes);
+    free(commits);
+    free(mult_chk);
+    free(alpha_shares);
+    free(v_share);
+    return -2;
+  }
+
+  memset(mult_chk, 0, AIMER_T * sizeof(*mult_chk));
+  memset(alpha_shares, 0, AIMER_T * sizeof(*alpha_shares));
+  memset(v_share, 0, AIMER_T * sizeof(*v_share));
+
+  // commitments for phase 1
+  int status = run_phase_1(sign, commits, nodes, mult_chk, sk, rnd, m, mlen,
+                           pre, prelen);
+  if (status != 0)
+  {
+    free(nodes);
+    free(commits);
+    free(mult_chk);
+    free(alpha_shares);
+    free(v_share);
+    return status;
+  }
+
+  /////////////////////////////////////////////////////////////////
+  // Phase 2, 3: Challenging and committing to the simulation of //
+  //             the multiplication checking protocol.           //
+  /////////////////////////////////////////////////////////////////
+
+  // compute the commitment of phase 3
+  run_phase_2_and_3(sign, alpha_shares, v_share,
+                    (const mult_chk_t (*)[AIMER_N])mult_chk);
+
+  //////////////////////////////////////////////////////
+  // Phase 4: Challenging views of the MPC protocols. //
+  //////////////////////////////////////////////////////
+
+  hash_instance ctx;
+  hash_init(&ctx);
+  hash_update(&ctx, sign->h_2, AIMER_COMMIT_SIZE);
+  hash_final(&ctx);
+
+  uint8_t indices[AIMER_T]; // AIMER_N <= 256
+  hash_squeeze(&ctx, indices, AIMER_T);
+  hash_ctx_release(&ctx);
+  for (size_t rep = 0; rep < AIMER_T; rep++)
+  {
+    indices[rep] &= (1 << AIMER_LOGN) - 1;
+  }
+
+  //////////////////////////////////////////////////////
+  // Phase 5: Opening the views of the MPC protocols. //
+  //////////////////////////////////////////////////////
+
+  crypto_declassify(indices, sizeof(indices));
+  for (size_t rep = 0; rep < AIMER_T; rep++)
+  {
+    size_t i_bar = indices[rep];
+    reveal_all_but(sign->proofs[rep].reveal_path,
+                   (const uint8_t (*)[AIMER_SEED_SIZE])nodes[rep], i_bar);
+    memcpy(sign->proofs[rep].missing_commitment, commits[rep][i_bar],
+           AIMER_COMMIT_SIZE);
+    for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+    {
+      gf_to_bytes(sign->proofs[rep].missing_alpha_share_bytes[ell],
+                  alpha_shares[rep][i_bar][ell]);
+    }
+  }
+  *siglen = CRYPTO_BYTES;
+
+  free(nodes);
+  free(commits);
+  free(mult_chk);
+  free(alpha_shares);
+  free(v_share);
+  return 0;
+}
+
+int crypto_sign_signature(uint8_t *sig, size_t *siglen,
+                          const uint8_t *m, size_t mlen,
+                          const uint8_t *ctx, size_t ctxlen, const uint8_t *sk)
+{
+  if (ctxlen > 255)
+  {
+    return -1;
+  }
+
+  uint8_t prefix[256];
+  uint8_t randomness[SECURITY_BYTES];
+
+  prefix[0] = ctxlen;
+  for (size_t i = 0; i < ctxlen; i++)
+  {
+    prefix[1 + i] = ctx[i];
+  }
+
+#ifdef RANDOMIZED_SIGNING
+  if (randombytes(randomness, SECURITY_BYTES) != 0)
+  {
+    return -1;
+  }
+#else
+  for (size_t i = 0; i < SECURITY_BYTES; i++)
+  {
+    randomness[i] = 0;
+  }
+#endif
+
+  return crypto_sign_signature_internal(sig, siglen, m, mlen, prefix,
+                                        1 + ctxlen, randomness, sk);
+}
+
+int crypto_sign(uint8_t *sm, size_t *smlen, const uint8_t *m, size_t mlen,
+                const uint8_t *ctx, size_t ctxlen, const uint8_t *sk)
+{
+  int ret = crypto_sign_signature(sm + mlen, smlen, m, mlen, ctx, ctxlen, sk);
+  if (ret != 0)
+  {
+    return ret;
+  }
+
+  memcpy(sm, m, mlen);
+  *smlen += mlen;
+
+  return 0;
+}
+
+static inline void recompute_last_share(const proof_t *proof, tape_t *tape)
+{
+  gf temp = {0,};
+
+  gf_from_bytes(temp, proof->delta_pt_bytes);
+  gf_add(tape->pt_share, tape->pt_share, temp);
+
+  for (size_t ell = 0; ell < AIMER_L; ell++)
+  {
+    gf_from_bytes(temp, proof->delta_ys_bytes[ell]);
+    gf_add(tape->y_shares[ell], tape->y_shares[ell], temp);
+  }
+
+  gf_from_bytes(temp, proof->delta_c_bytes);
+  gf_add(tape->c_share, tape->c_share, temp);
+}
+
+static inline void recompute_missing_alpha(const proof_t *proof,
+                                           gf *alpha_shares, gf *alpha)
+{
+  for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+  {
+    gf_from_bytes(alpha_shares[ell],
+                  proof->missing_alpha_share_bytes[ell]);
+    gf_add(alpha[ell], alpha[ell], alpha_shares[ell]);
+  }
+}
+
+static inline void recompute_v_shares(gf *v_shares,
+                                      gf (*b_shares)[AIMER_L + 1],
+                                      gf *alpha, size_t i_bar)
+{
+  gf_set0(v_shares[i_bar]);
+  for (size_t party = 0; party < AIMER_N; party++)
+  {
+    if (party == i_bar)
+    {
+      continue;
+    }
+    for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+    {
+      gf_mul_add(v_shares[party], b_shares[party][ell], alpha[ell]);
+    }
+    gf_add(v_shares[i_bar], v_shares[i_bar], v_shares[party]);
+  }
+}
+
+static int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen,
+                                       const uint8_t *m, size_t mlen,
+                                       const uint8_t *pre, size_t prelen,
+                                       const uint8_t *pk)
+{
+  if (siglen != CRYPTO_BYTES)
+  {
+    return -1;
+  }
+
+  const signature_t *sign = (const signature_t *)sig;
+
+  gf ct_gf = {0,};
+  gf_from_bytes(ct_gf, pk + AIM3_IV_SIZE);
+
+  // derive the binary matrix and the vector from the initial vector
+  aim_lin_t *lin = malloc(sizeof(aim_lin_t));
+  if (lin == NULL)
+  {
+    return -2;
+  }
+  aim3_generate_linear(lin, pk);
+
+  hash_instance ctx_e, ctx_h1, ctx_h2;
+
+  // indices = Expand(h_2)
+  hash_init(&ctx_e);
+  hash_update(&ctx_e, sign->h_2, AIMER_COMMIT_SIZE);
+  hash_final(&ctx_e);
+
+  uint8_t indices[AIMER_T]; // AIMER_N <= 256
+  hash_squeeze(&ctx_e, indices, AIMER_T);
+  hash_ctx_release(&ctx_e);
+  for (size_t rep = 0; rep < AIMER_T; rep++)
+  {
+    indices[rep] &= (1 << AIMER_LOGN) - 1;
+  }
+
+  // epsilons = Expand(h_1)
+  hash_init(&ctx_e);
+  hash_update(&ctx_e, sign->h_1, AIMER_COMMIT_SIZE);
+  hash_final(&ctx_e);
+
+  // message pre-hashing
+  uint8_t mu[AIMER_COMMIT_SIZE];
+  hash_init_prefix(&ctx_h1, HASH_PREFIX_0);
+  hash_update(&ctx_h1, pk, AIM3_IV_SIZE + AIM3_NUM_BYTES_FIELD);
+  hash_update(&ctx_h1, pre, prelen);
+  hash_update(&ctx_h1, m, mlen);
+  hash_final(&ctx_h1);
+  hash_squeeze(&ctx_h1, mu, AIMER_COMMIT_SIZE);
+  hash_ctx_release(&ctx_h1);
+
+  // ready for computing h_1' and h_2'
+  hash_init_prefix(&ctx_h1, HASH_PREFIX_1);
+  hash_update(&ctx_h1, mu, AIMER_COMMIT_SIZE);
+  hash_update(&ctx_h1, sign->salt, AIMER_SALT_SIZE);
+
+  hash_init_prefix(&ctx_h2, HASH_PREFIX_2);
+  hash_update(&ctx_h2, sign->h_1, AIMER_COMMIT_SIZE);
+  hash_update(&ctx_h2, sign->salt, AIMER_SALT_SIZE);
+
+  uint8_t (*nodes)[AIMER_SEED_SIZE] =
+    malloc(sizeof(*nodes) * (2 * AIMER_N - 2));
+
+  gf (*b_shares)[AIMER_L + 1] = malloc(AIMER_N * sizeof(*b_shares));
+  gf (*alpha_shares)[AIMER_L + 1] = malloc(AIMER_N * sizeof(*alpha_shares));
+  gf *v_shares = malloc(AIMER_N * sizeof(gf));
+
+  if (!nodes || !b_shares || !alpha_shares || !v_shares)
+  {
+    free(lin);
+    free(nodes);
+    free(b_shares);
+    free(alpha_shares);
+    free(v_shares);
+    return -2;
+  }
+
+  for (size_t rep = 0; rep < AIMER_T; rep++)
+  {
+    size_t i_bar = indices[rep];
+    reconstruct_tree(nodes, sign->salt, sign->proofs[rep].reveal_path,
+                     rep, i_bar);
+
+    gf epsilons[AIMER_L + 1];
+    hash_squeeze(&ctx_e, (uint8_t *)epsilons, sizeof(epsilons));
+
+    gf alpha[AIMER_L + 1];
+    memset(alpha, 0, sizeof(alpha));
+
+    for (size_t party = 0; party < AIMER_N; party++)
+    {
+      if (party == i_bar)
+      {
+        hash_update(&ctx_h1, sign->proofs[rep].missing_commitment,
+                    AIMER_COMMIT_SIZE);
+        recompute_missing_alpha(&sign->proofs[rep], alpha_shares[i_bar], alpha);
+        continue;
+      }
+
+      tape_t tape;
+      uint8_t commit[AIMER_COMMIT_SIZE];
+      commit_and_expand_tape(&tape, commit, sign->salt, rep, party,
+                             nodes[AIMER_N + party - 2]);
+      hash_update(&ctx_h1, commit, AIMER_COMMIT_SIZE);
+
+      // adjust last shares
+      mult_chk_t mult_chk;
+      memset(&mult_chk, 0, sizeof(mult_chk_t));
+      if (party == AIMER_N - 1)
+      {
+        recompute_last_share(&sign->proofs[rep], &tape);
+      }
+
+      // run the MPC simulation and prepare the mult check inputs
+      aim3_mpc(&mult_chk, lin, &tape, ct_gf, party);
+
+      gf_copy(v_shares[party], mult_chk.c_share);
+      for (size_t ell = 0; ell < AIMER_L + 1; ell++)
+      {
+        gf_copy(alpha_shares[party][ell], mult_chk.a_shares[ell]);
+        gf_mul_add(alpha_shares[party][ell], mult_chk.x_shares[ell],
+                   epsilons[ell]);
+        gf_add(alpha[ell], alpha[ell], alpha_shares[party][ell]);
+
+        gf_copy(b_shares[party][ell], mult_chk.b_shares[ell]);
+        gf_mul_add(v_shares[party], mult_chk.z_shares[ell], epsilons[ell]);
+      }
+    }
+
+    // alpha is opened, so we can finish calculating v_share
+    recompute_v_shares(v_shares, b_shares, alpha, i_bar);
+
+    // v is opened
+    hash_update(&ctx_h2, (const uint8_t *)alpha_shares,
+                AIM3_NUM_BYTES_FIELD * AIMER_N * (AIMER_L + 1));
+    hash_update(&ctx_h2, (const uint8_t *)v_shares,
+                AIM3_NUM_BYTES_FIELD * AIMER_N);
+    // NOTE: depend on the order of values in proof_t
+    hash_update(&ctx_h1, sign->proofs[rep].delta_pt_bytes,
+                AIM3_NUM_BYTES_FIELD * (AIMER_L + 2));
+  }
+  hash_ctx_release(&ctx_e);
+
+  uint8_t h_1_prime[AIMER_COMMIT_SIZE];
+  hash_final(&ctx_h1);
+  hash_squeeze(&ctx_h1, h_1_prime, AIMER_COMMIT_SIZE);
+  hash_ctx_release(&ctx_h1);
+
+  uint8_t h_2_prime[AIMER_COMMIT_SIZE];
+  hash_final(&ctx_h2);
+  hash_squeeze(&ctx_h2, h_2_prime, AIMER_COMMIT_SIZE);
+  hash_ctx_release(&ctx_h2);
+
+  free(lin);
+  free(nodes);
+  free(b_shares);
+  free(alpha_shares);
+  free(v_shares);
+
+  if (memcmp(h_1_prime, sign->h_1, AIMER_COMMIT_SIZE) != 0 ||
+      memcmp(h_2_prime, sign->h_2, AIMER_COMMIT_SIZE) != 0)
+  {
+    return -1;
+  }
+
+  return 0;
+}
+
+int crypto_sign_verify(const uint8_t *sig, size_t siglen,
+                       const uint8_t *m, size_t mlen,
+                       const uint8_t *ctx, size_t ctxlen, const uint8_t *pk)
+{
+  if (ctxlen > 255)
+  {
+    return -1;
+  }
+
+  uint8_t prefix[256];
+
+  prefix[0] = ctxlen;
+  for (size_t i = 0; i < ctxlen; i++)
+  {
+    prefix[1 + i] = ctx[i];
+  }
+
+  return crypto_sign_verify_internal(sig, siglen, m, mlen, prefix, 1 + ctxlen,
+                                     pk);
+}
+
+int crypto_sign_open(uint8_t *m, size_t *mlen, const uint8_t *sm, size_t smlen,
+                     const uint8_t *ctx, size_t ctxlen, const uint8_t *pk)
+{
+  if (smlen < CRYPTO_BYTES)
+  {
+    return -1;
+  }
+
+  const size_t message_len = smlen - CRYPTO_BYTES;
+  const uint8_t *message = sm;
+  const uint8_t *signature = sm + message_len;
+
+  if (crypto_sign_verify(signature, CRYPTO_BYTES, message, message_len,
+                         ctx, ctxlen, pk))
+  {
+    return -1;
+  }
+
+  memmove(m, message, message_len);
+  *mlen = message_len;
+
+  return 0;
+}
diff --git a/cbits/third_party/AIMer/sign.h b/cbits/third_party/AIMer/sign.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/sign.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SIGN_H
+#define SIGN_H
+
+#include "field.h"
+#include "hash.h"
+#include "params.h"
+#include <stddef.h>
+#include <stdint.h>
+
+#define RANDOMIZED_SIGNING
+
+typedef struct tape_t
+{
+  gf pt_share;
+  gf y_shares[AIMER_L];
+  gf a_shares[AIMER_L + 1];
+  gf c_share;
+} tape_t;
+
+typedef struct proof_t
+{
+  uint8_t reveal_path[AIMER_LOGN][AIMER_SEED_SIZE];
+  uint8_t missing_commitment[AIMER_COMMIT_SIZE];
+  uint8_t delta_pt_bytes[AIM3_NUM_BYTES_FIELD];
+  uint8_t delta_ys_bytes[AIMER_L][AIM3_NUM_BYTES_FIELD];
+  uint8_t delta_c_bytes[AIM3_NUM_BYTES_FIELD];
+  uint8_t missing_alpha_share_bytes[AIMER_L + 1][AIM3_NUM_BYTES_FIELD];
+} proof_t;
+
+typedef struct signature_t
+{
+  uint8_t salt[AIMER_SALT_SIZE];
+  uint8_t h_1[AIMER_COMMIT_SIZE];
+  uint8_t h_2[AIMER_COMMIT_SIZE];
+  proof_t proofs[AIMER_T];
+} signature_t;
+
+#endif // SIGN_H
diff --git a/cbits/third_party/AIMer/tree.c b/cbits/third_party/AIMer/tree.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/tree.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: MIT
+
+#include "tree.h"
+#include "hash.h"
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+void expand_tree(uint8_t nodes[2 * AIMER_N - 1][AIMER_SEED_SIZE],
+                 const uint8_t salt[AIMER_SALT_SIZE], size_t rep_index,
+                 const uint8_t seed[AIMER_SEED_SIZE])
+{
+  size_t node_index;
+  hash_instance ctx;
+  uint8_t rep_byte = (uint8_t)rep_index;
+
+  memcpy(nodes[0], seed, AIMER_SEED_SIZE);
+  for (node_index = 1; node_index < AIMER_N; node_index++)
+  {
+    uint8_t node_byte = (uint8_t)node_index;
+
+    hash_init_prefix(&ctx, HASH_PREFIX_4);
+    hash_update(&ctx, salt, AIMER_SALT_SIZE);
+    hash_update(&ctx, &rep_byte, sizeof(rep_byte));
+    hash_update(&ctx, &node_byte, sizeof(node_byte));
+    hash_update(&ctx, nodes[node_index - 1], AIMER_SEED_SIZE);
+    hash_final(&ctx);
+
+    hash_squeeze(&ctx, nodes[2 * node_index - 1], AIMER_SEED_SIZE << 1);
+    hash_ctx_release(&ctx);
+  }
+}
+
+void reveal_all_but(uint8_t reveal_path[AIMER_LOGN][AIMER_SEED_SIZE],
+                    const uint8_t nodes[2 * AIMER_N - 1][AIMER_SEED_SIZE],
+                    size_t cover_index)
+{
+  size_t index = cover_index + AIMER_N;
+  for (size_t depth = 0; depth < AIMER_LOGN; depth++)
+  {
+    // index ^ 1 is sibling index
+    memcpy(reveal_path[depth], nodes[(index ^ 1) - 1], AIMER_SEED_SIZE);
+
+    // go to parent node
+    index >>= 1;
+  }
+}
+
+void reconstruct_tree(uint8_t nodes[2 * AIMER_N - 2][AIMER_SEED_SIZE],
+                      const uint8_t salt[AIMER_SALT_SIZE],
+                      const uint8_t reveal_path[AIMER_LOGN][AIMER_SEED_SIZE],
+                      size_t rep_index, size_t cover_index)
+{
+  size_t index, depth, path;
+  hash_instance ctx;
+  uint8_t rep_byte = (uint8_t)rep_index;
+
+  for (depth = 1; depth < AIMER_LOGN; depth++)
+  {
+    path = ((cover_index + AIMER_N) >> (AIMER_LOGN - depth)) ^ 1;
+    memcpy(nodes[path - 2], reveal_path[AIMER_LOGN - depth], AIMER_SEED_SIZE);
+
+    for (index = (1U << depth); index < (2U << depth); index++)
+    {
+      uint8_t index_byte = (uint8_t)index;
+
+      hash_init_prefix(&ctx, HASH_PREFIX_4);
+      hash_update(&ctx, salt, AIMER_SALT_SIZE);
+      hash_update(&ctx, &rep_byte, sizeof(rep_byte));
+      hash_update(&ctx, &index_byte, sizeof(index_byte));
+      hash_update(&ctx, nodes[index - 2], AIMER_SEED_SIZE);
+      hash_final(&ctx);
+
+      hash_squeeze(&ctx, nodes[2 * index - 2], AIMER_SEED_SIZE << 1);
+      hash_ctx_release(&ctx);
+    }
+  }
+
+  path = ((cover_index + AIMER_N) >> (AIMER_LOGN - depth)) ^ 1;
+  memcpy(nodes[path - 2], reveal_path[AIMER_LOGN - depth], AIMER_SEED_SIZE);
+}
diff --git a/cbits/third_party/AIMer/tree.h b/cbits/third_party/AIMer/tree.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/AIMer/tree.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "params.h"
+#include <stddef.h>
+#include <stdint.h>
+
+#define expand_tree AIMER_NAMESPACE(expand_tree)
+void expand_tree(uint8_t nodes[2 * AIMER_N - 1][AIMER_SEED_SIZE],
+                 const uint8_t salt[AIMER_SALT_SIZE], size_t rep_index,
+                 const uint8_t seed[AIMER_SEED_SIZE]);
+
+#define reveal_all_but AIMER_NAMESPACE(reveal_all_but)
+void reveal_all_but(uint8_t reveal_path[AIMER_LOGN][AIMER_SEED_SIZE],
+                    const uint8_t nodes[2 * AIMER_N - 1][AIMER_SEED_SIZE],
+                    size_t cover_index);
+
+#define reconstruct_tree AIMER_NAMESPACE(reconstruct_tree)
+void reconstruct_tree(uint8_t nodes[2 * AIMER_N - 2][AIMER_SEED_SIZE],
+                      const uint8_t salt[AIMER_SALT_SIZE],
+                      const uint8_t reveal_path[AIMER_LOGN][AIMER_SEED_SIZE],
+                      size_t rep_index, size_t cover_index);
+
+#endif // TREE_H
diff --git a/cbits/third_party/HAETAE/LICENSE b/cbits/third_party/HAETAE/LICENSE
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2026 Team HAETAE
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/cbits/third_party/HAETAE/include/api.h b/cbits/third_party/HAETAE/include/api.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/api.h
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_API_H
+#define HAETAE_API_H
+
+#include "params.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define CRYPTO_ALGNAME HAETAE_CRYPTO_ALGNAME
+#define CRYPTO_SECRETKEYBYTES HAETAE_CRYPTO_SECRETKEYBYTES
+#define CRYPTO_PUBLICKEYBYTES HAETAE_CRYPTO_PUBLICKEYBYTES
+#define CRYPTO_BYTES HAETAE_CRYPTO_BYTES
+
+#define crypto_sign_keypair HAETAE_NAMESPACE(keypair)
+int crypto_sign_keypair(uint8_t *vk, uint8_t *sk);
+
+#define crypto_sign_keypair_internal HAETAE_NAMESPACE(keypair_internal)
+int crypto_sign_keypair_internal(uint8_t *vk, uint8_t *sk,
+                                 uint8_t seed[HAETAE_SEEDBYTES]);
+
+#define crypto_sign_signature_internal HAETAE_NAMESPACE(signature_internal)
+int crypto_sign_signature_internal(uint8_t *sig, size_t *siglen,
+                                   const uint8_t *m, size_t mlen,
+                                   const uint8_t *pre, size_t prelen,
+                                   const uint8_t rnd[HAETAE_SEEDBYTES],
+                                   const uint8_t *sk);
+
+#define crypto_sign_signature HAETAE_NAMESPACE(signature)
+int crypto_sign_signature(uint8_t *sig, size_t *siglen, const uint8_t *m,
+                          size_t mlen, const uint8_t *ctx, size_t ctxlen,
+                          const uint8_t *sk);
+
+#define crypto_sign HAETAE_NAMESPACE(sign)
+int crypto_sign(uint8_t *sm, size_t *smlen, const uint8_t *m, size_t mlen,
+                const uint8_t *ctx, size_t ctxlen, const uint8_t *sk);
+
+#define crypto_sign_verify_internal HAETAE_NAMESPACE(verify_internal)
+int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen,
+                                const uint8_t *m, size_t mlen,
+                                const uint8_t *pre, size_t prelen,
+                                const uint8_t *vk);
+
+#define crypto_sign_verify HAETAE_NAMESPACE(verify)
+int crypto_sign_verify(const uint8_t *sig, size_t siglen, const uint8_t *m,
+                       size_t mlen, const uint8_t *ctx, size_t ctxlen,
+                       const uint8_t *vk);
+
+#define crypto_sign_open HAETAE_NAMESPACE(open)
+int crypto_sign_open(uint8_t *m, size_t *mlen, const uint8_t *sm, size_t smlen,
+                     const uint8_t *ctx, size_t ctxlen, const uint8_t *vk);
+
+#endif // HAETAE_API_H
diff --git a/cbits/third_party/HAETAE/include/common.h b/cbits/third_party/HAETAE/include/common.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/common.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_COMMON_H
+#define HAETAE_COMMON_H
+
+#define HAETAE_CONCAT_(x1, x2) x1##x2
+#define HAETAE_CONCAT(x1, x2) HAETAE_CONCAT_(x1, x2)
+
+#define HAETAE_STR_(x) #x
+#define HAETAE_STR(x) HAETAE_STR_(x)
+
+#endif /* !HAETAE_COMMON_H */
diff --git a/cbits/third_party/HAETAE/include/config.h b/cbits/third_party/HAETAE/include/config.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/config.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_CONFIG_H
+#define HAETAE_CONFIG_H
+
+#include "common.h"
+
+#define HAETAE_MODE2 0
+#define HAETAE_MODE3 1
+#define HAETAE_MODE5 2
+
+#if !defined(HAETAE_CONFIG_MODE)
+#define HAETAE_CONFIG_MODE HAETAE_MODE2
+#endif
+
+#if (HAETAE_CONFIG_MODE != HAETAE_MODE2) &&                                    \
+    (HAETAE_CONFIG_MODE != HAETAE_MODE3) &&                                    \
+    (HAETAE_CONFIG_MODE != HAETAE_MODE5)
+#error "Invalid HAETAE_CONFIG_MODE"
+#endif
+
+#if !defined(HAETAE_CONFIG_MODE_STRING)
+#if HAETAE_CONFIG_MODE == HAETAE_MODE2
+#define HAETAE_CONFIG_MODE_STRING mode2
+#elif HAETAE_CONFIG_MODE == HAETAE_MODE3
+#define HAETAE_CONFIG_MODE_STRING mode3
+#elif HAETAE_CONFIG_MODE == HAETAE_MODE5
+#define HAETAE_CONFIG_MODE_STRING mode5
+#endif
+#endif
+
+#if !defined(HAETAE_CONFIG_NAMESPACE_PREFIX)
+#define HAETAE_CONFIG_NAMESPACE_PREFIX cryptolab_haetae_
+#endif
+
+#if !defined(HAETAE_CONFIG_NAMESPACE_PREFIX_MODE)
+#define HAETAE_ADD_MODE(s) HAETAE_CONCAT(s, HAETAE_CONFIG_MODE_STRING)
+#define HAETAE_CONFIG_NAMESPACE_PREFIX_MODE                                    \
+  HAETAE_CONCAT(HAETAE_ADD_MODE(HAETAE_CONFIG_NAMESPACE_PREFIX), _)
+#endif
+
+#endif /* !HAETAE_CONFIG_H */
diff --git a/cbits/third_party/HAETAE/include/decompose.h b/cbits/third_party/HAETAE/include/decompose.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/decompose.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_DECOMPOSE_H
+#define HAETAE_DECOMPOSE_H
+
+#include "params.h"
+
+#include <stdint.h>
+
+#define decompose_z1 HAETAE_NAMESPACE(decompose_z1)
+void decompose_z1(int32_t *highbits, int32_t *lowbits, const int32_t r);
+
+#define decompose_hint HAETAE_NAMESPACE(decompose_hint)
+void decompose_hint(int32_t *highbits, const int32_t r);
+
+#define decompose_vk HAETAE_NAMESPACE(decompose_vk)
+int32_t decompose_vk(int32_t *a0, const int32_t a);
+
+#endif /* !HAETAE_DECOMPOSE_H */
diff --git a/cbits/third_party/HAETAE/include/encoding.h b/cbits/third_party/HAETAE/include/encoding.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/encoding.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_ENCODING_H
+#define HAETAE_ENCODING_H
+
+#include "params.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define encode_h HAETAE_NAMESPACE(encode_h)
+uint16_t encode_h(uint8_t *buf, const int32_t *h);
+#define decode_h HAETAE_NAMESPACE(decode_h)
+uint16_t decode_h(int32_t *h, const uint8_t *buf, uint16_t size_in);
+#define encode_hb_z1 HAETAE_NAMESPACE(encode_hb_z1)
+uint16_t encode_hb_z1(uint8_t *buf, const int32_t *hb_z1);
+#define decode_hb_z1 HAETAE_NAMESPACE(decode_hb_z1)
+uint16_t decode_hb_z1(int32_t *hb_z1, const uint8_t *buf, uint16_t size_in);
+
+#endif /* !HAETAE_ENCODING_H */
diff --git a/cbits/third_party/HAETAE/include/fft.h b/cbits/third_party/HAETAE/include/fft.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/fft.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_FFT_H
+#define HAETAE_FFT_H
+
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+#define FFT_N 256
+#define FFT_LOGN 8
+
+typedef struct {
+  int32_t real;
+  int32_t imag;
+} complex_fp32_16;
+
+#define fft HAETAE_NAMESPACE(fft)
+void fft(complex_fp32_16 data[FFT_N]);
+#define fft_init_and_bitrev HAETAE_NAMESPACE(fft_init_and_bitrev)
+void fft_init_and_bitrev(complex_fp32_16 r[FFT_N], const poly *x);
+#define complex_fp_sqabs HAETAE_NAMESPACE(complex_fp_sqabs)
+int32_t complex_fp_sqabs(complex_fp32_16 x);
+
+#endif /* !HAETAE_FFT_H */
diff --git a/cbits/third_party/HAETAE/include/fips202.h b/cbits/third_party/HAETAE/include/fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/fips202.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_FIPS202_H
+#define HAETAE_FIPS202_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHAKE128_RATE 168
+#define SHAKE256_RATE 136
+#define SHA3_256_RATE 136
+#define SHA3_512_RATE 72
+
+typedef struct {
+  uint64_t s[25];
+  unsigned int pos;
+} keccak_state;
+
+extern const uint64_t KeccakF_RoundConstants[];
+
+void shake128_init(keccak_state *state);
+void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake128_finalize(keccak_state *state);
+void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
+void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
+
+void shake256_init(keccak_state *state);
+void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake256_finalize(keccak_state *state);
+void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
+void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
+
+void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);
+void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);
+void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen);
+void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen);
+
+#endif /* !HAETAE_FIPS202_H */
diff --git a/cbits/third_party/HAETAE/include/fixpoint.h b/cbits/third_party/HAETAE/include/fixpoint.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/fixpoint.h
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_FIXPOINT_H
+#define HAETAE_FIXPOINT_H
+
+#include "params.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef __SIZEOF_INT128__
+__extension__ typedef __int128 int128;
+__extension__ typedef unsigned __int128 uint128;
+#endif
+
+typedef struct {
+  uint64_t limb48[2];
+} fp96_76;
+
+#define fixpoint_square HAETAE_NAMESPACE(fixpoint_square)
+void fixpoint_square(fp96_76 *sqx, const fp96_76 *x);
+
+#define fixpoint_newton_invsqrt HAETAE_NAMESPACE(fixpoint_newton_invsqrt)
+void fixpoint_newton_invsqrt(fp96_76 *invsqrtx, const fp96_76 *xhalf);
+
+#define fixpoint_mul_rnd13 HAETAE_NAMESPACE(fixpoint_mul_rnd13)
+int32_t fixpoint_mul_rnd13(const uint64_t x, const fp96_76 *y,
+                           const uint8_t sign);
+
+#define fixpoint_add HAETAE_NAMESPACE(fixpoint_add)
+void fixpoint_add(fp96_76 *xy, const fp96_76 *x, const fp96_76 *y);
+
+static inline void renormalize(fp96_76 *x) {
+  x->limb48[1] += x->limb48[0] >> 48;
+  x->limb48[0] &= (UINT64_C(1) << 48) - 1;
+}
+
+static inline int64_t smulh48(int64_t a, uint64_t b) {
+#ifdef __SIZEOF_INT128__
+  return (((int128)a * (int128)b) + ((int128)1 << 48) - 1) >> 48;
+#else
+  int64_t ah = a >> 24;
+  int64_t al = a - (ah << 24);
+  int64_t bl = b & ((1 << 24) - 1);
+  int64_t bh = b >> 24;
+
+  int64_t res = (al * bl + ((1 << 24) - 1)) >> 24; // ceiling in low word
+  res += al * bh + ah * bl;
+  res = (res + ((1 << 24) - 1)) >> 24; // ceiling in high word
+  return res + (ah * bh);
+#endif
+}
+
+static inline void mul64(uint64_t r[2], const uint64_t b, const uint64_t a) {
+#ifndef __SIZEOF_INT128__
+  uint64_t al = a & ((UINT64_C(1) << 32) - 1),
+           bl = b & ((UINT64_C(1) << 32) - 1), ah = a >> 32, bh = b >> 32;
+  r[0] = a * b;
+  r[1] = ah * bl + al * bh + ((al * bl) >> 32);
+  r[1] >>= 32;
+  r[1] += ah * bh;
+#else
+  uint128 res = ((uint128)a * (uint128)b);
+  r[0] = res;
+  r[1] = res >> 64;
+#endif
+}
+
+static inline void sq64(uint64_t r[2], const uint64_t a) {
+#ifndef __SIZEOF_INT128__
+  uint64_t al = a & ((UINT64_C(1) << 32) - 1), ah = a >> 32;
+  r[0] = a * a;
+  r[1] = ah * al * 2 + ((al * al) >> 32);
+  r[1] >>= 32;
+  r[1] += ah * ah;
+#else
+  uint128 res = ((uint128)a * (uint128)a);
+  r[0] = res;
+  r[1] = res >> 64;
+#endif
+}
+
+static inline void mul48(uint64_t r[2], const uint64_t b, const uint64_t a) {
+  mul64(r, b, a);
+  r[1] <<= 16;
+  r[1] ^= r[0] >> 48;
+  r[0] &= (UINT64_C(1) << 48) - 1;
+}
+
+static inline void mulacc48(uint64_t r[2], const uint64_t b, const uint64_t a) {
+  uint64_t tmp[2];
+  mul48(tmp, b, a);
+  r[0] += tmp[0];
+  r[1] += tmp[1];
+}
+
+// (a0 + a1*2^32)^2 = a0^2 + 2^33*a0*a1 + 2^64*a1^2
+static inline void sq48(uint64_t r[2], const uint64_t a) {
+  sq64(r, a);
+
+  r[1] <<= 16;
+  r[1] ^= r[0] >> 48;
+  r[0] &= (UINT64_C(1) << 48) - 1;
+}
+
+static inline void fixpoint_mul_high(fp96_76 *xy, const fp96_76 *x,
+                                     const uint64_t y) {
+  uint64_t tmp[2];
+  mul48(&xy->limb48[0], x->limb48[0], y); // implicitly shifted right by 48
+
+  mul48(tmp, x->limb48[1], y);
+  xy->limb48[1] += tmp[0];
+
+  // shift right by 28, rounding
+  xy->limb48[0] += UINT64_C(1) << 27;
+  xy->limb48[0] >>= 28;
+  xy->limb48[0] += (xy->limb48[1] << 20) & ((UINT64_C(1) << 48) - 1);
+  xy->limb48[1] >>= 28;
+
+  xy->limb48[1] += tmp[1] << 20;
+
+  renormalize(xy);
+}
+
+#endif /* !HAETAE_FIXPOINT_H */
diff --git a/cbits/third_party/HAETAE/include/ntt.h b/cbits/third_party/HAETAE/include/ntt.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/ntt.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_NTT_H
+#define HAETAE_NTT_H
+
+#include "params.h"
+
+#include <stdint.h>
+
+#define ntt HAETAE_NAMESPACE(ntt)
+void ntt(int32_t a[HAETAE_N]);
+
+#define invntt_tomont HAETAE_NAMESPACE(invntt_tomont)
+void invntt_tomont(int32_t a[HAETAE_N]);
+
+#endif /* !HAETAE_NTT_H */
diff --git a/cbits/third_party/HAETAE/include/packing.h b/cbits/third_party/HAETAE/include/packing.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/packing.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_PACKING_H
+#define HAETAE_PACKING_H
+
+#include "params.h"
+#include "polyvec.h"
+
+#include <stdint.h>
+
+#define pack_vk HAETAE_NAMESPACE(pack_vk)
+void pack_vk(uint8_t vk[HAETAE_CRYPTO_PUBLICKEYBYTES], polyveck *b,
+             const uint8_t seed[HAETAE_SEEDBYTES]);
+#define unpack_vk HAETAE_NAMESPACE(unpack_vk)
+void unpack_vk(polyvecl A[HAETAE_K],
+               const uint8_t vk[HAETAE_CRYPTO_PUBLICKEYBYTES]);
+
+#define pack_sk HAETAE_NAMESPACE(pack_sk)
+void pack_sk(uint8_t sk[HAETAE_CRYPTO_SECRETKEYBYTES],
+             const uint8_t vk[HAETAE_CRYPTO_PUBLICKEYBYTES], const polyvecm *s0,
+             const polyveck *s1, const uint8_t key[HAETAE_SEEDBYTES]);
+#define unpack_sk HAETAE_NAMESPACE(unpack_sk)
+void unpack_sk(polyvecl A[HAETAE_K], polyvecm *s0, polyveck *s1, uint8_t *key,
+               const uint8_t sk[HAETAE_CRYPTO_SECRETKEYBYTES]);
+
+#define pack_sig HAETAE_NAMESPACE(pack_sig)
+int pack_sig(uint8_t sig[HAETAE_CRYPTO_BYTES], const poly *c,
+             const polyvecl *lowbits_z1, const polyvecl *highbits_z1,
+             const polyveck *h);
+#define unpack_sig HAETAE_NAMESPACE(unpack_sig)
+int unpack_sig(poly *c, polyvecl *lowbits_z1, polyvecl *highbits_z1,
+               polyveck *h, const uint8_t sig[HAETAE_CRYPTO_BYTES]);
+
+#define pack_poly_q HAETAE_NAMESPACE(pack_poly_q)
+void pack_poly_q(uint8_t *r, const poly *a);
+#define unpack_poly_q HAETAE_NAMESPACE(unpack_poly_q)
+void unpack_poly_q(poly *r, const uint8_t *a);
+
+#define pack_poly_eta HAETAE_NAMESPACE(pack_poly_eta)
+void pack_poly_eta(uint8_t *r, const poly *a);
+#define unpack_poly_eta HAETAE_NAMESPACE(unpack_poly_eta)
+void unpack_poly_eta(poly *r, const uint8_t *a);
+
+#define pack_poly2_eta HAETAE_NAMESPACE(pack_poly2_eta)
+void pack_poly2_eta(uint8_t *r, const poly *a);
+#define unpack_poly2_eta HAETAE_NAMESPACE(unpack_poly2_eta)
+void unpack_poly2_eta(poly *r, const uint8_t *a);
+
+#define pack_vec_highbits HAETAE_NAMESPACE(pack_vec_highbits)
+void pack_vec_highbits(uint8_t *buf, const polyveck *v);
+
+#define pack_poly_highbits HAETAE_NAMESPACE(pack_poly_highbits)
+void pack_poly_highbits(uint8_t *buf, const poly *a);
+
+#define pack_poly_lsb HAETAE_NAMESPACE(pack_poly_lsb)
+void pack_poly_lsb(uint8_t *buf, const poly *a);
+
+#endif /* !HAETAE_PACKING_H */
diff --git a/cbits/third_party/HAETAE/include/params.h b/cbits/third_party/HAETAE/include/params.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/params.h
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_PARAMS_H
+#define HAETAE_PARAMS_H
+
+#include "config.h"
+
+#define HAETAE_SEEDBYTES 32
+#define HAETAE_CRHBYTES 64
+#define HAETAE_N 256
+
+#define HAETAE_Q 64513
+#define HAETAE_DQ (HAETAE_Q << 1) // 2Q
+
+#define HAETAE_MODE HAETAE_CONFIG_MODE
+#define HAETAE_NAMESPACE(s)                                                    \
+  HAETAE_CONCAT(HAETAE_CONFIG_NAMESPACE_PREFIX_MODE, s)
+#define HAETAE_CRYPTO_ALGNAME                                                  \
+  HAETAE_STR(HAETAE_CONCAT(haetae_, HAETAE_CONFIG_MODE_STRING))
+
+#if HAETAE_MODE == HAETAE_MODE2
+#define HAETAE_K 2
+#define HAETAE_L 4
+#define HAETAE_TAU 58
+#define HAETAE_B0 9846.02
+#define HAETAE_B1 9838.98
+#define HAETAE_B2 12777.52
+#define HAETAE_GAMMA 48.858
+#define HAETAE_LN 8192 // Large N
+#define HAETAE_LNHALF 4096
+#define HAETAE_LNBITS 13
+#define HAETAE_SQNM 39.191835884530846 // \sqrt(n * m)
+#define HAETAE_CRYPTO_BYTES 1474
+
+#define HAETAE_BASE_ENC_HB_Z1 132
+#define HAETAE_BASE_ENC_H 7
+
+#define HAETAE_ALPHA_HINT 512
+#define HAETAE_LOG_ALPHA_HINT 9
+
+#define HAETAE_POLYQ_PACKEDBYTES 480 // 16bits * HAETAE_N / 8bits
+
+#elif HAETAE_MODE == HAETAE_MODE3
+#define HAETAE_K 3
+#define HAETAE_L 6
+#define HAETAE_TAU 80
+#define HAETAE_B0 18314.98
+#define HAETAE_B1 18307.70
+#define HAETAE_B2 21906.65
+#define HAETAE_GAMMA 57.707
+#define HAETAE_LN 8192 // Large N
+#define HAETAE_LNHALF 4096
+#define HAETAE_LNBITS 13
+#define HAETAE_SQNM 48.0
+#define HAETAE_CRYPTO_BYTES 2349
+
+#define HAETAE_BASE_ENC_HB_Z1 376
+#define HAETAE_BASE_ENC_H 127
+
+#define HAETAE_ALPHA_HINT 512
+#define HAETAE_LOG_ALPHA_HINT 9
+
+#define HAETAE_POLYQ_PACKEDBYTES 480 // 16bits * HAETAE_N / 8bits
+
+#elif HAETAE_MODE == HAETAE_MODE5
+#define HAETAE_K 4
+#define HAETAE_L 7
+#define HAETAE_TAU 128
+#define HAETAE_B0 22343.66
+#define HAETAE_B1 22334.95
+#define HAETAE_B2 24441.49
+#define HAETAE_GAMMA 55.13
+#define HAETAE_LN 8192 // Large N
+#define HAETAE_LNHALF 4096
+#define HAETAE_LNBITS 13
+#define HAETAE_SQNM 53.0659966456864
+#define HAETAE_CRYPTO_BYTES 2948
+
+#define HAETAE_BASE_ENC_HB_Z1 501
+#define HAETAE_BASE_ENC_H 358
+
+#define HAETAE_ALPHA_HINT 256
+#define HAETAE_LOG_ALPHA_HINT 8
+
+#define HAETAE_POLYQ_PACKEDBYTES 512 // 16bits * HAETAE_N / 8bits
+
+#endif // HAETAE_MODE
+
+#define HAETAE_HALF_ALPHA_HINT (HAETAE_ALPHA_HINT >> 1) // ALPHA / 2
+
+#define HAETAE_B0SQ ((uint64_t)(HAETAE_B0 * HAETAE_B0))
+#define HAETAE_B1SQ ((uint64_t)(HAETAE_B1 * HAETAE_B1))
+#define HAETAE_B2SQ ((uint64_t)(HAETAE_B2 * HAETAE_B2))
+
+#define HAETAE_M (HAETAE_L - 1)
+
+#define HAETAE_ETA 1
+#define HAETAE_POLYETA_PACKEDBYTES 64
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+#define HAETAE_POLY2ETA_PACKEDBYTES 96
+#elif HAETAE_MODE == HAETAE_MODE5
+#define HAETAE_POLY2ETA_PACKEDBYTES 64
+#endif
+
+#define HAETAE_POLYC_PACKEDBYTES 32 // 1bit * HAETAE_N / 8bits
+#define HAETAE_POLY_HIGHBITS_PACKEDBYTES (HAETAE_N * 9 / 8)
+#define HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES                                   \
+  (HAETAE_POLY_HIGHBITS_PACKEDBYTES * HAETAE_K)
+
+#define HAETAE_CRYPTO_PUBLICKEYBYTES                                           \
+  (HAETAE_SEEDBYTES + HAETAE_K * HAETAE_POLYQ_PACKEDBYTES) // seed + b
+#define HAETAE_CRYPTO_SECRETKEYBYTES                                           \
+  (HAETAE_CRYPTO_PUBLICKEYBYTES + HAETAE_M * HAETAE_POLYETA_PACKEDBYTES +      \
+   HAETAE_K * HAETAE_POLY2ETA_PACKEDBYTES +                                    \
+   HAETAE_SEEDBYTES) // vk + s + HAETAE_K
+
+#endif /* !HAETAE_PARAMS_H */
diff --git a/cbits/third_party/HAETAE/include/poly.h b/cbits/third_party/HAETAE/include/poly.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/poly.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_POLY_H
+#define HAETAE_POLY_H
+
+#include "params.h"
+
+#include <stdint.h>
+
+typedef struct {
+  int32_t coeffs[HAETAE_N];
+} poly;
+
+#define poly_add HAETAE_NAMESPACE(poly_add)
+void poly_add(poly *c, const poly *a, const poly *b);
+#define poly_sub HAETAE_NAMESPACE(poly_sub)
+void poly_sub(poly *c, const poly *a, const poly *b);
+#define poly_pointwise_montgomery HAETAE_NAMESPACE(poly_pointwise_montgomery)
+void poly_pointwise_montgomery(poly *c, const poly *a, const poly *b);
+
+#define poly_reduce2q HAETAE_NAMESPACE(poly_reduce2q)
+void poly_reduce2q(poly *a);
+#define poly_freeze2q HAETAE_NAMESPACE(poly_freeze2q)
+void poly_freeze2q(poly *a);
+#define poly_freeze HAETAE_NAMESPACE(poly_freeze)
+void poly_freeze(poly *a);
+
+#define poly_highbits HAETAE_NAMESPACE(poly_highbits)
+void poly_highbits(poly *a2, const poly *a);
+#define poly_lowbits HAETAE_NAMESPACE(poly_lowbits)
+void poly_lowbits(poly *a1, const poly *a);
+#define poly_compose HAETAE_NAMESPACE(poly_compose)
+void poly_compose(poly *a, const poly *ha, const poly *la);
+#define poly_lsb HAETAE_NAMESPACE(poly_lsb)
+void poly_lsb(poly *a0, const poly *a);
+
+#define poly_uniform HAETAE_NAMESPACE(poly_uniform)
+void poly_uniform(poly *a, const uint8_t seed[HAETAE_SEEDBYTES],
+                  uint16_t nonce);
+#define poly_uniform_eta HAETAE_NAMESPACE(poly_uniform_eta)
+void poly_uniform_eta(poly *a, const uint8_t seed[HAETAE_CRHBYTES],
+                      uint16_t nonce);
+#define poly_challenge HAETAE_NAMESPACE(poly_challenge)
+void poly_challenge(
+    poly *c,
+    const uint8_t highbits_lsb[HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES +
+                               HAETAE_POLYC_PACKEDBYTES],
+    const uint8_t mu[HAETAE_SEEDBYTES]);
+
+#define poly_decomposed_pack HAETAE_NAMESPACE(poly_decomposed_pack)
+void poly_decomposed_pack(uint8_t *buf, const poly *a);
+#define poly_decomposed_unpack HAETAE_NAMESPACE(poly_decomposed_unpack)
+void poly_decomposed_unpack(poly *a, const uint8_t *buf);
+
+#define poly_fromcrt HAETAE_NAMESPACE(poly_fromcrt)
+void poly_fromcrt(poly *w, const poly *u, const poly *v);
+#define poly_fromcrt0 HAETAE_NAMESPACE(poly_fromcrt0)
+void poly_fromcrt0(poly *w, const poly *u);
+
+#define poly_ntt HAETAE_NAMESPACE(poly_ntt)
+void poly_ntt(poly *a);
+#define poly_invntt_tomont HAETAE_NAMESPACE(poly_invntt_tomont)
+void poly_invntt_tomont(poly *a);
+
+#endif /* !HAETAE_POLY_H */
diff --git a/cbits/third_party/HAETAE/include/polyfix.h b/cbits/third_party/HAETAE/include/polyfix.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/polyfix.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_POLYFIX_H
+#define HAETAE_POLYFIX_H
+
+#include "params.h"
+#include "poly.h"
+#include "polyvec.h"
+
+#include <stdint.h>
+
+typedef struct {
+  int32_t coeffs[HAETAE_N];
+} polyfix;
+
+#define polyfix_norm2 HAETAE_NAMESPACE(polyfix_norm2)
+double polyfix_norm2(const polyfix *a);
+
+#define polyfix_round HAETAE_NAMESPACE(polyfix_round)
+void polyfix_round(poly *a, const polyfix *b);
+
+#define polyfix_add HAETAE_NAMESPACE(polyfix_add)
+void polyfix_add(polyfix *c, const polyfix *a, const poly *b);
+
+typedef struct {
+  polyfix vec[HAETAE_K];
+} polyfixveck;
+
+#define polyfixveck_add HAETAE_NAMESPACE(polyfixveck_add)
+void polyfixveck_add(polyfixveck *w, const polyfixveck *u, const polyveck *v);
+#define polyfixfixveck_sub HAETAE_NAMESPACE(polyfixfixveck_sub)
+void polyfixfixveck_sub(polyfixveck *w, const polyfixveck *u,
+                        const polyfixveck *v);
+#define polyfixveck_double HAETAE_NAMESPACE(polyfixveck_double)
+void polyfixveck_double(polyfixveck *b, const polyfixveck *a);
+
+#define polyfixveck_round HAETAE_NAMESPACE(polyfixveck_round)
+void polyfixveck_round(polyveck *a, const polyfixveck *b);
+
+typedef struct {
+  polyfix vec[HAETAE_L];
+} polyfixvecl;
+
+#define polyfixvecl_add HAETAE_NAMESPACE(polyfixvecl_add)
+void polyfixvecl_add(polyfixvecl *w, const polyfixvecl *u, const polyvecl *v);
+#define polyfixfixvecl_sub HAETAE_NAMESPACE(polyfixfixvecl_sub)
+void polyfixfixvecl_sub(polyfixvecl *w, const polyfixvecl *u,
+                        const polyfixvecl *v);
+#define polyfixvecl_double HAETAE_NAMESPACE(polyfixvecl_double)
+void polyfixvecl_double(polyfixvecl *b, const polyfixvecl *a);
+
+#define polyfixvecl_round HAETAE_NAMESPACE(polyfixvecl_round)
+void polyfixvecl_round(polyvecl *a, const polyfixvecl *b);
+
+#define polyfixveclk_sqnorm2 HAETAE_NAMESPACE(polyfixveclk_sqnorm2)
+uint64_t polyfixveclk_sqnorm2(const polyfixvecl *a, const polyfixveck *b);
+
+#define polyfixveclk_sample_hyperball                                          \
+  HAETAE_NAMESPACE(polyfixveclk_sample_hyperball)
+uint16_t polyfixveclk_sample_hyperball(polyfixvecl *y1, polyfixveck *y2,
+                                       uint8_t *b,
+                                       const uint8_t seed[HAETAE_CRHBYTES],
+                                       const uint16_t nonce);
+
+#endif /* !HAETAE_POLYFIX_H */
diff --git a/cbits/third_party/HAETAE/include/polymat.h b/cbits/third_party/HAETAE/include/polymat.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/polymat.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_POLYMAT_H
+#define HAETAE_POLYMAT_H
+
+#include "params.h"
+#include "polyvec.h"
+
+#include <stdint.h>
+
+#define polymatkl_expand_matA HAETAE_NAMESPACE(polymatkl_expand_matA)
+void polymatkl_expand_matA(polyvecl mat[HAETAE_K],
+                           const uint8_t rho[HAETAE_SEEDBYTES]);
+
+#define polymatkm_expand_matA HAETAE_NAMESPACE(polymatkm_expand_matA)
+void polymatkm_expand_matA(polyvecm mat[HAETAE_K],
+                           const uint8_t rho[HAETAE_SEEDBYTES]);
+
+#define polymatkm_pointwise_montgomery                                         \
+  HAETAE_NAMESPACE(polymatkm_pointwise_montgomery)
+void polymatkm_pointwise_montgomery(polyveck *t, const polyvecm mat[HAETAE_K],
+                                    const polyvecm *v);
+
+#define polymatkl_pointwise_montgomery                                         \
+  HAETAE_NAMESPACE(polymatkl_pointwise_montgomery)
+void polymatkl_pointwise_montgomery(polyveck *t, const polyvecl mat[HAETAE_K],
+                                    const polyvecl *v);
+
+#define polymatkl_double HAETAE_NAMESPACE(polymatkl_double)
+void polymatkl_double(polyvecl mat[HAETAE_K]);
+
+#endif /* !HAETAE_POLYMAT_H */
diff --git a/cbits/third_party/HAETAE/include/polyvec.h b/cbits/third_party/HAETAE/include/polyvec.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/polyvec.h
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_POLYVEC_H
+#define HAETAE_POLYVEC_H
+
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+/* Vectors of polynomials of length HAETAE_K */
+typedef struct {
+  poly vec[HAETAE_K];
+} polyveck;
+
+#define polyveck_add HAETAE_NAMESPACE(polyveck_add)
+void polyveck_add(polyveck *w, const polyveck *u, const polyveck *v);
+#define polyveck_sub HAETAE_NAMESPACE(polyveck_sub)
+void polyveck_sub(polyveck *w, const polyveck *u, const polyveck *v);
+#define polyveck_double HAETAE_NAMESPACE(polyveck_double)
+void polyveck_double(polyveck *b);
+
+#define polyveck_reduce2q HAETAE_NAMESPACE(polyveck_reduce2q)
+void polyveck_reduce2q(polyveck *v);
+#define polyveck_freeze2q HAETAE_NAMESPACE(polyveck_freeze2q)
+void polyveck_freeze2q(polyveck *v);
+#define polyveck_freeze HAETAE_NAMESPACE(polyveck_freeze)
+void polyveck_freeze(polyveck *v);
+
+#define polyveck_uniform_eta HAETAE_NAMESPACE(polyveck_uniform_eta)
+void polyveck_uniform_eta(polyveck *v, const uint8_t seed[HAETAE_CRHBYTES],
+                          uint16_t nonce);
+#define polyveck_expand_vecA HAETAE_NAMESPACE(polyveck_expand_vecA)
+void polyveck_expand_vecA(polyveck *v, const uint8_t seed[HAETAE_SEEDBYTES]);
+
+#define polyveck_ntt HAETAE_NAMESPACE(polyveck_ntt)
+void polyveck_ntt(polyveck *x);
+
+#define polyveck_invntt_tomont HAETAE_NAMESPACE(polyveck_invntt_tomont)
+void polyveck_invntt_tomont(polyveck *x);
+
+#define polyveck_double_negate HAETAE_NAMESPACE(polyveck_double_negate)
+void polyveck_double_negate(polyveck *x);
+
+#define polyveck_frommont HAETAE_NAMESPACE(polyveck_frommont)
+void polyveck_frommont(polyveck *x);
+
+#define polyveck_poly_pointwise_montgomery                                     \
+  HAETAE_NAMESPACE(polyveck_poly_pointwise_montgomery)
+void polyveck_poly_pointwise_montgomery(polyveck *w, const polyveck *u,
+                                        const poly *v);
+
+#define polyveck_poly_fromcrt HAETAE_NAMESPACE(polyveck_poly_fromcrt)
+void polyveck_poly_fromcrt(polyveck *w, const polyveck *u, const poly *v);
+
+#define polyveck_highbits_hint HAETAE_NAMESPACE(polyveck_highbits_hint)
+void polyveck_highbits_hint(polyveck *w, const polyveck *v);
+
+#define polyveck_cneg HAETAE_NAMESPACE(polyveck_cneg)
+void polyveck_cneg(polyveck *v, const uint8_t b);
+
+#define polyveck_caddDQ2ALPHA HAETAE_NAMESPACE(polyveck_caddDQ2ALPHA)
+void polyveck_caddDQ2ALPHA(polyveck *h);
+
+#define polyveck_csubDQ2ALPHA HAETAE_NAMESPACE(polyveck_csubDQ2ALPHA)
+void polyveck_csubDQ2ALPHA(polyveck *v);
+
+#define polyveck_mul_alpha HAETAE_NAMESPACE(polyveck_mul_alpha)
+void polyveck_mul_alpha(polyveck *v, const polyveck *u);
+
+#define polyveck_div2 HAETAE_NAMESPACE(polyveck_div2)
+void polyveck_div2(polyveck *v);
+
+#define polyveck_caddq HAETAE_NAMESPACE(polyveck_caddq)
+void polyveck_caddq(polyveck *v);
+
+#define polyveck_decompose_vk HAETAE_NAMESPACE(polyveck_decompose_vk)
+void polyveck_decompose_vk(polyveck *v0, polyveck *v);
+
+#define polyveck_sqnorm2 HAETAE_NAMESPACE(polyveck_sqnorm2)
+uint64_t polyveck_sqnorm2(const polyveck *b);
+
+/* Vectors of polynomials of length L */
+typedef struct {
+  poly vec[HAETAE_L];
+} polyvecl;
+
+#define polyvecl_cneg HAETAE_NAMESPACE(polyvecl_cneg)
+void polyvecl_cneg(polyvecl *v, const uint8_t b);
+
+#define polyvecl_sqnorm2 HAETAE_NAMESPACE(polyvecl_sqnorm2)
+uint64_t polyvecl_sqnorm2(const polyvecl *a);
+
+#define polyvecl_ntt HAETAE_NAMESPACE(polyvecl_ntt)
+void polyvecl_ntt(polyvecl *x);
+
+#define polyvecl_highbits HAETAE_NAMESPACE(polyvecl_highbits)
+void polyvecl_highbits(polyvecl *v2, const polyvecl *v);
+#define polyvecl_lowbits HAETAE_NAMESPACE(polyvecl_lowbits)
+void polyvecl_lowbits(polyvecl *v2, const polyvecl *v);
+
+#define polyvecl_pointwise_acc_montgomery                                      \
+  HAETAE_NAMESPACE(polyvecl_pointwise_acc_montgomery)
+void polyvecl_pointwise_acc_montgomery(poly *w, const polyvecl *u,
+                                       const polyvecl *v);
+
+/* Vectors of polynomials of length M */
+typedef struct {
+  poly vec[HAETAE_M];
+} polyvecm;
+
+#define polyvecm_ntt HAETAE_NAMESPACE(polyvecm_ntt)
+void polyvecm_ntt(polyvecm *x);
+
+#define polyvecmk_expand_S HAETAE_NAMESPACE(polyvecmk_expand_S)
+void polyvecmk_expand_S(polyvecm *u, polyveck *v,
+                        const uint8_t seed[HAETAE_CRHBYTES], uint16_t nonce);
+
+#define polyvecm_pointwise_acc_montgomery                                      \
+  HAETAE_NAMESPACE(polyvecm_pointwise_acc_montgomery)
+void polyvecm_pointwise_acc_montgomery(poly *w, const polyvecm *u,
+                                       const polyvecm *v);
+
+#define polyvecmk_sk_singular_value                                            \
+  HAETAE_NAMESPACE(polyvecmk_sk_singular_value)
+int64_t polyvecmk_sk_singular_value(const polyvecm *s1, const polyveck *s2);
+
+#define polyvecmk_sing_value HAETAE_NAMESPACE(polyvecmk_sing_value)
+int64_t polyvecmk_sing_value(const polyvecm *s1, const polyveck *s2);
+
+#endif /* !HAETAE_POLYVEC_H */
diff --git a/cbits/third_party/HAETAE/include/randombytes.h b/cbits/third_party/HAETAE/include/randombytes.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/randombytes.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_RANDOMBYTES_H
+#define HAETAE_RANDOMBYTES_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define RNG_SUCCESS 0
+#define RNG_BAD_MAXLEN -1
+#define RNG_BAD_OUTBUF -2
+#define RNG_BAD_REQ_LEN -3
+#define RNG_FAIL_SYSCALL -4
+
+typedef struct {
+  unsigned char buffer[16];
+  int buffer_pos;
+  unsigned long length_remaining;
+  unsigned char key[32];
+  unsigned char ctr[16];
+} AES_XOF_struct;
+
+typedef struct {
+  unsigned char Key[32];
+  unsigned char V[16];
+  int reseed_counter;
+  int init;
+} AES256_CTR_DRBG_struct;
+
+void AES256_CTR_DRBG_Update(unsigned char *provided_data, unsigned char *Key,
+                            unsigned char *V);
+
+void randombytes_init(unsigned char *entropy_input,
+                      unsigned char *personalization_string,
+                      int security_strength);
+
+int randombytes(uint8_t *out, size_t outlen);
+
+#endif /* !HAETAE_RANDOMBYTES_H */
diff --git a/cbits/third_party/HAETAE/include/rans_byte.h b/cbits/third_party/HAETAE/include/rans_byte.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/rans_byte.h
@@ -0,0 +1,336 @@
+// Simple byte-aligned rANS encoder/decoder - public domain - Fabian 'ryg'
+// Giesen 2014
+//
+// Not intended to be "industrial strength"; just meant to illustrate the
+// general idea.
+
+#ifndef RANS_BYTE_HEADER
+#define RANS_BYTE_HEADER
+
+#include <stdint.h>
+
+#ifdef assert
+#define RansAssert assert
+#else
+#define RansAssert(x)
+#endif
+
+// READ ME FIRST:
+//
+// This is designed like a typical arithmetic coder API, but there's three
+// twists you absolutely should be aware of before you start hacking:
+//
+// 1. You need to encode data in *reverse* - last symbol first. rANS works
+//    like a stack: last in, first out.
+// 2. Likewise, the encoder outputs bytes *in reverse* - that is, you give
+//    it a pointer to the *end* of your buffer (exclusive), and it will
+//    slowly move towards the beginning as more bytes are emitted.
+// 3. Unlike basically any other entropy coder implementation you might
+//    have used, you can interleave data from multiple independent rANS
+//    encoders into the same bytestream without any extra signaling;
+//    you can also just write some bytes by yourself in the middle if
+//    you want to. This is in addition to the usual arithmetic encoder
+//    property of being able to switch models on the fly. Writing raw
+//    bytes can be useful when you have some data that you know is
+//    incompressible, and is cheaper than going through the rANS encode
+//    function. Using multiple rANS coders on the same byte stream wastes
+//    a few bytes compared to using just one, but execution of two
+//    independent encoders can happen in parallel on superscalar and
+//    Out-of-Order CPUs, so this can be *much* faster in tight decoding
+//    loops.
+//
+//    This is why all the rANS functions take the write pointer as an
+//    argument instead of just storing it in some context struct.
+
+// --------------------------------------------------------------------------
+
+// L ('l' in the paper) is the lower bound of our normalization interval.
+// Between this and our byte-aligned emission, we use 31 (not 32!) bits.
+// This is done intentionally because exact reciprocals for 31-bit uints
+// fit in 32-bit uints: this permits some optimizations during encoding.
+#define RANS_BYTE_L                                                            \
+  (UINT32_C(1) << 23) // lower bound of our normalization interval
+
+// State for a rANS encoder. Yep, that's all there is to it.
+typedef uint32_t RansState;
+
+// Initialize a rANS encoder.
+static inline void RansEncInit(RansState *r) { *r = RANS_BYTE_L; }
+
+// Renormalize the encoder. Internal function.
+static inline RansState RansEncRenorm(RansState x, uint8_t **pptr,
+                                      uint32_t freq, uint32_t scale_bits) {
+  uint32_t x_max =
+      ((RANS_BYTE_L >> scale_bits) << 8) * freq; // this turns into a shift.
+  if (x >= x_max) {
+    uint8_t *ptr = *pptr;
+    do {
+      *--ptr = (uint8_t)(x & 0xff);
+      x >>= 8;
+    } while (x >= x_max);
+    *pptr = ptr;
+  }
+  return x;
+}
+
+// Encodes a single symbol with range start "start" and frequency "freq".
+// All frequencies are assumed to sum to "1 << scale_bits", and the
+// resulting bytes get written to ptr (which is updated).
+//
+// NOTE: With rANS, you need to encode symbols in *reverse order*, i.e. from
+// beginning to end! Likewise, the output bytestream is written *backwards*:
+// ptr starts pointing at the end of the output buffer and keeps decrementing.
+static inline void RansEncPut(RansState *r, uint8_t **pptr, uint32_t start,
+                              uint32_t freq, uint32_t scale_bits) {
+  // renormalize
+  RansState x = RansEncRenorm(*r, pptr, freq, scale_bits);
+
+  // x = C(s,x)
+  *r = ((x / freq) << scale_bits) + (x % freq) + start;
+}
+
+// Flushes the rANS encoder.
+static inline void RansEncFlush(RansState *r, uint8_t **pptr) {
+  uint32_t x = *r;
+  uint8_t *ptr = *pptr;
+
+  ptr -= 4;
+  ptr[0] = (uint8_t)(x >> 0);
+  ptr[1] = (uint8_t)(x >> 8);
+  ptr[2] = (uint8_t)(x >> 16);
+  ptr[3] = (uint8_t)(x >> 24);
+
+  *pptr = ptr;
+}
+
+// Initializes a rANS decoder.
+// Unlike the encoder, the decoder works forwards as you'd expect.
+static inline int RansDecInit(RansState *r, uint8_t **pptr) {
+  uint32_t x;
+  uint8_t *ptr = *pptr;
+
+  x = (uint32_t)ptr[0] << 0;
+  x |= (uint32_t)ptr[1] << 8;
+  x |= (uint32_t)ptr[2] << 16;
+  x |= (uint32_t)ptr[3] << 24;
+  if (x < RANS_BYTE_L || (RANS_BYTE_L << 8) <= x)
+    return 1; // initial state out of range
+
+  ptr += 4;
+  *pptr = ptr;
+  *r = x;
+  return 0;
+}
+
+// Returns the current cumulative frequency (map it to a symbol yourself!)
+static inline uint32_t RansDecGet(RansState *r, uint32_t scale_bits) {
+  return *r & ((UINT32_C(1) << scale_bits) - 1);
+}
+
+// Advances in the bit stream by "popping" a single symbol with range start
+// "start" and frequency "freq". All frequencies are assumed to sum to "1 <<
+// scale_bits", and the resulting bytes get written to ptr (which is updated).
+static inline void RansDecAdvance(RansState *r, uint8_t **pptr,
+                                  const uint8_t *end, uint32_t start,
+                                  uint32_t freq, uint32_t scale_bits) {
+  uint32_t mask = (UINT32_C(1) << scale_bits) - 1;
+
+  // s, x = D(x)
+  uint32_t x = *r;
+  x = freq * (x >> scale_bits) + (x & mask) - start;
+
+  // renormalize
+  if (x < RANS_BYTE_L && *pptr < end) {
+    uint8_t *ptr = *pptr;
+    do
+      x = (x << 8) | *ptr++;
+    while (x < RANS_BYTE_L && ptr < end);
+    *pptr = ptr;
+  }
+
+  *r = x;
+}
+
+// --------------------------------------------------------------------------
+
+// That's all you need for a full encoder; below here are some utility
+// functions with extra convenience or optimizations.
+
+// Encoder symbol description
+// This (admittedly odd) selection of parameters was chosen to make
+// RansEncPutSymbol as cheap as possible.
+typedef struct {
+  uint32_t x_max;     // (Exclusive) upper bound of pre-normalization interval
+  uint32_t rcp_freq;  // Fixed-point reciprocal frequency
+  uint32_t bias;      // Bias
+  uint16_t cmpl_freq; // Complement of frequency: (1 << scale_bits) - freq
+  uint16_t rcp_shift; // Reciprocal shift
+} RansEncSymbol;
+
+// Decoder symbols are straightforward.
+typedef struct {
+  uint16_t start; // Start of range.
+  uint16_t freq;  // Symbol frequency.
+} RansDecSymbol;
+
+// Initializes an encoder symbol to start "start" and frequency "freq"
+static inline void RansEncSymbolInit(RansEncSymbol *s, uint32_t start,
+                                     uint32_t freq, uint32_t scale_bits) {
+  RansAssert(scale_bits <= 16);
+  RansAssert(start <= (UINT32_C(1) << scale_bits));
+  RansAssert(freq <= (UINT32_C(1) << scale_bits) - start);
+
+  // Say M := 1 << scale_bits.
+  //
+  // The original encoder does:
+  //   x_new = (x/freq)*M + start + (x%freq)
+  //
+  // The fast encoder does (schematically):
+  //   q     = mul_hi(x, rcp_freq) >> rcp_shift   (division)
+  //   r     = x - q*freq                         (remainder)
+  //   x_new = q*M + bias + r                     (new x)
+  // plugging in r into x_new yields:
+  //   x_new = bias + x + q*(M - freq)
+  //        =: bias + x + q*cmpl_freq             (*)
+  //
+  // and we can just precompute cmpl_freq. Now we just need to
+  // set up our parameters such that the original encoder and
+  // the fast encoder agree.
+
+  s->x_max = ((RANS_BYTE_L >> scale_bits) << 8) * freq;
+  s->cmpl_freq = (uint16_t)((1 << scale_bits) - freq);
+  if (freq < 2) {
+    // freq=0 symbols are never valid to encode, so it doesn't matter what
+    // we set our values to.
+    //
+    // freq=1 is tricky, since the reciprocal of 1 is 1; unfortunately,
+    // our fixed-point reciprocal approximation can only multiply by values
+    // smaller than 1.
+    //
+    // So we use the "next best thing": rcp_freq=0xffffffff, rcp_shift=0.
+    // This gives:
+    //   q = mul_hi(x, rcp_freq) >> rcp_shift
+    //     = mul_hi(x, (1<<32) - 1)) >> 0
+    //     = floor(x - x/(2^32))
+    //     = x - 1 if 1 <= x < 2^32
+    // and we know that x>0 (x=0 is never in a valid normalization interval).
+    //
+    // So we now need to choose the other parameters such that
+    //   x_new = x*M + start
+    // plug it in:
+    //     x*M + start                   (desired result)
+    //   = bias + x + q*cmpl_freq        (*)
+    //   = bias + x + (x - 1)*(M - 1)    (plug in q=x-1, cmpl_freq)
+    //   = bias + 1 + (x - 1)*M
+    //   = x*M + (bias + 1 - M)
+    //
+    // so we have start = bias + 1 - M, or equivalently
+    //   bias = start + M - 1.
+    s->rcp_freq = ~UINT32_C(0);
+    s->rcp_shift = 0;
+    s->bias = start + (1 << scale_bits) - 1;
+  } else {
+    // Alverson, "Integer Division using reciprocals"
+    // shift=ceil(log2(freq))
+    uint32_t shift = 0;
+    while (freq > (UINT32_C(1) << shift))
+      shift++;
+
+    s->rcp_freq = (uint32_t)(((UINT64_C(1) << (shift + 31)) + freq - 1) / freq);
+    s->rcp_shift = shift - 1;
+
+    // With these values, 'q' is the correct quotient, so we
+    // have bias=start.
+    s->bias = start;
+  }
+}
+
+// Initialize a decoder symbol to start "start" and frequency "freq"
+static inline void RansDecSymbolInit(RansDecSymbol *s, uint32_t start,
+                                     uint32_t freq) {
+  RansAssert(start <= (1 << 16));
+  RansAssert(freq <= (1 << 16) - start);
+  s->start = (uint16_t)start;
+  s->freq = (uint16_t)freq;
+}
+
+// Encodes a given symbol. This is faster than straight RansEnc since we can do
+// multiplications instead of a divide.
+//
+// See RansEncSymbolInit for a description of how this works.
+static inline void RansEncPutSymbol(RansState *r, uint8_t **pptr,
+                                    RansEncSymbol const *sym) {
+  RansAssert(sym->x_max != 0); // can't encode symbol with freq=0
+
+  // renormalize
+  uint32_t x = *r;
+  uint32_t x_max = sym->x_max;
+  if (x >= x_max) {
+    uint8_t *ptr = *pptr;
+    do {
+      *--ptr = (uint8_t)(x & 0xff);
+      x >>= 8;
+    } while (x >= x_max);
+    *pptr = ptr;
+  }
+
+  // x = C(s,x)
+  // NOTE: written this way so we get a 32-bit "multiply high" when
+  // available. If you're on a 64-bit platform with cheap multiplies
+  // (e.g. x64), just bake the +32 into rcp_shift.
+  uint32_t q =
+      (uint32_t)(((uint64_t)x * sym->rcp_freq) >> 32) >> sym->rcp_shift;
+  *r = x + sym->bias + q * sym->cmpl_freq;
+}
+
+// Equivalent to RansDecAdvance that takes a symbol.
+static inline void RansDecAdvanceSymbol(RansState *r, uint8_t **pptr,
+                                        const uint8_t *end,
+                                        RansDecSymbol const *sym,
+                                        uint32_t scale_bits) {
+  RansDecAdvance(r, pptr, end, sym->start, sym->freq, scale_bits);
+}
+
+// Advances in the bit stream by "popping" a single symbol with range start
+// "start" and frequency "freq". All frequencies are assumed to sum to "1 <<
+// scale_bits". No renormalization or output happens.
+static inline void RansDecAdvanceStep(RansState *r, uint32_t start,
+                                      uint32_t freq, uint32_t scale_bits) {
+  uint32_t mask = (UINT32_C(1) << scale_bits) - 1;
+
+  // s, x = D(x)
+  uint32_t x = *r;
+  *r = freq * (x >> scale_bits) + (x & mask) - start;
+}
+
+// Equivalent to RansDecAdvanceStep that takes a symbol.
+static inline void RansDecAdvanceSymbolStep(RansState *r,
+                                            RansDecSymbol const *sym,
+                                            uint32_t scale_bits) {
+  RansDecAdvanceStep(r, sym->start, sym->freq, scale_bits);
+}
+
+// Renormalize.
+static inline void RansDecRenorm(RansState *r, uint8_t **pptr) {
+  // renormalize
+  uint32_t x = *r;
+  if (x < RANS_BYTE_L) {
+    uint8_t *ptr = *pptr;
+    do
+      x = (x << 8) | *ptr++;
+    while (x < RANS_BYTE_L);
+    *pptr = ptr;
+  }
+
+  *r = x;
+}
+
+// Verify final state
+static inline int RansDecVerify(const RansState *const r) {
+  if (*r != RANS_BYTE_L) {
+    return 1; // the final state is inconsistent with the initial state
+  }
+  return 0;
+}
+
+#endif // RANS_BYTE_HEADER
diff --git a/cbits/third_party/HAETAE/include/reduce.h b/cbits/third_party/HAETAE/include/reduce.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/reduce.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_REDUCE_H
+#define HAETAE_REDUCE_H
+
+#include "params.h"
+
+#include <stdint.h>
+
+#define MONT 14321     // 2^32 % HAETAE_Q
+#define MONTSQ 4214    // 2^64 % HAETAE_Q
+#define QINV 940508161 // q^(-1) mod 2^32
+#define QREC 66575     // 2^32 / HAETAE_Q for Barrett
+#define DQREC 33287    // 2^32 / DQ for Barrett
+
+#define montgomery_reduce HAETAE_NAMESPACE(montgomery_reduce)
+int32_t montgomery_reduce(int64_t a);
+
+#define caddq HAETAE_NAMESPACE(caddq)
+int32_t caddq(int32_t a);
+
+#define freeze HAETAE_NAMESPACE(freeze)
+int32_t freeze(int32_t a);
+
+#define reduce32_2q HAETAE_NAMESPACE(reduce32_2q)
+int32_t reduce32_2q(int32_t a);
+
+#define freeze2q HAETAE_NAMESPACE(freeze2q)
+int32_t freeze2q(int32_t a);
+
+#endif /* !HAETAE_REDUCE_H */
diff --git a/cbits/third_party/HAETAE/include/sampler.h b/cbits/third_party/HAETAE/include/sampler.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/sampler.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_SAMPLER_H
+#define HAETAE_SAMPLER_H
+
+#include "fixpoint.h"
+#include "params.h"
+
+#include <stdint.h>
+
+#define rej_uniform HAETAE_NAMESPACE(rej_uniform)
+unsigned int rej_uniform(int32_t *a, unsigned int len, const uint8_t *buf,
+                         unsigned int buflen);
+#define rej_eta HAETAE_NAMESPACE(rej_eta)
+unsigned int rej_eta(int32_t *a, unsigned int len, const uint8_t *buf,
+                     unsigned int buflen);
+
+#define sample_gauss_N HAETAE_NAMESPACE(sample_gauss_N)
+void sample_gauss_N(uint64_t *r, uint8_t *signs, fp96_76 *sqsum,
+                    const uint8_t seed[HAETAE_CRHBYTES], uint16_t nonce,
+                    size_t len);
+
+#endif /* !HAETAE_SAMPLER_H */
diff --git a/cbits/third_party/HAETAE/include/symmetric.h b/cbits/third_party/HAETAE/include/symmetric.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/include/symmetric.h
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef HAETAE_SYMMETRIC_H
+#define HAETAE_SYMMETRIC_H
+
+#include "fips202.h"
+#include "params.h"
+
+#include <stdint.h>
+
+typedef keccak_state xof256_state;
+
+void shake256_absorb_twice(keccak_state *state, const uint8_t *in1,
+                           size_t in1len, const uint8_t *in2, size_t in2len);
+void shake256_absorb_thrice(keccak_state *state, const uint8_t *in1,
+                            size_t in1len, const uint8_t *in2, size_t in2len,
+                            const uint8_t *in3, size_t in3len);
+
+#define XOF256_BLOCKBYTES SHAKE256_RATE
+
+#define xof256_absorb_once(STATE, IN, IN_LEN)                                  \
+  shake256_absorb_once(STATE, IN, IN_LEN)
+#define xof256_absorb_twice(STATE, IN, IN_LEN, IN2, IN2_LEN)                   \
+  shake256_absorb_twice(STATE, IN, IN_LEN, IN2, IN2_LEN)
+#define xof256_absorb_thrice(STATE, IN, IN_LEN, IN2, IN2_LEN, IN3, IN3_LEN)    \
+  shake256_absorb_thrice(STATE, IN, IN_LEN, IN2, IN2_LEN, IN3, IN3_LEN)
+#define xof256_squeeze(OUT, OUT_LEN, STATE)                                    \
+  shake256_squeeze(OUT, OUT_LEN, STATE)
+#define xof256_squeezeblocks(OUT, OUTBLOCKS, STATE)                            \
+  shake256_squeezeblocks(OUT, OUTBLOCKS, STATE)
+
+typedef keccak_state stream128_state;
+typedef keccak_state stream256_state;
+
+void shake128_stream_init(keccak_state *state,
+                          const uint8_t seed[HAETAE_SEEDBYTES], uint16_t nonce);
+void shake256_stream_init(keccak_state *state,
+                          const uint8_t seed[HAETAE_CRHBYTES], uint16_t nonce);
+
+#define STREAM128_BLOCKBYTES SHAKE128_RATE // 168
+#define STREAM256_BLOCKBYTES SHAKE256_RATE // 136
+
+#define stream128_init(STATE, SEED, NONCE)                                     \
+  shake128_stream_init(STATE, SEED, NONCE)
+#define stream128_squeezeblocks(OUT, OUTBLOCKS, STATE)                         \
+  shake128_squeezeblocks(OUT, OUTBLOCKS, STATE)
+#define stream256_init(STATE, SEED, NONCE)                                     \
+  shake256_stream_init(STATE, SEED, NONCE)
+#define stream256_squeezeblocks(OUT, OUTBLOCKS, STATE)                         \
+  shake256_squeezeblocks(OUT, OUTBLOCKS, STATE)
+
+#endif /* !HAETAE_SYMMETRIC_H */
diff --git a/cbits/third_party/HAETAE/src/decompose.c b/cbits/third_party/HAETAE/src/decompose.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/decompose.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: MIT
+
+#include "decompose.h"
+#include "params.h"
+
+#include <stdint.h>
+
+/*************************************************
+ * Name:        decompose_z1
+ *
+ * Description: For finite field element r, compute high and lowbits
+ *              hb, lb such that r = hb * b + lb with -b/2 <= lb < b/2,
+ *              for b = alpha = 256.
+ *
+ * Arguments:   - int32_t r: input element
+ *              - int32_t *lowbits: pointer to output element lb
+ *              - int32_t *highbits: pointer to output element hb
+ *
+ * Specification: Implements @[Algorithm 20, HighBits] and
+ *                @[Algorithm 21, LowBits] with alpha = 256
+ **************************************************/
+void decompose_z1(int32_t *highbits, int32_t *lowbits, const int32_t r) {
+  const int alpha = 256; // TODO magic numbers!
+  const int log_alpha = 8;
+
+  int32_t lb, center;
+  uint32_t alpha_mask = alpha - 1;
+
+  lb = r & alpha_mask;
+  center = ((alpha >> 1) - (lb + 1)) >> 31; // if lb >= HALF_ALPHA
+  lb -= alpha & center;
+  *lowbits = lb;
+  *highbits = (r + (alpha >> 1)) >> log_alpha;
+}
+
+/*************************************************
+ * Name:        decompose_hint
+ *
+ * Description: For finite field element r, compute highbits
+ *              hb, lb such that r = hb * b + lb with -b/4 < lb <= b/4.
+ *
+ * Arguments:   - int32_t r: input element
+ *              - int32_t *highbits: pointer to output element hb
+ *
+ * Specification: Implements @[Algorithm 22, DecomposeHint]
+ **************************************************/
+
+void decompose_hint(int32_t *highbits, const int32_t r) {
+  int32_t hb, edgecase;
+
+  hb = (r + HAETAE_HALF_ALPHA_HINT) >> HAETAE_LOG_ALPHA_HINT;
+  edgecase = ((HAETAE_DQ - 2) / HAETAE_ALPHA_HINT - (hb + 1)) >>
+             31; // if hb == (HAETAE_DQ-2)/ALPHA
+  hb -= (HAETAE_DQ - 2) / HAETAE_ALPHA_HINT & edgecase; // hb = 0
+
+  *highbits = hb;
+}
+
+/*************************************************
+ * Name:        decompose_vk
+ *
+ * Description: For finite field element a, compute a0, a1 such that
+ *              a mod^+ HAETAE_Q = a1*2^D + a0 with -2^{D-1} <= a0 < 2^{D-1}.
+ *              Assumes a to be standard representative.
+ *
+ * Arguments:   - int32_t a: input element
+ *              - int32_t *a0: pointer to output element a0
+ *
+ * Returns:     - a1
+ *
+ * Specification: Implements @[Algorithm 19, DecomposeVK]
+ **************************************************/
+int32_t decompose_vk(int32_t *a0, const int32_t a) {
+  *a0 = a & 1;
+  *a0 -= ((a >> 1) & *a0) << 1;
+  return (a - *a0) >> 1;
+}
diff --git a/cbits/third_party/HAETAE/src/encoding.c b/cbits/third_party/HAETAE/src/encoding.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/encoding.c
@@ -0,0 +1,700 @@
+// SPDX-License-Identifier: MIT
+
+#include "encoding.h"
+#include "params.h"
+#include "rans_byte.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SCALE_BITS 10
+#define SCALE (UINT32_C(1) << SCALE_BITS)
+
+#if HAETAE_MODE == HAETAE_MODE2
+#define M_H 13
+#define OFFSET_H 239
+#define M_HB_Z1 13
+#define OFFSET_HB_Z1 6
+static RansEncSymbol esyms_h[M_H] = {
+    {801112064, -1416664605, 0, 642, 8},
+    {515899392, -2060187564, 382, 778, 7},
+    {136314880, -66076419, 628, 959, 6},
+    {14680064, -1840700269, 693, 1017, 2},
+    {2097152, -1, 1723, 1023, 0},
+    {2097152, -1, 1724, 1023, 0},
+    {2097152, -1, 1725, 1023, 0},
+    {2097152, -1, 1726, 1023, 0},
+    {2097152, -1, 1727, 1023, 0},
+    {2097152, -1, 1728, 1023, 0},
+    {14680064, -1840700269, 706, 1017, 2},
+    {136314880, -66076419, 713, 959, 6},
+    {515899392, -2060187564, 778, 778, 7},
+};
+static RansDecSymbol dsyms_h[M_H] = {
+    {0, 382}, {382, 246}, {628, 65}, {693, 7}, {700, 1},  {701, 1},   {702, 1},
+    {703, 1}, {704, 1},   {705, 1},  {706, 7}, {713, 65}, {778, 246},
+};
+static uint16_t symbol_h[SCALE] = {
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  3,  3,  3,  3,  3,  3,  3,  4,  5,  6,
+    7,  8,  9,  10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+};
+static RansEncSymbol esyms_hb_z1[M_HB_Z1] = {
+    {2097152, -1, 1023, 1023, 0},
+    {2097152, -1, 1024, 1023, 0},
+    {2097152, -1, 1025, 1023, 0},
+    {10485760, -858993459, 3, 1019, 2},
+    {121634816, -1925330167, 8, 966, 5},
+    {515899392, -2060187564, 66, 778, 7},
+    {834666496, -1532375266, 312, 626, 8},
+    {517996544, -2069235255, 710, 777, 7},
+    {123731968, -1965493508, 957, 965, 5},
+    {10485760, -858993459, 1016, 1019, 2},
+    {2097152, -1, 2044, 1023, 0},
+    {2097152, -1, 2045, 1023, 0},
+    {2097152, -1, 2046, 1023, 0},
+};
+static RansDecSymbol dsyms_hb_z1[M_HB_Z1] = {
+    {0, 1},    {1, 1},     {2, 1},     {3, 5},    {8, 58},
+    {66, 246}, {312, 398}, {710, 247}, {957, 59}, {1016, 5},
+    {1021, 1}, {1022, 1},  {1023, 1},
+};
+static uint16_t symbol_hb_z1[SCALE] = {
+    0, 1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,  4,  4,  4, 4, 4, 4, 4, 4, 4, 4,
+    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,  4,  4,  4, 4, 4, 4, 4, 4, 4, 4,
+    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,  4,  4,  4, 4, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  5,  5,  5, 5, 5, 5, 5, 5, 5, 5,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  6,  6,  6, 6, 6, 6, 6, 6, 6, 6,
+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 7, 7, 7,
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,  7,  7,  7, 7, 7, 7, 7, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,  8,  8,  8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,  8,  8,  8, 8, 8, 8, 8, 8, 8, 8,
+    8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 11, 12,
+};
+
+#elif HAETAE_MODE == HAETAE_MODE3
+#define M_H 17
+#define OFFSET_H 235
+#define M_HB_Z1 17
+#define OFFSET_HB_Z1 8
+static RansEncSymbol esyms_h[M_H] = {
+    {557842432, -161464935, 0, 758, 8},
+    {446693376, -1713954085, 266, 811, 7},
+    {236978176, -1862419446, 479, 911, 6},
+    {83886080, -858993459, 592, 984, 5},
+    {18874368, -477218588, 632, 1015, 3},
+    {2097152, -1, 1664, 1023, 0},
+    {2097152, -1, 1665, 1023, 0},
+    {2097152, -1, 1666, 1023, 0},
+    {2097152, -1, 1667, 1023, 0},
+    {2097152, -1, 1668, 1023, 0},
+    {2097152, -1, 1669, 1023, 0},
+    {2097152, -1, 1670, 1023, 0},
+    {2097152, -1, 1671, 1023, 0},
+    {18874368, -477218588, 649, 1015, 3},
+    {83886080, -858993459, 658, 984, 5},
+    {236978176, -1862419446, 698, 911, 6},
+    {446693376, -1713954085, 811, 811, 7},
+};
+static RansDecSymbol dsyms_h[M_H] = {
+    {0, 266}, {266, 213}, {479, 113}, {592, 40},  {632, 9},   {641, 1},
+    {642, 1}, {643, 1},   {644, 1},   {645, 1},   {646, 1},   {647, 1},
+    {648, 1}, {649, 9},   {658, 40},  {698, 113}, {811, 213},
+};
+static uint16_t symbol_h[SCALE] = {
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
+    3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  4,  4,  4,  4,  5,  6,  7,  8,  9,
+    10, 11, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14,
+    14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+    14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15,
+    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+};
+static RansEncSymbol esyms_hb_z1[M_HB_Z1] = {
+    {2097152, -1, 1023, 1023, 0},
+    {2097152, -1, 1024, 1023, 0},
+    {2097152, -1, 1025, 1023, 0},
+    {2097152, -1, 1026, 1023, 0},
+    {16777216, -2147483648, 4, 1016, 2},
+    {77594624, -580400985, 12, 987, 5},
+    {234881024, -1840700269, 49, 912, 6},
+    {452984832, -1749801490, 161, 808, 7},
+    {564133888, -207563475, 377, 755, 8},
+    {452984832, -1749801490, 646, 808, 7},
+    {234881024, -1840700269, 862, 912, 6},
+    {79691776, -678152730, 974, 986, 5},
+    {16777216, -2147483648, 1012, 1016, 2},
+    {2097152, -1, 2043, 1023, 0},
+    {2097152, -1, 2044, 1023, 0},
+    {2097152, -1, 2045, 1023, 0},
+    {2097152, -1, 2046, 1023, 0},
+};
+static RansDecSymbol dsyms_hb_z1[M_HB_Z1] = {
+    {0, 1},    {1, 1},     {2, 1},     {3, 1},     {4, 8},     {12, 37},
+    {49, 112}, {161, 216}, {377, 269}, {646, 216}, {862, 112}, {974, 38},
+    {1012, 8}, {1020, 1},  {1021, 1},  {1022, 1},  {1023, 1},
+};
+static uint16_t symbol_hb_z1[SCALE] = {
+    0,  1,  2,  3,  4,  4,  4,  4,  4,  4,  4,  4,  5,  5,  5,  5,  5,  5,  5,
+    5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,
+    5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 13, 14, 15, 16,
+};
+
+#elif HAETAE_MODE == HAETAE_MODE5
+#define M_H 33
+#define OFFSET_H 471
+#define M_HB_Z1 19
+#define OFFSET_HB_Z1 9
+static RansEncSymbol esyms_h[M_H] = {
+    {255852544, -2041869698, 0, 902, 6},
+    {245366784, -1945583475, 122, 907, 6},
+    {213909504, -1600085855, 239, 922, 6},
+    {169869312, -901412889, 341, 943, 6},
+    {123731968, -1965493508, 422, 965, 5},
+    {81788928, -770891565, 481, 985, 5},
+    {48234496, -1307163959, 520, 1001, 4},
+    {27262976, -1651910498, 543, 1011, 3},
+    {12582912, -1431655765, 556, 1018, 2},
+    {6291456, -1431655765, 562, 1021, 1},
+    {2097152, -1, 1588, 1023, 0},
+    {2097152, -1, 1589, 1023, 0},
+    {2097152, -1, 1590, 1023, 0},
+    {2097152, -1, 1591, 1023, 0},
+    {2097152, -1, 1592, 1023, 0},
+    {2097152, -1, 1593, 1023, 0},
+    {2097152, -1, 1594, 1023, 0},
+    {2097152, -1, 1595, 1023, 0},
+    {2097152, -1, 1596, 1023, 0},
+    {2097152, -1, 1597, 1023, 0},
+    {2097152, -1, 1598, 1023, 0},
+    {2097152, -1, 1599, 1023, 0},
+    {2097152, -1, 1600, 1023, 0},
+    {2097152, -1, 1601, 1023, 0},
+    {6291456, -1431655765, 579, 1021, 1},
+    {12582912, -1431655765, 582, 1018, 2},
+    {27262976, -1651910498, 588, 1011, 3},
+    {50331648, -1431655765, 601, 1000, 4},
+    {81788928, -770891565, 625, 985, 5},
+    {123731968, -1965493508, 664, 965, 5},
+    {169869312, -901412889, 723, 943, 6},
+    {213909504, -1600085855, 804, 922, 6},
+    {247463936, -1965493508, 906, 906, 6},
+};
+static RansDecSymbol dsyms_h[M_H] = {
+    {0, 122},  {122, 117}, {239, 102}, {341, 81}, {422, 59}, {481, 39},
+    {520, 23}, {543, 13},  {556, 6},   {562, 3},  {565, 1},  {566, 1},
+    {567, 1},  {568, 1},   {569, 1},   {570, 1},  {571, 1},  {572, 1},
+    {573, 1},  {574, 1},   {575, 1},   {576, 1},  {577, 1},  {578, 1},
+    {579, 3},  {582, 6},   {588, 13},  {601, 24}, {625, 39}, {664, 59},
+    {723, 81}, {804, 102}, {906, 118},
+};
+static uint16_t symbol_h[SCALE] = {
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+    2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  3,
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
+    3,  3,  3,  3,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
+    4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
+    4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
+    4,  4,  4,  4,  4,  4,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,
+    5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,
+    5,  5,  5,  5,  5,  5,  5,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  8,  8,  8,  8,  8,  8,  9,  9,  9,  10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 25, 25, 25, 25, 25, 25, 26,
+    26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27,
+    27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28,
+    28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+    28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29,
+    29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+    29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+    29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+    29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+    30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+    30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+    30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+    30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+    31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+    31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+    31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+    31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+    31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+    32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+};
+static RansEncSymbol esyms_hb_z1[M_HB_Z1] = {
+    {2097152, -1, 1023, 1023, 0},
+    {2097152, -1, 1024, 1023, 0},
+    {2097152, -1, 1025, 1023, 0},
+    {2097152, -1, 1026, 1023, 0},
+    {4194304, -2147483648, 4, 1022, 0},
+    {27262976, -1651910498, 6, 1011, 3},
+    {100663296, -1431655765, 19, 976, 5},
+    {247463936, -1965493508, 67, 906, 6},
+    {427819008, -1600085855, 185, 820, 7},
+    {513802240, -2051066014, 389, 779, 7},
+    {427819008, -1600085855, 634, 820, 7},
+    {247463936, -1965493508, 838, 906, 6},
+    {100663296, -1431655765, 956, 976, 5},
+    {29360128, -1840700269, 1004, 1010, 3},
+    {4194304, -2147483648, 1018, 1022, 0},
+    {2097152, -1, 2043, 1023, 0},
+    {2097152, -1, 2044, 1023, 0},
+    {2097152, -1, 2045, 1023, 0},
+    {2097152, -1, 2046, 1023, 0},
+};
+static RansDecSymbol dsyms_hb_z1[M_HB_Z1] = {
+    {0, 1},     {1, 1},     {2, 1},    {3, 1},     {4, 2},
+    {6, 13},    {19, 48},   {67, 118}, {185, 204}, {389, 245},
+    {634, 204}, {838, 118}, {956, 48}, {1004, 14}, {1018, 2},
+    {1020, 1},  {1021, 1},  {1022, 1}, {1023, 1},
+};
+static uint16_t symbol_hb_z1[SCALE] = {
+    0,  1,  2,  3,  4,  4,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,
+    6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
+    9,  9,  9,  9,  9,  9,  9,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+    10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+    11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13,
+    13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 15, 16, 17, 18,
+};
+
+#endif // HAETAE_MODE
+#define H_CUT ((M_H - 1) >> 1)
+
+/*************************************************
+ * Name:        encode_h
+ *
+ * Description: rANS encode polynomial vector h
+ *
+ * Arguments:   - uint8_t *buf: pointer to output buffer
+ *              - const int32_t *h: pointer to polynomial vector h
+ *
+ * Specification: Implements @[Algorithm 44, EncodeH]
+ **************************************************/
+uint16_t encode_h(uint8_t *buf, const int32_t *h) {
+  size_t size_encoded;
+  RansState rans;
+  uint8_t *ptr;
+  size_t size_h = HAETAE_N * HAETAE_K;
+  uint32_t tmp;
+  uint8_t s;
+  uint8_t encoding[size_h]; // size_h is a loose upper bound
+
+  RansEncInit(&rans);
+  ptr = encoding + size_h; // end of encoding buffer
+
+  for (size_t i = size_h; i > 0; i--) {
+    tmp = h[i - 1];
+    // check for very unlikely values that we do not encode
+    // to make the encoding cheaper :
+    if (H_CUT < tmp && tmp <= H_CUT + OFFSET_H) {
+      return 0;
+    }
+    // map the upper likely part of symbols next to the lower part
+    // to have a dense and compact distribution:
+    tmp = (tmp > (H_CUT + OFFSET_H)) ? tmp - OFFSET_H : tmp;
+    s = (uint8_t)tmp;
+
+    RansEncPutSymbol(&rans, &ptr, &esyms_h[s]);
+    // check that at least 4 byte remain for memory safety:
+    if (ptr < encoding + 4) {
+      return 0;
+    }
+  }
+
+  RansEncFlush(&rans, &ptr);
+
+  size_encoded = encoding + size_h - ptr;
+  memcpy(buf, ptr, size_encoded);
+  return size_encoded;
+}
+
+/*************************************************
+ * Name:        decode_h
+ *
+ * Description: rANS decode polynomial vector h
+ *
+ * Arguments:   - int32_t *h: pointer to polynomial vector h
+ *              - uint8_t *buf: pointer to output buffer
+ *
+ * Specification: Implements @[Algorithm 45, DecodeH]
+ **************************************************/
+uint16_t decode_h(int32_t *h, const uint8_t *buf, uint16_t size_in) {
+  size_t size_used;
+  RansState rans;
+  size_t size_h = HAETAE_N * HAETAE_K;
+  uint32_t tmp;
+  uint8_t s;
+  const uint8_t *buf_start = buf;
+  const uint8_t *buf_end = buf + size_in;
+
+  if (RansDecInit(&rans, (uint8_t **)&buf)) {
+    return 1; // corrupted initial state
+  }
+
+  for (size_t i = 0; i < size_h; i++) {
+    s = symbol_h[RansDecGet(&rans, SCALE_BITS)];
+    tmp = (uint32_t)s;
+    if (tmp >= M_H) {
+      return 1; // invalid symbol
+    }
+    tmp = (H_CUT < tmp) ? (tmp + OFFSET_H) : tmp;
+    h[i] = tmp;
+    RansDecAdvanceSymbol(&rans, (uint8_t **)&buf, buf_end, &dsyms_h[s],
+                         SCALE_BITS);
+  }
+
+  if (RansDecVerify(&rans)) {
+    return 1; // final state not correct
+  }
+
+  size_used = buf - buf_start;
+  if (size_used != size_in) {
+    return 1; // size does not match
+  }
+  return 0;
+}
+
+/*************************************************
+ * Name:        encode_hb_z1
+ *
+ * Description: rANS encode polynomial vector HighBits(z1)
+ *
+ * Arguments:   - uint8_t *buf: pointer to output buffer
+ *              - const int32_t *hb_z1: pointer to polynomial vector
+ *
+ * Specification: Implements @[Algorithm 46, EncodeHBz1]
+ **************************************************/
+uint16_t encode_hb_z1(uint8_t *buf, const int32_t *hb_z1) {
+  size_t size_encoded;
+  RansState rans;
+  uint8_t *ptr;
+  size_t size_hb_z1 = HAETAE_N * HAETAE_L;
+  int8_t s;
+  int32_t tmp;
+  uint8_t encoding[size_hb_z1]; // size_hb_z1 is a loose upper bound
+
+  RansEncInit(&rans);
+  ptr = encoding + size_hb_z1; // end of output buffer
+
+  for (size_t i = size_hb_z1; i > 0; i--) {
+    // from centered to positive representation:
+    tmp = hb_z1[i - 1] + OFFSET_HB_Z1;
+    // check for very unlikely values that we do not encode
+    // to make the encoding cheaper :
+    if (tmp < 0 || M_HB_Z1 <= tmp) {
+      return 0;
+    }
+    s = (uint8_t)tmp;
+
+    RansEncPutSymbol(&rans, &ptr, &esyms_hb_z1[s]);
+    // check that at least 4 byte remain for memory safety:
+    if (ptr < encoding + 4) {
+      return 0;
+    }
+  }
+  RansEncFlush(&rans, &ptr);
+
+  size_encoded = encoding + size_hb_z1 - ptr;
+  memcpy(buf, ptr, size_encoded);
+  return size_encoded;
+}
+
+/*************************************************
+ * Name:        decode_hb_z1
+ *
+ * Description: rANS decode polynomial vector HighBits(z1)
+ *
+ * Arguments:   - int32_t *hb_z1: pointer to polynomial vector HighBits(z1)
+ *              - uint8_t *buf: pointer to output buffer
+ *
+ * Specification: Implements @[Algorithm 47, DecodeHBz1]
+ **************************************************/
+uint16_t decode_hb_z1(int32_t *hb_z1, const uint8_t *buf, uint16_t size_in) {
+  size_t size_used;
+  RansState rans;
+  size_t size_hb_z1 = HAETAE_N * HAETAE_L;
+  uint32_t tmp;
+  uint8_t s;
+  const uint8_t *buf_start = buf;
+  const uint8_t *buf_end = buf + size_in;
+
+  if (RansDecInit(&rans, (uint8_t **)&buf)) {
+    return 1; // corrupted initial state
+  }
+
+  for (size_t i = 0; i < size_hb_z1; i++) {
+    s = symbol_hb_z1[RansDecGet(&rans, SCALE_BITS)];
+    tmp = (uint32_t)s;
+    if (tmp >= M_HB_Z1) {
+      return 1; // invalid symbol
+    }
+    // from positive to centered representation:
+    hb_z1[i] = tmp - OFFSET_HB_Z1;
+    RansDecAdvanceSymbol(&rans, (uint8_t **)&buf, buf_end, &dsyms_hb_z1[s],
+                         SCALE_BITS);
+  }
+
+  if (RansDecVerify(&rans)) {
+    return 1; // final state not correct
+  }
+
+  size_used = buf - buf_start;
+  if (size_used != size_in) {
+    return 1; // size does not match
+  }
+  return 0;
+}
diff --git a/cbits/third_party/HAETAE/src/fft.c b/cbits/third_party/HAETAE/src/fft.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/fft.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: MIT
+
+#include "fft.h"
+
+/**************************************************************************
+ * Roots of unity. This table is used to initialize the FFT data.
+ * The even entries are reused as twiddle factors in the FFT.
+ * To generate values in Python using numpy:
+ *   rootsf = np.exp(-1j * np.pi * np.arange(256) / 256)
+ *   roots = [(int(r.real), int(r.imag)) for r in np.round(65536 * rootsf)]
+ **************************************************************************/
+static const complex_fp32_16 roots[] = {
+    {.real = +65536, .imag = +0},     {.real = +65531, .imag = -804},
+    {.real = +65516, .imag = -1608},  {.real = +65492, .imag = -2412},
+    {.real = +65457, .imag = -3216},  {.real = +65413, .imag = -4019},
+    {.real = +65358, .imag = -4821},  {.real = +65294, .imag = -5623},
+    {.real = +65220, .imag = -6424},  {.real = +65137, .imag = -7224},
+    {.real = +65043, .imag = -8022},  {.real = +64940, .imag = -8820},
+    {.real = +64827, .imag = -9616},  {.real = +64704, .imag = -10411},
+    {.real = +64571, .imag = -11204}, {.real = +64429, .imag = -11996},
+    {.real = +64277, .imag = -12785}, {.real = +64115, .imag = -13573},
+    {.real = +63944, .imag = -14359}, {.real = +63763, .imag = -15143},
+    {.real = +63572, .imag = -15924}, {.real = +63372, .imag = -16703},
+    {.real = +63162, .imag = -17479}, {.real = +62943, .imag = -18253},
+    {.real = +62714, .imag = -19024}, {.real = +62476, .imag = -19792},
+    {.real = +62228, .imag = -20557}, {.real = +61971, .imag = -21320},
+    {.real = +61705, .imag = -22078}, {.real = +61429, .imag = -22834},
+    {.real = +61145, .imag = -23586}, {.real = +60851, .imag = -24335},
+    {.real = +60547, .imag = -25080}, {.real = +60235, .imag = -25821},
+    {.real = +59914, .imag = -26558}, {.real = +59583, .imag = -27291},
+    {.real = +59244, .imag = -28020}, {.real = +58896, .imag = -28745},
+    {.real = +58538, .imag = -29466}, {.real = +58172, .imag = -30182},
+    {.real = +57798, .imag = -30893}, {.real = +57414, .imag = -31600},
+    {.real = +57022, .imag = -32303}, {.real = +56621, .imag = -33000},
+    {.real = +56212, .imag = -33692}, {.real = +55794, .imag = -34380},
+    {.real = +55368, .imag = -35062}, {.real = +54934, .imag = -35738},
+    {.real = +54491, .imag = -36410}, {.real = +54040, .imag = -37076},
+    {.real = +53581, .imag = -37736}, {.real = +53114, .imag = -38391},
+    {.real = +52639, .imag = -39040}, {.real = +52156, .imag = -39683},
+    {.real = +51665, .imag = -40320}, {.real = +51166, .imag = -40951},
+    {.real = +50660, .imag = -41576}, {.real = +50146, .imag = -42194},
+    {.real = +49624, .imag = -42806}, {.real = +49095, .imag = -43412},
+    {.real = +48559, .imag = -44011}, {.real = +48015, .imag = -44604},
+    {.real = +47464, .imag = -45190}, {.real = +46906, .imag = -45769},
+    {.real = +46341, .imag = -46341}, {.real = +45769, .imag = -46906},
+    {.real = +45190, .imag = -47464}, {.real = +44604, .imag = -48015},
+    {.real = +44011, .imag = -48559}, {.real = +43412, .imag = -49095},
+    {.real = +42806, .imag = -49624}, {.real = +42194, .imag = -50146},
+    {.real = +41576, .imag = -50660}, {.real = +40951, .imag = -51166},
+    {.real = +40320, .imag = -51665}, {.real = +39683, .imag = -52156},
+    {.real = +39040, .imag = -52639}, {.real = +38391, .imag = -53114},
+    {.real = +37736, .imag = -53581}, {.real = +37076, .imag = -54040},
+    {.real = +36410, .imag = -54491}, {.real = +35738, .imag = -54934},
+    {.real = +35062, .imag = -55368}, {.real = +34380, .imag = -55794},
+    {.real = +33692, .imag = -56212}, {.real = +33000, .imag = -56621},
+    {.real = +32303, .imag = -57022}, {.real = +31600, .imag = -57414},
+    {.real = +30893, .imag = -57798}, {.real = +30182, .imag = -58172},
+    {.real = +29466, .imag = -58538}, {.real = +28745, .imag = -58896},
+    {.real = +28020, .imag = -59244}, {.real = +27291, .imag = -59583},
+    {.real = +26558, .imag = -59914}, {.real = +25821, .imag = -60235},
+    {.real = +25080, .imag = -60547}, {.real = +24335, .imag = -60851},
+    {.real = +23586, .imag = -61145}, {.real = +22834, .imag = -61429},
+    {.real = +22078, .imag = -61705}, {.real = +21320, .imag = -61971},
+    {.real = +20557, .imag = -62228}, {.real = +19792, .imag = -62476},
+    {.real = +19024, .imag = -62714}, {.real = +18253, .imag = -62943},
+    {.real = +17479, .imag = -63162}, {.real = +16703, .imag = -63372},
+    {.real = +15924, .imag = -63572}, {.real = +15143, .imag = -63763},
+    {.real = +14359, .imag = -63944}, {.real = +13573, .imag = -64115},
+    {.real = +12785, .imag = -64277}, {.real = +11996, .imag = -64429},
+    {.real = +11204, .imag = -64571}, {.real = +10411, .imag = -64704},
+    {.real = +9616, .imag = -64827},  {.real = +8820, .imag = -64940},
+    {.real = +8022, .imag = -65043},  {.real = +7224, .imag = -65137},
+    {.real = +6424, .imag = -65220},  {.real = +5623, .imag = -65294},
+    {.real = +4821, .imag = -65358},  {.real = +4019, .imag = -65413},
+    {.real = +3216, .imag = -65457},  {.real = +2412, .imag = -65492},
+    {.real = +1608, .imag = -65516},  {.real = +804, .imag = -65531},
+    {.real = +0, .imag = -65536},     {.real = -804, .imag = -65531},
+    {.real = -1608, .imag = -65516},  {.real = -2412, .imag = -65492},
+    {.real = -3216, .imag = -65457},  {.real = -4019, .imag = -65413},
+    {.real = -4821, .imag = -65358},  {.real = -5623, .imag = -65294},
+    {.real = -6424, .imag = -65220},  {.real = -7224, .imag = -65137},
+    {.real = -8022, .imag = -65043},  {.real = -8820, .imag = -64940},
+    {.real = -9616, .imag = -64827},  {.real = -10411, .imag = -64704},
+    {.real = -11204, .imag = -64571}, {.real = -11996, .imag = -64429},
+    {.real = -12785, .imag = -64277}, {.real = -13573, .imag = -64115},
+    {.real = -14359, .imag = -63944}, {.real = -15143, .imag = -63763},
+    {.real = -15924, .imag = -63572}, {.real = -16703, .imag = -63372},
+    {.real = -17479, .imag = -63162}, {.real = -18253, .imag = -62943},
+    {.real = -19024, .imag = -62714}, {.real = -19792, .imag = -62476},
+    {.real = -20557, .imag = -62228}, {.real = -21320, .imag = -61971},
+    {.real = -22078, .imag = -61705}, {.real = -22834, .imag = -61429},
+    {.real = -23586, .imag = -61145}, {.real = -24335, .imag = -60851},
+    {.real = -25080, .imag = -60547}, {.real = -25821, .imag = -60235},
+    {.real = -26558, .imag = -59914}, {.real = -27291, .imag = -59583},
+    {.real = -28020, .imag = -59244}, {.real = -28745, .imag = -58896},
+    {.real = -29466, .imag = -58538}, {.real = -30182, .imag = -58172},
+    {.real = -30893, .imag = -57798}, {.real = -31600, .imag = -57414},
+    {.real = -32303, .imag = -57022}, {.real = -33000, .imag = -56621},
+    {.real = -33692, .imag = -56212}, {.real = -34380, .imag = -55794},
+    {.real = -35062, .imag = -55368}, {.real = -35738, .imag = -54934},
+    {.real = -36410, .imag = -54491}, {.real = -37076, .imag = -54040},
+    {.real = -37736, .imag = -53581}, {.real = -38391, .imag = -53114},
+    {.real = -39040, .imag = -52639}, {.real = -39683, .imag = -52156},
+    {.real = -40320, .imag = -51665}, {.real = -40951, .imag = -51166},
+    {.real = -41576, .imag = -50660}, {.real = -42194, .imag = -50146},
+    {.real = -42806, .imag = -49624}, {.real = -43412, .imag = -49095},
+    {.real = -44011, .imag = -48559}, {.real = -44604, .imag = -48015},
+    {.real = -45190, .imag = -47464}, {.real = -45769, .imag = -46906},
+    {.real = -46341, .imag = -46341}, {.real = -46906, .imag = -45769},
+    {.real = -47464, .imag = -45190}, {.real = -48015, .imag = -44604},
+    {.real = -48559, .imag = -44011}, {.real = -49095, .imag = -43412},
+    {.real = -49624, .imag = -42806}, {.real = -50146, .imag = -42194},
+    {.real = -50660, .imag = -41576}, {.real = -51166, .imag = -40951},
+    {.real = -51665, .imag = -40320}, {.real = -52156, .imag = -39683},
+    {.real = -52639, .imag = -39040}, {.real = -53114, .imag = -38391},
+    {.real = -53581, .imag = -37736}, {.real = -54040, .imag = -37076},
+    {.real = -54491, .imag = -36410}, {.real = -54934, .imag = -35738},
+    {.real = -55368, .imag = -35062}, {.real = -55794, .imag = -34380},
+    {.real = -56212, .imag = -33692}, {.real = -56621, .imag = -33000},
+    {.real = -57022, .imag = -32303}, {.real = -57414, .imag = -31600},
+    {.real = -57798, .imag = -30893}, {.real = -58172, .imag = -30182},
+    {.real = -58538, .imag = -29466}, {.real = -58896, .imag = -28745},
+    {.real = -59244, .imag = -28020}, {.real = -59583, .imag = -27291},
+    {.real = -59914, .imag = -26558}, {.real = -60235, .imag = -25821},
+    {.real = -60547, .imag = -25080}, {.real = -60851, .imag = -24335},
+    {.real = -61145, .imag = -23586}, {.real = -61429, .imag = -22834},
+    {.real = -61705, .imag = -22078}, {.real = -61971, .imag = -21320},
+    {.real = -62228, .imag = -20557}, {.real = -62476, .imag = -19792},
+    {.real = -62714, .imag = -19024}, {.real = -62943, .imag = -18253},
+    {.real = -63162, .imag = -17479}, {.real = -63372, .imag = -16703},
+    {.real = -63572, .imag = -15924}, {.real = -63763, .imag = -15143},
+    {.real = -63944, .imag = -14359}, {.real = -64115, .imag = -13573},
+    {.real = -64277, .imag = -12785}, {.real = -64429, .imag = -11996},
+    {.real = -64571, .imag = -11204}, {.real = -64704, .imag = -10411},
+    {.real = -64827, .imag = -9616},  {.real = -64940, .imag = -8820},
+    {.real = -65043, .imag = -8022},  {.real = -65137, .imag = -7224},
+    {.real = -65220, .imag = -6424},  {.real = -65294, .imag = -5623},
+    {.real = -65358, .imag = -4821},  {.real = -65413, .imag = -4019},
+    {.real = -65457, .imag = -3216},  {.real = -65492, .imag = -2412},
+    {.real = -65516, .imag = -1608},  {.real = -65531, .imag = -804}};
+
+static inline int32_t _mulrnd16(const int32_t x, const int32_t y) {
+  int64_t r = ((int64_t)x * (int64_t)y) + (1 << 15);
+  return r >> 16;
+}
+
+static inline int32_t _complex_mul_real(const complex_fp32_16 x,
+                                        const complex_fp32_16 y) {
+  return _mulrnd16(x.real, y.real) - _mulrnd16(x.imag, y.imag);
+}
+
+static inline int32_t _complex_mul_imag(const complex_fp32_16 x,
+                                        const complex_fp32_16 y) {
+  return _mulrnd16(x.real, y.imag) + _mulrnd16(x.imag, y.real);
+}
+
+static void _complex_mul(complex_fp32_16 *r, const complex_fp32_16 x,
+                         const complex_fp32_16 y) {
+  r->real = _complex_mul_real(x, y);
+  r->imag = _complex_mul_imag(x, y);
+}
+
+/****************************************************************************
+ * To generate values in Phython:
+ *   brv8 = [int(f"{t:08b}"[::-1],2) for t in range(2**8)]
+ ****************************************************************************/
+const uint16_t brv8[] = {
+    0,  128, 64, 192, 32, 160, 96,  224, 16, 144, 80, 208, 48, 176, 112, 240,
+    8,  136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248,
+    4,  132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244,
+    12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
+    2,  130, 66, 194, 34, 162, 98,  226, 18, 146, 82, 210, 50, 178, 114, 242,
+    10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
+    6,  134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
+    14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
+    1,  129, 65, 193, 33, 161, 97,  225, 17, 145, 81, 209, 49, 177, 113, 241,
+    9,  137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249,
+    5,  133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245,
+    13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253,
+    3,  131, 67, 195, 35, 163, 99,  227, 19, 147, 83, 211, 51, 179, 115, 243,
+    11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251,
+    7,  135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247,
+    15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255};
+
+/****************************************************************************
+ * Initialize the FFT array with coeffs[i] * root[i] and
+ * store the values in bit-reversed order.
+ ****************************************************************************/
+void fft_init_and_bitrev(complex_fp32_16 r[FFT_N], const poly *x) {
+  int i, inv_i;
+  int c;
+  for (i = 0; i < FFT_N; i++) {
+    inv_i = brv8[i];
+    c = x->coeffs[i];
+    r[inv_i].real = c * roots[i].real;
+    r[inv_i].imag = c * roots[i].imag;
+  }
+}
+
+int32_t complex_fp_sqabs(complex_fp32_16 x) {
+  return _mulrnd16(x.real, x.real) + _mulrnd16(x.imag, x.imag);
+}
+
+/*************************************************
+ * Name:        fft
+ *
+ * Description:
+ *
+ * Arguments:   - complex_fp32_16 data[FFT_N]
+ *
+ * Specification: Implements @[Algorithm 49, FFT]
+ **************************************************/
+void fft(complex_fp32_16 data[FFT_N]) {
+  unsigned int r, m, md2, n, k, even, odd, twid;
+  complex_fp32_16 u, t;
+
+  for (r = 1; r <= FFT_LOGN; r++) {
+    m = 1 << r;
+    md2 = m >> 1;
+    for (n = 0; n < FFT_N; n += m) {
+      for (k = 0; k < md2; k++) {
+        even = n + k;
+        odd = even + md2;
+        twid = k << (FFT_LOGN - r + 1);
+
+        u = data[even];
+        _complex_mul(&t, roots[twid], data[odd]);
+        data[even].real = u.real + t.real;
+        data[even].imag = u.imag + t.imag;
+        data[odd].real = u.real - t.real;
+        data[odd].imag = u.imag - t.imag;
+      }
+    }
+  }
+}
diff --git a/cbits/third_party/HAETAE/src/fips202.c b/cbits/third_party/HAETAE/src/fips202.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/fips202.c
@@ -0,0 +1,757 @@
+/* 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 "fips202.h"
+
+#include <stddef.h>
+#include <stdint.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[8]) {
+  unsigned int i;
+  uint64_t r = 0;
+
+  for (i = 0; i < 8; ++i)
+    r |= (uint64_t)x[i] << 8 * i;
+
+  return r;
+}
+
+/*************************************************
+ * Name:        store64
+ *
+ * Description: Store a 64-bit integer to array of 8 bytes in little-endian
+ *              order
+ *
+ * Arguments:   - uint8_t *x: pointer to the output byte array (allocated)
+ *              - uint64_t u: input 64-bit unsigned integer
+ **************************************************/
+static void store64(uint8_t x[8], uint64_t u) {
+  unsigned int i;
+
+  for (i = 0; i < 8; ++i)
+    x[i] = u >> 8 * i;
+}
+
+/* Keccak round constants */
+const uint64_t KeccakF_RoundConstants[NROUNDS] = {
+    (uint64_t)UINT64_C(0x0000000000000001),
+    (uint64_t)UINT64_C(0x0000000000008082),
+    (uint64_t)UINT64_C(0x800000000000808a),
+    (uint64_t)UINT64_C(0x8000000080008000),
+    (uint64_t)UINT64_C(0x000000000000808b),
+    (uint64_t)UINT64_C(0x0000000080000001),
+    (uint64_t)UINT64_C(0x8000000080008081),
+    (uint64_t)UINT64_C(0x8000000000008009),
+    (uint64_t)UINT64_C(0x000000000000008a),
+    (uint64_t)UINT64_C(0x0000000000000088),
+    (uint64_t)UINT64_C(0x0000000080008009),
+    (uint64_t)UINT64_C(0x000000008000000a),
+    (uint64_t)UINT64_C(0x000000008000808b),
+    (uint64_t)UINT64_C(0x800000000000008b),
+    (uint64_t)UINT64_C(0x8000000000008089),
+    (uint64_t)UINT64_C(0x8000000000008003),
+    (uint64_t)UINT64_C(0x8000000000008002),
+    (uint64_t)UINT64_C(0x8000000000000080),
+    (uint64_t)UINT64_C(0x000000000000800a),
+    (uint64_t)UINT64_C(0x800000008000000a),
+    (uint64_t)UINT64_C(0x8000000080008081),
+    (uint64_t)UINT64_C(0x8000000000008080),
+    (uint64_t)UINT64_C(0x0000000080000001),
+    (uint64_t)UINT64_C(0x8000000080008008)};
+
+/*************************************************
+ * 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[25]) {
+  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 ^= (uint64_t)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 ^= (uint64_t)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_init
+ *
+ * Description: Initializes the Keccak state.
+ *
+ * Arguments:   - uint64_t *s: pointer to Keccak state
+ **************************************************/
+static void keccak_init(uint64_t s[25]) {
+  unsigned int i;
+  for (i = 0; i < 25; ++i)
+    s[i] = 0;
+}
+
+/*************************************************
+ * Name:        keccak_absorb
+ *
+ * Description: Absorb step of Keccak; incremental.
+ *
+ * Arguments:   - uint64_t *s: pointer to Keccak state
+ *              - unsigned int pos: position in current block to be absorbed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ *
+ * Returns new position pos in current block
+ **************************************************/
+static unsigned int keccak_absorb(uint64_t s[25], unsigned int pos,
+                                  unsigned int r, const uint8_t *in,
+                                  size_t inlen) {
+  unsigned int i;
+
+  while (pos + inlen >= r) {
+    for (i = pos; i < r; ++i)
+      s[i / 8] ^= (uint64_t)*in++ << 8 * (i % 8);
+    inlen -= r - pos;
+    KeccakF1600_StatePermute(s);
+    pos = 0;
+  }
+
+  for (i = pos; i < pos + inlen; ++i)
+    s[i / 8] ^= (uint64_t)*in++ << 8 * (i % 8);
+
+  return i;
+}
+
+/*************************************************
+ * Name:        keccak_finalize
+ *
+ * Description: Finalize absorb step.
+ *
+ * Arguments:   - uint64_t *s: pointer to Keccak state
+ *              - unsigned int pos: position in current block to be absorbed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - uint8_t p: domain separation byte
+ **************************************************/
+static void keccak_finalize(uint64_t s[25], unsigned int pos, unsigned int r,
+                            uint8_t p) {
+  s[pos / 8] ^= (uint64_t)p << 8 * (pos % 8);
+  s[r / 8 - 1] ^= UINT64_C(1) << 63;
+}
+
+/*************************************************
+ * Name:        keccak_squeeze
+ *
+ * Description: Squeeze step of Keccak. Squeezes arbitratrily many bytes.
+ *              Modifies the state. Can be called multiple times to keep
+ *              squeezing, i.e., is incremental.
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: number of bytes to be squeezed (written to out)
+ *              - uint64_t *s: pointer to input/output Keccak state
+ *              - unsigned int pos: number of bytes in current block already
+ *              squeezed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *
+ * Returns new position pos in current block
+ **************************************************/
+static unsigned int keccak_squeeze(uint8_t *out, size_t outlen, uint64_t s[25],
+                                   unsigned int pos, unsigned int r) {
+  unsigned int i;
+
+  while (outlen) {
+    if (pos == r) {
+      KeccakF1600_StatePermute(s);
+      pos = 0;
+    }
+    for (i = pos; i < r && i < pos + outlen; ++i)
+      *out++ = s[i / 8] >> 8 * (i % 8);
+    outlen -= i - pos;
+    pos = i;
+  }
+
+  return pos;
+}
+
+/*************************************************
+ * Name:        keccak_absorb_once
+ *
+ * Description: Absorb step of Keccak;
+ *              non-incremental, starts by zeroeing the state.
+ *
+ * Arguments:   - uint64_t *s: pointer to (uninitialized) output Keccak state
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ *              - uint8_t p: domain-separation byte for different Keccak-derived
+ *functions
+ **************************************************/
+static void keccak_absorb_once(uint64_t s[25], unsigned int r,
+                               const uint8_t *in, size_t inlen, uint8_t p) {
+  unsigned int i;
+
+  for (i = 0; i < 25; ++i)
+    s[i] = 0;
+
+  while (inlen >= r) {
+    for (i = 0; i < r / 8; ++i)
+      s[i] ^= load64(in + 8 * i);
+    in += r;
+    inlen -= r;
+    KeccakF1600_StatePermute(s);
+  }
+
+  for (i = 0; i < inlen; ++i)
+    s[i / 8] ^= (uint64_t)in[i] << 8 * (i % 8);
+
+  s[i / 8] ^= (uint64_t)p << 8 * (i % 8);
+  s[(r - 1) / 8] ^= UINT64_C(1) << 63;
+}
+
+/*************************************************
+ * 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. Assumes zero bytes of current
+ *              block have already been squeezed.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *              out)
+ *              - uint64_t *s: pointer to input/output Keccak state
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ **************************************************/
+static void keccak_squeezeblocks(uint8_t *out, size_t nblocks, uint64_t s[25],
+                                 unsigned int r) {
+  unsigned int i;
+
+  while (nblocks) {
+    KeccakF1600_StatePermute(s);
+    for (i = 0; i < r / 8; ++i)
+      store64(out + 8 * i, s[i]);
+    out += r;
+    nblocks -= 1;
+  }
+}
+
+/*************************************************
+ * Name:        shake128_init
+ *
+ * Description: Initilizes Keccak state for use as SHAKE128 XOF
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) Keccak state
+ **************************************************/
+void shake128_init(keccak_state *state) {
+  keccak_init(state->s);
+  state->pos = 0;
+}
+
+/*************************************************
+ * Name:        shake128_absorb
+ *
+ * Description: Absorb step of the SHAKE128 XOF; incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (initialized) output Keccak
+ *              state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen) {
+  state->pos = keccak_absorb(state->s, state->pos, SHAKE128_RATE, in, inlen);
+}
+
+/*************************************************
+ * Name:        shake128_finalize
+ *
+ * Description: Finalize absorb step of the SHAKE128 XOF.
+ *
+ * Arguments:   - keccak_state *state: pointer to Keccak state
+ **************************************************/
+void shake128_finalize(keccak_state *state) {
+  keccak_finalize(state->s, state->pos, SHAKE128_RATE, 0x1F);
+  state->pos = SHAKE128_RATE;
+}
+
+/*************************************************
+ * Name:        shake128_squeeze
+ *
+ * Description: Squeeze step of SHAKE128 XOF. Squeezes arbitraily many
+ *              bytes. Can be called multiple times to keep squeezing.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t outlen : number of bytes to be squeezed (written to
+ *              output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state) {
+  state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE128_RATE);
+}
+
+/*************************************************
+ * Name:        shake128_absorb_once
+ *
+ * Description: Initialize, absorb into and finalize SHAKE128 XOF;
+ *non-incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) output Keccak
+ *              state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128_absorb_once(keccak_state *state, const uint8_t *in,
+                          size_t inlen) {
+  keccak_absorb_once(state->s, SHAKE128_RATE, in, inlen, 0x1F);
+  state->pos = SHAKE128_RATE;
+}
+
+/*************************************************
+ * Name:        shake128_squeezeblocks
+ *
+ * Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of
+ *              SHAKE128_RATE bytes each. Can be called multiple times
+ *              to keep squeezing. Assumes new block has not yet been
+ *              started (state->pos = SHAKE128_RATE).
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *              output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) {
+  keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE);
+}
+
+/*************************************************
+ * Name:        shake256_init
+ *
+ * Description: Initilizes Keccak state for use as SHAKE256 XOF
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) Keccak state
+ **************************************************/
+void shake256_init(keccak_state *state) {
+  keccak_init(state->s);
+  state->pos = 0;
+}
+
+/*************************************************
+ * Name:        shake256_absorb
+ *
+ * Description: Absorb step of the SHAKE256 XOF; incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (initialized) output Keccak
+ *              state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) {
+  state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen);
+}
+
+/*************************************************
+ * Name:        shake256_finalize
+ *
+ * Description: Finalize absorb step of the SHAKE256 XOF.
+ *
+ * Arguments:   - keccak_state *state: pointer to Keccak state
+ **************************************************/
+void shake256_finalize(keccak_state *state) {
+  keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F);
+  state->pos = SHAKE256_RATE;
+}
+
+/*************************************************
+ * Name:        shake256_squeeze
+ *
+ * Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many
+ *              bytes. Can be called multiple times to keep squeezing.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t outlen : number of bytes to be squeezed (written to
+ *              output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) {
+  state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE);
+}
+
+/*************************************************
+ * Name:        shake256_absorb_once
+ *
+ * Description: Initialize, absorb into and finalize SHAKE256 XOF;
+ *              non-incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) output Keccak
+ *              state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256_absorb_once(keccak_state *state, const uint8_t *in,
+                          size_t inlen) {
+  keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F);
+  state->pos = SHAKE256_RATE;
+}
+
+/*************************************************
+ * Name:        shake256_squeezeblocks
+ *
+ * Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of
+ *              SHAKE256_RATE bytes each. Can be called multiple times
+ *              to keep squeezing. Assumes next block has not yet been
+ *              started (state->pos = SHAKE256_RATE).
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *              output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) {
+  keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE);
+}
+
+/*************************************************
+ * Name:        shake128
+ *
+ * Description: SHAKE128 XOF with non-incremental API
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: requested output length in bytes
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) {
+  size_t nblocks;
+  keccak_state state;
+
+  shake128_absorb_once(&state, in, inlen);
+  nblocks = outlen / SHAKE128_RATE;
+  shake128_squeezeblocks(out, nblocks, &state);
+  outlen -= nblocks * SHAKE128_RATE;
+  out += nblocks * SHAKE128_RATE;
+  shake128_squeeze(out, outlen, &state);
+}
+
+/*************************************************
+ * Name:        shake256
+ *
+ * Description: SHAKE256 XOF with non-incremental API
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: requested output length in bytes
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) {
+  size_t nblocks;
+  keccak_state state;
+
+  shake256_absorb_once(&state, in, inlen);
+  nblocks = outlen / SHAKE256_RATE;
+  shake256_squeezeblocks(out, nblocks, &state);
+  outlen -= nblocks * SHAKE256_RATE;
+  out += nblocks * SHAKE256_RATE;
+  shake256_squeeze(out, outlen, &state);
+}
+
+/*************************************************
+ * Name:        sha3_256
+ *
+ * Description: SHA3-256 with non-incremental API
+ *
+ * Arguments:   - uint8_t *h: pointer to output (32 bytes)
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) {
+  unsigned int i;
+  uint64_t s[25];
+
+  keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06);
+  KeccakF1600_StatePermute(s);
+  for (i = 0; i < 4; ++i)
+    store64(h + 8 * i, s[i]);
+}
+
+/*************************************************
+ * Name:        sha3_512
+ *
+ * Description: SHA3-512 with non-incremental API
+ *
+ * Arguments:   - uint8_t *h: pointer to output (64 bytes)
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) {
+  unsigned int i;
+  uint64_t s[25];
+
+  keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06);
+  KeccakF1600_StatePermute(s);
+  for (i = 0; i < 8; ++i)
+    store64(h + 8 * i, s[i]);
+}
diff --git a/cbits/third_party/HAETAE/src/fixpoint.c b/cbits/third_party/HAETAE/src/fixpoint.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/fixpoint.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: MIT
+
+#include "fixpoint.h"
+#include "params.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+static void __cneg(fp96_76 *x, const uint8_t sign) {
+  x->limb48[0] ^= (-(int64_t)sign) & ((UINT64_C(1) << 48) - 1);
+  x->limb48[1] ^= -(int64_t)sign;
+  x->limb48[0] += sign;
+  renormalize(x);
+}
+
+static void __copy_cneg(fp96_76 *y, const fp96_76 *x, const uint8_t sign) {
+  y->limb48[0] = ((-(int64_t)sign) & ((UINT64_C(1) << 48) - 1)) ^ x->limb48[0];
+  ;
+  y->limb48[1] = x->limb48[1] ^ (-(int64_t)sign);
+  y->limb48[0] += sign;
+  renormalize(y);
+}
+
+static void fixpoint_mul(fp96_76 *xy, const fp96_76 *x, const fp96_76 *y) {
+  uint64_t tmp[2];
+  mul48(&xy->limb48[0], x->limb48[0], y->limb48[0]);
+
+  // shift right by 48, rounding
+  xy->limb48[0] = xy->limb48[1] + (((xy->limb48[0] >> 47) + 1) >> 1);
+
+  mul48(tmp, x->limb48[0], y->limb48[1]);
+  xy->limb48[0] += tmp[0];
+  xy->limb48[1] = tmp[1];
+  mulacc48(&xy->limb48[0], x->limb48[1], y->limb48[0]);
+
+  // shift right by 28, rounding
+  xy->limb48[0] += UINT64_C(1) << 27;
+  xy->limb48[0] >>= 28;
+  xy->limb48[0] += (xy->limb48[1] << 20) & ((UINT64_C(1) << 48) - 1);
+  xy->limb48[1] >>= 28;
+
+  mul64(tmp, x->limb48[1], y->limb48[1]);
+  xy->limb48[0] += (tmp[0] << 20) & ((UINT64_C(1) << 48) - 1);
+  xy->limb48[1] += (tmp[0] >> 28) + (tmp[1] << 36);
+
+  renormalize(xy);
+}
+
+static void fixpoint_unsigned_signed_mul(fp96_76 *xy, const fp96_76 *y) {
+  fp96_76 x, z;
+  uint8_t sign = (y->limb48[1] >> 63) & 1;
+  __copy_cneg(&x, y, sign);
+  fixpoint_mul(&z, &x, xy);
+  __copy_cneg(xy, &z, sign);
+}
+
+static void fixpoint_sub(fp96_76 *xminy, const fp96_76 *x, const fp96_76 *y) {
+  fp96_76 yneg;
+  __copy_cneg(&yneg, y, 1);
+  fixpoint_add(xminy, x, &yneg);
+}
+
+static void fixpoint_sub_from_threehalves(fp96_76 *x) {
+  __cneg(x, 1);
+  x->limb48[1] += UINT64_C(3) << 27; // left shift by 28 would be "3"
+  renormalize(x);
+}
+
+void fixpoint_square(fp96_76 *sqx, const fp96_76 *x) {
+  uint64_t tmp[2];
+  sq48(&sqx->limb48[0], x->limb48[0]);
+
+  // shift right by 48, rounding
+  // sqx->limb48[0] += UINT64_C(1) << 47;
+  sqx->limb48[0] >>= 48;
+  sqx->limb48[0] += sqx->limb48[1];
+
+  // mul
+  mul48(tmp, x->limb48[0], x->limb48[1]);
+  sqx->limb48[0] += tmp[0] << 1;
+  sqx->limb48[1] = tmp[1] << 1;
+
+  // shift right by 28, rounding
+  // sqx->limb48[0] += UINT64_C(1) << 27;
+  sqx->limb48[0] >>= 28;
+  sqx->limb48[0] += (sqx->limb48[1] << 20) & ((UINT64_C(1) << 48) - 1);
+  sqx->limb48[1] >>= 28;
+
+  sq64(tmp, x->limb48[1]);
+  sqx->limb48[0] += (tmp[0] << 20) & ((UINT64_C(1) << 48) - 1);
+  sqx->limb48[1] += (tmp[0] >> 28) + (tmp[1] << 36);
+
+  renormalize(sqx);
+}
+
+// start_cube = hex(round(2^64/(sqrt((K + L)*N + 2)^3)))
+// start_times_threehalfs = hex(round(2^64 * 2/(3 * sqrt((K + L)*N + 2))))
+#if HAETAE_MODE == HAETAE_MODE2
+const fp96_76 start_cube = {
+    .limb48 = {UINT64_C(0x770077e2e41a), UINT64_C(0x1162)}};
+const fp96_76 start_times_threehalves = {
+    .limb48 = {UINT64_C(0x693861ad937b), UINT64_C(0x9caa56)}};
+#elif HAETAE_MODE == HAETAE_MODE3
+const fp96_76 start_cube = {
+    .limb48 = {UINT64_C(0x1a2935cfae68), UINT64_C(0x978)}};
+const fp96_76 start_times_threehalves = {
+    .limb48 = {UINT64_C(0x7ad215218533), UINT64_C(0x7ff1c9)}};
+#elif HAETAE_MODE == HAETAE_MODE5
+const fp96_76 start_cube = {
+    .limb48 = {UINT64_C(0x700ff3e8890d), UINT64_C(0x702)}};
+const fp96_76 start_times_threehalves = {
+    .limb48 = {UINT64_C(0x5768588eed31), UINT64_C(0x73bd40)}};
+#endif
+
+// implements Newton's method
+void fixpoint_newton_invsqrt(fp96_76 *invsqrtx, const fp96_76 *xhalf) {
+  fp96_76 tmp, tmp2;
+  fixpoint_mul(&tmp, xhalf, &start_cube); // definitely two positive values
+  fixpoint_sub(invsqrtx, &start_times_threehalves,
+               &tmp); // first Newton iteration done, might be negative (very
+                      // improbable)
+
+  for (int i = 0; i < 6; i++) // 6 more iterations
+  {
+    fixpoint_square(&tmp, invsqrtx);      // tmp = y^2, never negative
+    fixpoint_mul(&tmp2, xhalf, &tmp);     // tmp2 = x/2 * y^2, never negative
+    fixpoint_sub_from_threehalves(&tmp2); // tmp = 3/2 - x/2 * y^2
+    fixpoint_unsigned_signed_mul(invsqrtx, &tmp2); // y * (3/2 - x/2 * y^2)
+  }
+}
+
+int32_t fixpoint_mul_rnd13(const uint64_t x, const fp96_76 *y,
+                           const uint8_t sign) {
+  int64_t res;
+  fp96_76 tmp, xx;
+  xx.limb48[1] = x >> 32;
+  xx.limb48[0] = (x & ((UINT64_C(1) << 32) - 1)) << 16;
+  fixpoint_mul(&tmp, &xx, y);
+  res = (tmp.limb48[1] + (UINT64_C(1) << 14)) >> 15; // rounding
+  return (1 - 2 * (int32_t)sign) * res;
+}
+
+void fixpoint_add(fp96_76 *xy, const fp96_76 *x, const fp96_76 *y) {
+  xy->limb48[0] = x->limb48[0] + y->limb48[0];
+  xy->limb48[1] = x->limb48[1] + y->limb48[1];
+}
diff --git a/cbits/third_party/HAETAE/src/ntt.c b/cbits/third_party/HAETAE/src/ntt.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/ntt.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: MIT
+
+#include "ntt.h"
+#include "params.h"
+#include "reduce.h"
+
+#include <stdint.h>
+
+static const int32_t zetas[HAETAE_N] = {
+    0,      26964,  -16505, 22229,  30746,  20243,  19064,  -31218, 9395,
+    -30985, 22859,  -8851,  32144,  13744,  21408,  17599,  -16039, -22946,
+    6241,   -19553, 10681,  22935,  22431,  -29104, 28147,  -27527, -29133,
+    -20035, 20143,  -11361, 30820,  25252,  -22562, -6789,  -10049, 9383,
+    16304,  -12296, 16446,  18239,  -1296,  -19725, -32076, 11782,  -17941,
+    29643,  -8577,  7893,   -21464, -19646, -15130, -2391,  30608,  -23970,
+    -16608, 19616,  -7941,  26533,  -19129, 27690,  7597,   -11459, 10615,
+    -9430,  11591,  7814,   12697,  32114,  -3761,  -9604,  19813,  20353,
+    17456,  -16267, -19555, 598,    -29942, 4538,   835,    15546,  3970,
+    -27685, 1488,   8311,   -12442, 31352,  -17631, 1806,   -5342,  9790,
+    29068,  16507,  -29051, 22131,  6759,   15510,  -14941, 28710,  1160,
+    -31327, 24985,  11261,  -10623, -27727, 21502,  18731,  -16186, -4127,
+    -18832, 12050,  -14501, 7929,   29563,  -31064, 5913,   5322,   -16405,
+    2844,   29439,  5876,   -9522,  -18586, -9874,  23844,  30362,  -21442,
+    9560,   17671,  -27989, 3350,   787,    -13857, 1657,   -21224, -7374,
+    -9190,  2464,   25555,  -3529,  -28772, 16588,  -15739, 23475,  13666,
+    5764,   30980,  13633,  -7401,  -30317, 28847,  7682,   -11808, -8796,
+    14864,  -24162, -19194, 689,    -1311,  -31332, -16319, 1025,   10971,
+    -23016, -2648,  -21900, -12543, -25921, 28254,  28521,  -16160, 12380,
+    -12882, -30332, -16630, 23439,  7742,   17182,  17494,  5920,   13642,
+    7382,   -18166, 21422,  -30274, -28190, 13283,  -20316, -9939,  10672,
+    21454,  6080,   -17374, -29735, -25912, -10170, 3808,   10639,  -26985,
+    -10865, 25636,  17261,  -26851, -8253,  -3304,  18282,  -2202,  -31368,
+    -22243, 13882,  12069,  -11242, -7729,  -10226, 1761,   -27298, -4800,
+    -17737, -22805, -3528,  65,     10770,  8908,   -23751, 26934,  21921,
+    -27010, -21944, 8889,   -1035,  23224,  -9488,  -5823,  -994,   -20206,
+    7655,   -16251, -22820, -27740, 15822,  23078,  13803,  -8099,  2931,
+    9217,   -21126, -14203, 25492,  -12831, 7947,   17463,  -12979, 29003,
+    31612,  26554,  8241,   -20175}; // q = 64513
+
+/*************************************************
+ * Name:        ntt
+ *
+ * Description: Forward NTT, in-place. No modular reduction is performed after
+ *              additions or subtractions. Output vector is in bitreversed
+ *              order.
+ *
+ * Arguments:   - uint32_t p[HAETAE_N]: input/output coefficient array
+ *
+ * Specification: Implements @[Algorithm 2, NTT]
+ **************************************************/
+void ntt(int32_t a[HAETAE_N]) {
+  unsigned int len, start, j, k;
+  int32_t zeta, t;
+
+  k = 0;
+  for (len = 128; len > 0; len >>= 1) {
+    for (start = 0; start < HAETAE_N; start = j + len) {
+      zeta = zetas[++k];
+      for (j = start; j < start + len; ++j) {
+        t = montgomery_reduce((int64_t)zeta * a[j + len]);
+        a[j + len] = a[j] - t;
+        a[j] = a[j] + t;
+      }
+    }
+  }
+}
+
+/*************************************************
+ * Name:        invntt_tomont
+ *
+ * Description: Inverse NTT and multiplication by Montgomery factor 2^32.
+ *              In-place. No modular reductions after additions or
+ *              subtractions; input coefficients need to be smaller than
+ *              HAETAE_Q in absolute value. Output coefficient are smaller than
+ *              HAETAE_Q in absolute value.
+ *
+ * Arguments:   - uint32_t p[HAETAE_N]: input/output coefficient array
+ **************************************************/
+void invntt_tomont(int32_t a[HAETAE_N]) {
+  unsigned int start, len, j, k;
+  int32_t t, zeta;
+  const int32_t f = -29720; // mont^2/256
+
+  k = 256;
+  for (len = 1; len < HAETAE_N; len <<= 1) {
+    for (start = 0; start < HAETAE_N; start = j + len) {
+      zeta = -zetas[--k];
+      for (j = start; j < start + len; ++j) {
+        t = a[j];
+        a[j] = t + a[j + len];
+        a[j + len] = t - a[j + len];
+        a[j + len] = montgomery_reduce((int64_t)zeta * a[j + len]);
+      }
+    }
+  }
+
+  for (j = 0; j < HAETAE_N; ++j) {
+    a[j] = montgomery_reduce((int64_t)f * a[j]);
+  }
+}
diff --git a/cbits/third_party/HAETAE/src/packing.c b/cbits/third_party/HAETAE/src/packing.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/packing.c
@@ -0,0 +1,595 @@
+// SPDX-License-Identifier: MIT
+
+#include "packing.h"
+#include "encoding.h"
+#include "params.h"
+#include "poly.h"
+#include "polymat.h"
+#include "polyvec.h"
+
+#include <string.h>
+
+/*************************************************
+ * Name:        pack_vk
+ *
+ * Description: Bit-pack public key vk = (seed, b).
+ *
+ * Arguments:   - uint8_t vk[]: output byte array
+ *              - const polyveck *b: polynomial vector of length HAETAE_K
+ *              containg b
+ *              - const uint8_t seed[]: seed for A'
+ *
+ * Specification: Implements @[Algorithm 23, PackVK]
+ **************************************************/
+void pack_vk(uint8_t vk[HAETAE_CRYPTO_PUBLICKEYBYTES], polyveck *b,
+             const uint8_t seed[HAETAE_SEEDBYTES]) {
+  unsigned int i;
+
+  memcpy(vk, seed, HAETAE_SEEDBYTES);
+
+  vk += HAETAE_SEEDBYTES;
+  for (i = 0; i < HAETAE_K; ++i) {
+    pack_poly_q(vk + i * HAETAE_POLYQ_PACKEDBYTES, &b->vec[i]);
+  }
+}
+
+/*************************************************
+ * Name:        unpack_vk
+ *
+ * Description: Unpack public key vk = (seed, b).
+ *
+ * Arguments:   - uint8_t seed[]: seed for A'
+ *              - polyveck *b: polynomial vector of length HAETAE_K containg b
+ *              - const uint8_t vk[]: output byte array
+ *
+ * Specification: Implements @[Algorithm 24, UnpackVK]
+ **************************************************/
+void unpack_vk(polyvecl A[HAETAE_K],
+               const uint8_t vk[HAETAE_CRYPTO_PUBLICKEYBYTES]) {
+  unsigned int i;
+  polyveck b;
+  uint8_t seed[HAETAE_SEEDBYTES];
+  memcpy(seed, vk, HAETAE_SEEDBYTES);
+
+  vk += HAETAE_SEEDBYTES;
+  for (i = 0; i < HAETAE_K; ++i) {
+    unpack_poly_q(&b.vec[i], vk + i * HAETAE_POLYQ_PACKEDBYTES);
+  }
+  // A' = PRG(rhoprime)
+  polymatkl_expand_matA(A, seed);
+  polymatkl_double(A);
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  polyveck a;
+  polyveck_expand_vecA(&a, seed);
+  // first column of A = 2(a-b1*2^d)
+  polyveck_double(&b);
+  polyveck_sub(&b, &a, &b);
+  polyveck_double(&b);
+  polyveck_ntt(&b);
+#endif
+  // append b into A
+  for (i = 0; i < HAETAE_K; ++i) {
+    A[i].vec[0] = b.vec[i];
+  }
+}
+
+/*************************************************
+ * Name:        pack_sk
+ *
+ * Description: Bit-pack secret key sk = (vk, s).
+ *
+ * Arguments:   - uint8_t sk[]: output byte array
+ *              - const uint8_t vk[PUBLICKEYBYTES]: packed vk
+ *              - const polyvecl *s0: polyvecl pointer containing s0 (encoding
+ *                starting at offset 1)
+ *              - const polyveck *s1: polyveck pointer containing s1
+ *
+ * Specification: Implements @[Algorithm 25, PackSK]
+ **************************************************/
+void pack_sk(uint8_t sk[HAETAE_CRYPTO_SECRETKEYBYTES],
+             const uint8_t vk[HAETAE_CRYPTO_PUBLICKEYBYTES], const polyvecm *s0,
+             const polyveck *s1, const uint8_t key[HAETAE_SEEDBYTES]) {
+  unsigned int i;
+  memcpy(sk, vk, HAETAE_CRYPTO_PUBLICKEYBYTES);
+
+  sk += HAETAE_CRYPTO_PUBLICKEYBYTES;
+  for (i = 0; i < HAETAE_M; ++i)
+    pack_poly_eta(sk + i * HAETAE_POLYETA_PACKEDBYTES, &s0->vec[i]);
+
+  sk += (HAETAE_L - 1) * HAETAE_POLYETA_PACKEDBYTES;
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  for (i = 0; i < HAETAE_K; ++i)
+    pack_poly2_eta(sk + i * HAETAE_POLY2ETA_PACKEDBYTES, &s1->vec[i]);
+  sk += HAETAE_K * HAETAE_POLY2ETA_PACKEDBYTES;
+#elif HAETAE_MODE == HAETAE_MODE5
+  for (i = 0; i < HAETAE_K; ++i)
+    pack_poly_eta(sk + i * HAETAE_POLYETA_PACKEDBYTES, &s1->vec[i]);
+  sk += HAETAE_K * HAETAE_POLYETA_PACKEDBYTES;
+#else
+#error "Not yet implemented."
+#endif
+  memcpy(sk, key, HAETAE_SEEDBYTES);
+}
+
+/*************************************************
+ * Name:        unpack_sk
+ *
+ * Description: Unpack secret key sk = (A, s).
+ *
+ * Arguments:   - polyvecl A[HAETAE_K]: output polyvecl array for A
+ *              - polyvecl s0: output polyvecl pointer for s0
+ *              - polyveck s1: output polyveck pointer for s1
+ *              - const uint8_t sk[]: byte array containing bit-packed sk
+ *
+ * Specification: Implements @[Algorithm 26, UnPackSK]
+ **************************************************/
+void unpack_sk(polyvecl A[HAETAE_K], polyvecm *s0, polyveck *s1, uint8_t *key,
+               const uint8_t sk[HAETAE_CRYPTO_SECRETKEYBYTES]) {
+  unsigned int i;
+  unpack_vk(A, sk);
+
+  sk += HAETAE_CRYPTO_PUBLICKEYBYTES;
+  for (i = 0; i < HAETAE_M; ++i)
+    unpack_poly_eta(&s0->vec[i], sk + i * HAETAE_POLYETA_PACKEDBYTES);
+
+  sk += HAETAE_M * HAETAE_POLYETA_PACKEDBYTES;
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  for (i = 0; i < HAETAE_K; ++i)
+    unpack_poly2_eta(&s1->vec[i], sk + i * HAETAE_POLY2ETA_PACKEDBYTES);
+
+  sk += HAETAE_K * HAETAE_POLY2ETA_PACKEDBYTES;
+#elif HAETAE_MODE == HAETAE_MODE5
+  for (i = 0; i < HAETAE_K; ++i)
+    unpack_poly_eta(&s1->vec[i], sk + i * HAETAE_POLYETA_PACKEDBYTES);
+
+  sk += HAETAE_K * HAETAE_POLYETA_PACKEDBYTES;
+#endif
+  memcpy(key, sk, HAETAE_SEEDBYTES);
+}
+
+/*************************************************
+ * Name:        pack_sig
+ *
+ * Description: Bit-pack signature sig = (c, LB(z1), len(x), len(y), x =
+ *              Enc(HB(z1)), y = Enc(h)), Zeropadding.
+ *
+ * Arguments:   - uint8_t sig[]: output byte array
+ *              - const poly *c: pointer to challenge polynomial
+ *              - const polyvecl *lowbits_z1: pointer to vector LowBits(z1) of
+ *              length L
+ *              - const polyvecl *highbits_z1: pointer to vector HighBits(z1) of
+ *              length L
+ *              - const polyveck *h: pointer t vector h of length HAETAE_K
+ * Returns 1 in case the signature packing failed; otherwise 0.
+ *
+ * Specification: Implements @[Algorithm 27, PackSIG]
+ **************************************************/
+int pack_sig(uint8_t sig[HAETAE_CRYPTO_BYTES], const poly *c,
+             const polyvecl *lowbits_z1, const polyvecl *highbits_z1,
+             const polyveck *h) {
+
+  uint8_t encoded_h[HAETAE_N * HAETAE_K];
+  uint8_t encoded_hb_z1[HAETAE_N * HAETAE_L];
+  uint16_t size_enc_h, size_enc_hb_z1;
+  uint8_t offset_enc_h, offset_enc_hb_z1;
+
+  // init/padding with zeros:
+  memset(sig, 0, HAETAE_CRYPTO_BYTES);
+
+  // encode challenge
+  for (size_t i = 0; i < HAETAE_N; i++) {
+    sig[i / 8] |= c->coeffs[i] << (i % 8);
+  }
+  sig += HAETAE_N / 8;
+
+  for (int i = 0; i < HAETAE_L; ++i)
+    poly_decomposed_pack(sig + HAETAE_N * i, &lowbits_z1->vec[i]);
+  sig += HAETAE_L * HAETAE_N;
+
+  size_enc_hb_z1 = encode_hb_z1(encoded_hb_z1, &highbits_z1->vec[0].coeffs[0]);
+  size_enc_h = encode_h(encoded_h, &h->vec[0].coeffs[0]);
+
+  if (size_enc_h == 0 || size_enc_hb_z1 == 0) {
+    return 1; // encoding failed
+  }
+
+  // The size of the encoded h and HB(z1) does not always fit in one byte,
+  // thus we output a one byte offset to a fixed baseline
+  if (size_enc_h < HAETAE_BASE_ENC_H ||
+      size_enc_hb_z1 < HAETAE_BASE_ENC_HB_Z1 ||
+      size_enc_h > HAETAE_BASE_ENC_H + 255 ||
+      size_enc_hb_z1 > HAETAE_BASE_ENC_HB_Z1 + 255) {
+    return 1; // encoding size offset out of range
+  }
+
+  offset_enc_hb_z1 = size_enc_hb_z1 - HAETAE_BASE_ENC_HB_Z1;
+  offset_enc_h = size_enc_h - HAETAE_BASE_ENC_H;
+
+  if (HAETAE_SEEDBYTES + HAETAE_L * HAETAE_N + 2 + size_enc_hb_z1 + size_enc_h >
+      HAETAE_CRYPTO_BYTES) {
+    return 1; // signature too big
+  }
+
+  sig[0] = offset_enc_hb_z1;
+  sig[1] = offset_enc_h;
+  sig += 2;
+
+  memcpy(sig, encoded_hb_z1, size_enc_hb_z1);
+  sig += size_enc_hb_z1;
+
+  memcpy(sig, encoded_h, size_enc_h);
+
+  return 0;
+}
+
+/*************************************************
+ * Name:        unpack_sig
+ *
+ * Description: Unpack signature sig = (c, LB(z1), len(x), len(y), x =
+ *              Enc(HB(z1)), y = Enc(h)), Zeropadding.
+ *
+ * Arguments:   - poly *c: pointer to challenge polynomial
+ *              - polyvecl *lowbits_z1: pointer to output vector LowBits(z1)
+ *              - polyvecl *highbits_z1: pointer to output vector HighBits(z1)
+ *              - polyveck *h: pointer to output vector h
+ *              - const uint8_t sig[]: byte array containing
+ *                bit-packed signature
+ *
+ * Returns 1 in case of malformed signature; otherwise 0.
+ *
+ * Specification: Implements @[Algorithm 28, UnpackSIG]
+ **************************************************/
+int unpack_sig(poly *c, polyvecl *lowbits_z1, polyvecl *highbits_z1,
+               polyveck *h, const uint8_t sig[HAETAE_CRYPTO_BYTES]) {
+
+  unsigned int i;
+  uint16_t size_enc_hb_z1, size_enc_h;
+
+  for (i = 0; i < HAETAE_N; i++) {
+    c->coeffs[i] = (sig[i / 8] >> (i % 8)) & 1;
+  }
+  sig += HAETAE_N / 8;
+
+  for (i = 0; i < HAETAE_L; ++i)
+    poly_decomposed_unpack(&lowbits_z1->vec[i], sig + HAETAE_N * i);
+  sig += HAETAE_L * HAETAE_N;
+
+  size_enc_hb_z1 = (uint16_t)sig[0] + HAETAE_BASE_ENC_HB_Z1;
+  size_enc_h = (uint16_t)sig[1] + HAETAE_BASE_ENC_H;
+  sig += 2;
+
+  if (HAETAE_CRYPTO_BYTES < (2 + HAETAE_L * HAETAE_N + HAETAE_SEEDBYTES +
+                             size_enc_h + size_enc_hb_z1))
+    return 1; // invalid size_enc_h and/or size_enc_hb_z1
+
+  if (decode_hb_z1(&highbits_z1->vec[0].coeffs[0], sig, size_enc_hb_z1)) {
+    return 1; // decoding failed
+  }
+
+  sig += size_enc_hb_z1;
+
+  if (decode_h(&h->vec[0].coeffs[0], sig, size_enc_h)) {
+    return 1; // decoding failed
+  }
+
+  sig += size_enc_h;
+
+  for (int i = 0;
+       i < HAETAE_CRYPTO_BYTES - (HAETAE_SEEDBYTES + HAETAE_L * HAETAE_N + 2 +
+                                  size_enc_hb_z1 + size_enc_h);
+       i++)
+    if (sig[i] != 0)
+      return 1; // verify zero padding
+
+  return 0;
+}
+
+/*************************************************
+ * Name:        pack_poly_q
+ *
+ * Description: Bit-pack polynomial with coefficients in [0, HAETAE_Q - 1].
+ *
+ * Arguments:   - uint8_t *r: pointer to output byte array with at least
+ *              HAETAE_POLYQ_PACKEDBYTES bytes
+ *              - const poly *a: pointer to input polynomial
+ *
+ * Specification: Implements @[Algorithm 29, PackPolyQ]
+ **************************************************/
+void pack_poly_q(uint8_t *r, const poly *a) {
+  unsigned int i;
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  int b_idx = 0, d_idx = 0;
+
+  for (i = 0; i < (HAETAE_N >> 3); ++i) {
+    b_idx = 15 * i;
+    d_idx = 8 * i;
+
+    r[b_idx] = (a->coeffs[d_idx] & 0xff);
+    r[b_idx + 1] =
+        ((a->coeffs[d_idx] >> 8) & 0x7f) | ((a->coeffs[d_idx + 1] & 0x1) << 7);
+    r[b_idx + 2] = ((a->coeffs[d_idx + 1] >> 1) & 0xff);
+    r[b_idx + 3] = ((a->coeffs[d_idx + 1] >> 9) & 0x3f) |
+                   ((a->coeffs[d_idx + 2] & 0x3) << 6);
+    r[b_idx + 4] = ((a->coeffs[d_idx + 2] >> 2) & 0xff);
+    r[b_idx + 5] = ((a->coeffs[d_idx + 2] >> 10) & 0x1f) |
+                   ((a->coeffs[d_idx + 3] & 0x7) << 5);
+    r[b_idx + 6] = ((a->coeffs[d_idx + 3] >> 3) & 0xff);
+    r[b_idx + 7] = ((a->coeffs[d_idx + 3] >> 11) & 0xf) |
+                   ((a->coeffs[d_idx + 4] & 0xf) << 4);
+    r[b_idx + 8] = ((a->coeffs[d_idx + 4] >> 4) & 0xff);
+    r[b_idx + 9] = ((a->coeffs[d_idx + 4] >> 12) & 0x7) |
+                   ((a->coeffs[d_idx + 5] & 0x1f) << 3);
+    r[b_idx + 10] = ((a->coeffs[d_idx + 5] >> 5) & 0xff);
+    r[b_idx + 11] = ((a->coeffs[d_idx + 5] >> 13) & 0x3) |
+                    ((a->coeffs[d_idx + 6] & 0x3f) << 2);
+    r[b_idx + 12] = ((a->coeffs[d_idx + 6] >> 6) & 0xff);
+    r[b_idx + 13] = ((a->coeffs[d_idx + 6] >> 14) & 0x1) |
+                    (a->coeffs[d_idx + 7] & 0x7f) << 1;
+    r[b_idx + 14] = ((a->coeffs[d_idx + 7] >> 7) & 0xff);
+  }
+#elif HAETAE_MODE == HAETAE_MODE5
+  for (i = 0; i < HAETAE_N / 1; ++i) {
+    r[2 * i + 0] = a->coeffs[1 * i + 0] >> 0;
+    r[2 * i + 1] = a->coeffs[1 * i + 0] >> 8;
+  }
+#endif
+}
+
+/*************************************************
+ * Name:        unpack_poly_q
+ *
+ * Description: Unpack polynomial with coefficients in [0, HAETAE_Q - 1].
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t *a: byte array with bit-packed polynomial
+ *
+ * Specification: Implements @[Algorithm 30, UnpackPolyQ]
+ **************************************************/
+void unpack_poly_q(poly *r, const uint8_t *a) {
+  unsigned int i;
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  int b_idx = 0, d_idx = 0;
+
+  for (i = 0; i < (HAETAE_N >> 3); ++i) {
+    b_idx = 15 * i;
+    d_idx = 8 * i;
+
+    r->coeffs[d_idx] = (a[b_idx] & 0xff) | ((a[b_idx + 1] & 0x7f) << 8);
+    r->coeffs[d_idx + 1] = ((a[b_idx + 1] >> 7) & 0x1) |
+                           ((a[b_idx + 2] & 0xff) << 1) |
+                           ((a[b_idx + 3] & 0x3f) << 9);
+    r->coeffs[d_idx + 2] = ((a[b_idx + 3] >> 6) & 0x3) |
+                           ((a[b_idx + 4] & 0xff) << 2) |
+                           ((a[b_idx + 5] & 0x1f) << 10);
+    r->coeffs[d_idx + 3] = ((a[b_idx + 5] >> 5) & 0x7) |
+                           ((a[b_idx + 6] & 0xff) << 3) |
+                           ((a[b_idx + 7] & 0xf) << 11);
+    r->coeffs[d_idx + 4] = ((a[b_idx + 7] >> 4) & 0xf) |
+                           ((a[b_idx + 8] & 0xff) << 4) |
+                           ((a[b_idx + 9] & 0x7) << 12);
+    r->coeffs[d_idx + 5] = ((a[b_idx + 9] >> 3) & 0x1f) |
+                           ((a[b_idx + 10] & 0xff) << 5) |
+                           ((a[b_idx + 11] & 0x3) << 13);
+    r->coeffs[d_idx + 6] = ((a[b_idx + 11] >> 2) & 0x3f) |
+                           ((a[b_idx + 12] & 0xff) << 6) |
+                           ((a[b_idx + 13] & 0x1) << 14);
+    r->coeffs[d_idx + 7] =
+        ((a[b_idx + 13] >> 1) & 0x7f) | ((a[b_idx + 14] & 0xff) << 7);
+  }
+
+#elif HAETAE_MODE == HAETAE_MODE5
+  for (i = 0; i < HAETAE_N / 1; ++i) {
+    r->coeffs[1 * i + 0] = a[2 * i + 0] >> 0;
+    r->coeffs[1 * i + 0] |= (uint16_t)a[2 * i + 1] << 8;
+    r->coeffs[1 * i + 0] &= 0xffff;
+  }
+#endif
+}
+
+/*************************************************
+ * Name:        pack_polyeta
+ *
+ * Description: Bit-pack polynomial with coefficients in [-ETA,ETA].
+ *
+ * Arguments:   - uint8_t *r: pointer to output byte array with at least
+ *              HAETAE_POLYETA_PACKEDBYTES bytes
+ *              - const poly *a: pointer to input polynomial
+ *
+ * Specification: Implements @[Algorithm 31, PackPolyEta]
+ **************************************************/
+void pack_poly_eta(uint8_t *r, const poly *a) {
+  unsigned int i;
+  uint8_t t[8];
+
+  for (i = 0; i < HAETAE_N / 4; ++i) {
+    t[0] = HAETAE_ETA - a->coeffs[4 * i + 0];
+    t[1] = HAETAE_ETA - a->coeffs[4 * i + 1];
+    t[2] = HAETAE_ETA - a->coeffs[4 * i + 2];
+    t[3] = HAETAE_ETA - a->coeffs[4 * i + 3];
+    r[i] = t[0] >> 0;
+    r[i] |= t[1] << 2;
+    r[i] |= t[2] << 4;
+    r[i] |= t[3] << 6;
+  }
+}
+
+/*************************************************
+ * Name:        unpack_poly_eta
+ *
+ * Description: Unpack polynomial with coefficients in [-ETA,ETA].
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t *a: byte array with bit-packed polynomial
+ *
+ * Specification: Implements @[Algorithm 32, UnpackPolyEta]
+ **************************************************/
+void unpack_poly_eta(poly *r, const uint8_t *a) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_N / 4; ++i) {
+    r->coeffs[4 * i + 0] = a[i] >> 0;
+    r->coeffs[4 * i + 0] &= 0x3;
+
+    r->coeffs[4 * i + 1] = a[i] >> 2;
+    r->coeffs[4 * i + 1] &= 0x3;
+
+    r->coeffs[4 * i + 2] = a[i] >> 4;
+    r->coeffs[4 * i + 2] &= 0x3;
+
+    r->coeffs[4 * i + 3] = a[i] >> 6;
+    r->coeffs[4 * i + 3] &= 0x3;
+
+    r->coeffs[4 * i + 0] = HAETAE_ETA - r->coeffs[4 * i + 0];
+    r->coeffs[4 * i + 1] = HAETAE_ETA - r->coeffs[4 * i + 1];
+    r->coeffs[4 * i + 2] = HAETAE_ETA - r->coeffs[4 * i + 2];
+    r->coeffs[4 * i + 3] = HAETAE_ETA - r->coeffs[4 * i + 3];
+  }
+}
+
+/*************************************************
+ * Name:        pack_poly2eta
+ *
+ * Description: Bit-pack polynomial with coefficients in [-ETA-1,ETA+1].
+ *
+ * Arguments:   - uint8_t *r: pointer to output byte array with at least
+ *                            HAETAE_POLYETA_PACKEDBYTES bytes
+ *              - const poly *a: pointer to input polynomial
+ *
+ * Specification: Implements @[Algorithm 33, PackPoly2Eta]
+ **************************************************/
+void pack_poly2_eta(uint8_t *r, const poly *a) {
+  unsigned int i;
+  uint8_t t[8];
+  for (i = 0; i < HAETAE_N / 8; ++i) {
+    t[0] = 2 * HAETAE_ETA - a->coeffs[8 * i + 0];
+    t[1] = 2 * HAETAE_ETA - a->coeffs[8 * i + 1];
+    t[2] = 2 * HAETAE_ETA - a->coeffs[8 * i + 2];
+    t[3] = 2 * HAETAE_ETA - a->coeffs[8 * i + 3];
+    t[4] = 2 * HAETAE_ETA - a->coeffs[8 * i + 4];
+    t[5] = 2 * HAETAE_ETA - a->coeffs[8 * i + 5];
+    t[6] = 2 * HAETAE_ETA - a->coeffs[8 * i + 6];
+    t[7] = 2 * HAETAE_ETA - a->coeffs[8 * i + 7];
+
+    r[3 * i + 0] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6);
+    r[3 * i + 1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7);
+    r[3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5);
+  }
+}
+
+/*************************************************
+ * Name:        unpack_poly2eta
+ *
+ * Description: Unpack polynomial with coefficients in [-ETA-1,ETA+1].
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t *a: byte array with bit-packed polynomial
+ *
+ * Specification: Implements @[Algorithm 34, UnpackPoly2Eta]
+ **************************************************/
+void unpack_poly2_eta(poly *r, const uint8_t *a) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_N / 8; ++i) {
+    r->coeffs[8 * i + 0] = (a[3 * i + 0] >> 0) & 7;
+    r->coeffs[8 * i + 1] = (a[3 * i + 0] >> 3) & 7;
+    r->coeffs[8 * i + 2] = ((a[3 * i + 0] >> 6) | (a[3 * i + 1] << 2)) & 7;
+    r->coeffs[8 * i + 3] = (a[3 * i + 1] >> 1) & 7;
+    r->coeffs[8 * i + 4] = (a[3 * i + 1] >> 4) & 7;
+    r->coeffs[8 * i + 5] = ((a[3 * i + 1] >> 7) | (a[3 * i + 2] << 1)) & 7;
+    r->coeffs[8 * i + 6] = (a[3 * i + 2] >> 2) & 7;
+    r->coeffs[8 * i + 7] = (a[3 * i + 2] >> 5) & 7;
+
+    r->coeffs[8 * i + 0] = 2 * HAETAE_ETA - r->coeffs[8 * i + 0];
+    r->coeffs[8 * i + 1] = 2 * HAETAE_ETA - r->coeffs[8 * i + 1];
+    r->coeffs[8 * i + 2] = 2 * HAETAE_ETA - r->coeffs[8 * i + 2];
+    r->coeffs[8 * i + 3] = 2 * HAETAE_ETA - r->coeffs[8 * i + 3];
+    r->coeffs[8 * i + 4] = 2 * HAETAE_ETA - r->coeffs[8 * i + 4];
+    r->coeffs[8 * i + 5] = 2 * HAETAE_ETA - r->coeffs[8 * i + 5];
+    r->coeffs[8 * i + 6] = 2 * HAETAE_ETA - r->coeffs[8 * i + 6];
+    r->coeffs[8 * i + 7] = 2 * HAETAE_ETA - r->coeffs[8 * i + 7];
+  }
+}
+
+/*************************************************
+ * Name:        pack_vec_highbits
+ *
+ * Description: pack highbits of a vector of polynomials
+ *
+ * Arguments:   - uint8_t *buf: pointer to output bytes array
+ *              - const polyveck *v: a vector of polynomials
+ *
+ * Specification: Implements @[Algorithm 35, PackVecHighBits]
+ **************************************************/
+void pack_vec_highbits(uint8_t *buf, const polyveck *v) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_K; i++) {
+    pack_poly_highbits(buf + i * HAETAE_POLY_HIGHBITS_PACKEDBYTES, &v->vec[i]);
+  }
+}
+
+/*************************************************
+ * Name:        pack_poly_highbits
+ *
+ * Description: pack highbits of a polynomial
+ *
+ * Arguments:   - uint8_t *buf: pointer to output bytes array
+ *              - const poly *v: a polynomial
+ *
+ * Specification: Implements @[Algorithm 36, PackPolyHighBits]
+ **************************************************/
+void pack_poly_highbits(uint8_t *buf, const poly *a) {
+  unsigned int i;
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  for (i = 0; i < HAETAE_N; i++) {
+    // if ((uint32_t)a->coeffs[i] > 0xFF) {
+    //     printf("1byte overflow: i=%d coeff=%08X\n", i, a->coeffs[i]);
+    // }
+    buf[i] = (uint8_t)a->coeffs[i];
+  }
+#elif HAETAE_MODE == HAETAE_MODE5
+  for (i = 0; i < HAETAE_N / 8; i++) {
+    buf[9 * i + 0] = a->coeffs[8 * i + 0] & 0xff;
+
+    buf[9 * i + 1] = (a->coeffs[8 * i + 0] >> 8) & 0x01;
+    buf[9 * i + 1] |= (a->coeffs[8 * i + 1] << 1) & 0xff;
+
+    buf[9 * i + 2] = (a->coeffs[8 * i + 1] >> 7) & 0x03;
+    buf[9 * i + 2] |= (a->coeffs[8 * i + 2] << 2) & 0xff;
+
+    buf[9 * i + 3] = (a->coeffs[8 * i + 2] >> 6) & 0x07;
+    buf[9 * i + 3] |= (a->coeffs[8 * i + 3] << 3) & 0xff;
+
+    buf[9 * i + 4] = (a->coeffs[8 * i + 3] >> 5) & 0x0f;
+    buf[9 * i + 4] |= (a->coeffs[8 * i + 4] << 4) & 0xff;
+
+    buf[9 * i + 5] = (a->coeffs[8 * i + 4] >> 4) & 0x1f;
+    buf[9 * i + 5] |= (a->coeffs[8 * i + 5] << 5) & 0xff;
+
+    buf[9 * i + 6] = (a->coeffs[8 * i + 5] >> 3) & 0x3f;
+    buf[9 * i + 6] |= (a->coeffs[8 * i + 6] << 6) & 0xff;
+
+    buf[9 * i + 7] = (a->coeffs[8 * i + 6] >> 2) & 0x7f;
+    buf[9 * i + 7] |= (a->coeffs[8 * i + 7] << 7) & 0xff;
+
+    buf[9 * i + 8] = (a->coeffs[8 * i + 7] >> 1) & 0xff;
+  }
+#endif
+}
+
+/*************************************************
+ * Name:        pack_poly_lsb
+ *
+ * Description: pack lsb of a polynomial
+ *
+ * Arguments:   - uint8_t *buf: pointer to output bytes array
+ *              - const poly *a: a polynomial
+ *
+ * Specification: Implements @[Algorithm 37, PackPolyLsb]
+ **************************************************/
+void pack_poly_lsb(uint8_t *buf, const poly *a) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_N; i++) {
+    if ((i % 8) == 0) {
+      buf[i / 8] = 0;
+    }
+    buf[i / 8] |= (a->coeffs[i] & 1) << (i % 8);
+  }
+}
diff --git a/cbits/third_party/HAETAE/src/poly.c b/cbits/third_party/HAETAE/src/poly.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/poly.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: MIT
+
+#include "poly.h"
+#include "decompose.h"
+#include "ntt.h"
+#include "params.h"
+#include "reduce.h"
+#include "sampler.h"
+#include "symmetric.h"
+
+#include <stdint.h>
+
+/*************************************************
+ * Name:        poly_add
+ *
+ * Description: Add polynomials. No modular reduction is performed.
+ *
+ * Arguments:   - poly *c: pointer to output polynomial
+ *              - const poly *a: pointer to first summand
+ *              - const poly *b: pointer to second summand
+ **************************************************/
+void poly_add(poly *c, const poly *a, const poly *b) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    c->coeffs[i] = a->coeffs[i] + b->coeffs[i];
+}
+
+/*************************************************
+ * Name:        poly_sub
+ *
+ * Description: Subtract polynomials. No modular reduction is
+ *              performed.
+ *
+ * Arguments:   - poly *c: pointer to output polynomial
+ *              - const poly *a: pointer to first input polynomial
+ *              - const poly *b: pointer to second input polynomial to be
+ *              subtraced from first input polynomial
+ **************************************************/
+void poly_sub(poly *c, const poly *a, const poly *b) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    c->coeffs[i] = a->coeffs[i] - b->coeffs[i];
+}
+
+/*************************************************
+ * Name:        poly_pointwise_montgomery
+ *
+ * Description: Pointwise multiplication of polynomials in NTT domain
+ *              representation and multiplication of resulting polynomial
+ *              by 2^{-32}.
+ *
+ * Arguments:   - poly *c: pointer to output polynomial
+ *              - const poly *a: pointer to first input polynomial
+ *              - const poly *b: pointer to second input polynomial
+ **************************************************/
+void poly_pointwise_montgomery(poly *c, const poly *a, const poly *b) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    c->coeffs[i] = montgomery_reduce((int64_t)a->coeffs[i] * b->coeffs[i]);
+}
+
+/*************************************************
+ * Name:        poly_reduce2q
+ *
+ * Description: Inplace reduction of all coefficients of polynomial to 2q
+ *
+ * Arguments:   - poly *a: pointer to input/output polynomial
+ **************************************************/
+void poly_reduce2q(poly *a) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    a->coeffs[i] = reduce32_2q(a->coeffs[i]);
+}
+
+/*************************************************
+ * Name:        poly_freeze2q
+ *
+ * Description: For all coefficients of in/out polynomial compute standard
+ *              representative r = a mod^+ 2Q
+ *
+ * Arguments:   - poly *a: pointer to input/output polynomial
+ **************************************************/
+void poly_freeze2q(poly *a) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    a->coeffs[i] = freeze2q(a->coeffs[i]);
+}
+
+/*************************************************
+ * Name:        poly_freeze
+ *
+ * Description: For all coefficients of in/out polynomial compute standard
+ *              representative r = a mod^+ HAETAE_Q
+ *
+ * Arguments:   - poly *a: pointer to input/output polynomial
+ **************************************************/
+void poly_freeze(poly *a) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    a->coeffs[i] = freeze(a->coeffs[i]);
+}
+
+/*************************************************
+ * Name:        poly_highbits
+ *
+ * Description: Compute HighBits of polynomial
+ *
+ * Arguments:   - poly *a2: pointer to output polynomial
+ *              - const poly *a: pointer to input polynomial
+ **************************************************/
+void poly_highbits(poly *a2, const poly *a) {
+  unsigned int i;
+  int32_t a1tmp;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    decompose_z1(&a2->coeffs[i], &a1tmp, a->coeffs[i]);
+}
+
+/*************************************************
+ * Name:        poly_lowbits
+ *
+ * Description: Compute LowBits of polynomial
+ *
+ * Arguments:   - poly *a1: pointer to output polynomial
+ *              - const poly *a: pointer to input polynomial
+ **************************************************/
+void poly_lowbits(poly *a1, const poly *a) {
+  unsigned int i = 0;
+  int32_t a2tmp = 0;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    decompose_z1(&a2tmp, &a1->coeffs[i], a->coeffs[i]);
+}
+
+/*************************************************
+ * Name:        poly_compose
+ *
+ * Description: Compose HighBits and LowBits to recreate the polynomial
+ *
+ * Arguments:   - poly *a3: pointer to output polynomial
+ *              - const poly *ha: pointer to HighBits polynomial
+ *              - const poly *la: pointer to HighBits polynomial
+ **************************************************/
+void poly_compose(poly *a, const poly *ha, const poly *la) {
+  unsigned int i = 0;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    a->coeffs[i] = (ha->coeffs[i] * 256) + la->coeffs[i];
+}
+
+/*************************************************
+ * Name:        poly_lsb
+ *
+ * Description: Compute least significant bits of polynomial
+ *
+ * Arguments:   - poly *a0: pointer to output polynomial
+ *              - const poly *a: pointer to input polynomial
+ **************************************************/
+void poly_lsb(poly *a0, const poly *a) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    a0->coeffs[i] = a->coeffs[i] & 1;
+}
+
+/*************************************************
+ * Name:        poly_uniform
+ *
+ * Description: Sample polynomial with uniformly random coefficients
+ *              in [0,Q-1] by performing rejection sampling on the
+ *              output stream of SHAKE128(seed|nonce)
+ *
+ * Arguments:   - poly *a: pointer to output polynomial
+ *              - const uint8_t seed[]: byte array with seed of length
+ *              HAETAE_SEEDBYTES
+ *              - uint16_t nonce: 2-byte nonce
+ *
+ * Specification: Implements @[Algorithm 8, PolyUniform]
+ **************************************************/
+#define POLY_UNIFORM_NBLOCKS                                                   \
+  ((512 + STREAM128_BLOCKBYTES - 1) / STREAM128_BLOCKBYTES)
+// HAETAE_N * 2(random bytes for [0, HAETAE_Q - 1])
+
+void poly_uniform(poly *a, const uint8_t seed[HAETAE_SEEDBYTES],
+                  uint16_t nonce) {
+  unsigned int i, ctr, off;
+  unsigned int buflen = POLY_UNIFORM_NBLOCKS * STREAM128_BLOCKBYTES;
+  uint8_t buf[POLY_UNIFORM_NBLOCKS * STREAM128_BLOCKBYTES + 1];
+  stream128_state state;
+
+  stream128_init(&state, seed, nonce);
+  stream128_squeezeblocks(buf, POLY_UNIFORM_NBLOCKS, &state);
+
+  ctr = rej_uniform(a->coeffs, HAETAE_N, buf, buflen);
+
+  while (ctr < HAETAE_N) {
+    off = buflen % 2;
+    for (i = 0; i < off; ++i)
+      buf[i] = buf[buflen - off + i];
+
+    stream128_squeezeblocks(buf + off, 1, &state);
+    buflen = STREAM128_BLOCKBYTES + off;
+    ctr += rej_uniform(a->coeffs + ctr, HAETAE_N - ctr, buf, buflen);
+  }
+}
+
+/*************************************************
+ * Name:        poly_uniform_eta
+ *
+ * Description: Sample polynomial with uniformly random coefficients
+ *              in [-ETA,ETA] by performing rejection sampling on the
+ *              output stream from SHAKE256(seed|nonce)
+ *
+ * Arguments:   - poly *a: pointer to output polynomial
+ *              - const uint8_t seed[]: byte array with seed of length
+ *              HAETAE_CRHBYTES
+ *              - uint16_t nonce: 2-byte nonce
+ *
+ * Specification: Implements @[Algorithm 11, PolyUniformEta]
+ **************************************************/
+#define POLY_UNIFORM_ETA_NBLOCKS                                               \
+  ((136 + STREAM256_BLOCKBYTES - 1) / STREAM256_BLOCKBYTES) // 1
+
+void poly_uniform_eta(poly *a, const uint8_t seed[HAETAE_CRHBYTES],
+                      uint16_t nonce) {
+  unsigned int ctr;
+  unsigned int buflen = POLY_UNIFORM_ETA_NBLOCKS * STREAM256_BLOCKBYTES;
+  uint8_t buf[POLY_UNIFORM_ETA_NBLOCKS * STREAM256_BLOCKBYTES];
+  stream256_state state;
+
+  stream256_init(&state, seed, nonce);
+  stream256_squeezeblocks(buf, POLY_UNIFORM_ETA_NBLOCKS, &state);
+
+  ctr = rej_eta(a->coeffs, HAETAE_N, buf, buflen);
+
+  while (ctr < HAETAE_N) {
+    stream256_squeezeblocks(buf, 1, &state);
+    ctr += rej_eta(a->coeffs + ctr, HAETAE_N - ctr, buf, STREAM256_BLOCKBYTES);
+  }
+}
+
+uint8_t hammingWeight_8(uint8_t x) {
+  x = (x & 0x55) + (x >> 1 & 0x55);
+  x = (x & 0x33) + (x >> 2 & 0x33);
+  x = (x & 0x0F) + (x >> 4 & 0x0F);
+
+  return x;
+}
+
+/*************************************************
+ * Name:        poly_challenge
+ *
+ * Description: Implementation of challenge. Samples polynomial with HAETAE_TAU
+ *              1 coefficients using the output stream of SHAKE256(seed).
+ *
+ * Arguments:   - poly *c: pointer to output polynomial
+ *              - const uint8_t highbits_lsb[]: packed highbits and lsb
+ *              - const uint8_t mu[]: hash of vk and message
+ *
+ * Specification: Implements @[Algorithm 18, SampleChallenge]
+ **************************************************/
+void poly_challenge(
+    poly *c,
+    const uint8_t highbits_lsb[HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES +
+                               HAETAE_POLYC_PACKEDBYTES],
+    const uint8_t mu[HAETAE_SEEDBYTES]) {
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  unsigned int i, b, pos = 0;
+  uint8_t buf[XOF256_BLOCKBYTES];
+  xof256_state state;
+
+  // H(HighBits(A * y mod 2q), LSB(round(y0) * j), HAETAE_M)
+  xof256_absorb_twice(&state, highbits_lsb,
+                      HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES +
+                          HAETAE_POLYC_PACKEDBYTES,
+                      mu, HAETAE_SEEDBYTES);
+  xof256_squeezeblocks(buf, 1, &state);
+
+  for (i = 0; i < HAETAE_N; ++i)
+    c->coeffs[i] = 0;
+  for (i = HAETAE_N - HAETAE_TAU; i < HAETAE_N; ++i) {
+    do {
+      if (pos >= XOF256_BLOCKBYTES) {
+        xof256_squeezeblocks(buf, 1, &state);
+        pos = 0;
+      }
+
+      b = buf[pos++];
+    } while (b > i);
+
+    c->coeffs[i] = c->coeffs[b];
+    c->coeffs[b] = 1;
+  }
+#elif HAETAE_MODE == HAETAE_MODE5
+  unsigned int i, hwt = 0, cond = 0;
+  uint8_t mask = 0, w0 = 0;
+  uint8_t buf[32] = {0};
+  xof256_state state;
+
+  // H(HighBits(A * y mod 2q), LSB(round(y0) * j), HAETAE_M)
+  xof256_absorb_twice(&state, highbits_lsb,
+                      HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES +
+                          HAETAE_POLYC_PACKEDBYTES,
+                      mu, HAETAE_SEEDBYTES);
+  xof256_squeeze(buf, 32, &state);
+
+  for (i = 0; i < 32; ++i)
+    hwt += hammingWeight_8(buf[i]);
+
+  cond = (128 - hwt);
+  mask = 0xff & (cond >> 8);
+  w0 = -(buf[0] & 1);
+  mask = w0 ^ ((-(!!cond & 1)) & (mask ^ w0)); // mask = !!cond ? mask : w0
+  for (i = 0; i < 32; ++i) {
+    buf[i] ^= mask;
+    c->coeffs[8 * i] = buf[i] & 1;
+    c->coeffs[8 * i + 1] = (buf[i] >> 1) & 1;
+    c->coeffs[8 * i + 2] = (buf[i] >> 2) & 1;
+    c->coeffs[8 * i + 3] = (buf[i] >> 3) & 1;
+    c->coeffs[8 * i + 4] = (buf[i] >> 4) & 1;
+    c->coeffs[8 * i + 5] = (buf[i] >> 5) & 1;
+    c->coeffs[8 * i + 6] = (buf[i] >> 6) & 1;
+    c->coeffs[8 * i + 7] = (buf[i] >> 7) & 1;
+  }
+#endif
+}
+
+void poly_decomposed_pack(uint8_t *buf, const poly *a) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_N; i++) {
+    buf[i] = a->coeffs[i];
+  }
+}
+
+void poly_decomposed_unpack(poly *a, const uint8_t *buf) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_N; i++) {
+    a->coeffs[i] = (int8_t)buf[i];
+  }
+}
+
+/*************************************************
+ * Name:        poly_fromcrt
+ *
+ * Description: Compute coefficient-wise inverse CRT from the "mod q"
+ *              polynomial u and the "mod 2" polynomial v: if the LSBs of the
+ *              two coefficients agree, keep the "mod q" coefficient, otherwise
+ *              add q. Output coefficients lie in [0, 2q-1].
+ *
+ * Arguments:   - poly *w: pointer to output polynomial
+ *              - const poly *u: pointer to input polynomial ("mod q")
+ *              - const poly *v: pointer to input polynomial ("mod 2")
+ *
+ * Specification: Implements @[Algorithm 4, poly_fromcrt]
+ **************************************************/
+void poly_fromcrt(poly *w, const poly *u, const poly *v) {
+  unsigned int i;
+  int32_t xq, x2;
+
+  for (i = 0; i < HAETAE_N; i++) {
+    xq = u->coeffs[i];
+    x2 = v->coeffs[i];
+    w->coeffs[i] = xq + (HAETAE_Q & -((xq ^ x2) & 1));
+  }
+}
+
+/*************************************************
+ * Name:        poly_fromcrt0
+ *
+ * Description: Compute coefficient-wise inverse CRT from the "mod q"
+ *              polynomial u, assuming the "mod 2" polynomial is zero: if the
+ *              LSB of a coefficient is zero, keep it, otherwise add q. Output
+ *              coefficients lie in [0, 2q-1].
+ *
+ * Arguments:   - poly *w: pointer to output polynomial
+ *              - const poly *u: pointer to input polynomial ("mod q")
+ *
+ * Specification: Implements @[Algorithm 5, poly_fromcrt0]
+ **************************************************/
+void poly_fromcrt0(poly *w, const poly *u) {
+  unsigned int i;
+  int32_t xq;
+
+  for (i = 0; i < HAETAE_N; i++) {
+    xq = u->coeffs[i];
+    w->coeffs[i] = xq + (HAETAE_Q & -(xq & 1));
+  }
+}
+
+void poly_ntt(poly *a) { ntt(&a->coeffs[0]); }
+
+void poly_invntt_tomont(poly *a) { invntt_tomont(&a->coeffs[0]); }
diff --git a/cbits/third_party/HAETAE/src/polyfix.c b/cbits/third_party/HAETAE/src/polyfix.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/polyfix.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: MIT
+
+#include "polyfix.h"
+#include "fips202.h"
+#include "fixpoint.h"
+#include "params.h"
+#include "sampler.h"
+
+#include <stdint.h>
+
+/*************************************************
+ * Name:        polyfix_add
+ *
+ * Description: Add double polynomial and integer polynomial.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyfix *c: pointer to output double polynomial
+ *              - const polyfix *a: pointer to first summand
+ *              - const poly *b: pointer to second summand
+ **************************************************/
+void polyfix_add(polyfix *c, const polyfix *a, const poly *b) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    c->coeffs[i] = a->coeffs[i] + HAETAE_LN * b->coeffs[i];
+}
+
+/*************************************************
+ * Name:        polyfixfix_sub
+ *
+ * Description: Subtract fixed polynomial and fixed polynomial.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyfix *c: pointer to output fixed polynomial
+ *              - const polyfix *a: pointer to first summand
+ *              - const polyfix *b: pointer to second summand
+ **************************************************/
+void polyfixfix_sub(polyfix *c, const polyfix *a, const polyfix *b) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    c->coeffs[i] = a->coeffs[i] - b->coeffs[i];
+}
+
+int32_t fix_round(int32_t num) {
+  return (num + HAETAE_LNHALF) >> HAETAE_LNBITS;
+}
+
+/*************************************************
+ * Name:        polyfix_round
+ *
+ * Description: rounds a fixed polynomial to integer polynomial
+ *
+ * Arguments:   - poly *a: output integer polynomial
+ *              - poly *b: input fixed polynomial
+ **************************************************/
+void polyfix_round(poly *a, const polyfix *b) {
+  unsigned i;
+
+  for (i = 0; i < HAETAE_N; ++i)
+    a->coeffs[i] = fix_round(b->coeffs[i]);
+}
+
+/*************************************************
+ * Name:        polyfixveck_add
+ *
+ * Description: Add vector to a vector of double polynomials of length HAETAE_K.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyveck *w: pointer to output vector
+ *              - const polyveck *u: pointer to first summand
+ *              - const polyveck *v: pointer to second summand
+ **************************************************/
+void polyfixveck_add(polyfixveck *w, const polyfixveck *u, const polyveck *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    polyfix_add(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyfixfixveck_sub
+ *
+ * Description: subtract vector to a vector of fixed polynomials of length k.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyveck *w: pointer to output vector
+ *              - const polyfixveck *u: pointer to first summand
+ *              - const polyfixveck *v: pointer to second summand
+ **************************************************/
+void polyfixfixveck_sub(polyfixveck *w, const polyfixveck *u,
+                        const polyfixveck *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    polyfixfix_sub(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyfixveck_double
+ *
+ * Description: Double vector of polynomials of length HAETAE_K.
+ *
+ * Arguments:   - polyveck *b: pointer to output vector
+ *              - polyveck *a: pointer to input vector
+ **************************************************/
+void polyfixveck_double(polyfixveck *b, const polyfixveck *a) {
+  unsigned int i, j;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_N; ++j)
+      b->vec[i].coeffs[j] = 2 * a->vec[i].coeffs[j];
+}
+
+/*************************************************
+ * Name:        polyfixveck_round
+ *
+ * Description: rounds a fixed polynomial vector of length HAETAE_K
+ *
+ * Arguments:   - polyveck *a: output integer polynomial vector
+ *              - polyfixveck *b: input fixed polynomial vector
+ **************************************************/
+void polyfixveck_round(polyveck *a, const polyfixveck *b) {
+  unsigned i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    polyfix_round(&a->vec[i], &b->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyfixvecl_add
+ *
+ * Description: Add vector to a vector of double polynomials of length L.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyvecl *w: pointer to output vector
+ *              - const polyfixvecl *u: pointer to first summand
+ *              - const polyvecl *v: pointer to second summand
+ **************************************************/
+void polyfixvecl_add(polyfixvecl *w, const polyfixvecl *u, const polyvecl *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_L; ++i)
+    polyfix_add(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyfixfixvecl_sub
+ *
+ * Description: subtract vector to a vector of fixed polynomials of length l.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyvecl *w: pointer to output vector
+ *              - const polyfixvecl *u: pointer to first summand
+ *              - const polyfixvecl *v: pointer to second summand
+ **************************************************/
+void polyfixfixvecl_sub(polyfixvecl *w, const polyfixvecl *u,
+                        const polyfixvecl *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_L; ++i)
+    polyfixfix_sub(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+/*************************************************
+ * Name:        polyfixvecl_double
+ *
+ * Description: Double vector of polynomials of length L.
+ *
+ * Arguments:   - polyveck *b: pointer to output vector
+ *              - polyveck *a: pointer to input vector
+ **************************************************/
+void polyfixvecl_double(polyfixvecl *b, const polyfixvecl *a) {
+  unsigned int i, j;
+
+  for (i = 0; i < HAETAE_L; ++i)
+    for (j = 0; j < HAETAE_N; ++j)
+      b->vec[i].coeffs[j] = 2 * a->vec[i].coeffs[j];
+}
+
+/*************************************************
+ * Name:        polyfixvecl_round
+ *
+ * Description: rounds a fixed polynomial vector of length L
+ *
+ * Arguments:   - polyvecl *a: output integer polynomial vector
+ *              - polyfixvecl *b: input fixed polynomial vector
+ **************************************************/
+void polyfixvecl_round(polyvecl *a, const polyfixvecl *b) {
+  unsigned i;
+
+  for (i = 0; i < HAETAE_L; ++i)
+    polyfix_round(&a->vec[i], &b->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyfixveclk_norm2
+ *
+ * Description: Calculates L2 norm of a fixed point polynomial vector with
+ *              length HAETAE_L + HAETAE_K The result is L2 norm * HAETAE_LN
+ *               similar to the way polynomial is usually stored
+ *
+ * Arguments:   - polyfixvecl *a: polynomial vector with length HAETAE_L to
+ *              calculate norm
+ *              - polyfixveck *b: polynomial vector with length HAETAE_K to
+ *              calculate norm
+ **************************************************/
+uint64_t polyfixveclk_sqnorm2(const polyfixvecl *a, const polyfixveck *b) {
+  unsigned int i, j;
+  uint64_t ret = 0;
+
+  for (i = 0; i < HAETAE_L; ++i) {
+    for (j = 0; j < HAETAE_N; ++j)
+      ret += (int64_t)a->vec[i].coeffs[j] * a->vec[i].coeffs[j];
+  }
+
+  for (i = 0; i < HAETAE_K; ++i) {
+    for (j = 0; j < HAETAE_N; ++j)
+      ret += (int64_t)b->vec[i].coeffs[j] * b->vec[i].coeffs[j];
+  }
+
+  return ret;
+}
+
+/*************************************************
+ * Name:        polyfixveclk_sample_hyperball
+ *
+ * Description: sample hyperball
+ *
+ * Arguments:   - polyfixvecl *y1: polynomial vector with length HAETAE_L to
+ *              calculate norm
+ *              - polyfixveck *y2: polynomial vector with length HAETAE_K to
+ *              calculate norm
+ *              - uint8_t *b: output byte
+ *              - uint8_t seed[HAETAE_CRHBYTES]: input seed bytes
+ *              - const uint16_t nonce: input nonce
+ *
+ * Specification: Implements @[Algorithm 13, SampleHyperball]
+ **************************************************/
+uint16_t polyfixveclk_sample_hyperball(polyfixvecl *y1, polyfixveck *y2,
+                                       uint8_t *b,
+                                       const uint8_t seed[HAETAE_CRHBYTES],
+                                       const uint16_t nonce) {
+  uint16_t ni = nonce;
+  uint64_t samples[HAETAE_N * (HAETAE_L + HAETAE_K)];
+  fp96_76 sqsum, invsqrt;
+  unsigned int i, j;
+  uint8_t signs[HAETAE_N * (HAETAE_L + HAETAE_K) / 8];
+
+  do {
+    sqsum.limb48[0] = 0;
+    sqsum.limb48[1] = 0;
+
+    sample_gauss_N(&samples[0], &signs[0], &sqsum, seed, ni++, HAETAE_N + 1);
+    sample_gauss_N(&samples[HAETAE_N], &signs[HAETAE_N / 8], &sqsum, seed, ni++,
+                   HAETAE_N + 1);
+
+    for (i = 2; i < HAETAE_L + HAETAE_K; i++)
+      sample_gauss_N(&samples[HAETAE_N * i], &signs[HAETAE_N / 8 * i], &sqsum,
+                     seed, ni++, HAETAE_N);
+
+    // divide sqsum by 2 and approximate inverse square root
+    sqsum.limb48[0] += 1; // rounding
+    sqsum.limb48[0] >>= 1;
+    sqsum.limb48[0] += (sqsum.limb48[1] & 1) << 47;
+    sqsum.limb48[1] >>= 1;
+    sqsum.limb48[1] += sqsum.limb48[0] >> 48;
+    sqsum.limb48[0] &= (UINT64_C(1) << 48) - 1;
+    fixpoint_newton_invsqrt(&invsqrt, &sqsum);
+    fixpoint_mul_high(&sqsum, &invsqrt,
+                      (uint64_t)(HAETAE_B0 * HAETAE_LN + HAETAE_SQNM / 2)
+                          << (28 - 13));
+
+    for (i = 0; i < HAETAE_L; i++) {
+      for (j = 0; j < HAETAE_N; j++)
+        y1->vec[i].coeffs[j] = fixpoint_mul_rnd13(
+            samples[(i * HAETAE_N + j)], &sqsum,
+            (signs[(i * HAETAE_N + j) / 8] >> ((i * HAETAE_N + j) % 8)) & 1);
+    }
+    for (i = HAETAE_L; i < HAETAE_K + HAETAE_L; i++) {
+      for (j = 0; j < HAETAE_N; j++)
+        y2->vec[i - HAETAE_L].coeffs[j] = fixpoint_mul_rnd13(
+            samples[(i * HAETAE_N + j)], &sqsum,
+            (signs[(i * HAETAE_N + j) / 8] >> ((i * HAETAE_N + j) % 8)) & 1);
+    }
+  } while (polyfixveclk_sqnorm2(y1, y2) > HAETAE_B0SQ * HAETAE_LN * HAETAE_LN);
+
+  {
+    uint8_t tmp[HAETAE_CRHBYTES + 2];
+    for (i = 0; i < HAETAE_CRHBYTES; i++) {
+      tmp[i] = seed[i];
+    }
+    tmp[HAETAE_CRHBYTES + 0] = ni >> 0;
+    tmp[HAETAE_CRHBYTES + 1] = ni >> 8;
+    shake256(b, 1, tmp, HAETAE_CRHBYTES + 2);
+  }
+
+  return ni;
+}
diff --git a/cbits/third_party/HAETAE/src/polymat.c b/cbits/third_party/HAETAE/src/polymat.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/polymat.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: MIT
+
+#include "polymat.h"
+#include "params.h"
+#include "poly.h"
+#include "polyvec.h"
+
+#include <stdint.h>
+
+/*************************************************
+ * Name:        polymatkl_expand_matA
+ *
+ * Description: Implementation of ExpandA. Generates matrix A with uniformly
+ *              random coefficients a_{i,j} by performing rejection
+ *              sampling on the output stream of SHAKE128(rho|j|i)
+ *              or AES256CTR(rho,j|i).
+ *
+ * Arguments:   - polyvecm mat[HAETAE_K]: output matrix k \times m
+ *              - const uint8_t rho[]: byte array containing seed rho
+ *
+ * Specification: Implements @[Algorithm 6, ExpandMatA]
+ **************************************************/
+void polymatkl_expand_matA(polyvecl mat[HAETAE_K],
+                           const uint8_t rho[HAETAE_SEEDBYTES]) {
+  unsigned int i, j;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_M; ++j)
+      poly_uniform(&mat[i].vec[j + 1], rho, (i << 8) + j);
+}
+
+/*************************************************
+ * Name:        polymatkm_expand_matA
+ *
+ * Description: Implementation of ExpandA. Generates matrix A with uniformly
+ *              random coefficients a_{i,j} by performing rejection
+ *              sampling on the output stream of SHAKE128(rho|j|i)
+ *              or AES256CTR(rho,j|i).
+ *
+ * Arguments:   - polyvecm mat[HAETAE_K]: output matrix k \times m
+ *              - const uint8_t rho[]: byte array containing seed rho
+ *
+ * Specification: Implements @[Algorithm 6, ExpandMatA]
+ **************************************************/
+void polymatkm_expand_matA(polyvecm mat[HAETAE_K],
+                           const uint8_t rho[HAETAE_SEEDBYTES]) {
+  unsigned int i, j;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_M; ++j)
+      poly_uniform(&mat[i].vec[j], rho, (i << 8) + j);
+}
+
+// doubles k * m sub-matrix of k * l mat
+void polymatkl_double(polyvecl mat[HAETAE_K]) {
+  unsigned int i, j, k;
+  for (i = 0; i < HAETAE_K; ++i) {
+    for (j = 1; j < HAETAE_L; ++j) {
+      for (k = 0; k < HAETAE_N; ++k) {
+        mat[i].vec[j].coeffs[k] *= 2;
+      }
+    }
+  }
+}
+
+void polymatkl_pointwise_montgomery(polyveck *t, const polyvecl mat[HAETAE_K],
+                                    const polyvecl *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i) {
+    polyvecl_pointwise_acc_montgomery(&t->vec[i], &mat[i], v);
+  }
+}
+
+void polymatkm_pointwise_montgomery(polyveck *t, const polyvecm mat[HAETAE_K],
+                                    const polyvecm *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i) {
+    polyvecm_pointwise_acc_montgomery(&t->vec[i], &mat[i], v);
+  }
+}
diff --git a/cbits/third_party/HAETAE/src/polyvec.c b/cbits/third_party/HAETAE/src/polyvec.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/polyvec.c
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: MIT
+
+#include "polyvec.h"
+#include "decompose.h"
+#include "fft.h"
+#include "params.h"
+#include "poly.h"
+#include "reduce.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+/*************************************************
+ * Name:        polyveck_add
+ *
+ * Description: Add vectors of polynomials of length HAETAE_K.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyveck *w: pointer to output vector
+ *              - const polyveck *u: pointer to first summand
+ *              - const polyveck *v: pointer to second summand
+ **************************************************/
+void polyveck_add(polyveck *w, const polyveck *u, const polyveck *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    poly_add(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyveck_sub
+ *
+ * Description: Subtract vectors of polynomials of length HAETAE_K.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyveck *w: pointer to output vector
+ *              - const polyveck *u: pointer to first input vector
+ *              - const polyveck *v: pointer to second input vector to be
+ *              subtracted from first input vector
+ **************************************************/
+void polyveck_sub(polyveck *w, const polyveck *u, const polyveck *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    poly_sub(&w->vec[i], &u->vec[i], &v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyveck_double
+ *
+ * Description: Double vector of polynomials of length HAETAE_K.
+ *              No modular reduction is performed.
+ *
+ * Arguments:   - polyveck *w: pointer to output vector
+ **************************************************/
+void polyveck_double(polyveck *b) {
+  unsigned int i, j;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_N; ++j)
+      b->vec[i].coeffs[j] *= 2;
+}
+
+/*************************************************
+ * Name:        polyveck_reduce2q
+ *
+ * Description: Reduce coefficients to 2q
+ *
+ * Arguments:   - polyveck *v: pointer to input/output vector
+ **************************************************/
+void polyveck_reduce2q(polyveck *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    poly_reduce2q(&v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyveck_freeze
+ *
+ * Description: For all coefficients of polynomials in vector of length HAETAE_K
+ *              compute standard representative r = a mod^+ HAETAE_Q.
+ *
+ * Arguments:   - polyveck *v: pointer to input/output vector
+ **************************************************/
+void polyveck_freeze(polyveck *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    poly_freeze(&v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyveck_freeze2q
+ *
+ * Description: For all coefficients of polynomials in vector of length HAETAE_K
+ *              compute standard representative r = a mod^+ 2Q.
+ *
+ * Arguments:   - polyveck *v: pointer to input/output vector
+ **************************************************/
+void polyveck_freeze2q(polyveck *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    poly_freeze2q(&v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyveck_expand_vecA
+ *
+ * Description: Sample a vector of polynomials with uniformly random
+ *              coefficients in Zq by rejection sampling on the
+ *              output stream from SHAKE128(seed|nonce)
+ *
+ * Arguments:   - polyveck *v: pointer to output a vector of polynomials of
+ *              length HAETAE_K
+ *              - const uint8_t seed[]: byte array with seed of length
+ *              HAETAE_SEEDBYTES
+ *
+ * Specification: Implements @[Algorithm 7, ExpandVeca]
+ **************************************************/
+void polyveck_expand_vecA(polyveck *v, const uint8_t seed[HAETAE_SEEDBYTES]) {
+  unsigned int i, nonce = (HAETAE_K << 8) + HAETAE_M;
+  for (i = 0; i < HAETAE_K; ++i)
+    poly_uniform(&v->vec[i], seed, nonce++);
+}
+
+/*************************************************
+ * Name:        polyvecmk_expand_S
+ *
+ * Description: Sample a vector of polynomials with uniformly random
+ *              coefficients in [-ETA,ETA] by rejection sampling on the
+ *              output stream from SHAKE256(seed|nonce)
+ *
+ * Arguments:   - polyveck *v: pointer to output a vector of polynomials of
+ *              length HAETAE_K
+ *              - const uint8_t seed[]: byte array with seed of length
+ *              HAETAE_CRHBYTES
+ *              - uint16_t nonce: 2-byte nonce
+ *
+ * Specification: Implements @[Algorithm 10, ExpandS]
+ **************************************************/
+void polyvecmk_expand_S(polyvecm *u, polyveck *v,
+                        const uint8_t seed[HAETAE_CRHBYTES], uint16_t nonce) {
+  unsigned int i, n = nonce;
+  for (i = 0; i < HAETAE_M; i++)
+    poly_uniform_eta(&u->vec[i], seed, n++);
+  for (i = 0; i < HAETAE_K; ++i)
+    poly_uniform_eta(&v->vec[i], seed, n++);
+}
+
+/*************************************************
+ * Name:        polyveck_double_negate
+ *
+ * Description: multiply each coefficient with -2
+ *
+ * Arguments:   - polyveck *v: pointer to output vector of polynomials of
+ *                              length HAETAE_K
+ **************************************************/
+void polyveck_double_negate(polyveck *v) {
+  unsigned int i, j;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_N; j++)
+      v->vec[i].coeffs[j] =
+          montgomery_reduce((int64_t)v->vec[i].coeffs[j] * MONT * -2);
+}
+
+/*************************************************
+ * Name:        polyveck_frommont
+ *
+ * Description: multiply each coefficient with MONT
+ *
+ * Arguments:   - polyveck *v: pointer to output vector of polynomials of
+ *              length HAETAE_K
+ **************************************************/
+void polyveck_frommont(polyveck *v) {
+  unsigned int i, j;
+
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_N; j++)
+      v->vec[i].coeffs[j] =
+          montgomery_reduce((int64_t)v->vec[i].coeffs[j] * MONTSQ);
+}
+
+void polyveck_poly_pointwise_montgomery(polyveck *w, const polyveck *u,
+                                        const poly *v) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_K; i++) {
+    poly_pointwise_montgomery(&w->vec[i], &u->vec[i], v);
+  }
+}
+
+/*************************************************
+ * Name:        polyveck_poly_fromcrt
+ *
+ * Description: recover polynomials from CRT domain, where all "mod q"
+ *              polynomials are known and only the uppermost "mod 2" polynomial
+ *              is non-zero
+ *
+ * Arguments:   - polyveck *w: pointer to output vector of polynomials of
+ *              length HAETAE_K
+ *              - const polyveck *u: pointer to the input vector of polynomials
+ *              of length HAETAE_K
+ *              - const poly *v: pointer to the input polynomial ("mod 2")
+ *
+ * Specification: Implements @[Algorithm 3, iCRT]
+ **************************************************/
+void polyveck_poly_fromcrt(polyveck *w, const polyveck *u, const poly *v) {
+  unsigned int i;
+
+  poly_fromcrt(&w->vec[0], &u->vec[0], v);
+
+  for (i = 1; i < HAETAE_K; i++) {
+    poly_fromcrt0(&w->vec[i], &u->vec[i]);
+  }
+}
+
+void polyveck_highbits_hint(polyveck *w, const polyveck *v) {
+  unsigned int i, j;
+  for (i = 0; i < HAETAE_K; i++) {
+    for (j = 0; j < HAETAE_N; j++) {
+      decompose_hint(&w->vec[i].coeffs[j], v->vec[i].coeffs[j]);
+    }
+  }
+}
+
+void polyveck_cneg(polyveck *v, const uint8_t b) {
+  unsigned int i, j;
+  for (i = 0; i < HAETAE_K; i++) {
+    for (j = 0; j < HAETAE_N; j++) {
+      v->vec[i].coeffs[j] *= 1 - 2 * b;
+    }
+  }
+}
+
+void polyveck_caddDQ2ALPHA(polyveck *h) {
+  unsigned int i, j;
+  for (i = 0; i < HAETAE_K; i++) {
+    for (j = 0; j < HAETAE_N; j++) {
+      h->vec[i].coeffs[j] +=
+          (h->vec[i].coeffs[j] >> 31) & ((HAETAE_DQ - 2) / HAETAE_ALPHA_HINT);
+    }
+  }
+}
+
+void polyveck_csubDQ2ALPHA(polyveck *v) {
+  unsigned int i, j;
+  for (i = 0; i < HAETAE_K; i++) {
+    for (j = 0; j < HAETAE_N; j++) {
+      v->vec[i].coeffs[j] -=
+          ~((v->vec[i].coeffs[j] - (HAETAE_DQ - 2) / HAETAE_ALPHA_HINT) >> 31) &
+          ((HAETAE_DQ - 2) / HAETAE_ALPHA_HINT);
+    }
+  }
+}
+
+void polyveck_mul_alpha(polyveck *v, const polyveck *u) {
+  unsigned int i, j;
+  for (i = 0; i < HAETAE_K; i++) {
+    for (j = 0; j < HAETAE_N; j++) {
+      v->vec[i].coeffs[j] = u->vec[i].coeffs[j] * HAETAE_ALPHA_HINT;
+    }
+  }
+}
+
+void polyveck_div2(polyveck *v) {
+  unsigned i, j;
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_N; ++j)
+      v->vec[i].coeffs[j] >>= 1;
+}
+
+void polyveck_caddq(polyveck *v) {
+  unsigned i, j;
+  for (i = 0; i < HAETAE_K; ++i)
+    for (j = 0; j < HAETAE_N; ++j)
+      v->vec[i].coeffs[j] = caddq(v->vec[i].coeffs[j]);
+}
+
+void polyveck_decompose_vk(polyveck *v0, polyveck *v) {
+  for (int i = 0; i < HAETAE_K; i++) {
+    for (int j = 0; j < HAETAE_N; j++) {
+      v->vec[i].coeffs[j] =
+          decompose_vk(&v0->vec[i].coeffs[j], v->vec[i].coeffs[j]);
+    }
+  }
+}
+
+void polyveck_ntt(polyveck *x) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_K; i++) {
+    poly_ntt(&x->vec[i]);
+  }
+}
+
+void polyveck_invntt_tomont(polyveck *x) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_K; i++) {
+    poly_invntt_tomont(&x->vec[i]);
+  }
+}
+
+/*************************************************
+ * Name:        polyveck_sqnorm2
+ *
+ * Description: Calculates L2 norm of a polynomial vector with length k
+ *
+ * Arguments:   - polyveck *b: polynomial vector with length k to calculate
+ *              norm
+ **************************************************/
+uint64_t polyveck_sqnorm2(const polyveck *b) {
+  unsigned int i, j;
+  uint64_t ret = 0;
+
+  for (i = 0; i < HAETAE_K; ++i) {
+    for (j = 0; j < HAETAE_N; ++j) {
+      ret += (uint64_t)b->vec[i].coeffs[j] * b->vec[i].coeffs[j];
+    }
+  }
+  return ret;
+}
+
+/*************************************************
+ * Name:        polyvecl_highbits
+ *
+ * Description: Compute HighBits of a vector of polynomials
+ *
+ * Arguments:   - polyvecl *v2: pointer to output vector of polynomials of
+ *              length L
+ *              - const polyvecl *v: pointer to input vector of polynomials of
+ *              length L
+ **************************************************/
+void polyvecl_highbits(polyvecl *v2, const polyvecl *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_L; ++i)
+    poly_highbits(&v2->vec[i], &v->vec[i]);
+}
+
+/*************************************************
+ * Name:        polyvecl_lowbits
+ *
+ * Description: Compute LowBits of a vector of polynomials
+ *
+ * Arguments:   - polyvecl *v1: pointer to output vector of polynomials of
+ *              length L
+ *              - const polyvecl *v: pointer to input vector of polynomials of
+ *              length L
+ **************************************************/
+void polyvecl_lowbits(polyvecl *v1, const polyvecl *v) {
+  unsigned int i;
+
+  for (i = 0; i < HAETAE_L; ++i)
+    poly_lowbits(&v1->vec[i], &v->vec[i]);
+}
+
+void polyvecl_cneg(polyvecl *v, const uint8_t b) {
+  unsigned int i, j;
+  for (i = 0; i < HAETAE_L; i++) {
+    for (j = 0; j < HAETAE_N; j++) {
+      v->vec[i].coeffs[j] *= 1 - 2 * b;
+    }
+  }
+}
+
+/*************************************************
+ * Name:        polyvecl_sqnorm2
+ *
+ * Description: Calculates L2 norm of a polynomial vector with length l
+ *
+ * Arguments:   - polyvecl *a: polynomial vector with length l to calculate
+ *              norm
+ **************************************************/
+uint64_t polyvecl_sqnorm2(const polyvecl *a) {
+  unsigned int i, j;
+  uint64_t ret = 0;
+
+  for (i = 0; i < HAETAE_L; ++i) {
+    for (j = 0; j < HAETAE_N; ++j) {
+      ret += (uint64_t)a->vec[i].coeffs[j] * a->vec[i].coeffs[j];
+    }
+  }
+
+  return ret;
+}
+
+/*************************************************
+ * Name:        polyvecl_pointwise_acc_montgomery
+ *
+ * Description: Pointwise multiply vectors of polynomials of length L, multiply
+ *              resulting vector by 2^{-32} and add (accumulate) polynomials
+ *              in it. Input/output vectors are in NTT domain representation.
+ *
+ * Arguments:   - poly *w: output polynomial
+ *              - const polyvecl *u: pointer to first input vector
+ *              - const polyvecl *v: pointer to second input vector
+ **************************************************/
+void polyvecl_pointwise_acc_montgomery(poly *w, const polyvecl *u,
+                                       const polyvecl *v) {
+  unsigned int i;
+  poly t;
+
+  poly_pointwise_montgomery(w, &u->vec[0], &v->vec[0]);
+  for (i = 1; i < HAETAE_L; ++i) {
+    poly_pointwise_montgomery(&t, &u->vec[i], &v->vec[i]);
+    poly_add(w, w, &t);
+  }
+}
+
+void polyvecl_ntt(polyvecl *x) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_L; i++) {
+    poly_ntt(&x->vec[i]);
+  }
+}
+
+/*************************************************
+ * Name:        polyvecm_pointwise_acc_montgomery
+ *
+ * Description: Pointwise multiply vectors of polynomials of length L, multiply
+ *              resulting vector by 2^{-32} and add (accumulate) polynomials
+ *              in it. Input/output vectors are in NTT domain representation.
+ *
+ * Arguments:   - poly *w: output polynomial
+ *              - const polyvecm *u: pointer to first input vector
+ *              - const polyvecm *v: pointer to second input vector
+ **************************************************/
+void polyvecm_pointwise_acc_montgomery(poly *w, const polyvecm *u,
+                                       const polyvecm *v) {
+  unsigned int i;
+  poly t;
+
+  poly_pointwise_montgomery(w, &u->vec[0], &v->vec[0]);
+  for (i = 1; i < HAETAE_M; ++i) {
+    poly_pointwise_montgomery(&t, &u->vec[i], &v->vec[i]);
+    poly_add(w, w, &t);
+  }
+}
+
+void polyvecm_ntt(polyvecm *x) {
+  unsigned int i;
+  for (i = 0; i < HAETAE_M; i++) {
+    poly_ntt(&x->vec[i]);
+  }
+}
+
+/*************************************************
+ * Name:        minmax
+ *
+ * Description: compare and swap two integers
+ *
+ * Arguments:   - int32_t *x: pointer to first input/output integer
+ *              - int32_t *y: pointer to second input/output integer
+ *
+ * Specification: Implements @[Algorithm 50, minmax]
+ * **************************************************/
+static inline void minmax(int32_t *x, int32_t *y) // taken from djbsort
+{
+  int32_t a = *x;
+  int32_t b = *y;
+  int32_t ab = b ^ a;
+  int32_t c = b - a;
+  c ^= ab & (c ^ b);
+  c >>= 31;
+  c &= ab;
+  *x = a ^ c;
+  *y = b ^ c;
+}
+/*************************************************
+ * Name:        polyvecmk_sk_singular_value
+ *
+ * Description: calculate singular value of signing key
+ *
+ * Arguments:   - const polyvecm *s1: pointer to first input vector
+ *              - const polyveck *s2: pointer to second input vector
+ *
+ * Returns:
+ *
+ * Specification: Implements @[Algorithm 48, skSingularValue]
+ **************************************************/
+int64_t polyvecmk_sk_singular_value(const polyvecm *s1, const polyveck *s2) {
+  int32_t res = 0;
+  complex_fp32_16 input[FFT_N] = {0};
+  int32_t sum[HAETAE_N] = {0}, bestm[HAETAE_N / HAETAE_TAU + 1] = {0}, min = 0;
+
+  for (size_t i = 0; i < HAETAE_M; ++i) {
+    fft_init_and_bitrev(input, &s1->vec[i]);
+    fft(input);
+    // cumulative sum
+    for (size_t j = 0; j < HAETAE_N; j++) {
+      sum[j] += complex_fp_sqabs(input[j]);
+    }
+  }
+
+  for (size_t i = 0; i < HAETAE_K; ++i) {
+    fft_init_and_bitrev(input, &s2->vec[i]);
+    fft(input);
+
+    // cumulative sum
+    for (size_t j = 0; j < HAETAE_N; j++) {
+      sum[j] += complex_fp_sqabs(input[j]);
+    }
+  }
+
+  // compute max m
+  for (size_t i = 0; i < HAETAE_N / HAETAE_TAU + 1; ++i) {
+    bestm[i] = sum[i];
+  }
+  for (size_t i = HAETAE_N / HAETAE_TAU + 1; i < HAETAE_N; i++) {
+    for (size_t j = 0; j < HAETAE_N / HAETAE_TAU + 1; j++) {
+      minmax(&sum[i], &bestm[j]);
+    }
+  }
+  // find minimum in bestm
+  min = bestm[0];
+  for (size_t i = 1; i < HAETAE_N / HAETAE_TAU + 1; i++) {
+    int32_t tmp = bestm[i];
+    minmax(&min, &tmp);
+  }
+  // multiply all but the minimum by HAETAE_N mod HAETAE_TAU
+  for (size_t i = 0; i < HAETAE_N / HAETAE_TAU + 1; i++) {
+    int32_t fac =
+        ((min - bestm[i]) >>
+         31); // all-ones if bestm[i] != min (TODO: impl specific behaviour)
+    fac = (fac & (HAETAE_TAU)) ^
+          ((~fac) & (HAETAE_N % HAETAE_TAU)); // fac = HAETAE_TAU for all != min
+                                              // and N%HAETAE_TAU for min
+    bestm[i] += 0x10200; // add 1 for the "1 poly" in S, and prepare rounding
+    bestm[i] >>= 10;     // round off 10 bits
+    bestm[i] *= fac;
+    res += bestm[i];
+  }
+
+  return (res + (1 << 5)) >> 6; // return rounded, squared value
+}
diff --git a/cbits/third_party/HAETAE/src/reduce.c b/cbits/third_party/HAETAE/src/reduce.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/reduce.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: MIT
+
+#include "reduce.h"
+#include "params.h"
+
+#include <stdint.h>
+
+/*************************************************
+ * Name:        montgomery_reduce
+ *
+ * Description: For finite field element a with -2^{31}Q <= a <= HAETAE_Q*2^31,
+ *              compute r \equiv a*2^{-32} (mod HAETAE_Q) such that -HAETAE_Q <
+ *              r < HAETAE_Q.
+ *
+ * Arguments:   - int64_t: finite field element a
+ *
+ * Returns r.
+ *
+ * Specification: Implements @[Algorithm 1, MontgomeryReduce]
+ **************************************************/
+int32_t montgomery_reduce(int64_t a) {
+  int32_t t;
+
+  t = (int64_t)(int32_t)a * QINV;
+  t = (a - (int64_t)t * HAETAE_Q) >> 32;
+  return t;
+}
+
+/*************************************************
+ * Name:        caddq
+ *
+ * Description: Add HAETAE_Q if input coefficient is negative.
+ *
+ * Arguments:   - int32_t: finite field element a
+ *
+ * Returns r.
+ **************************************************/
+int32_t caddq(int32_t a) {
+  a += (a >> 31) & HAETAE_Q;
+  return a;
+}
+
+/*************************************************
+ * Name:        freeze
+ *
+ * Description: For finite field element a, compute standard
+ *              representative r = a mod^+ HAETAE_Q.
+ *
+ * Arguments:   - int32_t: finite field element a
+ *
+ * Returns r.
+ **************************************************/
+int32_t freeze(int32_t a) {
+  int64_t t = (int64_t)a * QREC;
+  t = t >> 32;
+  t = a - t * HAETAE_Q;                    // -2Q <  t < 2Q
+  t += (t >> 31) & HAETAE_DQ;              //   0 <= t < 2Q
+  t -= ~((t - HAETAE_Q) >> 31) & HAETAE_Q; //   0 <= t < HAETAE_Q
+  return t;
+}
+
+/*************************************************
+ * Name:        reduce32_2q
+ *
+ * Description: compute reduction with 2Q
+ *
+ * Arguments:   - int32_t: finite field element a
+ *
+ * Returns r.
+ **************************************************/
+int32_t reduce32_2q(int32_t a) {
+  int64_t t = (int64_t)a * DQREC;
+  t >>= 32;
+  t = a - t * HAETAE_DQ;                     // -4Q <  t < 4Q
+  t += (t >> 31) & (HAETAE_DQ * 2);          //   0 <= t < 4Q
+  t -= ~((t - HAETAE_DQ) >> 31) & HAETAE_DQ; //   0 <= t < HAETAE_Q
+  t -= ~((t - HAETAE_Q) >> 31) & HAETAE_DQ;  // centered representation
+  return (int32_t)t;
+}
+
+/*************************************************
+ * Name:        freeze2q
+ *
+ * Description: For finite field element a, compute standard
+ *              representative r = a mod^+ 2Q.
+ *
+ * Arguments:   - int32_t: finite field element a
+ *
+ * Returns r.
+ **************************************************/
+int32_t freeze2q(int32_t a) {
+  int64_t t = (int64_t)a * DQREC;
+  t >>= 32;
+  t = a - t * HAETAE_DQ;                     // -4Q <  t < 4Q
+  t += (t >> 31) & (HAETAE_DQ * 2);          //   0 <= t < 4Q
+  t -= ~((t - HAETAE_DQ) >> 31) & HAETAE_DQ; //   0 <= t < HAETAE_Q
+  return (int32_t)t;
+}
diff --git a/cbits/third_party/HAETAE/src/sampler.c b/cbits/third_party/HAETAE/src/sampler.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/sampler.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: MIT
+
+#include "sampler.h"
+#include "fixpoint.h"
+#include "symmetric.h"
+
+#include <stdint.h>
+
+/*************************************************
+ * Name:        rej_uniform
+ *
+ * Description: Sample uniformly random coefficients in [0, HAETAE_Q-1] by
+ *              performing rejection sampling on array of random bytes.
+ *
+ * Arguments:   - int32_t *a: pointer to output array (allocated)
+ *              - unsigned int len: number of coefficients to be sampled
+ *              - const uint8_t *buf: array of random bytes
+ *              - unsigned int buflen: length of array of random bytes
+ *
+ * Returns number of sampled coefficients. Can be smaller than len if not enough
+ * random bytes were given.
+ *
+ * Specification: Implements @[Algorithm 9, RejUniform]
+ **************************************************/
+unsigned int rej_uniform(int32_t *a, unsigned int len, const uint8_t *buf,
+                         unsigned int buflen) {
+  unsigned int ctr, pos;
+  uint32_t t;
+
+  ctr = pos = 0;
+  while (ctr < len && pos + 2 <= buflen) {
+    t = buf[pos++];
+    t |= (uint32_t)buf[pos++] << 8;
+
+    if (t < HAETAE_Q)
+      a[ctr++] = t;
+  }
+  return ctr;
+}
+
+/*************************************************
+ * Name:        rej_eta
+ *
+ * Description: Sample uniformly random coefficients in [-HAETAE_ETA,
+ *              HAETAE_ETA] by performing rejection sampling on array
+ *              of random bytes.
+ *
+ * Arguments:   - int32_t *a: pointer to output array (allocated)
+ *              - unsigned int len: number of coefficients to be sampled
+ *              - const uint8_t *buf: array of random bytes
+ *              - unsigned int buflen: length of array of random bytes
+ *
+ * Returns number of sampled coefficients. Can be smaller than len if not enough
+ * random bytes were given.
+ *
+ * Specification: Implements @[Algorithm 12, RejEta]
+ **************************************************/
+static int32_t mod3(uint8_t t) {
+  int32_t r;
+  r = (t >> 4) + (t & 0xf);
+  r = (r >> 2) + (r & 3);
+  r = (r >> 2) + (r & 3);
+  r = (r >> 2) + (r & 3);
+  return r - (3 * (r >> 1));
+}
+static int32_t mod3_leq26(uint8_t t) {
+  int32_t r;
+  r = (t >> 4) + (t & 0xf);
+  r = (r >> 2) + (r & 3);
+  r = (r >> 2) + (r & 3);
+  return r - (3 * (r >> 1));
+}
+static int32_t mod3_leq8(uint8_t t) {
+  int32_t r;
+  r = (t >> 2) + (t & 3);
+  r = (r >> 2) + (r & 3);
+  return r - (3 * (r >> 1));
+}
+unsigned int rej_eta(int32_t *a, unsigned int len, const uint8_t *buf,
+                     unsigned int buflen) {
+  unsigned int ctr, pos;
+
+  ctr = pos = 0;
+  while (ctr < len && pos < buflen) {
+    uint32_t t = buf[pos++];
+    if (t < 243) {
+      // reduce mod 3
+      a[ctr++] = mod3(t);
+
+      if (ctr >= len)
+        break;
+
+      t *= 171; // 171*3 = 1 mod 256
+      t >>= 9;
+      a[ctr++] = mod3(t);
+
+      if (ctr >= len)
+        break;
+
+      t *= 171;
+      t >>= 9;
+      a[ctr++] = mod3_leq26(t);
+
+      if (ctr >= len)
+        break;
+
+      t *= 171;
+      t >>= 9;
+      a[ctr++] = mod3_leq8(t);
+
+      if (ctr >= len)
+        break;
+
+      t *= 171;
+      t >>= 9;
+      a[ctr++] = (int32_t)t - (int32_t)3 * (t >> 1);
+    }
+  }
+  return ctr;
+}
+
+/*
+ * GALACTICS [Rossi23]: degree-10 polynomial P(z) for exp(-z), z = xi/2^48.
+ *
+ * The constant term c_0 is lifted by +1 unit (2^{-48}) to enforce the ceiling
+ * condition P_c(z) ≥ exp(-z) everywhere.
+ *
+ * GALACTICS rational coefficients  c_k = integer * 2^{exponent}:
+ *   c_0  =                1 * 2^0     (= 1 exactly)
+ *   c_1  = -140737488355325 * 2^{-47}
+ *   c_2  =  140737488354861 * 2^{-48}
+ *   c_3  =   -5864062013275 * 2^{-45}
+ *   c_4  =   93824990983603 * 2^{-51}
+ *   c_5  =  -75059957176683 * 2^{-53}
+ *   c_6  =   50039335827767 * 2^{-55}
+ *   c_7  = -114346678051053 * 2^{-59}
+ *   c_8  =   56962476261175 * 2^{-61}
+ *   c_9  =  -24365119391573 * 2^{-63}
+ *   c_10 =   14645656383309 * 2^{-66}
+ *
+ * Integer coefficients c_k^int = ceil(c_k * 2^48):
+ *   c_0^int  = 281474976710657  [GALACTICS-C: 2^48+1; lifted by +1]
+ *   c_1^int  =-281474976710650  [exact]
+ *   c_2^int  = 140737488354861  [exact]
+ *   c_3^int  = -46912496106200  [exact]
+ *   c_4^int  =  11728123872951  [ceil(11728123872950.375)]
+ *   c_5^int  =  -2345623661771  [ceil(-2345623661771.344)]
+ *   c_6^int  =    390932311155  [ceil(390932311154.430)]
+ *   c_7^int  =    -55833338892  [ceil(-55833338892.116)]
+ *   c_8^int  =      6953427278  [ceil(6953427277.975)]
+ *   c_9^int  =      -743564434  [ceil(-743564434.557)]
+ *   c_10^int =        55868746  [ceil(55868745.359)]
+ */
+static uint64_t approx_exp(const uint64_t xi) {
+  int64_t r = INT64_C(55868746);                 /* c_10^int */
+  r = smulh48(r, xi) - INT64_C(743564434);       /* c_9^int  */
+  r = smulh48(r, xi) + INT64_C(6953427278);      /* c_8^int  */
+  r = smulh48(r, xi) - INT64_C(55833338892);     /* c_7^int  */
+  r = smulh48(r, xi) + INT64_C(390932311155);    /* c_6^int  */
+  r = smulh48(r, xi) - INT64_C(2345623661771);   /* c_5^int  */
+  r = smulh48(r, xi) + INT64_C(11728123872951);  /* c_4^int  */
+  r = smulh48(r, xi) - INT64_C(46912496106200);  /* c_3^int  */
+  r = smulh48(r, xi) + INT64_C(140737488354861); /* c_2^int  */
+  r = smulh48(r, xi) - INT64_C(281474976710650); /* c_1^int  */
+  r = smulh48(r, xi) + INT64_C(281474976710657); /* c_0^int  */
+  return (uint64_t)r;
+}
+
+#define CDTLEN 166
+#define CDTHILEN 76
+
+/*
+ * 83-bit CDT for D_{Z,16} half-Gaussian: rho(k) = exp(-k^2/512), k >= 0
+ * CDT83[k] = floor(CDF(k) * 2^83) where CDF(k) = sum_{j=0}^{k} rho(j)/Z_half
+ * Z_half = sum_{k=0}^{inf} exp(-k^2/512) = 20.5530...
+ * Split: CDT83_HI[k] = CDT83[k] >> 64 (upper 19 bits)
+ *        CDT83_LO[k] = CDT83[k] & 0xFFFFFFFFFFFFFFFF (lower 64 bits)
+ */
+
+static const uint32_t CDT83_HI[CDTHILEN] = {
+    UINT32_C(0x063a5), UINT32_C(0x0c718), UINT32_C(0x129f6), UINT32_C(0x18bdf),
+    UINT32_C(0x1ec73), UINT32_C(0x24b59), UINT32_C(0x2a83a), UINT32_C(0x302c6),
+    UINT32_C(0x35ab6), UINT32_C(0x3afc7), UINT32_C(0x401be), UINT32_C(0x4506a),
+    UINT32_C(0x49ba1), UINT32_C(0x4e343), UINT32_C(0x52736), UINT32_C(0x5676c),
+    UINT32_C(0x5a3dc), UINT32_C(0x5dc86), UINT32_C(0x61172), UINT32_C(0x642ad),
+    UINT32_C(0x6704c), UINT32_C(0x69a68), UINT32_C(0x6c120), UINT32_C(0x6e496),
+    UINT32_C(0x704ef), UINT32_C(0x72255), UINT32_C(0x73cf1), UINT32_C(0x754f0),
+    UINT32_C(0x76a7c), UINT32_C(0x77dc4), UINT32_C(0x78ef2), UINT32_C(0x79e32),
+    UINT32_C(0x7abaf), UINT32_C(0x7b78f), UINT32_C(0x7c1fb), UINT32_C(0x7cb17),
+    UINT32_C(0x7d304), UINT32_C(0x7d9e4), UINT32_C(0x7dfd4), UINT32_C(0x7e4f0),
+    UINT32_C(0x7e950), UINT32_C(0x7ed0d), UINT32_C(0x7f03b), UINT32_C(0x7f2ec),
+    UINT32_C(0x7f531), UINT32_C(0x7f71a), UINT32_C(0x7f8b3), UINT32_C(0x7fa08),
+    UINT32_C(0x7fb24), UINT32_C(0x7fc0e), UINT32_C(0x7fccf), UINT32_C(0x7fd6e),
+    UINT32_C(0x7fdf0), UINT32_C(0x7fe5a), UINT32_C(0x7feaf), UINT32_C(0x7fef5),
+    UINT32_C(0x7ff2c), UINT32_C(0x7ff59), UINT32_C(0x7ff7d), UINT32_C(0x7ff99),
+    UINT32_C(0x7ffb0), UINT32_C(0x7ffc2), UINT32_C(0x7ffd0), UINT32_C(0x7ffdb),
+    UINT32_C(0x7ffe3), UINT32_C(0x7ffea), UINT32_C(0x7ffef), UINT32_C(0x7fff3),
+    UINT32_C(0x7fff6), UINT32_C(0x7fff8), UINT32_C(0x7fffa), UINT32_C(0x7fffb),
+    UINT32_C(0x7fffd), UINT32_C(0x7fffd), UINT32_C(0x7fffe), UINT32_C(0x7fffe),
+};
+
+static const uint64_t CDT83_LO[CDTLEN] = {
+    UINT64_C(0x0aa572bc88db1e28), UINT64_C(0x4f3820e69064b2ee),
+    UINT64_C(0xd68dc44dd1418704), UINT64_C(0x6587a04d9b97b1dd),
+    UINT64_C(0x9b843ce0a65d0050), UINT64_C(0x02b62cb869003e7d),
+    UINT64_C(0x0d44f194f8bf43c6), UINT64_C(0xfaa0c7acb211b4db),
+    UINT64_C(0xa11387f7f524be80), UINT64_C(0x18526ca6f2ceb77f),
+    UINT64_C(0x42a01a906b7dad17), UINT64_C(0x32e5337a1966b9a7),
+    UINT64_C(0x6f00f7ac9735f58a), UINT64_C(0x0e6c48f40642ba60),
+    UINT64_C(0xb6192fa1afb5e4ec), UINT64_C(0x7339dba8ffc2fcf5),
+    UINT64_C(0x7746cbe1ac90c9eb), UINT64_C(0xb83004341d78466f),
+    UINT64_C(0x781dcca572bb3d8a), UINT64_C(0xb880376cc82a5384),
+    UINT64_C(0x9c68a5c477dfecb9), UINT64_C(0xbe45ceb02a25fef3),
+    UINT64_C(0x7d1a8ccd208dbaa1), UINT64_C(0x452c002b9085e7db),
+    UINT64_C(0xd7ef361c5551378d), UINT64_C(0x96b4ff49d9e5a8bb),
+    UINT64_C(0xd337c94ede732d83), UINT64_C(0x28c75b8d3fe83c38),
+    UINT64_C(0xe05d7bd130fe20e0), UINT64_C(0x6170e60b76d85108),
+    UINT64_C(0xb0e59892414901ff), UINT64_C(0xff05cf70b911f3de),
+    UINT64_C(0x4501460fdfd508c9), UINT64_C(0xf20b1303a2a54906),
+    UINT64_C(0xa7d3badbeb04b20c), UINT64_C(0x05ce666b741b666d),
+    UINT64_C(0x826e689fded20430), UINT64_C(0x5155d1354fcf6ff6),
+    UINT64_C(0x55469226a1d318f6), UINT64_C(0x1c8d3820cfa9cabc),
+    UINT64_C(0xe68d83bbf700fc40), UINT64_C(0xb1152d8944042803),
+    UINT64_C(0x4c1e741aabe53700), UINT64_C(0x72b94c56e0f9a967),
+    UINT64_C(0xe7e5a80a79e4539c), UINT64_C(0x9641c5d11feb4d12),
+    UINT64_C(0xb18b71598b73dde0), UINT64_C(0xd9112fb5a7904d7e),
+    UINT64_C(0x3a4f57cbefabc820), UINT64_C(0xb314025fd5022976),
+    UINT64_C(0xf2a2b228aab1981b), UINT64_C(0x996ce1e60378cd98),
+    UINT64_C(0x570ec64b38e3ca90), UINT64_C(0x0657269615dcd7c6),
+    UINT64_C(0xc7360038bdc85312), UINT64_C(0x167fa02a57a8ae1a),
+    UINT64_C(0xe380fab2ea9b2bef), UINT64_C(0xa36e6abd6509b5a2),
+    UINT64_C(0x62bfcf1d6ba81c37), UINT64_C(0xd4946e8bf96caa93),
+    UINT64_C(0x603e6262ea8cda2d), UINT64_C(0x2d18c8b333ae9203),
+    UINT64_C(0x2ccdedb5a7718d61), UINT64_C(0x24333ecab8497c0a),
+    UINT64_C(0xb2e06eb33bf2b7d9), UINT64_C(0x59a5f6c550dfe7e2),
+    UINT64_C(0x800548f3927f906e), UINT64_C(0x78cac146b573bc40),
+    UINT64_C(0x85e6dadec4b6aff1), UINT64_C(0xdba17dfa17def8ec),
+    UINT64_C(0xa33f84d06ce20a93), UINT64_C(0xfd2fe967c5186746),
+    UINT64_C(0x02d37ecd7e9b180f), UINT64_C(0xc7efafa8bd33f0bb),
+    UINT64_C(0x5bda826341e791cc), UINT64_C(0xca6c1c6ce045599f),
+    UINT64_C(0x1cc02c0faa9a526e), UINT64_C(0x59d002901a4a4e52),
+    UINT64_C(0x86ecbd139ce07540), UINT64_C(0xa81f9f15ffb47f4c),
+    UINT64_C(0xc075b17481687af0), UINT64_C(0xd23ad13e68301c65),
+    UINT64_C(0xdf27955fae1c84cb), UINT64_C(0xe884cdb07368c266),
+    UINT64_C(0xef46d4f897fc83f6), UINT64_C(0xf4227e46663ec049),
+    UINT64_C(0xf79d091c1b5aff07), UINT64_C(0xfa183c533c3890a6),
+    UINT64_C(0xfbdb8a602066b0f2), UINT64_C(0xfd1af06df3db86b7),
+    UINT64_C(0xfdfc1a818587e2aa), UINT64_C(0xfe9a37a348ac2a93),
+    UINT64_C(0xff08d0797970dbfe), UINT64_C(0xff55df73fd7a00bc),
+    UINT64_C(0xff8b5aa573bca5ce), UINT64_C(0xffb053c109f51681),
+    UINT64_C(0xffc9c9bd39a036ec), UINT64_C(0xffdb40bd4ee1f846),
+    UINT64_C(0xffe72fa859b20a4a), UINT64_C(0xffef4edda93d266d),
+    UINT64_C(0xfff4d07a9fb0d4e0), UINT64_C(0xfff88868ef31b1cc),
+    UINT64_C(0xfffb08c16a3f5f0c), UINT64_C(0xfffcb5d30be5cabf),
+    UINT64_C(0xfffdd43465d95fe6), UINT64_C(0xfffe929a538ad2fa),
+    UINT64_C(0xffff10b1c2119732), UINT64_C(0xffff63df8795a2c9),
+    UINT64_C(0xffff9a87a0a9f2af), UINT64_C(0xffffbe4df6f0f4a9),
+    UINT64_C(0xffffd5a10f204910), UINT64_C(0xffffe4c6f1668b32),
+    UINT64_C(0xffffee939680bfaf), UINT64_C(0xfffff4e4216290db),
+    UINT64_C(0xfffff8f1c044613e), UINT64_C(0xfffffb892dc2b174),
+    UINT64_C(0xfffffd2fb4188608), UINT64_C(0xfffffe3bc0a56eba),
+    UINT64_C(0xfffffee523b218c6), UINT64_C(0xffffff4fc31097e3),
+    UINT64_C(0xffffff929d76dbeb), UINT64_C(0xffffffbc5e7c0350),
+    UINT64_C(0xffffffd6586c6684), UINT64_C(0xffffffe6716865f7),
+    UINT64_C(0xfffffff0613c76e2), UINT64_C(0xfffffff67d62ab64),
+    UINT64_C(0xfffffffa3b6664c0), UINT64_C(0xfffffffc83e13d66),
+    UINT64_C(0xfffffffde713ef71), UINT64_C(0xfffffffebe18936f),
+    UINT64_C(0xffffffff3fbfc997), UINT64_C(0xffffffff8d9fa29a),
+    UINT64_C(0xffffffffbc372590), UINT64_C(0xffffffffd7fb7dfd),
+    UINT64_C(0xffffffffe87747f9), UINT64_C(0xfffffffff2368b89),
+    UINT64_C(0xfffffffff7f44d88), UINT64_C(0xfffffffffb52a49b),
+    UINT64_C(0xfffffffffd4a9ffb), UINT64_C(0xfffffffffe700579),
+    UINT64_C(0xffffffffff1a287f), UINT64_C(0xffffffffff7c6f06),
+    UINT64_C(0xffffffffffb4fa99), UINT64_C(0xffffffffffd56300),
+    UINT64_C(0xffffffffffe7e364), UINT64_C(0xfffffffffff268d7),
+    UINT64_C(0xfffffffffff85e8e), UINT64_C(0xfffffffffffbbb6f),
+    UINT64_C(0xfffffffffffd9f49), UINT64_C(0xfffffffffffeae2c),
+    UINT64_C(0xffffffffffff453d), UINT64_C(0xffffffffffff9928),
+    UINT64_C(0xffffffffffffc797), UINT64_C(0xffffffffffffe12f),
+    UINT64_C(0xffffffffffffef3c), UINT64_C(0xfffffffffffff6eb),
+    UINT64_C(0xfffffffffffffb1b), UINT64_C(0xfffffffffffffd60),
+    UINT64_C(0xfffffffffffffe9a), UINT64_C(0xffffffffffffff43),
+    UINT64_C(0xffffffffffffff9e), UINT64_C(0xffffffffffffffce),
+    UINT64_C(0xffffffffffffffe7), UINT64_C(0xfffffffffffffff4),
+    UINT64_C(0xfffffffffffffffb), UINT64_C(0xfffffffffffffffe),
+};
+
+/*************************************************
+ * Name:        sample_gauss83
+ *
+ * Description: sample gauss using 83 bits integer (CDT with 83-bit precision)
+ *
+ * Arguments:   - const uint64_t rand_lo: lower 64 bits of 83-bit random input
+ *              - const uint32_t rand_hi: upper 19 bits of 83-bit random input
+ *
+ * Returns:     non-negative integer x sampled from D_{Z,16} half-distribution
+ *
+ * Specification: Implements @[Algorithm 17, SampleGauss83]
+ **************************************************/
+static uint64_t sample_gauss83(const uint64_t rand_lo, const uint32_t rand_hi) {
+  unsigned int i;
+  uint64_t r = 0;
+
+  for (i = 0; i < CDTHILEN; i++) {
+#ifdef __SIZEOF_INT128__
+    uint128 cdt = ((uint128)CDT83_HI[i] << 64) | CDT83_LO[i];
+    uint128 rnd = ((uint128)rand_hi << 64) | rand_lo;
+    r += (uint64_t)((cdt - rnd) >> 127);
+#else
+    uint64_t borrow = (rand_lo > CDT83_LO[i]);
+    uint64_t hi_diff = (uint64_t)CDT83_HI[i] - rand_hi - borrow;
+    r += hi_diff >> 63;
+#endif
+  }
+
+#ifdef __SIZEOF_INT128__
+  uint128 rnd = ((uint128)rand_hi << 64) | rand_lo;
+  for (i = CDTHILEN; i < CDTLEN; i++) {
+    uint128 cdt = ((uint128)UINT64_C(0x7ffff) << 64) | CDT83_LO[i];
+    r += (uint64_t)((cdt - rnd) >> 127);
+  }
+#else
+  for (i = CDTHILEN; i < CDTLEN; i++) {
+    uint64_t borrow = (rand_lo > CDT83_LO[i]);
+    uint64_t hi_diff = (uint64_t)UINT64_C(0x7ffff) - rand_hi - borrow;
+    r += hi_diff >> 63;
+  }
+#endif
+
+  return r;
+}
+
+/*************************************************
+ * Name:        sample_gauss_sigma76
+ *
+ * Description: sample gauss with sigma 76
+ *
+ * Arguments:   - uint64_t *r: pointer to output integer
+ *              - fp96_76 *sqr: pointer to output fixed point
+ *              - const uint8_t rand[]: pointer to input integer
+ *
+ * Specification: Implements @[Algorithm 16, SampleGaussSigma76]
+ **************************************************/
+#define GAUSS_RAND (72 + 83 + 48)
+#define GAUSS_RAND_BYTES ((GAUSS_RAND + 7) / 8) // 26
+static int sample_gauss_sigma76(uint64_t *r, fp96_76 *sqr,
+                                const uint8_t rand[GAUSS_RAND_BYTES]) {
+  // rand[0..10]:  83-bit CDT random (rand_gauss83)
+  // rand[11..16]: 48-bit rejection random (rand_rej)
+  // rand[17..25]: 72-bit y noise
+  const uint64_t rand_gauss83_lo =
+      rand[0] | (((uint64_t)rand[1]) << 8) | (((uint64_t)rand[2]) << 16) |
+      (((uint64_t)rand[3]) << 24) | (((uint64_t)rand[4]) << 32) |
+      (((uint64_t)rand[5]) << 40) | (((uint64_t)rand[6]) << 48) |
+      (((uint64_t)rand[7]) << 56);
+  const uint32_t rand_gauss83_hi =
+      (rand[8] | (((uint32_t)rand[9]) << 8) | (((uint32_t)rand[10]) << 16)) &
+      UINT32_C(0x7FFFF); // mask to 19 bits
+  const uint64_t rand_rej =
+      rand[11] | (((uint64_t)rand[12]) << 8) | (((uint64_t)rand[13]) << 16) |
+      (((uint64_t)rand[14]) << 24) | (((uint64_t)rand[15]) << 32) |
+      (((uint64_t)rand[16]) << 40);
+  uint64_t x, exp_in;
+  fp96_76 y;
+
+  // sample x
+  x = sample_gauss83(rand_gauss83_lo, rand_gauss83_hi);
+
+  // y := append x to y
+  // leave 16 bit for carries
+  y.limb48[0] = rand[17] | ((uint64_t)rand[18] << 8) |
+                ((uint64_t)rand[19] << 16) | ((uint64_t)rand[20] << 24) |
+                ((uint64_t)rand[21] << 32) | ((uint64_t)rand[22] << 40);
+  y.limb48[1] = rand[23] | ((uint64_t)rand[24] << 8) |
+                ((uint64_t)rand[25] << 16) | (x << 24);
+
+  // r := round y
+  // y.limb48[1] can be up to 166*2^24 < 2^32 (for x <= 165), so << 33 would
+  // overflow uint64_t for x >= 128.  Split: lower bits round separately, then
+  // add upper bits via << 32 (safe since y.limb48[1] * 2^32 <= 166*2^56 <
+  // 2^64).
+  *r = ((y.limb48[0] >> 15) + 1) >> 1;
+  *r += y.limb48[1] << 32;
+
+  // sqr := y*y
+  fixpoint_square(sqr, &y);
+
+  // sqr[1] = y^2 >> (76+48)                // 34 bit
+  // sqr[0] = (y^2 >> 76) & ((UINT64_C(1)<<48)-1)   // 48 bit
+  // exp_in := sqr - ((x*x) << 68)
+  exp_in = sqr->limb48[1] - ((x * x) << (68 - 48));
+  exp_in <<= 20;
+  exp_in |= sqr->limb48[0] >> 28;
+  exp_in += 1; // rounding
+  exp_in >>= 1;
+
+  return ((((int64_t)(rand_rej ^
+                      (rand_rej & 1)) // set lowest bit to zero in order to
+                                      // use it for rejection if sample==0
+            - (int64_t)approx_exp(exp_in)) >>
+           63) // reject with prob 1-approx_exp(exp_in)
+          & (((*r | -*r) >> 63) | rand_rej)) &
+         1; // if the sample is zero, clear the return value with prob 1/2
+}
+
+/*************************************************
+ * Name:        sample_gauss
+ *
+ * Description: sample gauss using smaple_gauss_sigma76
+ *
+ * Arguments:   - uint64_t *r: pointer to output integer array
+ *              - fp96_76 *sqsum: pointer to output fixed point
+ *              - const uint8_t *buf: pointer to input byte array
+ *              - size_t buflen: length of input byte array
+ *              - size_t len: max length of samples
+ *              - int dont_write_last: flag
+ *
+ * Specification: Implements @[Algorithm 15, SampleGauss]
+ **************************************************/
+int sample_gauss(uint64_t *r, fp96_76 *sqsum, const uint8_t *buf, size_t buflen,
+                 size_t len, int dont_write_last) {
+  const uint8_t *pos = buf;
+  fp96_76 sqr;
+  size_t bytecnt = buflen, coefcnt = 0;
+  int accepted;
+  uint64_t dummy;
+
+  while (coefcnt < len) {
+    if (bytecnt < GAUSS_RAND_BYTES) {
+      renormalize(sqsum);
+      return coefcnt;
+    }
+
+    if (dont_write_last && coefcnt == len - 1) {
+      accepted = sample_gauss_sigma76(&dummy, &sqr, pos);
+    } else {
+      accepted = sample_gauss_sigma76(&r[coefcnt], &sqr, pos);
+    }
+    coefcnt += accepted;
+    pos += GAUSS_RAND_BYTES;
+    bytecnt -= GAUSS_RAND_BYTES;
+
+    sqsum->limb48[0] += sqr.limb48[0] & -(int64_t)accepted;
+    sqsum->limb48[1] += sqr.limb48[1] & -(int64_t)accepted;
+  }
+
+  renormalize(sqsum);
+  return len;
+}
+
+/*************************************************
+ * Name:        sample_gauss_N
+ *
+ * Description: sample gauss
+ *
+ * Arguments:   - uint64_t *r: pointer to output integer array
+ *              - uint8_t *signs: pointer to output byte array
+ *              - fp96_76 *sqsum: pointer to output fixed point
+ *              - const uint8_t seed[]: pointer to input seed
+ *              - uint16_t nonce: input nonce
+ *              - size_t len: number of samples
+ *
+ * Specification: Implements @[Algorithm 14, SampleGaussN]
+ **************************************************/
+#define POLY_HYPERBALL_BUFLEN (GAUSS_RAND_BYTES * HAETAE_N)
+#define POLY_HYPERBALL_NBLOCKS                                                 \
+  ((POLY_HYPERBALL_BUFLEN + STREAM256_BLOCKBYTES - 1) / STREAM256_BLOCKBYTES)
+void sample_gauss_N(uint64_t *r, uint8_t *signs, fp96_76 *sqsum,
+                    const uint8_t seed[HAETAE_CRHBYTES], uint16_t nonce,
+                    size_t len) {
+  uint8_t buf[POLY_HYPERBALL_NBLOCKS * STREAM256_BLOCKBYTES];
+  size_t bytecnt, coefcnt, firstflag = 1;
+  stream256_state state;
+  stream256_init(&state, seed, nonce);
+
+  stream256_squeezeblocks(buf, POLY_HYPERBALL_NBLOCKS, &state);
+  for (size_t i = 0; i < len / 8; i++) {
+    signs[i] = buf[i];
+  }
+  bytecnt = POLY_HYPERBALL_NBLOCKS * STREAM256_BLOCKBYTES - len / 8;
+  coefcnt = sample_gauss(r, sqsum, buf + len / 8, bytecnt, len, len % HAETAE_N);
+  while (coefcnt < len) {
+    size_t off = bytecnt % GAUSS_RAND_BYTES;
+    for (size_t i = 0; i < off; i++) {
+      buf[i] = buf[bytecnt + len / 8 * firstflag - off + i];
+    }
+    stream256_squeezeblocks(buf + off, 1, &state);
+    bytecnt = STREAM256_BLOCKBYTES + off;
+
+    coefcnt += sample_gauss(r + coefcnt, sqsum, buf, bytecnt, len - coefcnt,
+                            len % HAETAE_N);
+    firstflag = 0;
+  }
+}
diff --git a/cbits/third_party/HAETAE/src/sign.c b/cbits/third_party/HAETAE/src/sign.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/sign.c
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: MIT
+
+#include "api.h"
+#include "packing.h"
+#include "params.h"
+#include "poly.h"
+#include "polyfix.h"
+#include "polymat.h"
+#include "polyvec.h"
+#include "randombytes.h"
+#include "symmetric.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+/*************************************************
+ * Name:        crypto_sign_keypair_internal
+ *
+ * Description: Generates public and private key.
+ *
+ * Arguments:   - uint8_t *vk: pointer to output public key (allocated
+ *              array of HAETAE_CRYPTO_PUBLICKEYBYTES bytes)
+ *              - uint8_t *sk: pointer to output private key (allocated
+ *              array of HAETAE_CRYPTO_SECRETKEYBYTES bytes)
+ *              - uint8_t seed : input seed
+ *
+ * Returns 0 (success)
+ *
+ * Specification: Implements @[Algorithm 54, KeyGenInternal]
+ **************************************************/
+
+int crypto_sign_keypair_internal(uint8_t *vk, uint8_t *sk,
+                                 uint8_t seed[HAETAE_SEEDBYTES]) {
+  uint8_t seedbuf[2 * HAETAE_SEEDBYTES + HAETAE_CRHBYTES] = {0};
+  uint16_t counter = 0;
+  const uint8_t *rhoprime, *sigma, *key;
+  polyvecm A[HAETAE_K], s1, s1hat;
+  polyveck b, s2;
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  polyveck a, b0;
+#elif HAETAE_MODE == HAETAE_MODE5
+  polyveck s2hat;
+#endif
+  xof256_state state;
+
+  // Get seed_A
+  memcpy(seedbuf, seed, HAETAE_SEEDBYTES);
+
+  // Sample seeds with entropy \rho
+  xof256_absorb_once(&state, seedbuf, HAETAE_SEEDBYTES);
+  xof256_squeeze(seedbuf, 2 * HAETAE_SEEDBYTES + HAETAE_CRHBYTES, &state);
+
+  rhoprime = seedbuf;
+  sigma = rhoprime + HAETAE_SEEDBYTES;
+  key = sigma + HAETAE_CRHBYTES;
+
+  // Expand Matrix A0 and vector a
+  polymatkm_expand_matA(A, rhoprime);
+
+#if (HAETAE_MODE == HAETAE_MODE2) || (HAETAE_MODE == HAETAE_MODE3)
+  /**********************************************
+   * If there is rounding (D > 0), we need another polyveck a.
+   * Then, b = a + A0 * s1 + s2 and the lower D bits are
+   * rounded from b. The lower D bits are subsequently
+   * subtracted from s2.
+   **********************************************/
+  polyveck_expand_vecA(&a, rhoprime);
+
+reject:
+  // Sample secret vectors s1 and s2
+  polyvecmk_expand_S(&s1, &s2, sigma, counter);
+  counter += HAETAE_M + HAETAE_K;
+
+  // b = a + A0 * s1 + s2 mod q
+  s1hat = s1;
+  polyvecm_ntt(&s1hat);
+  polymatkm_pointwise_montgomery(&b, A, &s1hat);
+  polyveck_invntt_tomont(&b);
+  polyveck_add(&b, &b, &s2);
+  polyveck_add(&b, &b, &a);
+  polyveck_freeze(&b);
+
+  // round off D bits
+  polyveck_decompose_vk(&b0, &b);
+  polyveck_sub(&s2, &s2, &b0);
+
+  int64_t squared_singular_value = polyvecmk_sk_singular_value(&s1, &s2);
+  if (squared_singular_value > HAETAE_GAMMA * HAETAE_GAMMA * HAETAE_N) {
+    goto reject;
+  }
+#elif HAETAE_MODE == HAETAE_MODE5
+  /**********************************************
+   * If there is no rounding (D == 0), we store
+   * -2b directly in NTT domain into the public key.
+   **********************************************/
+reject:
+  // Sample secret vectors s1 and s2
+  polyvecmk_expand_S(&s1, &s2, sigma, counter);
+  counter += HAETAE_M + HAETAE_K;
+  int64_t squared_singular_value = polyvecmk_sk_singular_value(&s1, &s2);
+  if (squared_singular_value > HAETAE_GAMMA * HAETAE_GAMMA * HAETAE_N) {
+    goto reject;
+  }
+
+  // b = A0 * s1 + s2 mod q
+  s1hat = s1;
+  s2hat = s2;
+  polyvecm_ntt(&s1hat);
+  polyveck_ntt(&s2hat);
+  polymatkm_pointwise_montgomery(&b, A, &s1hat);
+  polyveck_frommont(&b);
+  polyveck_add(&b, &b, &s2hat);
+  polyveck_double_negate(&b);
+  polyveck_caddq(&b); // directly compute and store NTT(-2b)
+#endif
+
+  pack_vk(vk, &b, rhoprime);
+  pack_sk(sk, vk, &s1, &s2, key);
+
+  return 0;
+}
+
+/*************************************************
+ * Name:        crypto_sign_keypair_internal
+ *
+ * Description: Generates public and private key.
+ *
+ * Arguments:   - uint8_t *vk: pointer to output public key (allocated
+ *              array of HAETAE_CRYPTO_PUBLICKEYBYTES bytes)
+ *              - uint8_t *sk: pointer to output private key (allocated
+ *              array of HAETAE_CRYPTO_SECRETKEYBYTES bytes)
+ *
+ * Returns 0 (success)
+ *
+ * Specification: Implements @[Algorithm 51, KeyGen]
+ **************************************************/
+int crypto_sign_keypair(uint8_t *pk, uint8_t *sk) {
+  int ret = 0;
+  uint8_t seed[HAETAE_SEEDBYTES] = {0};
+
+  if (randombytes(seed, HAETAE_SEEDBYTES) != 0)
+    return -1;
+
+  ret = crypto_sign_keypair_internal(pk, sk, seed);
+
+  return ret;
+}
+
+/*************************************************
+ * Name:        crypto_sign_signature_internal
+ *
+ * Description: Computes signature. Internal API.
+ *
+ * Arguments:   - uint8_t *sig: pointer to output signature (of length
+ *              HAETAE_CRYPTO_BYTES)
+ *              - size_t *siglen: pointer to output length of signature
+ *              - uint8_t *m: pointer to message to be signed
+ *              - size_t mlen: length of message
+ *              - uint8_t *pre: pointer to prefix string
+ *              - size_t prelen: length of prefix string
+ *              - uint8_t *rnd: pointer to random seed
+ *              - uint8_t *sk: pointer to bit-packed secret key
+ *
+ * Returns 0 (success)
+ *
+ * Specification: Implements @[Algorithm 55, SignInternal]
+ **************************************************/
+int crypto_sign_signature_internal(uint8_t *sig, size_t *siglen,
+                                   const uint8_t *m, size_t mlen,
+                                   const uint8_t *pre, size_t prelen,
+                                   const uint8_t rnd[HAETAE_SEEDBYTES],
+                                   const uint8_t *sk) {
+
+  uint8_t buf[HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES + HAETAE_POLYC_PACKEDBYTES] =
+      {0};
+  uint8_t seedbuf[HAETAE_CRHBYTES] = {0}, key[HAETAE_SEEDBYTES] = {0};
+  uint8_t mu[HAETAE_CRHBYTES] = {0};
+  uint8_t b = 0; // one bit
+  uint16_t counter = 0;
+  uint64_t reject1, reject2;
+
+  polyvecm s1;
+  polyvecl A1[HAETAE_K], cs1;
+  polyveck s2, cs2, highbits, Ay;
+  polyfixvecl y1, z1, z1tmp;
+  polyfixveck y2, z2, z2tmp;
+  polyvecl z1rnd; // round of z1
+  polyvecl hb_z1, lb_z1;
+  polyveck z2rnd, h, htmp; // round of z2
+  poly c, chat, z1rnd0, lsb;
+
+  xof256_state state;
+
+  unsigned int i;
+
+  // Unpack secret key
+  unpack_sk(A1, &s1, &s2, key, sk);
+
+  xof256_absorb_thrice(&state, sk, HAETAE_CRYPTO_PUBLICKEYBYTES, pre, prelen, m,
+                       mlen);
+  xof256_squeeze(mu, HAETAE_CRHBYTES, &state);
+
+  xof256_absorb_thrice(&state, key, HAETAE_SEEDBYTES, rnd, HAETAE_SEEDBYTES, mu,
+                       HAETAE_CRHBYTES);
+  xof256_squeeze(seedbuf, HAETAE_CRHBYTES, &state);
+
+  polyvecm_ntt(&s1);
+  polyveck_ntt(&s2);
+
+reject:
+
+  /*------------------ 1. Sample y1 and y2 from hyperball ------------------*/
+  counter = polyfixveclk_sample_hyperball(&y1, &y2, &b, seedbuf, counter);
+
+  /*------------------- 2. Compute a chanllenge c --------------------------*/
+  // Round y1 and y2
+  polyfixvecl_round(&z1rnd, &y1);
+  polyfixveck_round(&z2rnd, &y2);
+
+  // A * round(y) mod q = A1 * round(y1) + 2 * round(y2) mod q
+  z1rnd0 = z1rnd.vec[0];
+  polyvecl_ntt(&z1rnd);
+  polymatkl_pointwise_montgomery(&Ay, A1, &z1rnd);
+  polyveck_invntt_tomont(&Ay);
+  polyveck_double(&z2rnd);
+  polyveck_add(&Ay, &Ay, &z2rnd);
+
+  // recover A * round(y) mod 2q
+  polyveck_poly_fromcrt(&Ay, &Ay, &z1rnd0);
+  polyveck_freeze2q(&Ay);
+
+  // HighBits of (A * round(y) mod 2q)
+  polyveck_highbits_hint(&highbits, &Ay);
+
+  // LSB(round(y_0) * j)
+  poly_lsb(&lsb, &z1rnd0);
+
+  // Pack HighBits of A * round(y) mod 2q and LSB of round(y0)
+  pack_vec_highbits(buf, &highbits);
+  pack_poly_lsb(buf + HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES, &lsb);
+
+  // c = challenge(highbits, lsb, mu)
+  poly_challenge(&c, buf, mu);
+
+  /*------------------- 3. Compute z = y + (-1)^b c * s --------------------*/
+  // cs = c * s = c * (si1 || s2)
+  cs1.vec[0] = c;
+  chat = c;
+  poly_ntt(&chat);
+
+  for (i = 1; i < HAETAE_L; ++i) {
+    poly_pointwise_montgomery(&cs1.vec[i], &chat, &s1.vec[i - 1]);
+    poly_invntt_tomont(&cs1.vec[i]);
+  }
+  polyveck_poly_pointwise_montgomery(&cs2, &s2, &chat);
+  polyveck_invntt_tomont(&cs2);
+
+  // z = y + (-1)^b cs = z1 + z2
+  polyvecl_cneg(&cs1, b & 1);
+  polyveck_cneg(&cs2, b & 1);
+  polyfixvecl_add(&z1, &y1, &cs1);
+  polyfixveck_add(&z2, &y2, &cs2);
+
+  // reject if norm(z) >= B'
+  reject1 = ((uint64_t)HAETAE_B1SQ * HAETAE_LN * HAETAE_LN -
+             polyfixveclk_sqnorm2(&z1, &z2)) >>
+            63;
+  reject1 &= 1;
+
+  polyfixvecl_double(&z1tmp, &z1);
+  polyfixveck_double(&z2tmp, &z2);
+
+  polyfixfixvecl_sub(&z1tmp, &z1tmp, &y1);
+  polyfixfixveck_sub(&z2tmp, &z2tmp, &y2);
+
+  // reject if norm(2z-y) < B and b' = 0
+  reject2 = (polyfixveclk_sqnorm2(&z1tmp, &z2tmp) -
+             (uint64_t)HAETAE_B0SQ * HAETAE_LN * HAETAE_LN) >>
+            63;
+  reject2 &= 1;
+  reject2 &= (b & 0x2) >> 1;
+
+  if (reject1 | reject2) {
+    goto reject;
+  }
+
+  /*------------------- 4. Make a hint -------------------------------------*/
+  // Round z1 and z2
+  polyfixvecl_round(&z1rnd, &z1);
+  polyfixveck_round(&z2rnd, &z2);
+
+  // recover A1 * round(z1) - qcj mod 2q
+  polyveck_double(&z2rnd);
+  polyveck_sub(&htmp, &Ay, &z2rnd);
+  polyveck_freeze2q(&htmp);
+
+  // HighBits of (A * round(z) - qcj mod 2q) and (A1 * round(z1) - qcj mod 2q)
+  polyveck_highbits_hint(&htmp, &htmp);
+  polyveck_sub(&h, &highbits, &htmp);
+  polyveck_caddDQ2ALPHA(&h);
+
+  /*------------------ Decompose(z1) and Pack signature -------------------*/
+  polyvecl_lowbits(&lb_z1, &z1rnd); // TODO do this in one function together!
+  polyvecl_highbits(&hb_z1, &z1rnd);
+
+  if (pack_sig(sig, &c, &lb_z1, &hb_z1,
+               &h)) { // reject if signature is too big
+    goto reject;
+  }
+  *siglen = HAETAE_CRYPTO_BYTES;
+
+  return 0;
+}
+
+/*************************************************
+ * Name:        crypto_sign_signature
+ *
+ * Description: Computes signature.
+ *
+ * Arguments:   - uint8_t *sig:   pointer to output signature (of length
+ *                                HAETAE_CRYPTO_BYTES)
+ *              - size_t *siglen: pointer to output length of signature
+ *              - uint8_t *m:     pointer to message to be signed
+ *              - size_t mlen:    length of message
+ *              - uint8_t *ctx:   pointer of contex string
+ *              - size_t ctxlen:  length of contex string
+ *              - uint8_t *sk:    pointer to bit-packed secret key
+ *
+ * Returns 0 (success)
+ *
+ * Specification: Implements @[Algorithm 52, Sign]
+ **************************************************/
+int crypto_sign_signature(uint8_t *sig, size_t *siglen, const uint8_t *m,
+                          size_t mlen, const uint8_t *ctx, size_t ctxlen,
+                          const uint8_t *sk) {
+  uint8_t pre[256];
+  uint8_t rnd[HAETAE_SEEDBYTES];
+
+  if (ctxlen > 255)
+    return -1;
+
+  /* Prepare pre = (ctxlen, ctx) */
+  pre[0] = ctxlen;
+  memcpy(pre + 1, ctx, ctxlen);
+
+  if (randombytes(rnd, HAETAE_SEEDBYTES) != 0)
+    return -1;
+
+  return crypto_sign_signature_internal(sig, siglen, m, mlen, pre, 1 + ctxlen,
+                                        rnd, sk);
+}
+
+/*************************************************
+ * Name:        crypto_sign
+ *
+ * Description: Compute signed message.
+ *
+ * Arguments:   - uint8_t *sm: pointer to output signed message (allocated
+ *                             array with HAETAE_CRYPTO_BYTES + mlen bytes),
+ *                             can be equal to m
+ *              - size_t *smlen: pointer to output length of signed
+ *                               message
+ *              - const uint8_t *m: pointer to message to be signed
+ *              - size_t mlen: length of message
+ *              - const uint8_t *ctx: pointer to context string
+ *              - size_t ctxlen: length of context string
+ *              - const uint8_t *sk: pointer to bit-packed secret key
+ *
+ * Returns 0 (success) or -1 (context string too long)
+ **************************************************/
+int crypto_sign(uint8_t *sm, size_t *smlen, const uint8_t *m, size_t mlen,
+                const uint8_t *ctx, size_t ctxlen, const uint8_t *sk) {
+  size_t i;
+
+  for (i = 0; i < mlen; ++i)
+    sm[HAETAE_CRYPTO_BYTES + mlen - 1 - i] = m[mlen - 1 - i];
+  int ret = crypto_sign_signature(sm, smlen, sm + HAETAE_CRYPTO_BYTES, mlen,
+                                  ctx, ctxlen, sk);
+  *smlen += mlen;
+  return ret;
+}
+
+/*************************************************
+ * Name:        crypto_sign_verify_internal
+ *
+ * Description: Verifies signature.
+ *
+ * Arguments:   - uint8_t *m: pointer to input signature
+ *              - size_t siglen: length of signature
+ *              - const uint8_t *m: pointer to message
+ *              - size_t mlen: length of message
+ *              - const uint8_t *pre: pointer to prefix string
+ *              - size_t prelen: length of prefix string
+ *              - const uint8_t *vk: pointer to bit-packed public key
+ *
+ * Returns 0 if signature could be verified correctly and -1 otherwise
+ *
+ * Specification: Implements @[Algorithm 56, VerifyInternal]
+ **************************************************/
+int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen,
+                                const uint8_t *m, size_t mlen,
+                                const uint8_t *pre, size_t prelen,
+                                const uint8_t *vk) {
+  unsigned int i;
+  uint8_t buf[HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES + HAETAE_POLYC_PACKEDBYTES] =
+      {0};
+  uint8_t mu[HAETAE_SEEDBYTES];
+  uint64_t sqnorm2;
+  polyvecl A1[HAETAE_K], highz, lowz, z1;
+  polyveck highbits, h, z2, w;
+  poly c, cprime, wprime;
+
+  xof256_state state;
+
+  // Check signature length
+  if (siglen != HAETAE_CRYPTO_BYTES) {
+    return -1;
+  }
+  /*------------------- 1. Recover A1 --------------------------------------*/
+  // A1 = (-2b + qj || 2 * A0)
+  // Unpack public key
+  unpack_vk(A1, vk);
+
+  // Unpack signature and Check conditions -- A1 is used only as intermediate
+  // buffer for the low bits
+  if (unpack_sig(&c, &lowz, &highz, &h, sig)) {
+    return -1;
+  }
+
+  // Compose z1 out of HighBits(z1) and LowBits(z1)
+  for (i = 0; i < HAETAE_L; ++i) {
+    poly_compose(&z1.vec[i], &highz.vec[i], &lowz.vec[i]);
+  }
+
+  /*------------------- 2. Compute \tilde{z}_2 -----------------------------*/
+  // compute squared norm of z1 and w' before NTT
+  sqnorm2 = polyvecl_sqnorm2(&z1);
+  poly_sub(&wprime, &z1.vec[0], &c);
+  poly_lsb(&wprime, &wprime);
+
+  // A1 * round(z1) - qcj mod q
+  polyvecl_ntt(&z1);
+  polymatkl_pointwise_montgomery(&highbits, A1, &z1);
+  polyveck_invntt_tomont(&highbits);
+
+  // recover A1 * round(z1) - qcj mod 2q
+  polyveck_poly_fromcrt(&highbits, &highbits, &wprime);
+  polyveck_freeze2q(&highbits);
+
+  // recover w1
+  polyveck_highbits_hint(&w, &highbits);
+  polyveck_add(&w, &w, &h);
+  polyveck_csubDQ2ALPHA(&w);
+
+  // recover \tilde{z}_2 mod q
+  polyveck_mul_alpha(&z2, &w);
+  polyveck_sub(&z2, &z2, &highbits);
+  poly_add(&z2.vec[0], &z2.vec[0], &wprime);
+  polyveck_reduce2q(&z2);
+  polyveck_div2(&z2);
+
+  // check final norm of \tilde{z}
+  if (sqnorm2 + polyveck_sqnorm2(&z2) > HAETAE_B2SQ) {
+    return -1;
+  }
+
+  /*------------------- 3. Compute c_seed' and Compare ---------------------*/
+
+  // Pack highBits(A * round(z) - qcj mod 2q) and h'
+  pack_vec_highbits(buf, &w);
+  pack_poly_lsb(buf + HAETAE_POLYVECK_HIGHBITS_PACKEDBYTES, &wprime);
+
+  xof256_absorb_thrice(&state, vk, HAETAE_CRYPTO_PUBLICKEYBYTES, pre, prelen, m,
+                       mlen);
+  xof256_squeeze(mu, HAETAE_SEEDBYTES, &state);
+  // c_seed = H(HighBits(A * y mod 2q), LSB(round(y0) * j), HAETAE_M)
+  poly_challenge(&cprime, buf, mu);
+
+  for (i = 0; i < HAETAE_N; ++i) {
+    if (c.coeffs[i] != cprime.coeffs[i]) {
+      return -1;
+    }
+  }
+  return 0;
+}
+
+/*************************************************
+ * Name:        crypto_sign_verify
+ *
+ * Description: Verifies signature.
+ *
+ * Arguments:   - uint8_t *m: pointer to input signature
+ *              - size_t siglen: length of signature
+ *              - const uint8_t *m: pointer to message
+ *              - size_t mlen: length of message
+ *              - const uint8_t *ctx: pointer to context string
+ *              - size_t ctxlen: length of context string
+ *              - const uint8_t *vk: pointer to bit-packed public key
+ *
+ * Returns 0 if signature could be verified correctly and -1 otherwise
+ *
+ * Specification: Implements @[Algorithm 53, Verify]
+ **************************************************/
+int crypto_sign_verify(const uint8_t *sig, size_t siglen, const uint8_t *m,
+                       size_t mlen, const uint8_t *ctx, size_t ctxlen,
+                       const uint8_t *vk) {
+  uint8_t pre[257];
+
+  if (ctxlen > 255)
+    return -1;
+
+  pre[0] = ctxlen;
+  memcpy(pre + 1, ctx, ctxlen);
+
+  return crypto_sign_verify_internal(sig, siglen, m, mlen, pre, 1 + ctxlen, vk);
+}
+
+/*************************************************
+ * Name:        crypto_sign_open
+ *
+ * Description: Verify signed message.
+ *
+ * Arguments:   - uint8_t *m: pointer to output message (allocated
+ *                            array with smlen bytes), can be equal to sm
+ *              - size_t *mlen: pointer to output length of message
+ *              - const uint8_t *sm: pointer to signed message
+ *              - size_t smlen: length of signed message
+ *              - const uint8_t *vk: pointer to bit-packed public key
+ *
+ * Returns 0 if signed message could be verified correctly and -1 otherwise
+ **************************************************/
+int crypto_sign_open(uint8_t *m, size_t *mlen, const uint8_t *sm, size_t smlen,
+                     const uint8_t *ctx, size_t ctxlen, const uint8_t *vk) {
+  size_t i;
+
+  if (smlen < HAETAE_CRYPTO_BYTES)
+    goto badsig;
+
+  *mlen = smlen - HAETAE_CRYPTO_BYTES;
+  if (crypto_sign_verify(sm, HAETAE_CRYPTO_BYTES, sm + HAETAE_CRYPTO_BYTES,
+                         *mlen, ctx, ctxlen, vk))
+    goto badsig;
+  else {
+    /* All good, copy msg, return 0 */
+    for (i = 0; i < *mlen; ++i)
+      m[i] = sm[HAETAE_CRYPTO_BYTES + i];
+    return 0;
+  }
+
+badsig:
+  /* Signature verification failed */
+  *mlen = -1;
+  for (i = 0; i < smlen; ++i)
+    m[i] = 0;
+
+  return -1;
+}
diff --git a/cbits/third_party/HAETAE/src/symmetric-shake.c b/cbits/third_party/HAETAE/src/symmetric-shake.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/HAETAE/src/symmetric-shake.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: MIT
+
+#include "fips202.h"
+#include "params.h"
+#include "symmetric.h"
+
+#include <stdint.h>
+
+void shake128_stream_init(keccak_state *state,
+                          const uint8_t seed[HAETAE_SEEDBYTES],
+                          uint16_t nonce) {
+  uint8_t t[2];
+  t[0] = nonce;
+  t[1] = nonce >> 8;
+
+  shake128_init(state);
+  shake128_absorb(state, seed, HAETAE_SEEDBYTES);
+  shake128_absorb(state, t, 2);
+  shake128_finalize(state);
+}
+
+void shake256_stream_init(keccak_state *state,
+                          const uint8_t seed[HAETAE_CRHBYTES], uint16_t nonce) {
+  uint8_t t[2];
+  t[0] = nonce;
+  t[1] = nonce >> 8;
+
+  shake256_init(state);
+  shake256_absorb(state, seed, HAETAE_CRHBYTES);
+  shake256_absorb(state, t, 2);
+  shake256_finalize(state);
+}
+
+void shake256_absorb_twice(keccak_state *state, const uint8_t *in1,
+                           size_t in1len, const uint8_t *in2, size_t in2len) {
+  shake256_init(state);
+  shake256_absorb(state, in1, in1len);
+  shake256_absorb(state, in2, in2len);
+  shake256_finalize(state);
+}
+
+void shake256_absorb_thrice(keccak_state *state, const uint8_t *in1,
+                            size_t in1len, const uint8_t *in2, size_t in2len,
+                            const uint8_t *in3, size_t in3len) {
+  shake256_init(state);
+  shake256_absorb(state, in1, in1len);
+  shake256_absorb(state, in2, in2len);
+  shake256_absorb(state, in3, in3len);
+  shake256_finalize(state);
+}
diff --git a/cbits/third_party/NTRUplus/LICENSE b/cbits/third_party/NTRUplus/LICENSE
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2024–2026 NTRU+ TEAM (https://www.ntruplus.org/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/api.h b/cbits/third_party/NTRUplus/NTRU+1152/api.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/api.h
@@ -0,0 +1,39 @@
+#ifndef API_H
+#define API_H
+
+#include "params.h"
+
+#ifndef NTRUPLUS_ABI_H
+#define NTRUPLUS_ABI_H
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#define NTRUPLUS_INTERNAL
+#define NTRUPLUS_EXTERNAL __declspec(dllexport)
+#define NTRUPLUS_SYSV __attribute__((sysv_abi))
+#else
+#define NTRUPLUS_INTERNAL __attribute__((visibility("hidden")))
+#define NTRUPLUS_EXTERNAL __attribute__((visibility("default")))
+#define NTRUPLUS_SYSV
+#endif
+
+#endif
+
+#define CRYPTO_SECRETKEYBYTES  NTRUPLUS_SECRETKEYBYTES
+#define CRYPTO_PUBLICKEYBYTES  NTRUPLUS_PUBLICKEYBYTES
+#define CRYPTO_CIPHERTEXTBYTES NTRUPLUS_CIPHERTEXTBYTES
+#define CRYPTO_BYTES           NTRUPLUS_SSBYTES
+
+#define CRYPTO_ALGNAME NTRUPLUS_ALGNAME
+
+NTRUPLUS_EXTERNAL int crypto_kem_keypair(unsigned char *pk,
+                                         unsigned char *sk);
+
+NTRUPLUS_EXTERNAL int crypto_kem_enc(unsigned char *ct,
+                                     unsigned char *ss,
+                                     const unsigned char *pk);
+
+NTRUPLUS_EXTERNAL int crypto_kem_dec(unsigned char *ss,
+                                     const unsigned char *ct,
+                                     const unsigned char *sk);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/fips202/fips202.c b/cbits/third_party/NTRUplus/NTRU+1152/fips202/fips202.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/fips202/fips202.c
@@ -0,0 +1,621 @@
+/* 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 "util.h"
+
+#include <stddef.h>
+#include <stdint.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., 136 for SHAKE256)
+ *              - 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);
+    }
+    secure_clear(t, sizeof t);
+}
+
+/*************************************************
+ * 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., 136 for SHAKE256)
+ **************************************************/
+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., 136 for SHAKE256)
+ *              - 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., 136 for SHAKE256)
+ *              - 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., 136 for SHAKE256)
+ **************************************************/
+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 shake256_inc_init(shake256incctx *state) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+void shake256_inc_ctx_release(shake256incctx *state) {
+    secure_clear(state->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+/*************************************************
+ * 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) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/** Clear the state. */
+void shake256_ctx_release(shake256ctx *state) {
+    secure_clear(state->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/*************************************************
+ * 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];
+        }
+    }
+    secure_clear(t, sizeof(t));
+    shake256_ctx_release(&s);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/fips202/fips202.h b/cbits/third_party/NTRUplus/NTRU+1152/fips202/fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/fips202/fips202.h
@@ -0,0 +1,57 @@
+#ifndef FIPS202_H
+#define FIPS202_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHAKE256_RATE 136
+
+#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[26];
+} shake256incctx;
+
+// Context for non-incremental API
+typedef struct {
+    uint64_t ctx[25];
+} shake256ctx;
+
+/* 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);
+/* Clear 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);
+/* Clear the state */
+void shake256_inc_ctx_release(shake256incctx *state);
+
+/* One-stop SHAKE256 call */
+void shake256(uint8_t *output, size_t outlen,
+              const uint8_t *input, size_t inlen);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/kem.c b/cbits/third_party/NTRUplus/NTRU+1152/kem.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/kem.c
@@ -0,0 +1,356 @@
+#include "util.h"
+#include <stddef.h>
+#include <stdint.h>
+#include "api.h"
+#include "params.h"
+#include "symmetric.h"
+#include "poly.h"
+#include "fips202/fips202.h"
+#include "randombytes.h"
+
+#ifdef NTRUPLUS_SUPERCOP
+#include "crypto_declassify.h"
+#define ntruplus_declassify crypto_declassify
+#else
+#define ntruplus_declassify(x, xlen) ((void)(x), (void)(xlen))
+#endif
+
+/*************************************************
+* Name:        verify
+*
+* Description: Compares two byte arrays for equality in constant time.
+*
+* Arguments:   - const uint8_t *a: first byte array
+*              - const uint8_t *b: second byte array
+*              - size_t len:      length of the arrays
+*
+* Returns 0 if the arrays are equal, 1 otherwise.
+**************************************************/
+static inline int verify(const uint8_t *a, const uint8_t *b, size_t len)
+{
+    uint8_t acc = 0;
+
+    for (size_t i = 0; i < len; i++)
+        acc |= (uint8_t)(a[i] ^ b[i]);
+
+    return (-(uint64_t)acc) >> 63;
+}
+
+/*************************************************
+* Name:        genf_derand
+*
+* Description: Deterministically generates a secret polynomial f and its
+*              multiplicative inverse finv in the NTT domain.
+*
+* Arguments:   - poly *f:     output polynomial f (NTT domain)
+*              - poly *finv:  output multiplicative inverse of f
+*                              in the NTT domain
+*              - uint8_t *buf: workspace of NTRUPLUS_N/4 bytes
+*              - const uint8_t *coins: 32-byte deterministic seed
+*
+* Returns 0 on success; non-zero if f is not invertible in the NTT domain.
+**************************************************/
+static inline int genf_derand(poly *f, poly *finv,
+                              uint8_t buf[NTRUPLUS_N / 4],
+                              const uint8_t *coins)
+{
+    shake256(buf, NTRUPLUS_N / 4, coins, 32);
+
+    poly_cbd1(f, buf);
+    poly_triple(f);
+    f->coeffs[0] += 1;
+
+    poly_ntt(f);
+
+    return poly_baseinv(finv, f);
+}
+
+/*************************************************
+* Name:        geng_derand
+*
+* Description: Deterministically generates a secret polynomial g and its
+*              multiplicative inverse ginv in the NTT domain.
+*
+* Arguments:   - poly *g:      output polynomial g (NTT domain)
+*              - poly *ginv:   output multiplicative inverse of g
+*                               in the NTT domain
+*              - uint8_t *buf: workspace of NTRUPLUS_N/4 bytes
+*              - const uint8_t *coins: 32-byte deterministic seed
+*
+* Returns 0 on success; non-zero if g is not invertible in the NTT domain.
+**************************************************/
+static inline int geng_derand(poly *g, poly *ginv,
+                              uint8_t buf[NTRUPLUS_N / 4],
+                              const uint8_t *coins)
+{
+    shake256(buf, NTRUPLUS_N / 4, coins, 32);
+
+    poly_cbd1(g, buf);
+    poly_triple(g);
+
+    poly_ntt(g);
+
+    return poly_baseinv(ginv, g);
+}
+
+/*************************************************
+* Name:        crypto_kem_keypair_derand
+*
+* Description: Computes the deterministic public and secret key pair from
+*              the secret polynomials f and g and their multiplicative
+*              inverses finv and ginv in the NTT domain.
+*
+* Arguments:   - uint8_t *pk:   output public key
+*              - uint8_t *sk:   output secret key
+*              - const poly *f:     secret polynomial f (NTT domain)
+*              - const poly *finv:  multiplicative inverse of f (NTT domain)
+*              - const poly *g:     secret polynomial g (NTT domain)
+*              - const poly *ginv:  multiplicative inverse of g (NTT domain)
+*
+* Returns:     void
+**************************************************/
+static inline void crypto_kem_keypair_derand(uint8_t *pk, uint8_t *sk,
+                                             const poly *f,  const poly *finv,
+                                             const poly *g,  const poly *ginv)
+{
+    poly h, hinv;
+
+    poly_basemul(&h, g, finv);
+    poly_basemul(&hinv, f, ginv);
+
+    poly_tobytes(pk, &h);
+    poly_tobytes(sk, f);
+    poly_tobytes(sk + NTRUPLUS_POLYBYTES, &hinv);
+    hash_f(sk + 2 * NTRUPLUS_POLYBYTES, pk);
+
+    secure_clear(&h, sizeof h);
+    secure_clear(&hinv, sizeof hinv);
+}
+
+/*************************************************
+* Name:        crypto_kem_keypair
+*
+* Description: Generates an NTRU+ public/secret key pair for the
+*              CCA-secure key encapsulation mechanism. Secret
+*              polynomials f and g and their NTT-domain inverses
+*              are sampled internally using fresh randomness.
+*
+* Arguments:   - uint8_t *pk: output public key
+*                (array of CRYPTO_PUBLICKEYBYTES bytes)
+*              - uint8_t *sk: output secret key
+*                (array of CRYPTO_SECRETKEYBYTES bytes)
+*
+* Returns 0 on success.
+**************************************************/
+int crypto_kem_keypair(unsigned char *pk, unsigned char *sk)
+{
+    uint8_t coins[NTRUPLUS_SYMBYTES];
+    uint8_t buf[NTRUPLUS_N / 4];
+    int r;
+    int status = -1;
+
+    poly f, finv;
+    poly g, ginv;
+
+    do {
+        if (randombytes(coins, sizeof coins) != 0) goto cleanup;
+        r = genf_derand(&f, &finv, buf, coins);
+        ntruplus_declassify(&r, sizeof r);
+    } while (r);
+
+    do {
+        if (randombytes(coins, sizeof coins) != 0) goto cleanup;
+        r = geng_derand(&g, &ginv, buf, coins);
+        ntruplus_declassify(&r, sizeof r);
+    } while (r);
+
+    crypto_kem_keypair_derand(pk, sk, &f, &finv, &g, &ginv);
+    status = 0;
+
+cleanup:
+    secure_clear(coins, sizeof coins);
+    secure_clear(buf, sizeof buf);
+    secure_clear(&f, sizeof f);
+    secure_clear(&finv, sizeof finv);
+    secure_clear(&g, sizeof g);
+    secure_clear(&ginv, sizeof ginv);
+    return status;
+}
+
+/*************************************************
+* Name:        crypto_kem_enc_derand
+*
+* Description: Deterministically performs NTRU+ KEM encapsulation using
+*              the public key pk and the supplied randomness coins,
+*              producing a ciphertext ct and shared secret ss.
+*
+* Arguments:   - uint8_t *ct:      output ciphertext
+*              - uint8_t *ss:      output shared secret
+*              - const uint8_t *pk:    input public key
+*              - const uint8_t *coins: input randomness for
+*                                      deterministic encapsulation
+*
+* Returns 0 on success. If pk contains a non-canonical coefficient, ct and ss
+* are set to all zero bytes and 1 is returned.
+**************************************************/
+static inline int crypto_kem_enc_derand(uint8_t *ct, uint8_t *ss,
+                                        const uint8_t *pk,
+                                        const uint8_t *coins)
+{
+	uint8_t msg[HASH_H_INBYTES];
+	uint8_t buf1[HASH_H_OUTBYTES];
+	uint8_t buf2[HASH_G_INBYTES];
+    
+    poly c, h, r, m;
+
+    if(poly_frombytes(&h, pk))
+    {
+        for (size_t i = 0; i < NTRUPLUS_CIPHERTEXTBYTES; i++)
+            ct[i] = 0;
+        secure_clear(ss, NTRUPLUS_SSBYTES);
+
+        return 1;
+    }
+    
+    for (size_t i = 0; i < NTRUPLUS_N / 8; i++)
+        msg[i] = coins[i];
+
+    hash_f(msg + NTRUPLUS_N / 8, pk);
+    hash_h(buf1, msg);
+    
+    poly_cbd1(&r, buf1 + NTRUPLUS_SYMBYTES);
+    poly_ntt(&r);
+    
+    poly_tobytes(buf2, &r);
+    hash_g(buf2, buf2);
+    poly_sotp_encode(&m, msg, buf2);
+    poly_ntt(&m);
+    
+    poly_basemul_add(&c, &h, &r, &m);
+    poly_tobytes(ct, &c);
+    
+    for (size_t i = 0; i < NTRUPLUS_SSBYTES; i++)
+        ss[i] = buf1[i];
+
+    secure_clear(msg, sizeof msg);
+    secure_clear(buf1, sizeof buf1);
+    secure_clear(buf2, sizeof buf2);
+    secure_clear(&r, sizeof r);
+    secure_clear(&m, sizeof m);
+    
+    return 0;
+}
+
+/*************************************************
+* Name:        crypto_kem_enc
+*
+* Description: Generates an NTRU+ KEM ciphertext ct and shared secret ss
+*              using the public key pk. Fresh randomness is internally
+*              sampled and used for encapsulation.
+*
+* Arguments:   - uint8_t *ct: output ciphertext
+*                (array of CRYPTO_CIPHERTEXTBYTES bytes)
+*              - uint8_t *ss: output shared secret
+*                (array of CRYPTO_BYTES bytes)
+*              - const uint8_t *pk: input public key
+*                (array of CRYPTO_PUBLICKEYBYTES bytes)
+*
+* Returns 0 on success. If pk contains a non-canonical coefficient, ct and ss
+* are set to all zero bytes and 1 is returned.
+**************************************************/
+int crypto_kem_enc(unsigned char *ct, unsigned char *ss,
+                   const unsigned char *pk)
+{
+    uint8_t coins[NTRUPLUS_N / 8];
+    int ret;
+
+    if (randombytes(coins, sizeof coins) != 0) {
+        secure_clear(coins, sizeof coins);
+        return -1;
+    }
+    ret = crypto_kem_enc_derand(ct, ss, pk, coins);
+    secure_clear(coins, sizeof coins);
+    return ret;
+}
+
+/*************************************************
+* Name:        crypto_kem_dec
+*
+* Description: Performs NTRU+ KEM decapsulation. Given a ciphertext ct
+*              and a secret key sk, computes the shared secret ss. If
+*              decapsulation fails, ss is set to all zero bytes.
+*
+* Arguments:   - uint8_t *ss:  output shared secret
+*                (array of CRYPTO_BYTES bytes)
+*              - const uint8_t *ct: input ciphertext
+*                (array of CRYPTO_CIPHERTEXTBYTES bytes)
+*              - const uint8_t *sk: input secret key
+*                (array of CRYPTO_SECRETKEYBYTES bytes)
+*
+* Returns 0 on success, 1 on failure.
+**************************************************/
+int crypto_kem_dec(unsigned char *ss, const unsigned char *ct,
+                   const unsigned char *sk)
+{
+	uint8_t msg[HASH_H_INBYTES];
+	uint8_t buf1[HASH_G_INBYTES];
+	uint8_t buf2[HASH_G_INBYTES];
+	uint8_t buf3[HASH_H_OUTBYTES];
+
+    int8_t fail = 1;
+    
+    poly c, f, hinv;
+    poly r1, r2;
+    poly m1, m2;
+
+    if(poly_frombytes(&c, ct) ||
+       poly_frombytes(&f, sk) ||
+       poly_frombytes(&hinv, sk + NTRUPLUS_POLYBYTES))
+    {
+        secure_clear(ss, NTRUPLUS_SSBYTES);
+
+        goto cleanup;
+    }
+    
+    poly_basemul(&m1, &c, &f);
+    poly_invntt(&m1);
+    poly_crepmod3(&m1);
+    
+    m2 = m1;
+    poly_ntt(&m2);
+    poly_sub(&c, &c, &m2);
+    poly_basemul(&r2, &c, &hinv);
+
+    poly_tobytes(buf1, &r2);
+    hash_g(buf2, buf1);
+    fail = poly_sotp_decode(msg, &m1, buf2);
+    
+    for (size_t i = 0; i < HASH_F_OUTBYTES; i++)
+        msg[i + NTRUPLUS_N / 8] = sk[i + 2 * NTRUPLUS_POLYBYTES];
+    
+    hash_h(buf3, msg);
+    
+    poly_cbd1(&r1, buf3 + NTRUPLUS_SSBYTES);
+    poly_ntt(&r1);
+    poly_tobytes(buf2, &r1);
+    
+    fail |= verify(buf1, buf2, NTRUPLUS_POLYBYTES);
+    
+    for (size_t i = 0; i < NTRUPLUS_SSBYTES; i++)
+        ss[i] = buf3[i] & ~(-fail);
+
+cleanup:
+    secure_clear(msg, sizeof msg);
+    secure_clear(buf1, sizeof buf1);
+    secure_clear(buf2, sizeof buf2);
+    secure_clear(buf3, sizeof buf3);
+    secure_clear(&c, sizeof c);
+    secure_clear(&f, sizeof f);
+    secure_clear(&hinv, sizeof hinv);
+    secure_clear(&r1, sizeof r1);
+    secure_clear(&r2, sizeof r2);
+    secure_clear(&m1, sizeof m1);
+    secure_clear(&m2, sizeof m2);
+
+    return fail;
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/ntt.c b/cbits/third_party/NTRUplus/NTRU+1152/ntt.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/ntt.c
@@ -0,0 +1,390 @@
+#include <stdint.h>
+#include "params.h"
+#include "ntt.h"
+
+#define NTRUPLUS_R            -147 // R = 2^16 mod q
+#define NTRUPLUS_RINV         -682 // (R)^(-1) mod q
+#define NTRUPLUS_RSQ           867 // (R^2) mod q
+#define NTRUPLUS_QINV        12929 // (q)^(-1) mod (2^16)
+
+#define NTRUPLUS_OMEGA        -886 // (omega * R) mod q
+#define NTRUPLUS_ZMINUSZ5INV -1665 // (z - z^5)^(-1) * R mod q
+                                   // where z = zeta^((n/d)/6)
+
+#define NTRUPLUS_NINV        -1693 // (n/d)^(-1) * R mod q
+#define NTRUPLUS_2NINV          71 // 2 * (n/d)^(-1) * R mod q
+
+// zetas: Montgomery-form twiddle factors
+const int16_t zetas[288] = {
+	 -147, -1033, -1265,   708,   460,  1265,  -467,   727,
+	  556,  1307,  -773,  -161,  1200, -1612,   570,  1529,
+	 1135,  -556,  1120,   298,  -822, -1556,   -93,  1463,
+	  532,  -377,  -909,    58,  -392,  -450,  1722,  1236,
+	 -486,  -491, -1569, -1078,    36,  1289, -1443,  1628,
+	 1664,  -725,  -952,    99, -1020,   353,  -599,  1119,
+	  592,   839,  1622,   652,  1244,  -783, -1085,  -726,
+	  566,  -284, -1369, -1292,   268,  -391,   781,  -172,
+	   96, -1172,   211,   737,   473,  -445,  -234,   264,
+	-1536,  1467,  -676, -1542,  -170,   635,  -705, -1332,
+	 -658,   831, -1712,  1311,  1488,  -881,  1087, -1315,
+	 1245,   -75,   791,    -6,  -875,  -697,   -70, -1162,
+	  287,  -767,  -945,  1598,  -882,  1261,   206,   654,
+	-1421,   -81,   716, -1251,   838, -1300,  1035,  -104,
+	  966,  -558,   -61, -1704,   404,  -899,   862, -1593,
+	-1460,   -37,  1266,   965, -1584, -1404,  -265,  -942,
+	  905,  1195,  -619,   787,   118,   576,   286, -1475,
+	 -194,   928,  1229, -1032,  1608,  1111, -1669,   642,
+	-1323,   163,   309,   981,  -557,  -258,   232, -1680,
+	-1657, -1233,   144,  1699,   311, -1060,   578,  1298,
+	 -403,  1607,  1074,  -148,   447, -1568,  1142,  -402,
+	-1412,  -623,   855,   365,   -98,  -244,   407,  1225,
+	  416,   683,  -105,  1714, -1019,  1061,  1163,   638,
+	  798,  1493,  -351,   396,  -542,    -9,  1616,  -139,
+	 -987,  -482,   889,   238, -1513,   466, -1089,  -101,
+	  849,  -426,  1589,  1487,   671,  1459,  -776,   255,
+	-1014,  1144,   472, -1153,  -325,  1519,   -26, -1123,
+	  324,  1230,  1547,  -593,  -428,  1192,  1072, -1564,
+	  688,  -333,  1023, -1686,   841,   824,   -71,  1587,
+	  522,  -323,  1148,   389,  1231,   384,  1343,   169,
+	  628, -1329, -1056,  -936,    24,  -293,  1523,  -300,
+	-1654,   891,  -962,   -67,   179, -1177,   844,  -509,
+	-1677, -1565,  -549, -1508,  1191,  -280,   -43,   669,
+	 -746,   753,   770, -1046,  1711,  1438,   690,  1083,
+	 1062,  1727,  -883,   553,  1670,    66,   825,  -133,
+	-1586,   637,  -680,  -917,   644,  -372, -1193, -1136
+};
+
+/*************************************************
+* Name:        montgomery_reduce
+*
+* Description: Montgomery reduction; given a 32-bit integer a, computes
+*              a 16-bit integer congruent to a * R^-1 mod q,
+*              where R = 2^16.
+*
+* Arguments:   - int32_t a: input integer to be reduced;
+*                           must lie in {-q*2^15, ..., q*2^15-1}
+*
+* Returns:     an integer in {-q+1, ..., q-1} congruent to
+*              a * R^-1 mod q.
+**************************************************/
+static inline int16_t montgomery_reduce(int32_t a)
+{
+	int16_t t;
+	
+	t = (int16_t)a * NTRUPLUS_QINV;
+	t = (a - (int32_t)t * NTRUPLUS_Q) >> 16;
+	return t;
+}
+
+/*************************************************
+* Name:        barrett_reduce
+*
+* Description: Barrett reduction; given a 16-bit integer a, computes a
+*              centered representative congruent to a mod q in
+*              {-(q+1)/2, ..., (q+1)/2}.
+*
+* Arguments:   - int16_t a: input integer to be reduced
+*
+* Returns:     integer in {-(q+1)/2, ..., (q+1)/2} congruent to a mod q.
+**************************************************/
+static inline int16_t barrett_reduce(int16_t a)
+{
+	int16_t t;
+	const int16_t v = ((1<<26) + NTRUPLUS_Q/2) / NTRUPLUS_Q;
+	
+	t  = ((int32_t)v*a + (1<<25)) >> 26;
+	t *= NTRUPLUS_Q;
+	return a - t;
+}
+
+/*************************************************
+* Name:        fqmul
+*
+* Description: Multiplication followed by Montgomery reduction.
+*
+* Arguments:   - int16_t a: first factor
+*              - int16_t b: second factor
+*
+* Returns:     16-bit integer congruent to a*b*R^-1 mod q.
+**************************************************/
+static inline int16_t fqmul(int16_t a, int16_t b)
+{
+    return montgomery_reduce((int32_t)a * b);
+}
+
+/*************************************************
+* Name:        fqinv
+*
+* Description: Computes the multiplicative inverse in Z_q using Fermat's
+*              little theorem. The exponent q-2 = 3455 is evaluated using
+*              a shortest addition chain of length 15 (OEIS A003313).
+*
+* Arguments:   - int16_t a: input value a mod q
+*
+* Returns:     16-bit integer congruent to a^{-1} mod q.
+**************************************************/
+static inline int16_t fqinv(int16_t a)
+{
+	int16_t t0, t1;
+
+	t0 = fqmul(a, a);    // 10
+	t0 = fqmul(t0, t0);  // 100
+	t0 = fqmul(t0, t0);  // 1000
+	t0 = fqmul(t0, t0);  // 10000
+	t1 = fqmul(t0, a);   // 10001
+
+	t0 = fqmul(t0, t0);  // 100000
+	t0 = fqmul(t0, t0);  // 1000000
+	t0 = fqmul(t0, t0);  // 10000000
+	t1 = fqmul(t0, t1);  // 10010001
+
+	t0 = fqmul(t1, t0);  // 100010001
+	t1 = fqmul(t0, t1);  // 110100010
+	t1 = fqmul(t1, t0);  // 1010110011
+	t0 = fqmul(t1, t1);  // 10101100110
+	t0 = fqmul(t0, t0);  // 101011001100
+	t0 = fqmul(t0, t1);  // 110101111111
+
+	return fqmul(NTRUPLUS_RINV, t0);
+}
+
+/*************************************************
+* Name:        ntt
+*
+* Description: Number-theoretic transform (NTT) in R_q. Transforms the
+*              coefficient representation of r into a representation
+*              where each block of 4 coefficients corresponds to an
+*              element of Zq[X]/(X^4 - zeta_i).
+*
+* Arguments:   - int16_t r[NTRUPLUS_N]: pointer to input/output vector;
+*                                       input coefficients of r in R_q,
+*                                       output NTT representation in the
+*                                       product ring Zq[X]/(X^4 - zeta_i)
+*
+* Returns:     none.
+**************************************************/
+void ntt(int16_t r[NTRUPLUS_N])
+{
+	int16_t t1, t2, t3;
+	int16_t zeta1, zeta2;
+	
+	int k = 1;
+
+	zeta1 = zetas[k++];
+
+	for (int i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t1 = fqmul(zeta1, r[i + NTRUPLUS_N/2]);
+
+		r[i + NTRUPLUS_N/2] = r[i] + r[i + NTRUPLUS_N/2] - t1;
+		r[i               ] = r[i]                       + t1;
+	}
+
+	for (int step = NTRUPLUS_N/6; step >= 64; step = step/3)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += 3*step)
+		{
+			zeta1 = zetas[k++];
+			zeta2 = zetas[k++];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = fqmul(zeta1, r[i +   step]);
+				t2 = fqmul(zeta2, r[i + 2*step]);
+				t3 = fqmul(NTRUPLUS_OMEGA, t1 - t2);
+
+				r[i + 2*step] = r[i] - t1 - t3;
+				r[i +   step] = r[i] - t2 + t3;
+				r[i         ] = r[i] + t1 + t2;
+			}		
+		}
+	}
+
+	for (int step = 32; step >= 4; step >>= 1)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += (step << 1))
+		{
+			zeta1 = zetas[k++];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = fqmul(zeta1, r[i + step]);
+				
+				r[i + step] = barrett_reduce(r[i] - t1);
+				r[i       ] = barrett_reduce(r[i] + t1);
+			}
+		}
+	}
+}
+
+/*************************************************
+* Name:        invntt
+*
+* Description: Inverse number-theoretic transform (NTT) in R_q. Transforms
+*              the NTT representation in r, where each block of 4
+*              coefficients corresponds to an element of Zq[X]/(X^4 - zeta_i),
+*              back to the coefficient representation in R_q.
+*
+* Arguments:   - int16_t r[NTRUPLUS_N]: pointer to input/output vector;
+*                                       input NTT representation in the
+*                                       product ring Zq[X]/(X^4 - zeta_i),
+*                                       output coefficient representation
+*                                       in R_q
+*
+* Returns:     none.
+**************************************************/
+void invntt(int16_t r[NTRUPLUS_N])
+{
+	int16_t t1, t2, t3;
+	int16_t zeta1, zeta2;
+	int k = 287;
+
+	for (int step = 4; step <= 32; step <<= 1)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += (step << 1))
+		{
+			zeta1 = zetas[k--];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = r[i + step];
+
+				r[i + step] = fqmul(zeta1, t1 - r[i]);
+				r[i       ] = barrett_reduce(r[i] + t1);
+			}
+		}
+	}
+
+	for (int step = 64; step <= NTRUPLUS_N/6; step = 3*step)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += 3*step)
+		{
+			zeta2 = zetas[k--];
+			zeta1 = zetas[k--];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = fqmul(NTRUPLUS_OMEGA, r[i +   step] - r[i]);
+				t2 = fqmul(zeta1, r[i + 2*step] - r[i]        + t1);
+				t3 = fqmul(zeta2, r[i + 2*step] - r[i + step] - t1);
+				
+				r[i         ] = barrett_reduce(r[i] + r[i + step] + r[i + 2*step]);
+				r[i +   step] = t2;			
+				r[i + 2*step] = t3;
+			}
+		}
+	}
+
+	for (int i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t1 = r[i] + r[i + NTRUPLUS_N/2];
+		t2 = fqmul(NTRUPLUS_ZMINUSZ5INV, r[i] - r[i + NTRUPLUS_N/2]);
+
+		r[i               ] = fqmul(NTRUPLUS_NINV, t1 - t2);
+		r[i + NTRUPLUS_N/2] = fqmul(NTRUPLUS_2NINV, t2);
+	}
+}
+
+/*************************************************
+* Name:        baseinv
+*
+* Description: Inversion of a polynomial in Zq[X]/(X^4 - zeta), used as
+*              a building block for inversion of elements in R_q in the
+*              NTT domain.
+*
+* Arguments:   - int16_t r[4]:        pointer to the output polynomial
+*              - const int16_t a[4]:  pointer to the input polynomial
+*              - const int16_t zeta:  parameter defining X^4 - zeta
+*
+* Returns:     0 if a is invertible, 1 otherwise.
+**************************************************/
+int baseinv(int16_t r[4], const int16_t a[4], const int16_t zeta)
+{
+	int16_t t0, t1, t2, t3;
+	
+	t0 = montgomery_reduce(a[2]*a[2] - 2*a[1]*a[3]);            // R^-1
+	t1 = montgomery_reduce(a[3]*a[3]);                          // R^-1
+	t0 = montgomery_reduce(a[0]*a[0] + t0*zeta);                // R^-1
+	t1 = montgomery_reduce(a[1]*a[1] + t1*zeta - 2*a[0]*a[2]);  // R^-1
+	t2 = montgomery_reduce(t1*zeta);                            // R^-1
+	
+	t3 = montgomery_reduce(t0*t0 - t1*t2);  // R^-3
+
+	if (t3 == 0) return 1;
+
+	r[0] = montgomery_reduce(a[0]*t0 + a[2]*t2); // R^-2
+	r[1] = montgomery_reduce(a[3]*t2 + a[1]*t0); // R^-2
+	r[2] = montgomery_reduce(a[2]*t0 + a[0]*t1); // R^-2
+	r[3] = montgomery_reduce(a[1]*t1 + a[3]*t0); // R^-2
+
+	t3 = fqinv(t3); // R^5
+
+	r[0] =  montgomery_reduce(r[0]*t3); // R^0
+	r[1] = -montgomery_reduce(r[1]*t3); // R^0
+	r[2] =  montgomery_reduce(r[2]*t3); // R^0
+	r[3] = -montgomery_reduce(r[3]*t3); // R^0
+
+	return 0;
+}
+
+/*************************************************
+* Name:        basemul
+*
+* Description: Multiplication of polynomials in Zq[X]/(X^4 - zeta),
+*              used for multiplication of elements in R_q in the NTT domain.
+*
+* Arguments:   - int16_t r[4]:        pointer to the output polynomial
+*              - const int16_t a[4]:  pointer to the first factor
+*              - const int16_t b[4]:  pointer to the second factor
+*              - const int16_t zeta:  parameter defining X^4 - zeta
+*
+* Returns:     none.
+**************************************************/
+void basemul(int16_t r[4], const int16_t a[4], const int16_t b[4], const int16_t zeta)
+{
+	int16_t t0 = montgomery_reduce(a[1]*b[3]+a[2]*b[2]+a[3]*b[1]); // R^-1
+	int16_t t1 = montgomery_reduce(a[2]*b[3]+a[3]*b[2]);           // R^-1
+	int16_t t2 = montgomery_reduce(a[3]*b[3]);                     // R^-1
+
+	int16_t c0 = montgomery_reduce(t0*zeta+a[0]*b[0]);                       // R^-1
+	int16_t c1 = montgomery_reduce(t1*zeta+a[0]*b[1]+a[1]*b[0]);             // R^-1
+	int16_t c2 = montgomery_reduce(t2*zeta+a[0]*b[2]+a[1]*b[1]+a[2]*b[0]);   // R^-1
+	int16_t c3 = montgomery_reduce(a[0]*b[3]+a[1]*b[2]+a[2]*b[1]+a[3]*b[0]); // R^-1
+
+	r[0] = montgomery_reduce(c0*NTRUPLUS_RSQ); // R^0
+	r[1] = montgomery_reduce(c1*NTRUPLUS_RSQ); // R^0
+	r[2] = montgomery_reduce(c2*NTRUPLUS_RSQ); // R^0
+	r[3] = montgomery_reduce(c3*NTRUPLUS_RSQ); // R^0
+}
+
+/*************************************************
+* Name:        basemul_add
+*
+* Description: Multiplication then addition of polynomials in
+*              Zq[X]/(X^4 - zeta), used for multiplication of
+*              elements in R_q in the NTT domain.
+*
+* Arguments:   - int16_t r[4]:        pointer to the output polynomial
+*              - const int16_t a[4]:  pointer to the first factor
+*              - const int16_t b[4]:  pointer to the second factor
+*              - const int16_t c[4]:  pointer to the third factor
+*              - const int16_t zeta:  parameter defining X^4 - zeta
+*
+* Returns:     none.
+**************************************************/
+void basemul_add(int16_t r[4], const int16_t a[4], const int16_t b[4], const int16_t c[4], const int16_t zeta)
+{
+	int16_t t0 = montgomery_reduce(a[1]*b[3]+a[2]*b[2]+a[3]*b[1]); // R^-1
+	int16_t t1 = montgomery_reduce(a[2]*b[3]+a[3]*b[2]);           // R^-1
+	int16_t t2 = montgomery_reduce(a[3]*b[3]);                     // R^-1
+
+	int16_t c0 = montgomery_reduce(t0*zeta+a[0]*b[0]);                       // R^-1
+	int16_t c1 = montgomery_reduce(t1*zeta+a[0]*b[1]+a[1]*b[0]);             // R^-1
+	int16_t c2 = montgomery_reduce(t2*zeta+a[0]*b[2]+a[1]*b[1]+a[2]*b[0]);   // R^-1
+	int16_t c3 = montgomery_reduce(a[0]*b[3]+a[1]*b[2]+a[2]*b[1]+a[3]*b[0]); // R^-1
+
+	r[0] = montgomery_reduce(c[0]*NTRUPLUS_R + c0*NTRUPLUS_RSQ); // R^0
+	r[1] = montgomery_reduce(c[1]*NTRUPLUS_R + c1*NTRUPLUS_RSQ); // R^0
+	r[2] = montgomery_reduce(c[2]*NTRUPLUS_R + c2*NTRUPLUS_RSQ); // R^0
+	r[3] = montgomery_reduce(c[3]*NTRUPLUS_R + c3*NTRUPLUS_RSQ); // R^0
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/ntt.h b/cbits/third_party/NTRUplus/NTRU+1152/ntt.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/ntt.h
@@ -0,0 +1,16 @@
+#ifndef NTT_H
+#define NTT_H
+
+#include <stdint.h>
+#include "params.h"
+
+extern const int16_t zetas[288];
+
+void ntt(int16_t r[NTRUPLUS_N]);
+void invntt(int16_t r[NTRUPLUS_N]);
+
+int  baseinv(int16_t r[4], const int16_t a[4], int16_t zeta);
+void basemul(int16_t r[4], const int16_t a[4], const int16_t b[4], int16_t zeta);
+void basemul_add(int16_t r[4], const int16_t a[4], const int16_t b[4], const int16_t c[4], int16_t zeta);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/params.h b/cbits/third_party/NTRUplus/NTRU+1152/params.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/params.h
@@ -0,0 +1,18 @@
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define NTRUPLUS_ALGNAME "NTRU+1152"
+
+#define NTRUPLUS_N 1152
+#define NTRUPLUS_Q 3457
+#define NTRUPLUS_D 4
+
+#define NTRUPLUS_SYMBYTES  32   /* size in bytes of hashes, and seeds */
+#define NTRUPLUS_SSBYTES   32   /* size in bytes of shared key */
+#define NTRUPLUS_POLYBYTES 1728
+
+#define NTRUPLUS_PUBLICKEYBYTES  NTRUPLUS_POLYBYTES
+#define NTRUPLUS_SECRETKEYBYTES  ((NTRUPLUS_POLYBYTES << 1) + NTRUPLUS_SYMBYTES)
+#define NTRUPLUS_CIPHERTEXTBYTES  NTRUPLUS_POLYBYTES
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/poly.c b/cbits/third_party/NTRUplus/NTRU+1152/poly.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/poly.c
@@ -0,0 +1,326 @@
+#include <stdint.h>
+#include <string.h>
+#include "params.h"
+#include "poly.h"
+#include "ntt.h"
+#include "symmetric.h"
+
+/*************************************************
+* Name:        crepmod3
+*
+* Description: Compute modulus 3 operation
+*
+* Arguments: - poly *a: pointer to intput integer to be reduced
+*
+* Returns:     integer in {-1,0,1} congruent to a modulo 3.
+**************************************************/
+static inline int16_t crepmod3(int16_t a)
+{
+	int16_t t;
+	const int16_t v = ((1<<15) + 3/2)/3;
+
+	a += (a >> 15) & NTRUPLUS_Q;
+	a -= (NTRUPLUS_Q+1)/2;
+	a += (a >> 15) & NTRUPLUS_Q;
+	a -= (NTRUPLUS_Q-1)/2;
+
+	t  = ((int32_t)v*a + (1<<14)) >> 15;
+	t *= 3;
+	return a - t;
+}
+
+/*************************************************
+* Name:        poly_tobytes
+*
+* Description: Serialization of a polynomial
+*
+* Arguments:   - uint8_t *r: pointer to output byte array
+*                            (needs space for NTRUPLUS_POLYBYTES bytes)
+*              - poly *a:    pointer to input polynomial
+**************************************************/
+void poly_tobytes(uint8_t r[NTRUPLUS_POLYBYTES], const poly *a)
+{
+	int16_t t[2];
+
+	for(size_t i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t[0] = a->coeffs[2*i];
+		t[0] += (t[0] >> 15) & NTRUPLUS_Q;
+		t[1] = a->coeffs[2*i+1];
+		t[1] += (t[1] >> 15) & NTRUPLUS_Q;
+
+		r[3*i+0] = (t[0] >> 0);
+		r[3*i+1] = (t[0] >> 8) | (t[1] << 4);
+		r[3*i+2] = (t[1] >> 4);
+	}
+}
+
+
+/*************************************************
+* Name:        poly_frombytes
+*
+* Description: De-serialization of a polynomial;
+*              inverse of poly_tobytes
+*
+* Arguments:   - poly *r:          pointer to output polynomial
+*              - const uint8_t *a: pointer to input byte array
+*                                  (of NTRUPLUS_POLYBYTES bytes)
+*
+* Returns 0 on success, 1 if any coefficient is greater than or equal to q.
+**************************************************/
+int poly_frombytes(poly *r, const uint8_t a[NTRUPLUS_POLYBYTES])
+{
+	uint32_t fail = 0;
+
+	for(size_t i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		const uint16_t t0 = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF;
+		const uint16_t t1 = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF;
+
+		r->coeffs[2*i]   = t0;
+		r->coeffs[2*i+1] = t1;
+		fail |= (uint32_t)(NTRUPLUS_Q - 1) - t0;
+		fail |= (uint32_t)(NTRUPLUS_Q - 1) - t1;
+	}
+
+	return fail >> 31;
+}
+
+/*************************************************
+* Name:        poly_cbd1
+*
+* Description: Sample a polynomial deterministically from a random,
+*              with output polynomial close to centered binomial distribution
+*
+* Arguments:   - poly *r: pointer to output polynomial
+*              - const uint8_t *buf: pointer to input random
+*                                     (of length NTRUPLUS_N/4 bytes)
+**************************************************/
+void poly_cbd1(poly *r, const unsigned char buf[NTRUPLUS_N/4])
+{
+	uint8_t t1, t2;
+
+	for(size_t i = 0; i < NTRUPLUS_N / 8; i++)
+	{
+		t1 = buf[i];
+		t2 = buf[i + NTRUPLUS_N / 8];
+
+		for(size_t j = 0; j < 8; j++)
+		{
+			r->coeffs[8*i + j] = (t1 & 0x1) - (t2 & 0x1);
+
+			t1 >>= 1;   
+			t2 >>= 1;
+		}
+	}
+}
+
+/*************************************************
+* Name:        poly_sotp_encode
+*
+* Description: Encode a message deterministically using SOTP and a random,
+			   with output polynomial close to centered binomial distribution
+*
+* Arguments:   - poly *r: pointer to output polynomial
+*              - const uint8_t *msg: pointer to input message
+*              - const uint8_t *buf: pointer to input random
+**************************************************/
+void poly_sotp_encode(poly *r, const uint8_t msg[NTRUPLUS_N/8], const uint8_t buf[NTRUPLUS_N/4])
+{
+    uint8_t tmp[NTRUPLUS_N/4];
+
+    for(int i = 0; i < NTRUPLUS_N/8; i++)
+    {
+         tmp[i] = buf[i]^msg[i];
+    }
+
+    for(int i = NTRUPLUS_N/8; i < NTRUPLUS_N/4; i++)
+    {
+         tmp[i] = buf[i];
+    }
+
+	poly_cbd1(r, tmp);
+}
+
+/*************************************************
+* Name:        poly_sotp_decode
+*
+* Description: Decode a message deterministically using SOTP_INV and a random
+*
+* Arguments:   - uint8_t *msg: pointer to output message
+*              - const poly *a: pointer to iput polynomial
+*              - const uint8_t *buf: pointer to input random
+*
+* Returns 0 (success) or 1 (failure)
+**************************************************/
+int poly_sotp_decode(uint8_t msg[NTRUPLUS_N/8], const poly *a, const uint8_t buf[NTRUPLUS_N/4])
+{
+	uint8_t t1, t2, t3;
+	uint16_t t4;
+	uint32_t r = 0;
+	volatile uint8_t mask;
+
+	for(size_t i = 0; i < NTRUPLUS_N / 8; i++)
+	{
+		t1 = buf[i                 ];
+		t2 = buf[i + NTRUPLUS_N / 8];
+		t3 = 0;
+
+		for(size_t j = 0; j < 8; j++)
+		{
+			t4 = t2 & 0x1;
+			t4 += a->coeffs[8*i + j];
+			r |= t4;
+			t4 = (t4 ^ t1) & 0x1;
+			t3 ^= (uint8_t)(t4 << j);
+
+			t1 >>= 1;
+			t2 >>= 1;
+		}
+
+		msg[i] = t3;
+	}
+
+	r = r >> 1;
+	r = (-(uint32_t)r) >> 31;
+
+	mask = (uint8_t)(r - 1);
+
+	for (size_t i = 0; i < NTRUPLUS_N / 8; i++)
+		msg[i] &= mask;
+
+	return r;
+}
+
+/*************************************************
+* Name:        poly_ntt
+*
+* Description: Computes number-theoretic transform (NTT)
+*
+* Arguments:   - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_ntt(poly *r)
+{
+	ntt(r->coeffs);
+}
+
+/*************************************************
+* Name:        poly_invntt
+*
+* Description: Computes inverse of number-theoretic transform (NTT)
+*
+* Arguments:   - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_invntt(poly *r)
+{
+	invntt(r->coeffs);
+}
+
+/*************************************************
+* Name:        poly_baseinv
+*
+* Description: Inverse of polynomial in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to input polynomial
+**************************************************/
+int poly_baseinv(poly *r, const poly *a)
+{
+	for(size_t i = 0; i < NTRUPLUS_N/8; ++i)
+	{
+		if(baseinv(r->coeffs + 8*i, a->coeffs + 8*i, zetas[144 + i]))
+		{
+			for (size_t j = 0; j < NTRUPLUS_N; ++j)
+				r->coeffs[j] = 0;
+
+			return 1;
+		} 
+
+		if(baseinv(r->coeffs + 8*i + 4, a->coeffs + 8*i + 4, -zetas[144 + i]))
+		{
+			for (size_t j = 0; j < NTRUPLUS_N; ++j)
+				r->coeffs[j] = 0;
+
+			return 1;
+		} 
+	 }
+
+	return 0;
+}
+
+/*************************************************
+* Name:        poly_basemul
+*
+* Description: Multiplication of two polynomials in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to first input polynomial
+*              - const poly *b: pointer to second input polynomial
+**************************************************/
+void poly_basemul(poly *r, const poly *a, const poly *b)
+{
+	for(int i = 0; i < NTRUPLUS_N/8; ++i)
+	{
+		basemul(r->coeffs + 8*i, a->coeffs + 8*i, b->coeffs + 8*i, zetas[144 + i]);
+		basemul(r->coeffs + 8*i + 4, a->coeffs + 8*i + 4, b->coeffs + 8*i + 4, -zetas[144 + i]);
+	}
+}
+
+/*************************************************
+* Name:        poly_basemul_add
+*
+* Description: Multiplication of two polynomials in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to first input polynomial
+*              - const poly *b: pointer to second input polynomial
+**************************************************/
+void poly_basemul_add(poly *r, const poly *a, const poly *b, const poly *c)
+{
+	for(int i = 0; i < NTRUPLUS_N/8; ++i)
+	{
+		basemul_add(r->coeffs + 8*i, a->coeffs + 8*i, b->coeffs + 8*i, c->coeffs + 8*i, zetas[144 + i]);
+		basemul_add(r->coeffs + 8*i + 4, a->coeffs + 8*i + 4, b->coeffs + 8*i + 4, c->coeffs + 8*i + 4, -zetas[144 + i]);
+	}
+}
+
+/*************************************************
+* Name:        poly_sub
+*
+* Description: Subtract two polynomials; no modular reduction is performed
+*
+* Arguments: - poly *r: pointer to output polynomial
+*            - const poly *a: pointer to first input polynomial
+*            - const poly *b: pointer to second input polynomial
+**************************************************/
+void poly_sub(poly *r, const poly *a, const poly *b)
+{
+	for(int i = 0; i < NTRUPLUS_N; ++i)
+		r->coeffs[i] = a->coeffs[i] - b->coeffs[i];
+}
+
+/*************************************************
+* Name:        poly_triple
+*
+* Description: Multiply polynomial by 3; no modular reduction is performed
+*
+* Arguments: - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_triple(poly *r)
+{
+	for(int i = 0; i < NTRUPLUS_N; ++i)
+		r->coeffs[i] *= 3;
+}
+
+/*************************************************
+* Name:        poly_crepmod3
+*
+* Description: Compute modulus 3 operation to polynomial
+*
+* Arguments: - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_crepmod3(poly *r)
+{
+	for(int i = 0; i < NTRUPLUS_N; i++)
+		r->coeffs[i] = crepmod3(r->coeffs[i]);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/poly.h b/cbits/third_party/NTRUplus/NTRU+1152/poly.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/poly.h
@@ -0,0 +1,31 @@
+#ifndef POLY_H
+#define POLY_H
+
+#include <stdint.h>
+#include "params.h"
+
+/*
+ * Elements of R_q = Z_q[X]/(X^n - X^n/2 + 1). Represents polynomial
+ * coeffs[0] + X*coeffs[1] + X^2*xoeffs[2] + ... + X^{n-1}*coeffs[n-1]
+ */
+typedef struct{
+	int16_t coeffs[NTRUPLUS_N];
+} poly;
+
+void poly_tobytes(uint8_t r[NTRUPLUS_POLYBYTES], const poly *a);
+int  poly_frombytes(poly *r, const uint8_t a[NTRUPLUS_POLYBYTES]);
+
+void poly_cbd1(poly *r, const uint8_t buf[NTRUPLUS_N/4]);
+void poly_sotp_encode(poly *r, const uint8_t msg[NTRUPLUS_N/8], const uint8_t buf[NTRUPLUS_N/4]);
+int  poly_sotp_decode(uint8_t msg[NTRUPLUS_N/8], const poly *a, const uint8_t buf[NTRUPLUS_N/4]);
+
+void poly_ntt(poly *r);
+void poly_invntt(poly *r);
+int  poly_baseinv(poly *r, const poly *a);
+void poly_basemul(poly *r, const poly *a, const poly *b);
+void poly_basemul_add(poly *r, const poly *a, const poly *b, const poly *c);
+void poly_sub(poly *r, const poly *a, const poly *b);
+void poly_triple(poly *r);
+void poly_crepmod3(poly *r);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/randombytes.h b/cbits/third_party/NTRUplus/NTRU+1152/randombytes.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/randombytes.h
@@ -0,0 +1,10 @@
+#ifndef RANDOMBYTES_H
+#define RANDOMBYTES_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* KpqC bindings propagate failures from the operating-system entropy source. */
+int randombytes(uint8_t *out, size_t outlen);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/symmetric.c b/cbits/third_party/NTRUplus/NTRU+1152/symmetric.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/symmetric.c
@@ -0,0 +1,33 @@
+#include "util.h"
+#include <string.h>
+#include "symmetric.h"
+#include "fips202/fips202.h"
+
+void hash_f(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_F_INBYTES];
+
+    data[0] = 0x00;
+    memcpy(data + 1, msg, HASH_F_INBYTES);
+    shake256(buf, HASH_F_OUTBYTES, data, HASH_F_INBYTES + 1);
+}
+
+void hash_g(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_G_INBYTES];
+
+    data[0] = 0x01;
+    memcpy(data + 1, msg, HASH_G_INBYTES);
+    shake256(buf, HASH_G_OUTBYTES, data, HASH_G_INBYTES + 1);
+    secure_clear(data, sizeof data);
+}
+
+void hash_h(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_H_INBYTES];
+
+    data[0] = 0x02;
+    memcpy(data + 1, msg, HASH_H_INBYTES);
+    shake256(buf, HASH_H_OUTBYTES, data, HASH_H_INBYTES + 1);
+    secure_clear(data, sizeof data);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/symmetric.h b/cbits/third_party/NTRUplus/NTRU+1152/symmetric.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/symmetric.h
@@ -0,0 +1,22 @@
+#ifndef SYMMETRIC_H
+#define SYMMETRIC_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "api.h"
+#include "params.h"
+
+#define HASH_F_INBYTES  (NTRUPLUS_POLYBYTES)
+#define HASH_F_OUTBYTES (NTRUPLUS_SYMBYTES)
+
+#define HASH_G_INBYTES  (NTRUPLUS_POLYBYTES)
+#define HASH_G_OUTBYTES (NTRUPLUS_N / 4)
+
+#define HASH_H_INBYTES  (NTRUPLUS_N / 8 + NTRUPLUS_SYMBYTES)
+#define HASH_H_OUTBYTES (NTRUPLUS_SSBYTES + NTRUPLUS_N / 4)
+
+NTRUPLUS_INTERNAL void hash_f(uint8_t *buf, const uint8_t *msg);
+NTRUPLUS_INTERNAL void hash_g(uint8_t *buf, const uint8_t *msg);
+NTRUPLUS_INTERNAL void hash_h(uint8_t *buf, const uint8_t *msg);
+
+#endif /* SYMMETRIC_H */
diff --git a/cbits/third_party/NTRUplus/NTRU+1152/util.h b/cbits/third_party/NTRUplus/NTRU+1152/util.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+1152/util.h
@@ -0,0 +1,15 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+static inline void secure_clear(void *v, size_t len)
+{
+    volatile uint8_t *p = v;
+
+    while (len-- > 0)
+        *p++ = 0;
+}
+
+#endif /* UTIL_H */
diff --git a/cbits/third_party/NTRUplus/NTRU+768/api.h b/cbits/third_party/NTRUplus/NTRU+768/api.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/api.h
@@ -0,0 +1,39 @@
+#ifndef API_H
+#define API_H
+
+#include "params.h"
+
+#ifndef NTRUPLUS_ABI_H
+#define NTRUPLUS_ABI_H
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#define NTRUPLUS_INTERNAL
+#define NTRUPLUS_EXTERNAL __declspec(dllexport)
+#define NTRUPLUS_SYSV __attribute__((sysv_abi))
+#else
+#define NTRUPLUS_INTERNAL __attribute__((visibility("hidden")))
+#define NTRUPLUS_EXTERNAL __attribute__((visibility("default")))
+#define NTRUPLUS_SYSV
+#endif
+
+#endif
+
+#define CRYPTO_SECRETKEYBYTES  NTRUPLUS_SECRETKEYBYTES
+#define CRYPTO_PUBLICKEYBYTES  NTRUPLUS_PUBLICKEYBYTES
+#define CRYPTO_CIPHERTEXTBYTES NTRUPLUS_CIPHERTEXTBYTES
+#define CRYPTO_BYTES           NTRUPLUS_SSBYTES
+
+#define CRYPTO_ALGNAME NTRUPLUS_ALGNAME
+
+NTRUPLUS_EXTERNAL int crypto_kem_keypair(unsigned char *pk,
+                                         unsigned char *sk);
+
+NTRUPLUS_EXTERNAL int crypto_kem_enc(unsigned char *ct,
+                                     unsigned char *ss,
+                                     const unsigned char *pk);
+
+NTRUPLUS_EXTERNAL int crypto_kem_dec(unsigned char *ss,
+                                     const unsigned char *ct,
+                                     const unsigned char *sk);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+768/fips202/fips202.c b/cbits/third_party/NTRUplus/NTRU+768/fips202/fips202.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/fips202/fips202.c
@@ -0,0 +1,621 @@
+/* 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 "util.h"
+
+#include <stddef.h>
+#include <stdint.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., 136 for SHAKE256)
+ *              - 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);
+    }
+    secure_clear(t, sizeof t);
+}
+
+/*************************************************
+ * 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., 136 for SHAKE256)
+ **************************************************/
+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., 136 for SHAKE256)
+ *              - 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., 136 for SHAKE256)
+ *              - 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., 136 for SHAKE256)
+ **************************************************/
+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 shake256_inc_init(shake256incctx *state) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+void shake256_inc_ctx_release(shake256incctx *state) {
+    secure_clear(state->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+/*************************************************
+ * 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) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/** Clear the state. */
+void shake256_ctx_release(shake256ctx *state) {
+    secure_clear(state->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/*************************************************
+ * 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];
+        }
+    }
+    secure_clear(t, sizeof(t));
+    shake256_ctx_release(&s);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+768/fips202/fips202.h b/cbits/third_party/NTRUplus/NTRU+768/fips202/fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/fips202/fips202.h
@@ -0,0 +1,57 @@
+#ifndef FIPS202_H
+#define FIPS202_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHAKE256_RATE 136
+
+#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[26];
+} shake256incctx;
+
+// Context for non-incremental API
+typedef struct {
+    uint64_t ctx[25];
+} shake256ctx;
+
+/* 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);
+/* Clear 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);
+/* Clear the state */
+void shake256_inc_ctx_release(shake256incctx *state);
+
+/* One-stop SHAKE256 call */
+void shake256(uint8_t *output, size_t outlen,
+              const uint8_t *input, size_t inlen);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+768/kem.c b/cbits/third_party/NTRUplus/NTRU+768/kem.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/kem.c
@@ -0,0 +1,356 @@
+#include "util.h"
+#include <stddef.h>
+#include <stdint.h>
+#include "api.h"
+#include "params.h"
+#include "symmetric.h"
+#include "poly.h"
+#include "fips202/fips202.h"
+#include "randombytes.h"
+
+#ifdef NTRUPLUS_SUPERCOP
+#include "crypto_declassify.h"
+#define ntruplus_declassify crypto_declassify
+#else
+#define ntruplus_declassify(x, xlen) ((void)(x), (void)(xlen))
+#endif
+
+/*************************************************
+* Name:        verify
+*
+* Description: Compares two byte arrays for equality in constant time.
+*
+* Arguments:   - const uint8_t *a: first byte array
+*              - const uint8_t *b: second byte array
+*              - size_t len:      length of the arrays
+*
+* Returns 0 if the arrays are equal, 1 otherwise.
+**************************************************/
+static inline int verify(const uint8_t *a, const uint8_t *b, size_t len)
+{
+    uint8_t acc = 0;
+
+    for (size_t i = 0; i < len; i++)
+        acc |= (uint8_t)(a[i] ^ b[i]);
+
+    return (-(uint64_t)acc) >> 63;
+}
+
+/*************************************************
+* Name:        genf_derand
+*
+* Description: Deterministically generates a secret polynomial f and its
+*              multiplicative inverse finv in the NTT domain.
+*
+* Arguments:   - poly *f:     output polynomial f (NTT domain)
+*              - poly *finv:  output multiplicative inverse of f
+*                              in the NTT domain
+*              - uint8_t *buf: workspace of NTRUPLUS_N/4 bytes
+*              - const uint8_t *coins: 32-byte deterministic seed
+*
+* Returns 0 on success; non-zero if f is not invertible in the NTT domain.
+**************************************************/
+static inline int genf_derand(poly *f, poly *finv,
+                              uint8_t buf[NTRUPLUS_N / 4],
+                              const uint8_t *coins)
+{
+    shake256(buf, NTRUPLUS_N / 4, coins, 32);
+
+    poly_cbd1(f, buf);
+    poly_triple(f);
+    f->coeffs[0] += 1;
+
+    poly_ntt(f);
+
+    return poly_baseinv(finv, f);
+}
+
+/*************************************************
+* Name:        geng_derand
+*
+* Description: Deterministically generates a secret polynomial g and its
+*              multiplicative inverse ginv in the NTT domain.
+*
+* Arguments:   - poly *g:      output polynomial g (NTT domain)
+*              - poly *ginv:   output multiplicative inverse of g
+*                               in the NTT domain
+*              - uint8_t *buf: workspace of NTRUPLUS_N/4 bytes
+*              - const uint8_t *coins: 32-byte deterministic seed
+*
+* Returns 0 on success; non-zero if g is not invertible in the NTT domain.
+**************************************************/
+static inline int geng_derand(poly *g, poly *ginv,
+                              uint8_t buf[NTRUPLUS_N / 4],
+                              const uint8_t *coins)
+{
+    shake256(buf, NTRUPLUS_N / 4, coins, 32);
+
+    poly_cbd1(g, buf);
+    poly_triple(g);
+
+    poly_ntt(g);
+
+    return poly_baseinv(ginv, g);
+}
+
+/*************************************************
+* Name:        crypto_kem_keypair_derand
+*
+* Description: Computes the deterministic public and secret key pair from
+*              the secret polynomials f and g and their multiplicative
+*              inverses finv and ginv in the NTT domain.
+*
+* Arguments:   - uint8_t *pk:   output public key
+*              - uint8_t *sk:   output secret key
+*              - const poly *f:     secret polynomial f (NTT domain)
+*              - const poly *finv:  multiplicative inverse of f (NTT domain)
+*              - const poly *g:     secret polynomial g (NTT domain)
+*              - const poly *ginv:  multiplicative inverse of g (NTT domain)
+*
+* Returns:     void
+**************************************************/
+static inline void crypto_kem_keypair_derand(uint8_t *pk, uint8_t *sk,
+                                             const poly *f,  const poly *finv,
+                                             const poly *g,  const poly *ginv)
+{
+    poly h, hinv;
+
+    poly_basemul(&h, g, finv);
+    poly_basemul(&hinv, f, ginv);
+
+    poly_tobytes(pk, &h);
+    poly_tobytes(sk, f);
+    poly_tobytes(sk + NTRUPLUS_POLYBYTES, &hinv);
+    hash_f(sk + 2 * NTRUPLUS_POLYBYTES, pk);
+
+    secure_clear(&h, sizeof h);
+    secure_clear(&hinv, sizeof hinv);
+}
+
+/*************************************************
+* Name:        crypto_kem_keypair
+*
+* Description: Generates an NTRU+ public/secret key pair for the
+*              CCA-secure key encapsulation mechanism. Secret
+*              polynomials f and g and their NTT-domain inverses
+*              are sampled internally using fresh randomness.
+*
+* Arguments:   - uint8_t *pk: output public key
+*                (array of CRYPTO_PUBLICKEYBYTES bytes)
+*              - uint8_t *sk: output secret key
+*                (array of CRYPTO_SECRETKEYBYTES bytes)
+*
+* Returns 0 on success.
+**************************************************/
+int crypto_kem_keypair(unsigned char *pk, unsigned char *sk)
+{
+    uint8_t coins[NTRUPLUS_SYMBYTES];
+    uint8_t buf[NTRUPLUS_N / 4];
+    int r;
+    int status = -1;
+
+    poly f, finv;
+    poly g, ginv;
+
+    do {
+        if (randombytes(coins, sizeof coins) != 0) goto cleanup;
+        r = genf_derand(&f, &finv, buf, coins);
+        ntruplus_declassify(&r, sizeof r);
+    } while (r);
+
+    do {
+        if (randombytes(coins, sizeof coins) != 0) goto cleanup;
+        r = geng_derand(&g, &ginv, buf, coins);
+        ntruplus_declassify(&r, sizeof r);
+    } while (r);
+
+    crypto_kem_keypair_derand(pk, sk, &f, &finv, &g, &ginv);
+    status = 0;
+
+cleanup:
+    secure_clear(coins, sizeof coins);
+    secure_clear(buf, sizeof buf);
+    secure_clear(&f, sizeof f);
+    secure_clear(&finv, sizeof finv);
+    secure_clear(&g, sizeof g);
+    secure_clear(&ginv, sizeof ginv);
+    return status;
+}
+
+/*************************************************
+* Name:        crypto_kem_enc_derand
+*
+* Description: Deterministically performs NTRU+ KEM encapsulation using
+*              the public key pk and the supplied randomness coins,
+*              producing a ciphertext ct and shared secret ss.
+*
+* Arguments:   - uint8_t *ct:      output ciphertext
+*              - uint8_t *ss:      output shared secret
+*              - const uint8_t *pk:    input public key
+*              - const uint8_t *coins: input randomness for
+*                                      deterministic encapsulation
+*
+* Returns 0 on success. If pk contains a non-canonical coefficient, ct and ss
+* are set to all zero bytes and 1 is returned.
+**************************************************/
+static inline int crypto_kem_enc_derand(uint8_t *ct, uint8_t *ss,
+                                        const uint8_t *pk,
+                                        const uint8_t *coins)
+{
+	uint8_t msg[HASH_H_INBYTES];
+	uint8_t buf1[HASH_H_OUTBYTES];
+	uint8_t buf2[HASH_G_INBYTES];
+    
+    poly c, h, r, m;
+
+    if(poly_frombytes(&h, pk))
+    {
+        for (size_t i = 0; i < NTRUPLUS_CIPHERTEXTBYTES; i++)
+            ct[i] = 0;
+        secure_clear(ss, NTRUPLUS_SSBYTES);
+
+        return 1;
+    }
+    
+    for (size_t i = 0; i < NTRUPLUS_N / 8; i++)
+        msg[i] = coins[i];
+
+    hash_f(msg + NTRUPLUS_N / 8, pk);
+    hash_h(buf1, msg);
+    
+    poly_cbd1(&r, buf1 + NTRUPLUS_SYMBYTES);
+    poly_ntt(&r);
+    
+    poly_tobytes(buf2, &r);
+    hash_g(buf2, buf2);
+    poly_sotp_encode(&m, msg, buf2);
+    poly_ntt(&m);
+    
+    poly_basemul_add(&c, &h, &r, &m);
+    poly_tobytes(ct, &c);
+    
+    for (size_t i = 0; i < NTRUPLUS_SSBYTES; i++)
+        ss[i] = buf1[i];
+
+    secure_clear(msg, sizeof msg);
+    secure_clear(buf1, sizeof buf1);
+    secure_clear(buf2, sizeof buf2);
+    secure_clear(&r, sizeof r);
+    secure_clear(&m, sizeof m);
+    
+    return 0;
+}
+
+/*************************************************
+* Name:        crypto_kem_enc
+*
+* Description: Generates an NTRU+ KEM ciphertext ct and shared secret ss
+*              using the public key pk. Fresh randomness is internally
+*              sampled and used for encapsulation.
+*
+* Arguments:   - uint8_t *ct: output ciphertext
+*                (array of CRYPTO_CIPHERTEXTBYTES bytes)
+*              - uint8_t *ss: output shared secret
+*                (array of CRYPTO_BYTES bytes)
+*              - const uint8_t *pk: input public key
+*                (array of CRYPTO_PUBLICKEYBYTES bytes)
+*
+* Returns 0 on success. If pk contains a non-canonical coefficient, ct and ss
+* are set to all zero bytes and 1 is returned.
+**************************************************/
+int crypto_kem_enc(unsigned char *ct, unsigned char *ss,
+                   const unsigned char *pk)
+{
+    uint8_t coins[NTRUPLUS_N / 8];
+    int ret;
+
+    if (randombytes(coins, sizeof coins) != 0) {
+        secure_clear(coins, sizeof coins);
+        return -1;
+    }
+    ret = crypto_kem_enc_derand(ct, ss, pk, coins);
+    secure_clear(coins, sizeof coins);
+    return ret;
+}
+
+/*************************************************
+* Name:        crypto_kem_dec
+*
+* Description: Performs NTRU+ KEM decapsulation. Given a ciphertext ct
+*              and a secret key sk, computes the shared secret ss. If
+*              decapsulation fails, ss is set to all zero bytes.
+*
+* Arguments:   - uint8_t *ss:  output shared secret
+*                (array of CRYPTO_BYTES bytes)
+*              - const uint8_t *ct: input ciphertext
+*                (array of CRYPTO_CIPHERTEXTBYTES bytes)
+*              - const uint8_t *sk: input secret key
+*                (array of CRYPTO_SECRETKEYBYTES bytes)
+*
+* Returns 0 on success, 1 on failure.
+**************************************************/
+int crypto_kem_dec(unsigned char *ss, const unsigned char *ct,
+                   const unsigned char *sk)
+{
+	uint8_t msg[HASH_H_INBYTES];
+	uint8_t buf1[HASH_G_INBYTES];
+	uint8_t buf2[HASH_G_INBYTES];
+	uint8_t buf3[HASH_H_OUTBYTES];
+
+    int8_t fail = 1;
+    
+    poly c, f, hinv;
+    poly r1, r2;
+    poly m1, m2;
+
+    if(poly_frombytes(&c, ct) ||
+       poly_frombytes(&f, sk) ||
+       poly_frombytes(&hinv, sk + NTRUPLUS_POLYBYTES))
+    {
+        secure_clear(ss, NTRUPLUS_SSBYTES);
+
+        goto cleanup;
+    }
+    
+    poly_basemul(&m1, &c, &f);
+    poly_invntt(&m1);
+    poly_crepmod3(&m1);
+    
+    m2 = m1;
+    poly_ntt(&m2);
+    poly_sub(&c, &c, &m2);
+    poly_basemul(&r2, &c, &hinv);
+
+    poly_tobytes(buf1, &r2);
+    hash_g(buf2, buf1);
+    fail = poly_sotp_decode(msg, &m1, buf2);
+    
+    for (size_t i = 0; i < HASH_F_OUTBYTES; i++)
+        msg[i + NTRUPLUS_N / 8] = sk[i + 2 * NTRUPLUS_POLYBYTES];
+    
+    hash_h(buf3, msg);
+    
+    poly_cbd1(&r1, buf3 + NTRUPLUS_SSBYTES);
+    poly_ntt(&r1);
+    poly_tobytes(buf2, &r1);
+    
+    fail |= verify(buf1, buf2, NTRUPLUS_POLYBYTES);
+    
+    for (size_t i = 0; i < NTRUPLUS_SSBYTES; i++)
+        ss[i] = buf3[i] & ~(-fail);
+
+cleanup:
+    secure_clear(msg, sizeof msg);
+    secure_clear(buf1, sizeof buf1);
+    secure_clear(buf2, sizeof buf2);
+    secure_clear(buf3, sizeof buf3);
+    secure_clear(&c, sizeof c);
+    secure_clear(&f, sizeof f);
+    secure_clear(&hinv, sizeof hinv);
+    secure_clear(&r1, sizeof r1);
+    secure_clear(&r2, sizeof r2);
+    secure_clear(&m1, sizeof m1);
+    secure_clear(&m2, sizeof m2);
+
+    return fail;
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+768/ntt.c b/cbits/third_party/NTRUplus/NTRU+768/ntt.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/ntt.c
@@ -0,0 +1,375 @@
+#include <stdint.h>
+#include "params.h"
+#include "ntt.h"
+
+#define NTRUPLUS_R            -147 // R = 2^16 mod q
+#define NTRUPLUS_RINV         -682 // (R)^(-1) mod q
+#define NTRUPLUS_RSQ           867 // (R^2) mod q
+#define NTRUPLUS_QINV        12929 // (q)^(-1) mod (2^16)
+
+#define NTRUPLUS_OMEGA        -886 // (omega * R) mod q
+#define NTRUPLUS_ZMINUSZ5INV -1665 // (z - z^5)^(-1) * R mod q
+                                   // where z = zeta^((n/d)/6)
+
+#define NTRUPLUS_NINV         -811 // (n/d)^(-1) * R mod q
+#define NTRUPLUS_2NINV       -1622 // 2 * (n/d)^(-1) * R mod q
+
+// zetas: Montgomery-form twiddle factors
+const int16_t zetas[192] = {
+	 -147, -1033,  -682,  -248,  -708,   682,     1,  -722,
+	 -723,  -257, -1124,  -867,  -256,  1484,  1262, -1590,
+	 1611,   222,  1164, -1346,  1716, -1521,  -357,   395,
+	 -455,   639,   502,   655,  -699,   541,    95, -1577,
+	-1241,   550,   -44,    39,  -820,  -216,  -121,  -757,
+	 -348,   937,   893,   387,  -603,  1713, -1105,  1058,
+	 1449,   837,   901,  1637,  -569, -1617, -1530,  1199,
+	   50,  -830,  -625,     4,   176,  -156,  1257, -1507,
+	 -380,  -606,  1293,   661,  1428, -1580,  -565,  -992,
+	  548,  -800,    64,  -371,   961,   641,    87,   630,
+	  675,  -834,   205,    54, -1081,  1351,  1413, -1331,
+	-1673, -1267, -1558,   281, -1464,  -588,  1015,   436,
+	  223,  1138, -1059,  -397,  -183,  1655,   559, -1674,
+	  277,   933,  1723,   437, -1514,   242,  1640,   432,
+	-1583,   696,   774,  1671,   927,   514,   512,   489,
+	  297,   601,  1473,  1130,  1322,   871,   760,  1212,
+	 -312,  -352,   443,   943,     8,  1250,  -100,  1660,
+	  -31,  1206, -1341, -1247,   444,   235,  1364, -1209,
+	  361,   230,   673,   582,  1409,  1501,  1401,   251,
+	 1022, -1063,  1053,  1188,   417, -1391,   -27, -1626,
+	 1685,  -315,  1408, -1248,   400,   274, -1543,    32,
+	-1550,  1531, -1367,  -124,  1458,  1379,  -940, -1681,
+	   22,  1709,  -275,  1108,   354, -1728,  -968,   858,
+	 1221,  -218,   294,  -732, -1095,   892,  1588,  -779
+};
+
+/*************************************************
+* Name:        montgomery_reduce
+*
+* Description: Montgomery reduction; given a 32-bit integer a, computes
+*              a 16-bit integer congruent to a * R^-1 mod q,
+*              where R = 2^16.
+*
+* Arguments:   - int32_t a: input integer to be reduced;
+*                           must lie in {-q*2^15, ..., q*2^15-1}
+*
+* Returns:     an integer in {-q+1, ..., q-1} congruent to
+*              a * R^-1 mod q.
+**************************************************/
+static inline int16_t montgomery_reduce(int32_t a)
+{
+	int16_t t;
+	
+	t = (int16_t)a * NTRUPLUS_QINV;
+	t = (a - (int32_t)t * NTRUPLUS_Q) >> 16;
+	return t;
+}
+
+/*************************************************
+* Name:        barrett_reduce
+*
+* Description: Barrett reduction; given a 16-bit integer a, computes a
+*              centered representative congruent to a mod q in
+*              {-(q+1)/2, ..., (q+1)/2}.
+*
+* Arguments:   - int16_t a: input integer to be reduced
+*
+* Returns:     integer in {-(q+1)/2, ..., (q+1)/2} congruent to a mod q.
+**************************************************/
+static inline int16_t barrett_reduce(int16_t a)
+{
+	int16_t t;
+	const int16_t v = ((1<<26) + NTRUPLUS_Q/2) / NTRUPLUS_Q;
+	
+	t  = ((int32_t)v*a + (1<<25)) >> 26;
+	t *= NTRUPLUS_Q;
+	return a - t;
+}
+
+/*************************************************
+* Name:        fqmul
+*
+* Description: Multiplication followed by Montgomery reduction.
+*
+* Arguments:   - int16_t a: first factor
+*              - int16_t b: second factor
+*
+* Returns:     16-bit integer congruent to a*b*R^-1 mod q.
+**************************************************/
+static inline int16_t fqmul(int16_t a, int16_t b)
+{
+    return montgomery_reduce((int32_t)a * b);
+}
+
+
+/*************************************************
+* Name:        fqinv
+*
+* Description: Computes the multiplicative inverse in Z_q using Fermat's
+*              little theorem. The exponent q-2 = 3455 is evaluated using
+*              a shortest addition chain of length 15 (OEIS A003313).
+*
+* Arguments:   - int16_t a: input value a mod q
+*
+* Returns:     16-bit integer congruent to a^{-1} mod q.
+**************************************************/
+static inline int16_t fqinv(int16_t a)
+{
+	int16_t t0, t1;
+
+	t0 = fqmul(a, a);    // 10
+	t0 = fqmul(t0, t0);  // 100
+	t0 = fqmul(t0, t0);  // 1000
+	t0 = fqmul(t0, t0);  // 10000
+	t1 = fqmul(t0, a);   // 10001
+
+	t0 = fqmul(t0, t0);  // 100000
+	t0 = fqmul(t0, t0);  // 1000000
+	t0 = fqmul(t0, t0);  // 10000000
+	t1 = fqmul(t0, t1);  // 10010001
+
+	t0 = fqmul(t1, t0);  // 100010001
+	t1 = fqmul(t0, t1);  // 110100010
+	t1 = fqmul(t1, t0);  // 1010110011
+	t0 = fqmul(t1, t1);  // 10101100110
+	t0 = fqmul(t0, t0);  // 101011001100
+	t0 = fqmul(t0, t1);  // 110101111111
+
+	return fqmul(NTRUPLUS_RINV, t0);
+}
+
+/*************************************************
+* Name:        ntt
+*
+* Description: Number-theoretic transform (NTT) in R_q. Transforms the
+*              coefficient representation of r into a representation
+*              where each block of 4 coefficients corresponds to an
+*              element of Zq[X]/(X^4 - zeta_i).
+*
+* Arguments:   - int16_t r[NTRUPLUS_N]: pointer to input/output vector;
+*                                       input coefficients of r in R_q,
+*                                       output NTT representation in the
+*                                       product ring Zq[X]/(X^4 - zeta_i)
+*
+* Returns:     none.
+**************************************************/
+void ntt(int16_t r[NTRUPLUS_N])
+{
+	int16_t t1, t2, t3;
+	int16_t zeta1, zeta2;
+	int k = 1;
+
+	zeta1 = zetas[k++];
+
+	for (int i = 0; i < NTRUPLUS_N / 2; i++)
+	{
+		t1 = fqmul(zeta1, r[i + NTRUPLUS_N / 2]);
+
+		r[i + NTRUPLUS_N / 2] = r[i] + r[i + NTRUPLUS_N / 2] - t1;
+		r[i                 ] = r[i]                         + t1;
+	}
+
+	for (int start = 0; start < NTRUPLUS_N; start += 384)
+	{
+		zeta1 = zetas[k++];
+		zeta2 = zetas[k++];
+
+		for (int i = start; i < start + 128; i++)
+		{
+			t1 = fqmul(zeta1, r[i + 128]);
+			t2 = fqmul(zeta2, r[i + 256]);
+			t3 = fqmul(NTRUPLUS_OMEGA, t1 - t2);
+
+			r[i + 256] = r[i] - t1 - t3;
+			r[i + 128] = r[i] - t2 + t3;
+			r[i      ] = r[i] + t1 + t2;
+		}		
+	}
+
+	for (int step = 64; step >= 4; step >>= 1)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += (step << 1))
+		{
+			zeta1 = zetas[k++];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = fqmul(zeta1, r[i + step]);
+				
+				r[i + step] = r[i] - t1;
+				r[i       ] = r[i] + t1;
+			}
+		}
+	}
+
+	for (int i = 0; i < NTRUPLUS_N; i++)
+		r[i] = barrett_reduce(r[i]);
+}
+
+/*************************************************
+* Name:        invntt
+*
+* Description: Inverse number-theoretic transform (NTT) in R_q. Transforms
+*              the NTT representation in r, where each block of 4
+*              coefficients corresponds to an element of Zq[X]/(X^4 - zeta_i),
+*              back to the coefficient representation in R_q.
+*
+* Arguments:   - int16_t r[NTRUPLUS_N]: pointer to input/output vector;
+*                                       input NTT representation in the
+*                                       product ring Zq[X]/(X^4 - zeta_i),
+*                                       output coefficient representation
+*                                       in R_q
+*
+* Returns:     none.
+**************************************************/
+void invntt(int16_t r[NTRUPLUS_N])
+{
+	int16_t t1, t2, t3;
+	int16_t zeta1, zeta2;
+	int k = 191;
+
+	for (int step = 4; step <= 64; step <<= 1)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += (step << 1))
+		{
+			zeta1 = zetas[k--];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = r[i + step];
+
+				r[i + step] = fqmul(zeta1, t1 - r[i]);
+				r[i       ] = barrett_reduce(r[i] + t1);
+			}
+		}
+	}
+
+	for (int start = 0; start < NTRUPLUS_N; start += 384)
+	{
+		zeta2 = zetas[k--];
+		zeta1 = zetas[k--];
+
+		for (int i = start; i < start + 128; i++)
+		{
+			t1 = fqmul(NTRUPLUS_OMEGA, r[i + 128] - r[i]);
+			t2 = fqmul(zeta1, r[i + 256] - r[i]       + t1);
+			t3 = fqmul(zeta2, r[i + 256] - r[i + 128] - t1);
+			
+			r[i      ] = r[i] + r[i + 128] + r[i + 256];
+			r[i + 128] = t2;			
+			r[i + 256] = t3;
+		}
+	}
+
+	for (int i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t1 = r[i] + r[i + NTRUPLUS_N/2];
+		t2 = fqmul(NTRUPLUS_ZMINUSZ5INV, r[i] - r[i + NTRUPLUS_N/2]);
+
+		r[i               ] = fqmul(NTRUPLUS_NINV, t1 - t2);
+		r[i + NTRUPLUS_N/2] = fqmul(NTRUPLUS_2NINV, t2);	
+	}
+}
+
+/*************************************************
+* Name:        baseinv
+*
+* Description: Inversion of a polynomial in Zq[X]/(X^4 - zeta), used as
+*              a building block for inversion of elements in R_q in the
+*              NTT domain.
+*
+* Arguments:   - int16_t r[4]:        pointer to the output polynomial
+*              - const int16_t a[4]:  pointer to the input polynomial
+*              - const int16_t zeta:  parameter defining X^4 - zeta
+*
+* Returns:     0 if a is invertible, 1 otherwise.
+**************************************************/
+int baseinv(int16_t r[4], const int16_t a[4], const int16_t zeta)
+{
+	int16_t t0, t1, t2, t3;
+	
+	t0 = montgomery_reduce(a[2]*a[2] - 2*a[1]*a[3]);            // R^-1
+	t1 = montgomery_reduce(a[3]*a[3]);                          // R^-1
+	t0 = montgomery_reduce(a[0]*a[0] + t0*zeta);                // R^-1
+	t1 = montgomery_reduce(a[1]*a[1] + t1*zeta - 2*a[0]*a[2]);  // R^-1
+	t2 = montgomery_reduce(t1*zeta);                            // R^-1
+	
+	t3 = montgomery_reduce(t0*t0 - t1*t2);  // R^-3
+
+	if (t3 == 0) return 1;
+
+	r[0] = montgomery_reduce(a[0]*t0 + a[2]*t2); // R^-2
+	r[1] = montgomery_reduce(a[3]*t2 + a[1]*t0); // R^-2
+	r[2] = montgomery_reduce(a[2]*t0 + a[0]*t1); // R^-2
+	r[3] = montgomery_reduce(a[1]*t1 + a[3]*t0); // R^-2
+
+	t3 = fqinv(t3); // R^5
+
+	r[0] =  montgomery_reduce(r[0]*t3); // R^0
+	r[1] = -montgomery_reduce(r[1]*t3); // R^0
+	r[2] =  montgomery_reduce(r[2]*t3); // R^0
+	r[3] = -montgomery_reduce(r[3]*t3); // R^0
+
+	return 0;
+}
+
+/*************************************************
+* Name:        basemul
+*
+* Description: Multiplication of polynomials in Zq[X]/(X^4 - zeta),
+*              used for multiplication of elements in R_q in the NTT domain.
+*
+* Arguments:   - int16_t r[4]:        pointer to the output polynomial
+*              - const int16_t a[4]:  pointer to the first factor
+*              - const int16_t b[4]:  pointer to the second factor
+*              - const int16_t zeta:  parameter defining X^4 - zeta
+*
+* Returns:     none.
+**************************************************/
+void basemul(int16_t r[4], const int16_t a[4], const int16_t b[4], const int16_t zeta)
+{
+	int16_t t0 = montgomery_reduce(a[1]*b[3]+a[2]*b[2]+a[3]*b[1]); // R^-1
+	int16_t t1 = montgomery_reduce(a[2]*b[3]+a[3]*b[2]);           // R^-1
+	int16_t t2 = montgomery_reduce(a[3]*b[3]);                     // R^-1
+
+	int16_t c0 = montgomery_reduce(t0*zeta+a[0]*b[0]);                       // R^-1
+	int16_t c1 = montgomery_reduce(t1*zeta+a[0]*b[1]+a[1]*b[0]);             // R^-1
+	int16_t c2 = montgomery_reduce(t2*zeta+a[0]*b[2]+a[1]*b[1]+a[2]*b[0]);   // R^-1
+	int16_t c3 = montgomery_reduce(a[0]*b[3]+a[1]*b[2]+a[2]*b[1]+a[3]*b[0]); // R^-1
+
+	r[0] = montgomery_reduce(c0*NTRUPLUS_RSQ); // R^0
+	r[1] = montgomery_reduce(c1*NTRUPLUS_RSQ); // R^0
+	r[2] = montgomery_reduce(c2*NTRUPLUS_RSQ); // R^0
+	r[3] = montgomery_reduce(c3*NTRUPLUS_RSQ); // R^0
+}
+
+/*************************************************
+* Name:        basemul_add
+*
+* Description: Multiplication then addition of polynomials in
+*              Zq[X]/(X^4 - zeta), used for multiplication of
+*              elements in R_q in the NTT domain.
+*
+* Arguments:   - int16_t r[4]:        pointer to the output polynomial
+*              - const int16_t a[4]:  pointer to the first factor
+*              - const int16_t b[4]:  pointer to the second factor
+*              - const int16_t c[4]:  pointer to the third factor
+*              - const int16_t zeta:  parameter defining X^4 - zeta
+*
+* Returns:     none.
+**************************************************/
+void basemul_add(int16_t r[4], const int16_t a[4], const int16_t b[4], const int16_t c[4], const int16_t zeta)
+{
+	int16_t t0 = montgomery_reduce(a[1]*b[3]+a[2]*b[2]+a[3]*b[1]); // R^-1
+	int16_t t1 = montgomery_reduce(a[2]*b[3]+a[3]*b[2]);           // R^-1
+	int16_t t2 = montgomery_reduce(a[3]*b[3]);                     // R^-1
+
+	int16_t c0 = montgomery_reduce(t0*zeta+a[0]*b[0]);                       // R^-1
+	int16_t c1 = montgomery_reduce(t1*zeta+a[0]*b[1]+a[1]*b[0]);             // R^-1
+	int16_t c2 = montgomery_reduce(t2*zeta+a[0]*b[2]+a[1]*b[1]+a[2]*b[0]);   // R^-1
+	int16_t c3 = montgomery_reduce(a[0]*b[3]+a[1]*b[2]+a[2]*b[1]+a[3]*b[0]); // R^-1
+
+	r[0] = montgomery_reduce(c[0]*NTRUPLUS_R + c0*NTRUPLUS_RSQ); // R^0
+	r[1] = montgomery_reduce(c[1]*NTRUPLUS_R + c1*NTRUPLUS_RSQ); // R^0
+	r[2] = montgomery_reduce(c[2]*NTRUPLUS_R + c2*NTRUPLUS_RSQ); // R^0
+	r[3] = montgomery_reduce(c[3]*NTRUPLUS_R + c3*NTRUPLUS_RSQ); // R^0
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+768/ntt.h b/cbits/third_party/NTRUplus/NTRU+768/ntt.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/ntt.h
@@ -0,0 +1,16 @@
+#ifndef NTT_H
+#define NTT_H
+
+#include <stdint.h>
+#include "params.h"
+
+extern const int16_t zetas[192];
+
+void ntt(int16_t r[NTRUPLUS_N]);
+void invntt(int16_t r[NTRUPLUS_N]);
+
+int  baseinv(int16_t r[4], const int16_t a[4], const int16_t zeta);
+void basemul(int16_t r[4], const int16_t a[4], const int16_t b[4], const int16_t zeta);
+void basemul_add(int16_t r[4], const int16_t a[4], const int16_t b[4], const int16_t c[4], const int16_t zeta);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+768/params.h b/cbits/third_party/NTRUplus/NTRU+768/params.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/params.h
@@ -0,0 +1,18 @@
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define NTRUPLUS_ALGNAME "NTRU+768"
+
+#define NTRUPLUS_N 768
+#define NTRUPLUS_Q 3457
+#define NTRUPLUS_D 4
+
+#define NTRUPLUS_SYMBYTES  32   /* size in bytes of hashes, and seeds */
+#define NTRUPLUS_SSBYTES   32   /* size in bytes of shared key */
+#define NTRUPLUS_POLYBYTES 1152
+
+#define NTRUPLUS_PUBLICKEYBYTES  NTRUPLUS_POLYBYTES
+#define NTRUPLUS_SECRETKEYBYTES  ((NTRUPLUS_POLYBYTES << 1) + NTRUPLUS_SYMBYTES)
+#define NTRUPLUS_CIPHERTEXTBYTES  NTRUPLUS_POLYBYTES
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+768/poly.c b/cbits/third_party/NTRUplus/NTRU+768/poly.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/poly.c
@@ -0,0 +1,330 @@
+#include "util.h"
+#include <stdint.h>
+#include <string.h>
+#include "params.h"
+#include "poly.h"
+#include "ntt.h"
+#include "symmetric.h"
+
+/*************************************************
+* Name:        crepmod3
+*
+* Description: Compute modulus 3 operation
+*
+* Arguments: - poly *a: pointer to intput integer to be reduced
+*
+* Returns:     integer in {-1,0,1} congruent to a modulo 3.
+**************************************************/
+static inline int16_t crepmod3(int16_t a)
+{
+	int16_t t;
+	const int16_t v = ((1<<15) + 3/2)/3;
+
+	a += (a >> 15) & NTRUPLUS_Q;
+	a -= (NTRUPLUS_Q+1)/2;
+	a += (a >> 15) & NTRUPLUS_Q;
+	a -= (NTRUPLUS_Q-1)/2;
+
+	t  = ((int32_t)v*a + (1<<14)) >> 15;
+	t *= 3;
+	return a - t;
+}
+
+/*************************************************
+* Name:        poly_tobytes
+*
+* Description: Serialization of a polynomial
+*
+* Arguments:   - uint8_t *r: pointer to output byte array
+*                            (needs space for NTRUPLUS_POLYBYTES bytes)
+*              - poly *a:    pointer to input polynomial
+**************************************************/
+void poly_tobytes(uint8_t r[NTRUPLUS_POLYBYTES], const poly *a)
+{
+	int16_t t[2];
+
+	for(size_t i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t[0] = a->coeffs[2*i];
+		t[0] += (t[0] >> 15) & NTRUPLUS_Q;
+		t[1] = a->coeffs[2*i+1];
+		t[1] += (t[1] >> 15) & NTRUPLUS_Q;
+
+		r[3*i+0] = (t[0] >> 0);
+		r[3*i+1] = (t[0] >> 8) | (t[1] << 4);
+		r[3*i+2] = (t[1] >> 4);
+	}
+}
+
+/*************************************************
+* Name:        poly_frombytes
+*
+* Description: De-serialization of a polynomial;
+*              inverse of poly_tobytes
+*
+* Arguments:   - poly *r:          pointer to output polynomial
+*              - const uint8_t *a: pointer to input byte array
+*                                  (of NTRUPLUS_POLYBYTES bytes)
+*
+* Returns 0 on success, 1 if any coefficient is greater than or equal to q.
+**************************************************/
+int poly_frombytes(poly *r, const uint8_t a[NTRUPLUS_POLYBYTES])
+{
+	uint32_t fail = 0;
+
+	for(size_t i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		const uint16_t t0 = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF;
+		const uint16_t t1 = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF;
+
+		r->coeffs[2*i]   = t0;
+		r->coeffs[2*i+1] = t1;
+		fail |= (uint32_t)(NTRUPLUS_Q - 1) - t0;
+		fail |= (uint32_t)(NTRUPLUS_Q - 1) - t1;
+	}
+
+	return fail >> 31;
+}
+
+/*************************************************
+* Name:        poly_cbd1
+*
+* Description: Sample a polynomial deterministically from a random,
+*              with output polynomial close to centered binomial distribution
+*
+* Arguments:   - poly *r: pointer to output polynomial
+*              - const uint8_t *buf: pointer to input random
+*                                     (of length NTRUPLUS_N/4 bytes)
+**************************************************/
+void poly_cbd1(poly *r, const unsigned char buf[NTRUPLUS_N/4])
+{
+	uint8_t t1, t2;
+
+	for(size_t i = 0; i < NTRUPLUS_N / 8; i++)
+	{
+		t1 = buf[i];
+		t2 = buf[i + NTRUPLUS_N / 8];
+
+		for(size_t j = 0; j < 8; j++)
+		{
+			r->coeffs[8*i + j] = (t1 & 0x1) - (t2 & 0x1);
+
+			t1 >>= 1;   
+			t2 >>= 1;
+		}
+	}
+}
+
+/*************************************************
+* Name:        poly_sotp_encode
+*
+* Description: Encode a message deterministically using SOTP and a random,
+			   with output polynomial close to centered binomial distribution
+*
+* Arguments:   - poly *r: pointer to output polynomial
+*              - const uint8_t *msg: pointer to input message
+*              - const uint8_t *buf: pointer to input random
+**************************************************/
+void poly_sotp_encode(poly *r, const uint8_t msg[NTRUPLUS_N/8], const uint8_t buf[NTRUPLUS_N/4])
+{
+    uint8_t tmp[NTRUPLUS_N / 4];
+
+    for(int i = 0; i < NTRUPLUS_N / 8; i++)
+    {
+         tmp[i] = buf[i]^msg[i];
+    }
+
+    for(int i = NTRUPLUS_N / 8; i < NTRUPLUS_N / 4; i++)
+    {
+         tmp[i] = buf[i];
+    }
+
+	poly_cbd1(r, tmp);
+	secure_clear(tmp, sizeof tmp);
+}
+
+/*************************************************
+* Name:        poly_sotp_decode
+*
+* Description: Decode a message deterministically using SOTP_INV and a random
+*
+* Arguments:   - uint8_t *msg: pointer to output message
+*              - const poly *a: pointer to iput polynomial
+*              - const uint8_t *buf: pointer to input random
+*
+* Returns 0 (success) or 1 (failure)
+**************************************************/
+int poly_sotp_decode(uint8_t msg[NTRUPLUS_N/8], const poly *a, const uint8_t buf[NTRUPLUS_N/4])
+{
+	uint8_t t1, t2, t3;
+	uint16_t t4;
+	uint32_t r = 0;
+	volatile uint8_t mask;
+
+	for(size_t i = 0; i < NTRUPLUS_N / 8; i++)
+	{
+		t1 = buf[i                 ];
+		t2 = buf[i + NTRUPLUS_N / 8];
+		t3 = 0;
+
+		for(size_t j = 0; j < 8; j++)
+		{
+			t4 = t2 & 0x1;
+			t4 += a->coeffs[8*i + j];
+			r |= t4;
+			t4 = (t4 ^ t1) & 0x1;
+			t3 ^= (uint8_t)(t4 << j);
+
+			t1 >>= 1;
+			t2 >>= 1;
+		}
+
+		msg[i] = t3;
+	}
+
+	r = r >> 1;
+	r = (-(uint32_t)r) >> 31;
+
+	mask = (uint8_t)(r - 1);
+
+	for (size_t i = 0; i < NTRUPLUS_N / 8; i++)
+		msg[i] &= mask;
+
+	return r;
+}
+
+/*************************************************
+* Name:        poly_ntt
+*
+* Description: Computes number-theoretic transform (NTT)
+*
+* Arguments:   - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_ntt(poly *r)
+{
+	ntt(r->coeffs);
+}
+
+/*************************************************
+* Name:        poly_invntt
+*
+* Description: Computes inverse of number-theoretic transform (NTT)
+*
+* Arguments:   - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_invntt(poly *r)
+{
+	invntt(r->coeffs);
+}
+
+/*************************************************
+* Name:        poly_baseinv
+*
+* Description: Inversion of polynomial in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to input polynomial
+* 
+* Returns:     integer
+**************************************************/
+int poly_baseinv(poly *r, const poly *a)
+{
+	for(size_t i = 0; i < NTRUPLUS_N/8; ++i)
+	{
+		if(baseinv(r->coeffs + 8*i, a->coeffs + 8*i, zetas[96 + i]))
+		{
+			for (size_t j = 0; j < NTRUPLUS_N; ++j)
+				r->coeffs[j] = 0;
+
+			return 1;
+		} 
+
+		if(baseinv(r->coeffs + 8*i + 4, a->coeffs + 8*i + 4, -zetas[96 + i]))
+		{
+			for (size_t j = 0; j < NTRUPLUS_N; ++j)
+				r->coeffs[j] = 0;
+
+			return 1;
+		} 
+	 }
+
+	return 0;
+}
+
+/*************************************************
+* Name:        poly_basemul
+*
+* Description: Multiplication of two polynomials in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to first input polynomial
+*              - const poly *b: pointer to second input polynomial
+**************************************************/
+void poly_basemul(poly *r, const poly *a, const poly *b)
+{
+	for(int i = 0; i < NTRUPLUS_N/8; ++i)
+	{
+		basemul(r->coeffs + 8*i, a->coeffs + 8*i, b->coeffs + 8*i, zetas[96 + i]);
+		basemul(r->coeffs + 8*i + 4, a->coeffs + 8*i + 4, b->coeffs + 8*i + 4, -zetas[96 + i]);
+	}
+}
+
+/*************************************************
+* Name:        poly_basemul_add
+*
+* Description: Multiplication then addition of three polynomials in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to first input polynomial
+*              - const poly *b: pointer to second input polynomial
+*              - const poly *c: pointer to third input polynomial
+**************************************************/
+void poly_basemul_add(poly *r, const poly *a, const poly *b, const poly *c)
+{
+	for(int i = 0; i < NTRUPLUS_N/8; ++i)
+	{
+		basemul_add(r->coeffs + 8*i, a->coeffs + 8*i, b->coeffs + 8*i, c->coeffs + 8*i, zetas[96 + i]);
+		basemul_add(r->coeffs + 8*i + 4, a->coeffs + 8*i + 4, b->coeffs + 8*i + 4, c->coeffs + 8*i + 4, -zetas[96 + i]);
+	}
+}
+
+/*************************************************
+* Name:        poly_sub
+*
+* Description: Subtract two polynomials; no modular reduction is performed
+*
+* Arguments: - poly *r: pointer to output polynomial
+*            - const poly *a: pointer to first input polynomial
+*            - const poly *b: pointer to second input polynomial
+**************************************************/
+void poly_sub(poly *r, const poly *a, const poly *b)
+{
+	for(int i = 0; i < NTRUPLUS_N; ++i)
+		r->coeffs[i] = a->coeffs[i] - b->coeffs[i];
+}
+
+/*************************************************
+* Name:        poly_triple
+*
+* Description: Multiply polynomial by 3; no modular reduction is performed
+*
+* Arguments: - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_triple(poly *r)
+{
+	for(int i = 0; i < NTRUPLUS_N; ++i)
+		r->coeffs[i] *= 3;
+}
+
+/*************************************************
+* Name:        poly_crepmod3
+*
+* Description: Compute modulus 3 operation to polynomial
+*
+* Arguments: - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_crepmod3(poly *r)
+{
+	for(int i = 0; i < NTRUPLUS_N; i++)
+		r->coeffs[i] = crepmod3(r->coeffs[i]);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+768/poly.h b/cbits/third_party/NTRUplus/NTRU+768/poly.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/poly.h
@@ -0,0 +1,31 @@
+#ifndef POLY_H
+#define POLY_H
+
+#include <stdint.h>
+#include "params.h"
+
+/*
+ * Elements of R_q = Z_q[X]/(X^n - X^n/2 + 1). Represents polynomial
+ * coeffs[0] + X*coeffs[1] + X^2*xoeffs[2] + ... + X^{n-1}*coeffs[n-1]
+ */
+typedef struct{
+	int16_t coeffs[NTRUPLUS_N];
+} poly;
+
+void poly_tobytes(uint8_t r[NTRUPLUS_POLYBYTES], const poly *a);
+int  poly_frombytes(poly *r, const uint8_t a[NTRUPLUS_POLYBYTES]);
+
+void poly_cbd1(poly *r, const uint8_t buf[NTRUPLUS_N/4]);
+void poly_sotp_encode(poly *r, const uint8_t msg[NTRUPLUS_N/8], const uint8_t buf[NTRUPLUS_N/4]);
+int  poly_sotp_decode(uint8_t msg[NTRUPLUS_N/8], const poly *a, const uint8_t buf[NTRUPLUS_N/4]);
+
+void poly_ntt(poly *r);
+void poly_invntt(poly *r);
+int  poly_baseinv(poly *r, const poly *a);
+void poly_basemul(poly *r, const poly *a, const poly *b);
+void poly_basemul_add(poly *r, const poly *a, const poly *b, const poly *c);
+void poly_sub(poly *r, const poly *a, const poly *b);
+void poly_triple(poly *r);
+void poly_crepmod3(poly *r);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+768/randombytes.h b/cbits/third_party/NTRUplus/NTRU+768/randombytes.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/randombytes.h
@@ -0,0 +1,10 @@
+#ifndef RANDOMBYTES_H
+#define RANDOMBYTES_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* KpqC bindings propagate failures from the operating-system entropy source. */
+int randombytes(uint8_t *out, size_t outlen);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+768/symmetric.c b/cbits/third_party/NTRUplus/NTRU+768/symmetric.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/symmetric.c
@@ -0,0 +1,33 @@
+#include "util.h"
+#include <string.h>
+#include "symmetric.h"
+#include "fips202/fips202.h"
+
+void hash_f(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_F_INBYTES];
+
+    data[0] = 0x00;
+    memcpy(data + 1, msg, HASH_F_INBYTES);
+    shake256(buf, HASH_F_OUTBYTES, data, HASH_F_INBYTES + 1);
+}
+
+void hash_g(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_G_INBYTES];
+
+    data[0] = 0x01;
+    memcpy(data + 1, msg, HASH_G_INBYTES);
+    shake256(buf, HASH_G_OUTBYTES, data, HASH_G_INBYTES + 1);
+    secure_clear(data, sizeof data);
+}
+
+void hash_h(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_H_INBYTES];
+
+    data[0] = 0x02;
+    memcpy(data + 1, msg, HASH_H_INBYTES);
+    shake256(buf, HASH_H_OUTBYTES, data, HASH_H_INBYTES + 1);
+    secure_clear(data, sizeof data);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+768/symmetric.h b/cbits/third_party/NTRUplus/NTRU+768/symmetric.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/symmetric.h
@@ -0,0 +1,22 @@
+#ifndef SYMMETRIC_H
+#define SYMMETRIC_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "api.h"
+#include "params.h"
+
+#define HASH_F_INBYTES  (NTRUPLUS_POLYBYTES)
+#define HASH_F_OUTBYTES (NTRUPLUS_SYMBYTES)
+
+#define HASH_G_INBYTES  (NTRUPLUS_POLYBYTES)
+#define HASH_G_OUTBYTES (NTRUPLUS_N / 4)
+
+#define HASH_H_INBYTES  (NTRUPLUS_N / 8 + NTRUPLUS_SYMBYTES)
+#define HASH_H_OUTBYTES (NTRUPLUS_SSBYTES + NTRUPLUS_N / 4)
+
+NTRUPLUS_INTERNAL void hash_f(uint8_t *buf, const uint8_t *msg);
+NTRUPLUS_INTERNAL void hash_g(uint8_t *buf, const uint8_t *msg);
+NTRUPLUS_INTERNAL void hash_h(uint8_t *buf, const uint8_t *msg);
+
+#endif /* SYMMETRIC_H */
diff --git a/cbits/third_party/NTRUplus/NTRU+768/util.h b/cbits/third_party/NTRUplus/NTRU+768/util.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+768/util.h
@@ -0,0 +1,15 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+static inline void secure_clear(void *v, size_t len)
+{
+    volatile uint8_t *p = v;
+
+    while (len-- > 0)
+        *p++ = 0;
+}
+
+#endif /* UTIL_H */
diff --git a/cbits/third_party/NTRUplus/NTRU+864/api.h b/cbits/third_party/NTRUplus/NTRU+864/api.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/api.h
@@ -0,0 +1,39 @@
+#ifndef API_H
+#define API_H
+
+#include "params.h"
+
+#ifndef NTRUPLUS_ABI_H
+#define NTRUPLUS_ABI_H
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#define NTRUPLUS_INTERNAL
+#define NTRUPLUS_EXTERNAL __declspec(dllexport)
+#define NTRUPLUS_SYSV __attribute__((sysv_abi))
+#else
+#define NTRUPLUS_INTERNAL __attribute__((visibility("hidden")))
+#define NTRUPLUS_EXTERNAL __attribute__((visibility("default")))
+#define NTRUPLUS_SYSV
+#endif
+
+#endif
+
+#define CRYPTO_SECRETKEYBYTES  NTRUPLUS_SECRETKEYBYTES
+#define CRYPTO_PUBLICKEYBYTES  NTRUPLUS_PUBLICKEYBYTES
+#define CRYPTO_CIPHERTEXTBYTES NTRUPLUS_CIPHERTEXTBYTES
+#define CRYPTO_BYTES           NTRUPLUS_SSBYTES
+
+#define CRYPTO_ALGNAME NTRUPLUS_ALGNAME
+
+NTRUPLUS_EXTERNAL int crypto_kem_keypair(unsigned char *pk,
+                                         unsigned char *sk);
+
+NTRUPLUS_EXTERNAL int crypto_kem_enc(unsigned char *ct,
+                                     unsigned char *ss,
+                                     const unsigned char *pk);
+
+NTRUPLUS_EXTERNAL int crypto_kem_dec(unsigned char *ss,
+                                     const unsigned char *ct,
+                                     const unsigned char *sk);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+864/fips202/fips202.c b/cbits/third_party/NTRUplus/NTRU+864/fips202/fips202.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/fips202/fips202.c
@@ -0,0 +1,621 @@
+/* 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 "util.h"
+
+#include <stddef.h>
+#include <stdint.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., 136 for SHAKE256)
+ *              - 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);
+    }
+    secure_clear(t, sizeof t);
+}
+
+/*************************************************
+ * 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., 136 for SHAKE256)
+ **************************************************/
+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., 136 for SHAKE256)
+ *              - 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., 136 for SHAKE256)
+ *              - 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., 136 for SHAKE256)
+ **************************************************/
+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 shake256_inc_init(shake256incctx *state) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+void shake256_inc_ctx_release(shake256incctx *state) {
+    secure_clear(state->ctx, PQC_SHAKEINCCTX_BYTES);
+}
+
+/*************************************************
+ * 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) {
+    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) {
+    memcpy(dest->ctx, src->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/** Clear the state. */
+void shake256_ctx_release(shake256ctx *state) {
+    secure_clear(state->ctx, PQC_SHAKECTX_BYTES);
+}
+
+/*************************************************
+ * 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];
+        }
+    }
+    secure_clear(t, sizeof(t));
+    shake256_ctx_release(&s);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+864/fips202/fips202.h b/cbits/third_party/NTRUplus/NTRU+864/fips202/fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/fips202/fips202.h
@@ -0,0 +1,57 @@
+#ifndef FIPS202_H
+#define FIPS202_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHAKE256_RATE 136
+
+#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[26];
+} shake256incctx;
+
+// Context for non-incremental API
+typedef struct {
+    uint64_t ctx[25];
+} shake256ctx;
+
+/* 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);
+/* Clear 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);
+/* Clear the state */
+void shake256_inc_ctx_release(shake256incctx *state);
+
+/* One-stop SHAKE256 call */
+void shake256(uint8_t *output, size_t outlen,
+              const uint8_t *input, size_t inlen);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+864/kem.c b/cbits/third_party/NTRUplus/NTRU+864/kem.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/kem.c
@@ -0,0 +1,356 @@
+#include "util.h"
+#include <stddef.h>
+#include <stdint.h>
+#include "api.h"
+#include "params.h"
+#include "symmetric.h"
+#include "poly.h"
+#include "fips202/fips202.h"
+#include "randombytes.h"
+
+#ifdef NTRUPLUS_SUPERCOP
+#include "crypto_declassify.h"
+#define ntruplus_declassify crypto_declassify
+#else
+#define ntruplus_declassify(x, xlen) ((void)(x), (void)(xlen))
+#endif
+
+/*************************************************
+* Name:        verify
+*
+* Description: Compares two byte arrays for equality in constant time.
+*
+* Arguments:   - const uint8_t *a: first byte array
+*              - const uint8_t *b: second byte array
+*              - size_t len:      length of the arrays
+*
+* Returns 0 if the arrays are equal, 1 otherwise.
+**************************************************/
+static inline int verify(const uint8_t *a, const uint8_t *b, size_t len)
+{
+    uint8_t acc = 0;
+
+    for (size_t i = 0; i < len; i++)
+        acc |= (uint8_t)(a[i] ^ b[i]);
+
+    return (-(uint64_t)acc) >> 63;
+}
+
+/*************************************************
+* Name:        genf_derand
+*
+* Description: Deterministically generates a secret polynomial f and its
+*              multiplicative inverse finv in the NTT domain.
+*
+* Arguments:   - poly *f:     output polynomial f (NTT domain)
+*              - poly *finv:  output multiplicative inverse of f
+*                              in the NTT domain
+*              - uint8_t *buf: workspace of NTRUPLUS_N/4 bytes
+*              - const uint8_t *coins: 32-byte deterministic seed
+*
+* Returns 0 on success; non-zero if f is not invertible in the NTT domain.
+**************************************************/
+static inline int genf_derand(poly *f, poly *finv,
+                              uint8_t buf[NTRUPLUS_N / 4],
+                              const uint8_t *coins)
+{
+    shake256(buf, NTRUPLUS_N / 4, coins, 32);
+
+    poly_cbd1(f, buf);
+    poly_triple(f);
+    f->coeffs[0] += 1;
+
+    poly_ntt(f);
+
+    return poly_baseinv(finv, f);
+}
+
+/*************************************************
+* Name:        geng_derand
+*
+* Description: Deterministically generates a secret polynomial g and its
+*              multiplicative inverse ginv in the NTT domain.
+*
+* Arguments:   - poly *g:      output polynomial g (NTT domain)
+*              - poly *ginv:   output multiplicative inverse of g
+*                               in the NTT domain
+*              - uint8_t *buf: workspace of NTRUPLUS_N/4 bytes
+*              - const uint8_t *coins: 32-byte deterministic seed
+*
+* Returns 0 on success; non-zero if g is not invertible in the NTT domain.
+**************************************************/
+static inline int geng_derand(poly *g, poly *ginv,
+                              uint8_t buf[NTRUPLUS_N / 4],
+                              const uint8_t *coins)
+{
+    shake256(buf, NTRUPLUS_N / 4, coins, 32);
+
+    poly_cbd1(g, buf);
+    poly_triple(g);
+
+    poly_ntt(g);
+
+    return poly_baseinv(ginv, g);
+}
+
+/*************************************************
+* Name:        crypto_kem_keypair_derand
+*
+* Description: Computes the deterministic public and secret key pair from
+*              the secret polynomials f and g and their multiplicative
+*              inverses finv and ginv in the NTT domain.
+*
+* Arguments:   - uint8_t *pk:   output public key
+*              - uint8_t *sk:   output secret key
+*              - const poly *f:     secret polynomial f (NTT domain)
+*              - const poly *finv:  multiplicative inverse of f (NTT domain)
+*              - const poly *g:     secret polynomial g (NTT domain)
+*              - const poly *ginv:  multiplicative inverse of g (NTT domain)
+*
+* Returns:     void
+**************************************************/
+static inline void crypto_kem_keypair_derand(uint8_t *pk, uint8_t *sk,
+                                             const poly *f,  const poly *finv,
+                                             const poly *g,  const poly *ginv)
+{
+    poly h, hinv;
+
+    poly_basemul(&h, g, finv);
+    poly_basemul(&hinv, f, ginv);
+
+    poly_tobytes(pk, &h);
+    poly_tobytes(sk, f);
+    poly_tobytes(sk + NTRUPLUS_POLYBYTES, &hinv);
+    hash_f(sk + 2 * NTRUPLUS_POLYBYTES, pk);
+
+    secure_clear(&h, sizeof h);
+    secure_clear(&hinv, sizeof hinv);
+}
+
+/*************************************************
+* Name:        crypto_kem_keypair
+*
+* Description: Generates an NTRU+ public/secret key pair for the
+*              CCA-secure key encapsulation mechanism. Secret
+*              polynomials f and g and their NTT-domain inverses
+*              are sampled internally using fresh randomness.
+*
+* Arguments:   - uint8_t *pk: output public key
+*                (array of CRYPTO_PUBLICKEYBYTES bytes)
+*              - uint8_t *sk: output secret key
+*                (array of CRYPTO_SECRETKEYBYTES bytes)
+*
+* Returns 0 on success.
+**************************************************/
+int crypto_kem_keypair(unsigned char *pk, unsigned char *sk)
+{
+    uint8_t coins[NTRUPLUS_SYMBYTES];
+    uint8_t buf[NTRUPLUS_N / 4];
+    int r;
+    int status = -1;
+
+    poly f, finv;
+    poly g, ginv;
+
+    do {
+        if (randombytes(coins, sizeof coins) != 0) goto cleanup;
+        r = genf_derand(&f, &finv, buf, coins);
+        ntruplus_declassify(&r, sizeof r);
+    } while (r);
+
+    do {
+        if (randombytes(coins, sizeof coins) != 0) goto cleanup;
+        r = geng_derand(&g, &ginv, buf, coins);
+        ntruplus_declassify(&r, sizeof r);
+    } while (r);
+
+    crypto_kem_keypair_derand(pk, sk, &f, &finv, &g, &ginv);
+    status = 0;
+
+cleanup:
+    secure_clear(coins, sizeof coins);
+    secure_clear(buf, sizeof buf);
+    secure_clear(&f, sizeof f);
+    secure_clear(&finv, sizeof finv);
+    secure_clear(&g, sizeof g);
+    secure_clear(&ginv, sizeof ginv);
+    return status;
+}
+
+/*************************************************
+* Name:        crypto_kem_enc_derand
+*
+* Description: Deterministically performs NTRU+ KEM encapsulation using
+*              the public key pk and the supplied randomness coins,
+*              producing a ciphertext ct and shared secret ss.
+*
+* Arguments:   - uint8_t *ct:      output ciphertext
+*              - uint8_t *ss:      output shared secret
+*              - const uint8_t *pk:    input public key
+*              - const uint8_t *coins: input randomness for
+*                                      deterministic encapsulation
+*
+* Returns 0 on success. If pk contains a non-canonical coefficient, ct and ss
+* are set to all zero bytes and 1 is returned.
+**************************************************/
+static inline int crypto_kem_enc_derand(uint8_t *ct, uint8_t *ss,
+                                        const uint8_t *pk,
+                                        const uint8_t *coins)
+{
+	uint8_t msg[HASH_H_INBYTES];
+	uint8_t buf1[HASH_H_OUTBYTES];
+	uint8_t buf2[HASH_G_INBYTES];
+    
+    poly c, h, r, m;
+
+    if(poly_frombytes(&h, pk))
+    {
+        for (size_t i = 0; i < NTRUPLUS_CIPHERTEXTBYTES; i++)
+            ct[i] = 0;
+        secure_clear(ss, NTRUPLUS_SSBYTES);
+
+        return 1;
+    }
+    
+    for (size_t i = 0; i < NTRUPLUS_N / 8; i++)
+        msg[i] = coins[i];
+
+    hash_f(msg + NTRUPLUS_N / 8, pk);
+    hash_h(buf1, msg);
+    
+    poly_cbd1(&r, buf1 + NTRUPLUS_SYMBYTES);
+    poly_ntt(&r);
+    
+    poly_tobytes(buf2, &r);
+    hash_g(buf2, buf2);
+    poly_sotp_encode(&m, msg, buf2);
+    poly_ntt(&m);
+    
+    poly_basemul_add(&c, &h, &r, &m);
+    poly_tobytes(ct, &c);
+    
+    for (size_t i = 0; i < NTRUPLUS_SSBYTES; i++)
+        ss[i] = buf1[i];
+
+    secure_clear(msg, sizeof msg);
+    secure_clear(buf1, sizeof buf1);
+    secure_clear(buf2, sizeof buf2);
+    secure_clear(&r, sizeof r);
+    secure_clear(&m, sizeof m);
+    
+    return 0;
+}
+
+/*************************************************
+* Name:        crypto_kem_enc
+*
+* Description: Generates an NTRU+ KEM ciphertext ct and shared secret ss
+*              using the public key pk. Fresh randomness is internally
+*              sampled and used for encapsulation.
+*
+* Arguments:   - uint8_t *ct: output ciphertext
+*                (array of CRYPTO_CIPHERTEXTBYTES bytes)
+*              - uint8_t *ss: output shared secret
+*                (array of CRYPTO_BYTES bytes)
+*              - const uint8_t *pk: input public key
+*                (array of CRYPTO_PUBLICKEYBYTES bytes)
+*
+* Returns 0 on success. If pk contains a non-canonical coefficient, ct and ss
+* are set to all zero bytes and 1 is returned.
+**************************************************/
+int crypto_kem_enc(unsigned char *ct, unsigned char *ss,
+                   const unsigned char *pk)
+{
+    uint8_t coins[NTRUPLUS_N / 8];
+    int ret;
+
+    if (randombytes(coins, sizeof coins) != 0) {
+        secure_clear(coins, sizeof coins);
+        return -1;
+    }
+    ret = crypto_kem_enc_derand(ct, ss, pk, coins);
+    secure_clear(coins, sizeof coins);
+    return ret;
+}
+
+/*************************************************
+* Name:        crypto_kem_dec
+*
+* Description: Performs NTRU+ KEM decapsulation. Given a ciphertext ct
+*              and a secret key sk, computes the shared secret ss. If
+*              decapsulation fails, ss is set to all zero bytes.
+*
+* Arguments:   - uint8_t *ss:  output shared secret
+*                (array of CRYPTO_BYTES bytes)
+*              - const uint8_t *ct: input ciphertext
+*                (array of CRYPTO_CIPHERTEXTBYTES bytes)
+*              - const uint8_t *sk: input secret key
+*                (array of CRYPTO_SECRETKEYBYTES bytes)
+*
+* Returns 0 on success, 1 on failure.
+**************************************************/
+int crypto_kem_dec(unsigned char *ss, const unsigned char *ct,
+                   const unsigned char *sk)
+{
+	uint8_t msg[HASH_H_INBYTES];
+	uint8_t buf1[HASH_G_INBYTES];
+	uint8_t buf2[HASH_G_INBYTES];
+	uint8_t buf3[HASH_H_OUTBYTES];
+
+    int8_t fail = 1;
+    
+    poly c, f, hinv;
+    poly r1, r2;
+    poly m1, m2;
+
+    if(poly_frombytes(&c, ct) ||
+       poly_frombytes(&f, sk) ||
+       poly_frombytes(&hinv, sk + NTRUPLUS_POLYBYTES))
+    {
+        secure_clear(ss, NTRUPLUS_SSBYTES);
+
+        goto cleanup;
+    }
+    
+    poly_basemul(&m1, &c, &f);
+    poly_invntt(&m1);
+    poly_crepmod3(&m1);
+    
+    m2 = m1;
+    poly_ntt(&m2);
+    poly_sub(&c, &c, &m2);
+    poly_basemul(&r2, &c, &hinv);
+
+    poly_tobytes(buf1, &r2);
+    hash_g(buf2, buf1);
+    fail = poly_sotp_decode(msg, &m1, buf2);
+    
+    for (size_t i = 0; i < HASH_F_OUTBYTES; i++)
+        msg[i + NTRUPLUS_N / 8] = sk[i + 2 * NTRUPLUS_POLYBYTES];
+    
+    hash_h(buf3, msg);
+    
+    poly_cbd1(&r1, buf3 + NTRUPLUS_SSBYTES);
+    poly_ntt(&r1);
+    poly_tobytes(buf2, &r1);
+    
+    fail |= verify(buf1, buf2, NTRUPLUS_POLYBYTES);
+    
+    for (size_t i = 0; i < NTRUPLUS_SSBYTES; i++)
+        ss[i] = buf3[i] & ~(-fail);
+
+cleanup:
+    secure_clear(msg, sizeof msg);
+    secure_clear(buf1, sizeof buf1);
+    secure_clear(buf2, sizeof buf2);
+    secure_clear(buf3, sizeof buf3);
+    secure_clear(&c, sizeof c);
+    secure_clear(&f, sizeof f);
+    secure_clear(&hinv, sizeof hinv);
+    secure_clear(&r1, sizeof r1);
+    secure_clear(&r2, sizeof r2);
+    secure_clear(&m1, sizeof m1);
+    secure_clear(&m2, sizeof m2);
+
+    return fail;
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+864/ntt.c b/cbits/third_party/NTRUplus/NTRU+864/ntt.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/ntt.c
@@ -0,0 +1,379 @@
+#include <stdint.h>
+#include "params.h"
+#include "ntt.h"
+
+#define NTRUPLUS_R            -147 // R = 2^16 mod q
+#define NTRUPLUS_RINV         -682 // (R)^(-1) mod q
+#define NTRUPLUS_RSQ           867 // (R^2) mod q
+#define NTRUPLUS_QINV        12929 // (q)^(-1) mod (2^16)
+
+#define NTRUPLUS_OMEGA        -886 // (omega * R) mod q
+#define NTRUPLUS_ZMINUSZ5INV -1665 // (z - z^5)^(-1) * R mod q
+                                   // where z = zeta^((n/d)/6)
+
+#define NTRUPLUS_NINV        -1693 // (n/d)^(-1) * R mod q
+#define NTRUPLUS_2NINV          71 // 2 * (n/d)^(-1) * R mod q
+
+// zetas: Montgomery-form twiddle factors
+const int16_t zetas[288] = {
+	 -147, -1033, -1265,   708,   460,  1265,  -467,   727,
+	  556,  1307,  -773,  -161,  1200, -1612,   570,  1529,
+	 1135,  -556,  1120,   298,  -822, -1556,   -93,  1463,
+	  532,  -377,  -909,    58,  -392,  -450,  1722,  1236,
+	 -486,  -491, -1569, -1078,    36,  1289, -1443,  1628,
+	 1664,  -725,  -952,    99, -1020,   353,  -599,  1119,
+	  592,   839,  1622,   652,  1244,  -783, -1085,  -726,
+	  566,  -284, -1369, -1292,   268,  -391,   781,  -172,
+	   96, -1172,   211,   737,   473,  -445,  -234,   264,
+	-1536,  1467,  -676, -1542,  -170,   635,  -705, -1332,
+	 -658,   831, -1712,  1311,  1488,  -881,  1087, -1315,
+	 1245,   -75,   791,    -6,  -875,  -697,   -70, -1162,
+	  287,  -767,  -945,  1598,  -882,  1261,   206,   654,
+	-1421,   -81,   716, -1251,   838, -1300,  1035,  -104,
+	  966,  -558,   -61, -1704,   404,  -899,   862, -1593,
+	-1460,   -37,  1266,   965, -1584, -1404,  -265,  -942,
+	  905,  1195,  -619,   787,   118,   576,   286, -1475,
+	 -194,   928,  1229, -1032,  1608,  1111, -1669,   642,
+	-1323,   163,   309,   981,  -557,  -258,   232, -1680,
+	-1657, -1233,   144,  1699,   311, -1060,   578,  1298,
+	 -403,  1607,  1074,  -148,   447, -1568,  1142,  -402,
+	-1412,  -623,   855,   365,   -98,  -244,   407,  1225,
+	  416,   683,  -105,  1714, -1019,  1061,  1163,   638,
+	  798,  1493,  -351,   396,  -542,    -9,  1616,  -139,
+	 -987,  -482,   889,   238, -1513,   466, -1089,  -101,
+	  849,  -426,  1589,  1487,   671,  1459,  -776,   255,
+	-1014,  1144,   472, -1153,  -325,  1519,   -26, -1123,
+	  324,  1230,  1547,  -593,  -428,  1192,  1072, -1564,
+	  688,  -333,  1023, -1686,   841,   824,   -71,  1587,
+	  522,  -323,  1148,   389,  1231,   384,  1343,   169,
+	  628, -1329, -1056,  -936,    24,  -293,  1523,  -300,
+	-1654,   891,  -962,   -67,   179, -1177,   844,  -509,
+	-1677, -1565,  -549, -1508,  1191,  -280,   -43,   669,
+	 -746,   753,   770, -1046,  1711,  1438,   690,  1083,
+	 1062,  1727,  -883,   553,  1670,    66,   825,  -133,
+	-1586,   637,  -680,  -917,   644,  -372, -1193, -1136
+};
+
+/*************************************************
+* Name:        montgomery_reduce
+*
+* Description: Montgomery reduction; given a 32-bit integer a, computes
+*              a 16-bit integer congruent to a * R^-1 mod q,
+*              where R = 2^16.
+*
+* Arguments:   - int32_t a: input integer to be reduced;
+*                           must lie in {-q*2^15, ..., q*2^15-1}
+*
+* Returns:     an integer in {-q+1, ..., q-1} congruent to
+*              a * R^-1 mod q.
+**************************************************/
+static inline int16_t montgomery_reduce(int32_t a)
+{
+	int16_t t;
+	
+	t = (int16_t)a * NTRUPLUS_QINV;
+	t = (a - (int32_t)t * NTRUPLUS_Q) >> 16;
+	return t;
+}
+
+/*************************************************
+* Name:        barrett_reduce
+*
+* Description: Barrett reduction; given a 16-bit integer a, computes a
+*              centered representative congruent to a mod q in
+*              {-(q+1)/2, ..., (q+1)/2}.
+*
+* Arguments:   - int16_t a: input integer to be reduced
+*
+* Returns:     integer in {-(q+1)/2, ..., (q+1)/2} congruent to a mod q.
+**************************************************/
+static inline int16_t barrett_reduce(int16_t a)
+{
+	int16_t t;
+	const int16_t v = ((1<<26) + NTRUPLUS_Q/2) / NTRUPLUS_Q;
+	
+	t  = ((int32_t)v*a + (1<<25)) >> 26;
+	t *= NTRUPLUS_Q;
+	return a - t;
+}
+
+/*************************************************
+* Name:        fqmul
+*
+* Description: Multiplication followed by Montgomery reduction.
+*
+* Arguments:   - int16_t a: first factor
+*              - int16_t b: second factor
+*
+* Returns:     16-bit integer congruent to a*b*R^-1 mod q.
+**************************************************/
+static inline int16_t fqmul(int16_t a, int16_t b)
+{
+    return montgomery_reduce((int32_t)a * b);
+}
+
+/*************************************************
+* Name:        fqinv
+*
+* Description: Computes the multiplicative inverse in Z_q using Fermat's
+*              little theorem. The exponent q-2 = 3455 is evaluated using
+*              a shortest addition chain of length 15 (OEIS A003313).
+*
+* Arguments:   - int16_t a: input value a mod q
+*
+* Returns:     16-bit integer congruent to a^{-1} mod q.
+**************************************************/
+static inline int16_t fqinv(int16_t a)
+{
+	int16_t t0, t1;
+
+	t0 = fqmul(a, a);    // 10
+	t0 = fqmul(t0, t0);  // 100
+	t0 = fqmul(t0, t0);  // 1000
+	t0 = fqmul(t0, t0);  // 10000
+	t1 = fqmul(t0, a);   // 10001
+
+	t0 = fqmul(t0, t0);  // 100000
+	t0 = fqmul(t0, t0);  // 1000000
+	t0 = fqmul(t0, t0);  // 10000000
+	t1 = fqmul(t0, t1);  // 10010001
+
+	t0 = fqmul(t1, t0);  // 100010001
+	t1 = fqmul(t0, t1);  // 110100010
+	t1 = fqmul(t1, t0);  // 1010110011
+	t0 = fqmul(t1, t1);  // 10101100110
+	t0 = fqmul(t0, t0);  // 101011001100
+	t0 = fqmul(t0, t1);  // 110101111111
+
+	return fqmul(NTRUPLUS_RINV, t0);
+}
+
+/*************************************************
+* Name:        ntt
+*
+* Description: Number-theoretic transform (NTT) in R_q. Transforms the
+*              coefficient representation of r into a representation
+*              where each block of 3 coefficients corresponds to an
+*              element of Zq[X]/(X^3 - zeta_i).
+*
+* Arguments:   - int16_t r[NTRUPLUS_N]: pointer to input/output vector;
+*                                       input coefficients of r in R_q,
+*                                       output NTT representation in the
+*                                       product ring Zq[X]/(X^3 - zeta_i)
+*
+* Returns:     none.
+**************************************************/
+void ntt(int16_t r[NTRUPLUS_N])
+{
+	int16_t t1, t2, t3;
+	int16_t zeta1, zeta2;
+	
+	int k = 1;
+
+	zeta1 = zetas[k++];
+
+	for (int i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t1 = fqmul(zeta1, r[i + NTRUPLUS_N/2]);
+
+		r[i + NTRUPLUS_N/2] = r[i] + r[i + NTRUPLUS_N/2] - t1;
+		r[i               ] = r[i]                       + t1;
+	}
+
+	for (int step = NTRUPLUS_N/6; step >= 48; step = step/3)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += 3*step)
+		{
+			zeta1 = zetas[k++];
+			zeta2 = zetas[k++];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = fqmul(zeta1, r[i +   step]);
+				t2 = fqmul(zeta2, r[i + 2*step]);
+				t3 = fqmul(NTRUPLUS_OMEGA, t1 - t2);
+	
+				r[i + 2*step] = r[i] - t1 - t3;
+				r[i +   step] = r[i] - t2 + t3;
+				r[i         ] = r[i] + t1 + t2;
+			}
+		}
+	}
+
+	for (int step = 24; step >= 3; step >>= 1)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += (step << 1))
+		{
+			zeta1 = zetas[k++];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = fqmul(zeta1, r[i + step]);
+				
+				r[i + step] = barrett_reduce(r[i] - t1);
+				r[i       ] = barrett_reduce(r[i] + t1);
+			}
+		}
+	}
+}
+
+/*************************************************
+* Name:        invntt
+*
+* Description: Inverse number-theoretic transform (NTT) in R_q. Transforms
+*              the NTT representation in r, where each block of 3
+*              coefficients corresponds to an element of Zq[X]/(X^3 - zeta_i),
+*              back to the coefficient representation in R_q.
+*
+* Arguments:   - int16_t r[NTRUPLUS_N]: pointer to input/output vector;
+*                                       input NTT representation in the
+*                                       product ring Zq[X]/(X^3 - zeta_i),
+*                                       output coefficient representation
+*                                       in R_q
+*
+* Returns:     none.
+**************************************************/
+void invntt(int16_t r[NTRUPLUS_N])
+{
+	int16_t t1,t2,t3;
+	int16_t zeta1,zeta2;
+	int k = 287;
+
+	for (int step = 3; step <= 24; step <<= 1)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += (step << 1))
+		{
+			zeta1 = zetas[k--];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = r[i + step];
+
+				r[i + step] = fqmul(zeta1, t1 - r[i]);
+				r[i       ] = barrett_reduce(r[i] + t1);
+			}
+		}
+	}
+
+	for (int step = 48; step <= NTRUPLUS_N/6; step = 3*step)
+	{
+		for (int start = 0; start < NTRUPLUS_N; start += 3*step)
+		{
+			zeta2 = zetas[k--];
+			zeta1 = zetas[k--];
+
+			for (int i = start; i < start + step; i++)
+			{
+				t1 = fqmul(NTRUPLUS_OMEGA, r[i +   step] - r[i]);
+				t2 = fqmul(zeta1, r[i + 2*step] - r[i]        + t1);
+				t3 = fqmul(zeta2, r[i + 2*step] - r[i + step] - t1);
+				
+				r[i         ] = barrett_reduce(r[i] + r[i + step] + r[i + 2*step]);
+				r[i +   step] = t2;			
+				r[i + 2*step] = t3;
+			}			
+		}
+	}
+	
+	for (int i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t1 = r[i] + r[i + NTRUPLUS_N/2];
+		t2 = fqmul(NTRUPLUS_ZMINUSZ5INV, r[i] - r[i + NTRUPLUS_N/2]);
+
+		r[i               ] = fqmul(NTRUPLUS_NINV, t1 - t2);
+		r[i + NTRUPLUS_N/2] = fqmul(NTRUPLUS_2NINV, t2);
+	}
+}
+
+/*************************************************
+* Name:        baseinv
+*
+* Description: Inversion of a polynomial in Zq[X]/(X^3 - zeta), used as
+*              a building block for inversion of elements in R_q in the
+*              NTT domain.
+*
+* Arguments:   - int16_t r[3]:        pointer to the output polynomial
+*              - const int16_t a[3]:  pointer to the input polynomial
+*              - const int16_t zeta:  parameter defining X^3 - zeta
+*
+* Returns:     0 if a is invertible, 1 otherwise.
+**************************************************/
+int baseinv(int16_t r[3], const int16_t a[3], int16_t zeta)
+{
+	int16_t t;
+
+	r[0] = montgomery_reduce(a[1]*a[2]);		   // R^-1
+	r[1] = montgomery_reduce(a[2]*a[2]);		   // R^-1
+	r[2] = montgomery_reduce(a[1]*a[1]-a[0]*a[2]); // R^-1
+	r[0] = montgomery_reduce(a[0]*a[0]-r[0]*zeta); // R^-1
+	r[1] = montgomery_reduce(r[1]*zeta-a[0]*a[1]); // R^-1
+
+	t    = montgomery_reduce(r[2]*a[1]+r[1]*a[2]); // R^-2
+	t    = montgomery_reduce(t*zeta+r[0]*a[0]);    // R^-2
+
+	if(t == 0) return 1;
+
+	t   = fqinv(t); // R^4
+	
+	r[0]  = montgomery_reduce(r[0]*t); // R^0
+	r[1]  = montgomery_reduce(r[1]*t); // R^0
+	r[2]  = montgomery_reduce(r[2]*t); // R^0
+
+	return 0;
+}
+
+/*************************************************
+* Name:        basemul
+*
+* Description: Multiplication of polynomials in Zq[X]/(X^3 - zeta),
+*              used for multiplication of elements in R_q in the NTT domain.
+*
+* Arguments:   - int16_t r[3]:        pointer to the output polynomial
+*              - const int16_t a[3]:  pointer to the first factor
+*              - const int16_t b[3]:  pointer to the second factor
+*              - const int16_t zeta:  parameter defining X^3 - zeta
+*
+* Returns:     none.
+**************************************************/
+void basemul(int16_t r[3], const int16_t a[3], const int16_t b[3], int16_t zeta)
+{
+	r[0] = montgomery_reduce(a[2]*b[1]+a[1]*b[2]); // R^-1
+	r[1] = montgomery_reduce(a[2]*b[2]);		   // R^-1
+
+	r[0] = montgomery_reduce(r[0]*zeta+a[0]*b[0]);		 	 // R^-1
+	r[1] = montgomery_reduce(r[1]*zeta+a[0]*b[1]+a[1]*b[0]); // R^-1
+	r[2] = montgomery_reduce(a[2]*b[0]+a[1]*b[1]+a[0]*b[2]); // R^-1
+
+	r[0] = montgomery_reduce(r[0]*NTRUPLUS_RSQ); // R^0
+	r[1] = montgomery_reduce(r[1]*NTRUPLUS_RSQ); // R^0
+	r[2] = montgomery_reduce(r[2]*NTRUPLUS_RSQ);	// R^0
+}
+
+/*************************************************
+* Name:        basemul_add
+*
+* Description: Multiplication then addition of polynomials in
+*              Zq[X]/(X^3 - zeta), used for multiplication of
+*              elements in R_q in the NTT domain.
+*
+* Arguments:   - int16_t r[3]:        pointer to the output polynomial
+*              - const int16_t a[3]:  pointer to the first factor
+*              - const int16_t b[3]:  pointer to the second factor
+*              - const int16_t c[3]:  pointer to the third factor
+*              - const int16_t zeta:  parameter defining X^3 - zeta
+*
+* Returns:     none.
+**************************************************/
+void basemul_add(int16_t r[3], const int16_t a[3], const int16_t b[3], const int16_t c[3], int16_t zeta)
+{
+	r[0] = montgomery_reduce(a[2]*b[1]+a[1]*b[2]); // R^-1
+	r[1] = montgomery_reduce(a[2]*b[2]);		   // R^-1
+
+	r[0] = montgomery_reduce(r[0]*zeta+a[0]*b[0]);		 	 // R^-1
+	r[1] = montgomery_reduce(r[1]*zeta+a[0]*b[1]+a[1]*b[0]); // R^-1
+	r[2] = montgomery_reduce(a[2]*b[0]+a[1]*b[1]+a[0]*b[2]); // R^-1
+
+	r[0] = montgomery_reduce(c[0]*NTRUPLUS_R + r[0]*NTRUPLUS_RSQ); // R^0
+	r[1] = montgomery_reduce(c[1]*NTRUPLUS_R + r[1]*NTRUPLUS_RSQ); // R^0
+	r[2] = montgomery_reduce(c[2]*NTRUPLUS_R + r[2]*NTRUPLUS_RSQ); // R^0
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+864/ntt.h b/cbits/third_party/NTRUplus/NTRU+864/ntt.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/ntt.h
@@ -0,0 +1,16 @@
+#ifndef NTT_H
+#define NTT_H
+
+#include <stdint.h>
+#include "params.h"
+
+extern const int16_t zetas[288];
+
+void ntt(int16_t r[NTRUPLUS_N]);
+void invntt(int16_t r[NTRUPLUS_N]);
+
+int  baseinv(int16_t r[3], const int16_t a[3], int16_t zeta);
+void basemul(int16_t r[3], const int16_t a[3], const int16_t b[3], int16_t zeta);
+void basemul_add(int16_t r[3], const int16_t a[3], const int16_t b[3], const int16_t c[3], int16_t zeta);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+864/params.h b/cbits/third_party/NTRUplus/NTRU+864/params.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/params.h
@@ -0,0 +1,18 @@
+#ifndef PARAMS_H
+#define PARAMS_H
+
+#define NTRUPLUS_ALGNAME "NTRU+864"
+
+#define NTRUPLUS_N 864
+#define NTRUPLUS_Q 3457
+#define NTRUPLUS_D 3
+
+#define NTRUPLUS_SYMBYTES  32   /* size in bytes of hashes, and seeds */
+#define NTRUPLUS_SSBYTES   32   /* size in bytes of shared key */
+#define NTRUPLUS_POLYBYTES 1296
+
+#define NTRUPLUS_PUBLICKEYBYTES  NTRUPLUS_POLYBYTES
+#define NTRUPLUS_SECRETKEYBYTES  ((NTRUPLUS_POLYBYTES << 1) + NTRUPLUS_SYMBYTES)
+#define NTRUPLUS_CIPHERTEXTBYTES  NTRUPLUS_POLYBYTES
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+864/poly.c b/cbits/third_party/NTRUplus/NTRU+864/poly.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/poly.c
@@ -0,0 +1,328 @@
+#include <stdint.h>
+#include <string.h>
+#include "params.h"
+#include "poly.h"
+#include "ntt.h"
+#include "symmetric.h"
+
+/*************************************************
+* Name:        crepmod3
+*
+* Description: Compute modulus 3 operation
+*
+* Arguments: - poly *a: pointer to intput integer to be reduced
+*
+* Returns:     integer in {-1,0,1} congruent to a modulo 3.
+**************************************************/
+static inline int16_t crepmod3(int16_t a)
+{
+	int16_t t;
+	const int16_t v = ((1<<15) + 3/2)/3;
+
+	a += (a >> 15) & NTRUPLUS_Q;
+	a -= (NTRUPLUS_Q+1)/2;
+	a += (a >> 15) & NTRUPLUS_Q;
+	a -= (NTRUPLUS_Q-1)/2;
+
+	t  = ((int32_t)v*a + (1<<14)) >> 15;
+	t *= 3;
+	return a - t;
+}
+
+/*************************************************
+* Name:        poly_tobytes
+*
+* Description: Serialization of a polynomial
+*
+* Arguments:   - uint8_t *r: pointer to output byte array
+*                            (needs space for NTRUPLUS_POLYBYTES bytes)
+*              - poly *a:    pointer to input polynomial
+**************************************************/
+void poly_tobytes(uint8_t r[NTRUPLUS_POLYBYTES], const poly *a)
+{
+	int16_t t[2];
+
+	for(size_t i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		t[0] = a->coeffs[2*i];
+		t[0] += (t[0] >> 15) & NTRUPLUS_Q;
+		t[1] = a->coeffs[2*i+1];
+		t[1] += (t[1] >> 15) & NTRUPLUS_Q;
+
+		r[3*i+0] = (t[0] >> 0);
+		r[3*i+1] = (t[0] >> 8) | (t[1] << 4);
+		r[3*i+2] = (t[1] >> 4);
+	}
+}
+
+/*************************************************
+* Name:        poly_frombytes
+*
+* Description: De-serialization of a polynomial;
+*              inverse of poly_tobytes
+*
+* Arguments:   - poly *r:          pointer to output polynomial
+*              - const uint8_t *a: pointer to input byte array
+*                                  (of NTRUPLUS_POLYBYTES bytes)
+*
+* Returns 0 on success, 1 if any coefficient is greater than or equal to q.
+**************************************************/
+int poly_frombytes(poly *r, const uint8_t a[NTRUPLUS_POLYBYTES])
+{
+	uint32_t fail = 0;
+
+	for(size_t i = 0; i < NTRUPLUS_N/2; i++)
+	{
+		const uint16_t t0 = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF;
+		const uint16_t t1 = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF;
+
+		r->coeffs[2*i]   = t0;
+		r->coeffs[2*i+1] = t1;
+		fail |= (uint32_t)(NTRUPLUS_Q - 1) - t0;
+		fail |= (uint32_t)(NTRUPLUS_Q - 1) - t1;
+	}
+
+	return fail >> 31;
+}
+
+/*************************************************
+* Name:        poly_cbd1
+*
+* Description: Sample a polynomial deterministically from a random,
+*              with output polynomial close to centered binomial distribution
+*
+* Arguments:   - poly *r: pointer to output polynomial
+*              - const uint8_t *buf: pointer to input random
+*                                     (of length NTRUPLUS_N/4 bytes)
+**************************************************/
+void poly_cbd1(poly *r, const unsigned char buf[NTRUPLUS_N/4])
+{
+	uint8_t t1, t2;
+
+	for(size_t i = 0; i < NTRUPLUS_N / 8; i++)
+	{
+		t1 = buf[i];
+		t2 = buf[i + NTRUPLUS_N / 8];
+
+		for(size_t j = 0; j < 8; j++)
+		{
+			r->coeffs[8*i + j] = (t1 & 0x1) - (t2 & 0x1);
+
+			t1 >>= 1;   
+			t2 >>= 1;
+		}
+	}
+}
+
+/*************************************************
+* Name:        poly_sotp_encode
+*
+* Description: Encode a message deterministically using SOTP and a random,
+			   with output polynomial close to centered binomial distribution
+*
+* Arguments:   - poly *r: pointer to output polynomial
+*              - const uint8_t *msg: pointer to input message
+*              - const uint8_t *buf: pointer to input random
+**************************************************/
+void poly_sotp_encode(poly *r, const uint8_t msg[NTRUPLUS_N/8], const uint8_t buf[NTRUPLUS_N/4])
+{
+    uint8_t tmp[NTRUPLUS_N/4];
+
+    for(int i = 0; i < NTRUPLUS_N/8; i++)
+    {
+         tmp[i] = buf[i]^msg[i];
+    }
+
+    for(int i = NTRUPLUS_N/8; i < NTRUPLUS_N/4; i++)
+    {
+         tmp[i] = buf[i];
+    }
+
+	poly_cbd1(r, tmp);
+}
+
+/*************************************************
+* Name:        poly_sotp_decode
+*
+* Description: Decode a message deterministically using SOTP_INV and a random
+*
+* Arguments:   - uint8_t *msg: pointer to output message
+*              - const poly *a: pointer to iput polynomial
+*              - const uint8_t *buf: pointer to input random
+*
+* Returns 0 (success) or 1 (failure)
+**************************************************/
+int poly_sotp_decode(uint8_t msg[NTRUPLUS_N/8], const poly *a, const uint8_t buf[NTRUPLUS_N/4])
+{
+	uint8_t t1, t2, t3;
+	uint16_t t4;
+	uint32_t r = 0;
+	volatile uint8_t mask;
+
+	for(size_t i = 0; i < NTRUPLUS_N / 8; i++)
+	{
+		t1 = buf[i                 ];
+		t2 = buf[i + NTRUPLUS_N / 8];
+		t3 = 0;
+
+		for(int j = 0; j < 8; j++)
+		{
+			t4 = t2 & 0x1;
+			t4 += a->coeffs[8*i + j];
+			r |= t4;
+			t4 = (t4 ^ t1) & 0x1;
+			t3 ^= (uint8_t)(t4 << j);
+
+			t1 >>= 1;
+			t2 >>= 1;
+		}
+
+		msg[i] = t3;
+	}
+
+	r = r >> 1;
+	r = (-(uint32_t)r) >> 31;
+
+	mask = (uint8_t)(r - 1);
+
+	for (size_t i = 0; i < NTRUPLUS_N / 8; i++)
+		msg[i] &= mask;
+
+	return r;
+}
+
+/*************************************************
+* Name:        poly_ntt
+*
+* Description: Computes number-theoretic transform (NTT)
+*
+* Arguments:   - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_ntt(poly *r)
+{
+	ntt(r->coeffs);
+}
+
+/*************************************************
+* Name:        poly_invntt
+*
+* Description: Computes inverse of number-theoretic transform (NTT)
+*
+* Arguments:   - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_invntt(poly *r)
+{
+	invntt(r->coeffs);
+}
+
+/*************************************************
+* Name:        poly_baseinv
+*
+* Description: Inversion of polynomial in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to input polynomial
+* 
+* Returns:     integer
+**************************************************/
+int poly_baseinv(poly *r, const poly *a)
+{
+	for(int i = 0; i < NTRUPLUS_N/6; ++i)
+	{
+		if(baseinv(r->coeffs+6*i, a->coeffs+6*i, zetas[144+i]))
+		{
+			for (size_t j = 0; j < NTRUPLUS_N; ++j)
+				r->coeffs[j] = 0;
+
+			return 1;
+		}
+
+		if(baseinv(r->coeffs+6*i+3, a->coeffs+6*i+3, -zetas[144+i]))
+		{
+			for (size_t j = 0; j < NTRUPLUS_N; ++j)
+				r->coeffs[j] = 0;
+
+			return 1;
+		}
+	 }
+
+	return 0;
+}
+
+/*************************************************
+* Name:        poly_basemul
+*
+* Description: Multiplication of two polynomials in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to first input polynomial
+*              - const poly *b: pointer to second input polynomial
+**************************************************/
+void poly_basemul(poly *r, const poly *a, const poly *b)
+{
+	for(int i = 0; i < NTRUPLUS_N/6; i++)
+	{
+		basemul(r->coeffs+6*i,   a->coeffs+6*i,   b->coeffs+6*i,    zetas[144+i]);
+		basemul(r->coeffs+6*i+3, a->coeffs+6*i+3, b->coeffs+6*i+3, -zetas[144+i]);
+	}
+}
+
+/*************************************************
+* Name:        poly_basemul_add
+*
+* Description: Multiplication then addition of three polynomials in NTT domain
+*
+* Arguments:   - poly *r:       pointer to output polynomial
+*              - const poly *a: pointer to first input polynomial
+*              - const poly *b: pointer to second input polynomial
+*              - const poly *c: pointer to third input polynomial
+**************************************************/
+void poly_basemul_add(poly *r, const poly *a, const poly *b, const poly *c)
+{
+	for(int i = 0; i < NTRUPLUS_N/6; i++)
+	{
+		basemul_add(r->coeffs+6*i,   a->coeffs+6*i,   b->coeffs+6*i,   c->coeffs+6*i,    zetas[144+i]);
+		basemul_add(r->coeffs+6*i+3, a->coeffs+6*i+3, b->coeffs+6*i+3, c->coeffs+6*i+3, -zetas[144+i]);
+	}
+}
+
+/*************************************************
+* Name:        poly_sub
+*
+* Description: Subtract two polynomials; no modular reduction is performed
+*
+* Arguments: - poly *r: pointer to output polynomial
+*            - const poly *a: pointer to first input polynomial
+*            - const poly *b: pointer to second input polynomial
+**************************************************/
+void poly_sub(poly *r, const poly *a, const poly *b)
+{
+	for(int i = 0; i < NTRUPLUS_N; ++i)
+		r->coeffs[i] = a->coeffs[i] - b->coeffs[i];
+}
+
+/*************************************************
+* Name:        poly_triple
+*
+* Description: Multiply polynomial by 3; no modular reduction is performed
+*
+* Arguments: - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_triple(poly *r)
+{
+	for(int i = 0; i < NTRUPLUS_N; ++i)
+		r->coeffs[i] *= 3;
+}
+
+/*************************************************
+* Name:        poly_crepmod3
+*
+* Description: Compute modulus 3 operation to polynomial
+*
+* Arguments: - poly *r: pointer to input/output polynomial
+**************************************************/
+void poly_crepmod3(poly *r)
+{
+	for(int i = 0; i < NTRUPLUS_N; i++)
+		r->coeffs[i] = crepmod3(r->coeffs[i]);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+864/poly.h b/cbits/third_party/NTRUplus/NTRU+864/poly.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/poly.h
@@ -0,0 +1,31 @@
+#ifndef POLY_H
+#define POLY_H
+
+#include <stdint.h>
+#include "params.h"
+
+/*
+ * Elements of R_q = Z_q[X]/(X^n - X^n/2 + 1). Represents polynomial
+ * coeffs[0] + X*coeffs[1] + X^2*xoeffs[2] + ... + X^{n-1}*coeffs[n-1]
+ */
+typedef struct{
+	int16_t coeffs[NTRUPLUS_N];
+} poly;
+
+void poly_tobytes(uint8_t r[NTRUPLUS_POLYBYTES], const poly *a);
+int  poly_frombytes(poly *r, const uint8_t a[NTRUPLUS_POLYBYTES]);
+
+void poly_cbd1(poly *r, const uint8_t buf[NTRUPLUS_N/4]);
+void poly_sotp_encode(poly *r, const uint8_t msg[NTRUPLUS_N/8], const uint8_t buf[NTRUPLUS_N/4]);
+int  poly_sotp_decode(uint8_t msg[NTRUPLUS_N/8], const poly *a, const uint8_t buf[NTRUPLUS_N/4]);
+
+void poly_ntt(poly *r);
+void poly_invntt(poly *r);
+int  poly_baseinv(poly *r, const poly *a);
+void poly_basemul(poly *r, const poly *a, const poly *b);
+void poly_basemul_add(poly *r, const poly *a, const poly *b, const poly *c);
+void poly_sub(poly *r, const poly *a, const poly *b);
+void poly_triple(poly *r);
+void poly_crepmod3(poly *r);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+864/randombytes.h b/cbits/third_party/NTRUplus/NTRU+864/randombytes.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/randombytes.h
@@ -0,0 +1,10 @@
+#ifndef RANDOMBYTES_H
+#define RANDOMBYTES_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* KpqC bindings propagate failures from the operating-system entropy source. */
+int randombytes(uint8_t *out, size_t outlen);
+
+#endif
diff --git a/cbits/third_party/NTRUplus/NTRU+864/symmetric.c b/cbits/third_party/NTRUplus/NTRU+864/symmetric.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/symmetric.c
@@ -0,0 +1,33 @@
+#include "util.h"
+#include <string.h>
+#include "symmetric.h"
+#include "fips202/fips202.h"
+
+void hash_f(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_F_INBYTES];
+
+    data[0] = 0x00;
+    memcpy(data + 1, msg, HASH_F_INBYTES);
+    shake256(buf, HASH_F_OUTBYTES, data, HASH_F_INBYTES + 1);
+}
+
+void hash_g(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_G_INBYTES];
+
+    data[0] = 0x01;
+    memcpy(data + 1, msg, HASH_G_INBYTES);
+    shake256(buf, HASH_G_OUTBYTES, data, HASH_G_INBYTES + 1);
+    secure_clear(data, sizeof data);
+}
+
+void hash_h(uint8_t *buf, const uint8_t *msg)
+{
+    uint8_t data[1 + HASH_H_INBYTES];
+
+    data[0] = 0x02;
+    memcpy(data + 1, msg, HASH_H_INBYTES);
+    shake256(buf, HASH_H_OUTBYTES, data, HASH_H_INBYTES + 1);
+    secure_clear(data, sizeof data);
+}
diff --git a/cbits/third_party/NTRUplus/NTRU+864/symmetric.h b/cbits/third_party/NTRUplus/NTRU+864/symmetric.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/symmetric.h
@@ -0,0 +1,22 @@
+#ifndef SYMMETRIC_H
+#define SYMMETRIC_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "api.h"
+#include "params.h"
+
+#define HASH_F_INBYTES  (NTRUPLUS_POLYBYTES)
+#define HASH_F_OUTBYTES (NTRUPLUS_SYMBYTES)
+
+#define HASH_G_INBYTES  (NTRUPLUS_POLYBYTES)
+#define HASH_G_OUTBYTES (NTRUPLUS_N / 4)
+
+#define HASH_H_INBYTES  (NTRUPLUS_N / 8 + NTRUPLUS_SYMBYTES)
+#define HASH_H_OUTBYTES (NTRUPLUS_SSBYTES + NTRUPLUS_N / 4)
+
+NTRUPLUS_INTERNAL void hash_f(uint8_t *buf, const uint8_t *msg);
+NTRUPLUS_INTERNAL void hash_g(uint8_t *buf, const uint8_t *msg);
+NTRUPLUS_INTERNAL void hash_h(uint8_t *buf, const uint8_t *msg);
+
+#endif /* SYMMETRIC_H */
diff --git a/cbits/third_party/NTRUplus/NTRU+864/util.h b/cbits/third_party/NTRUplus/NTRU+864/util.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/NTRUplus/NTRU+864/util.h
@@ -0,0 +1,15 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+static inline void secure_clear(void *v, size_t len)
+{
+    volatile uint8_t *p = v;
+
+    while (len-- > 0)
+        *p++ = 0;
+}
+
+#endif /* UTIL_H */
diff --git a/cbits/third_party/SMAUG-T/LICENSE b/cbits/third_party/SMAUG-T/LICENSE
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2026 Team SMAUG-T
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/cbits/third_party/SMAUG-T/include/api.h b/cbits/third_party/SMAUG-T/include/api.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/api.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_API_H
+#define SMAUGT_API_H
+
+#include "params.h"
+
+#include <stdint.h>
+
+/* Export API in SUPERCOP naming scheme CRYPTO_xxx / crypto_kem_xxx */
+#define CRYPTO_SECRETKEYBYTES SMAUGT_KEM_SECRETKEY_BYTES
+#define CRYPTO_PUBLICKEYBYTES SMAUGT_PUBLICKEY_BYTES
+#define CRYPTO_CIPHERTEXTBYTES SMAUGT_CIPHERTEXT_BYTES
+#define CRYPTO_BYTES SMAUGT_SHARED_SECRETE_BYTES
+#define CRYPTO_ALGNAME SMAUGT_CRYPTO_ALGNAME
+
+#define crypto_kem_keypair_internal SMAUGT_NAMESPACE(keypair_internal)
+#define crypto_kem_keypair SMAUGT_NAMESPACE(keypair)
+#define crypto_kem_enc_internal SMAUGT_NAMESPACE(enc_internal)
+#define crypto_kem_enc SMAUGT_NAMESPACE(enc)
+#define crypto_kem_dec_internal SMAUGT_NAMESPACE(dec_internal)
+#define crypto_kem_dec SMAUGT_NAMESPACE(dec)
+
+int crypto_kem_keypair_internal(uint8_t *pk, uint8_t *sk,
+                                uint8_t d[SMAUGT_T_BYTES],
+                                uint8_t seed[SMAUGT_CRYPTO_BYTES]);
+int crypto_kem_keypair(uint8_t *pk, uint8_t *sk);
+
+int crypto_kem_enc_internal(uint8_t *ctxt, uint8_t *ss, const uint8_t *pk,
+                            const uint8_t *mu);
+int crypto_kem_enc(uint8_t *ctxt, uint8_t *ss, const uint8_t *pk);
+
+int crypto_kem_dec_internal(uint8_t *ss, const uint8_t *ctxt,
+                            const uint8_t *sk);
+int crypto_kem_dec(uint8_t *ss, const uint8_t *ctxt, const uint8_t *sk);
+
+#endif /* !SMAUGT_API_H */
diff --git a/cbits/third_party/SMAUG-T/include/cbd.h b/cbits/third_party/SMAUG-T/include/cbd.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/cbd.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_CBD_H
+#define SMAUGT_CBD_H
+
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+#define sp_cbd SMAUGT_NAMESPACE(sp_cbd)
+
+void sp_cbd(poly *r, const uint8_t buf[SMAUGT_CBDSEED_BYTES]);
+
+#endif /* !SMAUGT_CBD_H */
diff --git a/cbits/third_party/SMAUG-T/include/ciphertext.h b/cbits/third_party/SMAUG-T/include/ciphertext.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/ciphertext.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_CIPHERTEXT_H
+#define SMAUGT_CIPHERTEXT_H
+
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+#if SMAUGT_MODE == SMAUGT_MODE1
+#define SMAUGT_RD_ADD 0x80    // 2^(15 - SMAUGT_LOG_P)
+#define SMAUGT_RD_AND 0xff00  // 2^16 - 2^(16 - SMAUGT_LOG_P)
+#define SMAUGT_RD_ADD2 0x0400 // 2^(15 - SMAUGT_LOG_P_PRIME)
+#define SMAUGT_RD_AND2 0xf800 // 2^16 - 2^(16 - SMAUGT_LOG_P_PRIME)
+#elif SMAUGT_MODE == SMAUGT_MODE3
+#define SMAUGT_RD_ADD 0x40    // 2^(15 - SMAUGT_LOG_P)
+#define SMAUGT_RD_AND 0xff80  // 2^16 - 2^(16 - SMAUGT_LOG_P)
+#define SMAUGT_RD_ADD2 0x0800 // 2^(15 - SMAUGT_LOG_P_PRIME)
+#define SMAUGT_RD_AND2 0xf000 // 2^16 - 2^(16 - SMAUGT_LOG_P_PRIME)
+#elif SMAUGT_MODE == SMAUGT_MODE5
+#define SMAUGT_RD_ADD 0x40    // 2^(15 - SMAUGT_LOG_P)
+#define SMAUGT_RD_AND 0xff80  // 2^16 - 2^(16 - SMAUGT_LOG_P)
+#define SMAUGT_RD_ADD2 0x0100 // 2^(15 - SMAUGT_LOG_P_PRIME)
+#define SMAUGT_RD_AND2 0xfe00 // 2^16 - 2^(16 - SMAUGT_LOG_P_PRIME)
+#elif SMAUGT_MODE == SMAUGT_MODET
+#define SMAUGT_RD_ADD 0x80    // 2^(15 - SMAUGT_LOG_P)
+#define SMAUGT_RD_AND 0xff00  // 2^16 - 2^(16 - SMAUGT_LOG_P)
+#define SMAUGT_RD_ADD2 0x1000 // 2^(15 - SMAUGT_LOG_P_PRIME)
+#define SMAUGT_RD_AND2 0xe000 // 2^16 - 2^(16 - SMAUGT_LOG_P_PRIME)
+#endif
+
+typedef struct Ciphertext {
+    polyvec c1;
+    poly c2;
+} ciphertext;
+
+#define computeC1 SMAUGT_NAMESPACE(computeC1)
+#define computeC2 SMAUGT_NAMESPACE(computeC2)
+
+void computeC1(polyvec *c1, const polyvec A[SMAUGT_K], const polyvec *r);
+void computeC2(poly *c2, const uint8_t mu[SMAUGT_MSG_BYTES], const polyvec *b,
+               const polyvec *r);
+
+#endif /* !SMAUGT_CIPHERTEXT_H */
diff --git a/cbits/third_party/SMAUG-T/include/common.h b/cbits/third_party/SMAUG-T/include/common.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/common.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_COMMON_H
+#define SMAUGT_COMMON_H
+
+#define SMAUGT_CONCAT_(x1, x2) x1##x2
+#define SMAUGT_CONCAT(x1, x2) SMAUGT_CONCAT_(x1, x2)
+
+#define SMAUGT_STR_(x) #x
+#define SMAUGT_STR(x) SMAUGT_STR_(x)
+
+#endif /* !SMAUGT_COMMON_H */
diff --git a/cbits/third_party/SMAUG-T/include/config.h b/cbits/third_party/SMAUG-T/include/config.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/config.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_CONFIG_H
+#define SMAUGT_CONFIG_H
+
+#include "common.h"
+
+#define SMAUGT_MODE1 0
+#define SMAUGT_MODE3 1
+#define SMAUGT_MODE5 2
+#define SMAUGT_MODET 3
+
+#if !defined(SMAUGT_CONFIG_MODE)
+#define SMAUGT_CONFIG_MODE SMAUGT_MODE1
+#endif
+
+#if (SMAUGT_CONFIG_MODE != SMAUGT_MODE1) &&                                    \
+    (SMAUGT_CONFIG_MODE != SMAUGT_MODE3) &&                                    \
+    (SMAUGT_CONFIG_MODE != SMAUGT_MODE5) &&                                    \
+    (SMAUGT_CONFIG_MODE != SMAUGT_MODET)
+#error "Invalid SMAUGT_CONFIG_MODE"
+#endif
+
+#if !defined(SMAUGT_CONFIG_MODE_STRING)
+
+#if SMAUGT_CONFIG_MODE == SMAUGT_MODE1
+#define SMAUGT_CONFIG_MODE_STRING mode1
+#elif SMAUGT_CONFIG_MODE == SMAUGT_MODE3
+#define SMAUGT_CONFIG_MODE_STRING mode3
+#elif SMAUGT_CONFIG_MODE == SMAUGT_MODE5
+#define SMAUGT_CONFIG_MODE_STRING mode5
+#elif SMAUGT_CONFIG_MODE == SMAUGT_MODET
+#define SMAUGT_CONFIG_MODE_STRING modet
+#endif
+
+#endif
+
+#if !defined(SMAUGT_CONFIG_NAMESPACE_PREFIX)
+#define SMAUGT_CONFIG_NAMESPACE_PREFIX cryptolab_smaugt_
+#endif
+
+#if !defined(SMAUGT_CONFIG_NAMESPACE_PREFIX_MODE)
+#define SMAUGT_ADD_MODE(s) SMAUGT_CONCAT(s, SMAUGT_CONFIG_MODE_STRING)
+#define SMAUGT_CONFIG_NAMESPACE_PREFIX_MODE                                    \
+    SMAUGT_CONCAT(SMAUGT_ADD_MODE(SMAUGT_CONFIG_NAMESPACE_PREFIX), _)
+#endif
+
+#endif /* !SMAUGT_CONFIG_H */
diff --git a/cbits/third_party/SMAUG-T/include/dg.h b/cbits/third_party/SMAUG-T/include/dg.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/dg.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_DG_H
+#define SMAUGT_DG_H
+
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+// error distribution for LWE instances in public key generation:
+// store probability table (CDF_TABLE)
+// sample a single error from (SMAUGT_DG_RAND_BITS)-length of random binary
+// string A discrete error distribution close to the discrete Gaussian
+// distribution
+#define SMAUGT_DG_RAND_BITS 10 // bits for RND + SIGN
+#define SMAUGT_DG_SMAUGT_SLEN 2
+#define SMAUGT_DG_SMAUGT_SEED_LEN                                              \
+    (SMAUGT_DG_RAND_BITS * SMAUGT_N / 64) // 64-bit seed length
+
+#define d_gaussian_poly SMAUGT_NAMESPACE(d_gaussian_poly)
+#define d_gaussian SMAUGT_NAMESPACE(d_gaussian)
+
+int d_gaussian_poly(poly *op, const uint8_t *seed);
+
+void d_gaussian(polyvec *op, const uint8_t seed[SMAUGT_CRYPTO_BYTES]);
+
+#endif /* !SMAUGT_DG_H */
diff --git a/cbits/third_party/SMAUG-T/include/fips202.h b/cbits/third_party/SMAUG-T/include/fips202.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/fips202.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_FIPS202_H
+#define SMAUGT_FIPS202_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHAKE128_RATE 168
+#define SHAKE256_RATE 136
+#define SHA3_256_RATE 136
+#define SHA3_512_RATE 72
+
+#define SHA3_256_HashSize 32
+#define SHA3_512_HashSize 64
+
+typedef struct {
+    uint64_t s[25];
+    unsigned int pos;
+} keccak_state;
+
+void shake128_init(keccak_state *state);
+void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake128_finalize(keccak_state *state);
+void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
+void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
+
+void shake256_init(keccak_state *state);
+void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake256_finalize(keccak_state *state);
+void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
+void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
+
+void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);
+void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);
+void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen);
+void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen);
+
+#endif /* !SMAUGT_FIPS202_H */
diff --git a/cbits/third_party/SMAUG-T/include/hash.h b/cbits/third_party/SMAUG-T/include/hash.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/hash.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_HASH_H
+#define SMAUGT_HASH_H
+
+#include "params.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define hash_h(OUT, IN, INLEN) sha3_256(OUT, IN, INLEN)
+#define hash_g(OUT, OUTLEN, IN1, INLEN1, IN2, INLEN2)                          \
+    shake256_absorb_twice_squeeze(OUT, OUTLEN, IN1, INLEN1, IN2, INLEN2)
+#define shake256_absorb_twice_squeeze                                          \
+    SMAUGT_NAMESPACE(shake256_absorb_twice_squeeze)
+
+void shake256_absorb_twice_squeeze(uint8_t *out, size_t outlen,
+                                   const uint8_t *in1, size_t inlen1,
+                                   const uint8_t *in2, size_t inlen2);
+
+#endif /* !SMAUGT_HASH_H */
diff --git a/cbits/third_party/SMAUG-T/include/hwt.h b/cbits/third_party/SMAUG-T/include/hwt.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/hwt.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_HWT_H
+#define SMAUGT_HWT_H
+
+#include "params.h"
+
+#include <stdint.h>
+
+// https://github.com/dgazzoni/NTRU-sampling/blob/main/jupyter/NTRU-sampling.ipynb
+// Use enough seed size 308
+#define SMAUGT_HWTSEEDBYTES ((16 * 308) / 8)
+
+// Hamming weight of coefficient vector s
+#if SMAUGT_MODE == SMAUGT_MODE1
+#define SMAUGT_HS 70
+#elif SMAUGT_MODE == SMAUGT_MODE3
+#define SMAUGT_HS 88
+#elif SMAUGT_MODE == SMAUGT_MODE5
+#define SMAUGT_HS 87
+#elif SMAUGT_MODE == SMAUGT_MODET
+#define SMAUGT_HS 70
+#endif
+
+#define hwt SMAUGT_NAMESPACE(hwt)
+int hwt(int16_t *res, const uint8_t *seed);
+
+#endif /* !SMAUGT_HWT_H */
diff --git a/cbits/third_party/SMAUG-T/include/indcpa.h b/cbits/third_party/SMAUG-T/include/indcpa.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/indcpa.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_INDCPA_H
+#define SMAUGT_INDCPA_H
+
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+#define SMAUGT_DEC_ADD 0x4000 // 2^(15 - SMAUGT_LOG_T)
+
+#define expand_r SMAUGT_NAMESPACE(expand_r)
+#define indcpa_keypair SMAUGT_NAMESPACE(indcpa_keypair)
+#define indcpa_enc SMAUGT_NAMESPACE(indcpa_enc)
+#define indcpa_dec SMAUGT_NAMESPACE(indcpa_dec)
+
+void expand_r(polyvec *r, const uint8_t *seed);
+
+void indcpa_keypair(uint8_t pk[SMAUGT_PUBLICKEY_BYTES],
+                    uint8_t sk[SMAUGT_PKE_SECRETKEY_BYTES],
+                    const uint8_t seed[SMAUGT_CRYPTO_BYTES]);
+
+void indcpa_enc(uint8_t ctxt[SMAUGT_CIPHERTEXT_BYTES],
+                const uint8_t pk[SMAUGT_PUBLICKEY_BYTES],
+                const uint8_t mu[SMAUGT_MSG_BYTES],
+                const uint8_t seed[SMAUGT_DELTA_BYTES]);
+
+void indcpa_dec(uint8_t mu[SMAUGT_MSG_BYTES],
+                const uint8_t sk[SMAUGT_PKE_SECRETKEY_BYTES],
+                const uint8_t ctxt[SMAUGT_CIPHERTEXT_BYTES]);
+
+#endif /* !SMAUGT_INDCPA_H */
diff --git a/cbits/third_party/SMAUG-T/include/key.h b/cbits/third_party/SMAUG-T/include/key.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/key.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_KEY_H
+#define SMAUGT_KEY_H
+
+#include "poly.h"
+
+#include <stdint.h>
+
+typedef polyvec secret_key;
+
+typedef struct PublicKey {
+    uint8_t seed[SMAUGT_PKSEED_BYTES];
+    polyvec A[SMAUGT_K];
+    polyvec b;
+} public_key;
+
+#define expand_A SMAUGT_NAMESPACE(expand_A)
+#define expand_b SMAUGT_NAMESPACE(expand_b)
+#define expand_s SMAUGT_NAMESPACE(expand_s)
+#define gen_pub_key SMAUGT_NAMESPACE(gen_pub_key)
+
+void expand_A(polyvec A[SMAUGT_K],
+              const unsigned char seed[SMAUGT_PKSEED_BYTES]);
+
+void expand_b(polyvec *b, const polyvec A[SMAUGT_K], const polyvec *s,
+              const uint8_t e_seed[SMAUGT_CRYPTO_BYTES]);
+
+void expand_s(secret_key *sk, const uint8_t seed[SMAUGT_CRYPTO_BYTES]);
+
+void gen_pub_key(public_key *pk, const secret_key *sk,
+                 const uint8_t err_seed[SMAUGT_CRYPTO_BYTES]);
+
+#endif /* !SMAUGT_KEY_H */
diff --git a/cbits/third_party/SMAUG-T/include/pack.h b/cbits/third_party/SMAUG-T/include/pack.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/pack.h
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_PACK_H
+#define SMAUGT_PACK_H
+
+#include "ciphertext.h"
+#include "key.h"
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+#define pack_enck SMAUGT_NAMESPACE(pack_enck)
+#define unpack_enck SMAUGT_NAMESPACE(unpack_enck)
+
+#define pack_deck SMAUGT_NAMESPACE(pack_deck)
+#define unpack_deck SMAUGT_NAMESPACE(unpack_deck)
+
+#define pack_s_poly SMAUGT_NAMESPACE(pack_s_poly)
+#define unpack_s_poly SMAUGT_NAMESPACE(unpack_s_poly)
+
+#define pack_ct SMAUGT_NAMESPACE(pack_ct)
+#define unpack_ct SMAUGT_NAMESPACE(unpack_ct)
+
+#define pack_ring SMAUGT_NAMESPACE(pack_ring)
+#define unpack_ring SMAUGT_NAMESPACE(unpack_ring)
+
+#define pack_ring_p SMAUGT_NAMESPACE(pack_ring_p)
+#define unpack_ring_p SMAUGT_NAMESPACE(unpack_ring_p)
+
+#define pack_ring_p_prime SMAUGT_NAMESPACE(pack_ring_p_prime)
+#define unpack_ring_p_prime SMAUGT_NAMESPACE(unpack_ring_p_prime)
+
+#define pack_ring_vec SMAUGT_NAMESPACE(pack_ring_vec)
+#define unpack_ring_vec SMAUGT_NAMESPACE(unpack_ring_vec)
+
+#define pack_ring_p_vec SMAUGT_NAMESPACE(pack_ring_p_vec)
+#define unpack_ring_p_vec SMAUGT_NAMESPACE(unpack_ring_p_vec)
+
+#define pack_ring_mat SMAUGT_NAMESPACE(pack_ring_mat)
+#define unpack_ring_mat SMAUGT_NAMESPACE(unpack_ring_mat)
+
+void pack_enck(uint8_t *output, const public_key *pk);
+void unpack_enck(public_key *pk, const uint8_t *input);
+
+void pack_deck(uint8_t *output, const secret_key *sk);
+void unpack_deck(secret_key *sk, const uint8_t *input);
+
+void pack_s_poly(uint8_t *bytes, const poly *data);
+void unpack_s_poly(poly *data, const uint8_t *bytes);
+
+void pack_ct(uint8_t *output, const ciphertext *ctxt);
+void unpack_ct(ciphertext *ctxt, const uint8_t *input);
+
+void pack_ring(uint8_t bytes[SMAUGT_PKPOLY_BYTES], const poly *data);
+void unpack_ring(poly *data, const uint8_t bytes[SMAUGT_PKPOLY_BYTES]);
+
+void pack_ring_p(uint8_t bytes[SMAUGT_CTPOLY1_BYTES], const poly *data);
+void unpack_ring_p(poly *data, const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]);
+
+void pack_ring_p_prime(uint8_t bytes[SMAUGT_CTPOLY2_BYTES], const poly *data);
+void unpack_ring_p_prime(poly *data, const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]);
+
+void pack_ring_vec(uint8_t bytes[SMAUGT_PKPOLYVEC_BYTES], const polyvec *data);
+void unpack_ring_vec(polyvec *data,
+                     const uint8_t bytes[SMAUGT_PKPOLYVEC_BYTES]);
+
+void pack_ring_p_vec(uint8_t bytes[SMAUGT_CTPOLYVEC_BYTES],
+                     const polyvec *data);
+void unpack_ring_p_vec(polyvec *data,
+                       const uint8_t bytes[SMAUGT_CTPOLYVEC_BYTES]);
+
+void pack_ring_mat(uint8_t bytes[SMAUGT_PKPOLYMAT_BYTES],
+                   const polyvec data[SMAUGT_K]);
+void unpack_ring_mat(polyvec data[SMAUGT_K],
+                     const uint8_t bytes[SMAUGT_PKPOLYMAT_BYTES]);
+
+#endif /* !SMAUGT_PACK_H */
diff --git a/cbits/third_party/SMAUG-T/include/packring.h b/cbits/third_party/SMAUG-T/include/packring.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/packring.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_PACK_RING_H
+#define SMAUGT_PACK_RING_H
+
+#include "params.h"
+#include "poly.h"
+
+#include <stdint.h>
+
+#define _CONCAT(a, b) a##b
+#define CONCAT(a, b) _CONCAT(a, b)
+
+#define PACK_RING(q) CONCAT(pack_R2_, q)
+#define UNPACK_RING(q) CONCAT(unpack_R2_, q)
+
+#define pack_R2_3 SMAUGT_NAMESPACE(pack_R2_3)
+#define unpack_R2_3 SMAUGT_NAMESPACE(unpack_R2_3)
+
+#define pack_R2_4 SMAUGT_NAMESPACE(pack_R2_4)
+#define unpack_R2_4 SMAUGT_NAMESPACE(unpack_R2_4)
+
+#define pack_R2_5 SMAUGT_NAMESPACE(pack_R2_5)
+#define unpack_R2_5 SMAUGT_NAMESPACE(unpack_R2_5)
+
+#define pack_R2_7 SMAUGT_NAMESPACE(pack_R2_7)
+#define unpack_R2_7 SMAUGT_NAMESPACE(unpack_R2_7)
+
+#define pack_R2_8 SMAUGT_NAMESPACE(pack_R2_8)
+#define unpack_R2_8 SMAUGT_NAMESPACE(unpack_R2_8)
+
+#define pack_R2_9 SMAUGT_NAMESPACE(pack_R2_9)
+#define unpack_R2_9 SMAUGT_NAMESPACE(unpack_R2_9)
+
+#define pack_R2_10 SMAUGT_NAMESPACE(pack_R2_10)
+#define unpack_R2_10 SMAUGT_NAMESPACE(unpack_R2_10)
+
+#define pack_R2_11 SMAUGT_NAMESPACE(pack_R2_11)
+#define unpack_R2_11 SMAUGT_NAMESPACE(unpack_R2_11)
+
+void PACK_RING(SMAUGT_LOG_Q)(uint8_t bytes[SMAUGT_PKPOLY_BYTES],
+                             const poly *data);
+void UNPACK_RING(SMAUGT_LOG_Q)(
+    poly *data, const uint8_t bytes[SMAUGT_PKPOLY_BYTES]);
+
+void PACK_RING(SMAUGT_LOG_P)(uint8_t bytes[SMAUGT_CTPOLY1_BYTES],
+                             const poly *data);
+void UNPACK_RING(SMAUGT_LOG_P)(
+    poly *data, const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]);
+
+void PACK_RING(SMAUGT_LOG_P_PRIME)(uint8_t bytes[SMAUGT_CTPOLY2_BYTES],
+                                   const poly *data);
+void UNPACK_RING(SMAUGT_LOG_P_PRIME)(
+    poly *data, const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]);
+
+#endif /* !SMAUGT_PACK_RING_H */
diff --git a/cbits/third_party/SMAUG-T/include/params.h b/cbits/third_party/SMAUG-T/include/params.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/params.h
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_PARAMS_H
+#define SMAUGT_PARAMS_H
+
+#include "config.h"
+
+#define SMAUGT_MODE SMAUGT_CONFIG_MODE
+#define SMAUGT_NAMESPACE(s)                                                    \
+    SMAUGT_CONCAT(SMAUGT_CONFIG_NAMESPACE_PREFIX_MODE, s)
+#define SMAUGT_CRYPTO_ALGNAME                                                  \
+    SMAUGT_STR(SMAUGT_CONCAT(smaugt_, SMAUGT_CONFIG_MODE_STRING))
+
+#define SMAUGT_N 256                      // LWE dim and LWR dim
+#define SMAUGT_DELTA_BYTES (SMAUGT_N / 8) // 32
+#define SMAUGT_T_BYTES (SMAUGT_N / 8)     // 32
+#define SMAUGT_LOG_T 1                    // plaintext modulus
+
+#if SMAUGT_MODE == SMAUGT_MODE1
+#define SMAUGT_K 2           // rank of the module, in (2, 3, 4)
+#define SMAUGT_LOG_Q 10      // public key modulus
+#define SMAUGT_LOG_P 8       // ciphertext modulus
+#define SMAUGT_LOG_P_PRIME 5 // ciphertext2 modulus
+#define SMAUGT_CBDSEED_BYTES ((3 * SMAUGT_N) / 8)
+#define SMAUGT_MSG_BYTES SMAUGT_DELTA_BYTES
+#elif SMAUGT_MODE == SMAUGT_MODE3
+#define SMAUGT_K 3           // rank of the module, in (2, 3, 4)
+#define SMAUGT_LOG_Q 11      // public key modulus
+#define SMAUGT_LOG_P 9       // ciphertext modulus
+#define SMAUGT_LOG_P_PRIME 4 // ciphertext2 modulus
+#define SMAUGT_CBDSEED_BYTES ((2 * SMAUGT_N) / 8)
+#define SMAUGT_MSG_BYTES SMAUGT_DELTA_BYTES
+#elif SMAUGT_MODE == SMAUGT_MODE5
+#define SMAUGT_K 4           // rank of the module, in (2, 3, 4)
+#define SMAUGT_LOG_Q 11      // public key modulus
+#define SMAUGT_LOG_P 9       // ciphertext modulus
+#define SMAUGT_LOG_P_PRIME 7 // ciphertext2 modulus
+#define SMAUGT_CBDSEED_BYTES ((4 * SMAUGT_N) / 8)
+#define SMAUGT_MSG_BYTES SMAUGT_DELTA_BYTES
+#elif SMAUGT_MODE == SMAUGT_MODET
+#define SMAUGT_K 2           // rank of the module, in (2, 3, 4)
+#define SMAUGT_LOG_Q 10      // public key modulus
+#define SMAUGT_LOG_P 8       // ciphertext modulus
+#define SMAUGT_LOG_P_PRIME 3 // ciphertext2 modulus
+#define SMAUGT_CBDSEED_BYTES ((3 * SMAUGT_N) / 8)
+#define SMAUGT_MSG_BYTES 16 // for D2
+#endif
+
+#define SMAUGT_MODULUS_16_LOG_Q                                                \
+    (16 - SMAUGT_LOG_Q) // modulus (16 - SMAUGT_LOG_Q)
+#define SMAUGT_MODULUS_16_LOG_P                                                \
+    (16 - SMAUGT_LOG_P) // modulus (16 - SMAUGT_LOG_P)
+#define SMAUGT_MODULUS_16_LOG_P_PRIME                                          \
+    (16 - SMAUGT_LOG_P_PRIME) // modulus (16 - SMAUGT_LOG_P_PRIME)
+#define SMAUGT_MODULUS_16_LOG_T                                                \
+    (16 - SMAUGT_LOG_T) // modulus (16 - SMAUGT_LOG_T)
+
+// Size of keys and ciphertext
+#define SMAUGT_SHARED_SECRETE_BYTES (32)                   // 32
+#define SMAUGT_CRYPTO_BYTES SMAUGT_SHARED_SECRETE_BYTES    // 32
+#define SMAUGT_CTPOLY1_BYTES (SMAUGT_LOG_P * SMAUGT_N / 8) // element in R_p
+#define SMAUGT_CTPOLY2_BYTES                                                   \
+    (SMAUGT_LOG_P_PRIME * SMAUGT_N / 8)    // element in R_p'
+#define SMAUGT_SKPOLY_BYTES (SMAUGT_N / 4) // 64
+#define SMAUGT_SKPOLYVEC_BYTES                                                 \
+    (SMAUGT_SKPOLY_BYTES * SMAUGT_K) // vector of secret polynomials
+#define SMAUGT_CTPOLYVEC_BYTES                                                 \
+    (SMAUGT_CTPOLY1_BYTES * SMAUGT_K) // vector with element in R_p
+#define SMAUGT_CIPHERTEXT_BYTES                                                \
+    (SMAUGT_CTPOLYVEC_BYTES + SMAUGT_CTPOLY2_BYTES)         // (vector c21, c22)
+#define SMAUGT_PKSEED_BYTES (32)                            // seed for a(x) 32
+#define SMAUGT_PKPOLY_BYTES ((SMAUGT_LOG_Q * SMAUGT_N) / 8) // b(x)
+#define SMAUGT_PKPOLYVEC_BYTES                                                 \
+    (SMAUGT_PKPOLY_BYTES * SMAUGT_K) // vector with element in R_q
+#define SMAUGT_PKPOLYMAT_BYTES                                                 \
+    (SMAUGT_PKPOLYVEC_BYTES * SMAUGT_K) // matrix with element in R_q
+#define SMAUGT_PUBLICKEY_BYTES                                                 \
+    (SMAUGT_PKSEED_BYTES + SMAUGT_PKPOLYVEC_BYTES) // (A seed, b(x) vector)
+#define SMAUGT_PKE_SECRETKEY_BYTES (SMAUGT_SKPOLYVEC_BYTES) // s(x) vector
+#define SMAUGT_KEM_SECRETKEY_BYTES                                             \
+    (SMAUGT_PKE_SECRETKEY_BYTES + SMAUGT_T_BYTES +                             \
+     SMAUGT_PUBLICKEY_BYTES) // s(x) vector, t, pk
+
+#endif /* !SMAUGT_PARAMS_H */
diff --git a/cbits/third_party/SMAUG-T/include/poly.h b/cbits/third_party/SMAUG-T/include/poly.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/poly.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_POLY_H
+#define SMAUGT_POLY_H
+
+#include "params.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#if SMAUGT_MODE == SMAUGT_MODET
+#define SMAUGT_MODULUS_SCALED_Q_HALF 32767 // for D2, Q/2 = 2^15 - 1
+#endif
+
+typedef struct {
+    int16_t coeffs[SMAUGT_N];
+} poly;
+
+typedef struct {
+    poly vec[SMAUGT_K];
+} polyvec;
+
+#define vec_vec_mult_add SMAUGT_NAMESPACE(vec_vec_mult_add)
+#define matrix_vec_mult_add SMAUGT_NAMESPACE(matrix_vec_mult_add)
+#define matrix_vec_mult_sub SMAUGT_NAMESPACE(matrix_vec_mult_sub)
+
+void vec_vec_mult_add(poly *r, const polyvec *a, const polyvec *b, uint8_t mod);
+void matrix_vec_mult_add(polyvec *r, const polyvec a[SMAUGT_K],
+                         const polyvec *b);
+void matrix_vec_mult_sub(polyvec *r, const polyvec a[SMAUGT_K],
+                         const polyvec *b);
+
+#if SMAUGT_MODE == SMAUGT_MODET
+#define d2_ecd SMAUGT_NAMESPACE(d2_ecd)
+#define d2_dcd SMAUGT_NAMESPACE(d2_dcd)
+
+void d2_ecd(poly *r, const uint8_t msg[SMAUGT_MSG_BYTES]);
+void d2_dcd(uint8_t msg[SMAUGT_MSG_BYTES], const poly *x);
+#endif
+
+#endif /* !SMAUGT_POLY_H */
diff --git a/cbits/third_party/SMAUG-T/include/randombytes.h b/cbits/third_party/SMAUG-T/include/randombytes.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/randombytes.h
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_RANDOMBYTES_H
+#define SMAUGT_RANDOMBYTES_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+int randombytes(uint8_t *x, size_t xlen);
+
+#endif /* !SMAUGT_RANDOMBYTES_H */
diff --git a/cbits/third_party/SMAUG-T/include/toomcook.h b/cbits/third_party/SMAUG-T/include/toomcook.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/toomcook.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_TOOMCOOK_H
+#define SMAUGT_TOOMCOOK_H
+
+#include "params.h"
+
+#include <stdint.h>
+
+#define poly_mul_acc SMAUGT_NAMESPACE(poly_mul_acc)
+void poly_mul_acc(const int16_t a[SMAUGT_N], const int16_t b[SMAUGT_N],
+                  int16_t res[SMAUGT_N]);
+
+#endif /* !SMAUGT_TOOMCOOK_H */
diff --git a/cbits/third_party/SMAUG-T/include/verify.h b/cbits/third_party/SMAUG-T/include/verify.h
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/include/verify.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: MIT
+
+#ifndef SMAUGT_VERIFY_H
+#define SMAUGT_VERIFY_H
+
+#include "params.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define verify SMAUGT_NAMESPACE(verify)
+int verify(const uint8_t *a, const uint8_t *b, size_t len);
+#define cmov SMAUGT_NAMESPACE(cmov)
+void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b);
+
+#endif /* !SMAUGT_VERIFY_H */
diff --git a/cbits/third_party/SMAUG-T/src/cbd.c b/cbits/third_party/SMAUG-T/src/cbd.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/cbd.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: MIT
+
+#include "cbd.h"
+
+#if SMAUGT_MODE == SMAUGT_MODE1 || SMAUGT_MODE == SMAUGT_MODET
+#define load24_littleendian SMAUGT_NAMESPACE(load24_littleendian)
+static uint32_t load24_littleendian(const uint8_t x[3]) {
+    uint32_t r;
+    r = (uint32_t)x[0];
+    r |= (uint32_t)x[1] << 8;
+    r |= (uint32_t)x[2] << 16;
+    return r;
+}
+
+/*************************************************
+ * Name:        sp_cbd1
+ *
+ * Description: Given an array of uniformly random bytes, compute
+ *              polynomial with coefficients distributed according to
+ *              a modified centered binomial distribution with parameter eta=2
+ *              (p(0)=3/4, p(1)=p(-1)=1/8)
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t buf[SMAUGT_CBDSEED_BYTES]: pointer to input byte
+ *              array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 7, spCBD1]
+ **************************************************/
+#define sp_cbd1 SMAUGT_NAMESPACE(sp_cbd1)
+static void sp_cbd1(poly *r, const uint8_t buf[SMAUGT_CBDSEED_BYTES]) {
+    unsigned int i, j;
+    uint32_t t, d, s;
+    int16_t a;
+
+    for (i = 0; i < SMAUGT_N / 8; i++) {
+        t = load24_littleendian(buf + 3 * i);
+        d = t & 0x00249249;
+        d &= (t >> 1) & 0x00249249;
+        s = (t >> 2) & 0x00249249;
+
+        for (j = 0; j < 8; j++) {
+            a = (d >> (3 * j)) & 0x1;
+            r->coeffs[8 * i + j] =
+                a * (((((s >> (3 * j)) & 0x1) - 1) ^ -2) | 1);
+        }
+    }
+}
+#endif
+
+#if SMAUGT_MODE == SMAUGT_MODE3 || SMAUGT_MODE == SMAUGT_MODE5
+/*************************************************
+ * Name:        load32_littleendian
+ *
+ * Description: load 4 bytes into a 32-bit integer
+ *              in little-endian order
+ *
+ * Arguments:   - const uint8_t x[4]: pointer to input byte array
+ *
+ * Return: 32-bit unsigned integer loaded from x
+ **************************************************/
+#define load32_littleendian SMAUGT_NAMESPACE(load32_littleendian)
+static uint32_t load32_littleendian(const uint8_t x[4]) {
+    uint32_t r;
+    r = (uint32_t)x[0];
+    r |= (uint32_t)x[1] << 8;
+    r |= (uint32_t)x[2] << 16;
+    r |= (uint32_t)x[3] << 24;
+    return r;
+}
+#endif
+
+#if SMAUGT_MODE == SMAUGT_MODE3
+/*************************************************
+ * Name:        cbd
+ *
+ * Description: Given an array of uniformly random bytes, compute
+ *              polynomial with coefficients distributed according to
+ *              a centered binomial distribution with parameter eta=1
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t buf[SMAUGT_CBDSEED_BYTES]: pointer to input byte
+ *              array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 9, CBD]
+ **************************************************/
+#define cbd SMAUGT_NAMESPACE(cbd)
+static void cbd(poly *r, const uint8_t buf[SMAUGT_CBDSEED_BYTES]) {
+    unsigned int i, j;
+    uint32_t t;
+    int16_t a, b;
+
+    for (i = 0; i < SMAUGT_N / 16; i++) {
+        t = load32_littleendian(buf + 4 * i);
+
+        for (j = 0; j < 16; j++) {
+            a = (t >> (2 * j + 0)) & 0x01;
+            b = (t >> (2 * j + 1)) & 0x01;
+            r->coeffs[16 * i + j] = a - b;
+        }
+    }
+}
+#endif
+#if SMAUGT_MODE == SMAUGT_MODE5
+/*************************************************
+ * Name:        sp_cbd2
+ *
+ * Description: Given an array of uniformly random bytes, compute
+ *              polynomial with coefficients distributed according to
+ *              a modified centered binomial distribution with parameter eta=2
+ *              (p(0)=5/8, p(1)=p(-1)=3/16)
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t buf[SMAUGT_CBDSEED_BYTES]: pointer to input byte
+ *              array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 8, spCBD2]
+ **************************************************/
+#define sp_cbd2 SMAUGT_NAMESPACE(sp_cbd2)
+static void sp_cbd2(poly *r, const uint8_t buf[SMAUGT_CBDSEED_BYTES]) {
+    unsigned int i, j;
+    uint32_t t, s, d;
+    int16_t a;
+
+    for (i = 0; i < SMAUGT_N / 8; i++) {
+        t = load32_littleendian(buf + 4 * i);
+        d = t & 0x11111111;
+        d |= (t >> 1) & 0x11111111;
+        d &= (t >> 2) & 0x11111111;
+        s = (t >> 3) & 0x11111111;
+        for (j = 0; j < 8; j++) {
+            a = (d >> (4 * j)) & 0x1;
+            r->coeffs[8 * i + j] =
+                a * (((((s >> (4 * j)) & 0x1) - 1) ^ -2) | 1);
+        }
+    }
+}
+#endif
+
+/*************************************************
+ * Name:        sp_cbd
+ *
+ * Description: Given an array of uniformly random bytes, compute
+ *              polynomial with coefficients
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t buf[SMAUGT_CBDSEED_BYTES]: pointer to input byte
+ *              array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 6, spCBD]
+ **************************************************/
+void sp_cbd(poly *r, const uint8_t buf[SMAUGT_CBDSEED_BYTES]) {
+#if SMAUGT_MODE == SMAUGT_MODE1 || SMAUGT_MODE == SMAUGT_MODET
+    sp_cbd1(r, buf);
+#elif SMAUGT_MODE == SMAUGT_MODE3
+    cbd(r, buf);
+#elif SMAUGT_MODE == SMAUGT_MODE5
+    sp_cbd2(r, buf);
+#endif
+}
diff --git a/cbits/third_party/SMAUG-T/src/ciphertext.c b/cbits/third_party/SMAUG-T/src/ciphertext.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/ciphertext.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: MIT
+
+#include "ciphertext.h"
+
+/*************************************************
+ * Name:        round1
+ *
+ * Description: rounding q to p
+ *
+ * Arguments:   - polyvec *a: pointer to inout vector a. This should be
+ *                already initialized.
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 12, Round1]
+ **************************************************/
+#define round1 SMAUGT_NAMESPACE(round1)
+static inline void round1(polyvec *a) {
+    unsigned int i, j;
+    for (i = 0; i < SMAUGT_K; ++i) {
+        for (j = 0; j < SMAUGT_N; ++j) {
+            a->vec[i].coeffs[j] =
+                ((a->vec[i].coeffs[j] + SMAUGT_RD_ADD) & SMAUGT_RD_AND) >>
+                SMAUGT_MODULUS_16_LOG_P;
+        }
+    }
+}
+
+/*************************************************
+ * Name:        computeC1
+ *
+ * Description: Compute the first ciphertext c1 = round(p/q * (A * r))
+ *
+ * Arguments:   - polyvec *c1: pointer to output vector c1. The c1 should be
+ *              already initialized
+ *              - const polyvec A[SMAUGT_K]: pointer to input matrix of public A
+ *              - const polyvec *r: pointer to input vector of ephemeral key r
+ **************************************************/
+void computeC1(polyvec *c1, const polyvec A[SMAUGT_K], const polyvec *r) {
+    // c1 = A * r
+    matrix_vec_mult_add(c1, A, r);
+
+    // Rounding q to p
+    round1(c1);
+}
+
+/*************************************************
+ * Name:        round2
+ *
+ * Description: rounding q to p'
+ *
+ * Arguments:   - poly *a: pointer to an input/output vector a. This should be
+ *              already initialized.
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 13, Round2]
+ **************************************************/
+#define round2 SMAUGT_NAMESPACE(round2)
+static inline void round2(poly *a) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_N; ++i) {
+        a->coeffs[i] = ((a->coeffs[i] + SMAUGT_RD_ADD2) & SMAUGT_RD_AND2) >>
+                       SMAUGT_MODULUS_16_LOG_P_PRIME;
+    }
+}
+/*************************************************
+ * Name:        computeC2
+ *
+ * Description: Compute the secode ciphertext c2 = round(p/q * (b^T * r) + msg)
+ *
+ * Arguments:   - poly *c2: pointer to ouptput poly c2. The c2 should be
+ *              already initialized
+ *              - const uint8_t delta[SMAUGT_MSG_BYTES]: pointer to input
+ * message delta
+ *              - const polyvec *b: pointer to input vector of public b
+ *              - const polyvec *r: pointer to input vector of ephemeral key r
+ **************************************************/
+void computeC2(poly *c2, const uint8_t delta[SMAUGT_MSG_BYTES],
+               const polyvec *b, const polyvec *r) {
+#if SMAUGT_MODE == SMAUGT_MODET
+    // c2 = q/2 * delta
+    d2_ecd(c2, delta); // EDIT TiMER
+#else
+    unsigned int i, j;
+    // c2 = q/2 * delta
+    for (i = 0; i < SMAUGT_MSG_BYTES; ++i) {
+        for (j = 0; j < sizeof(uint8_t) * 8; ++j) {
+            c2->coeffs[8 * i + j] =
+                (uint16_t)((delta[i] >> j) << SMAUGT_MODULUS_16_LOG_T);
+        }
+    }
+#endif
+    // c2 = q/2 * delta + (b * r)
+    vec_vec_mult_add(c2, b, r, SMAUGT_MODULUS_16_LOG_Q);
+
+    // Rounding q to p'
+    round2(c2);
+}
diff --git a/cbits/third_party/SMAUG-T/src/dg.c b/cbits/third_party/SMAUG-T/src/dg.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/dg.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: MIT
+
+#include "dg.h"
+#include "fips202.h"
+
+#include <string.h>
+
+/*************************************************
+ * Name:        load64_littleendian
+ *
+ * Description: load 8 bytes into a 64-bit integer
+ *              in little-endian order
+ *
+ * Arguments:   - uint64_t *out: pointer to output int64_t array
+ *              - size_t outlen: output length (should be a multiple of 10)
+ *              - const uint8_t *in: pointer to input byte array
+ **************************************************/
+#define load64_littleendian SMAUGT_NAMESPACE(load64_littleendian)
+static void load64_littleendian(uint64_t *out, size_t outlen,
+                                const uint8_t *in) {
+    unsigned int i, j, pos = 0;
+    for (i = 0; i < outlen / 10; ++i) {
+        for (j = 0; j < 10; j++) {
+            out[10 * i + j] = ((uint64_t)(in[pos + j])) |
+                              ((uint64_t)(in[pos + 10 + j]) << 8) |
+                              ((uint64_t)(in[pos + 20 + j]) << 16) |
+                              ((uint64_t)(in[pos + 30 + j]) << 24) |
+                              ((uint64_t)(in[pos + 40 + j]) << 32) |
+                              ((uint64_t)(in[pos + 50 + j]) << 40) |
+                              ((uint64_t)(in[pos + 60 + j]) << 48) |
+                              ((uint64_t)(in[pos + 70 + j]) << 56);
+        }
+        pos += 80;
+    }
+}
+
+// referenced
+// A. Karmakar, S. S. Roy, O. Reparaz, F. Vercauteren and I.
+// Verbauwhede, "Constant-Time Discrete Gaussian Sampling," in IEEE Transactions
+// on Computers, vol. 67, no. 11, pp. 1561-1571, 1 Nov. 2018,
+// doi: 10.1109/TC.2018.2814587.
+
+// for python metacode that generates this code, please refer to notion page
+// https://www.notion.so/Constant-Time-Discrete-Gaussian-Sampling-25cc46cdf40549eabd4923d01d8ce259
+
+/*************************************************
+ * Name:        d_gaussian_poly
+ *
+ * Description: Sample a discrete Gaussian noise e and add the noise e to the
+ * output vector op
+ *
+ * Arguments:   - poly *op: pointer to output vector op
+ *              - const uint8_t *seed: pointer to input seed (of length
+ *              SMAUGT_CRYPTO_BYTES+1)
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 11, dGaussianPoly]
+ **************************************************/
+int d_gaussian_poly(poly *op, const uint8_t *seed) {
+    unsigned int i = 0, j = 0, k = 0;
+    uint64_t seed_temp[SMAUGT_DG_SMAUGT_SEED_LEN] = {0};
+    uint8_t buf[SMAUGT_DG_SMAUGT_SEED_LEN * 8] = {0};
+    uint64_t s[SMAUGT_DG_SMAUGT_SLEN] = {0};
+    uint64_t *x = NULL;
+
+    shake256(buf, SMAUGT_DG_SMAUGT_SEED_LEN * 8, seed, SMAUGT_CRYPTO_BYTES + 1);
+    load64_littleendian(seed_temp, SMAUGT_DG_SMAUGT_SEED_LEN, buf);
+
+    for (i = 0; i < SMAUGT_N; i += 64) {
+        x = seed_temp + j;
+        s[0] = (x[0] & x[1] & x[2] & x[3] & x[4] & x[5] & x[7] & ~x[8]) |
+               (x[0] & x[3] & x[4] & x[5] & x[6] & x[8]) |
+               (x[1] & x[3] & x[4] & x[5] & x[6] & x[8]) |
+               (x[2] & x[3] & x[4] & x[5] & x[6] & x[8]) |
+               (~x[2] & ~x[3] & ~x[6] & x[8]) | (~x[1] & ~x[3] & ~x[6] & x[8]) |
+               (x[6] & x[7] & ~x[8]) | (~x[5] & ~x[6] & x[8]) |
+               (~x[4] & ~x[6] & x[8]) | (~x[7] & x[8]);
+        s[1] = (x[1] & x[2] & x[4] & x[5] & x[7] & x[8]) |
+               (x[3] & x[4] & x[5] & x[7] & x[8]) | (x[6] & x[7] & x[8]);
+        for (k = 0; k < 64; ++k) {
+            op->coeffs[i + k] =
+                ((s[0] >> k) & 0x01) | (((s[1] >> k) & 0x01) << 1);
+            uint16_t sign = (x[9] >> k) & 0x01;
+            uint16_t signed_coefficient =
+                (uint16_t)(((uint16_t)(0u - sign) ^
+                            (uint16_t)op->coeffs[i + k]) +
+                           sign);
+            op->coeffs[i + k] =
+                (int16_t)(uint16_t)((uint32_t)signed_coefficient
+                                    << SMAUGT_MODULUS_16_LOG_Q);
+        }
+        j += SMAUGT_DG_RAND_BITS;
+    }
+    return 0;
+}
+
+/*************************************************
+ * Name:        d_gaussian
+ *
+ * Description: Given an array of seed bytes, compute
+ *              polynomial with coefficients sampled from a discrete gaussian
+ * distribution
+ *
+ * Arguments:   - polyvec *op: pointer to output polynomial vector
+ *              - const uint8_t seed[SMAUGT_CRYPTO_BYTES]: pointer to input byte
+ * array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 10, dGaussian]
+ **************************************************/
+void d_gaussian(polyvec *op, const uint8_t seed[SMAUGT_CRYPTO_BYTES]) {
+    unsigned int i;
+    uint8_t extseed[SMAUGT_CRYPTO_BYTES + 1] = {0};
+    memcpy(extseed, seed, SMAUGT_CRYPTO_BYTES);
+    for (i = 0; i < SMAUGT_K; ++i) {
+        extseed[SMAUGT_CRYPTO_BYTES] = SMAUGT_K * i;
+        d_gaussian_poly(&(op->vec[i]), extseed);
+    }
+}
diff --git a/cbits/third_party/SMAUG-T/src/fips202.c b/cbits/third_party/SMAUG-T/src/fips202.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/fips202.c
@@ -0,0 +1,745 @@
+// SPDX-License-Identifier: MIT
+
+#include "fips202.h"
+
+#include <stddef.h>
+#include <stdint.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[8]: pointer to input byte array
+ *
+ * Return: loaded 64-bit unsigned integer
+ **************************************************/
+static uint64_t load64(const uint8_t x[8]) {
+    unsigned int i;
+    uint64_t r = 0;
+
+    for (i = 0; i < 8; i++)
+        r |= (uint64_t)x[i] << 8 * i;
+
+    return r;
+}
+
+/*************************************************
+ * Name:        store64
+ *
+ * Description: Store a 64-bit integer to array of 8 bytes in little-endian
+ *order
+ *
+ * Arguments:   - uint8_t x[8]: pointer to output byte array (allocated)
+ *              - uint64_t u: input 64-bit unsigned integer
+ **************************************************/
+static void store64(uint8_t x[8], uint64_t u) {
+    unsigned int i;
+
+    for (i = 0; i < 8; i++)
+        x[i] = u >> 8 * i;
+}
+
+/* Keccak round constants */
+const uint64_t KeccakF_RoundConstants[NROUNDS] = {
+    (uint64_t)0x0000000000000001ULL, (uint64_t)0x0000000000008082ULL,
+    (uint64_t)0x800000000000808aULL, (uint64_t)0x8000000080008000ULL,
+    (uint64_t)0x000000000000808bULL, (uint64_t)0x0000000080000001ULL,
+    (uint64_t)0x8000000080008081ULL, (uint64_t)0x8000000000008009ULL,
+    (uint64_t)0x000000000000008aULL, (uint64_t)0x0000000000000088ULL,
+    (uint64_t)0x0000000080008009ULL, (uint64_t)0x000000008000000aULL,
+    (uint64_t)0x000000008000808bULL, (uint64_t)0x800000000000008bULL,
+    (uint64_t)0x8000000000008089ULL, (uint64_t)0x8000000000008003ULL,
+    (uint64_t)0x8000000000008002ULL, (uint64_t)0x8000000000000080ULL,
+    (uint64_t)0x000000000000800aULL, (uint64_t)0x800000008000000aULL,
+    (uint64_t)0x8000000080008081ULL, (uint64_t)0x8000000000008080ULL,
+    (uint64_t)0x0000000080000001ULL, (uint64_t)0x8000000080008008ULL};
+
+/*************************************************
+ * Name:        KeccakF1600_StatePermute
+ *
+ * Description: The Keccak F1600 Permutation
+ *
+ * Arguments:   - uint64_t state[25]: pointer to input/output Keccak state
+ **************************************************/
+static void KeccakF1600_StatePermute(uint64_t state[25]) {
+    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 ^= (uint64_t)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 ^= (uint64_t)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_init
+ *
+ * Description: Initializes the Keccak state.
+ *
+ * Arguments:   - uint64_t s[25]: pointer to output Keccak state
+ **************************************************/
+static void keccak_init(uint64_t s[25]) {
+    unsigned int i;
+    for (i = 0; i < 25; i++)
+        s[i] = 0;
+}
+
+/*************************************************
+ * Name:        keccak_absorb
+ *
+ * Description: Absorb step of Keccak; incremental.
+ *
+ * Arguments:   - uint64_t s[25]: pointer to input/output Keccak state
+ *              - unsigned int pos: position in current block to be absorbed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ *
+ * Returns new position pos in current block
+ **************************************************/
+static unsigned int keccak_absorb(uint64_t s[25], unsigned int pos,
+                                  unsigned int r, const uint8_t *in,
+                                  size_t inlen) {
+    unsigned int i;
+
+    while (pos + inlen >= r) {
+        for (i = pos; i < r; i++)
+            s[i / 8] ^= (uint64_t)*in++ << 8 * (i % 8);
+        inlen -= r - pos;
+        KeccakF1600_StatePermute(s);
+        pos = 0;
+    }
+
+    for (i = pos; i < pos + inlen; i++)
+        s[i / 8] ^= (uint64_t)*in++ << 8 * (i % 8);
+
+    return i;
+}
+
+/*************************************************
+ * Name:        keccak_finalize
+ *
+ * Description: Finalize absorb step.
+ *
+ * Arguments:   - uint64_t s[25]: pointer to input/output Keccak state
+ *              - unsigned int pos: position in current block to be absorbed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - uint8_t p: domain separation byte
+ **************************************************/
+static void keccak_finalize(uint64_t s[25], unsigned int pos, unsigned int r,
+                            uint8_t p) {
+    s[pos / 8] ^= (uint64_t)p << 8 * (pos % 8);
+    s[r / 8 - 1] ^= 1ULL << 63;
+}
+
+/*************************************************
+ * Name:        keccak_squeeze
+ *
+ * Description: Squeeze step of Keccak. Squeezes arbitratrily many bytes.
+ *              Modifies the state. Can be called multiple times to keep
+ *              squeezing, i.e., is incremental.
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: number of bytes to be squeezed (written to out)
+ *              - uint64_t s[25]: pointer to input/output Keccak state
+ *              - unsigned int pos: number of bytes in current block already
+ *              squeezed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *
+ * Returns new position pos in current block
+ **************************************************/
+static unsigned int keccak_squeeze(uint8_t *out, size_t outlen, uint64_t s[25],
+                                   unsigned int pos, unsigned int r) {
+    unsigned int i;
+
+    while (outlen) {
+        if (pos == r) {
+            KeccakF1600_StatePermute(s);
+            pos = 0;
+        }
+        for (i = pos; i < r && i < pos + outlen; i++)
+            *out++ = s[i / 8] >> 8 * (i % 8);
+        outlen -= i - pos;
+        pos = i;
+    }
+
+    return pos;
+}
+
+/*************************************************
+ * Name:        keccak_absorb_once
+ *
+ * Description: Absorb step of Keccak;
+ *              non-incremental, starts by zeroeing the state.
+ *
+ * Arguments:   - uint64_t s[25]: pointer to (uninitialized) output Keccak state
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ *              - uint8_t p: domain-separation byte for different Keccak-derived
+ *functions
+ **************************************************/
+static void keccak_absorb_once(uint64_t s[25], unsigned int r,
+                               const uint8_t *in, size_t inlen, uint8_t p) {
+    unsigned int i;
+
+    for (i = 0; i < 25; i++)
+        s[i] = 0;
+
+    while (inlen >= r) {
+        for (i = 0; i < r / 8; i++)
+            s[i] ^= load64(in + 8 * i);
+        in += r;
+        inlen -= r;
+        KeccakF1600_StatePermute(s);
+    }
+
+    for (i = 0; i < inlen; i++)
+        s[i / 8] ^= (uint64_t)in[i] << 8 * (i % 8);
+
+    s[i / 8] ^= (uint64_t)p << 8 * (i % 8);
+    s[(r - 1) / 8] ^= 1ULL << 63;
+}
+
+/*************************************************
+ * 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. Assumes zero bytes of current
+ *              block have already been squeezed.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *              out)
+ *              - uint64_t s[25]: pointer to input/output Keccak state
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ **************************************************/
+static void keccak_squeezeblocks(uint8_t *out, size_t nblocks, uint64_t s[25],
+                                 unsigned int r) {
+    unsigned int i;
+
+    while (nblocks) {
+        KeccakF1600_StatePermute(s);
+        for (i = 0; i < r / 8; i++)
+            store64(out + 8 * i, s[i]);
+        out += r;
+        nblocks -= 1;
+    }
+}
+
+/*************************************************
+ * Name:        shake128_init
+ *
+ * Description: Initilizes Keccak state for use as SHAKE128 XOF
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) Keccak state
+ **************************************************/
+void shake128_init(keccak_state *state) {
+    keccak_init(state->s);
+    state->pos = 0;
+}
+
+/*************************************************
+ * Name:        shake128_absorb
+ *
+ * Description: Absorb step of the SHAKE128 XOF; incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (initialized) input/output
+ *              Keccak state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen) {
+    state->pos = keccak_absorb(state->s, state->pos, SHAKE128_RATE, in, inlen);
+}
+
+/*************************************************
+ * Name:        shake128_finalize
+ *
+ * Description: Finalize absorb step of the SHAKE128 XOF.
+ *
+ * Arguments:   - keccak_state *state: pointer to input/output Keccak state
+ **************************************************/
+void shake128_finalize(keccak_state *state) {
+    keccak_finalize(state->s, state->pos, SHAKE128_RATE, 0x1F);
+    state->pos = SHAKE128_RATE;
+}
+
+/*************************************************
+ * Name:        shake128_squeeze
+ *
+ * Description: Squeeze step of SHAKE128 XOF. Squeezes arbitraily many
+ *              bytes. Can be called multiple times to keep squeezing.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t outlen : number of bytes to be squeezed (written to
+ *              output)
+ *              - keccak_state *state: pointer to input/output Keccak state
+ **************************************************/
+void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state) {
+    state->pos =
+        keccak_squeeze(out, outlen, state->s, state->pos, SHAKE128_RATE);
+}
+
+/*************************************************
+ * Name:        shake128_absorb_once
+ *
+ * Description: Initialize, absorb into and finalize SHAKE128 XOF;
+ *non-incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) input/output
+ * Keccak state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128_absorb_once(keccak_state *state, const uint8_t *in,
+                          size_t inlen) {
+    keccak_absorb_once(state->s, SHAKE128_RATE, in, inlen, 0x1F);
+    state->pos = SHAKE128_RATE;
+}
+
+/*************************************************
+ * Name:        shake128_squeezeblocks
+ *
+ * Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of
+ *              SHAKE128_RATE bytes each. Can be called multiple times
+ *              to keep squeezing. Assumes new block has not yet been
+ *              started (state->pos = SHAKE128_RATE).
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *              output)
+ *              - keccak_state *state: pointer to input/output Keccak state
+ **************************************************/
+void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) {
+    keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE);
+}
+
+/*************************************************
+ * Name:        shake256_init
+ *
+ * Description: Initilizes Keccak state for use as SHAKE256 XOF
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) output Keccak
+ *              state
+ **************************************************/
+void shake256_init(keccak_state *state) {
+    keccak_init(state->s);
+    state->pos = 0;
+}
+
+/*************************************************
+ * Name:        shake256_absorb
+ *
+ * Description: Absorb step of the SHAKE256 XOF; incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (initialized) input/output
+ *              Keccak state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) {
+    state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen);
+}
+
+/*************************************************
+ * Name:        shake256_finalize
+ *
+ * Description: Finalize absorb step of the SHAKE256 XOF.
+ *
+ * Arguments:   - keccak_state *state: pointer to input/output Keccak state
+ **************************************************/
+void shake256_finalize(keccak_state *state) {
+    keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F);
+    state->pos = SHAKE256_RATE;
+}
+
+/*************************************************
+ * Name:        shake256_squeeze
+ *
+ * Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many
+ *              bytes. Can be called multiple times to keep squeezing.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t outlen : number of bytes to be squeezed (written to
+ *              output)
+ *              - keccak_state *state: pointer to input/output Keccak state
+ **************************************************/
+void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) {
+    state->pos =
+        keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE);
+}
+
+/*************************************************
+ * Name:        shake256_absorb_once
+ *
+ * Description: Initialize, absorb into and finalize SHAKE256 XOF;
+ *              non-incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) input/output
+ *              Keccak state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256_absorb_once(keccak_state *state, const uint8_t *in,
+                          size_t inlen) {
+    keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F);
+    state->pos = SHAKE256_RATE;
+}
+
+/*************************************************
+ * Name:        shake256_squeezeblocks
+ *
+ * Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of
+ *              SHAKE256_RATE bytes each. Can be called multiple times
+ *              to keep squeezing. Assumes next block has not yet been
+ *              started (state->pos = SHAKE256_RATE).
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *              output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) {
+    keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE);
+}
+
+/*************************************************
+ * Name:        shake128
+ *
+ * Description: SHAKE128 XOF with non-incremental API
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: requested output length in bytes
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) {
+    size_t nblocks;
+    keccak_state state;
+
+    shake128_absorb_once(&state, in, inlen);
+    nblocks = outlen / SHAKE128_RATE;
+    shake128_squeezeblocks(out, nblocks, &state);
+    outlen -= nblocks * SHAKE128_RATE;
+    out += nblocks * SHAKE128_RATE;
+    shake128_squeeze(out, outlen, &state);
+}
+
+/*************************************************
+ * Name:        shake256
+ *
+ * Description: SHAKE256 XOF with non-incremental API
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: requested output length in bytes
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) {
+    size_t nblocks;
+    keccak_state state;
+
+    shake256_absorb_once(&state, in, inlen);
+    nblocks = outlen / SHAKE256_RATE;
+    shake256_squeezeblocks(out, nblocks, &state);
+    outlen -= nblocks * SHAKE256_RATE;
+    out += nblocks * SHAKE256_RATE;
+    shake256_squeeze(out, outlen, &state);
+}
+
+/*************************************************
+ * Name:        sha3_256
+ *
+ * Description: SHA3-256 with non-incremental API
+ *
+ * Arguments:   - uint8_t h[32]: pointer to output
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) {
+    unsigned int i;
+    uint64_t s[25];
+
+    keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06);
+    KeccakF1600_StatePermute(s);
+    for (i = 0; i < 4; i++)
+        store64(h + 8 * i, s[i]);
+}
+
+/*************************************************
+ * Name:        sha3_512
+ *
+ * Description: SHA3-512 with non-incremental API
+ *
+ * Arguments:   - uint8_t h[64]: pointer to output
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) {
+    unsigned int i;
+    uint64_t s[25];
+
+    keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06);
+    KeccakF1600_StatePermute(s);
+    for (i = 0; i < 8; i++)
+        store64(h + 8 * i, s[i]);
+}
diff --git a/cbits/third_party/SMAUG-T/src/hash.c b/cbits/third_party/SMAUG-T/src/hash.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/hash.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: MIT
+
+#include "hash.h"
+#include "fips202.h"
+
+void shake256_absorb_twice_squeeze(uint8_t *out, size_t outlen,
+                                   const uint8_t *in1, size_t inlen1,
+                                   const uint8_t *in2, size_t inlen2) {
+    keccak_state state;
+    shake256_init(&state);
+    shake256_absorb(&state, in1, inlen1);
+    shake256_absorb(&state, in2, inlen2);
+    shake256_finalize(&state);
+    shake256_squeeze(out, outlen, &state);
+}
diff --git a/cbits/third_party/SMAUG-T/src/hwt.c b/cbits/third_party/SMAUG-T/src/hwt.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/hwt.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: MIT
+
+#include "hwt.h"
+#include "fips202.h"
+
+/*************************************************
+ * Name:        load16_littleendian
+ *
+ * Description: load 2 bytes into a 16-bit integer
+ *              in little-endian order
+ *
+ * Arguments:   - uint16_t *out: pointer to output int16_t array
+ *              - size_t outlen: output length
+ *              - const uint8_t *in: pointer to input byte array
+ **************************************************/
+#define load16_littleendian SMAUGT_NAMESPACE(load16_littleendian)
+static void load16_littleendian(uint16_t *out, size_t outlen,
+                                const uint8_t *in) {
+    size_t pos = 0;
+    for (size_t i = 0; i < outlen; ++i) {
+        out[i] = ((uint16_t)(in[pos])) | ((uint16_t)(in[pos + 1]) << 8);
+        pos += 2;
+    }
+}
+
+// referenced
+// Décio Luiz Gazzoni Filho and Tomás S. R. Silva and Julio López
+// “Efficient isochronous fixed-weight sampling with applications to {NTRU},” in
+// Cryptology {ePrint} Archive, Paper 2024/548. 2024,
+// url: eprint.iacr.org/2024/548.
+/*************************************************
+ * Name:        rej_sample_mod
+ *
+ * Description: Sample array of random integers such that res[i] is in the range
+ *              [0, SMAUGT_N - i] for 0 <= i < SMAUGT_N
+ *
+ * Arguments:   - uint8_t si[SMAUGT_N]: pointer to ouptput polynomial r(x),
+ *              assumed to be already initialized
+ *              - const uint16_t *seed: pointer to input seed (of length
+ *              SMAUGT_HWTSEEDBYTES/2)
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 4, RejSampleMod]
+ **************************************************/
+#define rej_sample_mod SMAUGT_NAMESPACE(rej_sample_mod)
+static int rej_sample_mod(int16_t si[SMAUGT_N], const uint16_t *rand) {
+    unsigned int i, j = SMAUGT_N;
+    uint32_t m;
+    uint16_t s, t, l;
+
+    for (i = 0; i < SMAUGT_N; i++) {
+        s = SMAUGT_N - i;
+        t = 65536 % s;
+
+        m = (uint32_t)rand[i] * s;
+        l = m;
+
+        while (l < t) {
+            if (j >= (SMAUGT_HWTSEEDBYTES / 2))
+                return -1; // all randomness used
+            m = (uint32_t)rand[j++] * s;
+            l = m;
+        }
+
+        si[i] = m >> 16;
+    }
+
+    return 0;
+}
+
+/*************************************************
+ * Name:        hwt
+ *
+ * Description: Hamming weight sampling deterministically to generate sparse
+ *              polynomial r(x) from a seed. shake256 is the Extendable-Output
+ *              Function from the SHA-3 family.
+ *
+ * Arguments:   - int16_t *res: pointer to ouptput polynomial r(x)
+ *              (of length SMAUGT_N), assumed to be already initialized
+ *              - const uint8_t *seed: pointer to input seed (of length
+ *              SMAUGT_CRYPTO_BYTES + 2)
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 3, HWT]
+ **************************************************/
+int hwt(int16_t *res, const uint8_t *seed) {
+    unsigned int i;
+    int16_t si[SMAUGT_N] = {0};
+    uint16_t rand[SMAUGT_HWTSEEDBYTES / 2] = {0};
+    uint8_t sign[SMAUGT_N / 4] = {0};
+    uint8_t buf[SMAUGT_HWTSEEDBYTES] = {0};
+
+    keccak_state state;
+    shake256_init(&state);
+    shake256_absorb_once(&state, seed, SMAUGT_CRYPTO_BYTES + 2);
+
+    // only executed once with overwhelming probability:
+    shake256_squeeze(buf, SMAUGT_HWTSEEDBYTES, &state);
+    load16_littleendian(rand, SMAUGT_HWTSEEDBYTES / 2, buf);
+    if (rej_sample_mod(si, rand)) {
+        return -1;
+    }
+
+    shake256_squeeze(sign, SMAUGT_N / 4, &state);
+
+    int16_t t0;
+    int16_t c0 = SMAUGT_N - SMAUGT_HS;
+    for (i = 0; i < SMAUGT_N; i++) {
+        t0 = (si[i] - c0) >> 15;
+        c0 += t0;
+        res[i] = 1 + t0;
+        // Convert to ternary
+        // index of sign: (i / 16 / 8) * 16 + (i % 16)
+        // shift size   : (i / 16) % 8
+        res[i] =
+            (-res[i]) &
+            ((((sign[(((i >> 4) >> 3) << 4) + (i & 0x0F)] >> ((i >> 4) & 0x07))
+               << 1) &
+              0x02) -
+             1);
+    }
+    return 0;
+}
diff --git a/cbits/third_party/SMAUG-T/src/indcpa.c b/cbits/third_party/SMAUG-T/src/indcpa.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/indcpa.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: MIT
+
+#include "indcpa.h"
+#include "cbd.h"
+#include "fips202.h"
+#include "pack.h"
+#include "randombytes.h"
+#include "verify.h"
+
+#include <string.h>
+
+/*************************************************
+ * Name:        expand_r
+ *
+ * Description: Deterministically generate a vector of sparse polynomial r(x)
+ *              from a seed.
+ *
+ * Arguments:   - polyvec *r: pointer to ouptput vector r
+ *              - uint8_t *seed: pointer to input seed (of length
+ * SMAUGT_DELTA_BYTES)
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 5, ExpandR]
+ **************************************************/
+void expand_r(polyvec *r, const uint8_t *seed) {
+    unsigned int i;
+    uint8_t buf[SMAUGT_CBDSEED_BYTES] = {0};
+
+    for (i = 0; i < SMAUGT_K; ++i) {
+        uint8_t extseed[SMAUGT_DELTA_BYTES + 1];
+        memcpy(extseed, seed, SMAUGT_DELTA_BYTES);
+        extseed[SMAUGT_DELTA_BYTES] = i;
+
+        shake256(buf, SMAUGT_CBDSEED_BYTES, extseed, SMAUGT_DELTA_BYTES + 1);
+        sp_cbd(&r->vec[i], buf);
+    }
+}
+
+/*************************************************
+ * Name:        indcpa_keypair
+ *
+ * Description: Generates a public key and a private key deterministically
+ *              from the given randomness seed.
+ *              Key generation function of the CPA-secure Module-Lizard
+ *              public-key encryption scheme.
+ *
+ * Arguments:   - uint8_t pk[SMAUGT_PUBLICKEY_BYTES]: pointer to output public
+ *              key
+ *              - uint8_t sk[SMAUGT_PKE_SECRETKEY_BYTES]: pointer to output
+ *              private key
+ *              - uint8_t seed[SMAUGT_CRYPTO_BYTES]: pointer to input seed
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 25, SMAUGT-T.PKE.KeyGen]
+ **************************************************/
+void indcpa_keypair(uint8_t pk[SMAUGT_PUBLICKEY_BYTES],
+                    uint8_t sk[SMAUGT_PKE_SECRETKEY_BYTES],
+                    const uint8_t seed[SMAUGT_CRYPTO_BYTES]) {
+    public_key pk_tmp;
+    secret_key sk_tmp;
+    memset(&pk_tmp, 0, sizeof(public_key));
+    memset(&sk_tmp, 0, sizeof(secret_key));
+
+    uint8_t extseed[SMAUGT_CRYPTO_BYTES + SMAUGT_PKSEED_BYTES] = {0};
+#if SMAUGT_CRYPTO_BYTES + SMAUGT_PKSEED_BYTES != 64
+#error                                                                         \
+    "This implementation assumes SMAUGT_CRYPTO_BYTES + SMAUGT_PKSEED_BYTES to be 64"
+#endif
+    sha3_512(extseed, seed, SMAUGT_CRYPTO_BYTES);
+
+    expand_s(&sk_tmp, extseed);
+
+    memcpy(&pk_tmp.seed, extseed + SMAUGT_CRYPTO_BYTES, SMAUGT_PKSEED_BYTES);
+    gen_pub_key(&pk_tmp, &sk_tmp, extseed);
+
+    memset(pk, 0, SMAUGT_PUBLICKEY_BYTES);
+    memset(sk, 0, SMAUGT_PKE_SECRETKEY_BYTES);
+    pack_enck(pk, &pk_tmp);
+    pack_deck(sk, &sk_tmp);
+}
+
+/*************************************************
+ * Name:        indcpa_enc
+ *
+ * Description: Generates the ciphertext ctxt deterministically
+ *              by encrypting the given message mu
+ *              with the provided public key pk and the given randomness seed.
+ *              Encryption function of the CPA-secure Module-Lizard public-key
+ *              encryption scheme.
+ *
+ * Arguments:   - uint8_t ctxt[SMAUGT_CIPHERTEXT_BYTES]: pointer to output
+ *              ciphertext
+ *              - const uint8_t pk[SMAUGT_PUBLICKEY_BYTES]: pointer to input
+ *              public key
+ *              - const uint8_t mu[SMAUGT_MSG_BYTES]: pointer to input plaintext
+ *              message
+ *              - const uint8_t seed[SMAUGT_DELTA_BYTES]: pointer to input seed
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 26, SMAUGT-T.PKE.Enc]
+ **************************************************/
+void indcpa_enc(uint8_t ctxt[SMAUGT_CIPHERTEXT_BYTES],
+                const uint8_t pk[SMAUGT_PUBLICKEY_BYTES],
+                const uint8_t mu[SMAUGT_MSG_BYTES],
+                const uint8_t seed[SMAUGT_DELTA_BYTES]) {
+
+    uint8_t seed_r[SMAUGT_DELTA_BYTES] = {0};
+    public_key pk_tmp;
+    unpack_enck(&pk_tmp, pk);
+
+    // Compute a vector r = hwt(delta, H'(pk))
+    polyvec r;
+    memset(&r, 0, sizeof(polyvec));
+
+    if (seed == NULL)
+        randombytes(seed_r, SMAUGT_DELTA_BYTES);
+    else
+        cmov(seed_r, seed, SMAUGT_DELTA_BYTES, 1);
+    expand_r(&r, seed_r);
+
+    // Compute c1(x), c2(x)
+    ciphertext ctxt_tmp;
+    memset(&ctxt_tmp, 0, sizeof(ciphertext));
+    computeC1(&(ctxt_tmp.c1), pk_tmp.A, &r);
+    computeC2(&(ctxt_tmp.c2), mu, &pk_tmp.b, &r);
+
+    pack_ct(ctxt, &ctxt_tmp);
+}
+
+/*************************************************
+ * Name:        indcpa_dec
+ *
+ * Description: Generates the plaintext message mu deterministically
+ *              by decrypting the given ciphertext ctxt with the provided
+ *              private key sk. Decryption function of the CPA-secure
+ * Module-Lizard public-key encryption scheme.
+ *
+ * Arguments:   - uint8_t mu[SMAUGT_MSG_BYTES]: pointer to output plaintext
+ *              message
+ *              - const uint8_t sk[SMAUGT_PKE_SECRETKEY_BYTES]: pointer to input
+ *              private key
+ *              - const uint8_t ctxt[SMAUGT_CIPHERTEXT_BYTES]: pointer to input
+ *              ciphertext
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 28, SMAUGT-T.PKE.Dec]
+ **************************************************/
+void indcpa_dec(uint8_t mu[SMAUGT_MSG_BYTES],
+                const uint8_t sk[SMAUGT_PKE_SECRETKEY_BYTES],
+                const uint8_t ctxt[SMAUGT_CIPHERTEXT_BYTES]) {
+    poly delta;
+    polyvec c1;
+
+    secret_key sk_tmp;
+    memset(&sk_tmp, 0, sizeof(secret_key));
+    unpack_deck(&sk_tmp, sk);
+
+    ciphertext ctxt_tmp;
+    unpack_ct(&ctxt_tmp, ctxt);
+
+    unsigned int i, j;
+    c1 = ctxt_tmp.c1;
+    delta = ctxt_tmp.c2;
+    for (i = 0; i < SMAUGT_N; ++i)
+        delta.coeffs[i] =
+            (int16_t)((uint16_t)delta.coeffs[i]
+                      << SMAUGT_MODULUS_16_LOG_P_PRIME);
+    for (i = 0; i < SMAUGT_K; ++i)
+        for (j = 0; j < SMAUGT_N; ++j)
+            c1.vec[i].coeffs[j] =
+                (int16_t)((uint16_t)c1.vec[i].coeffs[j]
+                          << SMAUGT_MODULUS_16_LOG_P);
+
+    // Compute delta = (delta + c1^T * s)
+    vec_vec_mult_add(&delta, &c1, &sk_tmp, SMAUGT_MODULUS_16_LOG_P);
+
+#if SMAUGT_MODE == SMAUGT_MODET
+    d2_dcd(mu, &delta); // EDIT TiMER
+#else
+    // Compute delta = 2/p * delta
+    for (i = 0; i < SMAUGT_N; ++i) {
+        delta.coeffs[i] += SMAUGT_DEC_ADD;
+        delta.coeffs[i] >>= SMAUGT_MODULUS_16_LOG_T;
+        delta.coeffs[i] &= 0x01;
+    }
+
+    // Set delta
+    memset(mu, 0, SMAUGT_MSG_BYTES);
+    for (i = 0; i < SMAUGT_MSG_BYTES; ++i) {
+        for (j = 0; j < 8; ++j) {
+            mu[i] ^= ((uint8_t)(delta.coeffs[8 * i + j]) << j);
+        }
+    }
+#endif
+}
diff --git a/cbits/third_party/SMAUG-T/src/kem.c b/cbits/third_party/SMAUG-T/src/kem.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/kem.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: MIT
+
+#include "api.h"
+#include "fips202.h"
+#include "hash.h"
+#include "indcpa.h"
+#include "randombytes.h"
+#include "verify.h"
+#include <string.h>
+
+/*************************************************
+ * Name:        crypto_kem_keypair_internal
+ *
+ * Description: Generates a public key pk and a private key sk deterministically
+ *              from the given randomness (i.e., d and seed)
+ *              for the CCA-secure Module-Lizard key encapsulation mechanism.
+ *
+ * Arguments:   - uint8_t *pk: pointer to output public key
+ *              - uint8_t *sk: pointer to output private key
+ *              - uint8_t *d: pointer to input d (of length
+ *              SMAUGT_T_BYTES)
+ *              - uint8_t *seed: pointer to input seed (of length
+ *              SMAUGT_CRYPTO_BYTES)
+ *
+ * Return: 0 on success, 1 on failure
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 33,
+ * SMAUG-T.KEM.EncapInternal]
+ **************************************************/
+int crypto_kem_keypair_internal(uint8_t *pk, uint8_t *sk,
+                                uint8_t d[SMAUGT_T_BYTES],
+                                uint8_t seed[SMAUGT_CRYPTO_BYTES]) {
+    int ret = 0;
+    indcpa_keypair(pk, sk, seed);
+    memcpy(sk + SMAUGT_PKE_SECRETKEY_BYTES, d, SMAUGT_T_BYTES);
+    memcpy(sk + SMAUGT_PKE_SECRETKEY_BYTES + SMAUGT_T_BYTES, pk,
+           SMAUGT_PUBLICKEY_BYTES);
+
+    return ret;
+}
+
+/*************************************************
+ * Name:        crypto_kem_keypair
+ *
+ * Description: Generates a public key pk and a private key sk probabilistically
+ *              by internally sampling the required randomness (i.e., d and
+ * seed) for the CCA-secure Module-Lizard key encapsulation mechanism.
+ *
+ * Arguments:   - uint8_t *pk: pointer to output public key
+ *              - uint8_t *sk: pointer to output private key
+ *
+ * Return: 0 on success, 1 on failure
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 30, SMAUG-T.KEM.KeyGen]
+ **************************************************/
+int crypto_kem_keypair(uint8_t *pk, uint8_t *sk) {
+    int ret = 0;
+    uint8_t d[SMAUGT_T_BYTES] = {0};
+    uint8_t seed[SMAUGT_CRYPTO_BYTES] = {0};
+
+    if (randombytes(d, SMAUGT_T_BYTES) != 0) return -1;
+    if (randombytes(seed, SMAUGT_CRYPTO_BYTES) != 0) return -1;
+
+    ret = crypto_kem_keypair_internal(pk, sk, d, seed);
+
+    return ret;
+}
+
+/*************************************************
+ * Name:        crypto_kem_enc_internal
+ *
+ * Description: Generates a shared secret ss and an associated ciphertext ctxt
+ * deterministically for the given message mu using the provided public key pk.
+ *
+ * Arguments:   - uint8_t *ctxt: pointer to output ciphertext
+ *              - uint8_t *ss: pointer to output shared secret
+ *              (of length SMAUGT_CRYPTO_BYTES)
+ *              - uint8_t *pk: pointer to input public key
+ *              - uint8_t *mu: pointer to input message (of length
+ *              SMAUGT_DELTA_BYTES)
+ *
+ * Return: 0 on success, 1 on failure
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 34,
+ * SMAUG-T.KEM.EncapInternal]
+ **************************************************/
+int crypto_kem_enc_internal(uint8_t *ctxt, uint8_t *ss, const uint8_t *pk,
+                            const uint8_t *mu) {
+    int ret = 0;
+    uint8_t seed_r[SMAUGT_DELTA_BYTES + SMAUGT_CRYPTO_BYTES] = {0};
+    hash_h(seed_r, pk, SMAUGT_PUBLICKEY_BYTES);
+    hash_g(seed_r, SMAUGT_DELTA_BYTES + SMAUGT_CRYPTO_BYTES, mu,
+           SMAUGT_MSG_BYTES, seed_r, SHA3_256_HashSize);
+
+    memset(ss, 0, SMAUGT_CRYPTO_BYTES);
+    indcpa_enc(ctxt, pk, mu, seed_r);
+    cmov(ss, seed_r + SMAUGT_DELTA_BYTES, SMAUGT_CRYPTO_BYTES, 1);
+    return ret;
+}
+
+/*************************************************
+ * Name:        crypto_kem_enc
+ *
+ * Description: Generates a shared secret ss and an associated ciphertext ctxt
+ * probabilistically by internally sampling the required randomness (i.e., mu)
+ *              for the given public key.
+ *
+ * Arguments:   - uint8_t *ctxt: pointer to output ciphertext
+ *              - uint8_t *ss: pointer to output  shared secret
+ *              (of length SMAUGT_CRYPTO_BYTES)
+ *              - uint8_t *pk: pointer to input public key
+ *
+ * Return: 0 on success, 1 on failure
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 31, SMAUG-T.KEM.Encap]
+ **************************************************/
+int crypto_kem_enc(uint8_t *ctxt, uint8_t *ss, const uint8_t *pk) {
+    int ret = 0;
+    uint8_t mu[SMAUGT_MSG_BYTES] = {0}; // shared secret and seed
+    if (randombytes(mu, SMAUGT_MSG_BYTES) != 0) return -1;
+
+    ret = crypto_kem_enc_internal(ctxt, ss, pk, mu);
+
+    return ret;
+}
+
+/*************************************************
+ * Name:        crypto_kem_dec_internal
+ *
+ * Description: Produces a shared secret ss deterministically
+ *              for the given ciphertext ctxt using the provided private key sk.
+ *
+ * Arguments:   - uint8_t *ss: pointer to output shared secret
+ *              (of length SMAUGT_CRYPTO_BYTES)
+ *              - uint8_t *sk: pointer to input private key
+ *              - uint8_t *ctxt: pointer to input ciphertext
+ *
+ * Return: 0 on success, 1 on failure
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 35,
+ * SMAUG-T.KEM.DecapInternal]
+ **************************************************/
+int crypto_kem_dec_internal(uint8_t *ss, const uint8_t *ctxt,
+                            const uint8_t *sk) {
+    int ret = 0;
+    uint8_t mu[SMAUGT_MSG_BYTES] = {0};
+    uint8_t buf[SMAUGT_DELTA_BYTES + SMAUGT_CRYPTO_BYTES] = {
+        0}; // shared secret and seed
+    uint8_t buf_tmp[SMAUGT_DELTA_BYTES + SMAUGT_CRYPTO_BYTES] = {0};
+    uint8_t hash_res[SHA3_256_HashSize] = {0};
+    const uint8_t *pk = sk + SMAUGT_PKE_SECRETKEY_BYTES + SMAUGT_T_BYTES;
+
+    indcpa_dec(mu, sk, ctxt);
+    hash_h(hash_res, pk, SMAUGT_PUBLICKEY_BYTES);
+    hash_g(buf, SMAUGT_DELTA_BYTES + SMAUGT_CRYPTO_BYTES, mu, SMAUGT_MSG_BYTES,
+           hash_res, SHA3_256_HashSize);
+
+    uint8_t ctxt_temp[SMAUGT_CIPHERTEXT_BYTES] = {0};
+    indcpa_enc(ctxt_temp, pk, mu, buf);
+
+    int fail = verify(ctxt, ctxt_temp, SMAUGT_CIPHERTEXT_BYTES);
+
+    hash_h(hash_res, ctxt, SMAUGT_CIPHERTEXT_BYTES);
+    hash_g(buf_tmp, SMAUGT_DELTA_BYTES + SMAUGT_CRYPTO_BYTES,
+           sk + SMAUGT_PKE_SECRETKEY_BYTES, SMAUGT_T_BYTES, hash_res,
+           SHA3_256_HashSize);
+
+    memset(ss, 0, SMAUGT_CRYPTO_BYTES);
+    cmov(buf + SMAUGT_DELTA_BYTES, buf_tmp + SMAUGT_DELTA_BYTES,
+         SMAUGT_CRYPTO_BYTES, fail);
+    cmov(ss, buf + SMAUGT_DELTA_BYTES, SMAUGT_CRYPTO_BYTES, 1);
+
+    return ret;
+}
+
+/*************************************************
+ * Name:        crypto_kem_dec
+ *
+ * Description: Produces a shared secret ss deterministically
+ *              for the given ciphertext ctxt using the provided private key sk.
+ *
+ * Arguments:   - uint8_t *ss: pointer to output shared secret (of length
+ *              SMAUGT_CRYPTO_BYTES). On failure, ss will contain a
+ * pseudo-random value.
+ *              - uint8_t *sk: pointer to input private key
+ *              - uint8_t *ctxt: pointer to input ciphertext
+ *
+ * Return: 0 on success, 1 on failure
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 32, SMAUG-T.KEM.Decap]
+ **************************************************/
+int crypto_kem_dec(uint8_t *ss, const uint8_t *ctxt, const uint8_t *sk) {
+    int ret = 0;
+    ret = crypto_kem_dec_internal(ss, ctxt, sk);
+    return ret;
+}
diff --git a/cbits/third_party/SMAUG-T/src/key.c b/cbits/third_party/SMAUG-T/src/key.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/key.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: MIT
+
+#include "key.h"
+#include "dg.h"
+#include "fips202.h"
+#include "hwt.h"
+#include "pack.h"
+#include "poly.h"
+
+#include <string.h>
+
+/*************************************************
+ * Name:        expand_A
+ *
+ * Description: Deterministically generate public matrix A from a seed.
+ *              Entries of the A are polynomials that look uniformly random.
+ *
+ * Arguments:   - polyvec A[SMAUGT_K]: pointer to output matrix A
+ *              - const uint8_t seed[SMAUGT_PKSEED_BYTES]: pointer to input seed
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 1, ExpandA]
+ **************************************************/
+void expand_A(polyvec A[SMAUGT_K], const uint8_t seed[SMAUGT_PKSEED_BYTES]) {
+    unsigned int i, j;
+    uint8_t buf[SMAUGT_PKPOLY_BYTES] = {0}, extseed[SMAUGT_PKSEED_BYTES + 2];
+    memcpy(extseed, seed, SMAUGT_PKSEED_BYTES);
+    for (i = 0; i < SMAUGT_K; i++) {
+        for (j = 0; j < SMAUGT_K; j++) {
+            extseed[32] = i;
+            extseed[33] = j;
+            shake128(buf, SMAUGT_PKPOLY_BYTES, extseed,
+                     SMAUGT_PKSEED_BYTES + 2);
+            unpack_ring(&A[i].vec[j], buf);
+        }
+    }
+}
+
+/*************************************************
+ * Name:        expand_b
+ *
+ * Description: Generate public vector b from a matrix A, vector s and noise e.
+ *              Random noise e is generated by Gaussian sampling.
+ *
+ * Arguments:   - polyvec *b: pointer to output vector b
+ *              - const polyvec A[SMAUGT_K]: pointer to input matrix A
+ *              - const polyvec *s: pointer to input vector s
+ *              - const uint8_t e_seed[SMAUGT_CRYPTO_BYTES]: pointer to input
+ *              seed of error
+ **************************************************/
+void expand_b(polyvec *b, const polyvec A[SMAUGT_K], const polyvec *s,
+              const uint8_t e_seed[SMAUGT_CRYPTO_BYTES]) {
+    // b = e
+    d_gaussian(b, e_seed);
+
+    // b = -a * s + e
+    matrix_vec_mult_sub(b, A, s);
+}
+
+/*************************************************
+ * Name:        expand_s
+ *
+ * Description: Generate a vector of secret sparse polynomial s(x) from a seed.
+ *
+ * Arguments:   - secret_key *sk: pointer to output private key
+ *              - const uint8_t seed[SMAUGT_CRYPTO_BYTES]: pointer to input seed
+ *              of s(x)
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 2, ExpandS]
+ **************************************************/
+void expand_s(secret_key *sk, const uint8_t seed[SMAUGT_CRYPTO_BYTES]) {
+    unsigned int i, j;
+    uint8_t extseed[SMAUGT_CRYPTO_BYTES + 2] = {0};
+    memcpy(extseed, seed, SMAUGT_CRYPTO_BYTES);
+
+    for (i = 0; i < SMAUGT_K; ++i) {
+        extseed[SMAUGT_CRYPTO_BYTES] = i * SMAUGT_K;
+        j = 0;
+        do {
+            extseed[SMAUGT_CRYPTO_BYTES + 1] = j;
+            j += 1;
+        } while (hwt(sk->vec[i].coeffs, extseed));
+    }
+}
+
+/*************************************************
+ * Name:        genPubkey
+ *
+ * Description: Generate public key correspending to private key.
+ *
+ * Arguments:   - public_key *pk: pointer to output public key
+ *              - const secret_key *sk: pointer to input private key
+ *              - const uint8_t err_seed[SMAUGT_CRYPTO_BYTES]: pointer to input
+ *              seed of error
+ **************************************************/
+void gen_pub_key(public_key *pk, const secret_key *sk,
+                 const uint8_t err_seed[SMAUGT_CRYPTO_BYTES]) {
+    expand_A(pk->A, pk->seed);
+
+    memset(&(pk->b), 0, sizeof(uint16_t) * SMAUGT_N * SMAUGT_K);
+    // Initialized at addGaussian, Unnecessary
+    expand_b(&(pk->b), pk->A, sk, err_seed);
+}
diff --git a/cbits/third_party/SMAUG-T/src/pack.c b/cbits/third_party/SMAUG-T/src/pack.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/pack.c
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: MIT
+
+#include "pack.h"
+#include "packring.h"
+
+#include <string.h>
+
+/*************************************************
+ * Name:        pack_enck
+ *
+ * Description: pack an encryption key to a byte array
+ *
+ * Arguments:   - uint8_t *output: pointer to output byte array
+ *              - const public_key *pk: pointer to input public key
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 14, PackEncK]
+ **************************************************/
+void pack_enck(uint8_t *output, const public_key *pk) {
+    memcpy(output, pk->seed, sizeof(uint8_t) * SMAUGT_PKSEED_BYTES);
+    pack_ring_vec(output + SMAUGT_PKSEED_BYTES, &(pk->b));
+}
+
+/*************************************************
+ * Name:        unpack_enck
+ *
+ * Description: unpack a byte array to an encryption key
+ *
+ * Arguments:   - public_key *pk: pointer to output public key
+ *              - const uint8_t *input: pointer to input byte array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 15, UnpackEncK]
+ **************************************************/
+void unpack_enck(public_key *pk, const uint8_t *input) {
+    memcpy(pk->seed, input, SMAUGT_PKSEED_BYTES);
+    expand_A(pk->A, pk->seed);
+    unpack_ring_vec(&(pk->b), input + SMAUGT_PKSEED_BYTES);
+}
+
+/*************************************************
+ * Name:        pack_deck
+ *
+ * Description: pack a decryption key to a byte array
+ *
+ * Arguments:   - uint8_t *output: pointer to output byte array
+ *              - const secret_key *sk: pointer to input private key
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 16, PackDecK]
+ **************************************************/
+void pack_deck(uint8_t *output, const secret_key *sk) {
+    for (size_t i = 0; i < SMAUGT_K; ++i)
+        pack_s_poly(output + SMAUGT_SKPOLY_BYTES * i, &sk->vec[i]);
+}
+
+/*************************************************
+ * Name:        unpack_deck
+ *
+ * Description: unpack a byte array to a decryption key
+ *
+ * Arguments:   - secret_key *sk: pointer to output private key
+ *              - const uint8_t *input: pointer to input byte array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 17, UnpackDecK]
+ **************************************************/
+void unpack_deck(secret_key *sk, const uint8_t *input) {
+    for (size_t i = 0; i < SMAUGT_K; ++i)
+        unpack_s_poly(&sk->vec[i], input + SMAUGT_SKPOLY_BYTES * i);
+}
+
+/*************************************************
+ * Name:        pack_s_poly
+ *
+ * Description: Transform to bytes array from a degree of array of secrey poly
+ *
+ * Arguments:   - uint8_t *bytes: pointer to output bytes
+ *              - const poly *s: pointer to input poly in Sn
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 18, PackSPoly]
+ **************************************************/
+void pack_s_poly(uint8_t *bytes, const poly *s) {
+    unsigned int i;
+    int d_idx = 0;
+    for (i = 0; i < SMAUGT_N / 4; ++i) {
+        d_idx = i * 4;
+        bytes[i] = ((1 - s->coeffs[d_idx]) & 0x03) |
+                   (((1 - s->coeffs[d_idx + 1]) & 0x03) << 2) |
+                   (((1 - s->coeffs[d_idx + 2]) & 0x03) << 4) |
+                   (((1 - s->coeffs[d_idx + 3]) & 0x03) << 6);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_s_poly
+ *
+ * Description: Transform to a degree of array of secrey poly from bytes array
+ *
+ * Arguments:   - poly *s: pointer to output poly in Sn
+ *              - const uint8_t *bytes: pointer to input bytes
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 19, UnpackSPoly]
+ **************************************************/
+void unpack_s_poly(poly *s, const uint8_t *bytes) {
+    unsigned int i;
+    int d_idx = 0;
+    for (i = 0; i < SMAUGT_N / 4; ++i) {
+        d_idx = i * 4;
+        uint8_t t[4] = {0};
+        t[0] = (bytes[i] & 0x03);
+        t[1] = ((bytes[i] >> 2) & 0x03);
+        t[2] = ((bytes[i] >> 4) & 0x03);
+        t[3] = ((bytes[i] >> 6) & 0x03);
+        s->coeffs[d_idx] = 1 - t[0];
+        s->coeffs[d_idx + 1] = 1 - t[1];
+        s->coeffs[d_idx + 2] = 1 - t[2];
+        s->coeffs[d_idx + 3] = 1 - t[3];
+    }
+}
+
+/*************************************************
+ * Name:        pack_ct
+ *
+ * Description: pack a ciphertext to a byte array
+ *
+ * Arguments:   - uint8_t *output: pointer to output byte array
+ *              - const ciphertext *ctxt: pointer to input ciphertext
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 20, PackCT]
+ * **************************************************/
+void pack_ct(uint8_t *output, const ciphertext *ctxt) {
+    pack_ring_p_vec(output, &(ctxt->c1));
+    pack_ring_p_prime(output + SMAUGT_CTPOLYVEC_BYTES, &(ctxt->c2));
+}
+
+/*************************************************
+ * Name:        unpack_ct
+ *
+ * Description: unpack a byte array to a ciphertext
+ *
+ * Arguments:   - ciphertext *ctxt: pointer to output ciphertext
+ *              - const uint8_t *input: pointer to input byte array
+ *
+ * Specification: Implements @[KS X 123456, Algorithm 21, UnpackCT]
+ **************************************************/
+void unpack_ct(ciphertext *ctxt, const uint8_t *input) {
+    unpack_ring_p_vec(&(ctxt->c1), input);
+    unpack_ring_p_prime(&(ctxt->c2), input + SMAUGT_CTPOLYVEC_BYTES);
+}
+
+/*************************************************
+ * Name:        pack_ring
+ *
+ * Description: Transform to bytes array from polynomial in Rq
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_PKPOLY_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rq
+ **************************************************/
+void pack_ring(uint8_t bytes[SMAUGT_PKPOLY_BYTES], const poly *data) {
+#if SMAUGT_LOG_Q == 10
+    pack_R2_10(bytes, data);
+#elif SMAUGT_LOG_Q == 11
+    pack_R2_11(bytes, data);
+#endif
+}
+
+/*************************************************
+ * Name:        unpack_ring
+ *
+ * Description: Transform to polynomial in Rq from bytes array
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rq
+ *              - const uint8_t bytes[SMAUGT_PKPOLY_BYTES]: pointer to input
+ * bytes
+ **************************************************/
+void unpack_ring(poly *data, const uint8_t bytes[SMAUGT_PKPOLY_BYTES]) {
+#if SMAUGT_LOG_Q == 10
+    unpack_R2_10(data, bytes);
+#elif SMAUGT_LOG_Q == 11
+    unpack_R2_11(data, bytes);
+#endif
+}
+
+/*************************************************
+ * Name:        pack_ring_p
+ *
+ * Description: Transform to bytes array from polynomial in Rp
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY1_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp
+ **************************************************/
+void pack_ring_p(uint8_t bytes[SMAUGT_CTPOLY1_BYTES], const poly *data) {
+#if SMAUGT_LOG_P == 8
+    pack_R2_8(bytes, data);
+#elif SMAUGT_LOG_P == 9
+    pack_R2_9(bytes, data);
+#endif
+}
+
+/*************************************************
+ * Name:        unpack_ring_p
+ *
+ * Description: Transform to polynomial in Rp from bytes array
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp
+ *              - const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]: pointer to input
+ * bytes
+ **************************************************/
+void unpack_ring_p(poly *data, const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]) {
+#if SMAUGT_LOG_P == 8
+    unpack_R2_8(data, bytes);
+#elif SMAUGT_LOG_P == 9
+    unpack_R2_9(data, bytes);
+#endif
+}
+
+/*************************************************
+ * Name:        pack_ring_p_prime
+ *
+ * Description: Transform to bytes array from polynomial in Rp'
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp'
+ **************************************************/
+void pack_ring_p_prime(uint8_t bytes[SMAUGT_CTPOLY2_BYTES], const poly *data) {
+#if SMAUGT_LOG_P_PRIME == 3
+    pack_R2_3(bytes, data);
+#elif SMAUGT_LOG_P_PRIME == 4
+    pack_R2_4(bytes, data);
+#elif SMAUGT_LOG_P_PRIME == 5
+    pack_R2_5(bytes, data);
+#elif SMAUGT_LOG_P_PRIME == 7
+    pack_R2_7(bytes, data);
+#endif
+}
+
+/*************************************************
+ * Name:        unpack_ring_p_prime
+ *
+ * Description: Transform to polynomial in Rp' from bytes array
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp'
+ *              - const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to input
+ * bytes
+ **************************************************/
+void unpack_ring_p_prime(poly *data,
+                         const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]) {
+#if SMAUGT_LOG_P_PRIME == 3
+    unpack_R2_3(data, bytes);
+#elif SMAUGT_LOG_P_PRIME == 4
+    unpack_R2_4(data, bytes);
+#elif SMAUGT_LOG_P_PRIME == 5
+    unpack_R2_5(data, bytes);
+#elif SMAUGT_LOG_P_PRIME == 7
+    unpack_R2_7(data, bytes);
+#endif
+}
+
+/*************************************************
+ * Name:        pack_ring_vec
+ *
+ * Description: Transform to bytes array from vector of polynomial in Rq
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_PKPOLYVEC_BYTES]: pointer to output bytes
+ *              - const polyvec *data: pointer to input vector of polynomial in
+ *              Rq
+ **************************************************/
+void pack_ring_vec(uint8_t bytes[SMAUGT_PKPOLYVEC_BYTES], const polyvec *data) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_K; ++i)
+        pack_ring(bytes + i * SMAUGT_PKPOLY_BYTES, &(data->vec[i]));
+}
+
+/*************************************************
+ * Name:        unpack_ring_vec
+ *
+ * Description: Transform to vector of polynomial in Rq from bytes array
+ *
+ * Arguments:   - polyvec *data: pointer to output a vector of poly in Rq
+ *              - uint8_t *bytes: pointer to input bytes
+ **************************************************/
+void unpack_ring_vec(polyvec *data,
+                     const uint8_t bytes[SMAUGT_PKPOLYVEC_BYTES]) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_K; ++i)
+        unpack_ring(&(data->vec[i]), bytes + i * SMAUGT_PKPOLY_BYTES);
+}
+
+/*************************************************
+ * Name:        pack_ring_p_vec
+ *
+ * Description: Transform to bytes array from vector of polynomial in Rp
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLYVEC_BYTES]: pointer to output bytes
+ *              - const polyvec *data: pointer to input vector of polynomial in
+ *              Rp
+ **************************************************/
+void pack_ring_p_vec(uint8_t bytes[SMAUGT_CTPOLYVEC_BYTES],
+                     const polyvec *data) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_K; ++i)
+        pack_ring_p(bytes + i * SMAUGT_CTPOLY1_BYTES, &(data->vec[i]));
+}
+
+/*************************************************
+ * Name:        unpack_ring_p_vec
+ *
+ * Description: Transform to vector of polynomial in Rp from bytes array
+ *
+ * Arguments:   - polyvec *data: pointer to output a vector of poly in Rp
+ *              - const uint8_t bytes[SMAUGT_CTPOLYVEC_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_ring_p_vec(polyvec *data,
+                       const uint8_t bytes[SMAUGT_CTPOLYVEC_BYTES]) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_K; ++i)
+        unpack_ring_p(&(data->vec[i]), bytes + i * SMAUGT_CTPOLY1_BYTES);
+}
+
+/*************************************************
+ * Name:        pack_ring_mat
+ *
+ * Description: Transform to bytes array from matrix of polynomial in Rq
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_PKPOLYMAT_BYTES]: pointer to output bytes
+ *              - const polyvec data[SMAUGT_K]: pointer to input matrix of
+ *              polynomial in Rq
+ **************************************************/
+void pack_ring_mat(uint8_t bytes[SMAUGT_PKPOLYMAT_BYTES],
+                   const polyvec data[SMAUGT_K]) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_K; ++i)
+        pack_ring_vec(bytes + i * SMAUGT_PKPOLYVEC_BYTES, &(data[i]));
+}
+
+/*************************************************
+ * Name:        unpack_ring_mat
+ *
+ * Description: Transform to matrix of  polynomial in Rq from bytes array
+ *
+ * Arguments:   - polyvec data[SMAUGT_K]: pointer to output matrix of polynomial
+ *              in Rq
+ *              - const uint8_t bytes[SMAUGT_PKPOLYMAT_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_ring_mat(polyvec data[SMAUGT_K],
+                     const uint8_t bytes[SMAUGT_PKPOLYMAT_BYTES]) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_K; ++i)
+        unpack_ring_vec(&(data[i]), bytes + i * SMAUGT_PKPOLYVEC_BYTES);
+}
diff --git a/cbits/third_party/SMAUG-T/src/packring.c b/cbits/third_party/SMAUG-T/src/packring.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/packring.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: MIT
+
+#include "packring.h"
+#include "poly.h"
+
+#include <string.h>
+
+#if SMAUGT_LOG_P_PRIME == 3
+/*************************************************
+ * Name:        pack_R2_3
+ *
+ * Description: Transform to bytes array from polynomial in Rp' where p' = 2^3
+ *              Use 3 bits to encode each coefficient in [0, 2^3).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp'
+ **************************************************/
+void pack_R2_3(uint8_t bytes[SMAUGT_CTPOLY2_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_CTPOLY2_BYTES);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint32_t packed = 0;
+        for (i = 0; i < 8; ++i) {
+
+            packed |=
+                ((uint32_t)(data->coeffs[data_idx + i] & 0x0007) << (3 * i));
+        }
+
+        bytes[bytes_idx++] = (uint8_t)(packed & 0x000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 8) & 0x000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 16) & 0x000000ff);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_R2_3
+ *
+ * Description: Transform to polynomial in Rp' from bytes array, where p' = 2^3
+ *              Use 3 bits to decode each coefficient in [0, 2^3).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp'
+ *              - const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_R2_3(poly *data, const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint32_t packed = 0;
+        packed = packed | ((uint32_t)bytes[bytes_idx++] & 0x000000ff);
+        packed = packed | (((uint32_t)bytes[bytes_idx++] << 8) & 0x0000ff00);
+        packed = packed | (((uint32_t)bytes[bytes_idx++] << 16) & 0x00ff0000);
+
+        for (i = 0; i < 8; i++) {
+            data->coeffs[data_idx + i] =
+                (int16_t)((packed >> (3 * i)) & 0x00000007);
+        }
+    }
+}
+#elif SMAUGT_LOG_P_PRIME == 4
+/*************************************************
+ * Name:        pack_R2_4
+ *
+ * Description: Transform to bytes array from polynomial in Rp' where p' = 2^4
+ *              Use 4 bits to encode each coefficient in [0, 2^4).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp'
+ **************************************************/
+void pack_R2_4(uint8_t bytes[SMAUGT_CTPOLY2_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_CTPOLY2_BYTES);
+    size_t bytes_idx = 0;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 2) {
+        bytes[bytes_idx] = (uint8_t)(data->coeffs[data_idx] & 0x000f);
+        bytes[bytes_idx++] |=
+            (uint8_t)((data->coeffs[data_idx + 1] & 0x000f) << 4);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_R2_4
+ *
+ * Description: Transform to polynomial in Rp' from bytes array, where p' = 2^4
+ *              Use 4 bits to decode each coefficient in [0, 2^4).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp'
+ *              - const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_R2_4(poly *data, const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t bytes_idx = 0;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 2) {
+        data->coeffs[data_idx] = (int16_t)(bytes[bytes_idx] & 0x000f);
+        data->coeffs[data_idx + 1] =
+            (int16_t)((bytes[bytes_idx++] >> 4) & 0x000f);
+    }
+}
+#elif SMAUGT_LOG_P_PRIME == 5
+/*************************************************
+ * Name:        pack_R2_5
+ *
+ * Description: Transform to bytes array from polynomial in Rp' where p' = 2^5
+ *              Use 5 bits to encode each coefficient in [0, 2^5).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp'
+ **************************************************/
+void pack_R2_5(uint8_t bytes[SMAUGT_CTPOLY2_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_CTPOLY2_BYTES);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+
+        for (i = 0; i < 8; ++i) {
+            packed |=
+                ((uint64_t)(data->coeffs[data_idx + i] & 0x001f) << (5 * i));
+        }
+
+        bytes[bytes_idx++] = (uint8_t)(packed & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 8) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 16) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 24) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 32) & 0x00000000000000ff);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_R2_5
+ *
+ * Description: Transform to polynomial in Rp' from bytes array, where p' = 2^5
+ *              Use 5 bits to decode each coefficient in [0, 2^5).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp'
+ *              - const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_R2_5(poly *data, const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+        packed = packed | ((uint64_t)bytes[bytes_idx++] & 0x00000000000000ff);
+        packed =
+            packed | (((uint64_t)bytes[bytes_idx++] << 8) & 0x000000000000ff00);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 16) & 0x0000000000ff0000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 24) & 0x00000000ff000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 32) & 0x000000ff00000000);
+
+        for (i = 0; i < 8; i++) {
+            data->coeffs[data_idx + i] =
+                (int16_t)((packed >> (5 * i)) & 0x000000000000001f);
+        }
+    }
+}
+#elif SMAUGT_LOG_P_PRIME == 7
+/*************************************************
+ * Name:        pack_R2_7
+ *
+ * Description: Transform to bytes array from polynomial in Rp' where p' = 2^7
+ *              Use 7 bits to encode each coefficient in [0, 2^7).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY2_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp'
+ **************************************************/
+void pack_R2_7(uint8_t bytes[SMAUGT_CTPOLY2_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_CTPOLY2_BYTES);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+        for (i = 0; i < 8; ++i) {
+            packed |=
+                ((uint64_t)(data->coeffs[data_idx + i] & 0x007f) << (7 * i));
+        }
+
+        bytes[bytes_idx++] = (uint8_t)(packed & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 8) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 16) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 24) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 32) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 40) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 48) & 0x00000000000000ff);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_R2_7
+ *
+ * Description: Transform to polynomial in Rp' from bytes array, where p' = 2^7
+ *              Use 7 bits to decode each coefficient in [0, 2^7).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp'
+ *              - const bytes[SMAUGT_CTPOLY2_BYTES]: pointer to input bytes
+ **************************************************/
+void unpack_R2_7(poly *data, const uint8_t bytes[SMAUGT_CTPOLY2_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+        packed = packed | ((uint64_t)bytes[bytes_idx++] & 0x00000000000000ff);
+        packed =
+            packed | (((uint64_t)bytes[bytes_idx++] << 8) & 0x000000000000ff00);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 16) & 0x0000000000ff0000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 24) & 0x00000000ff000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 32) & 0x000000ff00000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 40) & 0x0000ff0000000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 48) & 0x00ff000000000000);
+
+        for (i = 0; i < 8; i++) {
+            data->coeffs[data_idx + i] =
+                (int16_t)((packed >> (7 * i)) & 0x000000000000007f);
+        }
+    }
+}
+#endif
+
+#if SMAUGT_LOG_P == 8
+/*************************************************
+ * Name:        pack_R2_8
+ *
+ * Description: Transform to bytes array from polynomial in Rp where p = 2^8
+ *              Use 8 bits to encode each coefficient in [0, 2^8).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY1_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp
+ **************************************************/
+void pack_R2_8(uint8_t bytes[SMAUGT_CTPOLY1_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_CTPOLY1_BYTES);
+    size_t i;
+    for (i = 0; i < SMAUGT_N; ++i)
+        bytes[i] = (uint8_t)(data->coeffs[i] & 0x00ff);
+}
+
+/*************************************************
+ * Name:        unpack_R2_8
+ *
+ * Description: Transform to polynomial in Rp from bytes array, where p = 2^8
+ *              Use 8 bits to decode each coefficient in [0, 2^8).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp
+ *              - const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_R2_8(poly *data, const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t i;
+    for (i = 0; i < SMAUGT_N; ++i)
+        data->coeffs[i] = (int16_t)(bytes[i] & 0xff);
+}
+#elif SMAUGT_LOG_P == 9
+/*************************************************
+ * Name:        pack_R2_9
+ *
+ * Description: Transform to bytes array from polynomial in Rp where p = 2^9
+ *              Use 9 bits to encode each coefficient in [0, 2^9).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_CTPOLY1_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rp
+ **************************************************/
+void pack_R2_9(uint8_t bytes[SMAUGT_CTPOLY1_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_CTPOLY1_BYTES);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+        uint8_t packed2 = 0;
+        for (i = 0; i < 7; ++i) {
+            packed |=
+                ((uint64_t)(data->coeffs[data_idx + i] & 0x01ff) << (9 * i));
+        }
+        packed |= (uint64_t)(data->coeffs[data_idx + 7] & 0x0001) << 63;
+        packed2 = (uint8_t)(data->coeffs[data_idx + 7] >> 1);
+
+        bytes[bytes_idx++] = (uint8_t)(packed & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 8) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 16) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 24) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 32) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 40) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 48) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 56) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)(packed2 & 0xff);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_R2_9
+ *
+ * Description: Transform to polynomial in Rp from bytes array, where p = 2^9
+ *              Use 9 bits to decode each coefficient in [0, 2^9).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rp
+ *              - const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_R2_9(poly *data, const uint8_t bytes[SMAUGT_CTPOLY1_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+        uint8_t packed2 = 0;
+        packed = packed | ((uint64_t)bytes[bytes_idx++] & 0x00000000000000ff);
+        packed =
+            packed | (((uint64_t)bytes[bytes_idx++] << 8) & 0x000000000000ff00);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 16) & 0x0000000000ff0000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 24) & 0x00000000ff000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 32) & 0x000000ff00000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 40) & 0x0000ff0000000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 48) & 0x00ff000000000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 56) & 0xff00000000000000);
+        packed2 = bytes[bytes_idx++];
+
+        for (i = 0; i < 7; i++) {
+            data->coeffs[data_idx + i] =
+                (int16_t)((packed >> (9 * i)) & 0x00000000000001ff);
+        }
+        data->coeffs[data_idx + 7] = (int16_t)(packed >> 63) & 0x0001;
+        data->coeffs[data_idx + 7] |= ((int16_t)packed2 << 1 & 0x01fe);
+    }
+}
+#endif
+
+#if SMAUGT_LOG_Q == 10
+/*************************************************
+ * Name:        pack_R2_10
+ *
+ * Description: Transform to bytes array from polynomial in Rq where q = 2^10
+ *              Use 10 bits to encode each coefficient in [0, 2^10).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_PKPOLY_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rq
+ **************************************************/
+void pack_R2_10(uint8_t bytes[SMAUGT_PKPOLY_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_PKPOLY_BYTES);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 4) {
+        uint64_t packed = 0;
+        for (i = 0; i < 4; ++i) {
+            packed |= ((uint64_t)((data->coeffs[data_idx + i] >> 6) & 0x03ff)
+                       << (10 * i));
+        }
+
+        bytes[bytes_idx++] = (uint8_t)(packed & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 8) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 16) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 24) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 32) & 0x00000000000000ff);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_R2_10
+ *
+ * Description: Transform to polynomial in Rq from bytes array, where q = 2^10
+ *              Use 10 bits to decode each coefficient in [0, 2^10).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rq
+ *              - const uint8_t bytes[SMAUGT_PKPOLY_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_R2_10(poly *data, const uint8_t bytes[SMAUGT_PKPOLY_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 4) {
+        uint64_t packed = 0;
+        packed = packed | ((uint64_t)bytes[bytes_idx++] & 0x00000000000000ff);
+        packed =
+            packed | (((uint64_t)bytes[bytes_idx++] << 8) & 0x000000000000ff00);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 16) & 0x0000000000ff0000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 24) & 0x00000000ff000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 32) & 0x000000ff00000000);
+
+        for (i = 0; i < 4; i++) {
+            data->coeffs[data_idx + i] =
+                (int16_t)(((packed >> (10 * i)) & 0x00000000000003ff) << 6);
+        }
+    }
+}
+#elif SMAUGT_LOG_Q == 11
+/*************************************************
+ * Name:        pack_R2_11
+ *
+ * Description: Transform to bytes array from polynomial in Rq where q = 2^11
+ *              Use 11 bits to encode each coefficient in [0, 2^11).
+ *
+ * Arguments:   - uint8_t bytes[SMAUGT_PKPOLY_BYTES]: pointer to output bytes
+ *              - const poly *data: pointer to input polynomial in Rq
+ **************************************************/
+void pack_R2_11(uint8_t bytes[SMAUGT_PKPOLY_BYTES], const poly *data) {
+    memset(bytes, 0, sizeof(uint8_t) * SMAUGT_PKPOLY_BYTES);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+        uint32_t packed2 = 0;
+
+        for (i = 0; i < 5; ++i) {
+            packed |= ((uint64_t)((data->coeffs[data_idx + i] >> 5) & 0x07ff)
+                       << (11 * i));
+        }
+        packed |= (uint64_t)((data->coeffs[data_idx + 5] >> 5) & 0x0001) << 55;
+
+        packed2 = (uint32_t)((data->coeffs[data_idx + 5] >> 6) & 0x03ff);
+        for (i = 0; i < 2; ++i) {
+            packed2 |=
+                ((uint64_t)((data->coeffs[data_idx + 6 + i] >> 5) & 0x07ff)
+                 << (10 + 11 * i));
+        }
+
+        bytes[bytes_idx++] = (uint8_t)(packed & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 8) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 16) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 24) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 32) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 40) & 0x00000000000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed >> 48) & 0x00000000000000ff);
+
+        bytes[bytes_idx++] = (uint8_t)(packed2 & 0x000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed2 >> 8) & 0x000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed2 >> 16) & 0x000000ff);
+        bytes[bytes_idx++] = (uint8_t)((packed2 >> 24) & 0x000000ff);
+    }
+}
+
+/*************************************************
+ * Name:        unpack_R2_11
+ *
+ * Description: Transform to polynomial in Rq from bytes array, where q = 2^11
+ *              Use 11 bits to decode each coefficient in [0, 2^11).
+ *
+ * Arguments:   - poly *data: pointer to output polynomial in Rq
+ *              - const uint8_t bytes[SMAUGT_PKPOLY_BYTES]: pointer to input
+ *              bytes
+ **************************************************/
+void unpack_R2_11(poly *data, const uint8_t bytes[SMAUGT_PKPOLY_BYTES]) {
+    memset(data, 0, sizeof(int16_t) * SMAUGT_N);
+    size_t bytes_idx = 0;
+    size_t i;
+    for (size_t data_idx = 0; data_idx < SMAUGT_N; data_idx += 8) {
+        uint64_t packed = 0;
+        uint32_t packed2 = 0;
+        packed = packed | ((uint64_t)bytes[bytes_idx++] & 0x00000000000000ff);
+        packed =
+            packed | (((uint64_t)bytes[bytes_idx++] << 8) & 0x000000000000ff00);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 16) & 0x0000000000ff0000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 24) & 0x00000000ff000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 32) & 0x000000ff00000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 40) & 0x0000ff0000000000);
+        packed = packed |
+                 (((uint64_t)bytes[bytes_idx++] << 48) & 0x00ff000000000000);
+
+        packed2 = packed2 | ((uint64_t)bytes[bytes_idx++] & 0x00000000000000ff);
+        packed2 = packed2 |
+                  (((uint64_t)bytes[bytes_idx++] << 8) & 0x000000000000ff00);
+        packed2 = packed2 |
+                  (((uint64_t)bytes[bytes_idx++] << 16) & 0x0000000000ff0000);
+        packed2 = packed2 |
+                  (((uint64_t)bytes[bytes_idx++] << 24) & 0x00000000ff000000);
+
+        for (i = 0; i < 5; i++) {
+            data->coeffs[data_idx + i] =
+                (int16_t)(((packed >> (11 * i)) & 0x00000000000007ff) << 5);
+        }
+        data->coeffs[data_idx + 5] = (int16_t)(((packed >> 55) & 0x0001) << 5);
+
+        data->coeffs[data_idx + 5] |= (int16_t)((packed2 & 0x03ff) << 6);
+        for (i = 0; i < 2; i++) {
+            data->coeffs[data_idx + 6 + i] =
+                (int16_t)(((packed2 >> (10 + 11 * i)) & 0x000007ff) << 5);
+        }
+    }
+}
+#endif
diff --git a/cbits/third_party/SMAUG-T/src/poly.c b/cbits/third_party/SMAUG-T/src/poly.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/poly.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: MIT
+
+#include "poly.h"
+#include "toomcook.h"
+#include <string.h>
+
+#if SMAUGT_MODE == SMAUGT_MODET
+/*************************************************
+ * Name:        flipabs
+ *
+ * Description: Computes |(x mod q) - Q/2|
+ *
+ * Arguments:   - uint16_t x: input coefficient
+ *
+ * Return: |(x mod q) - Q/2|
+ **************************************************/
+uint16_t flipabs(uint16_t x) {
+    int16_t r, m;
+    r = x - (SMAUGT_MODULUS_SCALED_Q_HALF);
+    m = r >> 15;
+    return (r + m) ^ m;
+}
+
+/*************************************************
+ * Name:        d2_ecd
+ *
+ * Description: Convert 16-byte message to polynomial
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const uint8_t msg[SMAUGT_MSG_BYTES]: pointer to input message
+ **************************************************/
+void d2_ecd(poly *r, const uint8_t msg[SMAUGT_MSG_BYTES]) {
+    unsigned int mask;
+    for (size_t i = 0; i < SMAUGT_MSG_BYTES; i++) {
+        for (size_t j = 0; j < 8; j++) {
+            mask = (msg[i] >> j) & 1;
+            mask = (mask * SMAUGT_MODULUS_SCALED_Q_HALF) &
+                   SMAUGT_MODULUS_SCALED_Q_HALF;
+            r->coeffs[8 * i + j] = mask;
+            r->coeffs[8 * i + j + 128] = mask;
+        }
+    }
+}
+
+/*************************************************
+ * Name:        d2_dcd
+ *
+ * Description: Convert polynomial to 16-byte message
+ *
+ * Arguments:   - uint8_t msg[SMAUGT_MSG_BYTES]: pointer to output message
+ *              - const poly *x: pointer to input polynomial
+ **************************************************/
+void d2_dcd(uint8_t msg[SMAUGT_MSG_BYTES], const poly *x) {
+    uint16_t t;
+    for (size_t i = 0; i < SMAUGT_MSG_BYTES; i++)
+        msg[i] = 0;
+
+    for (size_t i = 0; i < SMAUGT_N / 2; i++) {
+        t = flipabs(x->coeffs[i]);
+        t += flipabs(x->coeffs[i + 128]);
+        t = t - SMAUGT_MODULUS_SCALED_Q_HALF;
+        t >>= 15;
+        msg[i >> 3] |= t << (i & 7);
+    }
+}
+#endif
+/*************************************************
+ * Name:        poly_add
+ *
+ * Description: Add two polynomials. no modular reduction is performed
+ *
+ * Arguments: - poly *r: pointer to output polynomial
+ *            - const poly *a: pointer to first input polynomial
+ *            - const poly *b: pointer to second input polynomial
+ **************************************************/
+static inline void poly_add(poly *r, const poly *a, const poly *b) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_N; i++)
+        r->coeffs[i] = a->coeffs[i] + b->coeffs[i];
+}
+
+/*************************************************
+ * Name:        poly_sub
+ *
+ * Description: Subtract two polynomials. no modular reduction is performed
+ *
+ * Arguments: - poly *r: pointer to output polynomial
+ *            - const poly *a: pointer to first input polynomial
+ *            - const poly *b: pointer to second input polynomial
+ **************************************************/
+static inline void poly_sub(poly *r, const poly *a, const poly *b) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_N; i++)
+        r->coeffs[i] = a->coeffs[i] - b->coeffs[i];
+}
+
+#define vec_vec_mult SMAUGT_NAMESPACE(vec_vec_mult)
+/*************************************************
+ * Name:        vec_vec_mult
+ *
+ * Description: Multiply two vector of polynomials.
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const polyvec *a: pointer to input vector of polynomials
+ *              - const polyvec *b: pointer to input vector of polynomials
+ **************************************************/
+void vec_vec_mult(poly *r, const polyvec *a, const polyvec *b) {
+    unsigned int i;
+    for (i = 0; i < SMAUGT_K; i++)
+        poly_mul_acc(a->vec[i].coeffs, b->vec[i].coeffs, r->coeffs);
+}
+
+/*************************************************
+ * Name:        vec_vec_mult_add
+ *
+ * Description: Multiply two vectors of polynomials and add the result to output
+ *              polynomial
+ *
+ * Arguments:   - poly *r: pointer to output polynomial
+ *              - const polyvec *a: pointer to input vector of polynomials
+ *              - const polyvec *b: pointer to input vector of polynomials
+ *              - uint8_t mod: modulus (16-SMAUGT_LOG_P) or (16-SMAUGT_LOG_Q)
+ **************************************************/
+void vec_vec_mult_add(poly *r, const polyvec *a, const polyvec *b,
+                      uint8_t mod) {
+    unsigned int i, j;
+    polyvec al;
+    poly res;
+
+    for (i = 0; i < SMAUGT_K; ++i)
+        for (j = 0; j < SMAUGT_N; ++j)
+            al.vec[i].coeffs[j] = a->vec[i].coeffs[j] >> mod;
+
+    memset(&res, 0, sizeof(poly));
+    vec_vec_mult(&res, &al, b);
+    for (j = 0; j < SMAUGT_N; ++j)
+        res.coeffs[j] = (int16_t)((uint16_t)res.coeffs[j] << mod);
+
+    poly_add(r, r, &res);
+}
+
+/*************************************************
+ * Name:        matrix_vec_mult_add
+ *
+ * Description: Transpose the matrix of polynomial and multiply it with the
+ *              vector of polynomials.
+ *
+ * Arguments:   - polyvec *r: pointer to output vector of polynomials
+ *              - const polyvec a[SMAUGT_K]: pointer to input matrix of
+ *              polynomials
+ *              - const polyvec *b: pointer to input vector of polynomials
+ **************************************************/
+void matrix_vec_mult_add(polyvec *r, const polyvec a[SMAUGT_K],
+                         const polyvec *b) {
+    unsigned int i, j, k;
+    polyvec at;
+
+    for (i = 0; i < SMAUGT_K; ++i) {
+        for (j = 0; j < SMAUGT_K; ++j)
+            for (k = 0; k < SMAUGT_N; ++k)
+                at.vec[j].coeffs[k] =
+                    a[j].vec[i].coeffs[k] >> SMAUGT_MODULUS_16_LOG_Q;
+
+        vec_vec_mult(&r->vec[i], &at, b);
+        for (j = 0; j < SMAUGT_N; ++j)
+            r->vec[i].coeffs[j] =
+                (int16_t)((uint16_t)r->vec[i].coeffs[j]
+                          << SMAUGT_MODULUS_16_LOG_Q);
+    }
+}
+
+/*************************************************
+ * Name:        matrix_vec_mult_sub
+ *
+ * Description: Multiply the matrix of polynomial with the vector of polynomial
+ *              and subtract the result to output vector of polynomials.
+ *
+ * Arguments:   - polyvec *r: pointer to input/output vector of polynomials
+ *              - const polyvec a[SMAUGT_K]: pointer to input matrix of
+ *              polynomials
+ *              - const polyvec *b: pointer to input vector of polynomials
+ **************************************************/
+void matrix_vec_mult_sub(polyvec *r, const polyvec a[SMAUGT_K],
+                         const polyvec *b) {
+    unsigned int i, j, k;
+    polyvec al;
+    poly res;
+
+    for (i = 0; i < SMAUGT_K; ++i) {
+        for (j = 0; j < SMAUGT_K; ++j)
+            for (k = 0; k < SMAUGT_N; ++k)
+                al.vec[j].coeffs[k] =
+                    a[i].vec[j].coeffs[k] >> SMAUGT_MODULUS_16_LOG_Q;
+
+        memset(&res, 0, sizeof(poly));
+        vec_vec_mult(&res, &al, b);
+        for (j = 0; j < SMAUGT_N; ++j)
+            res.coeffs[j] =
+                (int16_t)((uint16_t)res.coeffs[j]
+                          << SMAUGT_MODULUS_16_LOG_Q);
+
+        poly_sub(&r->vec[i], &r->vec[i], &res);
+    }
+}
diff --git a/cbits/third_party/SMAUG-T/src/toomcook.c b/cbits/third_party/SMAUG-T/src/toomcook.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/toomcook.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: MIT
+
+#include "toomcook.h"
+#include <stdint.h>
+#include <string.h>
+
+#define SCHB_N 16
+
+#define N_RES (SMAUGT_N << 1)
+#define N_SB (SMAUGT_N >> 2)
+#define N_SB_RES (2 * N_SB - 1)
+
+#define OVERFLOWING_MUL(X, Y) ((uint16_t)((uint32_t)(X) * (uint32_t)(Y)))
+
+#define KARATSUBA_N 64
+#define karatsuba_simple SMAUGT_NAMESPACE(karatsuba_simple)
+static void karatsuba_simple(const uint16_t *a_1, const uint16_t *b_1,
+                             uint16_t *result_final) {
+    uint16_t d01[KARATSUBA_N / 2 - 1];
+    uint16_t d0123[KARATSUBA_N / 2 - 1];
+    uint16_t d23[KARATSUBA_N / 2 - 1];
+    uint16_t result_d01[KARATSUBA_N - 1];
+
+    int32_t i, j;
+
+    memset(result_d01, 0, (KARATSUBA_N - 1) * sizeof(uint16_t));
+    memset(d01, 0, (KARATSUBA_N / 2 - 1) * sizeof(uint16_t));
+    memset(d0123, 0, (KARATSUBA_N / 2 - 1) * sizeof(uint16_t));
+    memset(d23, 0, (KARATSUBA_N / 2 - 1) * sizeof(uint16_t));
+    memset(result_final, 0, (2 * KARATSUBA_N - 1) * sizeof(uint16_t));
+
+    uint16_t acc1, acc2, acc3, acc4, acc5, acc6, acc7, acc8, acc9, acc10;
+
+    for (i = 0; i < KARATSUBA_N / 4; i++) {
+        acc1 = a_1[i];                       // a0
+        acc2 = a_1[i + KARATSUBA_N / 4];     // a1
+        acc3 = a_1[i + 2 * KARATSUBA_N / 4]; // a2
+        acc4 = a_1[i + 3 * KARATSUBA_N / 4]; // a3
+        for (j = 0; j < KARATSUBA_N / 4; j++) {
+
+            acc5 = b_1[j];                   // b0
+            acc6 = b_1[j + KARATSUBA_N / 4]; // b1
+
+            result_final[i + j + 0 * KARATSUBA_N / 4] =
+                result_final[i + j + 0 * KARATSUBA_N / 4] +
+                OVERFLOWING_MUL(acc1, acc5);
+            result_final[i + j + 2 * KARATSUBA_N / 4] =
+                result_final[i + j + 2 * KARATSUBA_N / 4] +
+                OVERFLOWING_MUL(acc2, acc6);
+
+            acc7 = acc5 + acc6; // b01
+            acc8 = acc1 + acc2; // a01
+            d01[i + j] = d01[i + j] + (uint16_t)(acc7 * (uint64_t)acc8);
+            //--------------------------------------------------------
+
+            acc7 = b_1[j + 2 * KARATSUBA_N / 4]; // b2
+            acc8 = b_1[j + 3 * KARATSUBA_N / 4]; // b3
+            result_final[i + j + 4 * KARATSUBA_N / 4] =
+                result_final[i + j + 4 * KARATSUBA_N / 4] +
+                OVERFLOWING_MUL(acc7, acc3);
+
+            result_final[i + j + 6 * KARATSUBA_N / 4] =
+                result_final[i + j + 6 * KARATSUBA_N / 4] +
+                OVERFLOWING_MUL(acc8, acc4);
+
+            acc9 = acc3 + acc4;
+            acc10 = acc7 + acc8;
+            d23[i + j] = d23[i + j] + OVERFLOWING_MUL(acc9, acc10);
+            //--------------------------------------------------------
+
+            acc5 = acc5 + acc7; // b02
+            acc7 = acc1 + acc3; // a02
+            result_d01[i + j + 0 * KARATSUBA_N / 4] =
+                result_d01[i + j + 0 * KARATSUBA_N / 4] +
+                OVERFLOWING_MUL(acc5, acc7);
+
+            acc6 = acc6 + acc8; // b13
+            acc8 = acc2 + acc4;
+            result_d01[i + j + 2 * KARATSUBA_N / 4] =
+                result_d01[i + j + 2 * KARATSUBA_N / 4] +
+                OVERFLOWING_MUL(acc6, acc8);
+
+            acc5 = acc5 + acc6;
+            acc7 = acc7 + acc8;
+            d0123[i + j] = d0123[i + j] + OVERFLOWING_MUL(acc5, acc7);
+        }
+    }
+
+    // 2nd last stage
+
+    for (i = 0; i < KARATSUBA_N / 2 - 1; i++) {
+        d0123[i] = d0123[i] - result_d01[i + 0 * KARATSUBA_N / 4] -
+                   result_d01[i + 2 * KARATSUBA_N / 4];
+        d01[i] = d01[i] - result_final[i + 0 * KARATSUBA_N / 4] -
+                 result_final[i + 2 * KARATSUBA_N / 4];
+        d23[i] = d23[i] - result_final[i + 4 * KARATSUBA_N / 4] -
+                 result_final[i + 6 * KARATSUBA_N / 4];
+    }
+
+    for (i = 0; i < KARATSUBA_N / 2 - 1; i++) {
+        result_d01[i + 1 * KARATSUBA_N / 4] =
+            result_d01[i + 1 * KARATSUBA_N / 4] + d0123[i];
+        result_final[i + 1 * KARATSUBA_N / 4] =
+            result_final[i + 1 * KARATSUBA_N / 4] + d01[i];
+        result_final[i + 5 * KARATSUBA_N / 4] =
+            result_final[i + 5 * KARATSUBA_N / 4] + d23[i];
+    }
+
+    // Last stage
+    for (i = 0; i < KARATSUBA_N - 1; i++) {
+        result_d01[i] =
+            result_d01[i] - result_final[i] - result_final[i + KARATSUBA_N];
+    }
+
+    for (i = 0; i < KARATSUBA_N - 1; i++) {
+        result_final[i + 1 * KARATSUBA_N / 2] =
+            result_final[i + 1 * KARATSUBA_N / 2] + result_d01[i];
+    }
+}
+
+#define toom_cook_4way SMAUGT_NAMESPACE(toom_cook_4way)
+static void toom_cook_4way(const uint16_t *a1, const uint16_t *b1,
+                           uint16_t *result) {
+    uint16_t inv3 = 43691, inv9 = 36409, inv15 = 61167;
+
+    uint16_t aw1[N_SB], aw2[N_SB], aw3[N_SB], aw4[N_SB], aw5[N_SB], aw6[N_SB],
+        aw7[N_SB];
+    uint16_t bw1[N_SB], bw2[N_SB], bw3[N_SB], bw4[N_SB], bw5[N_SB], bw6[N_SB],
+        bw7[N_SB];
+    uint16_t w1[N_SB_RES] = {0}, w2[N_SB_RES] = {0}, w3[N_SB_RES] = {0},
+             w4[N_SB_RES] = {0}, w5[N_SB_RES] = {0}, w6[N_SB_RES] = {0},
+             w7[N_SB_RES] = {0};
+    uint16_t r0, r1, r2, r3, r4, r5, r6, r7;
+    uint16_t *A0, *A1, *A2, *A3, *B0, *B1, *B2, *B3;
+    A0 = (uint16_t *)a1;
+    A1 = (uint16_t *)&a1[N_SB];
+    A2 = (uint16_t *)&a1[2 * N_SB];
+    A3 = (uint16_t *)&a1[3 * N_SB];
+    B0 = (uint16_t *)b1;
+    B1 = (uint16_t *)&b1[N_SB];
+    B2 = (uint16_t *)&b1[2 * N_SB];
+    B3 = (uint16_t *)&b1[3 * N_SB];
+
+    uint16_t *C;
+    C = result;
+
+    int i, j;
+
+    // EVALUATION
+    for (j = 0; j < N_SB; ++j) {
+        r0 = A0[j];
+        r1 = A1[j];
+        r2 = A2[j];
+        r3 = A3[j];
+        r4 = r0 + r2;
+        r5 = r1 + r3;
+        r6 = r4 + r5;
+        r7 = r4 - r5;
+        aw3[j] = r6;
+        aw4[j] = r7;
+        r4 = ((r0 << 2) + r2) << 1;
+        r5 = (r1 << 2) + r3;
+        r6 = r4 + r5;
+        r7 = r4 - r5;
+        aw5[j] = r6;
+        aw6[j] = r7;
+        r4 = (r3 << 3) + (r2 << 2) + (r1 << 1) + r0;
+        aw2[j] = r4;
+        aw7[j] = r0;
+        aw1[j] = r3;
+    }
+    for (j = 0; j < N_SB; ++j) {
+        r0 = B0[j];
+        r1 = B1[j];
+        r2 = B2[j];
+        r3 = B3[j];
+        r4 = r0 + r2;
+        r5 = r1 + r3;
+        r6 = r4 + r5;
+        r7 = r4 - r5;
+        bw3[j] = r6;
+        bw4[j] = r7;
+        r4 = ((r0 << 2) + r2) << 1;
+        r5 = (r1 << 2) + r3;
+        r6 = r4 + r5;
+        r7 = r4 - r5;
+        bw5[j] = r6;
+        bw6[j] = r7;
+        r4 = (r3 << 3) + (r2 << 2) + (r1 << 1) + r0;
+        bw2[j] = r4;
+        bw7[j] = r0;
+        bw1[j] = r3;
+    }
+
+    // MULTIPLICATION
+
+    karatsuba_simple(aw1, bw1, w1);
+    karatsuba_simple(aw2, bw2, w2);
+    karatsuba_simple(aw3, bw3, w3);
+    karatsuba_simple(aw4, bw4, w4);
+    karatsuba_simple(aw5, bw5, w5);
+    karatsuba_simple(aw6, bw6, w6);
+    karatsuba_simple(aw7, bw7, w7);
+
+    // INTERPOLATION
+    for (i = 0; i < N_SB_RES; ++i) {
+        r0 = w1[i];
+        r1 = w2[i];
+        r2 = w3[i];
+        r3 = w4[i];
+        r4 = w5[i];
+        r5 = w6[i];
+        r6 = w7[i];
+
+        r1 = r1 + r4;
+        r5 = r5 - r4;
+        r3 = ((r3 - r2) >> 1);
+        r4 = r4 - r0;
+        r4 = r4 - (r6 << 6);
+        r4 = (r4 << 1) + r5;
+        r2 = r2 + r3;
+        r1 = r1 - (r2 << 6) - r2;
+        r2 = r2 - r6;
+        r2 = r2 - r0;
+        r1 = r1 + 45 * r2;
+        r4 = (uint16_t)(((r4 - (r2 << 3)) * (uint32_t)inv3) >> 3);
+        r5 = r5 + r1;
+        r1 = (uint16_t)(((r1 + (r3 << 4)) * (uint32_t)inv9) >> 1);
+        r3 = -(r3 + r1);
+        r5 = (uint16_t)(((30 * r1 - r5) * (uint32_t)inv15) >> 2);
+        r2 = r2 - r4;
+        r1 = r1 - r5;
+
+        C[i] += r6;
+        C[i + 64] += r5;
+        C[i + 128] += r4;
+        C[i + 192] += r3;
+        C[i + 256] += r2;
+        C[i + 320] += r1;
+        C[i + 384] += r0;
+    }
+}
+
+/* res += a*b */
+void poly_mul_acc(const int16_t a[SMAUGT_N], const int16_t b[SMAUGT_N],
+                  int16_t res[SMAUGT_N]) {
+    uint16_t c[2 * SMAUGT_N] = {0};
+    int i;
+
+    toom_cook_4way((uint16_t *)a, (uint16_t *)b, c);
+
+    /* reduction */
+    for (i = SMAUGT_N; i < 2 * SMAUGT_N; i++) {
+        res[i - SMAUGT_N] += (c[i - SMAUGT_N] - c[i]);
+    }
+}
diff --git a/cbits/third_party/SMAUG-T/src/verify.c b/cbits/third_party/SMAUG-T/src/verify.c
new file mode 100644
--- /dev/null
+++ b/cbits/third_party/SMAUG-T/src/verify.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: MIT
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "verify.h"
+
+/*************************************************
+ * Name:        verify
+ *
+ * Description: Compare two arrays for equality in constant time.
+ *
+ * Arguments:   - const uint8_t *a: pointer to first byte array
+ *              - const uint8_t *b: pointer to second byte array
+ *              - size_t len: length of the byte arrays
+ *
+ * Return: 0 if the byte arrays are equal, 1 otherwise
+ **************************************************/
+int verify(const uint8_t *a, const uint8_t *b, size_t len) {
+    size_t i;
+    uint8_t r = 0;
+
+    for (i = 0; i < len; i++)
+        r |= a[i] ^ b[i];
+
+    return (-(uint64_t)r) >> 63;
+}
+
+/*************************************************
+ * Name:        cmov
+ *
+ * Description: Copy len bytes from x to r if b is 1;
+ *              don't modify x if b is 0. Requires b to be in {0,1};
+ *              assumes two's complement representation of negative integers.
+ *              Runs in constant time.
+ *
+ * Arguments:   - uint8_t *r: pointer to output byte array
+ *              - const uint8_t *x: pointer to input byte array
+ *              - size_t len: amount of bytes to be copied
+ *              - uint8_t b: condition bit; has to be in {0,1}
+ **************************************************/
+void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b) {
+    size_t i;
+
+    b = -b;
+    for (i = 0; i < len; i++)
+        r[i] ^= b & (r[i] ^ x[i]);
+}
diff --git a/native/Crypto/KpqC/Internal/Native/AIMer.hs b/native/Crypto/KpqC/Internal/Native/AIMer.hs
new file mode 100644
--- /dev/null
+++ b/native/Crypto/KpqC/Internal/Native/AIMer.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Crypto.KpqC.Internal.Native.AIMer where
+
+import Data.Word (Word8, Word64)
+import Foreign.C.Types (CInt (..), CSize (..))
+import Foreign.Ptr (Ptr)
+
+foreign import ccall safe "kpqc_aimer128f_keypair" aimer128fKeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer128f_sign" aimer128fSign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer128f_verify" aimer128fVerify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_aimer128s_keypair" aimer128sKeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer128s_sign" aimer128sSign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer128s_verify" aimer128sVerify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_aimer192f_keypair" aimer192fKeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer192f_sign" aimer192fSign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer192f_verify" aimer192fVerify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_aimer192s_keypair" aimer192sKeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer192s_sign" aimer192sSign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer192s_verify" aimer192sVerify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_aimer256f_keypair" aimer256fKeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer256f_sign" aimer256fSign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer256f_verify" aimer256fVerify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_aimer256s_keypair" aimer256sKeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer256s_sign" aimer256sSign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_aimer256s_verify" aimer256sVerify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+#ifdef KPQC_TEST_ENTROPY
+foreign import ccall safe "kpqc_aimer128f_set_test_entropy" aimer128fSetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_aimer128f_test_entropy_remaining" aimer128fTestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_aimer128f_clear_test_entropy" aimer128fClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_aimer128s_set_test_entropy" aimer128sSetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_aimer128s_test_entropy_remaining" aimer128sTestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_aimer128s_clear_test_entropy" aimer128sClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_aimer192f_set_test_entropy" aimer192fSetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_aimer192f_test_entropy_remaining" aimer192fTestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_aimer192f_clear_test_entropy" aimer192fClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_aimer192s_set_test_entropy" aimer192sSetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_aimer192s_test_entropy_remaining" aimer192sTestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_aimer192s_clear_test_entropy" aimer192sClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_aimer256f_set_test_entropy" aimer256fSetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_aimer256f_test_entropy_remaining" aimer256fTestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_aimer256f_clear_test_entropy" aimer256fClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_aimer256s_set_test_entropy" aimer256sSetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_aimer256s_test_entropy_remaining" aimer256sTestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_aimer256s_clear_test_entropy" aimer256sClearTestEntropy :: IO ()
+#endif
diff --git a/native/Crypto/KpqC/Internal/Native/HAETAE.hs b/native/Crypto/KpqC/Internal/Native/HAETAE.hs
new file mode 100644
--- /dev/null
+++ b/native/Crypto/KpqC/Internal/Native/HAETAE.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Crypto.KpqC.Internal.Native.HAETAE where
+
+import Data.Word (Word8, Word64)
+import Foreign.C.Types (CInt (..), CSize (..))
+import Foreign.Ptr (Ptr)
+
+foreign import ccall safe "kpqc_haetae2_keypair" haetae2KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_haetae2_sign" haetae2Sign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_haetae2_verify" haetae2Verify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_haetae3_keypair" haetae3KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_haetae3_sign" haetae3Sign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_haetae3_verify" haetae3Verify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_haetae5_keypair" haetae5KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_haetae5_sign" haetae5Sign :: Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_haetae5_verify" haetae5Verify :: Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+#ifdef KPQC_TEST_ENTROPY
+foreign import ccall safe "kpqc_haetae2_set_test_entropy" haetae2SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_haetae2_test_entropy_remaining" haetae2TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_haetae2_clear_test_entropy" haetae2ClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_haetae3_set_test_entropy" haetae3SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_haetae3_test_entropy_remaining" haetae3TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_haetae3_clear_test_entropy" haetae3ClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_haetae5_set_test_entropy" haetae5SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_haetae5_test_entropy_remaining" haetae5TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_haetae5_clear_test_entropy" haetae5ClearTestEntropy :: IO ()
+#endif
diff --git a/native/Crypto/KpqC/Internal/Native/NTRUPlus.hs b/native/Crypto/KpqC/Internal/Native/NTRUPlus.hs
new file mode 100644
--- /dev/null
+++ b/native/Crypto/KpqC/Internal/Native/NTRUPlus.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Crypto.KpqC.Internal.Native.NTRUPlus where
+
+import Data.Word (Word8)
+import Foreign.C.Types (CInt (..))
+import Foreign.Ptr (Ptr)
+
+#ifdef KPQC_TEST_ENTROPY
+import Foreign.C.Types (CSize (..))
+#endif
+
+foreign import ccall safe "kpqc_ntruplus768_keypair" ntruplus768KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_ntruplus768_encapsulate" ntruplus768Encapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_ntruplus768_decapsulate" ntruplus768Decapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_ntruplus864_keypair" ntruplus864KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_ntruplus864_encapsulate" ntruplus864Encapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_ntruplus864_decapsulate" ntruplus864Decapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_ntruplus1152_keypair" ntruplus1152KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_ntruplus1152_encapsulate" ntruplus1152Encapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_ntruplus1152_decapsulate" ntruplus1152Decapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+#ifdef KPQC_TEST_ENTROPY
+foreign import ccall safe "kpqc_ntruplus768_set_test_entropy" ntruplus768SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_ntruplus768_test_entropy_remaining" ntruplus768TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_ntruplus768_clear_test_entropy" ntruplus768ClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_ntruplus864_set_test_entropy" ntruplus864SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_ntruplus864_test_entropy_remaining" ntruplus864TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_ntruplus864_clear_test_entropy" ntruplus864ClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_ntruplus1152_set_test_entropy" ntruplus1152SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_ntruplus1152_test_entropy_remaining" ntruplus1152TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_ntruplus1152_clear_test_entropy" ntruplus1152ClearTestEntropy :: IO ()
+#endif
diff --git a/native/Crypto/KpqC/Internal/Native/SMAUGT.hs b/native/Crypto/KpqC/Internal/Native/SMAUGT.hs
new file mode 100644
--- /dev/null
+++ b/native/Crypto/KpqC/Internal/Native/SMAUGT.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Crypto.KpqC.Internal.Native.SMAUGT where
+
+import Data.Word (Word8)
+import Foreign.C.Types (CInt (..))
+import Foreign.Ptr (Ptr)
+
+#ifdef KPQC_TEST_ENTROPY
+import Foreign.C.Types (CSize (..))
+#endif
+
+foreign import ccall safe "kpqc_smaugt128_keypair" smaugt128KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_smaugt128_encapsulate" smaugt128Encapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_smaugt128_decapsulate" smaugt128Decapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_smaugt192_keypair" smaugt192KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_smaugt192_encapsulate" smaugt192Encapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_smaugt192_decapsulate" smaugt192Decapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_smaugt256_keypair" smaugt256KeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_smaugt256_encapsulate" smaugt256Encapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_smaugt256_decapsulate" smaugt256Decapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+foreign import ccall safe "kpqc_timer_keypair" timerKeyPair :: Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_timer_encapsulate" timerEncapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+foreign import ccall safe "kpqc_timer_decapsulate" timerDecapsulate :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+#ifdef KPQC_TEST_ENTROPY
+foreign import ccall safe "kpqc_smaugt128_set_test_entropy" smaugt128SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_smaugt128_test_entropy_remaining" smaugt128TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_smaugt128_clear_test_entropy" smaugt128ClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_smaugt192_set_test_entropy" smaugt192SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_smaugt192_test_entropy_remaining" smaugt192TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_smaugt192_clear_test_entropy" smaugt192ClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_smaugt256_set_test_entropy" smaugt256SetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_smaugt256_test_entropy_remaining" smaugt256TestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_smaugt256_clear_test_entropy" smaugt256ClearTestEntropy :: IO ()
+foreign import ccall safe "kpqc_timer_set_test_entropy" timerSetTestEntropy :: Ptr Word8 -> CSize -> IO CInt
+foreign import ccall safe "kpqc_timer_test_entropy_remaining" timerTestEntropyRemaining :: IO CSize
+foreign import ccall safe "kpqc_timer_clear_test_entropy" timerClearTestEntropy :: IO ()
+#endif
diff --git a/src/Crypto/KpqC.hs b/src/Crypto/KpqC.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/KpqC.hs
@@ -0,0 +1,409 @@
+-- | Synchronous bindings for the KpqC post-quantum algorithms.
+module Crypto.KpqC
+  ( Algorithm (..),
+    SignatureAlgorithm (..),
+    KEMAlgorithm (..),
+    SignatureSizes (..),
+    KEMSizes (..),
+    SecretBytes,
+    KeyPair (..),
+    EncapsulatedSecret (..),
+    Operation (..),
+    KpqCError (..),
+    signatureAlgorithms,
+    kemAlgorithms,
+    signatureSizes,
+    kemSizes,
+    sign,
+    signWithContext,
+    verify,
+    verifyWithContext,
+    encapsulate,
+    decapsulate
+  )
+where
+
+import Control.Exception (finally)
+import Control.Monad (when)
+import qualified Crypto.KpqC.Internal.Native.AIMer as AIMer
+import qualified Crypto.KpqC.Internal.Native.HAETAE as HAETAE
+import qualified Crypto.KpqC.Internal.Native.NTRUPlus as NTRUPlus
+import qualified Crypto.KpqC.Internal.Native.SMAUGT as SMAUGT
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import qualified Data.ByteArray as BA
+import Data.Word (Word8, Word64)
+import Foreign.C.Types (CInt, CSize)
+import Foreign.Marshal.Alloc (alloca)
+import Foreign.Marshal.Array (allocaArray)
+import Foreign.Marshal.Utils (fillBytes)
+import Foreign.Ptr (Ptr, castPtr)
+import Foreign.Storable (peek, poke)
+
+-- | A detached-signature parameter set.
+data SignatureAlgorithm
+  = AIMer128f
+  | AIMer128s
+  | AIMer192f
+  | AIMer192s
+  | AIMer256f
+  | AIMer256s
+  | HAETAE2
+  | HAETAE3
+  | HAETAE5
+  deriving (Bounded, Enum, Eq, Ord, Read, Show)
+
+-- | A KEM parameter set.
+data KEMAlgorithm
+  = NTRUPlus768
+  | NTRUPlus864
+  | NTRUPlus1152
+  | SMAUGT128
+  | SMAUGT192
+  | SMAUGT256
+  | TiMER
+  deriving (Bounded, Enum, Eq, Ord, Read, Show)
+
+-- | Fixed byte lengths for a signature parameter set.
+data SignatureSizes = SignatureSizes
+  { signaturePublicKeyBytes :: !Int,
+    signatureSecretKeyBytes :: !Int,
+    signatureBytes :: !Int
+  }
+  deriving (Eq, Show)
+
+-- | Fixed byte lengths for a KEM parameter set.
+data KEMSizes = KEMSizes
+  { kemPublicKeyBytes :: !Int,
+    kemSecretKeyBytes :: !Int,
+    ciphertextBytes :: !Int,
+    sharedSecretBytes :: !Int
+  }
+  deriving (Eq, Show)
+
+-- | Secret material backed by memory that is scrubbed when no longer
+-- referenced and compared in constant time.
+type SecretBytes = BA.ScrubbedBytes
+
+-- | A public key and its corresponding secret key.
+data KeyPair = KeyPair
+  { publicKey :: !ByteString,
+    secretKey :: !SecretBytes
+  }
+  deriving (Eq)
+
+instance Show KeyPair where
+  showsPrec _ keys =
+    showString "KeyPair {publicKey = <"
+      . shows (BS.length (publicKey keys))
+      . showString " bytes>, secretKey = <redacted>}"
+
+-- | A ciphertext and the shared secret established for its recipient.
+data EncapsulatedSecret = EncapsulatedSecret
+  { ciphertext :: !ByteString,
+    sharedSecret :: !SecretBytes
+  }
+  deriving (Eq)
+
+instance Show EncapsulatedSecret where
+  showsPrec _ value =
+    showString "EncapsulatedSecret {ciphertext = <"
+      . shows (BS.length (ciphertext value))
+      . showString " bytes>, sharedSecret = <redacted>}"
+
+-- | Native operation associated with a failure.
+data Operation
+  = KeyGeneration
+  | Signing
+  | Verification
+  | Encapsulation
+  | Decapsulation
+  deriving (Eq, Show)
+
+-- | Errors reported before or during a native operation.
+data KpqCError
+  = InvalidSize !String !Int !Int
+  | ContextTooLong !Int
+  | NativeFailure !Operation !Int
+  | UnexpectedOutputSize !Operation !Int !Int
+  deriving (Eq, Show)
+
+-- | Algorithms that can generate a key pair.
+class Algorithm algorithm where
+  algorithmId :: algorithm -> String
+  generateKeyPair :: algorithm -> IO (Either KpqCError KeyPair)
+
+instance Algorithm SignatureAlgorithm where
+  algorithmId algorithm = case algorithm of
+    AIMer128f -> "aimer-128f"
+    AIMer128s -> "aimer-128s"
+    AIMer192f -> "aimer-192f"
+    AIMer192s -> "aimer-192s"
+    AIMer256f -> "aimer-256f"
+    AIMer256s -> "aimer-256s"
+    HAETAE2 -> "haetae-mode2"
+    HAETAE3 -> "haetae-mode3"
+    HAETAE5 -> "haetae-mode5"
+
+  generateKeyPair algorithm =
+    let sizes = signatureSizes algorithm
+        (keyPairFunction, _, _) = signatureBackend algorithm
+     in runKeyPair
+          (signaturePublicKeyBytes sizes)
+          (signatureSecretKeyBytes sizes)
+          keyPairFunction
+
+instance Algorithm KEMAlgorithm where
+  algorithmId algorithm = case algorithm of
+    NTRUPlus768 -> "NTRU+768"
+    NTRUPlus864 -> "NTRU+864"
+    NTRUPlus1152 -> "NTRU+1152"
+    SMAUGT128 -> "SMAUG-T128"
+    SMAUGT192 -> "SMAUG-T192"
+    SMAUGT256 -> "SMAUG-T256"
+    TiMER -> "TiMER"
+
+  generateKeyPair algorithm =
+    let sizes = kemSizes algorithm
+        (keyPairFunction, _, _) = kemBackend algorithm
+     in runKeyPair
+          (kemPublicKeyBytes sizes)
+          (kemSecretKeyBytes sizes)
+          keyPairFunction
+
+-- | Every supported signature parameter set.
+signatureAlgorithms :: [SignatureAlgorithm]
+signatureAlgorithms = [minBound .. maxBound]
+
+-- | Every supported KEM parameter set.
+kemAlgorithms :: [KEMAlgorithm]
+kemAlgorithms = [minBound .. maxBound]
+
+-- | Return the fixed byte lengths for a signature parameter set.
+signatureSizes :: SignatureAlgorithm -> SignatureSizes
+signatureSizes algorithm = case algorithm of
+  AIMer128f -> SignatureSizes 32 48 6944
+  AIMer128s -> SignatureSizes 32 48 4704
+  AIMer192f -> SignatureSizes 48 72 15408
+  AIMer192s -> SignatureSizes 48 72 10320
+  AIMer256f -> SignatureSizes 64 96 31360
+  AIMer256s -> SignatureSizes 64 96 20224
+  HAETAE2 -> SignatureSizes 992 1408 1474
+  HAETAE3 -> SignatureSizes 1472 2112 2349
+  HAETAE5 -> SignatureSizes 2080 2752 2948
+
+-- | Return the fixed byte lengths for a KEM parameter set.
+kemSizes :: KEMAlgorithm -> KEMSizes
+kemSizes algorithm = case algorithm of
+  NTRUPlus768 -> KEMSizes 1152 2336 1152 32
+  NTRUPlus864 -> KEMSizes 1296 2624 1296 32
+  NTRUPlus1152 -> KEMSizes 1728 3488 1728 32
+  SMAUGT128 -> KEMSizes 672 832 672 32
+  SMAUGT192 -> KEMSizes 1088 1312 992 32
+  SMAUGT256 -> KEMSizes 1440 1728 1376 32
+  TiMER -> KEMSizes 672 832 608 32
+
+-- | Sign a message using an empty domain-separation context.
+sign :: BA.ByteArrayAccess secret => SignatureAlgorithm -> ByteString -> secret -> IO (Either KpqCError ByteString)
+sign algorithm message secret = signWithContext algorithm message secret BS.empty
+
+-- | Sign a message with a domain-separation context of at most 255 bytes.
+signWithContext :: BA.ByteArrayAccess secret => SignatureAlgorithm -> ByteString -> secret -> ByteString -> IO (Either KpqCError ByteString)
+signWithContext algorithm message secret context =
+  let sizes = signatureSizes algorithm
+      expectedSecret = signatureSecretKeyBytes sizes
+      expectedSignature = signatureBytes sizes
+      (_, signFunction, _) = signatureBackend algorithm
+   in case validateSize "secret key" expectedSecret secret of
+        Left failure -> pure (Left failure)
+        Right () -> case validateContext context of
+          Left failure -> pure (Left failure)
+          Right () ->
+            runSizedOutput Signing expectedSignature $ \signaturePointer ->
+              alloca $ \lengthPointer -> do
+                poke lengthPointer 0
+                status <-
+                  withBytes message $ \messagePointer messageLength ->
+                    withBytes context $ \contextPointer contextLength ->
+                      withBytes secret $ \secretPointer _ ->
+                        signFunction
+                          signaturePointer
+                          lengthPointer
+                          messagePointer
+                          messageLength
+                          contextPointer
+                          contextLength
+                          secretPointer
+                actualLength <- fromIntegral <$> peek lengthPointer
+                pure (status, actualLength)
+
+-- | Verify a signature using an empty domain-separation context.
+verify :: SignatureAlgorithm -> ByteString -> ByteString -> ByteString -> IO (Either KpqCError Bool)
+verify algorithm message signature public =
+  verifyWithContext algorithm message signature public BS.empty
+
+-- | Verify a signature with the domain-separation context used when signing.
+verifyWithContext :: SignatureAlgorithm -> ByteString -> ByteString -> ByteString -> ByteString -> IO (Either KpqCError Bool)
+verifyWithContext algorithm message signature public context =
+  let sizes = signatureSizes algorithm
+      expectedPublic = signaturePublicKeyBytes sizes
+      expectedSignature = signatureBytes sizes
+      (_, _, verifyFunction) = signatureBackend algorithm
+   in case validateSize "public key" expectedPublic public of
+        Left failure -> pure (Left failure)
+        Right () -> case validateContext context of
+          Left failure -> pure (Left failure)
+          Right ()
+            | BS.length signature /= expectedSignature -> pure (Right False)
+            | otherwise -> do
+                status <-
+                  withBytes signature $ \signaturePointer signatureLength ->
+                    withBytes message $ \messagePointer messageLength ->
+                      withBytes context $ \contextPointer contextLength ->
+                        withBytes public $ \publicPointer _ ->
+                          verifyFunction
+                            signaturePointer
+                            signatureLength
+                            messagePointer
+                            messageLength
+                            contextPointer
+                            contextLength
+                            publicPointer
+                pure $ case status of
+                  0 -> Right True
+                  -1 -> Right False
+                  _ -> Left (NativeFailure Verification (fromIntegral status))
+
+-- | Encapsulate a new shared secret to a public key.
+encapsulate :: KEMAlgorithm -> ByteString -> IO (Either KpqCError EncapsulatedSecret)
+encapsulate algorithm public =
+  let sizes = kemSizes algorithm
+      expectedPublic = kemPublicKeyBytes sizes
+      expectedCiphertext = ciphertextBytes sizes
+      expectedSecret = sharedSecretBytes sizes
+      (_, encapsulateFunction, _) = kemBackend algorithm
+   in case validateSize "public key" expectedPublic public of
+        Left failure -> pure (Left failure)
+        Right () ->
+          runTwoOutputs Encapsulation expectedCiphertext expectedSecret EncapsulatedSecret $ \ciphertextPointer sharedSecretPointer ->
+            withBytes public $ \publicPointer _ ->
+              encapsulateFunction ciphertextPointer sharedSecretPointer publicPointer
+
+-- | Decapsulate a shared secret from a ciphertext.
+decapsulate :: BA.ByteArrayAccess secret => KEMAlgorithm -> ByteString -> secret -> IO (Either KpqCError SecretBytes)
+decapsulate algorithm encrypted secret =
+  let sizes = kemSizes algorithm
+      expectedCiphertext = ciphertextBytes sizes
+      expectedSecretKey = kemSecretKeyBytes sizes
+      expectedSharedSecret = sharedSecretBytes sizes
+      (_, _, decapsulateFunction) = kemBackend algorithm
+   in case validateSize "ciphertext" expectedCiphertext encrypted of
+        Left failure -> pure (Left failure)
+        Right () -> case validateSize "secret key" expectedSecretKey secret of
+          Left failure -> pure (Left failure)
+          Right () ->
+            runSecretOutput Decapsulation expectedSharedSecret $ \sharedSecretPointer ->
+              withBytes encrypted $ \ciphertextPointer _ ->
+                withBytes secret $ \secretPointer _ ->
+                  decapsulateFunction sharedSecretPointer ciphertextPointer secretPointer
+
+type KeyPairFunction = Ptr Word8 -> Ptr Word8 -> IO CInt
+
+type SignFunction = Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+type VerifyFunction = Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt
+
+type EncapsulateFunction = Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+type DecapsulateFunction = Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt
+
+signatureBackend :: SignatureAlgorithm -> (KeyPairFunction, SignFunction, VerifyFunction)
+signatureBackend algorithm = case algorithm of
+  AIMer128f -> (AIMer.aimer128fKeyPair, AIMer.aimer128fSign, AIMer.aimer128fVerify)
+  AIMer128s -> (AIMer.aimer128sKeyPair, AIMer.aimer128sSign, AIMer.aimer128sVerify)
+  AIMer192f -> (AIMer.aimer192fKeyPair, AIMer.aimer192fSign, AIMer.aimer192fVerify)
+  AIMer192s -> (AIMer.aimer192sKeyPair, AIMer.aimer192sSign, AIMer.aimer192sVerify)
+  AIMer256f -> (AIMer.aimer256fKeyPair, AIMer.aimer256fSign, AIMer.aimer256fVerify)
+  AIMer256s -> (AIMer.aimer256sKeyPair, AIMer.aimer256sSign, AIMer.aimer256sVerify)
+  HAETAE2 -> (HAETAE.haetae2KeyPair, HAETAE.haetae2Sign, HAETAE.haetae2Verify)
+  HAETAE3 -> (HAETAE.haetae3KeyPair, HAETAE.haetae3Sign, HAETAE.haetae3Verify)
+  HAETAE5 -> (HAETAE.haetae5KeyPair, HAETAE.haetae5Sign, HAETAE.haetae5Verify)
+
+kemBackend :: KEMAlgorithm -> (KeyPairFunction, EncapsulateFunction, DecapsulateFunction)
+kemBackend algorithm = case algorithm of
+  NTRUPlus768 -> (NTRUPlus.ntruplus768KeyPair, NTRUPlus.ntruplus768Encapsulate, NTRUPlus.ntruplus768Decapsulate)
+  NTRUPlus864 -> (NTRUPlus.ntruplus864KeyPair, NTRUPlus.ntruplus864Encapsulate, NTRUPlus.ntruplus864Decapsulate)
+  NTRUPlus1152 -> (NTRUPlus.ntruplus1152KeyPair, NTRUPlus.ntruplus1152Encapsulate, NTRUPlus.ntruplus1152Decapsulate)
+  SMAUGT128 -> (SMAUGT.smaugt128KeyPair, SMAUGT.smaugt128Encapsulate, SMAUGT.smaugt128Decapsulate)
+  SMAUGT192 -> (SMAUGT.smaugt192KeyPair, SMAUGT.smaugt192Encapsulate, SMAUGT.smaugt192Decapsulate)
+  SMAUGT256 -> (SMAUGT.smaugt256KeyPair, SMAUGT.smaugt256Encapsulate, SMAUGT.smaugt256Decapsulate)
+  TiMER -> (SMAUGT.timerKeyPair, SMAUGT.timerEncapsulate, SMAUGT.timerDecapsulate)
+
+runKeyPair :: Int -> Int -> KeyPairFunction -> IO (Either KpqCError KeyPair)
+runKeyPair publicLength secretLength keyPairFunction =
+  runTwoOutputs KeyGeneration publicLength secretLength KeyPair keyPairFunction
+
+runSecretOutput :: Operation -> Int -> (Ptr Word8 -> IO CInt) -> IO (Either KpqCError SecretBytes)
+runSecretOutput operation size action = do
+  (status, secret) <-
+    BA.allocRet size $ \pointer -> do
+      fillBytes pointer 0 size
+      result <- action (castPtr pointer)
+      when (result /= 0) (fillBytes pointer 0 size)
+      pure result
+  if status == 0
+    then pure (Right secret)
+    else pure (Left (NativeFailure operation (fromIntegral status)))
+
+runSizedOutput :: Operation -> Int -> (Ptr Word8 -> IO (CInt, Int)) -> IO (Either KpqCError ByteString)
+runSizedOutput operation expected action =
+  allocaArray expected $ \pointer ->
+    ( do
+        fillBytes pointer 0 expected
+        (status, actual) <- action pointer
+        if status /= 0
+          then pure (Left (NativeFailure operation (fromIntegral status)))
+          else
+            if actual /= expected
+              then pure (Left (UnexpectedOutputSize operation expected actual))
+              else Right <$> BS.packCStringLen (castPtr pointer, expected)
+    )
+      `finally` fillBytes pointer 0 expected
+
+runTwoOutputs :: Operation -> Int -> Int -> (ByteString -> SecretBytes -> value) -> (Ptr Word8 -> Ptr Word8 -> IO CInt) -> IO (Either KpqCError value)
+runTwoOutputs operation firstSize secondSize constructor action =
+  allocaArray firstSize $ \firstPointer ->
+    ( do
+        fillBytes firstPointer 0 firstSize
+        (status, second) <-
+          BA.allocRet secondSize $ \secondPointer -> do
+            fillBytes secondPointer 0 secondSize
+            result <- action firstPointer (castPtr secondPointer)
+            when (result /= 0) (fillBytes secondPointer 0 secondSize)
+            pure result
+        if status == 0
+          then do
+            first <- BS.packCStringLen (castPtr firstPointer, firstSize)
+            pure (Right (constructor first second))
+          else pure (Left (NativeFailure operation (fromIntegral status)))
+    )
+      `finally` fillBytes firstPointer 0 firstSize
+
+withBytes :: BA.ByteArrayAccess bytes => bytes -> (Ptr Word8 -> CSize -> IO value) -> IO value
+withBytes bytes action =
+  BA.withByteArray bytes $ \pointer ->
+    action (castPtr pointer) (fromIntegral (BA.length bytes))
+
+validateSize :: BA.ByteArrayAccess bytes => String -> Int -> bytes -> Either KpqCError ()
+validateSize label expected value
+  | actual == expected = Right ()
+  | otherwise = Left (InvalidSize label expected actual)
+  where
+    actual = BA.length value
+
+validateContext :: ByteString -> Either KpqCError ()
+validateContext context
+  | size <= 255 = Right ()
+  | otherwise = Left (ContextTooLong size)
+  where
+    size = BS.length context
diff --git a/test/KAT.hs b/test/KAT.hs
new file mode 100644
--- /dev/null
+++ b/test/KAT.hs
@@ -0,0 +1,345 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Main (main) where
+
+import Control.Exception (finally)
+import Control.Monad (forM_)
+import Crypto.KpqC
+import qualified Crypto.KpqC.Internal.Native.AIMer as AIMer
+import qualified Crypto.KpqC.Internal.Native.HAETAE as HAETAE
+import qualified Crypto.KpqC.Internal.Native.NTRUPlus as NTRUPlus
+import qualified Crypto.KpqC.Internal.Native.SMAUGT as SMAUGT
+import Data.Bits (shiftR, xor, (.&.))
+import qualified Data.ByteArray as BA
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Unsafe as BSU
+import Data.Char (digitToInt, isHexDigit, isSpace)
+import qualified Data.Map.Strict as Map
+import Data.Word (Word8)
+import Foreign.C.Types (CInt, CSize)
+import Foreign.Marshal.Array (allocaArray)
+import Foreign.Marshal.Utils (fillBytes)
+import Foreign.Ptr (Ptr, castPtr)
+import System.Environment (lookupEnv)
+import System.Exit (exitFailure)
+import System.FilePath ((</>))
+import System.IO.Unsafe (unsafePerformIO)
+
+foreign import ccall unsafe "kpqc_test_aes256_ecb" aes256EcbNative :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO ()
+
+data EntropyBackend = EntropyBackend
+  { setEntropyNative :: Ptr Word8 -> CSize -> IO CInt,
+    entropyRemainingNative :: IO CSize,
+    clearEntropyNative :: IO ()
+  }
+
+data DRBG = DRBG
+  { drbgKey :: !ByteString,
+    drbgCounter :: !ByteString
+  }
+
+type Vector = Map.Map String String
+
+main :: IO ()
+main = do
+  root <- maybe "../kpqc-test-vectors" id <$> lookupEnv "KPQC_TEST_VECTORS"
+  signatureCount <- sum <$> mapM (checkSignatureVectors root) signatureConfigurations
+  kemCount <- sum <$> mapM (checkKEMVectors root) kemConfigurations
+  assertEqual "known-answer vector count" 1600 (signatureCount + kemCount)
+  checkEntropyExhaustion
+  putStrLn "All 1,600 KpqC known-answer vectors passed."
+
+signatureConfigurations :: [(SignatureAlgorithm, FilePath, Bool, Int)]
+signatureConfigurations =
+  [ (AIMer128f, "aimer" </> "128f", False, 16),
+    (AIMer128s, "aimer" </> "128s", False, 16),
+    (AIMer192f, "aimer" </> "192f", False, 24),
+    (AIMer192s, "aimer" </> "192s", False, 24),
+    (AIMer256f, "aimer" </> "256f", False, 32),
+    (AIMer256s, "aimer" </> "256s", False, 32),
+    (HAETAE2, "haetae" </> "mode2", True, 32),
+    (HAETAE3, "haetae" </> "mode3", True, 32),
+    (HAETAE5, "haetae" </> "mode5", True, 32)
+  ]
+
+kemConfigurations :: [(KEMAlgorithm, FilePath, Int, Int)]
+kemConfigurations =
+  [ (NTRUPlus768, "ntruplus" </> "768", 0, 96),
+    (NTRUPlus864, "ntruplus" </> "864", 0, 108),
+    (NTRUPlus1152, "ntruplus" </> "1152", 0, 144),
+    (SMAUGT128, "smaugt" </> "mode1", 2, 32),
+    (SMAUGT192, "smaugt" </> "mode3", 2, 32),
+    (SMAUGT256, "smaugt" </> "mode5", 2, 32),
+    (TiMER, "smaugt" </> "modet", 2, 16)
+  ]
+
+checkSignatureVectors :: FilePath -> (SignatureAlgorithm, FilePath, Bool, Int) -> IO Int
+checkSignatureVectors root (algorithm, relativePath, isHAETAE, seedBytes) = do
+  vectors <- readVectors (root </> relativePath </> "kat.rsp")
+  assertEqual (algorithmId algorithm ++ " vector count") 100 (length vectors)
+  forM_ vectors $ \vector -> do
+    let initial = newDRBG (field vector "seed")
+    (keys, afterKeyPair) <-
+      if isHAETAE
+        then do
+          let (entropy, next) = generate seedBytes initial
+          result <- withExactEntropy (signatureEntropy algorithm) entropy (generateKeyPair algorithm)
+          value <- expectRight result
+          pure (value, next)
+        else do
+          (result, next) <- withRepeatedEntropy (signatureEntropy algorithm) seedBytes initial (generateKeyPair algorithm)
+          value <- expectRight result
+          pure (value, next)
+    assertEqual (label vector "public key") (field vector "pk") (publicKey keys)
+    assertEqual (label vector "secret key") (field vector "sk") (secretByteString (secretKey keys))
+    let message = field vector "msg"
+        expectedSignature =
+          case Map.lookup "sig" vector of
+            Just encoded -> decodeHex encoded
+            Nothing -> BS.drop (BS.length message) (field vector "sm")
+        (signingEntropy, afterSigningEntropy) = generate seedBytes afterKeyPair
+        (context, _) =
+          if isHAETAE
+            then
+              let (lengthByte, afterLength) = generate 1 afterSigningEntropy
+                  contextLength = fromIntegral (BS.head lengthByte)
+               in generate contextLength afterLength
+            else (BS.empty, afterSigningEntropy)
+    signature <-
+      withExactEntropy
+        (signatureEntropy algorithm)
+        signingEntropy
+        (signWithContext algorithm message (secretKey keys) context)
+        >>= expectRight
+    assertEqual (label vector "signature") expectedSignature signature
+    verifyWithContext algorithm message expectedSignature (publicKey keys) context
+      >>= expectRight
+      >>= assertTrue (label vector "verification")
+  pure (length vectors)
+
+checkKEMVectors :: FilePath -> (KEMAlgorithm, FilePath, Int, Int) -> IO Int
+checkKEMVectors root (algorithm, relativePath, keyCalls, encapsulationBytes) = do
+  vectors <- readVectors (root </> relativePath </> "kat.rsp")
+  assertEqual (algorithmId algorithm ++ " vector count") 100 (length vectors)
+  forM_ vectors $ \vector -> do
+    let initial = newDRBG (field vector "seed")
+    (keys, afterKeyPair) <-
+      if keyCalls == 0
+        then do
+          (result, next) <- withRepeatedEntropy (kemEntropy algorithm) 32 initial (generateKeyPair algorithm)
+          value <- expectRight result
+          pure (value, next)
+        else do
+          let (chunks, next) = generateMany keyCalls 32 initial
+          result <- withExactEntropy (kemEntropy algorithm) (BS.concat chunks) (generateKeyPair algorithm)
+          value <- expectRight result
+          pure (value, next)
+    assertEqual (label vector "public key") (field vector "pk") (publicKey keys)
+    assertEqual (label vector "secret key") (field vector "sk") (secretByteString (secretKey keys))
+    let (entropy, _) = generate encapsulationBytes afterKeyPair
+    outbound <-
+      withExactEntropy
+        (kemEntropy algorithm)
+        entropy
+        (encapsulate algorithm (publicKey keys))
+        >>= expectRight
+    assertEqual (label vector "ciphertext") (field vector "ct") (ciphertext outbound)
+    assertEqual (label vector "shared secret") (field vector "ss") (secretByteString (sharedSecret outbound))
+    decapsulate algorithm (ciphertext outbound) (secretKey keys)
+      >>= expectRight
+      >>= assertEqual (label vector "decapsulation") (BA.convert (field vector "ss") :: SecretBytes)
+  pure (length vectors)
+
+checkEntropyExhaustion :: IO ()
+checkEntropyExhaustion = do
+  forM_ signatureAlgorithms $ \algorithm ->
+    expectEntropyExhaustion (algorithmId algorithm) (signatureEntropy algorithm) (generateKeyPair algorithm)
+  forM_ kemAlgorithms $ \algorithm ->
+    expectEntropyExhaustion (algorithmId algorithm) (kemEntropy algorithm) (generateKeyPair algorithm)
+
+expectEntropyExhaustion :: String -> EntropyBackend -> IO (Either KpqCError value) -> IO ()
+expectEntropyExhaustion name backend operation = do
+  setEntropy backend BS.empty
+  result <- operation `finally` clearEntropyNative backend
+  case result of
+    Left (NativeFailure KeyGeneration _) -> pure ()
+    Left failure -> failTest (name ++ " entropy exhaustion returned " ++ show failure)
+    Right _ -> failTest (name ++ " accepted an exhausted entropy source")
+
+secretByteString :: SecretBytes -> ByteString
+secretByteString = BA.convert
+
+withExactEntropy :: EntropyBackend -> ByteString -> IO value -> IO value
+withExactEntropy backend entropy operation = do
+  setEntropy backend entropy
+  ( do
+      result <- operation
+      remaining <- (fromIntegral <$> entropyRemainingNative backend) :: IO Int
+      assertEqual "unused deterministic entropy" 0 remaining
+      pure result
+    )
+    `finally` clearEntropyNative backend
+
+withRepeatedEntropy :: EntropyBackend -> Int -> DRBG -> IO value -> IO (value, DRBG)
+withRepeatedEntropy backend callBytes state operation = do
+  let (chunks, _) = generateMany 128 callBytes state
+      entropy = BS.concat chunks
+  setEntropy backend entropy
+  ( do
+      result <- operation
+      remaining <- fromIntegral <$> entropyRemainingNative backend
+      let consumed = BS.length entropy - remaining
+      assertTrue "deterministic entropy was consumed" (consumed > 0)
+      assertEqual "deterministic entropy chunk alignment" 0 (consumed `mod` callBytes)
+      let (_, next) = generateMany (consumed `div` callBytes) callBytes state
+      pure (result, next)
+    )
+    `finally` clearEntropyNative backend
+
+setEntropy :: EntropyBackend -> ByteString -> IO ()
+setEntropy backend entropy = do
+  status <-
+    BSU.unsafeUseAsCStringLen entropy $ \(pointer, size) ->
+      setEntropyNative backend (castPtr pointer) (fromIntegral size)
+  assertEqual "set deterministic entropy" 0 status
+
+signatureEntropy :: SignatureAlgorithm -> EntropyBackend
+signatureEntropy algorithm = case algorithm of
+  AIMer128f -> EntropyBackend AIMer.aimer128fSetTestEntropy AIMer.aimer128fTestEntropyRemaining AIMer.aimer128fClearTestEntropy
+  AIMer128s -> EntropyBackend AIMer.aimer128sSetTestEntropy AIMer.aimer128sTestEntropyRemaining AIMer.aimer128sClearTestEntropy
+  AIMer192f -> EntropyBackend AIMer.aimer192fSetTestEntropy AIMer.aimer192fTestEntropyRemaining AIMer.aimer192fClearTestEntropy
+  AIMer192s -> EntropyBackend AIMer.aimer192sSetTestEntropy AIMer.aimer192sTestEntropyRemaining AIMer.aimer192sClearTestEntropy
+  AIMer256f -> EntropyBackend AIMer.aimer256fSetTestEntropy AIMer.aimer256fTestEntropyRemaining AIMer.aimer256fClearTestEntropy
+  AIMer256s -> EntropyBackend AIMer.aimer256sSetTestEntropy AIMer.aimer256sTestEntropyRemaining AIMer.aimer256sClearTestEntropy
+  HAETAE2 -> EntropyBackend HAETAE.haetae2SetTestEntropy HAETAE.haetae2TestEntropyRemaining HAETAE.haetae2ClearTestEntropy
+  HAETAE3 -> EntropyBackend HAETAE.haetae3SetTestEntropy HAETAE.haetae3TestEntropyRemaining HAETAE.haetae3ClearTestEntropy
+  HAETAE5 -> EntropyBackend HAETAE.haetae5SetTestEntropy HAETAE.haetae5TestEntropyRemaining HAETAE.haetae5ClearTestEntropy
+
+kemEntropy :: KEMAlgorithm -> EntropyBackend
+kemEntropy algorithm = case algorithm of
+  NTRUPlus768 -> EntropyBackend NTRUPlus.ntruplus768SetTestEntropy NTRUPlus.ntruplus768TestEntropyRemaining NTRUPlus.ntruplus768ClearTestEntropy
+  NTRUPlus864 -> EntropyBackend NTRUPlus.ntruplus864SetTestEntropy NTRUPlus.ntruplus864TestEntropyRemaining NTRUPlus.ntruplus864ClearTestEntropy
+  NTRUPlus1152 -> EntropyBackend NTRUPlus.ntruplus1152SetTestEntropy NTRUPlus.ntruplus1152TestEntropyRemaining NTRUPlus.ntruplus1152ClearTestEntropy
+  SMAUGT128 -> EntropyBackend SMAUGT.smaugt128SetTestEntropy SMAUGT.smaugt128TestEntropyRemaining SMAUGT.smaugt128ClearTestEntropy
+  SMAUGT192 -> EntropyBackend SMAUGT.smaugt192SetTestEntropy SMAUGT.smaugt192TestEntropyRemaining SMAUGT.smaugt192ClearTestEntropy
+  SMAUGT256 -> EntropyBackend SMAUGT.smaugt256SetTestEntropy SMAUGT.smaugt256TestEntropyRemaining SMAUGT.smaugt256ClearTestEntropy
+  TiMER -> EntropyBackend SMAUGT.timerSetTestEntropy SMAUGT.timerTestEntropyRemaining SMAUGT.timerClearTestEntropy
+
+newDRBG :: ByteString -> DRBG
+newDRBG entropy
+  | BS.length entropy == 48 = updateDRBG (DRBG (BS.replicate 32 0) (BS.replicate 16 0)) entropy
+  | otherwise = error "CTR-DRBG seed must be 48 bytes"
+
+generate :: Int -> DRBG -> (ByteString, DRBG)
+generate size state =
+  let blocks = (size + 15) `div` 16
+      (output, advanced) = generateBlocks blocks state
+   in (BS.take size (BS.concat output), updateDRBG advanced BS.empty)
+
+generateMany :: Int -> Int -> DRBG -> ([ByteString], DRBG)
+generateMany count size = run count []
+  where
+    run 0 chunks state = (reverse chunks, state)
+    run remaining chunks state =
+      let (chunk, next) = generate size state
+       in run (remaining - 1) (chunk : chunks) next
+
+updateDRBG :: DRBG -> ByteString -> DRBG
+updateDRBG state provided =
+  let (blocks, _) = generateBlocks 3 state
+      material = xorBytes (BS.concat blocks) (provided <> BS.replicate (48 - BS.length provided) 0)
+   in DRBG (BS.take 32 material) (BS.drop 32 material)
+
+generateBlocks :: Int -> DRBG -> ([ByteString], DRBG)
+generateBlocks count = run count []
+  where
+    run 0 blocks state = (reverse blocks, state)
+    run remaining blocks state =
+      let counter = incrementCounter (drbgCounter state)
+          block = aes256 (drbgKey state) counter
+          next = state {drbgCounter = counter}
+       in run (remaining - 1) (block : blocks) next
+
+aes256 :: ByteString -> ByteString -> ByteString
+aes256 key block = unsafePerformIO $
+  allocaArray 16 $ \output ->
+    ( do
+        fillBytes output 0 16
+        BSU.unsafeUseAsCStringLen key $ \(keyPointer, _) ->
+          BSU.unsafeUseAsCStringLen block $ \(blockPointer, _) ->
+            aes256EcbNative output (castPtr keyPointer) (castPtr blockPointer)
+        BS.packCStringLen (castPtr output, 16)
+    )
+      `finally` fillBytes output 0 16
+{-# NOINLINE aes256 #-}
+
+incrementCounter :: ByteString -> ByteString
+incrementCounter counter = integerBytes 16 ((bytesInteger counter + 1) `mod` (2 ^ (128 :: Int)))
+
+bytesInteger :: ByteString -> Integer
+bytesInteger = BS.foldl' (\value byte -> value * 256 + fromIntegral byte) 0
+
+integerBytes :: Int -> Integer -> ByteString
+integerBytes size value =
+  BS.pack [fromIntegral ((value `shiftR` (position * 8)) .&. 255) | position <- reverse [0 .. size - 1]]
+
+xorBytes :: ByteString -> ByteString -> ByteString
+xorBytes left right = BS.pack (BS.zipWith xor left right)
+
+readVectors :: FilePath -> IO [Vector]
+readVectors path = do
+  contents <- readFile path
+  pure (finish (foldl parseLine ([], Map.empty) (lines contents)))
+  where
+    finish (vectors, current)
+      | Map.null current = reverse vectors
+      | otherwise = reverse (current : vectors)
+    parseLine (vectors, current) line =
+      case splitField line of
+        Just ("count", value)
+          | not (Map.null current) -> (current : vectors, Map.singleton "count" value)
+        Just (name, value) -> (vectors, Map.insert name value current)
+        Nothing -> (vectors, current)
+
+splitField :: String -> Maybe (String, String)
+splitField line = case break (== '=') line of
+  (name, '=' : value) -> Just (trim name, trim value)
+  _ -> Nothing
+
+trim :: String -> String
+trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
+
+field :: Vector -> String -> ByteString
+field vector name = maybe (error ("missing vector field: " ++ name)) decodeHex (Map.lookup name vector)
+
+decodeHex :: String -> ByteString
+decodeHex value
+  | odd (length value) || any (not . isHexDigit) value = error "invalid hexadecimal vector field"
+  | otherwise = BS.pack (decode value)
+  where
+    decode [] = []
+    decode (high : low : rest) = fromIntegral (digitToInt high * 16 + digitToInt low) : decode rest
+    decode _ = error "unreachable"
+
+label :: Vector -> String -> String
+label vector fieldName = Map.findWithDefault "?" "count" vector ++ " " ++ fieldName
+
+expectRight :: Either KpqCError value -> IO value
+expectRight result = case result of
+  Right value -> pure value
+  Left failure -> failTest ("unexpected failure: " ++ show failure)
+
+assertTrue :: String -> Bool -> IO ()
+assertTrue message condition = if condition then pure () else failTest message
+
+assertEqual :: (Eq value, Show value) => String -> value -> value -> IO ()
+assertEqual message expected actual =
+  if expected == actual
+    then pure ()
+    else failTest (message ++ ": expected " ++ show expected ++ ", received " ++ show actual)
+
+failTest :: String -> IO value
+failTest message = do
+  putStrLn ("FAIL: " ++ message)
+  exitFailure
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,110 @@
+module Main (main) where
+
+import Control.Monad (forM_, unless)
+import Crypto.KpqC
+import Data.Bits (xor)
+import qualified Data.ByteArray as BA
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BSC
+import System.Exit (exitFailure)
+
+main :: IO ()
+main = do
+  assertEqual "signature algorithm count" 9 (length signatureAlgorithms)
+  assertEqual "KEM algorithm count" 7 (length kemAlgorithms)
+  forM_ signatureAlgorithms checkSignature
+  forM_ kemAlgorithms checkKEM
+  checkValidation
+  putStrLn "All KpqC tests passed."
+
+checkSignature :: SignatureAlgorithm -> IO ()
+checkSignature algorithm = do
+  keys <- expectRight (generateKeyPair algorithm)
+  let sizes = signatureSizes algorithm
+      message = BSC.pack "KpqC Haskell"
+      context = BSC.pack "release"
+  assertEqual "signature public-key size" (signaturePublicKeyBytes sizes) (BS.length (publicKey keys))
+  assertEqual "signature secret-key size" (signatureSecretKeyBytes sizes) (BA.length (secretKey keys))
+  assertEqual
+    "key-pair display redacts the secret key"
+    ("KeyPair {publicKey = <" ++ show (signaturePublicKeyBytes sizes) ++ " bytes>, secretKey = <redacted>}")
+    (show keys)
+  signature <- expectRight (signWithContext algorithm message (secretKey keys) context)
+  assertEqual "signature size" (signatureBytes sizes) (BS.length signature)
+  expectRight (verifyWithContext algorithm message signature (publicKey keys) context) >>= assertTrue "valid signature"
+  expectRight (verifyWithContext algorithm message (flipFirst signature) (publicKey keys) context) >>= assertFalse "modified signature"
+  expectRight (verifyWithContext algorithm (BS.snoc message 33) signature (publicKey keys) context) >>= assertFalse "modified message"
+  expectRight (verifyWithContext algorithm message signature (publicKey keys) (BSC.pack "wrong")) >>= assertFalse "wrong context"
+  expectRight (verify algorithm message (BS.singleton 0) (publicKey keys)) >>= assertFalse "wrong signature length"
+
+checkKEM :: KEMAlgorithm -> IO ()
+checkKEM algorithm = do
+  keys <- expectRight (generateKeyPair algorithm)
+  let sizes = kemSizes algorithm
+  assertEqual "KEM public-key size" (kemPublicKeyBytes sizes) (BS.length (publicKey keys))
+  assertEqual "KEM secret-key size" (kemSecretKeyBytes sizes) (BA.length (secretKey keys))
+  outbound <- expectRight (encapsulate algorithm (publicKey keys))
+  assertEqual "ciphertext size" (ciphertextBytes sizes) (BS.length (ciphertext outbound))
+  assertEqual "shared-secret size" (sharedSecretBytes sizes) (BA.length (sharedSecret outbound))
+  assertEqual
+    "encapsulation display redacts the shared secret"
+    ("EncapsulatedSecret {ciphertext = <" ++ show (ciphertextBytes sizes) ++ " bytes>, sharedSecret = <redacted>}")
+    (show outbound)
+  inbound <- expectRight (decapsulate algorithm (ciphertext outbound) (secretKey keys))
+  assertEqual "shared secrets" (sharedSecret outbound) inbound
+  rejection <- decapsulate algorithm (flipFirst (ciphertext outbound)) (secretKey keys)
+  case algorithm of
+    NTRUPlus768 -> assertNativeFailure rejection
+    NTRUPlus864 -> assertNativeFailure rejection
+    NTRUPlus1152 -> assertNativeFailure rejection
+    _ -> do
+      replacement <- expectEitherRight rejection
+      assertEqual "replacement-secret size" (sharedSecretBytes sizes) (BA.length replacement)
+      assertTrue "implicit rejection" (replacement /= sharedSecret outbound)
+
+checkValidation :: IO ()
+checkValidation = do
+  keys <- expectRight (generateKeyPair AIMer128f)
+  invalidSecret <- sign AIMer128f BS.empty (BS.singleton 0)
+  assertEqual "invalid secret key" (Left (InvalidSize "secret key" 48 1)) invalidSecret
+  longContext <- signWithContext AIMer128f BS.empty (secretKey keys) (BS.replicate 256 0)
+  assertEqual "long context" (Left (ContextTooLong 256)) longContext
+  invalidPublic <- verify AIMer128f BS.empty BS.empty (BS.singleton 0)
+  assertEqual "invalid public key" (Left (InvalidSize "public key" 32 1)) invalidPublic
+  invalidCiphertext <- decapsulate NTRUPlus768 (BS.singleton 0) (BS.replicate 2336 0)
+  assertEqual "invalid ciphertext" (Left (InvalidSize "ciphertext" 1152 1)) invalidCiphertext
+
+flipFirst :: ByteString -> ByteString
+flipFirst input = case BS.uncons input of
+  Nothing -> input
+  Just (first, rest) -> BS.cons (first `xor` 1) rest
+
+assertNativeFailure :: Either KpqCError SecretBytes -> IO ()
+assertNativeFailure result = case result of
+  Left (NativeFailure Decapsulation _) -> pure ()
+  _ -> failTest ("expected native decapsulation failure, received " ++ show result)
+
+expectRight :: IO (Either KpqCError value) -> IO value
+expectRight operation = operation >>= expectEitherRight
+
+expectEitherRight :: Either KpqCError value -> IO value
+expectEitherRight result = case result of
+  Right value -> pure value
+  Left failure -> failTest ("unexpected failure: " ++ show failure)
+
+assertTrue :: String -> Bool -> IO ()
+assertTrue label value = unless value (failTest (label ++ ": expected True"))
+
+assertFalse :: String -> Bool -> IO ()
+assertFalse label = assertTrue label . not
+
+assertEqual :: (Eq value, Show value) => String -> value -> value -> IO ()
+assertEqual label expected actual =
+  unless (expected == actual) $
+    failTest (label ++ ": expected " ++ show expected ++ ", received " ++ show actual)
+
+failTest :: String -> IO value
+failTest message = do
+  putStrLn ("FAIL: " ++ message)
+  exitFailure
