]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Implement low-level encryption functions for CGO.
authorNick Mathewson <nickm@torproject.org>
Sun, 20 Apr 2025 14:22:54 +0000 (10:22 -0400)
committerNick Mathewson <nickm@torproject.org>
Wed, 21 May 2025 13:43:51 +0000 (09:43 -0400)
These include a regular LRW2 tweakable block cipher,
a pseudorandom function,
and a UIV+ tweakable wide-block rugged pseudorandom permutation.

Also included are a few test vectors from the reference
 implementation.

src/core/crypto/include.am
src/core/crypto/relay_crypto_cgo.c [new file with mode: 0644]
src/core/crypto/relay_crypto_cgo.h [new file with mode: 0644]
src/test/cgo_vectors.inc [new file with mode: 0644]
src/test/include.am
src/test/test.c
src/test/test.h
src/test/test_crypto_cgo.c [new file with mode: 0644]

index 651e8803d4e87055e2d08739d8bcc2ee34b58d4f..9898220f0d05a8890c68b94c6543102e7ae9da32 100644 (file)
@@ -6,7 +6,8 @@ LIBTOR_APP_A_SOURCES +=                                 \
        src/core/crypto/onion_fast.c            \
        src/core/crypto/onion_ntor.c            \
        src/core/crypto/onion_ntor_v3.c         \
-       src/core/crypto/relay_crypto.c
+       src/core/crypto/relay_crypto.c          \
+        src/core/crypto/relay_crypto_cgo.c
 
 # ADD_C_FILE: INSERT HEADERS HERE.
 noinst_HEADERS +=                                      \
@@ -15,4 +16,5 @@ noinst_HEADERS +=                                     \
        src/core/crypto/onion_fast.h                    \
        src/core/crypto/onion_ntor.h                    \
        src/core/crypto/onion_ntor_v3.h                 \
