Add fixed key generation for Trust Token.

Certain applications of Trust Token need to be able to generate
a large number of keys, instead of storing them all, we provide
an API to take a secret that can be used to generate keys
in a deterministic manner.

Change-Id: I2b61958d1e949a3a47a3c91ab3a866c2e33a9d1d
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53011
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Steven Valdez <svaldez@google.com>
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
index 0aa1936..31ecc49 100644
--- a/crypto/trust_token/internal.h
+++ b/crypto/trust_token/internal.h
@@ -93,6 +93,9 @@
 // functions for |TRUST_TOKENS_experiment_v1|'s PMBTokens construction which
 // uses P-384.
 int pmbtoken_exp1_generate_key(CBB *out_private, CBB *out_public);
+int pmbtoken_exp1_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                         const uint8_t *secret,
+                                         size_t secret_len);
 int pmbtoken_exp1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len);
 int pmbtoken_exp1_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
@@ -118,6 +121,9 @@
 // functions for |TRUST_TOKENS_experiment_v2|'s PMBTokens construction which
 // uses P-384.
 int pmbtoken_exp2_generate_key(CBB *out_private, CBB *out_public);
+int pmbtoken_exp2_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                         const uint8_t *secret,
+                                         size_t secret_len);
 int pmbtoken_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len);
 int pmbtoken_exp2_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
@@ -153,6 +159,8 @@
 // functions for |TRUST_TOKENS_experiment_v2|'s VOPRF construction which uses
 // P-384.
 int voprf_exp2_generate_key(CBB *out_private, CBB *out_public);
+int voprf_exp2_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                      const uint8_t *secret, size_t secret_len);
 int voprf_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                      const uint8_t *in, size_t len);
 int voprf_exp2_issuer_key_from_bytes(TRUST_TOKEN_ISSUER_KEY *key,
@@ -179,6 +187,12 @@
   // zero on failure.
   int (*generate_key)(CBB *out_private, CBB *out_public);
 
+  // derive_key_from_secret deterministically derives a keypair based on
+  // |secret| and writes their serialized forms into |out_private| and
+  // |out_public|. It returns one on success and zero on failure.
+  int (*derive_key_from_secret)(CBB *out_private, CBB *out_public,
+                                  const uint8_t *secret, size_t secret_len);
+
   // client_key_from_bytes decodes a client key from |in| and sets |key|
   // to the resulting key. It returns one on success and zero
   // on failure.
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index a6549b9..68d8909 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -37,6 +37,8 @@
                              const uint8_t s[TRUST_TOKEN_NONCE_SIZE]);
 typedef int (*hash_c_func_t)(const EC_GROUP *group, EC_SCALAR *out,
                              uint8_t *buf, size_t len);
+typedef int (*hash_to_scalar_func_t)(const EC_GROUP *group, EC_SCALAR *out,
+                                     uint8_t *buf, size_t len);
 
 typedef struct {
   const EC_GROUP *group;
@@ -52,6 +54,9 @@
   // hash_c implements the H_c operation in PMBTokens. It returns one on success
   // and zero on error.
   hash_c_func_t hash_c;
+  // hash_to_scalar implements the HashToScalar operation for PMBTokens. It
+  // returns one on success and zero on error.
+  hash_to_scalar_func_t hash_to_scalar;
   int prefix_point : 1;
 } PMBTOKEN_METHOD;
 
