
function define_tests() {
  // May want to test prefixed implementations.
  var subtle = self.crypto.subtle;

  // Verify the derive functions perform checks against the all-zero value results,
  // ensuring small-order points are rejected.
  // https://www.rfc-editor.org/rfc/rfc7748#section-6.1
  // TODO: The spec states that the check must be done on use, but there is discussion about doing it on import.
  // https://github.com/WICG/webcrypto-secure-curves/pull/13
  Object.keys(kSmallOrderPoint).forEach(function(algorithmName) {
      kSmallOrderPoint[algorithmName].forEach(function(test) {
          promise_test(async() => {
              let derived;
              let privateKey;
              let publicKey;
              try {
                  privateKey = await subtle.importKey("pkcs8", pkcs8[algorithmName],
                                                  {name: algorithmName},
                                                  false, ["deriveBits", "deriveKey"]);
                  publicKey = await subtle.importKey("spki", test.vector,
                                                 {name: algorithmName},
                                                 false, [])
                  derived = await subtle.deriveKey({name: algorithmName, public: publicKey}, privateKey,
                                                   {name: "HMAC", hash: "SHA-256", length: 256}, true,
                                                   ["sign", "verify"]);
              } catch (err) {
                  assert_false(privateKey === undefined, "Private key should be valid.");
                  assert_false(publicKey === undefined, "Public key should be valid.");
                  assert_equals(err.name, "OperationError", "Should throw correct error, not " + err.name + ": " + err.message + ".");
              }
              assert_equals(derived, undefined, "Operation succeeded, but should not have.");
          }, algorithmName + " deriveBits checks for all-zero value result with a key of order " + test.order);
      });
  });

  // Ensure the keys generated by each algorithm are valid for key derivation.
  Object.keys(sizes).forEach(function(algorithmName) {
      promise_test(async() => {
          let derived;
          try {
              let key = await subtle.generateKey({name: algorithmName}, true, ["deriveKey", "deriveBits"]);
              derived = await subtle.deriveKey({name: algorithmName, public: key.publicKey}, key.privateKey, {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"]);
          } catch (err) {
              assert_unreached("Threw an unexpected error: " + err.toString() + " -");
          }
          assert_false (derived === undefined, "Key derivation failed.");
      }, "Key derivation using a " + algorithmName + " generated keys.");
  });

  return importKeys(pkcs8, spki, sizes)
  .then(function(results) {
      publicKeys = results.publicKeys;
      privateKeys = results.privateKeys;
      noDeriveKeyKeys = results.noDeriveKeyKeys;

      Object.keys(sizes).forEach(function(algorithmName) {
          // Basic success case
          promise_test(function(test) {
              return subtle.deriveKey({name: algorithmName, public: publicKeys[algorithmName]}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_true(equalBuffers(exportedKey, derivations[algorithmName], 8 * exportedKey.length), "Derived correct key");
              }, function(err) {
                  assert_unreached("deriveKey failed with error " + err.name + ": " + err.message);
              });
          }, algorithmName + " good parameters");

          // Case insensitivity check
          promise_test(function(test) {
              return subtle.deriveKey({name: algorithmName.toLowerCase(), public: publicKeys[algorithmName]}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_true(equalBuffers(exportedKey, derivations[algorithmName], 8 * exportedKey.length), "Derived correct key");
              }, function(err) {
                  assert_unreached("deriveKey failed with error " + err.name + ": " + err.message);
              });
          }, algorithmName + " mixed case parameters");
          // Errors to test:

          // - missing public property TypeError
          promise_test(function(test) {
              return subtle.deriveKey({name: algorithmName}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_unreached("deriveKey succeeded but should have failed with TypeError");
              }, function(err) {
                  assert_equals(err.name, "TypeError", "Should throw correct error, not " + err.name + ": " + err.message);
              });
          }, algorithmName + " missing public property");

          // - Non CryptoKey public property TypeError
          promise_test(function(test) {
              return subtle.deriveKey({name: algorithmName, public: {message: "Not a CryptoKey"}}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_unreached("deriveKey succeeded but should have failed with TypeError");
              }, function(err) {
                  assert_equals(err.name, "TypeError", "Should throw correct error, not " + err.name + ": " + err.message);
              });
          }, algorithmName + " public property of algorithm is not a CryptoKey");

          // - wrong algorithm
          promise_test(function(test) {
              publicKey = publicKeys["X25519"];
              if (algorithmName === "X25519") {
                  publicKey = publicKeys["X448"];
              }
              return subtle.deriveKey({name: algorithmName, public: publicKey}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError");
              }, function(err) {
                  assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message);
              });
          }, algorithmName + " mismatched algorithms");

          // - No deriveKey usage in baseKey InvalidAccessError
          promise_test(function(test) {
              return subtle.deriveKey({name: algorithmName, public: publicKeys[algorithmName]}, noDeriveKeyKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError");
              }, function(err) {
                  assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message);
              });
          }, algorithmName + " no deriveKey usage for base key");

          // - Use public key for baseKey InvalidAccessError
          promise_test(function(test) {
              return subtle.deriveKey({name: algorithmName, public: publicKeys[algorithmName]}, publicKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError");
              }, function(err) {
                  assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message);
              });
          }, algorithmName + " base key is not a private key");

          // - Use private key for public property InvalidAccessError
          promise_test(function(test) {
              return subtle.deriveKey({name: algorithmName, public: privateKeys[algorithmName]}, privateKeys[algorithmName], {name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(key) {return crypto.subtle.exportKey("raw", key);})
              .then(function(exportedKey) {
                  assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError");
              }, function(err) {
                  assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message);
              });
          }, algorithmName + " public property value is a private key");

          // - Use secret key for public property InvalidAccessError
          promise_test(function(test) {
              return subtle.generateKey({name: "HMAC", hash: "SHA-256", length: 256}, true, ["sign", "verify"])
              .then(function(secretKey) {
                  return subtle.deriveKey({name: algorithmName, public: secretKey}, privateKeys[algorithmName], {name: "AES-CBC", length: 256}, true, ["sign", "verify"])
                  .then(function(key) {return crypto.subtle.exportKey("raw", key);})
                  .then(function(exportedKey) {
                      assert_unreached("deriveKey succeeded but should have failed with InvalidAccessError");
                  }, function(err) {
                      assert_equals(err.name, "InvalidAccessError", "Should throw correct error, not " + err.name + ": " + err.message);
                  });
              });
          }, algorithmName + " public property value is a secret key");
      });
  });

  function importKeys(pkcs8, spki, sizes) {
      var privateKeys = {};
      var publicKeys = {};
      var noDeriveKeyKeys = {};

      var promises = [];
      Object.keys(pkcs8).forEach(function(algorithmName) {
          var operation = subtle.importKey("pkcs8", pkcs8[algorithmName],
                                          {name: algorithmName},
                                          false, ["deriveBits", "deriveKey"])
                          .then(function(key) {
                              privateKeys[algorithmName] = key;
                          }, function (err) {
                            privateKeys[algorithmName] = null;
                          });
          promises.push(operation);
      });
      Object.keys(pkcs8).forEach(function(algorithmName) {
          var operation = subtle.importKey("pkcs8", pkcs8[algorithmName],
                                          {name: algorithmName},
                                          false, ["deriveBits"])
                          .then(function(key) {
                              noDeriveKeyKeys[algorithmName] = key;
                          }, function (err) {
                            noDeriveKeyKeys[algorithmName] = null;
                          });
          promises.push(operation);
      });
      Object.keys(spki).forEach(function(algorithmName) {
          var operation = subtle.importKey("spki", spki[algorithmName],
                                          {name: algorithmName},
                                          false, [])
                          .then(function(key) {
                              publicKeys[algorithmName] = key;
                          }, function (err) {
                            publicKeys[algorithmName] = null;
                          });
          promises.push(operation);
      });

      return Promise.all(promises)
             .then(function(results) {return {privateKeys: privateKeys, publicKeys: publicKeys, noDeriveKeyKeys: noDeriveKeyKeys}});
  }

  // Compares two ArrayBuffer or ArrayBufferView objects. If bitCount is
  // omitted, the two values must be the same length and have the same contents
  // in every byte. If bitCount is included, only that leading number of bits
  // have to match.
  function equalBuffers(a, b, bitCount) {
      var remainder;

      if (typeof bitCount === "undefined" && a.byteLength !== b.byteLength) {
          return false;
      }

      var aBytes = new Uint8Array(a);
      var bBytes = new Uint8Array(b);

      var length = a.byteLength;
      if (typeof bitCount !== "undefined") {
          length = Math.floor(bitCount / 8);
      }

      for (var i=0; i<length; i++) {
          if (aBytes[i] !== bBytes[i]) {
              return false;
          }
      }

      if (typeof bitCount !== "undefined") {
          remainder = bitCount % 8;
          return aBytes[length] >> (8 - remainder) === bBytes[length] >> (8 - remainder);
      }

      return true;
  }

}