-       src/core/crypto/relay_crypto.h
+       src/core/crypto/relay_crypto.h                  \
+        src/core/crypto/relay_crypto_cgo.h
diff --git a/src/core/crypto/relay_crypto_cgo.c b/src/core/crypto/relay_crypto_cgo.c
new file mode 100644 (file)
index 0000000..a26a5dd
--- /dev/null
@@ -0,0 +1,335 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2025, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file relay_crypto_cgo.c
+ * \brief Implementation for counter galois onion encryption.
+ **/
+
+#define RELAY_CRYPTO_CGO_PRIVATE
+#define USE_AES_RAW
+
+#include "orconfig.h"
+#include "lib/crypt_ops/aes.h"
+#include "ext/polyval/polyval.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "lib/log/util_bug.h"
+#include "lib/arch/bytes.h"
+#include "ext/polyval/polyval.h"
+#include "core/crypto/relay_crypto_cgo.h"
+
+#if 0
+// XXXX debugging.
+#include "lib/encoding/binascii.h"
+#include <stdio.h>
+#endif
+
+#include <string.h>
+
+
+/** Initialize an instance of the tweakable block cipher,
+ * using an 'aesbits'-bit AES key.
+ *
+ * The total key material used from 'key' will be
+ * (aesbits / 8) + 16.
+ *
+ * This will be initialized for encryption or decryption depending
+ * on the value of 'encrypt'
+ */
+STATIC int
+cgo_et_init(cgo_et_t *et, int aesbits, bool encrypt,
+            const uint8_t *key)
+{
+  size_t aes_key_bytes = aesbits / 8;
+  et->kb = aes_raw_new(key, aesbits, encrypt);
+  if (et->kb == NULL)
+    return -1;
+  polyval_key_init(&et->ku, key + aes_key_bytes);
+  return 0;
+}
+/** Replace the key on an existing, already initialized cgo_et_t.
+ *
+ * Does fewer allocations than a clear+init. */
+STATIC void
+cgo_et_set_key(cgo_et_t *et, int aesbits, bool encrypt,
+               const uint8_t *key)
+{
+  size_t aes_key_bytes = aesbits / 8;
+  aes_raw_set_key(&et->kb, key, aesbits, encrypt);
+  polyval_key_init(&et->ku, key + aes_key_bytes);
+}
+
+/** Helper: Compute polyval(KU, H | CMD | X_R). */
+static inline void
+compute_et_mask(polyval_key_t *pvk, const et_tweak_t tweak, uint8_t *t_out) {
+  // block 0: tweak.h
+  // block 1: one byte of command, first 15 bytes of x_r
+  // block 2...: remainder of x_r, zero-padded.
+  polyval_t pv;
+  uint8_t block1[16];
+  block1[0] = tweak.uiv.cmd;
+  memcpy(block1+1, tweak.x_r, 15);
+  polyval_init_from_key(&pv, pvk);
+  polyval_add_block(&pv, tweak.uiv.h);
+  polyval_add_block(&pv, block1);
+  polyval_add_zpad(&pv, tweak.x_r + 15, ET_TWEAK_LEN_X_R - 15);
+  polyval_get_tag(&pv, t_out);
+}
+/** XOR the 16 byte block from inp into out. */
+static void
+xor_block(uint8_t *out, const uint8_t *inp)
+{
+  for (int i = 0; i < 16; ++i)
+    out[i] ^= inp[i];
+}
+
+/**
+ * Encrypt the 16-byte block in 'block'.
+ */
+STATIC void
+cgo_et_encrypt(cgo_et_t *et, const et_tweak_t tweak,
+               uint8_t *block)
+{
+  uint8_t mask[16];
+  compute_et_mask(&et->ku, tweak, mask);
+  xor_block(block, mask);
+  aes_raw_encrypt(et->kb, block);
+  xor_block(block, mask);
+}
+/**
+ * Decrypt the 16-byte b lock in 'block'
+ */
+STATIC void
+cgo_et_decrypt(cgo_et_t *et, const et_tweak_t tweak,
+               uint8_t *block)
+{
+  uint8_t mask[16];
+  compute_et_mask(&et->ku, tweak, mask);
+  xor_block(block, mask);
+  aes_raw_decrypt(et->kb, block);
+  xor_block(block, mask);
+}
+/**
+ * Release any storage held in 'et'.
+ *
+ * This _doesn't_ wipe 'et'; that's done from a higher-level function.
+ */
+STATIC void
+cgo_et_clear(cgo_et_t *et)
+{
+  aes_raw_free(et->kb);
+}
+
+/**
+ * Initialize a psedorandom function from a given key.
+ * Uses an internal 'aesbits'-bit AES key.
+ *
+ * The total key material used from 'key' will be
+ * (aesbits / 8) + 16.
+ */
+STATIC int
+cgo_prf_init(cgo_prf_t *prf, int aesbits,
+             const uint8_t *key)
+{
+  size_t aes_key_bytes = aesbits / 8;
+  memset(prf,0, sizeof(*prf));
+  prf->k = aes_raw_new(key, aesbits, true);
+  polyval_key_init(&prf->b, key + aes_key_bytes);
+  return 0;
+}
+/** Replace the key on an existing cgo_prf_t.
+ *
+ * Does fewer allocations than a clear+init. */
+STATIC void
+cgo_prf_set_key(cgo_prf_t *prf, int aesbits,
+                const uint8_t *key)
+{
+  size_t aes_key_bytes = aesbits / 8;
+  aes_raw_set_key(&prf->k, key, aesbits, true);
+  polyval_key_init(&prf->b, key + aes_key_bytes);
+}
+/**
+ * Compute the PRF's results on 'input', for position t=0,
+ * XOR it into 'data'.
+ *
+ * 'input' must be PRF_INPUT_LEN bytes long.
+ *
+ * 'data' must be PRF_T0_DATA_LEN bytes long.
+ */
+STATIC void
+cgo_prf_xor_t0(cgo_prf_t *prf, const uint8_t *input,
+               uint8_t *data)
+{
+  uint8_t hash[16];
+  polyval_t pv;
+  polyval_init_from_key(&pv, &prf->b);
+  polyval_add_block(&pv, input);
+  polyval_get_tag(&pv, hash);
+  hash[15] &= 0xC0; // Clear the low six bits.
+
+  aes_raw_counter_xor(prf->k, hash, 0, data, PRF_T0_DATA_LEN);
+}
+/**
+ * Generate 'n' bytes of the PRF's results on 'input', for position t=1,
+ * and store them into 'buf'.
+ *
+ * 'input' must be PRF_INPUT_LEN bytes long.
+ */
+STATIC void
+cgo_prf_gen_t1(cgo_prf_t *prf, const uint8_t *input,
+               uint8_t *buf, size_t n)
+{
+  #define T1_OFFSET 31
+  uint8_t hash[16];
+  polyval_t pv;
+  polyval_init_from_key(&pv, &prf->b);
+  polyval_add_block(&pv, input);
+  polyval_get_tag(&pv, hash);
+  hash[15] &= 0xC0; // Clear the low six bits.
+
+  memset(buf, 0, n);
+  aes_raw_counter_xor(prf->k, hash, T1_OFFSET, buf, n);
+}
+/**
+ * Release any storage held in 'prf'.
+ *
+ * This _doesn't_ wipe 'prf'; that's done from a higher-level function.
+ */
+STATIC void
+cgo_prf_clear(cgo_prf_t *prf)
+{
+  aes_raw_free(prf->k);
+}
+
+/**
+ * Initialize the 'uiv' wide-block cipher, using 'aesbits'-bit
+ * AES keys internally.
+ *
+ * Initializes for encryption or decryption depending on the value of
+ * 'encrypt'.
+ *
+ * The total key material used from 'key' will be
+ * (aesbits / 8) * 2 + 32.
+ */
+STATIC int
+cgo_uiv_init(cgo_uiv_t *uiv, int aesbits, bool encrypt,
+             const uint8_t *key)
+{
+  size_t aes_key_bytes = aesbits / 8;
+  if (cgo_et_init(&uiv->j, aesbits, encrypt, key) < 0)
+    return -1;
+  if (cgo_prf_init(&uiv->s, aesbits, key + aes_key_bytes + POLYVAL_KEY_LEN)<0)
+    return -1;
+#ifdef TOR_UNIT_TESTS
+  /* Testing only: copy the keys so we can test UIV_UPDATE function. */
+  size_t total_key_len = aes_key_bytes * 2 + POLYVAL_KEY_LEN * 2;
+  tor_assert(total_key_len <= sizeof(uiv->uiv_keys_));
+  memset(uiv->uiv_keys_, 0, sizeof(uiv->uiv_keys_));
+  memcpy(uiv->uiv_keys_, key, total_key_len);
+#endif
+  return 0;
+}
+/**
+ * Encrypt 'cell_body', with the provided tweak.
+ *
+ * The cell body must be UIV_BLOCK_LEN bytes long.
+ */
+STATIC void
+cgo_uiv_encrypt(cgo_uiv_t *uiv, const uiv_tweak_t tweak, uint8_t *cell_body)
+{
+  uint8_t *X_L = cell_body;
+  uint8_t *X_R = cell_body + 16;
+
+  const et_tweak_t et_tweak = {
+    .uiv = tweak,
+    .x_r = X_R,
+  };
+  cgo_et_encrypt(&uiv->j, et_tweak, X_L);
+  cgo_prf_xor_t0(&uiv->s, X_L, X_R);
+}
+/**
+ * Decrypt 'cell_body', with the provided tweak.
+ *
+ * The cell body must be UIV_BLOCK_LEN bytes long.
+ */
+STATIC void
+cgo_uiv_decrypt(cgo_uiv_t *uiv, const uiv_tweak_t tweak, uint8_t *cell_body)
+{
+  uint8_t *X_L = cell_body;
+  uint8_t *X_R = cell_body + 16;
+
+  const et_tweak_t et_tweak = {
+    .uiv = tweak,
+    .x_r = X_R,
+  };
+  cgo_prf_xor_t0(&uiv->s, X_L, X_R);
+  cgo_et_decrypt(&uiv->j, et_tweak, X_L);
+}
+/**
+ * Irreversibly ransform the keys of this UIV+, and the provided nonce,
+ * using the nonce as input.
+ *
+ * The nonce must be 16 bytes long.
+ */
+STATIC void
+cgo_uiv_update(cgo_uiv_t *uiv, int aesbits, bool encrypt, uint8_t *nonce)
+{
+  size_t aes_bytes = aesbits / 8;
+  size_t single_key_len = aes_bytes + POLYVAL_KEY_LEN;
+  size_t total_key_len = single_key_len * 2 + 16;
+  // Note: We could store this on the stack, but stack-protector
+  // wouldn't like that.
+  uint8_t *new_keys = tor_malloc(total_key_len);
+
+  cgo_prf_gen_t1(&uiv->s, nonce, new_keys, total_key_len);
+
+  cgo_et_set_key(&uiv->j, aesbits, encrypt, new_keys);
+  cgo_prf_set_key(&uiv->s, aesbits, new_keys + single_key_len);
+
+  memcpy(nonce, new_keys + single_key_len * 2, 16);
+
+#ifdef TOR_UNIT_TESTS
+  /* Testing only: copy the keys so we can test UIV_UPDATE function. */
+  memset(uiv->uiv_keys_, 0, sizeof(uiv->uiv_keys_));
+  memcpy(uiv->uiv_keys_, new_keys, total_key_len);
+#endif
+
+  // This is key material, so we should really discard it.
+  memwipe(new_keys, 0, total_key_len);
+  tor_free(new_keys);
+}
+/**
+ * Release any storage held in 'prf'.
+ *
+ * This _doesn't_ wipe 'prf'; that's done from a higher-level function.
+ */
+STATIC void
+cgo_uiv_clear(cgo_uiv_t *uiv)
+{
+  cgo_et_clear(&uiv->j);
+  cgo_prf_clear(&uiv->s);
+}
+
+// XXXX temporarily suppress unused-function warnings
+void temporary(void);
+void temporary(void)
+{
+  (void)cgo_et_init;
+  (void)cgo_et_encrypt;
+  (void)cgo_et_decrypt;
+  (void)cgo_et_clear;
+
+  (void)cgo_prf_init;
+  (void)cgo_prf_xor_t0;
+  (void)cgo_prf_gen_t1;
+  (void)cgo_prf_clear;
+
+  (void)cgo_uiv_init;
+  (void)cgo_uiv_encrypt;
+  (void)cgo_uiv_decrypt;
+  (void)cgo_uiv_update;
+  (void)cgo_uiv_clear;
+}
diff --git a/src/core/crypto/relay_crypto_cgo.h b/src/core/crypto/relay_crypto_cgo.h
new file mode 100644 (file)
index 0000000..c9966d5
--- /dev/null
@@ -0,0 +1,165 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2025, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file relay_crypto_cgo.h
+ * \brief Header file for relay_crypto_cgo.c.
+ **/
+
+#ifndef TOR_RELAY_CRYPTO_CGO_H
+#define TOR_RELAY_CRYPTO_CGO_H
+
+#include "lib/testsupport/testsupport.h"
+
+#ifdef RELAY_CRYPTO_CGO_PRIVATE
+/* Internal types and definitions for CGO encryption algorithms.
+ *
+ * Where reasonable, the identifiers here are chosen to match those
+ * in the spec (proposal 359), which in turn were chosen to match
+ * those in the paper.
+ */
+
+/**
+ * Tweakable block cipher, following the LRW2 construction,
+ * instantiated with AES.
+ *
+ * Any given instance can be used for encryption _or_ decryption,
+ * not both.
+ */
+typedef struct cgo_et_t {
+  /**
+   * AES block cipher instance
+   */
+  aes_raw_t *kb;
+  /**
+   * Polyval key.
+   */
+  polyval_key_t ku;
+} cgo_et_t;
+/**
+ * Keyed pseudorandom function, based on polyval and AES-CTR.
+ */
+typedef struct cgo_prf_t {
+  /**
+   * AES key: may be 128, 192, or 256 bits.
+   *
+   * Even though we're going to be using this in counter mode,
+   * we don't make an aes_cnt_cipher_t here, since that type
+   * does not support efficient re-use of the key with multiple
+   * IVs.
+   */
+  aes_raw_t *k;
+  /**
+   * Polyval instance.
+   */
+  polyval_key_t b;
+} cgo_prf_t;
+/**
+ * Rugged tweakable pseudorandom permutation, using the UIV+ construction.
+ *
+ * This is, roughly, a wide-block cipher where _encryption_
+ * is non-malleable, but where _decryption_ is malleable.
+ *
+ * UIV+ is the basis of CGO encryption, though it is used in different
+ * ways for each of the relay operations.
+ */
+typedef struct cgo_uiv_t {
+  /**
+   * Tweakable block cipher instance.
+   */
+  cgo_et_t j;
+  /**
+   * PRF instance.
+   */
+  cgo_prf_t s;
+#ifdef TOR_UNIT_TESTS
+  /** Testing only: Copy of keys used to instantiate this UIV.
+   * We use this in tests so that we can confirm the correctness
+   * of cgo_uiv_update().
+   */
+  uint8_t uiv_keys_[32 * 2 + 16 * 2];
+#endif
+} cgo_uiv_t;
+/**
+ * Length of the 'h' component of uiv_tweak_t.
+ */
+#define ET_TWEAK_LEN_H 16
+/**
+ * Length of the 'x_r' component of et_tweak_t.
+ */
+#define ET_TWEAK_LEN_X_R 493
+
+/**
+ * Tweak for the UIV+ wide-block cipher.
+ */
+typedef struct uiv_tweak_t {
+  /** H component of the wide-block cipher.
+   *
+   * This must be ET_TWEAK_LEN_H bytes long.
+   **/
+  const uint8_t *h;
+  /** Additional data component of the wide-block cipher.
+   * This value is sent to the cell command (RELAY or RELAY_EARLY)
+   * for each relay cell.
+   */
+  const uint8_t cmd;
+} uiv_tweak_t;
+/**
+ * Tweak for the ET tweakable block cipher.
+ */
+typedef struct et_tweak_t {
+  /** Components from the UIV+ tweak. */
+  uiv_tweak_t uiv;
+  /**
+   * X_R component of the ET tweak.
+   *
+   * This must be X_R bytes long.
+   */
+  const uint8_t *x_r;
+} et_tweak_t;
+
+
+/** Length of expected input to the PRF. */
+#define PRF_INPUT_LEN 16
+/** Output length for cgo_prf_xor_t0(). */
+#define PRF_T0_DATA_LEN 493
+
+/** Length of block handled by uiv instantiation. */
+#define UIV_BLOCK_LEN 509
+
+STATIC int cgo_et_init(cgo_et_t *et, int aesbits, bool encrypt,
+                       const uint8_t *key);
+STATIC void cgo_et_set_key(cgo_et_t *et, int aesbits, bool encrypt,
+                           const uint8_t *key);
+STATIC void cgo_et_encrypt(cgo_et_t *et, const et_tweak_t tweak,
+                    uint8_t *block);
+STATIC void cgo_et_decrypt(cgo_et_t *et, const et_tweak_t tweak,
+                    uint8_t *block);
+STATIC void cgo_et_clear(cgo_et_t *et);
+
+STATIC int cgo_prf_init(cgo_prf_t *prf, int aesbits,
+                        const uint8_t *key);
+STATIC void cgo_prf_set_key(cgo_prf_t *prf, int aesbits,
+                           const uint8_t *key);
+STATIC void cgo_prf_xor_t0(cgo_prf_t *prf, const uint8_t *input,
+                          uint8_t *data);
+STATIC void cgo_prf_gen_t1(cgo_prf_t *prf, const uint8_t *input,
+                   uint8_t *buf, size_t n);
+STATIC void cgo_prf_clear(cgo_prf_t *prf);
+
+STATIC int cgo_uiv_init(cgo_uiv_t *uiv, int aesbits, bool encrypt,
+                        const uint8_t *key);
+STATIC void cgo_uiv_encrypt(cgo_uiv_t *uiv, const uiv_tweak_t tweak,
+                            uint8_t *cell_body);
+STATIC void cgo_uiv_decrypt(cgo_uiv_t *uiv, const uiv_tweak_t tweak,
+                            uint8_t *cell_body);
+STATIC void cgo_uiv_update(cgo_uiv_t *uiv, int aesbits, bool encrypt,
+                           uint8_t *nonce);
+STATIC void cgo_uiv_clear(cgo_uiv_t *uiv);
+
+#endif
+
+#endif /* !defined(TOR_RELAY_CRYPTO_CGO_H) */
diff --git a/src/test/cgo_vectors.inc b/src/test/cgo_vectors.inc
new file mode 100644 (file)
index 0000000..2654757
--- /dev/null
@@ -0,0 +1,373 @@
+/* CGO test vectors.  Chosen from those generated by
+ * the python reference implementation.
+ */
+
+/* Accept python booleans here. */
+#define True true
+#define False false
+
+static const struct et_testvec {
+  bool encrypt;
+  const char *keys;
+  const char *tweaks;
+  const char *block;
+  const char *expect;
+} ET_TESTVECS[] = {
+// Zeroes, encrypt
+{
+    // Encrypt
+    True,
+    // (KB,KU)
+    "0000000000000000000000000000000000000000000000000000000000000000",
+    // T
+    "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    // M
+    "00000000000000000000000000000000",
+    // OUTPUT
+    "66e94bd4ef8a2c3b884cfa59ca342b2e",
+},
+// Zeroes, decrypt
+{
+    // Encrypt
+    False,
+    // (KB,KU)
+    "0000000000000000000000000000000000000000000000000000000000000000",
+    // T
+    "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    // M
+    "00000000000000000000000000000000",
+    // OUTPUT
+    "140f0f1011b5223d79587717ffd9ec3a",
+},
+// Pseudorandom 1, Encrypt
+{
+    // Encrypt
+    True,
+    // (KB,KU)
+    "39dd87e0b958cec5d2ba04a17fad9f134770f20f14038bdcd751056a7f16041f",
+    // T
+    "fbf69df9bfc3bec7e4a5dd0c9785dd18727dab2b11baf2898b3b775baed777d209812a71e8d5a1f624a4c2c3ccd91064f494f5deb2b7ab362cda53df3e0291cc439052a05cdbc8fe259f7190792b637eeaf0c5ebdf7d02ec6b89beecf131a916f5c6989267e28defaa5937b35f0a1ce1ef91838c408b2d199170f29e76ae21b8b62a733e4de9d281e6935d20d991e3e1801907f6477f9fd40bd4e72de681336e603bb7ec17d512728864b7cebc9bc6bbc0629082830fa3702cb2eff0fb289b7431d4e1b0b6109599c91c4c78540792331e592fe8c0c190ea18275386ec3d85f68996b6891e484ad4b0601008ead6ed60145f8d01b81d1cf31556744b1676f6c5caea56c5cd424350e0bc3c478efc2e11d868ddf73185627c778ba8b7d684f3d0b9dfe7e1b63985bb43e37a2e5938cae8b1741cb58aea2b383de9bf0531e344a5651f7f145aad1656e695e30ee6483b5e18e43b0aa6e308f2e1c8cfdd85a118476c9ca91c8ca993563b2df014289738c4b6ce772e2ac36a26547b97ba26673e28e634f88a91007e220f1beaa97ae00972954fc705de30642014fa5c4c07792a0f0b4a8ef3c6f0584b1029171a28cd5898e760c91f71c5f9610747ae21f30f1b1bfa7e4df9aedfa8b006f29e89e5b182ac9957067f86767ed5620abcb2c50a41c423e48a676864a2d151c5bf2442f3b90bfd7c047f92cd112367d0579c9f02",
+    // M
+    "40f417ba5a4c78a23e6540b52b68e1e6",
+    // OUTPUT
+    "84bfff8347889f1a9f2cf930c82677be",
+},
+// Pseudorandom 1, Decrypt
+{
+    // Encrypt
+    False,
+    // (KB,KU)
+    "39dd87e0b958cec5d2ba04a17fad9f134770f20f14038bdcd751056a7f16041f",
+    // T
+    "fbf69df9bfc3bec7e4a5dd0c9785dd18727dab2b11baf2898b3b775baed777d209812a71e8d5a1f624a4c2c3ccd91064f494f5deb2b7ab362cda53df3e0291cc439052a05cdbc8fe259f7190792b637eeaf0c5ebdf7d02ec6b89beecf131a916f5c6989267e28defaa5937b35f0a1ce1ef91838c408b2d199170f29e76ae21b8b62a733e4de9d281e6935d20d991e3e1801907f6477f9fd40bd4e72de681336e603bb7ec17d512728864b7cebc9bc6bbc0629082830fa3702cb2eff0fb289b7431d4e1b0b6109599c91c4c78540792331e592fe8c0c190ea18275386ec3d85f68996b6891e484ad4b0601008ead6ed60145f8d01b81d1cf31556744b1676f6c5caea56c5cd424350e0bc3c478efc2e11d868ddf73185627c778ba8b7d684f3d0b9dfe7e1b63985bb43e37a2e5938cae8b1741cb58aea2b383de9bf0531e344a5651f7f145aad1656e695e30ee6483b5e18e43b0aa6e308f2e1c8cfdd85a118476c9ca91c8ca993563b2df014289738c4b6ce772e2ac36a26547b97ba26673e28e634f88a91007e220f1beaa97ae00972954fc705de30642014fa5c4c07792a0f0b4a8ef3c6f0584b1029171a28cd5898e760c91f71c5f9610747ae21f30f1b1bfa7e4df9aedfa8b006f29e89e5b182ac9957067f86767ed5620abcb2c50a41c423e48a676864a2d151c5bf2442f3b90bfd7c047f92cd112367d0579c9f02",
+    // M
+    "40f417ba5a4c78a23e6540b52b68e1e6",
+    // OUTPUT
+    "e2c0bfdef28b5504cf0ec708a6866a17",
+}};
+
+static const struct prf_testvec {
+  const char *keys;
+  int t;
+  const char *input;
+  const char *expect;
+} PRF_TESTVECS[] = {
+// All zeros, t=0
+{
+    // K,B
+    "0000000000000000000000000000000000000000000000000000000000000000",
+    // T
+    0,
+    // t
+    "00000000000000000000000000000000",
+    // OUTPUT
+    "66e94bd4ef8a2c3b884cfa59ca342b2e58e2fccefa7e3061367f1d57a4e7455a0388dace60b6a392f328c2b971b2fe78f795aaab494b5923f7fd89ff948bc1e0200211214e7394da2089b6acd093abe0c94da219118e297d7b7ebcbcc9c388f28ade7d85a8ee35616f7124a9d527029195b84d1b96c690ff2f2de30bf2ec89e00253786e126504f0dab90c48a30321de3345e6b0461e7c9e6c6b7afedde83f40deb3fa6794f8fd8f55a88dcbda9d68f2137cc9c83420077e7cf28ab2696b0df05d11452b58ac50aa2eb3a195b61b87e5c65a6dd5d7f7a84065d5a17ff46273086002496db63fa4b91bee387fa3030c95a73f8d0437e0915fbce5d7a62d8dab0a58b2431bc0bede02550f40238969ec780410befccde6944b69dd007debe39a9dbc5e24f519a4bdf478b1d9ec0b67125f28b06efaa55d79412ad628d45089c3c304f94db3a21df6cdaf6d2e2e3b355441eff64ad90527e752a4b2ebb4d0a1070ce2e2982e272fdb7cf4b584b095a0f957fdb828689437e37dc48b2ad379c6f3c6e957ee77afb88c65949ba12eec45c22865e4907ae42aee813898acdf91e2e4c21d828e0a76de2bb6bb6f869e5eef1f618dedd27562812b9a14e8996a5c352df3817e60d6ec20119a52c80a61ec195622627240212decca515feab63e2734587948a836a7de205cfec0c288351c",
+},
+// All zeros, t=1
+{
+    // K,B
+    "0000000000000000000000000000000000000000000000000000000000000000",
+    // T
+    1,
+    // t
+    "00000000000000000000000000000000",
+    // OUTPUT
+    "7941dd0a63d994703e63d94a446804213ab4fb1d2b7ba376590a2c241d1f508dc6a7f418a14503deb89b17aadb2806f73fc06e5d14e675f5ec880023d4f7329612dce4a0e5bc792b5b5a55f9c2f30e07",
+},
+// All ones, t=0
+{
+    // K,B
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    // T
+    0,
+    // t
+    "ffffffffffffffffffffffffffffffff",
+    // OUTPUT
+    "3ae902931ae8d3ba07a2efdbf0411525c966356463673443646d3ed3e5ba68a61ccd8ae0d8f8d84adca5c0023f02efd10cc7bc7c9fbfd169bf47a792bbba07d6c9338101d0c28e476e99520413a37c3c9045909a8d37c6cba7f9e33254ff0b2ea11ccd6d0a6eed028bb3acccb38fe0a50ea2ca51dc35dc12541f5ce00611336ef966d9a9027a6342c09590d056880e79bfd1a271aff821114649e33a6b4d83be0883b3ad4b315ac6b77017c748a8bb71a981678ff9c6ca086507efb6e8850043767bea06d66ee5e9b2870107474150044488a2d00bd1d5154e0f1aeac5af0b73049004f8717baa13c1ae0088f2dfdf8e08a612aa11a8bb64ab2a4d292967504a6cb451a56275b756c2d7f65aab728617154693a7b31d048802b0ce635754977fb851bb21dddcd9564795e31523fcc35ccfc066b9542508b4daa65b4c9083b12a5cd08f7c45906523d5d7131f279959900473a756e48ababa93fb42663401d84bef93c6cfd6e02fc17797a7004671d21d3bc35cb2e9a344da0a16c300f3c977b6c892e0c2a9517862bec47a51fd49c5a4d46620ea5df9a055e89c2d54abffb00704c1219175433bc1683d93c75ffd7feccc605092b197c5aa5aa995ba0c0e33870b0f513a5eefd9d64fc6e89862709ca9876df86c189181e70e4eed2d876a12480b64afaffe54ba4ac8f23043d8",
+},
+// All ones, t=1
+{
+    // K,B
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    // T
+    1,
+    // t
+    "ffffffffffffffffffffffffffffffff",
+    // OUTPUT
+    "be938fc23009440bfb5d7bba1d28428ec0897793bb878b8e1eb3ec1257b88024d551d770b56c9312de3dd4ac64e194c91185a89f2ba01f90b03b3acb93f634df4b263f03af0acda2f9721d0f4c0783c7",
+},
+// Pseudorandom 1, t=0
+{
+    // K,B
+    "908bf9a158c67f9cd5cb5cee476a4b30b9ec92756d94b1f9f40073c185c39780",
+    // T
+    0,
+    // t
+    "d5d73799a0f37a1c7ff3ceb187ebd059",
+    // OUTPUT
+    "0573c27e297dad58e84942c498d3c376c1f8423dea1df70338259814f2b939ffe074b22fae1385e2d453aca882cb6257ec4fc86915f05be11ae36df4d8678ff29f68c45797c2a4158c4c30c703faeda452858ae37b7735f47b2924cd6f6040e7f6b19958f6d5a12aae72b589d6f85e5a3267881b2916da948fe1626d68ee60a13c90599b40f8a14fd8d1354bc5beb23f38043d7e547b879f618302426dd7785bc2caf44b31a2e1ec5d4dcb4047415d0bbc6900dfdfa01e61c4aa3624f6d7f03cc1641610bd0d6f155cae7465a3d1e0ae8db7d5a00afd27824fd35b65a91728981566c7a046f1773210b76a449b76b5684eb3532a06225d89b70aae1d20c27d03c528b00051a3eb74f3b217b3d474076b8c4bab837476c6191bc7ae8266328d5a65f6a2c55c30999984e2747b7dc45af9e43533b750d9196cd2d64947b6dcf839821002441a428bbbeee45de052502aa76385b47aedbdbeed500c359fdd1ba042a5c9956a1b2e3e456472665f7a674d898a9c5ee58e6288304a323986f0d09b731ae401ac5f8ebeaf808cecce9945439ec76cccb5aafb875faf1274bc29beb29413e01ef72cb37eebc06428c24221f8af3c5ea742fc41506d501d8e45bed5d982ab16669da486c985a3e140eb55c754aaf3d7923d76519f8bac02e0e0c0961411d8c010d1c58498caeae8c69c62",
+},
+// Pseudorandom 1, t=1
+{
+    // K,B
+    "908bf9a158c67f9cd5cb5cee476a4b30b9ec92756d94b1f9f40073c185c39780",
+    // T
+    1,
+    // t
+    "d5d73799a0f37a1c7ff3ceb187ebd059",
+    // OUTPUT
+    "75c5c6787bd0edf325cf772b8c09478df5094f093eddda9d4b7c2ab4ebcdd84c87192038ade81b8560ef7f5a3eb89a4957cf9bd138c575b6951f9940d44d443f47616dfdaf73be7a4664aa46bc279c20",
+},
+// Pseudorandom 2, t=0
+{
+    // K,B
+    "fcd9361d1dca1c7b9a24f754464eeda5727152bc98a818b2e720a0afa9103e10",
+    // T
+    0,
+    // t
+    "d22ea954482f5bc511c6fcae80233d57",
+    // OUTPUT
+    "e5be8144283c5b638a7bbcae231cfb9d6727ee8f04bcf0f3c7ff94930facee8030712bc8b0d151a5ea27c4f614cd46fe1ef9eee2f64c8d8ddfce7ab1ed01cdd610e7b341b75408a6a8310ed62389348b76a59277a0eee05bdd5f5227d43fc113294b3af71d505a0c88d920387f44ef33029797d0c282991683adca83449e68aff578df836ee6b89cfc5fb0e8ce0d8d5146a09d57ec816e0fbbbdf80b298dfe6509aefcc892a6dd4bbbbfc6d9507e4013305986bb9d0cc050de172faa2039623cb93177a0b7c5966933ad2ee6fa930dbfe24148b63561551364fdcc3a1e1a5381d62a9508c6bd86a43ed602e5fcc0cee2c8d2a67c7de50428fa72ef75fd4aafe4bbc17cb03ffeb69066e5c6ee65c1df67fc9c33515d45cfc504c2d2db213b36df5a6b7864ccf11061464048329f2a79b6935b4219aab811fa07896497ba8dc47c1f2e9207e4be7a4f3651bba673d4911787df8423cc60601785ac5072516954efa6e30f27c04136f6bdff2d23b0489b2d7c9aea7f5320d48c337eeb2dd4f06eedab55316ba8f45b6f9a584ae8241183a261b467be3b500febbb609a38895d92f0aa8ca31197e81c85aefcea4d7a5f0d4f7825de0260f9563db272720c723a38f935113d1f2a89b02303eced1312316f29001f224a730031ac6e002916a2077fff2de0cb740622692fde1ffe1811",
+},
+// Pseudorandom 2, t=1
+{
+    // K,B
+    "fcd9361d1dca1c7b9a24f754464eeda5727152bc98a818b2e720a0afa9103e10",
+    // T
+    1,
+    // t
+    "d22ea954482f5bc511c6fcae80233d57",
+    // OUTPUT
+    "242f479249fefc782eb74cb5304e54cf9f1230ba8908c1a322650edd916ecf76527d3a84db32d8ca0931b0420c8bcf388042cb152ee080af416a37cf2e0406de5ebb9c27061650e72552ecadca5af451",
+},
+// Pseudorandom 3, t=0
+{
+    // K,B
+    "b445f9a92d267dbd3b2dd9ad0cf90538b4013b72df0a23ef997f7baf9440733a",
+    // T
+    0,
+    // t
+    "1d16d4b08c19887a050603dc8f17bb6d",
+    // OUTPUT
+    "d609818f6c26b96efadb6e5d84ed63c4d426e133cc72acc222a161feb8810f99d0d8dbace6181b8f41e601cf7bd33a3a23e84bf8898505e222a42fb889fe759e48fda34e6d39c357e6445864e55633bc264d4f4d33d0afd20a51ad1b3d6d18288d6930ac9775e6928cdce46642b5b81bdbbdb469f1d6f1a889a2c76c0258ca6a95c5f590c54f1503ec46f1b3ad6e93233f9f4ca9a57bc14aa505f4678d9fee8fa7b8f047252569b83c79e3a4abea6d4a481e159e91dced13f58be46f41ef52f1ebb17cd68875f2dee11ab6409fe5f0d5a189fdc86b4b214f2ccd47d419e66ba3889bac4b7da810b3ba647082662c74c2e2972962d8af31fc05a22e0df7f5e87a6bdfb2b414bf55e46581ca982bca237a0af99030d079b6e81404b6bad3011bdf114e2d042e019342a41b06d1ece656ff994b80796747ca413f783c903f0980873c95b1b3d6d3c99410d75993a9b28ae23d26847da256e09f7c727f1b05eb1fa4aea6d0b4b69307363c2ec7d75e136c14f3ca44d2d2bf001095ee5505bcb56c06842233411e5bc38da56d97c1ce6b1807db9b832fc10625d55fb259a5cd75c0a1d27a8b8a8c9b2a98bdfe7903a927026f2d2616b481e13e3bf7eb1039ec892a2fa054fd8dd6aac76b0efbece35b374310583d1c5f594604c3a49b35af7c3065f9f5f3bbe5cac3e8522a0654463c",
+},
+// Pseudorandom 3, t=1
+{
+    // K,B
+    "b445f9a92d267dbd3b2dd9ad0cf90538b4013b72df0a23ef997f7baf9440733a",
+    // T
+    1,
+    // t
+    "1d16d4b08c19887a050603dc8f17bb6d",
+    // OUTPUT
+    "85da4380c7376cdd451aef93c15303626a9a54b1b21ddf5fa88beb1ade9331438764e8ad4f640dae16e3708806bab62d948e2ac6210b198184d0bface602a49f606d13e72574d7cda3e8458736d180b6",
+},
+};
+
+static const struct uiv_testvec {
+  bool encrypt;
+  const char *keys;
+  const char *tweaks;
+  const char *x_l;
+  const char *x_r;
+  struct {
+    const char *y_l;
+    const char *y_r;
+  } y;
+} UIV_TESTVECS[] = {
+// All zeros, encrypt
+{
+    // Encrypt
+    True,
+    // {J,S}
+    "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    // H
+    "0000000000000000000000000000000000",
+    // X_L
+    "00000000000000000000000000000000",
+    // X_R
+    "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    // Output={Y_L, Y_R}
+    {"66e94bd4ef8a2c3b884cfa59ca342b2e", "66e94bd4ef8a2c3b884cfa59ca342b2e58e2fccefa7e3061367f1d57a4e7455a0388dace60b6a392f328c2b971b2fe78f795aaab494b5923f7fd89ff948bc1e0200211214e7394da2089b6acd093abe0c94da219118e297d7b7ebcbcc9c388f28ade7d85a8ee35616f7124a9d527029195b84d1b96c690ff2f2de30bf2ec89e00253786e126504f0dab90c48a30321de3345e6b0461e7c9e6c6b7afedde83f40deb3fa6794f8fd8f55a88dcbda9d68f2137cc9c83420077e7cf28ab2696b0df05d11452b58ac50aa2eb3a195b61b87e5c65a6dd5d7f7a84065d5a17ff46273086002496db63fa4b91bee387fa3030c95a73f8d0437e0915fbce5d7a62d8dab0a58b2431bc0bede02550f40238969ec780410befccde6944b69dd007debe39a9dbc5e24f519a4bdf478b1d9ec0b67125f28b06efaa55d79412ad628d45089c3c304f94db3a21df6cdaf6d2e2e3b355441eff64ad90527e752a4b2ebb4d0a1070ce2e2982e272fdb7cf4b584b095a0f957fdb828689437e37dc48b2ad379c6f3c6e957ee77afb88c65949ba12eec45c22865e4907ae42aee813898acdf91e2e4c21d828e0a76de2bb6bb6f869e5eef1f618dedd27562812b9a14e8996a5c352df3817e60d6ec20119a52c80a61ec195622627240212decca515feab63e2734587948a836a7de205cfec0c288351c"},
+},
+// All zeros, decrypt
+{
+    // Encrypt
+    False,
+    // {J,S}
+    "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    // H
+    "0000000000000000000000000000000000",
+    // X_L
+    "00000000000000000000000000000000",
+    // X_R
+    "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    // Output={Y_L, Y_R}
+    {"140f0f1011b5223d79587717ffd9ec3a", "66e94bd4ef8a2c3b884cfa59ca342b2e58e2fccefa7e3061367f1d57a4e7455a0388dace60b6a392f328c2b971b2fe78f795aaab494b5923f7fd89ff948bc1e0200211214e7394da2089b6acd093abe0c94da219118e297d7b7ebcbcc9c388f28ade7d85a8ee35616f7124a9d527029195b84d1b96c690ff2f2de30bf2ec89e00253786e126504f0dab90c48a30321de3345e6b0461e7c9e6c6b7afedde83f40deb3fa6794f8fd8f55a88dcbda9d68f2137cc9c83420077e7cf28ab2696b0df05d11452b58ac50aa2eb3a195b61b87e5c65a6dd5d7f7a84065d5a17ff46273086002496db63fa4b91bee387fa3030c95a73f8d0437e0915fbce5d7a62d8dab0a58b2431bc0bede02550f40238969ec780410befccde6944b69dd007debe39a9dbc5e24f519a4bdf478b1d9ec0b67125f28b06efaa55d79412ad628d45089c3c304f94db3a21df6cdaf6d2e2e3b355441eff64ad90527e752a4b2ebb4d0a1070ce2e2982e272fdb7cf4b584b095a0f957fdb828689437e37dc48b2ad379c6f3c6e957ee77afb88c65949ba12eec45c22865e4907ae42aee813898acdf91e2e4c21d828e0a76de2bb6bb6f869e5eef1f618dedd27562812b9a14e8996a5c352df3817e60d6ec20119a52c80a61ec195622627240212decca515feab63e2734587948a836a7de205cfec0c288351c"},
+},
+// All ones, encrypt
+{
+    // Encrypt
+    True,
+    // {J,S}
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    // H
+    "ffffffffffffffffffffffffffffffffff",
+    // X_L
+    "ffffffffffffffffffffffffffffffff",
+    // X_R
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    // Output={Y_L, Y_R}
+    {"7209baed4b605b158b7eb25de2200e83", "5546c0d2d8da37d92908803d88ff5a646e24a9f9c8c0e9f239726ccc5107d45ed813697aab0b27969930489e47c87475d71b92fc875e268d2ed92fa735b8258c657ff883512adf916a7a8819596e878415da7dc689fc658b862235133b4366e5bea11ece0990a544cb324e27313d67567797213ddb9102e75caca82a15035e44a306c906f8c17e2c88975808b35ad13443849d9ebec10f2c888738ff5b7cb3043b2bbd6098b167746addcc55238fb32d9ef404f3d0f7db0bc5f30aca0cf9ce5f87c989268d18b1069b33bbd5b7818a99603ec0d82871e75cffd1d84e2be1e0f8e8b3678b1ccd7a5a676d83fe0e68f09027ad912d58d2257932750b383e2f2fa3c889ee9d71919cc05d982230c6ff8b7e5e3ed302ed82bed429794c261aa009d231bb6c8675e513313432017cea50843a0309153f7f9d556330f19c38bc5ae6d33d63abaa7ebabd3335c1bf59a2121378288da679259bb1b8a8b027938f3e902c655c781e7f5d9514e53502e7ebc31e344344c3ae2a6397a9a8b846dab8a84174e91664804c7804bab09d6d40aeeb491d6f6184830ac7b5807418a05a7ab9938c3fdc18066b5d503f8c98e83be033b6fba905324267618cc6989b486e0decf7cc897d17be093286a4d4fb5016c3e3323ccc416a30473081473bd471e430194ec4e2ac0af3bca0577b78f4c70e4d"},
+},
+// All ones, decrypt
+{
+    // Encrypt
+    False,
+    // {J,S}
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    // H
+    "ffffffffffffffffffffffffffffffffff",
+    // X_L
+    "ffffffffffffffffffffffffffffffff",
+    // X_R
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    // Output={Y_L, Y_R}
+    {"a9166add184e00b007217cb4e4f52ee5", "c516fd6ce5172c45f85d10240fbeeada3699ca9b9c98cbbc9b92c12c1a459759e332751f270727b5235a3ffdc0fd102ef338438360402e9640b8586d4445f82936cc7efe2f3d71b89166adfbec5c83c36fba6f6572c8393458061ccdab00f4d15ee33292f59112fd744c53334c701f5af15d35ae23ca23edabe0a31ff9eecc9106992656fd859cbd3f6a6f2fa977f186402e5d8e5007deeeb9b61cc594b27c41f77c4c52b4cea539488fe838b757448e567e9870063935f79af81049177affbc898415f929911a164d78fef8b8beaffbbb775d2ff42e2aeab1f0e5153a50f48cfb6ffb078e8455ec3e51ff770d202071f759ed55ee57449b54d5b2d6d698afb5934bae5a9d8a48a93d2809a5548d79e8eab96c584ce2fb77fd4f319ca8ab688047ae44de222326a9b86a1ceadc033ca3303f9946abdaf74b2559a4b36f7c4ed5a32f7083ba6f9adc2a28ece0d866a66ffb8c58a91b7545456c04bd99cbfe27b4106c3930291fd03e886858ffb98e2de2c43ca34d165cbb25f5e93cff0c368849376d1f3d56ae879d413b85ae02b63a5b2b99df15a2065faa1763d2ab54004ff8fb3ede6e8abcc43e97c26c38a0028013339faf6d4e683a55a5566a45f3f1cc78f4f0aec5a1102629b03917679d8f635678920793e76e7e18f1b112d27895edb7f49b505001ab45b5370dcfbc27"},
+},
+// Pseudorandom 1, Encrypt
+{
+    // Encrypt
+    True,
+    // {J,S}
+    "22169d562646db8ce3322daf9fa8e84bed57dac18beabdb619b40f5a02786e439e6132a5b7e7445ef962e2c7f30a0b2ec994ee1935e1787d6f89cbc15b9254d5",
+    // H
+    "81b1a82c63d8dd9f682f734e82e259a14b",
+    // X_L
+    "db4adc246c6a54fe55e11901f08f0eee",
+    // X_R
+    "dccb8aa0570982d5b08fa6b4fbb3bc7bcd7e1cb374f114fcdd0ed53255c523c1e8ecabfbaab00105af9933eff304a40877e3006be82417e44462fc6955777baf2f2f9c99966ddee30b7c65050dcc8f5d7bc22f9192e1c4a83ca0a2338deca9da4fed14058812ace259e9851f50c83d97515b647ff56749a3c615e1860a404e9b05619a48aa3e5f09779e72c77dfdd1ab5d796860178938efffb4e43bda443c2fba0d5be14baa375d881ee5e14d760a12c4d9807c8c54b714a75324c4074296ba8c86beceb4dc2337946f0af67c759d999d56aceebefd80438dc625b0b50f749440e8cd5bcee1afa05ff8cff1bfd3e0e68930b74b3baa7e6cb723896afc3098326716e0554319c9f8f3683a8fd359ae48a3591dac67adeea6e24600037cb1a42c38b23a9e7c31e6d0b13b84e88341458050856c476f6875dfe17806e0cd72baeebd0e1108fb13088a887153a1d13bac414ed120c26bda5bc08202fc1489746305dcda5dfc3e6a869b52d63654f3bc35841a2345df8691caf1de06d5ebd214af729e239efd62d05060eab13fb973ffb41f461224e2eee1f739529d37e8db40c065e52de7b8f19fbf695470341062c7f212d8cf5310cc17f487e04e8ed03fe50f3758eda9e41bf37af62f94709b16e01b4305d1a5ccad2bcacc7e35d4a1d1a40c26dbf3153739175ca65ab59cc1c9",
+    // Output={Y_L, Y_R}
+    {"9ff8aec547f02caae7555ad7b0cb1372", "d2ceaad0ef52ac304d9550f0c365fa7728dca633f4f62228b241e47754dfa01c709e66f54a018e667573dbf5063c10a6167592250a8f6af17c68d5e190c5e8bdb40082b54e428ada58b3a805cdd56d87cc56591f8ac5f310eb37ce4d8a6ea6a80c3d7316f872aeb8a1a40ef7d1f0bbd1711d38c500eccc0acb88c1a3491ceee45bf82ba3fdf939dcbedb4f140fae21339f671df748d18e8bc8d72eb8e7cf18feafcd6f363345e2b05bc2d4d6f3affb8589d002aadb711d5ad2fa81398137b9e88f5a8663787e746bb878c0973d9b80145f8dd18bce30ea10e13ad07aeb469f62c4507ff8aaba09ed988a794232b5fc4e64466103366ed474fb99406fc80dd5ae5cb90001b1485476978dada0df83456feb3674ec7070b177871203410c5f7adbefbf5069c64bce59ef381514cb2a8d0804d855cca0a9c84d4c60c44e7242ffc479aabf357016166e9b184311c0e04dd9d1c512ae508b8f8c6bfdba68d46e338250f23668aaf11f45f57bfbc8e32d14ccab853af085a17405058ecd993cb21b41f8456c54a3900a50c270fc2b0931394ba257fd49bf79ae54d9c39a8504067b9cee1e42b688dcc606bee57006bc561b991dd10c56c129d1db8997e0d163fd06fca83249f146e348a4f35d0cb47d0ea6b3c6c587487f3816e13c2494d6d1e866fa182855cd668462a30b4e7019d2"},
+},
+// Pseudorandom 1, Decrypt
+{
+    // Encrypt
+    False,
+    // {J,S}
+    "22169d562646db8ce3322daf9fa8e84bed57dac18beabdb619b40f5a02786e439e6132a5b7e7445ef962e2c7f30a0b2ec994ee1935e1787d6f89cbc15b9254d5",
+    // H
+    "81b1a82c63d8dd9f682f734e82e259a14b",
+    // X_L
+    "db4adc246c6a54fe55e11901f08f0eee",
+    // X_R
+    "dccb8aa0570982d5b08fa6b4fbb3bc7bcd7e1cb374f114fcdd0ed53255c523c1e8ecabfbaab00105af9933eff304a40877e3006be82417e44462fc6955777baf2f2f9c99966ddee30b7c65050dcc8f5d7bc22f9192e1c4a83ca0a2338deca9da4fed14058812ace259e9851f50c83d97515b647ff56749a3c615e1860a404e9b05619a48aa3e5f09779e72c77dfdd1ab5d796860178938efffb4e43bda443c2fba0d5be14baa375d881ee5e14d760a12c4d9807c8c54b714a75324c4074296ba8c86beceb4dc2337946f0af67c759d999d56aceebefd80438dc625b0b50f749440e8cd5bcee1afa05ff8cff1bfd3e0e68930b74b3baa7e6cb723896afc3098326716e0554319c9f8f3683a8fd359ae48a3591dac67adeea6e24600037cb1a42c38b23a9e7c31e6d0b13b84e88341458050856c476f6875dfe17806e0cd72baeebd0e1108fb13088a887153a1d13bac414ed120c26bda5bc08202fc1489746305dcda5dfc3e6a869b52d63654f3bc35841a2345df8691caf1de06d5ebd214af729e239efd62d05060eab13fb973ffb41f461224e2eee1f739529d37e8db40c065e52de7b8f19fbf695470341062c7f212d8cf5310cc17f487e04e8ed03fe50f3758eda9e41bf37af62f94709b16e01b4305d1a5ccad2bcacc7e35d4a1d1a40c26dbf3153739175ca65ab59cc1c9",
+    // Output={Y_L, Y_R}
+    {"1c1593f30a020d44b7b2cf9b75204808", "c8135c678f97432394c2fa38ee6f590104a99028da2624a44d9a503adf1fe0d236ed5253d2e1cfca479c1b41609ef7cab58d7bb4d8e50e21108f837791f2e3066d3c40672f8e0f1a304d25ba20401a8aac6b15c1582788fe8996277b5cb554cd82e0920f212293b8bc4357349465f75e2f688a084d4c4741e2b384523d51cf94062cb7e6c1481eb48f1eb37e00937e0a2b2e12c88ac1b8ca2eef537b74b9f22e016b1a72818eddd1bde1e3643f4aa8fbf1e69543475131c17e8562c6c7d9a4375936fcc1c6ffeb131195e2168cd26368c3ab40f436563b4a5b6db2a7fa61e521321dad4d1aaaca8d0b38d68de26f402233d3bc25c78cd9019d2985f0f02c51cd07423726bd7ef3d19a8bb7b5a94d54ed86c460e7b02388fcedaa99902bc43c8339a171e89dc8123a9cd05287f2c0a39fdf24dd56dcf4326c163d38c9282d1f2f9a0faf50beb6e5caeb300a6192f6c1aaa5ba515bb677bb399edfcb05eab48870e79018d60ccdf3f1b5b6eb1e1a9e24a8a11da26c0919e6ec7db6a1260d6299e7a2830d48d3bf33b40f4c5cc0c4acae037a4bf5731fff732f44752bd002c8493f1d48ea6c5f0cbbd596d679c7130d687211e74a96473275f2c8df1634ad36aab16eb0221777e98f62057d669b51bee20ab45db78b619aa619a40245d9ddd074a35c54b77f7ccc0410c00719e423"},
+},
+// Pseudorandom 2, Encrypt
+{
+    // Encrypt
+    True,
+    // {J,S}
+    "e61126a280c66e99171b1ec940a75ba6d4c3301062760a9ff9f9b332433b8408af4a49ad5326b95da7f501bb8b34bce8ce6e8c33f57263a4779a161d3736b7e4",
+    // H
+    "837d18ad2b0162977d3461349b00bcfdd7",
+    // X_L
+    "4b9fa9b1d31ada9a15d29cd0bd920a53",
+    // X_R
+    "c819e68a3dbdb6e730e4c9ed6dc11187f7ef0f021f63f0da32b55c8e2c78d4bc55d0e487329542e0b63a8f57313a146443cb5b8ba9eaf77d7e80884333b0e33b806da9cb32e231b5a23aa9ffa6f598df337126b26d6f9ef0d6c6a1bb59d83d253ea0ddc7272d633eb785dd7649fb7618a3b2c721cfba7d3e3d4a95fafcb90c2c60a52258ff3da3e1ab1205747e049e4730d734eaa5744a18fa8b8c4aed37d28857a5877839a40659e9754795859dad3163d6358234072ae62f64e58c861fd2225c60a708bb9b199e2070315e6d8eda8365b7cdaf5d067280086fda2a304ccbf3177fb0d34bbc3b02b4aaf42e535fbf2a15b782a8f3004eecda69aade92eb7bc92b6d552172bd26db28dcdf36547fa2794bf598db51ba0578f176e0cd3db04bf8333819a17f9c0d6faa9128930a3d8e93170d1bdf4e17a26f794631c870d9fd0edc73c3ce51753cbf59f3c942b689ccd08d8c0e0b19840b5113b760893b7d3b3a7c8f2c8794cab2f35a49d1d3f8c93bc353f376904d1961d3958cdc7a1841fe305c5c48b98f3b829ab1540876c3bd9d73e979107ab6f8146fe8acf33bde92b0a0ec2088dca60ae8374bda9d36977e5653762e8744d2b66d5bcc26dde754ec5eb3f665fbdd1ce2d0a7a48c499c0a5b846a7cb3c478484703c31ebb0c20c66bc1c1722529be0bb850d9bff7437ca7",
+    // Output={Y_L, Y_R}
+    {"6db7f5e28e54c81ce9a22df58eef5f65", "bc742015a226d2f1f855c06e9c12ce9866a311622befc8ff5f796c1813308fecb8c91b7fdc00de93198498afeec55422f678fd1822131877c947236c4c978b0f0fc3d0ddeee5770bac3c635895d9b922399a0062e4c80b9ea753f89608ed6920fc2f8a9e09ba87b40c6ef2a9354524e7f0ac1d967ef520a348e37fccc35f584ed5270b7e4694cd263a25f8869e07693df3f9a5fbc6aa4f8697456fa830e07f6ab89d3ae18790b6208c09ec2cf530239bb7d514691a01b4a772233693dc398bc6b4a08e412dce4d3b3556c0aa8ad2d0ee474ef56c1dd232b677c2d38be103921b8373e9e1c8ef2e98ededce0439351e74ca751db03e8213cd6adb6ee5a7b3103675776b82e59120d21e4b4e03e01aa420c254530ed74c24dcc514ddb241450642dedd0c5b1a0f7c2a7af243b96f1d1312ff8448a0c3a2b9cdaa90c45d11fdd82e6b17bc83f246313d7d67dd380165ceac938f5cd7896ffb8c22669526345f42892e1cb2b5af64f39139e6f3d5ed8e03efa61d653bbfcdf387c9ccf9de686b9603a681143ccd21a12004c810acc7e9c17bbdeba632bc9e5f0c6d3cbf72c8e7b958fb66d38bdb77c1e170de541b0750d942e9f8bcf123bd4a369af220135816b6d9b09778a8bc5e0378ec82678c1344a978fd1b271376ac31ba3981a1639a3808a8cd35424daa91d59f6e34967434"},
+},
+// Pseudorandom 2, Decrypt
+{
+    // Encrypt
+    False,
+    // {J,S}
+    "e61126a280c66e99171b1ec940a75ba6d4c3301062760a9ff9f9b332433b8408af4a49ad5326b95da7f501bb8b34bce8ce6e8c33f57263a4779a161d3736b7e4",
+    // H
+    "837d18ad2b0162977d3461349b00bcfdd7",
+    // X_L
+    "4b9fa9b1d31ada9a15d29cd0bd920a53",
+    // X_R
+    "c819e68a3dbdb6e730e4c9ed6dc11187f7ef0f021f63f0da32b55c8e2c78d4bc55d0e487329542e0b63a8f57313a146443cb5b8ba9eaf77d7e80884333b0e33b806da9cb32e231b5a23aa9ffa6f598df337126b26d6f9ef0d6c6a1bb59d83d253ea0ddc7272d633eb785dd7649fb7618a3b2c721cfba7d3e3d4a95fafcb90c2c60a52258ff3da3e1ab1205747e049e4730d734eaa5744a18fa8b8c4aed37d28857a5877839a40659e9754795859dad3163d6358234072ae62f64e58c861fd2225c60a708bb9b199e2070315e6d8eda8365b7cdaf5d067280086fda2a304ccbf3177fb0d34bbc3b02b4aaf42e535fbf2a15b782a8f3004eecda69aade92eb7bc92b6d552172bd26db28dcdf36547fa2794bf598db51ba0578f176e0cd3db04bf8333819a17f9c0d6faa9128930a3d8e93170d1bdf4e17a26f794631c870d9fd0edc73c3ce51753cbf59f3c942b689ccd08d8c0e0b19840b5113b760893b7d3b3a7c8f2c8794cab2f35a49d1d3f8c93bc353f376904d1961d3958cdc7a1841fe305c5c48b98f3b829ab1540876c3bd9d73e979107ab6f8146fe8acf33bde92b0a0ec2088dca60ae8374bda9d36977e5653762e8744d2b66d5bcc26dde754ec5eb3f665fbdd1ce2d0a7a48c499c0a5b846a7cb3c478484703c31ebb0c20c66bc1c1722529be0bb850d9bff7437ca7",
+    // Output={Y_L, Y_R}
+    {"e6d9d943df0c57ffcf625bd142f023cc", "16fcd880981c22d459e61a502213668f6f040c57f9db8cf39fc07bce35a9df2e2cd083359e833944bb1da2d107caa11d171abfe5878787ca89c5460753a8df4c3af7d9e1091a68e1976eba0ba81005f60f1fb2f26b6163896070efb1c39e31d24bfcd23e897062645b5d5766ab81eef662ca789d4e5bf7aa76414d01acd67e9ec372e63f5a676fb4645cbff0c47b3ec0c667b781ac42a0fedb75eaf39fce4da7f37a660db56ca92836609ca32faa4987f232e89177cee0b45ab8d34ca1c9a9f29fcd057cc6e491387f75e01ffc40ddef01c0bb557803cb514afb19718a9b918ed266f5c3283d93b6fdd1232758f059a0f6f78b7d5e8cb0d61a7e974936b6bf007a0121db05051a3f3279c3b295de61af596f8ce8820480f666f963ad4c9af0490272f88eb01bdad77ca43c8d5cc3abf7b1d1b6b15890b43008d68ddbc9f2426985d6ef946cae5099b1b81eb500959d8e5571b438f796aafadfa85ae29e6373878f0eac3f91e75cd37c609064e57fc588bdba25f5f2cd095e14d01fe72ddb30f784fe97ccb7619ebe809a5acb24f73a69f9a3b348b99ff554fa316b4b16cb7cdcd79602ac17003057e7bff980521e43a57a43df4c13ed71740767ab277e1da09b371843aa1160b599b5cc0b1cb4c31d348718b64b9a6d66cd691185465ae5dcea649391b342bea21d430df41080"},
+},
+};
+
+static const struct uiv_update_testvec {
+  const char *keys;
+  const char *nonce;
+  struct {
+     const char *new_keys;
+     const char *new_nonce;
+  } output;
+} UIV_UPDATE_TESTVECS[] = {
+// All zeros
+{
+    // {J,S}
+    "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    // N
+    "00000000000000000000000000000000",
+    // Output={{J',S'}, N'}
+    {"7941dd0a63d994703e63d94a446804213ab4fb1d2b7ba376590a2c241d1f508dc6a7f418a14503deb89b17aadb2806f73fc06e5d14e675f5ec880023d4f73296", "12dce4a0e5bc792b5b5a55f9c2f30e07"},
+},
+// All ones
+{
+    // {J,S}
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    // N
+    "ffffffffffffffffffffffffffffffff",
+    // Output={{J',S'}, N'}
+    {"be938fc23009440bfb5d7bba1d28428ec0897793bb878b8e1eb3ec1257b88024d551d770b56c9312de3dd4ac64e194c91185a89f2ba01f90b03b3acb93f634df", "4b263f03af0acda2f9721d0f4c0783c7"},
+},
+// Pseudorandom 1
+{
+    // {J,S}
+    "bf9b13b7097f5eee96b15423348ebf7aa13da31a2955a72ff17c7f75f9fd92f3280bb2d8992bc2a5f70f8e4e10f0404f16f3a0bbb91975bae7e11180adcba6da",
+    // N
+    "1ecb6e1ee6f0531c233676fd1c3ca701",
+    // Output={{J',S'}, N'}
+    {"4c03bdb301aaa20b907ac88ecd3174bb1b476a00a81ad67cb4eceba22354ce8f241ab2da59d565781634e9252358006b5b78166ea59028e1ff40a71687dd2f11", "30151c332e40594324ef033a469d4f50"},
+},
+// Pseudorandom 2
+{
+    // {J,S}
+    "e773e6211d22f03e0220bd6e69803b2d0bfbd6321a9e0184e49660bc1989318a868d5ccd3f769537c4990df2998cddc14cd853d8614c6e7e3548af9c824b457d",
+    // N
+    "d8097e67afff2d56a6279229cfb37f29",
+    // Output={{J',S'}, N'}
+    {"c9f14480f4d46a1e40ba3daaab9004ec0e7afab84f3c60da9f7f01ab46aab7a81e2138eed6f141047bc4169013450134586120852ca9a952417b402834fbedf4", "c79fdf8d9ae28e55eaed6e556e12b994"},
+},
+// Pseudorandom 3
+{
+    // {J,S}
+    "a150329bf382e5c3b11619ac72ac3b2cdf55b19e94fd2f92bfca40ef540274ba571c53695d5d8f0d4d4935d2b599ee095754d3bbdd9929d60997eb4f745e0a48",
+    // N
+    "071652e3ea1b1d299ac07ab3df431090",
+    // Output={{J',S'}, N'}
+    {"7c7a284801c7aa0f727f8e24b87a2dca1b7550577924490291a7343924dfd1b9e2b86e4f7db39b77715888f7d3b9aa4ea44bd72a8b1eb79df0d46f25c130fc87", "3c8ecadfbe9ee9587332a6568c7f2476"},
+},
+};
index b3e3f8bbf201e8eb253e9ce53d9432e9f28a7a16..86b9c16a9ada8d4afeead8359750d1f238ba48c3 100644 (file)
@@ -162,6 +162,7 @@ src_test_test_SOURCES += \
        src/test/test_crypto.c \
        src/test/test_crypto_ope.c \
        src/test/test_crypto_rng.c \