@@ -60,7 +65,9 @@
 static int pmbtoken_init_method(PMBTOKEN_METHOD *method, int curve_nid,
                                 const uint8_t *h_bytes, size_t h_len,
                                 hash_t_func_t hash_t, hash_s_func_t hash_s,
-                                hash_c_func_t hash_c, int prefix_point) {
+                                hash_c_func_t hash_c,
+                                hash_to_scalar_func_t hash_to_scalar,
+                                int prefix_point) {
   method->group = EC_GROUP_new_by_curve_name(curve_nid);
   if (method->group == NULL) {
     return 0;
@@ -69,6 +76,7 @@
   method->hash_t = hash_t;
   method->hash_s = hash_s;
   method->hash_c = hash_c;
+  method->hash_to_scalar = hash_to_scalar;
   method->prefix_point = prefix_point;
 
   EC_AFFINE h;
@@ -85,21 +93,32 @@
   return 1;
 }
 
-// generate_keypair generates a keypair for the PMBTokens construction.
-// |out_x| and |out_y| are set to the secret half of the keypair, while
-// |*out_pub| is set to the public half of the keypair. It returns one on
-// success and zero on failure.
-static int generate_keypair(const PMBTOKEN_METHOD *method, EC_SCALAR *out_x,
-                            EC_SCALAR *out_y, EC_RAW_POINT *out_pub) {
-  if (!ec_random_nonzero_scalar(method->group, out_x, kDefaultAdditionalData) ||
-      !ec_random_nonzero_scalar(method->group, out_y, kDefaultAdditionalData) ||
-      !ec_point_mul_scalar_precomp(method->group, out_pub, &method->g_precomp,
-                                   out_x, &method->h_precomp, out_y, NULL,
-                                   NULL)) {
-    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
-    return 0;
+static int derive_scalar_from_secret(const PMBTOKEN_METHOD *method,
+                                     EC_SCALAR *out, const uint8_t *secret,
+                                     size_t secret_len, uint8_t scalar_id) {
+  static const uint8_t kKeygenLabel[] = "TrustTokenPMBTokenKeyGen";
+
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kKeygenLabel, sizeof(kKeygenLabel)) ||
+      !CBB_add_u8(&cbb, scalar_id) ||
+      !CBB_add_bytes(&cbb, secret, secret_len) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !method->hash_to_scalar(method->group, out, buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    goto err;
   }
-  return 1;
+
+  ok = 1;
+
+err:
+  CBB_cleanup(&cbb);
+  OPENSSL_free(buf);
+  return ok;
 }
 
 static int point_to_cbb(CBB *out, const EC_GROUP *group,
@@ -165,19 +184,24 @@
                                           scalars, 3);
 }
 
-static int pmbtoken_generate_key(const PMBTOKEN_METHOD *method,
-                                 CBB *out_private, CBB *out_public) {
+static int pmbtoken_compute_keys(const PMBTOKEN_METHOD *method,
+                                 CBB *out_private, CBB *out_public,
+                                 const EC_SCALAR *x0, const EC_SCALAR *y0,
+                                 const EC_SCALAR *x1, const EC_SCALAR *y1,
+                                 const EC_SCALAR *xs, const EC_SCALAR *ys) {
   const EC_GROUP *group = method->group;
   EC_RAW_POINT pub[3];
-  EC_SCALAR x0, y0, x1, y1, xs, ys;
-  if (!generate_keypair(method, &x0, &y0, &pub[0]) ||
-      !generate_keypair(method, &x1, &y1, &pub[1]) ||
-      !generate_keypair(method, &xs, &ys, &pub[2])) {
+  if (!ec_point_mul_scalar_precomp(group, &pub[0], &method->g_precomp,
+                                   x0, &method->h_precomp, y0, NULL, NULL) ||
+      !ec_point_mul_scalar_precomp(group, &pub[1], &method->g_precomp,
+                                   x1, &method->h_precomp, y1, NULL, NULL) ||
+      !ec_point_mul_scalar_precomp(method->group, &pub[2], &method->g_precomp,
+                                   xs, &method->h_precomp, ys, NULL, NULL)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
     return 0;
   }
 
-  const EC_SCALAR *scalars[] = {&x0, &y0, &x1, &y1, &xs, &ys};
+  const EC_SCALAR *scalars[] = {x0, y0, x1, y1, xs, ys};
   size_t scalar_len = BN_num_bytes(&group->order);
   for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(scalars); i++) {
     uint8_t *buf;
@@ -206,6 +230,42 @@
   return 1;
 }
 
+static int pmbtoken_generate_key(const PMBTOKEN_METHOD *method,
+                                 CBB *out_private, CBB *out_public) {
+  EC_SCALAR x0, y0, x1, y1, xs, ys;
+  if (!ec_random_nonzero_scalar(method->group, &x0, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(method->group, &y0, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(method->group, &x1, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(method->group, &y1, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(method->group, &xs, kDefaultAdditionalData) ||
+      !ec_random_nonzero_scalar(method->group, &ys, kDefaultAdditionalData)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    return 0;
+  }
+
+  return pmbtoken_compute_keys(method, out_private, out_public, &x0, &y0, &x1,
+                               &y1, &xs, &ys);
+}
+
+static int pmbtoken_derive_key_from_secret(const PMBTOKEN_METHOD *method,
+                                           CBB *out_private, CBB *out_public,
+                                           const uint8_t *secret,
+                                           size_t secret_len) {
+  EC_SCALAR x0, y0, x1, y1, xs, ys;
+  if (!derive_scalar_from_secret(method, &x0, secret, secret_len, 0) ||
+      !derive_scalar_from_secret(method, &y0, secret, secret_len, 1) ||
+      !derive_scalar_from_secret(method, &x1, secret, secret_len, 2) ||
+      !derive_scalar_from_secret(method, &y1, secret, secret_len, 3) ||
+      !derive_scalar_from_secret(method, &xs, secret, secret_len, 4) ||
+      !derive_scalar_from_secret(method, &ys, secret, secret_len, 5)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    return 0;
+  }
+
+  return pmbtoken_compute_keys(method, out_private, out_public, &x0, &y0, &x1,
+                               &y1, &xs, &ys);
+}
+
 static int pmbtoken_client_key_from_bytes(const PMBTOKEN_METHOD *method,
                                           TRUST_TOKEN_CLIENT_KEY *key,
                                           const uint8_t *in, size_t len) {
@@ -1140,6 +1200,13 @@
       group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
 }
 
+static int pmbtoken_exp1_hash_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                                        uint8_t *buf, size_t len) {
+  const uint8_t kHashLabel[] = "PMBTokens Experiment V1 HashToScalar";
+  return ec_hash_to_scalar_p384_xmd_sha512_draft07(
+      group, out, kHashLabel, sizeof(kHashLabel), buf, len);
+}
+
 static int pmbtoken_exp1_ok = 0;
 static PMBTOKEN_METHOD pmbtoken_exp1_method;
 static CRYPTO_once_t pmbtoken_exp1_method_once = CRYPTO_ONCE_INIT;
@@ -1159,10 +1226,10 @@
       0x87, 0xc3, 0x95, 0xd0, 0x13, 0xb7, 0x0b, 0x5c, 0xc7,
   };
 
-  pmbtoken_exp1_ok =
-      pmbtoken_init_method(&pmbtoken_exp1_method, NID_secp384r1, kH, sizeof(kH),
-                           pmbtoken_exp1_hash_t, pmbtoken_exp1_hash_s,
-                           pmbtoken_exp1_hash_c, 1);
+  pmbtoken_exp1_ok = pmbtoken_init_method(
+      &pmbtoken_exp1_method, NID_secp384r1, kH, sizeof(kH),
+      pmbtoken_exp1_hash_t, pmbtoken_exp1_hash_s, pmbtoken_exp1_hash_c,
+      pmbtoken_exp1_hash_to_scalar, 1);
 }
 
 static int pmbtoken_exp1_init_method(void) {
@@ -1182,6 +1249,17 @@
   return pmbtoken_generate_key(&pmbtoken_exp1_method, out_private, out_public);
 }
 
+int pmbtoken_exp1_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                         const uint8_t *secret,
+                                         size_t secret_len) {
+  if (!pmbtoken_exp1_init_method()) {
+    return 0;
+  }
+
+  return pmbtoken_derive_key_from_secret(&pmbtoken_exp1_method, out_private,
+                                         out_public, secret, secret_len);
+}
+
 int pmbtoken_exp1_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len) {
   if (!pmbtoken_exp1_init_method()) {
@@ -1290,6 +1368,13 @@
       group, out, kHashCLabel, sizeof(kHashCLabel), buf, len);
 }
 
+static int pmbtoken_exp2_hash_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                                        uint8_t *buf, size_t len) {
+  const uint8_t kHashLabel[] = "PMBTokens Experiment V2 HashToScalar";
+  return ec_hash_to_scalar_p384_xmd_sha512_draft07(
+      group, out, kHashLabel, sizeof(kHashLabel), buf, len);
+}
+
 static int pmbtoken_exp2_ok = 0;
 static PMBTOKEN_METHOD pmbtoken_exp2_method;
 static CRYPTO_once_t pmbtoken_exp2_method_once = CRYPTO_ONCE_INIT;
@@ -1309,10 +1394,10 @@
       0x25, 0x62, 0xbf, 0x59, 0xb2, 0xd2, 0x3d, 0x71, 0xff
   };
 