+        src/test/test_crypto_cgo.c \
        src/test/test_data.c \
        src/test/test_dir.c \
        src/test/test_dirauth_ports.c \
@@ -382,6 +383,7 @@ noinst_HEADERS+= \
        src/test/example_extrainfo.inc \
        src/test/failing_routerdescs.inc \
        src/test/ed25519_vectors.inc \
+       src/test/cgo_vectors.inc \
        src/test/test_descriptors.inc \
        src/test/test_hs_descriptor.inc \
        src/test/vote_descriptors.inc
index 317b570d8ee3b3ea0a125084a4b326ea5242bfab..75df254bcaa29df00bd22e305aac3e2564bfef58 100644 (file)
@@ -628,6 +628,7 @@ struct testgroup_t testgroups[] = {
 #endif
   { "crypto/pem/", pem_tests },
   { "crypto/rng/", crypto_rng_tests },
+  { "crypto/cgo/", crypto_cgo_tests },
   { "dir/", dir_tests },
   { "dir/auth/ports/", dirauth_port_tests },
   { "dir/auth/process_descs/", process_descs_tests },
index 7a405b649e1d9195786cfb5e4ddae1bd2b74dba2..48d81bb56754c7e009087da138387ab2afe883eb 100644 (file)
@@ -122,6 +122,7 @@ extern struct testcase_t controller_tests[];
 extern struct testcase_t crypto_ope_tests[];
 extern struct testcase_t crypto_openssl_tests[];
 extern struct testcase_t crypto_rng_tests[];
+extern struct testcase_t crypto_cgo_tests[];
 extern struct testcase_t crypto_tests[];
 extern struct testcase_t dirauth_port_tests[];
 extern struct testcase_t dir_handle_get_tests[];
diff --git a/src/test/test_crypto_cgo.c b/src/test/test_crypto_cgo.c
new file mode 100644 (file)
index 0000000..ec362f1
--- /dev/null
@@ -0,0 +1,309 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2021, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#define RELAY_CRYPTO_CGO_PRIVATE
+#define USE_AES_RAW
+
+#include "orconfig.h"
+#include "core/or/or.h"
+#include "test/test.h"
+#include "lib/cc/compat_compiler.h"
+#include "lib/crypt_ops/aes.h"
+#include "ext/polyval/polyval.h"
+#include "core/crypto/relay_crypto_cgo.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_util.h"
+
+#include "test/cgo_vectors.inc"
+
+static const int AESBITS[] = { 128, 192, 256 };
+
+static void
+test_crypto_cgo_et_roundtrip(void *arg)
+{
+  (void)arg;
+  uint8_t key[32 + 16]; // max
+  uint8_t tweak_h[16];
+  uint8_t tweak_x_r[493];
+  uint8_t block[16], block_orig[16];
+  cgo_et_t et1, et2;
+  memset(&et1, 0, sizeof(et1));
+  memset(&et2, 0, sizeof(et2));
+
+  et_tweak_t tweak = {
+    .uiv = {
+      .h = tweak_h,
+      .cmd = 7,
+    },
+    .x_r = tweak_x_r,
+  };
+
+  for (int bi = 0; bi < (int) ARRAY_LENGTH(AESBITS); ++bi) {
+    const int aesbits = AESBITS[bi];
+
+    for (int i = 0; i < 16; ++i) {
+      crypto_rand((char*)key, sizeof(key));
+      crypto_rand((char*)tweak_h, sizeof(tweak_h));
+      crypto_rand((char*)tweak_x_r, sizeof(tweak_x_r));
+      crypto_rand((char*)block_orig, sizeof(block_orig));
+      memcpy(block, block_orig, 16);
+      cgo_et_init(&et1, aesbits, true, key);
+      cgo_et_init(&et2, aesbits, false, key);
+
+      // encrypt-then-decrypt should round-trip.
+      cgo_et_encrypt(&et1, tweak, block);
+      tt_mem_op(block, OP_NE, block_orig, 16);
+      cgo_et_decrypt(&et2, tweak, block);
+      tt_mem_op(block, OP_EQ, block_orig, 16);
+
+      // decrypt-then-encrypt should round-trip.
+      cgo_et_decrypt(&et2, tweak, block);
+      tt_mem_op(block, OP_NE, block_orig, 16);
+      cgo_et_encrypt(&et1, tweak, block);
+      tt_mem_op(block, OP_EQ, block_orig, 16);
+
+      cgo_et_clear(&et1);
+      cgo_et_clear(&et2);
+    }
+  }
+ done:
+  cgo_et_clear(&et1);
+  cgo_et_clear(&et2);
+}
+
+static void
+test_crypto_cgo_uiv_roundtrip(void *arg)
+{
+  (void)arg;
+  uint8_t key[64 + 32]; // max
+  uint8_t tweak_h[16];
+  uint8_t cell[509], cell_orig[509];
+  cgo_uiv_t uiv1, uiv2;
+  memset(&uiv1, 0, sizeof(uiv1));
+  memset(&uiv2, 0, sizeof(uiv2));
+
+  uiv_tweak_t tweak = {
+    .h = tweak_h,
+    .cmd = 4,
+  };
+
+  for (int bi = 0; bi < (int) ARRAY_LENGTH(AESBITS); ++bi) {
+    const int aesbits = AESBITS[bi];
+
+    for (int i = 0; i < 16; ++i) {
+      crypto_rand((char*)key, sizeof(key));
+      crypto_rand((char*)tweak_h, sizeof(tweak_h));
+      crypto_rand((char*)cell_orig, sizeof(cell_orig));
+      memcpy(cell, cell_orig, sizeof(cell_orig));
+
+      cgo_uiv_init(&uiv1, aesbits, true, key);
+      cgo_uiv_init(&uiv2, aesbits, false, key);
+
+      // encrypt-then-decrypt should round-trip.
+      cgo_uiv_encrypt(&uiv1, tweak, cell);
+      tt_mem_op(cell, OP_NE, cell_orig, sizeof(cell));
+      cgo_uiv_decrypt(&uiv2, tweak, cell);
+      tt_mem_op(cell, OP_EQ, cell_orig, sizeof(cell));
+
+      // decrypt-then-encrypt should round-trip.
+      cgo_uiv_decrypt(&uiv2, tweak, cell);
+      tt_mem_op(cell, OP_NE, cell_orig, sizeof(cell));
+      cgo_uiv_encrypt(&uiv1, tweak, cell);
+      tt_mem_op(cell, OP_EQ, cell_orig, sizeof(cell));
+
+      cgo_uiv_clear(&uiv1);
+      cgo_uiv_clear(&uiv2);
+    }
+  }
+ done:
+  cgo_uiv_clear(&uiv1);
+  cgo_uiv_clear(&uiv2);
+}
+
+#define UNHEX(out,inp) STMT_BEGIN {                                     \
+    size_t inplen = strlen(inp);                                        \
+    tt_int_op(sizeof(out), OP_EQ, inplen / 2);                          \
+    int r = base16_decode((char*)(out), sizeof(out), inp, inplen);      \
+    tt_int_op(r, OP_EQ, sizeof(out));                                   \
+  } STMT_END
+
+static void
+test_crypto_cgo_et_testvec(void *arg)
+{
+  (void)arg;
+  cgo_et_t et;
+  memset(&et, 0, sizeof(et));
+
+  for (int i = 0; i < (int)ARRAY_LENGTH(ET_TESTVECS); ++i) {
+    const struct et_testvec *tv = &ET_TESTVECS[i];
+    uint8_t keys[32];
+    uint8_t tweaks[16 + 1 + 493];
+    uint8_t block[16], expect[16];
+    UNHEX(keys, tv->keys);
+    UNHEX(tweaks, tv->tweaks);
+    UNHEX(block, tv->block);
+    UNHEX(expect, tv->expect);
+
+    et_tweak_t tweak = {
+      .uiv = {
+        .h = tweaks,
+        .cmd = tweaks[16],
+      },
+      .x_r = tweaks + 17,
+    };
+
+    cgo_et_init(&et, 128, tv->encrypt, keys);
+    if (tv->encrypt) {
+      cgo_et_encrypt(&et, tweak, block);
+    } else {
+      cgo_et_decrypt(&et, tweak, block);
+    }
+    cgo_et_clear(&et);
+
+    tt_mem_op(block, OP_EQ, expect, 16);
+  }
+
+ done:
+  cgo_et_clear(&et);
+}
+
+static void
+test_crypto_cgo_prf_testvec(void *arg)
+{
+  (void)arg;
+  cgo_prf_t prf;
+  memset(&prf, 0, sizeof(prf));
+
+  for (int i = 0; i < (int)ARRAY_LENGTH(PRF_TESTVECS); ++i) {
+    const struct prf_testvec *tv = &PRF_TESTVECS[i];
+    uint8_t keys[32];
+    uint8_t input[16];
+    uint8_t expect_t0[493];
+    uint8_t expect_t1[80];
+    uint8_t output[493]; // max
+    UNHEX(keys, tv->keys);
+    UNHEX(input, tv->input);
+
+    cgo_prf_init(&prf, 128, keys);
+    if (tv->t == 0) {
+      UNHEX(expect_t0, tv->expect);
+      memset(output, 0, sizeof(output));
+      cgo_prf_xor_t0(&prf, input, output);
+      tt_mem_op(output, OP_EQ, expect_t0, PRF_T0_DATA_LEN);
+    } else {
+      tt_int_op(tv->t, OP_EQ, 1);
+      UNHEX(expect_t1, tv->expect);
+      cgo_prf_gen_t1(&prf, input, output, sizeof(expect_t1));
+      tt_mem_op(output, OP_EQ, expect_t1, sizeof(expect_t1));
+    }
+    cgo_prf_clear(&prf);
+  }
+ done:
+  cgo_prf_clear(&prf);
+}
+
+static void
+test_crypto_cgo_uiv_testvec(void *arg)
+{
+  (void)arg;
+  cgo_uiv_t uiv;
+  memset(&uiv, 0, sizeof(uiv));
+
+  for (int i = 0; i < (int)ARRAY_LENGTH(UIV_TESTVECS); ++i) {
+    const struct uiv_testvec *tv = &UIV_TESTVECS[i];
+    uint8_t keys[64];
+    uint8_t tweaks[17];
+    uint8_t x_l[16], x_r[493];
+    uint8_t y_l[16], y_r[493];
+    uint8_t cell[509];
+    UNHEX(keys, tv->keys);
+    UNHEX(tweaks, tv->tweaks);
+    UNHEX(x_l, tv->x_l);
+    UNHEX(x_r, tv->x_r);
+    UNHEX(y_l, tv->y.y_l);
+    UNHEX(y_r, tv->y.y_r);
+
+    uiv_tweak_t tweak = {
+      .h = tweaks,
+      .cmd = tweaks[16]
+    };
+    memcpy(cell, x_l, 16);
+    memcpy(cell+16, x_r, 493);
+
+    cgo_uiv_init(&uiv, 128, tv->encrypt, keys);
+    if (tv->encrypt) {
+      cgo_uiv_encrypt(&uiv, tweak, cell);
+    } else {
+      cgo_uiv_decrypt(&uiv, tweak, cell);
+    }
+    cgo_uiv_clear(&uiv);
+
+    tt_mem_op(cell, OP_EQ, y_l, 16);
+    tt_mem_op(cell+16, OP_EQ, y_r, 493);
+  }
+
+ done:
+  cgo_uiv_clear(&uiv);
+}
+
+static void
+test_crypto_cgo_uiv_update_testvec(void *arg)
+{
+  (void)arg;
+  cgo_uiv_t uiv;
+  cgo_uiv_t uiv2;
+  memset(&uiv, 0, sizeof(uiv));
+  memset(&uiv2, 0, sizeof(uiv2));
+
+  uint8_t tw[16];
+  memset(tw, 42, sizeof(tw));
+  uiv_tweak_t tweak = {
+      .h = tw,
+      .cmd = 42
+  };
+
+  for (int i = 0; i < (int)ARRAY_LENGTH(UIV_UPDATE_TESTVECS); ++i) {
+    const struct uiv_update_testvec *tv = &UIV_UPDATE_TESTVECS[i];
+    uint8_t keys[64];
+    uint8_t nonce[16];
+    uint8_t new_keys[64];
+    uint8_t new_nonce[16];
+    UNHEX(keys, tv->keys);
+    UNHEX(nonce, tv->nonce);
+    UNHEX(new_keys, tv->output.new_keys);
+    UNHEX(new_nonce, tv->output.new_nonce);
+
+    cgo_uiv_init(&uiv, 128, true, keys);
+    cgo_uiv_update(&uiv, 128, true, nonce);
+    // Make sure that the recorded keys are what we expect.
+    tt_mem_op(uiv.uiv_keys_, OP_EQ, new_keys, 64);
+    tt_mem_op(nonce, OP_EQ, new_nonce, 16);
+
+    // Construct a new UIV from these keys and make sure it acts like this one.
+    uint8_t cell[509], cell2[509];
+    crypto_rand((char*)cell, sizeof(cell));
+    memcpy(cell2, cell, 509);
+    cgo_uiv_init(&uiv2, 128, true, uiv.uiv_keys_);
+    cgo_uiv_encrypt(&uiv, tweak, cell);
+    cgo_uiv_encrypt(&uiv2, tweak, cell2);
+    tt_mem_op(cell, OP_EQ, cell2, 509);
+
+    cgo_uiv_clear(&uiv);
+    cgo_uiv_clear(&uiv2);
+  }
+ done:
+  cgo_uiv_clear(&uiv);
+  cgo_uiv_clear(&uiv2);
+}
+
+struct testcase_t crypto_cgo_tests[] = {
+  { "et_roundtrip", test_crypto_cgo_et_roundtrip, 0, NULL, NULL },
+  { "et_testvec", test_crypto_cgo_et_testvec, 0, NULL, NULL },
+  { "prf_testvec", test_crypto_cgo_prf_testvec, 0, NULL, NULL },
+  { "uiv_roundtrip", test_crypto_cgo_uiv_roundtrip, 0, NULL, NULL },
+  { "uiv_testvec", test_crypto_cgo_uiv_testvec, 0, NULL, NULL },
+  { "uiv_update_testvec", test_crypto_cgo_uiv_update_testvec, 0, NULL, NULL },
+  END_OF_TESTCASES
+};