-  pmbtoken_exp2_ok =
-      pmbtoken_init_method(&pmbtoken_exp2_method, NID_secp384r1, kH, sizeof(kH),
-                           pmbtoken_exp2_hash_t, pmbtoken_exp2_hash_s,
-                           pmbtoken_exp2_hash_c, 0);
+  pmbtoken_exp2_ok = pmbtoken_init_method(
+      &pmbtoken_exp2_method, NID_secp384r1, kH, sizeof(kH),
+      pmbtoken_exp2_hash_t, pmbtoken_exp2_hash_s, pmbtoken_exp2_hash_c,
+      pmbtoken_exp2_hash_to_scalar, 0);
 }
 
 static int pmbtoken_exp2_init_method(void) {
@@ -1332,6 +1417,18 @@
   return pmbtoken_generate_key(&pmbtoken_exp2_method, out_private, out_public);
 }
 
+
+int pmbtoken_exp2_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                         const uint8_t *secret,
+                                         size_t secret_len) {
+  if (!pmbtoken_exp2_init_method()) {
+    return 0;
+  }
+
+  return pmbtoken_derive_key_from_secret(&pmbtoken_exp2_method, out_private,
+                                         out_public, secret, secret_len);
+}
+
 int pmbtoken_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                         const uint8_t *in, size_t len) {
   if (!pmbtoken_exp2_init_method()) {
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c
index 3334fba..5afb487 100644
--- a/crypto/trust_token/trust_token.c
+++ b/crypto/trust_token/trust_token.c
@@ -30,6 +30,7 @@
 const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v1(void) {
   static const TRUST_TOKEN_METHOD kMethod = {
       pmbtoken_exp1_generate_key,
+      pmbtoken_exp1_derive_key_from_secret,
       pmbtoken_exp1_client_key_from_bytes,
       pmbtoken_exp1_issuer_key_from_bytes,
       pmbtoken_exp1_blind,
@@ -46,6 +47,7 @@
 const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_voprf(void) {
   static const TRUST_TOKEN_METHOD kMethod = {
       voprf_exp2_generate_key,
+      voprf_exp2_derive_key_from_secret,
       voprf_exp2_client_key_from_bytes,
       voprf_exp2_issuer_key_from_bytes,
       voprf_exp2_blind,
@@ -62,6 +64,7 @@
 const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pmb(void) {
   static const TRUST_TOKEN_METHOD kMethod = {
       pmbtoken_exp2_generate_key,
+      pmbtoken_exp2_derive_key_from_secret,
       pmbtoken_exp2_client_key_from_bytes,
       pmbtoken_exp2_issuer_key_from_bytes,
       pmbtoken_exp2_blind,
@@ -140,6 +143,43 @@
   return ret;
 }
 
+int TRUST_TOKEN_derive_key_from_secret(
+    const TRUST_TOKEN_METHOD *method, uint8_t *out_priv_key,
+    size_t *out_priv_key_len, size_t max_priv_key_len, uint8_t *out_pub_key,
+    size_t *out_pub_key_len, size_t max_pub_key_len, uint32_t id,
+    const uint8_t *secret, size_t secret_len) {
+  // Prepend the key ID in front of the PMBTokens format.
+  int ret = 0;
+  CBB priv_cbb, pub_cbb;
+  CBB_zero(&priv_cbb);
+  CBB_zero(&pub_cbb);
+  if (!CBB_init_fixed(&priv_cbb, out_priv_key, max_priv_key_len) ||
+      !CBB_init_fixed(&pub_cbb, out_pub_key, max_pub_key_len) ||
+      !CBB_add_u32(&priv_cbb, id) ||
+      !CBB_add_u32(&pub_cbb, id)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    goto err;
+  }
+
+  if (!method->derive_key_from_secret(&priv_cbb, &pub_cbb, secret,
+                                        secret_len)) {
+    goto err;
+  }
+
+  if (!CBB_finish(&priv_cbb, NULL, out_priv_key_len) ||
+      !CBB_finish(&pub_cbb, NULL, out_pub_key_len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  CBB_cleanup(&priv_cbb);
+  CBB_cleanup(&pub_cbb);
+  return ret;
+}
+
 TRUST_TOKEN_CLIENT *TRUST_TOKEN_CLIENT_new(const TRUST_TOKEN_METHOD *method,
                                            size_t max_batchsize) {
   if (max_batchsize > 0xffff) {
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index 72d555b..5ab80cd 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -54,6 +54,73 @@
       TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
   ASSERT_EQ(292u, priv_key_len);
   ASSERT_EQ(301u, pub_key_len);
+
+  const uint8_t kKeygenSecret[] = "SEED";
+  ASSERT_TRUE(TRUST_TOKEN_derive_key_from_secret(
+      TRUST_TOKEN_experiment_v1(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001, kKeygenSecret,
+      sizeof(kKeygenSecret) - 1));
+
+  const uint8_t kExpectedPriv[] = {
+      0x00, 0x00, 0x00, 0x01, 0x98, 0xaa, 0x32, 0xfc, 0x5f, 0x83, 0x35, 0xea,
+      0x57, 0x4f, 0x9e, 0x61, 0x48, 0x6e, 0x89, 0x9d, 0x3d, 0xaa, 0x38, 0x5d,
+      0xd0, 0x06, 0x96, 0x62, 0xe8, 0x0b, 0xd6, 0x5f, 0x12, 0xa4, 0xcc, 0xa9,
+      0xb5, 0x20, 0x1b, 0x13, 0x8c, 0x1c, 0xaf, 0x36, 0x1b, 0xab, 0x0c, 0xc6,
+      0xac, 0x38, 0xae, 0x96, 0x3d, 0x14, 0x9d, 0xb8, 0x8d, 0xf4, 0x7f, 0xe2,
+      0x7d, 0xeb, 0x17, 0xc2, 0xbc, 0x63, 0x42, 0x93, 0x94, 0xe4, 0x97, 0xbf,
+      0x97, 0xea, 0x02, 0x40, 0xac, 0xb6, 0xa5, 0x03, 0x4c, 0x6b, 0x4c, 0xb8,
+      0x8c, 0xf4, 0x66, 0x1b, 0x4e, 0x02, 0x45, 0xf9, 0xcd, 0xb6, 0x0f, 0x59,
+      0x09, 0x21, 0x03, 0x7e, 0x92, 0x1f, 0x3f, 0x40, 0x83, 0x50, 0xe3, 0xdc,
+      0x9e, 0x6f, 0x65, 0xc5, 0xbd, 0x2c, 0x7d, 0xab, 0x74, 0x49, 0xc8, 0xa2,
+      0x3c, 0xab, 0xcb, 0x4d, 0x63, 0x73, 0x81, 0x2b, 0xb2, 0x1e, 0x00, 0x8f,
+      0x00, 0xb8, 0xd8, 0xb4, 0x5d, 0xc4, 0x3f, 0x3d, 0xa8, 0x4f, 0x4c, 0x72,
+      0x0e, 0x20, 0x17, 0x4b, 0xac, 0x14, 0x8f, 0xb2, 0xa5, 0x20, 0x41, 0x2b,
+      0xf7, 0x62, 0x25, 0x6a, 0xd6, 0x41, 0x26, 0x62, 0x10, 0xc1, 0xbc, 0x42,
+      0xac, 0x54, 0x1b, 0x75, 0x05, 0xd6, 0x53, 0xb1, 0x7b, 0x84, 0x6a, 0x7b,
+      0x5b, 0x2a, 0x34, 0x6e, 0x43, 0x4b, 0x43, 0xcc, 0x6c, 0xdb, 0x1d, 0x02,
+      0x34, 0x7f, 0xd1, 0xe8, 0xfd, 0x42, 0x2c, 0xd9, 0x14, 0xdb, 0xd6, 0xf4,
+      0xad, 0xb5, 0xe4, 0xac, 0xdd, 0x7e, 0xb5, 0x4c, 0x3f, 0x59, 0x24, 0xfa,
+      0x04, 0xd9, 0xb6, 0xd2, 0xb7, 0x7d, 0xf1, 0xfa, 0x13, 0xc0, 0x4d, 0xd5,
+      0xca, 0x3a, 0x4e, 0xa8, 0xdd, 0xa9, 0xfc, 0xcb, 0x06, 0xb2, 0xde, 0x4b,
+      0x2a, 0x86, 0xbb, 0x0d, 0x41, 0xb6, 0x3d, 0xfb, 0x49, 0xc8, 0xdf, 0x9a,
+      0x48, 0xe5, 0x68, 0x8a, 0xfc, 0x86, 0x9c, 0x79, 0x5a, 0x79, 0xc1, 0x09,
+      0x33, 0x53, 0xdc, 0x3d, 0xe9, 0x93, 0x7c, 0x5b, 0x72, 0xf7, 0xa0, 0x8a,
+      0x1f, 0x07, 0x6c, 0x38, 0x3c, 0x99, 0x0b, 0xe4, 0x4e, 0xa4, 0xbd, 0x41,
+      0x1f, 0x83, 0xa6, 0xd3
+  };
+  ASSERT_EQ(Bytes(kExpectedPriv, sizeof(kExpectedPriv)),
+            Bytes(priv_key, priv_key_len));
+
+  const uint8_t kExpectedPub[] = {
+      0x00, 0x00, 0x00, 0x01, 0x00, 0x61, 0x04, 0x5e, 0x06, 0x6b, 0x7b, 0xfd,
+      0x54, 0x01, 0xe0, 0xd2, 0xb5, 0x12, 0xce, 0x48, 0x16, 0x66, 0xb2, 0xdf,
+      0xfd, 0xa8, 0x38, 0x7c, 0x1f, 0x45, 0x1a, 0xb8, 0x21, 0x52, 0x17, 0x25,
+      0xbb, 0x0b, 0x00, 0xd4, 0xa1, 0xbc, 0x28, 0xd9, 0x08, 0x36, 0x98, 0xb2,
+      0x17, 0xd3, 0xb5, 0xad, 0xb6, 0x4e, 0x03, 0x5f, 0xd3, 0x66, 0x2c, 0x58,
+      0x1c, 0xcc, 0xc6, 0x23, 0xa4, 0xf9, 0xa2, 0x7e, 0xb0, 0xe4, 0xd3, 0x95,
+      0x41, 0x6f, 0xba, 0x23, 0x4a, 0x82, 0x93, 0x29, 0x73, 0x75, 0x38, 0x85,
+      0x64, 0x9c, 0xaa, 0x12, 0x6d, 0x7d, 0xcd, 0x52, 0x02, 0x91, 0x9f, 0xa9,
+      0xee, 0x4b, 0xfd, 0x68, 0x97, 0x40, 0xdc, 0x00, 0x61, 0x04, 0x14, 0x16,
+      0x39, 0xf9, 0x63, 0x66, 0x94, 0x03, 0xfa, 0x0b, 0xbf, 0xca, 0x5a, 0x39,
+      0x9f, 0x27, 0x5b, 0x3f, 0x69, 0x7a, 0xc9, 0xf7, 0x25, 0x7c, 0x84, 0x9e,
+      0x1d, 0x61, 0x5a, 0x24, 0x53, 0xf2, 0x4a, 0x9d, 0xe9, 0x05, 0x53, 0xfd,
+      0x12, 0x01, 0x2d, 0x9a, 0x69, 0x50, 0x74, 0x82, 0xa3, 0x45, 0x73, 0xdc,
+      0x34, 0x36, 0x31, 0x44, 0x07, 0x0c, 0xda, 0x13, 0xbe, 0x94, 0x37, 0x65,
+      0xa0, 0xab, 0x16, 0x52, 0x90, 0xe5, 0x8a, 0x03, 0xe5, 0x98, 0x79, 0x14,
+      0x79, 0xd5, 0x17, 0xee, 0xd4, 0xb8, 0xda, 0x77, 0x76, 0x03, 0x20, 0x2a,
+      0x7e, 0x3b, 0x76, 0x0b, 0x23, 0xb7, 0x72, 0x77, 0xb2, 0xeb, 0x00, 0x61,
+      0x04, 0x68, 0x18, 0x4d, 0x23, 0x23, 0xf4, 0x45, 0xb8, 0x81, 0x0d, 0xa4,
+      0x5d, 0x0b, 0x9e, 0x08, 0xfb, 0x45, 0xfb, 0x96, 0x29, 0x43, 0x2f, 0xab,
+      0x93, 0x04, 0x4c, 0x04, 0xb6, 0x5e, 0x27, 0xf5, 0x39, 0x66, 0x94, 0x15,
+      0x1d, 0xb1, 0x1c, 0x7c, 0x27, 0x6f, 0xa5, 0x19, 0x0c, 0x30, 0x12, 0xcc,
+      0x77, 0x7f, 0x10, 0xa9, 0x7c, 0xe4, 0x08, 0x77, 0x3c, 0xd3, 0x6f, 0xa4,
+      0xf4, 0xaf, 0xf1, 0x9d, 0x14, 0x1d, 0xd0, 0x02, 0x33, 0x50, 0x55, 0x00,
+      0x6a, 0x47, 0x96, 0xe1, 0x8b, 0x4e, 0x44, 0x41, 0xad, 0xb3, 0xea, 0x0d,
+      0x0d, 0xd5, 0x73, 0x8e, 0x62, 0x67, 0x8a, 0xb4, 0xe7, 0x5d, 0x17, 0xa9,
+      0x24};
+  ASSERT_EQ(Bytes(kExpectedPub, sizeof(kExpectedPub)),
+            Bytes(pub_key, pub_key_len));
 }
 
 TEST(TrustTokenTest, KeyGenExp2VOPRF) {
@@ -66,6 +133,37 @@
       TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
   ASSERT_EQ(52u, priv_key_len);
   ASSERT_EQ(101u, pub_key_len);
+
+  const uint8_t kKeygenSecret[] = "SEED";
+  ASSERT_TRUE(TRUST_TOKEN_derive_key_from_secret(
+      TRUST_TOKEN_experiment_v2_voprf(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001, kKeygenSecret,
+      sizeof(kKeygenSecret) - 1));
+
+  const uint8_t kExpectedPriv[] = {
+      0x00, 0x00, 0x00, 0x01, 0x0b, 0xe2, 0xc4, 0x73, 0x92, 0xe7, 0xf8,
+      0x3e, 0xba, 0xab, 0x85, 0xa7, 0x77, 0xd7, 0x0a, 0x02, 0xc5, 0x36,
+      0xfe, 0x62, 0xa3, 0xca, 0x01, 0x75, 0xc7, 0x62, 0x19, 0xc7, 0xf0,
+      0x30, 0xc5, 0x14, 0x60, 0x13, 0x97, 0x4f, 0x63, 0x05, 0x37, 0x92,
+      0x7b, 0x76, 0x8e, 0x9f, 0xd0, 0x1a, 0x74, 0x44
+  };
+  ASSERT_EQ(Bytes(kExpectedPriv, sizeof(kExpectedPriv)),
+            Bytes(priv_key, priv_key_len));
+
+  const uint8_t kExpectedPub[] = {
+      0x00, 0x00, 0x00, 0x01, 0x04, 0x2c, 0x9c, 0x11, 0xc1, 0xe5, 0x52, 0x59,
+      0x0b, 0x6d, 0x88, 0x8b, 0x6e, 0x28, 0xe8, 0xc5, 0xa3, 0xbe, 0x48, 0x18,
+      0xf7, 0x1d, 0x31, 0xcf, 0xa2, 0x6e, 0x2a, 0xd6, 0xcb, 0x83, 0x26, 0x04,
+      0xbd, 0x93, 0x67, 0xe4, 0x53, 0xf6, 0x11, 0x7d, 0x45, 0xe9, 0xfe, 0x27,
+      0x33, 0x90, 0xdb, 0x1b, 0xfc, 0x9b, 0x31, 0x4d, 0x39, 0x1f, 0x1f, 0x8c,
+      0x43, 0x06, 0x70, 0x2c, 0x84, 0xdc, 0x23, 0x18, 0xc7, 0x6a, 0x58, 0xcf,
+      0x9e, 0xc1, 0xfa, 0xf2, 0x30, 0xdd, 0xad, 0x62, 0x24, 0xde, 0x11, 0xc1,
+      0xba, 0x8d, 0xc3, 0x4f, 0xfb, 0xe5, 0xa5, 0xd4, 0x37, 0xba, 0x3b, 0x70,
+      0xc0, 0xc3, 0xef, 0x20, 0x43
+  };
+  ASSERT_EQ(Bytes(kExpectedPub, sizeof(kExpectedPub)),
+            Bytes(pub_key, pub_key_len));
 }
 
 TEST(TrustTokenTest, KeyGenExp2PMB) {
@@ -78,6 +176,73 @@
       TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001));
   ASSERT_EQ(292u, priv_key_len);
   ASSERT_EQ(295u, pub_key_len);
+
+  const uint8_t kKeygenSecret[] = "SEED";
+  ASSERT_TRUE(TRUST_TOKEN_derive_key_from_secret(
+      TRUST_TOKEN_experiment_v2_pmb(), priv_key, &priv_key_len,
+      TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE, pub_key, &pub_key_len,
+      TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE, 0x0001, kKeygenSecret,
+      sizeof(kKeygenSecret) - 1));
+
+  const uint8_t kExpectedPriv[] = {
+      0x00, 0x00, 0x00, 0x01, 0x1b, 0x74, 0xdc, 0xf0, 0xa9, 0xa7, 0x6c, 0xfb,
+      0x41, 0xef, 0xfa, 0x65, 0x52, 0xc9, 0x86, 0x4e, 0xfb, 0x16, 0x9d, 0xea,
+      0x62, 0x3f, 0x47, 0xab, 0x1f, 0x1b, 0x05, 0xf2, 0x4f, 0x05, 0xfe, 0x64,
+      0xb7, 0xe8, 0xcd, 0x2a, 0x10, 0xfa, 0xa2, 0x48, 0x3f, 0x0e, 0x8b, 0x94,
+      0x39, 0xf1, 0xe7, 0x53, 0xe9, 0x50, 0x29, 0xe2, 0xb7, 0x0e, 0xc0, 0x94,
+      0xa9, 0xd3, 0xef, 0x64, 0x10, 0x1d, 0x08, 0xd0, 0x60, 0xcb, 0x6d, 0x97,
+      0x68, 0xc7, 0x04, 0x92, 0x07, 0xb2, 0x22, 0x83, 0xf7, 0xd9, 0x9b, 0x2c,
+      0xf2, 0x52, 0x34, 0x0c, 0x42, 0x31, 0x47, 0x41, 0x19, 0xb9, 0xee, 0xfc,
+      0x46, 0xbd, 0x14, 0xce, 0x42, 0xd7, 0x43, 0xc8, 0x32, 0x3b, 0x24, 0xed,
+      0xdc, 0x69, 0xa3, 0x8e, 0x29, 0x01, 0xbe, 0xae, 0x24, 0x39, 0x14, 0xa7,
+      0x52, 0xe5, 0xd5, 0xff, 0x9a, 0xc4, 0x15, 0x79, 0x29, 0x4c, 0x9b, 0x4e,
+      0xfc, 0x61, 0xf2, 0x12, 0x6f, 0x4f, 0xd3, 0x96, 0x28, 0xb0, 0x79, 0xf0,
+      0x4e, 0x6e, 0x7d, 0x56, 0x19, 0x1b, 0xc2, 0xd7, 0xf9, 0x3a, 0x58, 0x06,
+      0xe5, 0xec, 0xa4, 0x33, 0x14, 0x1c, 0x78, 0x0c, 0x83, 0x94, 0x34, 0x22,
+      0x5a, 0x8e, 0x2e, 0xa1, 0x72, 0x4a, 0x03, 0x35, 0xfe, 0x46, 0x92, 0x41,
+      0x6b, 0xe6, 0x4b, 0x3f, 0xf0, 0xe7, 0x0b, 0xb5, 0xf3, 0x66, 0x6c, 0xc6,
+      0x14, 0xcf, 0xce, 0x32, 0x0a, 0x2c, 0x28, 0xba, 0x4e, 0xb9, 0x75, 0x4a,
+      0xa9, 0x2d, 0xb0, 0x8c, 0xd0, 0x62, 0x52, 0x29, 0x1f, 0x12, 0xfd, 0xfb,
+      0xd3, 0x2a, 0x36, 0x0f, 0x89, 0x32, 0x86, 0x25, 0x56, 0xb9, 0xe7, 0x3c,
+      0xeb, 0xb4, 0x84, 0x41, 0x2b, 0xa8, 0xf3, 0xa5, 0x3d, 0xfe, 0x56, 0x94,
+      0x5b, 0x74, 0xb3, 0x5b, 0x27, 0x3f, 0xe7, 0xcf, 0xe4, 0xf8, 0x15, 0x95,
+      0x2a, 0xd2, 0x5f, 0x92, 0xb4, 0x6a, 0x89, 0xa5, 0x54, 0xbd, 0x27, 0x5e,
+      0xeb, 0x43, 0x07, 0x9b, 0x2b, 0x8b, 0x22, 0x59, 0x13, 0x4b, 0x9c, 0x56,
+      0xd8, 0x63, 0xd9, 0xe6, 0x85, 0x15, 0x2c, 0x82, 0x52, 0x40, 0x8f, 0xb1,
+      0xe7, 0x56, 0x07, 0x98
+  };
+  ASSERT_EQ(Bytes(kExpectedPriv, sizeof(kExpectedPriv)),
+            Bytes(priv_key, priv_key_len));
+
+  const uint8_t kExpectedPub[] = {
+      0x00, 0x00, 0x00, 0x01, 0x04, 0x48, 0xb1, 0x2d, 0xdd, 0x03, 0x32, 0xeb,
+      0x93, 0x31, 0x3d, 0x59, 0x74, 0xf0, 0xcf, 0xaa, 0xa5, 0x39, 0x5f, 0x53,
+      0xc4, 0x94, 0x98, 0xbe, 0x8f, 0x22, 0xd7, 0x30, 0xde, 0x1e, 0xb4, 0xf3,
+      0x32, 0x23, 0x90, 0x0b, 0xa6, 0x37, 0x4a, 0x4b, 0x44, 0xb3, 0x26, 0x52,
+      0x93, 0x7b, 0x4b, 0xa4, 0x79, 0xe8, 0x77, 0x6a, 0x19, 0x81, 0x2a, 0xdd,
+      0x91, 0xfb, 0x90, 0x8b, 0x24, 0xb5, 0xbe, 0x20, 0x2e, 0xe8, 0xbc, 0xd3,
+      0x83, 0x6c, 0xa8, 0xc5, 0xa1, 0x9a, 0x5b, 0x5e, 0x60, 0xda, 0x45, 0x2e,
+      0x31, 0x7f, 0x54, 0x0e, 0x14, 0x40, 0xd2, 0x4d, 0x40, 0x2e, 0x21, 0x79,
+      0xfc, 0x77, 0xdd, 0xc7, 0x2d, 0x04, 0xfe, 0xc6, 0xe3, 0xcf, 0x99, 0xef,
+      0x88, 0xab, 0x76, 0x86, 0x16, 0x14, 0xed, 0x72, 0x35, 0xa7, 0x05, 0x13,
+      0x9f, 0x2c, 0x53, 0xd5, 0xdf, 0x66, 0x75, 0x2e, 0x68, 0xdc, 0xd4, 0xc4,
+      0x00, 0x36, 0x08, 0x6d, 0xb7, 0x15, 0xf7, 0xe5, 0x32, 0x59, 0x81, 0x16,
+      0x57, 0xaa, 0x72, 0x06, 0xf0, 0xad, 0xd1, 0x85, 0xa0, 0x04, 0xd4, 0x11,
+      0x95, 0x1d, 0xac, 0x0b, 0x25, 0xbe, 0x59, 0xa2, 0xb3, 0x30, 0xee, 0x97,
+      0x07, 0x2a, 0x51, 0x15, 0xc1, 0x8d, 0xa8, 0xa6, 0x57, 0x9a, 0x4e, 0xbf,
+      0xd7, 0x2d, 0x35, 0x07, 0x6b, 0xd6, 0xc9, 0x3c, 0xe4, 0xcf, 0x0b, 0x14,
+      0x3e, 0x10, 0x51, 0x77, 0xd6, 0x84, 0x04, 0xbe, 0xd1, 0xd5, 0xa8, 0xf3,
+      0x9d, 0x1d, 0x4f, 0xc1, 0xc9, 0xf1, 0x0c, 0x6d, 0xb6, 0xcb, 0xe2, 0x05,
+      0x0b, 0x9c, 0x7a, 0x3a, 0x9a, 0x99, 0xe9, 0xa1, 0x93, 0xdc, 0x72, 0x2e,
+      0xef, 0xf3, 0x8d, 0xb9, 0x7b, 0xb0, 0x19, 0x24, 0x95, 0x0d, 0x68, 0xa7,
+      0xe0, 0xaa, 0x0b, 0xb1, 0xd1, 0xcc, 0x52, 0x14, 0xf9, 0x6c, 0x91, 0x59,
+      0xe4, 0xe1, 0x9b, 0xf9, 0x12, 0x39, 0xb1, 0x79, 0xbb, 0x21, 0x92, 0x00,
+      0xa4, 0x89, 0xf5, 0xbd, 0xd7, 0x89, 0x27, 0x40, 0xdc, 0xb1, 0x09, 0x38,
+      0x63, 0x91, 0x8c, 0xa5, 0x27, 0x27, 0x97, 0x39, 0x35, 0xfa, 0x1a, 0x8a,
+      0xa7, 0xe5, 0xc4, 0xd8, 0xbf, 0xe7, 0xbe
+  };
+  ASSERT_EQ(Bytes(kExpectedPub, sizeof(kExpectedPub)),
+            Bytes(pub_key, pub_key_len));
 }
 
 // Test that H in |TRUST_TOKEN_experiment_v1| was computed correctly.
diff --git a/crypto/trust_token/voprf.c b/crypto/trust_token/voprf.c
index f93ee9c..cedee1e 100644
--- a/crypto/trust_token/voprf.c
+++ b/crypto/trust_token/voprf.c
@@ -110,20 +110,18 @@
   return 1;
 }
 
-static int voprf_generate_key(const VOPRF_METHOD *method, CBB *out_private,
-                              CBB *out_public) {
+static int voprf_calculate_key(const VOPRF_METHOD *method, CBB *out_private,
+                               CBB *out_public, const EC_SCALAR *priv) {
   const EC_GROUP *group = method->group;
   EC_RAW_POINT pub;
-  EC_SCALAR priv;
   EC_AFFINE pub_affine;
-  if (!ec_random_nonzero_scalar(group, &priv, kDefaultAdditionalData) ||
-      !ec_point_mul_scalar_base(group, &pub, &priv) ||
+  if (!ec_point_mul_scalar_base(group, &pub, priv) ||
       !ec_jacobian_to_affine(group, &pub_affine, &pub)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
     return 0;
   }
 
-  if (!scalar_to_cbb(out_private, group, &priv) ||
+  if (!scalar_to_cbb(out_private, group, priv) ||
       !cbb_add_point(out_public, group, &pub_affine)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BUFFER_TOO_SMALL);
     return 0;
@@ -132,6 +130,46 @@
   return 1;
 }
 
+
+static int voprf_generate_key(const VOPRF_METHOD *method, CBB *out_private,
+                              CBB *out_public) {
+  EC_SCALAR priv;
+  if (!ec_random_nonzero_scalar(method->group, &priv, kDefaultAdditionalData)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    return 0;
+  }
+  return voprf_calculate_key(method, out_private, out_public, &priv);
+}
+
+static int voprf_derive_key_from_secret(const VOPRF_METHOD *method,
+                                        CBB *out_private, CBB *out_public,
+                                        const uint8_t *secret,
+                                        size_t secret_len) {
+  static const uint8_t kKeygenLabel[] = "TrustTokenVOPRFKeyGen";
+
+  EC_SCALAR priv;
+  int ok = 0;
+  CBB cbb;
+  CBB_zero(&cbb);
+  uint8_t *buf = NULL;
+  size_t len;
+  if (!CBB_init(&cbb, 0) ||
+      !CBB_add_bytes(&cbb, kKeygenLabel, sizeof(kKeygenLabel)) ||
+      !CBB_add_bytes(&cbb, secret, secret_len) ||
+      !CBB_finish(&cbb, &buf, &len) ||
+      !method->hash_to_scalar(method->group, &priv, buf, len)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_KEYGEN_FAILURE);
+    goto err;
+  }
+
+  ok = voprf_calculate_key(method, out_private, out_public, &priv);
+
+err:
+  CBB_cleanup(&cbb);
+  OPENSSL_free(buf);
+  return ok;
+}
+
 static int voprf_client_key_from_bytes(const VOPRF_METHOD *method,
                                        TRUST_TOKEN_CLIENT_KEY *key,
                                        const uint8_t *in, size_t len) {
@@ -711,6 +749,17 @@
   return voprf_generate_key(&voprf_exp2_method, out_private, out_public);
 }
 
+int voprf_exp2_derive_key_from_secret(CBB *out_private, CBB *out_public,
+                                      const uint8_t *secret,
+                                      size_t secret_len) {
+  if (!voprf_exp2_init_method()) {
+    return 0;
+  }
+
+  return voprf_derive_key_from_secret(&voprf_exp2_method, out_private,
+                                      out_public, secret, secret_len);
+}
+
 int voprf_exp2_client_key_from_bytes(TRUST_TOKEN_CLIENT_KEY *key,
                                      const uint8_t *in, size_t len) {
   if (!voprf_exp2_init_method()) {
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index d9247f7..745a860 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -78,15 +78,30 @@
 // to ensure success, these should be at least
 // |TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE| and |TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE|.
 //
-// WARNING: This API is unstable and the serializations of these keys are
-// subject to change. Keys generated with this function may not be persisted.
-//
 // This function returns one on success or zero on error.
 OPENSSL_EXPORT int TRUST_TOKEN_generate_key(
     const TRUST_TOKEN_METHOD *method, uint8_t *out_priv_key,
     size_t *out_priv_key_len, size_t max_priv_key_len, uint8_t *out_pub_key,
     size_t *out_pub_key_len, size_t max_pub_key_len, uint32_t id);
 
+// TRUST_TOKEN_derive_key_from_secret deterministically derives a new Trust
+// Token keypair labeled with |id| from an input |secret| and serializes the
+// private and public keys, writing the private key to |out_priv_key| and
+// setting |*out_priv_key_len| to the number of bytes written, and writing the
+// public key to |out_pub_key| and setting |*out_pub_key_len| to the number of
+// bytes written.
+//
+// At most |max_priv_key_len| and |max_pub_key_len| bytes are written. In order
+// to ensure success, these should be at least
+// |TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE| and |TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE|.
+//
+// This function returns one on success or zero on error.
+OPENSSL_EXPORT int TRUST_TOKEN_derive_key_from_secret(
+    const TRUST_TOKEN_METHOD *method, uint8_t *out_priv_key,
+    size_t *out_priv_key_len, size_t max_priv_key_len, uint8_t *out_pub_key,
+    size_t *out_pub_key_len, size_t max_pub_key_len, uint32_t id,
+    const uint8_t *secret, size_t secret_len);
+
 
 // Trust Token client implementation.
 //