diff --git a/README.REDIST.BINS b/README.REDIST.BINS
index 14a93d5e29479..0f6e4fdd7fc6c 100644
--- a/README.REDIST.BINS
+++ b/README.REDIST.BINS
@@ -19,6 +19,7 @@
 19. xxHash (ext/hash/xxhash)
 20. Lexbor (ext/dom/lexbor/lexbor) see ext/dom/lexbor/LICENSE
 21. Portions of libcperciva (ext/hash/hash_sha_{ni,sse2}.c) see the header in the source file
+22. yescrypt (ext/standard/yescrypt) see the header in the source files
 
 3. pcre2lib (ext/pcre)
 
diff --git a/ext/standard/config.m4 b/ext/standard/config.m4
index ef6b3c5a01018..caa5d095b4394 100644
--- a/ext/standard/config.m4
+++ b/ext/standard/config.m4
@@ -89,6 +89,9 @@ AS_VAR_IF([PHP_EXTERNAL_LIBCRYPT], [no], [
     crypt_sha256.c
     crypt_sha512.c
     php_crypt_r.c
+    yescrypt/yescrypt-opt.c
+    yescrypt/yescrypt-common.c
+    yescrypt/sha256.c
   "])
 ], [
 AC_SEARCH_LIBS([crypt], [crypt],
@@ -206,6 +209,33 @@ int main(void) {
   [ac_cv_crypt_blowfish=no],
   [ac_cv_crypt_blowfish=no])])
 
+AC_CACHE_CHECK([for Yescrypt crypt], [ac_cv_crypt_yescrypt],
+  [AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_CRYPT_H
+#include <crypt.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+int main(void) {
+  char answer[128];
+  char *encrypted;
+  char salt[] = "\$y\$j9T\$fFqB7ZKMpdoOep2IXlKMuBnGplYOF/\$";
+
+  strcpy(answer, salt);
+  strcpy(&answer[sizeof(salt) - 1], "YUbFz9cPA2OISKzl1FhXHQP556fm3v7K1PBuIcVwyL/");
+  encrypted = crypt("rasmuslerdorf", salt);
+  return !encrypted || strcmp(encrypted, answer);
+}]])],
+  [ac_cv_crypt_yescrypt=yes],
+  [ac_cv_crypt_yescrypt=no],
+  [ac_cv_crypt_yescrypt=no])])
+
 AC_CACHE_CHECK([for SHA512 crypt], [ac_cv_crypt_sha512],
   [AC_RUN_IFELSE([AC_LANG_SOURCE([[
 #ifdef HAVE_UNISTD_H
@@ -260,7 +290,7 @@ int main(void) {
   [ac_cv_crypt_sha256=no],
   [ac_cv_crypt_sha256=no])])
 
-  if test "$ac_cv_crypt_blowfish" = "no" || test "$ac_cv_crypt_des" = "no" || test "$ac_cv_crypt_ext_des" = "no" || test "$ac_cv_crypt_md5" = "no" || test "$ac_cv_crypt_sha512" = "no" || test "$ac_cv_crypt_sha256" = "no"; then
+  if test "$ac_cv_crypt_blowfish" = "no" || test "$ac_cv_crypt_yescrypt" = "no" || test "$ac_cv_crypt_des" = "no" || test "$ac_cv_crypt_ext_des" = "no" || test "$ac_cv_crypt_md5" = "no" || test "$ac_cv_crypt_sha512" = "no" || test "$ac_cv_crypt_sha256" = "no"; then
     AC_MSG_FAILURE([Cannot use external libcrypt as some algo are missing.])
   fi
 
@@ -396,6 +426,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([
     crc32.c
     credits.c
     crypt.c
+    yescrypt/yescrypt-config.c
     css.c
     datetime.c
     dir.c
@@ -456,6 +487,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([
   [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])
 
 PHP_ADD_BUILD_DIR([$ext_builddir/libavifinfo])
+PHP_ADD_BUILD_DIR([$ext_builddir/yescrypt])
 
 PHP_ADD_MAKEFILE_FRAGMENT
 PHP_INSTALL_HEADERS([ext/standard/])
diff --git a/ext/standard/config.w32 b/ext/standard/config.w32
index c7c14b8705ca2..8d07c55d3a19f 100644
--- a/ext/standard/config.w32
+++ b/ext/standard/config.w32
@@ -38,6 +38,7 @@ EXTENSION("standard", "array.c base64.c basic_functions.c browscap.c \
 	streamsfuncs.c http.c flock_compat.c hrtime.c", false /* never shared */,
 	'/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
 ADD_SOURCES("ext/standard/libavifinfo", "avifinfo.c", "standard");
+ADD_SOURCES("ext/standard/yescrypt", "yescrypt-opt.c yescrypt-common.c yescrypt-config.c sha256.c", "standard");
 PHP_STANDARD = "yes";
 ADD_MAKEFILE_FRAGMENT();
 PHP_INSTALL_HEADERS("", "ext/standard");
diff --git a/ext/standard/crypt.c b/ext/standard/crypt.c
index 54687f6cdf307..47195dade1273 100644
--- a/ext/standard/crypt.c
+++ b/ext/standard/crypt.c
@@ -27,6 +27,7 @@
 #if PHP_USE_PHP_CRYPT_R
 # include "php_crypt_r.h"
 # include "crypt_freesec.h"
+# include "yescrypt/yescrypt.h"
 #else
 # ifdef HAVE_CRYPT_H
 #  if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE)
@@ -76,6 +77,19 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch
 		return NULL;
 	}
 
+	if (salt[0] == '$' && (salt[1] == 'y' || salt[1] == '7') && salt[2] == '$') {
+		/* Reference yescrypt can handle NUL bytes in the password, but sytem crypt cannot.
+		 * Return NULL for both cases for consistency. */
+		if (zend_char_has_nul_byte(password, (size_t) pass_len)) {
+			return NULL;
+		}
+
+		/* Neither reference yescrypt nor system crypt can handle NUL bytes in the salt. */
+		if (zend_char_has_nul_byte(salt, (size_t) salt_len)) {
+			return NULL;
+		}
+	}
+
 /* Windows (win32/crypt) has a stripped down version of libxcrypt and
 	a CryptoApi md5_crypt implementation */
 #if PHP_USE_PHP_CRYPT_R
@@ -138,6 +152,33 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch
 				ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
 				return result;
 			}
+		} else if (salt[0] == '$' && (salt[1] == 'y' || salt[1] == '7') && salt[2] == '$') {
+			yescrypt_local_t local;
+			uint8_t buf[PREFIX_LEN + 1 + HASH_LEN + 1]; /* prefix, '$', hash, NUL */
+
+			if (yescrypt_init_local(&local)) {
+				return NULL;
+			}
+
+			uint8_t *hash = yescrypt_r(
+				NULL,
+				&local,
+				(const uint8_t *) password,
+				(size_t) pass_len,
+				(const uint8_t *) salt,
+				NULL /* no key */,
+				buf,
+				sizeof(buf)
+			);
+
+			if (yescrypt_free_local(&local) || !hash) {
+				ZEND_SECURE_ZERO(buf, sizeof(buf));
+				return NULL;
+			}
+
+			result = zend_string_init((const char *) hash, strlen((const char *) hash), false);
+			ZEND_SECURE_ZERO(buf, sizeof(buf));
+			return result;
 		} else if (salt[0] == '_'
 				|| (IS_VALID_SALT_CHARACTER(salt[0]) && IS_VALID_SALT_CHARACTER(salt[1]))) {
 			/* DES Fallback */
diff --git a/ext/standard/password.c b/ext/standard/password.c
index 1e647bb301c3b..7713654731ab3 100644
--- a/ext/standard/password.c
+++ b/ext/standard/password.c
@@ -30,6 +30,7 @@
 #ifdef HAVE_ARGON2LIB
 #include "argon2.h"
 #endif
+#include "yescrypt/yescrypt.h"
 
 #ifdef PHP_WIN32
 #include "win32/winutil.h"
@@ -151,7 +152,8 @@ static bool php_password_bcrypt_needs_rehash(const zend_string *hash, zend_array
 	return old_cost != new_cost;
 }
 
-static bool php_password_bcrypt_verify(const zend_string *password, const zend_string *hash) {
+/* Password verification using the crypt() API, works for both bcrypt and yescrypt. */
+static bool php_password_crypt_verify(const zend_string *password, const zend_string *hash) {
 	int status = 0;
 	zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
 
@@ -224,12 +226,215 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a
 const php_password_algo php_password_algo_bcrypt = {
 	"bcrypt",
 	php_password_bcrypt_hash,
-	php_password_bcrypt_verify,
+	php_password_crypt_verify,
 	php_password_bcrypt_needs_rehash,
 	php_password_bcrypt_get_info,
 	php_password_bcrypt_valid,
 };
 
+/* yescrypt implementation */
+
+static void php_password_yescrypt_expect_long(const char *parameter_name) {
+	if (!EG(exception)) {
+		zend_value_error("Parameter \"%s\" cannot be converted to int", parameter_name);
+	}
+}
+
+static zend_string *php_password_yescrypt_hash(const zend_string *password, zend_array *options) {
+	zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT;
+	zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE;
+	zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM;
+	zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME;
+
+	if (UNEXPECTED(ZEND_LONG_INT_OVFL(ZSTR_LEN(password)))) {
+		zend_value_error("Password is too long");
+		return NULL;
+	}
+
+	if (options) {
+		bool failed;
+		const zval *option;
+
+		option = zend_hash_str_find(options, ZEND_STRL("block_count"));
+		if (option) {
+			block_count = zval_try_get_long(option, &failed);
+			if (UNEXPECTED(failed)) {
+				php_password_yescrypt_expect_long("block_count");
+				return NULL;
+			}
+
+			if (block_count < 4 || block_count > UINT32_MAX) {
+				zend_value_error("Parameter \"block_count\" must be between 4 and %u", UINT32_MAX);
+				return NULL;
+			}
+		}
+
+		option = zend_hash_str_find(options, ZEND_STRL("block_size"));
+		if (option) {
+			block_size = zval_try_get_long(option, &failed);
+			if (UNEXPECTED(failed)) {
+				php_password_yescrypt_expect_long("block_size");
+				return NULL;
+			}
+
+			if (block_size < 1) {
+				zend_value_error("Parameter \"block_size\" must be greater than 0");
+				return NULL;
+			}
+		}
+
+		option = zend_hash_str_find(options, ZEND_STRL("parallelism"));
+		if (option) {
+			parallelism = zval_try_get_long(option, &failed);
+			if (UNEXPECTED(failed)) {
+				php_password_yescrypt_expect_long("parallelism");
+				return NULL;
+			}
+
+			if (parallelism < 1) {
+				zend_value_error("Parameter \"parallelism\" must be greater than 0");
+				return NULL;
+			}
+		}
+
+		option = zend_hash_str_find(options, ZEND_STRL("time"));
+		if (option) {
+			time = zval_try_get_long(option, &failed);
+			if (UNEXPECTED(failed)) {
+				php_password_yescrypt_expect_long("time");
+				return NULL;
+			}
+
+			if (time < 0) {
+				zend_value_error("Parameter \"time\" must be greater than or equal to 0");
+				return NULL;
+			}
+		}
+
+		if ((uint64_t) block_size * (uint64_t) parallelism >= (1U << 30)) {
+			zend_value_error("Parameter \"block_size\" * parameter \"parallelism\" must be less than 2**30");
+			return NULL;
+		}
+	}
+
+	zend_string *salt = php_password_get_salt(NULL, Z_UL(16), options);
+	if (UNEXPECTED(!salt)) {
+		return NULL;
+	}
+	ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;
+
+	uint8_t prefix_buffer[PREFIX_LEN + 1];
+	yescrypt_params_t params = {
+		.flags = YESCRYPT_DEFAULTS,
+		.N = block_count, .r = block_size, .p = parallelism, .t = time,
+		.g = 0, .NROM = 0
+	};
+	uint8_t *prefix = yescrypt_encode_params_r(
+		&params,
+		(const uint8_t *) ZSTR_VAL(salt),
+		ZSTR_LEN(salt),
+		prefix_buffer,
+		sizeof(prefix_buffer)
+	);
+
+	zend_string_release_ex(salt, false);
+
+	if (UNEXPECTED(prefix == NULL)) {
+		return NULL;
+	}
+
+	return php_crypt(
+		ZSTR_VAL(password),
+		/* This cast is safe because we check that the password length fits in an int at the start. */
+		(int) ZSTR_LEN(password),
+		(const char *) prefix_buffer,
+		/* The following cast is safe because the prefix buffer size is always below INT_MAX. */
+		(int) strlen((const char *) prefix_buffer),
+		true
+	);
+}
+
+static bool php_password_yescrypt_valid(const zend_string *hash) {
+	const char *h = ZSTR_VAL(hash);
+	/* Note: $7$-style is longer */
+	return (ZSTR_LEN(hash) >= 3 /* "$y$" */ + 3 /* 3 parameters that must be encoded */ + 2 /* $salt$ */ + HASH_LEN
+			&& ZSTR_LEN(hash) <= PREFIX_LEN + 1 + HASH_LEN)
+			&& (h[0] == '$') && h[1] == 'y' && (h[2] == '$');
+}
+
+static bool php_password_yescrypt_needs_rehash(const zend_string *hash, zend_array *options) {
+	zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT;
+	zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE;
+	zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM;
+	zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME;
+
+	if (!php_password_yescrypt_valid(hash)) {
+		/* Should never get called this way. */
+		return true;
+	}
+
+	yescrypt_params_t params = { .p = 1 };
+	const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), &params, NULL);
+	if (!src) {
+		return true;
+	}
+
+	if (options) {
+		const zval *option;
+
+		option = zend_hash_str_find(options, ZEND_STRL("block_count"));
+		if (option) {
+			block_count = zval_get_long(option);
+		}
+
+		option = zend_hash_str_find(options, ZEND_STRL("block_size"));
+		if (option) {
+			block_size = zval_get_long(option);
+		}
+
+		option = zend_hash_str_find(options, ZEND_STRL("parallelism"));
+		if (option) {
+			parallelism = zval_get_long(option);
+		}
+
+		option = zend_hash_str_find(options, ZEND_STRL("time"));
+		if (option) {
+			time = zval_get_long(option);
+		}
+	}
+
+	return block_count != params.N || block_size != params.r || parallelism != params.p || time != params.t;
+}
+
+static int php_password_yescrypt_get_info(zval *return_value, const zend_string *hash) {
+	if (!php_password_yescrypt_valid(hash)) {
+		/* Should never get called this way. */
+		return FAILURE;
+	}
+
+	yescrypt_params_t params = { .p = 1 };
+	const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), &params, NULL);
+	if (!src) {
+		return FAILURE;
+	}
+
+	add_assoc_long(return_value, "block_count", (zend_long) params.N);
+	add_assoc_long(return_value, "block_size", (zend_long) params.r);
+	add_assoc_long(return_value, "parallelism", (zend_long) params.p);
+	add_assoc_long(return_value, "time", (zend_long) params.t);
+
+	return SUCCESS;
+}
+
+const php_password_algo php_password_algo_yescrypt = {
+	"yescrypt",
+	php_password_yescrypt_hash,
+	php_password_crypt_verify,
+	php_password_yescrypt_needs_rehash,
+	php_password_yescrypt_get_info,
+	php_password_yescrypt_valid,
+};
+
 
 #ifdef HAVE_ARGON2LIB
 /* argon2i/argon2id shared implementation */
@@ -427,6 +632,10 @@ PHP_MINIT_FUNCTION(password) /* {{{ */
 		return FAILURE;
 	}
 
+	if (FAILURE == php_password_algo_register("y", &php_password_algo_yescrypt)) {
+		return FAILURE;
+	}
+
 #ifdef HAVE_ARGON2LIB
 	if (FAILURE == php_password_algo_register("argon2i", &php_password_algo_argon2i)) {
 		return FAILURE;
diff --git a/ext/standard/password.stub.php b/ext/standard/password.stub.php
index c3c99117d514c..7cb796b6c8ccf 100644
--- a/ext/standard/password.stub.php
+++ b/ext/standard/password.stub.php
@@ -10,12 +10,37 @@
  * @var string
  */
 const PASSWORD_BCRYPT = "2y";
+/**
+ * @var string
+ */
+const PASSWORD_YESCRYPT = "y";
 /**
  * @var int
  * @cvalue PHP_PASSWORD_BCRYPT_COST
  */
 const PASSWORD_BCRYPT_DEFAULT_COST = UNKNOWN;
 
+/**
+ * @var int
+ * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT
+ */
+const PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT = UNKNOWN;
+/**
+ * @var int
+ * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE
+ */
+const PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE = UNKNOWN;
+/**
+ * @var int
+ * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM
+ */
+const PASSWORD_YESCRYPT_DEFAULT_PARALLELISM = UNKNOWN;
+/**
+ * @var int
+ * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_TIME
+ */
+const PASSWORD_YESCRYPT_DEFAULT_TIME = UNKNOWN;
+
 #ifdef HAVE_ARGON2LIB
 /**
  * @var string
diff --git a/ext/standard/password_arginfo.h b/ext/standard/password_arginfo.h
index bf1b614273a4a..fd3d27705afec 100644
--- a/ext/standard/password_arginfo.h
+++ b/ext/standard/password_arginfo.h
@@ -1,11 +1,16 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: aab38646ace967e985c348b78251474693da95a7 */
+ * Stub hash: ae870a71e45d4cd590c24498f7fd88afe213b3b5 */
 
 static void register_password_symbols(int module_number)
 {
 	REGISTER_STRING_CONSTANT("PASSWORD_DEFAULT", "2y", CONST_PERSISTENT);
 	REGISTER_STRING_CONSTANT("PASSWORD_BCRYPT", "2y", CONST_PERSISTENT);
+	REGISTER_STRING_CONSTANT("PASSWORD_YESCRYPT", "y", CONST_PERSISTENT);
 	REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT", PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT, CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE", PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE, CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_PARALLELISM", PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM, CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_TIME", PHP_PASSWORD_YESCRYPT_DEFAULT_TIME, CONST_PERSISTENT);
 #if defined(HAVE_ARGON2LIB)
 	REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_PERSISTENT);
 #endif
diff --git a/ext/standard/php_password.h b/ext/standard/php_password.h
index 5a6f3b2af5c9e..04ad0e9b2ad4a 100644
--- a/ext/standard/php_password.h
+++ b/ext/standard/php_password.h
@@ -24,6 +24,11 @@ PHP_MSHUTDOWN_FUNCTION(password);
 #define PHP_PASSWORD_DEFAULT    PHP_PASSWORD_BCRYPT
 #define PHP_PASSWORD_BCRYPT_COST 12
 
+#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT 4096
+#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE 32
+#define PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM 1
+#define PHP_PASSWORD_YESCRYPT_DEFAULT_TIME 0
+
 #ifdef HAVE_ARGON2LIB
 /**
  * When updating these values, synchronize values in
diff --git a/ext/standard/tests/crypt/yescrypt.phpt b/ext/standard/tests/crypt/yescrypt.phpt
new file mode 100644
index 0000000000000..35f8498e590c4
--- /dev/null
+++ b/ext/standard/tests/crypt/yescrypt.phpt
@@ -0,0 +1,38 @@
+--TEST--
+Test crypt() with yescrypt
+--FILE--
+<?php
+
+var_dump(crypt("test", '$y$'));
+var_dump(crypt("test", '$y$$'));
+var_dump(crypt("test", '$y$j$'));
+var_dump(crypt("test", '$y$j9$'));
+var_dump(crypt("test\0x", '$y$j9T$salt'.chr(0)));
+var_dump(crypt("test\0", '$y$j9T$salt'));
+var_dump(crypt("test\0x", '$y$j9T$salt'));
+var_dump(crypt("\0", '$y$j9T$salt'));
+
+var_dump(crypt("test", '$y$j9T$'));
+var_dump(crypt("test", '$y$j9T$salt'));
+var_dump(crypt("test", '$y$j9T$salt$'));
+var_dump(crypt("", '$y$j9T$salt'));
+
+var_dump(crypt("", '$7$400.../....$'));
+var_dump(crypt("", '$7$400.../....$salt$'));
+
+?>
+--EXPECT--
+string(2) "*0"
+string(2) "*0"
+string(2) "*0"
+string(2) "*0"
+string(2) "*0"
+string(2) "*0"
+string(2) "*0"
+string(2) "*0"
+string(51) "$y$j9T$$6tN6tt5mmPHxQskcf5Oi7Sb.1nKYbi5cOZgTiMq7Qw4"
+string(55) "$y$j9T$salt$a9CZafQyDF042zUCgPAhoF7Zd5phBweZqIIw6SMCTh."
+string(55) "$y$j9T$salt$a9CZafQyDF042zUCgPAhoF7Zd5phBweZqIIw6SMCTh."
+string(55) "$y$j9T$salt$sE5vvd.NbRw0CRzUgcEQ/PZMH4hmete7N5s3qN09F12"
+string(58) "$7$400.../....$fsLd.toTUvgzSAYmoHbKwQGAmqLK6y.yIpW2WKuemOA"
+string(63) "$7$400.../....$salt$3SJITk6BqtXkmuOQkPe7e.yClr8MVXc6twSB2ZBHPE3"
diff --git a/ext/standard/tests/password/password_get_info_yescrypt.phpt b/ext/standard/tests/password/password_get_info_yescrypt.phpt
new file mode 100644
index 0000000000000..00a1b392f2ce4
--- /dev/null
+++ b/ext/standard/tests/password/password_get_info_yescrypt.phpt
@@ -0,0 +1,76 @@
+--TEST--
+Test normal operation of password_get_info() with Yescrypt
+--FILE--
+<?php
+
+var_dump(password_get_info('$y$jC5//$7NbMKtqBsR3PDV5JHBYPDtaRD3nRg/$FT4mgFH/6EJNHRAGvD6yvzGjCo01KpIhLwGbQW.Nxk1'));
+var_dump(password_get_info('$y$jC50..$bBICnZqGiE5P5h4KjoYGpp4S773JB/$ZcD9FJW35.VG0kN0hh5C7oXa3o3dSBSXg/WDaTiWsA8'));
+var_dump(password_get_info('$y$jA.0./$pU1KtJbSnMYKcNIQZZLPpAXFpZ4RB/$TeI5bGZQ5l589gWeUjaJzSlIPQZk7Wp2gLsnVG0gJH6'));
+
+var_dump(password_get_info('$y$jA.0./$'));
+
+echo "OK!";
+
+?>
+--EXPECT--
+array(3) {
+  ["algo"]=>
+  string(1) "y"
+  ["algoName"]=>
+  string(8) "yescrypt"
+  ["options"]=>
+  array(4) {
+    ["block_count"]=>
+    int(32768)
+    ["block_size"]=>
+    int(8)
+    ["parallelism"]=>
+    int(1)
+    ["time"]=>
+    int(2)
+  }
+}
+array(3) {
+  ["algo"]=>
+  string(1) "y"
+  ["algoName"]=>
+  string(8) "yescrypt"
+  ["options"]=>
+  array(4) {
+    ["block_count"]=>
+    int(32768)
+    ["block_size"]=>
+    int(8)
+    ["parallelism"]=>
+    int(2)
+    ["time"]=>
+    int(1)
+  }
+}
+array(3) {
+  ["algo"]=>
+  string(1) "y"
+  ["algoName"]=>
+  string(8) "yescrypt"
+  ["options"]=>
+  array(4) {
+    ["block_count"]=>
+    int(8192)
+    ["block_size"]=>
+    int(1)
+    ["parallelism"]=>
+    int(2)
+    ["time"]=>
+    int(2)
+  }
+}
+array(3) {
+  ["algo"]=>
+  NULL
+  ["algoName"]=>
+  string(7) "unknown"
+  ["options"]=>
+  array(0) {
+  }
+}
+OK!
diff --git a/ext/standard/tests/password/password_hash.phpt b/ext/standard/tests/password/password_hash.phpt
index 6eb786887ba72..cc22b71fb4a6d 100644
--- a/ext/standard/tests/password/password_hash.phpt
+++ b/ext/standard/tests/password/password_hash.phpt
@@ -8,8 +8,11 @@ Test normal operation of password_hash()
 
 var_dump(password_hash("foo", PASSWORD_BCRYPT));
 
+var_dump(password_hash("foo", PASSWORD_YESCRYPT));
+
 $algos = [
   PASSWORD_BCRYPT,
+  PASSWORD_YESCRYPT,
   '2y',
   1,
 ];
@@ -23,6 +26,8 @@ echo "OK!";
 ?>
 --EXPECTF--
 string(60) "$2y$12$%s"
+string(73) "$y$j9T$%s"
+bool(true)
 bool(true)
 bool(true)
 bool(true)
diff --git a/ext/standard/tests/password/password_hash_error_yescrypt.phpt b/ext/standard/tests/password/password_hash_error_yescrypt.phpt
new file mode 100644
index 0000000000000..244c37fe6d4e9
--- /dev/null
+++ b/ext/standard/tests/password/password_hash_error_yescrypt.phpt
@@ -0,0 +1,69 @@
+--TEST--
+Test error operation of password_hash() with Yescrypt
+--FILE--
+<?php
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['block_count' => 3]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['block_count' => -1]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['block_count' => []]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['block_size' => 0]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['block_size' => []]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['parallelism' => 0]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['parallelism' => []]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['time' => -1]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+try {
+    password_hash('test', PASSWORD_YESCRYPT, ['time' => []]);
+} catch (ValueError $exception) {
+    echo $exception->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+Parameter "block_count" must be between 4 and 4294967295
+Parameter "block_count" must be between 4 and 4294967295
+Parameter "block_count" cannot be converted to int
+Parameter "block_size" must be greater than 0
+Parameter "block_size" cannot be converted to int
+Parameter "parallelism" must be greater than 0
+Parameter "parallelism" cannot be converted to int
+Parameter "time" must be greater than or equal to 0
+Parameter "time" cannot be converted to int
diff --git a/ext/standard/tests/password/password_hash_yescrypt.phpt b/ext/standard/tests/password/password_hash_yescrypt.phpt
new file mode 100644
index 0000000000000..a4833b56f1fcf
--- /dev/null
+++ b/ext/standard/tests/password/password_hash_yescrypt.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test normal operation of password_hash() with Yescrypt
+--FILE--
+<?php
+
+$password = "the password for testing 12345!";
+
+$hash = password_hash($password, PASSWORD_YESCRYPT);
+var_dump(password_verify($password, $hash));
+var_dump(password_get_info($hash)['algo']);
+
+echo "OK!";
+?>
+--EXPECT--
+bool(true)
+string(1) "y"
+OK!
diff --git a/ext/standard/tests/password/password_needs_rehash_yescrypt.phpt b/ext/standard/tests/password/password_needs_rehash_yescrypt.phpt
new file mode 100644
index 0000000000000..0ef78c7a79677
--- /dev/null
+++ b/ext/standard/tests/password/password_needs_rehash_yescrypt.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Test normal operation of password_needs_rehash() with Yescrypt
+--FILE--
+<?php
+
+$hash = password_hash('test', PASSWORD_YESCRYPT);
+var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT));
+var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['block_size' => PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE * 2]));
+var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['block_count' => PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT * 2]));
+var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['parallelism' => PASSWORD_YESCRYPT_DEFAULT_PARALLELISM + 1]));
+var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['time' => PASSWORD_YESCRYPT_DEFAULT_TIME + 1]));
+
+echo "OK!";
+?>
+--EXPECT--
+bool(false)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+OK!
diff --git a/ext/standard/tests/password/password_verify.phpt b/ext/standard/tests/password/password_verify.phpt
index 5f2ff1230cc63..99ad58f40ba93 100644
--- a/ext/standard/tests/password/password_verify.phpt
+++ b/ext/standard/tests/password/password_verify.phpt
@@ -1,5 +1,5 @@
 --TEST--
-Test normal operation of password_verify)
+Test normal operation of password_verify()
 --FILE--
 <?php
 //-=-=-=-
diff --git a/ext/standard/tests/password/password_verify_yescrypt.phpt b/ext/standard/tests/password/password_verify_yescrypt.phpt
new file mode 100644
index 0000000000000..72abfa00548d0
--- /dev/null
+++ b/ext/standard/tests/password/password_verify_yescrypt.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Test normal operation of password_verify() with Yescrypt
+--FILE--
+<?php
+
+// TODO: what to do with \0 ???
+
+var_dump(password_verify('letmein', '$y$jA.0./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3'));
+var_dump(password_verify('test', '$y$jA.0./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3'));
+var_dump(password_verify('letmei', '$y$jA.0./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3'));
+var_dump(password_verify('letmein', '$y$jA.1./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3'));
+var_dump(password_verify('letmein', '$y$jA.1./$'.chr(0)));
+
+var_dump(password_verify('this is yescrypt', '$y$j9T$oZ1CpcJNi71CnQpOHlY9q.$7KlWq26Kv1pBblGfbg3HQn7j84oZv4dSYIAlZ2YzOh6'));
+var_dump(password_verify('test', '$y$j9T$oZ1CpcJNi71CnQpOHlY9q.$7KlWq26Kv1pBblGfbg3HQn7j84oZv4dSYIAlZ2YzOh6'));
+var_dump(password_verify('letmein', '$y$j9T$oZ1CpcJNi71CnQpOHlY9q.$7KlWq26Kv1pBblGfbg3HQn7j84oZv4dSYIAlZ2YzOh6'));
+
+echo "OK!";
+?>
+--EXPECT--
+bool(true)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(true)
+bool(false)
+bool(false)
+OK!
diff --git a/ext/standard/yescrypt/insecure_memzero.h b/ext/standard/yescrypt/insecure_memzero.h
new file mode 100644
index 0000000000000..f46b44f1b6482
--- /dev/null
+++ b/ext/standard/yescrypt/insecure_memzero.h
@@ -0,0 +1,22 @@
+/*
+   +----------------------------------------------------------------------+
+   | Copyright (c) The PHP Group                                          |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | https://www.php.net/license/3_01.txt                                 |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef INSECURE_MEMZERO_H
+#define INSECURE_MEMZERO_H
+
+#include "php.h"
+#include "zend_portability.h"
+#define insecure_memzero ZEND_SECURE_ZERO
+
+#endif
diff --git a/ext/standard/yescrypt/sha256.c b/ext/standard/yescrypt/sha256.c
new file mode 100644
index 0000000000000..614475e988b5b
--- /dev/null
+++ b/ext/standard/yescrypt/sha256.c
@@ -0,0 +1,653 @@
+/*-
+ * Copyright 2005-2016 Colin Percival
+ * Copyright 2016-2018 Alexander Peslyak
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "insecure_memzero.h"
+#include "sysendian.h"
+
+#include "sha256.h"
+
+#ifdef __ICC
+/* Miscompile with icc 14.0.0 (at least), so don't use restrict there */
+#define restrict
+#elif __STDC_VERSION__ >= 199901L
+/* Have restrict */
+#elif defined(__GNUC__)
+#define restrict __restrict
+#else
+#define restrict
+#endif
+
+/* PHP change: Windows compatibility */
+#ifdef _MSC_VER
+# define PHP_STATIC_RESTRICT
+#else
+# define PHP_STATIC_RESTRICT static restrict
+#endif
+
+/*
+ * Encode a length len*2 vector of (uint32_t) into a length len*8 vector of
+ * (uint8_t) in big-endian form.
+ */
+static void
+be32enc_vect(uint8_t * dst, const uint32_t * src, size_t len)
+{
+
+	/* Encode vector, two words at a time. */
+	do {
+		be32enc(&dst[0], src[0]);
+		be32enc(&dst[4], src[1]);
+		src += 2;
+		dst += 8;
+	} while (--len);
+}
+
+/*
+ * Decode a big-endian length len*8 vector of (uint8_t) into a length
+ * len*2 vector of (uint32_t).
+ */
+static void
+be32dec_vect(uint32_t * dst, const uint8_t * src, size_t len)
+{
+
+	/* Decode vector, two words at a time. */
+	do {
+		dst[0] = be32dec(&src[0]);
+		dst[1] = be32dec(&src[4]);
+		src += 8;
+		dst += 2;
+	} while (--len);
+}
+
+/* SHA256 round constants. */
+static const uint32_t Krnd[64] = {
+	0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+	0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+	0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+	0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+	0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+	0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+	0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+	0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+	0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+	0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+	0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+	0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+	0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+	0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+	0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+	0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+/* Elementary functions used by SHA256 */
+#define Ch(x, y, z)	((x & (y ^ z)) ^ z)
+#define Maj(x, y, z)	((x & (y | z)) | (y & z))
+#define SHR(x, n)	(x >> n)
+#define ROTR(x, n)	((x >> n) | (x << (32 - n)))
+#define S0(x)		(ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define S1(x)		(ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define s0(x)		(ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3))
+#define s1(x)		(ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10))
+
+/* SHA256 round function */
+#define RND(a, b, c, d, e, f, g, h, k)			\
+	h += S1(e) + Ch(e, f, g) + k;			\
+	d += h;						\
+	h += S0(a) + Maj(a, b, c);
+
+/* Adjusted round function for rotating state */
+#define RNDr(S, W, i, ii)			\
+	RND(S[(64 - i) % 8], S[(65 - i) % 8],	\
+	    S[(66 - i) % 8], S[(67 - i) % 8],	\
+	    S[(68 - i) % 8], S[(69 - i) % 8],	\
+	    S[(70 - i) % 8], S[(71 - i) % 8],	\
+	    W[i + ii] + Krnd[i + ii])
+
+/* Message schedule computation */
+#define MSCH(W, ii, i)				\
+	W[i + ii + 16] = s1(W[i + ii + 14]) + W[i + ii + 9] + s0(W[i + ii + 1]) + W[i + ii]
+
+/*
+ * SHA256 block compression function.  The 256-bit state is transformed via
+ * the 512-bit input block to produce a new state.
+ */
+static void
+SHA256_Transform(uint32_t state[PHP_STATIC_RESTRICT 8],
+    const uint8_t block[PHP_STATIC_RESTRICT 64],
+    uint32_t W[PHP_STATIC_RESTRICT 64], uint32_t S[PHP_STATIC_RESTRICT 8])
+{
+	int i;
+
+	/* 1. Prepare the first part of the message schedule W. */
+	be32dec_vect(W, block, 8);
+
+	/* 2. Initialize working variables. */
+	memcpy(S, state, 32);
+
+	/* 3. Mix. */
+	for (i = 0; i < 64; i += 16) {
+		RNDr(S, W, 0, i);
+		RNDr(S, W, 1, i);
+		RNDr(S, W, 2, i);
+		RNDr(S, W, 3, i);
+		RNDr(S, W, 4, i);
+		RNDr(S, W, 5, i);
+		RNDr(S, W, 6, i);
+		RNDr(S, W, 7, i);
+		RNDr(S, W, 8, i);
+		RNDr(S, W, 9, i);
+		RNDr(S, W, 10, i);
+		RNDr(S, W, 11, i);
+		RNDr(S, W, 12, i);
+		RNDr(S, W, 13, i);
+		RNDr(S, W, 14, i);
+		RNDr(S, W, 15, i);
+
+		if (i == 48)
+			break;
+		MSCH(W, 0, i);
+		MSCH(W, 1, i);
+		MSCH(W, 2, i);
+		MSCH(W, 3, i);
+		MSCH(W, 4, i);
+		MSCH(W, 5, i);
+		MSCH(W, 6, i);
+		MSCH(W, 7, i);
+		MSCH(W, 8, i);
+		MSCH(W, 9, i);
+		MSCH(W, 10, i);
+		MSCH(W, 11, i);
+		MSCH(W, 12, i);
+		MSCH(W, 13, i);
+		MSCH(W, 14, i);
+		MSCH(W, 15, i);
+	}
+
+	/* 4. Mix local working variables into global state. */
+	state[0] += S[0];
+	state[1] += S[1];
+	state[2] += S[2];
+	state[3] += S[3];
+	state[4] += S[4];
+	state[5] += S[5];
+	state[6] += S[6];
+	state[7] += S[7];
+}
+
+static const uint8_t PAD[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* Add padding and terminating bit-count. */
+static void
+SHA256_Pad(SHA256_CTX * ctx, uint32_t tmp32[PHP_STATIC_RESTRICT 72])
+{
+	size_t r;
+
+	/* Figure out how many bytes we have buffered. */
+	r = (ctx->count >> 3) & 0x3f;
+
+	/* Pad to 56 mod 64, transforming if we finish a block en route. */
+	if (r < 56) {
+		/* Pad to 56 mod 64. */
+		memcpy(&ctx->buf[r], PAD, 56 - r);
+	} else {
+		/* Finish the current block and mix. */
+		memcpy(&ctx->buf[r], PAD, 64 - r);
+		SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]);
+
+		/* The start of the final block is all zeroes. */
+		memset(&ctx->buf[0], 0, 56);
+	}
+
+	/* Add the terminating bit-count. */
+	be64enc(&ctx->buf[56], ctx->count);
+
+	/* Mix in the final block. */
+	SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]);
+}
+
+/* Magic initialization constants. */
+static const uint32_t initial_state[8] = {
+	0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
+	0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
+};
+
+/**
+ * SHA256_Init(ctx):
+ * Initialize the SHA256 context ${ctx}.
+ */
+void
+SHA256_Init(SHA256_CTX * ctx)
+{
+
+	/* Zero bits processed so far. */
+	ctx->count = 0;
+
+	/* Initialize state. */
+	memcpy(ctx->state, initial_state, sizeof(initial_state));
+}
+
+/**
+ * SHA256_Update(ctx, in, len):
+ * Input ${len} bytes from ${in} into the SHA256 context ${ctx}.
+ */
+static void
+_SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len,
+    uint32_t tmp32[PHP_STATIC_RESTRICT 72])
+{
+	uint32_t r;
+	const uint8_t * src = in;
+
+	/* Return immediately if we have nothing to do. */
+	if (len == 0)
+		return;
+
+	/* Number of bytes left in the buffer from previous updates. */
+	r = (ctx->count >> 3) & 0x3f;
+
+	/* Update number of bits. */
+	ctx->count += (uint64_t)(len) << 3;
+
+	/* Handle the case where we don't need to perform any transforms. */
+	if (len < 64 - r) {
+		memcpy(&ctx->buf[r], src, len);
+		return;
+	}
+
+	/* Finish the current block. */
+	memcpy(&ctx->buf[r], src, 64 - r);
+	SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]);
+	src += 64 - r;
+	len -= 64 - r;
+
+	/* Perform complete blocks. */
+	while (len >= 64) {
+		SHA256_Transform(ctx->state, src, &tmp32[0], &tmp32[64]);
+		src += 64;
+		len -= 64;
+	}
+
+	/* Copy left over data into buffer. */
+	memcpy(ctx->buf, src, len);
+}
+
+/* Wrapper function for intermediate-values sanitization. */
+void
+SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len)
+{
+	uint32_t tmp32[72];
+
+	/* Call the real function. */
+	_SHA256_Update(ctx, in, len, tmp32);
+
+	/* Clean the stack. */
+	insecure_memzero(tmp32, 288);
+}
+
+/**
+ * SHA256_Final(digest, ctx):
+ * Output the SHA256 hash of the data input to the context ${ctx} into the
+ * buffer ${digest}.
+ */
+static void
+_SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx,
+    uint32_t tmp32[PHP_STATIC_RESTRICT 72])
+{
+
+	/* Add padding. */
+	SHA256_Pad(ctx, tmp32);
+
+	/* Write the hash. */
+	be32enc_vect(digest, ctx->state, 4);
+}
+
+/* Wrapper function for intermediate-values sanitization. */
+void
+SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx)
+{
+	uint32_t tmp32[72];
+
+	/* Call the real function. */
+	_SHA256_Final(digest, ctx, tmp32);
+
+	/* Clear the context state. */
+	insecure_memzero(ctx, sizeof(SHA256_CTX));
+
+	/* Clean the stack. */
+	insecure_memzero(tmp32, 288);
+}
+
+/**
+ * SHA256_Buf(in, len, digest):
+ * Compute the SHA256 hash of ${len} bytes from ${in} and write it to ${digest}.
+ */
+void
+SHA256_Buf(const void * in, size_t len, uint8_t digest[32])
+{
+	SHA256_CTX ctx;
+	uint32_t tmp32[72];
+
+	SHA256_Init(&ctx);
+	_SHA256_Update(&ctx, in, len, tmp32);
+	_SHA256_Final(digest, &ctx, tmp32);
+
+	/* Clean the stack. */
+	insecure_memzero(&ctx, sizeof(SHA256_CTX));
+	insecure_memzero(tmp32, 288);
+}
+
+/**
+ * HMAC_SHA256_Init(ctx, K, Klen):
+ * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from
+ * ${K}.
+ */
+static void
+_HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen,
+    uint32_t tmp32[PHP_STATIC_RESTRICT 72], uint8_t pad[PHP_STATIC_RESTRICT 64],
+    uint8_t khash[PHP_STATIC_RESTRICT 32])
+{
+	const uint8_t * K = _K;
+	size_t i;
+
+	/* If Klen > 64, the key is really SHA256(K). */
+	if (Klen > 64) {
+		SHA256_Init(&ctx->ictx);
+		_SHA256_Update(&ctx->ictx, K, Klen, tmp32);
+		_SHA256_Final(khash, &ctx->ictx, tmp32);
+		K = khash;
+		Klen = 32;
+	}
+
+	/* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */
+	SHA256_Init(&ctx->ictx);
+	memset(pad, 0x36, 64);
+	for (i = 0; i < Klen; i++)
+		pad[i] ^= K[i];
+	_SHA256_Update(&ctx->ictx, pad, 64, tmp32);
+
+	/* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */
+	SHA256_Init(&ctx->octx);
+	memset(pad, 0x5c, 64);
+	for (i = 0; i < Klen; i++)
+		pad[i] ^= K[i];
+	_SHA256_Update(&ctx->octx, pad, 64, tmp32);
+}
+
+/* Wrapper function for intermediate-values sanitization. */
+void
+HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen)
+{
+	uint32_t tmp32[72];
+	uint8_t pad[64];
+	uint8_t khash[32];
+
+	/* Call the real function. */
+	_HMAC_SHA256_Init(ctx, _K, Klen, tmp32, pad, khash);
+
+	/* Clean the stack. */
+	insecure_memzero(tmp32, 288);
+	insecure_memzero(khash, 32);
+	insecure_memzero(pad, 64);
+}
+
+/**
+ * HMAC_SHA256_Update(ctx, in, len):
+ * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}.
+ */
+static void
+_HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len,
+    uint32_t tmp32[PHP_STATIC_RESTRICT 72])
+{
+
+	/* Feed data to the inner SHA256 operation. */
+	_SHA256_Update(&ctx->ictx, in, len, tmp32);
+}
+
+/* Wrapper function for intermediate-values sanitization. */
+void
+HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len)
+{
+	uint32_t tmp32[72];
+
+	/* Call the real function. */
+	_HMAC_SHA256_Update(ctx, in, len, tmp32);
+
+	/* Clean the stack. */
+	insecure_memzero(tmp32, 288);
+}
+
+/**
+ * HMAC_SHA256_Final(digest, ctx):
+ * Output the HMAC-SHA256 of the data input to the context ${ctx} into the
+ * buffer ${digest}.
+ */
+static void
+_HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx,
+    uint32_t tmp32[PHP_STATIC_RESTRICT 72], uint8_t ihash[PHP_STATIC_RESTRICT 32])
+{
+
+	/* Finish the inner SHA256 operation. */
+	_SHA256_Final(ihash, &ctx->ictx, tmp32);
+
+	/* Feed the inner hash to the outer SHA256 operation. */
+	_SHA256_Update(&ctx->octx, ihash, 32, tmp32);
+
+	/* Finish the outer SHA256 operation. */
+	_SHA256_Final(digest, &ctx->octx, tmp32);
+}
+
+/* Wrapper function for intermediate-values sanitization. */
+void
+HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx)
+{
+	uint32_t tmp32[72];
+	uint8_t ihash[32];
+
+	/* Call the real function. */
+	_HMAC_SHA256_Final(digest, ctx, tmp32, ihash);
+
+	/* Clean the stack. */
+	insecure_memzero(tmp32, 288);
+	insecure_memzero(ihash, 32);
+}
+
+/**
+ * HMAC_SHA256_Buf(K, Klen, in, len, digest):
+ * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of
+ * length ${Klen}, and write the result to ${digest}.
+ */
+void
+HMAC_SHA256_Buf(const void * K, size_t Klen, const void * in, size_t len,
+    uint8_t digest[32])
+{
+	HMAC_SHA256_CTX ctx;
+	uint32_t tmp32[72];
+	uint8_t tmp8[96];
+
+	_HMAC_SHA256_Init(&ctx, K, Klen, tmp32, &tmp8[0], &tmp8[64]);
+	_HMAC_SHA256_Update(&ctx, in, len, tmp32);
+	_HMAC_SHA256_Final(digest, &ctx, tmp32, &tmp8[0]);
+
+	/* Clean the stack. */
+	insecure_memzero(&ctx, sizeof(HMAC_SHA256_CTX));
+	insecure_memzero(tmp32, 288);
+	insecure_memzero(tmp8, 96);
+}
+
+/* Add padding and terminating bit-count, but don't invoke Transform yet. */
+static int
+SHA256_Pad_Almost(SHA256_CTX * ctx, uint8_t len[PHP_STATIC_RESTRICT 8],
+    uint32_t tmp32[PHP_STATIC_RESTRICT 72])
+{
+	uint32_t r;
+
+	r = (ctx->count >> 3) & 0x3f;
+	if (r >= 56)
+		return -1;
+
+	/*
+	 * Convert length to a vector of bytes -- we do this now rather
+	 * than later because the length will change after we pad.
+	 */
+	be64enc(len, ctx->count);
+
+	/* Add 1--56 bytes so that the resulting length is 56 mod 64. */
+	_SHA256_Update(ctx, PAD, 56 - r, tmp32);
+
+	/* Add the terminating bit-count. */
+	ctx->buf[63] = len[7];
+	_SHA256_Update(ctx, len, 7, tmp32);
+
+	return 0;
+}
+
+/**
+ * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen):
+ * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and
+ * write the output to buf.  The value dkLen must be at most 32 * (2^32 - 1).
+ */
+void
+PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt,
+    size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen)
+{
+	HMAC_SHA256_CTX Phctx, PShctx, hctx;
+	uint32_t tmp32[72];
+	union {
+		uint8_t tmp8[96];
+		uint32_t state[8];
+	} u;
+	size_t i;
+	uint8_t ivec[4];
+	uint8_t U[32];
+	uint8_t T[32];
+	uint64_t j;
+	int k;
+	size_t clen;
+
+	/* Sanity-check. */
+	assert(dkLen <= 32 * (size_t)(UINT32_MAX));
+
+	if (c == 1 && (dkLen & 31) == 0 && (saltlen & 63) <= 51) {
+		uint32_t oldcount;
+		uint8_t * ivecp;
+
+		/* Compute HMAC state after processing P and S. */
+		_HMAC_SHA256_Init(&hctx, passwd, passwdlen,
+		    tmp32, &u.tmp8[0], &u.tmp8[64]);
+		_HMAC_SHA256_Update(&hctx, salt, saltlen, tmp32);
+
+		/* Prepare ictx padding. */
+		oldcount = hctx.ictx.count & (0x3f << 3);
+		_HMAC_SHA256_Update(&hctx, "\0\0\0", 4, tmp32);
+		if ((hctx.ictx.count & (0x3f << 3)) < oldcount ||
+		    SHA256_Pad_Almost(&hctx.ictx, u.tmp8, tmp32))
+			goto generic; /* Can't happen due to saltlen check */
+		ivecp = hctx.ictx.buf + (oldcount >> 3);
+
+		/* Prepare octx padding. */
+		hctx.octx.count += 32 << 3;
+		SHA256_Pad_Almost(&hctx.octx, u.tmp8, tmp32);
+
+		/* Iterate through the blocks. */
+		for (i = 0; i * 32 < dkLen; i++) {
+			/* Generate INT(i + 1). */
+			be32enc(ivecp, (uint32_t)(i + 1));
+
+			/* Compute U_1 = PRF(P, S || INT(i)). */
+			memcpy(u.state, hctx.ictx.state, sizeof(u.state));
+			SHA256_Transform(u.state, hctx.ictx.buf,
+			    &tmp32[0], &tmp32[64]);
+			be32enc_vect(hctx.octx.buf, u.state, 4);
+			memcpy(u.state, hctx.octx.state, sizeof(u.state));
+			SHA256_Transform(u.state, hctx.octx.buf,
+			    &tmp32[0], &tmp32[64]);
+			be32enc_vect(&buf[i * 32], u.state, 4);
+		}
+
+		goto cleanup;
+	}
+
+generic:
+	/* Compute HMAC state after processing P. */
+	_HMAC_SHA256_Init(&Phctx, passwd, passwdlen,
+	    tmp32, &u.tmp8[0], &u.tmp8[64]);
+
+	/* Compute HMAC state after processing P and S. */
+	memcpy(&PShctx, &Phctx, sizeof(HMAC_SHA256_CTX));
+	_HMAC_SHA256_Update(&PShctx, salt, saltlen, tmp32);
+
+	/* Iterate through the blocks. */
+	for (i = 0; i * 32 < dkLen; i++) {
+		/* Generate INT(i + 1). */
+		be32enc(ivec, (uint32_t)(i + 1));
+
+		/* Compute U_1 = PRF(P, S || INT(i)). */
+		memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX));
+		_HMAC_SHA256_Update(&hctx, ivec, 4, tmp32);
+		_HMAC_SHA256_Final(T, &hctx, tmp32, u.tmp8);
+
+		if (c > 1) {
+			/* T_i = U_1 ... */
+			memcpy(U, T, 32);
+
+			for (j = 2; j <= c; j++) {
+				/* Compute U_j. */
+				memcpy(&hctx, &Phctx, sizeof(HMAC_SHA256_CTX));
+				_HMAC_SHA256_Update(&hctx, U, 32, tmp32);
+				_HMAC_SHA256_Final(U, &hctx, tmp32, u.tmp8);
+
+				/* ... xor U_j ... */
+				for (k = 0; k < 32; k++)
+					T[k] ^= U[k];
+			}
+		}
+
+		/* Copy as many bytes as necessary into buf. */
+		clen = dkLen - i * 32;
+		if (clen > 32)
+			clen = 32;
+		memcpy(&buf[i * 32], T, clen);
+	}
+
+	/* Clean the stack. */
+	insecure_memzero(&Phctx, sizeof(HMAC_SHA256_CTX));
+	insecure_memzero(&PShctx, sizeof(HMAC_SHA256_CTX));
+	insecure_memzero(U, 32);
+	insecure_memzero(T, 32);
+
+cleanup:
+	insecure_memzero(&hctx, sizeof(HMAC_SHA256_CTX));
+	insecure_memzero(tmp32, 288);
+	insecure_memzero(&u, sizeof(u));
+}
diff --git a/ext/standard/yescrypt/sha256.h b/ext/standard/yescrypt/sha256.h
new file mode 100644
index 0000000000000..6210502ff1b44
--- /dev/null
+++ b/ext/standard/yescrypt/sha256.h
@@ -0,0 +1,129 @@
+/*-
+ * Copyright 2005-2016 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _SHA256_H_
+#define _SHA256_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Use #defines in order to avoid namespace collisions with anyone else's
+ * SHA256 code (e.g., the code in OpenSSL).
+ */
+#define SHA256_Init libcperciva_SHA256_Init
+#define SHA256_Update libcperciva_SHA256_Update
+#define SHA256_Final libcperciva_SHA256_Final
+#define SHA256_Buf libcperciva_SHA256_Buf
+#define SHA256_CTX libcperciva_SHA256_CTX
+#define HMAC_SHA256_Init libcperciva_HMAC_SHA256_Init
+#define HMAC_SHA256_Update libcperciva_HMAC_SHA256_Update
+#define HMAC_SHA256_Final libcperciva_HMAC_SHA256_Final
+#define HMAC_SHA256_Buf libcperciva_HMAC_SHA256_Buf
+#define HMAC_SHA256_CTX libcperciva_HMAC_SHA256_CTX
+
+/* Context structure for SHA256 operations. */
+typedef struct {
+	uint32_t state[8];
+	uint64_t count;
+	uint8_t buf[64];
+} SHA256_CTX;
+
+/**
+ * SHA256_Init(ctx):
+ * Initialize the SHA256 context ${ctx}.
+ */
+void SHA256_Init(SHA256_CTX *);
+
+/**
+ * SHA256_Update(ctx, in, len):
+ * Input ${len} bytes from ${in} into the SHA256 context ${ctx}.
+ */
+void SHA256_Update(SHA256_CTX *, const void *, size_t);
+
+/**
+ * SHA256_Final(digest, ctx):
+ * Output the SHA256 hash of the data input to the context ${ctx} into the
+ * buffer ${digest}.
+ */
+void SHA256_Final(uint8_t[32], SHA256_CTX *);
+
+/**
+ * SHA256_Buf(in, len, digest):
+ * Compute the SHA256 hash of ${len} bytes from ${in} and write it to ${digest}.
+ */
+void SHA256_Buf(const void *, size_t, uint8_t[32]);
+
+/* Context structure for HMAC-SHA256 operations. */
+typedef struct {
+	SHA256_CTX ictx;
+	SHA256_CTX octx;
+} HMAC_SHA256_CTX;
+
+/**
+ * HMAC_SHA256_Init(ctx, K, Klen):
+ * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from
+ * ${K}.
+ */
+void HMAC_SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t);
+
+/**
+ * HMAC_SHA256_Update(ctx, in, len):
+ * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}.
+ */
+void HMAC_SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t);
+
+/**
+ * HMAC_SHA256_Final(digest, ctx):
+ * Output the HMAC-SHA256 of the data input to the context ${ctx} into the
+ * buffer ${digest}.
+ */
+void HMAC_SHA256_Final(uint8_t[32], HMAC_SHA256_CTX *);
+
+/**
+ * HMAC_SHA256_Buf(K, Klen, in, len, digest):
+ * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of
+ * length ${Klen}, and write the result to ${digest}.
+ */
+void HMAC_SHA256_Buf(const void *, size_t, const void *, size_t, uint8_t[32]);
+
+/**
+ * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen):
+ * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and
+ * write the output to buf.  The value dkLen must be at most 32 * (2^32 - 1).
+ */
+void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t,
+    uint64_t, uint8_t *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_SHA256_H_ */
diff --git a/ext/standard/yescrypt/sysendian.h b/ext/standard/yescrypt/sysendian.h
new file mode 100644
index 0000000000000..c51730d1cd5d5
--- /dev/null
+++ b/ext/standard/yescrypt/sysendian.h
@@ -0,0 +1,122 @@
+/*-
+ * Copyright 2007-2014 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _SYSENDIAN_H_
+#define _SYSENDIAN_H_
+
+#include <stdint.h>
+
+/* Avoid namespace collisions with BSD <sys/endian.h>. */
+#define be32dec libcperciva_be32dec
+#define be32enc libcperciva_be32enc
+#define be64enc libcperciva_be64enc
+#define le32dec libcperciva_le32dec
+#define le32enc libcperciva_le32enc
+#define le64dec libcperciva_le64dec
+#define le64enc libcperciva_le64enc
+
+static inline uint32_t
+be32dec(const void * pp)
+{
+	const uint8_t * p = (uint8_t const *)pp;
+
+	return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) +
+	    ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24));
+}
+
+static inline void
+be32enc(void * pp, uint32_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[3] = x & 0xff;
+	p[2] = (x >> 8) & 0xff;
+	p[1] = (x >> 16) & 0xff;
+	p[0] = (x >> 24) & 0xff;
+}
+
+static inline void
+be64enc(void * pp, uint64_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[7] = x & 0xff;
+	p[6] = (x >> 8) & 0xff;
+	p[5] = (x >> 16) & 0xff;
+	p[4] = (x >> 24) & 0xff;
+	p[3] = (x >> 32) & 0xff;
+	p[2] = (x >> 40) & 0xff;
+	p[1] = (x >> 48) & 0xff;
+	p[0] = (x >> 56) & 0xff;
+}
+
+static inline uint32_t
+le32dec(const void * pp)
+{
+	const uint8_t * p = (uint8_t const *)pp;
+
+	return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) +
+	    ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24));
+}
+
+static inline void
+le32enc(void * pp, uint32_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[0] = x & 0xff;
+	p[1] = (x >> 8) & 0xff;
+	p[2] = (x >> 16) & 0xff;
+	p[3] = (x >> 24) & 0xff;
+}
+
+static inline uint64_t
+le64dec(const void * pp)
+{
+	const uint8_t * p = (uint8_t const *)pp;
+
+	return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) +
+	    ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) +
+	    ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) +
+	    ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56));
+}
+
+static inline void
+le64enc(void * pp, uint64_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[0] = x & 0xff;
+	p[1] = (x >> 8) & 0xff;
+	p[2] = (x >> 16) & 0xff;
+	p[3] = (x >> 24) & 0xff;
+	p[4] = (x >> 32) & 0xff;
+	p[5] = (x >> 40) & 0xff;
+	p[6] = (x >> 48) & 0xff;
+	p[7] = (x >> 56) & 0xff;
+}
+
+#endif /* !_SYSENDIAN_H_ */
diff --git a/ext/standard/yescrypt/yescrypt-common.c b/ext/standard/yescrypt/yescrypt-common.c
new file mode 100644
index 0000000000000..459bdc4416a23
--- /dev/null
+++ b/ext/standard/yescrypt/yescrypt-common.c
@@ -0,0 +1,133 @@
+/*-
+ * Copyright 2013-2018 Alexander Peslyak
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "insecure_memzero.h"
+
+#define YESCRYPT_INTERNAL
+#include "yescrypt.h"
+
+static uint8_t *encode64(uint8_t *dst, size_t dstlen,
+    const uint8_t *src, size_t srclen)
+{
+	size_t i;
+
+	for (i = 0; i < srclen; ) {
+		uint8_t *dnext;
+		uint32_t value = 0, bits = 0;
+		do {
+			value |= (uint32_t)src[i++] << bits;
+			bits += 8;
+		} while (bits < 24 && i < srclen);
+		dnext = yescrypt_encode64_uint32_fixed(dst, dstlen, value, bits);
+		if (!dnext)
+			return NULL;
+		dstlen -= dnext - dst;
+		dst = dnext;
+	}
+
+	if (dstlen < 1)
+		return NULL;
+
+	*dst = 0; /* NUL terminate just in case */
+
+	return dst;
+}
+
+uint8_t *yescrypt_r(const yescrypt_shared_t *shared, yescrypt_local_t *local,
+    const uint8_t *passwd, size_t passwdlen,
+    const uint8_t *setting,
+    const yescrypt_binary_t *key,
+    uint8_t *buf, size_t buflen)
+{
+	unsigned char saltbin[64], hashbin[32];
+	const uint8_t *src, *saltstr, *salt;
+	uint8_t *dst;
+	size_t need, prefixlen, saltstrlen, saltlen;
+	yescrypt_params_t params = { .p = 1 };
+
+	/* PHP change: extracted the settings parsing */
+	src = yescrypt_parse_settings(setting, &params, key);
+	if (!src) {
+		return NULL;
+	}
+
+	prefixlen = src - setting;
+
+	saltstr = src;
+	src = (uint8_t *)strrchr((char *)saltstr, '$');
+	if (src)
+		saltstrlen = src - saltstr;
+	else
+		saltstrlen = strlen((char *)saltstr);
+
+	if (setting[1] == '7') {
+		salt = saltstr;
+		saltlen = saltstrlen;
+	} else {
+		const uint8_t *saltend;
+
+		saltlen = sizeof(saltbin);
+		saltend = yescrypt_decode64(saltbin, &saltlen, saltstr, saltstrlen);
+
+		if (!saltend || (size_t)(saltend - saltstr) != saltstrlen)
+			goto fail;
+
+		salt = saltbin;
+
+		if (key) {
+			/* PHP change: removed so we don't carry the encrypt implementation */
+			ZEND_UNREACHABLE();
+		}
+	}
+
+	need = prefixlen + saltstrlen + 1 + HASH_LEN + 1;
+	if (need > buflen || need < saltstrlen)
+		goto fail;
+
+	if (yescrypt_kdf(shared, local, passwd, passwdlen, salt, saltlen,
+	    &params, hashbin, sizeof(hashbin)))
+		goto fail;
+
+	if (key) {
+		/* PHP change: removed so we don't carry the encrypt implementation */
+		ZEND_UNREACHABLE();
+	}
+
+	dst = buf;
+	memcpy(dst, setting, prefixlen + saltstrlen);
+	dst += prefixlen + saltstrlen;
+	*dst++ = '$';
+
+	dst = encode64(dst, buflen - (dst - buf), hashbin, sizeof(hashbin));
+	insecure_memzero(hashbin, sizeof(hashbin));
+	if (!dst || dst >= buf + buflen)
+		return NULL;
+
+	*dst = 0; /* NUL termination */
+
+	return buf;
+
+fail:
+	insecure_memzero(saltbin, sizeof(saltbin));
+	insecure_memzero(hashbin, sizeof(hashbin));
+	return NULL;
+}
diff --git a/ext/standard/yescrypt/yescrypt-config.c b/ext/standard/yescrypt/yescrypt-config.c
new file mode 100644
index 0000000000000..50bbc721ce28d
--- /dev/null
+++ b/ext/standard/yescrypt/yescrypt-config.c
@@ -0,0 +1,442 @@
+/*-
+ * Copyright 2013-2018 Alexander Peslyak
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#define YESCRYPT_INTERNAL
+#include "yescrypt.h"
+
+static const char * const itoa64 =
+	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static const uint8_t atoi64_partial[77] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+	64, 64, 64, 64, 64, 64, 64,
+	12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+	25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+	64, 64, 64, 64, 64, 64,
+	38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+	51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63
+};
+
+static uint8_t *encode64_uint32(uint8_t *dst, size_t dstlen,
+    uint32_t src, uint32_t min)
+{
+	uint32_t start = 0, end = 47, chars = 1, bits = 0;
+
+	if (src < min)
+		return NULL;
+	src -= min;
+
+	do {
+		uint32_t count = (end + 1 - start) << bits;
+		if (src < count)
+			break;
+		if (start >= 63)
+			return NULL;
+		start = end + 1;
+		end = start + (62 - end) / 2;
+		src -= count;
+		chars++;
+		bits += 6;
+	} while (1);
+
+	if (dstlen <= chars) /* require room for a NUL terminator */
+		return NULL;
+
+	*dst++ = itoa64[start + (src >> bits)];
+
+	while (--chars) {
+		bits -= 6;
+		*dst++ = itoa64[(src >> bits) & 0x3f];
+	}
+
+	*dst = 0; /* NUL terminate just in case */
+
+	return dst;
+}
+
+static inline uint32_t atoi64(uint8_t src)
+{
+	if (src >= '.' && src <= 'z')
+		return atoi64_partial[src - '.'];
+
+	return 64;
+}
+
+static const uint8_t *decode64_uint32(uint32_t *dst,
+    const uint8_t *src, uint32_t min)
+{
+	uint32_t start = 0, end = 47, chars = 1, bits = 0;
+	uint32_t c;
+
+	c = atoi64(*src++);
+	if (c > 63)
+		goto fail;
+
+	*dst = min;
+	while (c > end) {
+		*dst += (end + 1 - start) << bits;
+		start = end + 1;
+		end = start + (62 - end) / 2;
+		chars++;
+		bits += 6;
+	}
+
+	*dst += (c - start) << bits;
+
+	while (--chars) {
+		c = atoi64(*src++);
+		if (c > 63)
+			goto fail;
+		bits -= 6;
+		*dst += c << bits;
+	}
+
+	return src;
+
+fail:
+	*dst = 0;
+	return NULL;
+}
+
+uint8_t *yescrypt_encode64_uint32_fixed(uint8_t *dst, size_t dstlen,
+										uint32_t src, uint32_t srcbits)
+{
+	uint32_t bits;
+
+	for (bits = 0; bits < srcbits; bits += 6) {
+		if (dstlen < 2)
+			return NULL;
+		*dst++ = itoa64[src & 0x3f];
+		dstlen--;
+		src >>= 6;
+	}
+
+	if (src || dstlen < 1)
+		return NULL;
+
+	*dst = 0; /* NUL terminate just in case */
+
+	return dst;
+}
+
+static uint8_t *encode64(uint8_t *dst, size_t dstlen,
+    const uint8_t *src, size_t srclen)
+{
+	size_t i;
+
+	for (i = 0; i < srclen; ) {
+		uint8_t *dnext;
+		uint32_t value = 0, bits = 0;
+		do {
+			value |= (uint32_t)src[i++] << bits;
+			bits += 8;
+		} while (bits < 24 && i < srclen);
+		dnext = yescrypt_encode64_uint32_fixed(dst, dstlen, value, bits);
+		if (!dnext)
+			return NULL;
+		dstlen -= dnext - dst;
+		dst = dnext;
+	}
+
+	if (dstlen < 1)
+		return NULL;
+
+	*dst = 0; /* NUL terminate just in case */
+
+	return dst;
+}
+
+static const uint8_t *decode64_uint32_fixed(uint32_t *dst, uint32_t dstbits,
+    const uint8_t *src)
+{
+	uint32_t bits;
+
+	*dst = 0;
+	for (bits = 0; bits < dstbits; bits += 6) {
+		uint32_t c = atoi64(*src++);
+		if (c > 63) {
+			*dst = 0;
+			return NULL;
+		}
+		*dst |= c << bits;
+	}
+
+	return src;
+}
+
+const uint8_t *yescrypt_decode64(uint8_t *dst, size_t *dstlen,
+							     const uint8_t *src, size_t srclen)
+{
+	size_t dstpos = 0;
+
+	while (dstpos <= *dstlen && srclen) {
+		uint32_t value = 0, bits = 0;
+		while (srclen--) {
+			uint32_t c = atoi64(*src);
+			if (c > 63) {
+				srclen = 0;
+				break;
+			}
+			src++;
+			value |= c << bits;
+			bits += 6;
+			if (bits >= 24)
+				break;
+		}
+		if (!bits)
+			break;
+		if (bits < 12) /* must have at least one full byte */
+			goto fail;
+		while (dstpos++ < *dstlen) {
+			*dst++ = value;
+			value >>= 8;
+			bits -= 8;
+			if (bits < 8) { /* 2 or 4 */
+				if (value) /* must be 0 */
+					goto fail;
+				bits = 0;
+				break;
+			}
+		}
+		if (bits)
+			goto fail;
+	}
+
+	if (!srclen && dstpos <= *dstlen) {
+		*dstlen = dstpos;
+		return src;
+	}
+
+	fail:
+	*dstlen = 0;
+	return NULL;
+}
+
+/* PHP change: code to parse the settings extracted from yescrypt_r */
+const uint8_t *yescrypt_parse_settings(const uint8_t *setting, yescrypt_params_t *params, const yescrypt_binary_t *key)
+{
+	if (setting[0] != '$' ||
+		(setting[1] != '7' && setting[1] != 'y') ||
+		setting[2] != '$')
+		return NULL;
+	const uint8_t *src = setting + 3;
+
+	if (setting[1] == '7') {
+		uint32_t N_log2 = atoi64(*src++);
+		if (N_log2 < 1 || N_log2 > 63)
+			return NULL;
+		params->N = (uint64_t)1 << N_log2;
+
+		src = decode64_uint32_fixed(&params->r, 30, src);
+		if (!src)
+			return NULL;
+
+		src = decode64_uint32_fixed(&params->p, 30, src);
+		if (!src)
+			return NULL;
+
+		if (key)
+			return NULL;
+	} else {
+		uint32_t flavor, N_log2;
+
+		src = decode64_uint32(&flavor, src, 0);
+		if (!src)
+			return NULL;
+
+		if (flavor < YESCRYPT_RW) {
+			params->flags = flavor;
+		} else if (flavor <= YESCRYPT_RW + (YESCRYPT_RW_FLAVOR_MASK >> 2)) {
+			params->flags = YESCRYPT_RW + ((flavor - YESCRYPT_RW) << 2);
+		} else {
+			return NULL;
+		}
+
+		src = decode64_uint32(&N_log2, src, 1);
+		if (!src || N_log2 > 63)
+			return NULL;
+		params->N = (uint64_t)1 << N_log2;
+
+		src = decode64_uint32(&params->r, src, 1);
+		if (!src)
+			return NULL;
+
+		if (*src != '$') {
+			uint32_t have;
+
+			src = decode64_uint32(&have, src, 1);
+			if (!src)
+				return NULL;
+
+			if (have & 1) {
+				src = decode64_uint32(&params->p, src, 2);
+				if (!src)
+					return NULL;
+			}
+
+			if (have & 2) {
+				src = decode64_uint32(&params->t, src, 1);
+				if (!src)
+					return NULL;
+			}
+
+			if (have & 4) {
+				src = decode64_uint32(&params->g, src, 1);
+				if (!src)
+					return NULL;
+			}
+
+			if (have & 8) {
+				uint32_t NROM_log2;
+				src = decode64_uint32(&NROM_log2, src, 1);
+				if (!src || NROM_log2 > 63)
+					return NULL;
+				params->NROM = (uint64_t)1 << NROM_log2;
+			}
+		}
+
+		if (*src++ != '$')
+			return NULL;
+	}
+
+	return src;
+}
+
+static uint32_t N2log2(uint64_t N)
+{
+	uint32_t N_log2;
+
+	if (N < 2)
+		return 0;
+
+	N_log2 = 2;
+	while (N >> N_log2 != 0)
+		N_log2++;
+	N_log2--;
+
+	if (N >> N_log2 != 1)
+		return 0;
+
+	return N_log2;
+}
+
+uint8_t *yescrypt_encode_params_r(const yescrypt_params_t *params,
+    const uint8_t *src, size_t srclen,
+    uint8_t *buf, size_t buflen)
+{
+	uint32_t flavor, N_log2, NROM_log2, have;
+	uint8_t *dst;
+
+	if (srclen > SIZE_MAX / 16)
+		return NULL;
+
+	if (params->flags < YESCRYPT_RW) {
+		flavor = params->flags;
+	} else if ((params->flags & YESCRYPT_MODE_MASK) == YESCRYPT_RW &&
+	    params->flags <= (YESCRYPT_RW | YESCRYPT_RW_FLAVOR_MASK)) {
+		flavor = YESCRYPT_RW + (params->flags >> 2);
+	} else {
+		return NULL;
+	}
+
+	N_log2 = N2log2(params->N);
+	if (!N_log2)
+		return NULL;
+
+	NROM_log2 = N2log2(params->NROM);
+	if (params->NROM && !NROM_log2)
+		return NULL;
+
+	if ((uint64_t)params->r * (uint64_t)params->p >= (1U << 30))
+		return NULL;
+
+	dst = buf;
+	*dst++ = '$';
+	*dst++ = 'y';
+	*dst++ = '$';
+
+	dst = encode64_uint32(dst, buflen - (dst - buf), flavor, 0);
+	if (!dst)
+		return NULL;
+
+	dst = encode64_uint32(dst, buflen - (dst - buf), N_log2, 1);
+	if (!dst)
+		return NULL;
+
+	dst = encode64_uint32(dst, buflen - (dst - buf), params->r, 1);
+	if (!dst)
+		return NULL;
+
+	have = 0;
+	if (params->p != 1)
+		have |= 1;
+	if (params->t)
+		have |= 2;
+	if (params->g)
+		have |= 4;
+	if (NROM_log2)
+		have |= 8;
+
+	if (have) {
+		dst = encode64_uint32(dst, buflen - (dst - buf), have, 1);
+		if (!dst)
+			return NULL;
+	}
+
+	if (params->p != 1) {
+		dst = encode64_uint32(dst, buflen - (dst - buf), params->p, 2);
+		if (!dst)
+			return NULL;
+	}
+
+	if (params->t) {
+		dst = encode64_uint32(dst, buflen - (dst - buf), params->t, 1);
+		if (!dst)
+			return NULL;
+	}
+
+	if (params->g) {
+		dst = encode64_uint32(dst, buflen - (dst - buf), params->g, 1);
+		if (!dst)
+			return NULL;
+	}
+
+	if (NROM_log2) {
+		dst = encode64_uint32(dst, buflen - (dst - buf), NROM_log2, 1);
+		if (!dst)
+			return NULL;
+	}
+
+	if (dst >= buf + buflen)
+		return NULL;
+
+	*dst++ = '$';
+
+	dst = encode64(dst, buflen - (dst - buf), src, srclen);
+	if (!dst || dst >= buf + buflen)
+		return NULL;
+
+	*dst = 0; /* NUL termination */
+
+	return buf;
+}
diff --git a/ext/standard/yescrypt/yescrypt-opt.c b/ext/standard/yescrypt/yescrypt-opt.c
new file mode 100644
index 0000000000000..7e960940d1b9a
--- /dev/null
+++ b/ext/standard/yescrypt/yescrypt-opt.c
@@ -0,0 +1,1532 @@
+/*-
+ * Copyright 2009 Colin Percival
+ * Copyright 2012-2018 Alexander Peslyak
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+
+/* PHP change: Commented out the warnings */
+#if 0
+/*
+ * AVX and especially XOP speed up Salsa20 a lot, but this mostly matters for
+ * classic scrypt and for YESCRYPT_WORM (which use 8 rounds of Salsa20 per
+ * sub-block), and much less so for YESCRYPT_RW (which uses 2 rounds of Salsa20
+ * per block except during pwxform S-box initialization).
+ */
+#ifdef __XOP__
+#warning "Note: XOP is enabled.  That's great."
+#elif defined(__AVX__)
+#warning "Note: AVX is enabled, which is great for classic scrypt and YESCRYPT_WORM, but is sometimes slightly slower than plain SSE2 for YESCRYPT_RW"
+#elif defined(__SSE2__)
+#warning "Note: AVX and XOP are not enabled, which is great for YESCRYPT_RW, but they would substantially improve performance at classic scrypt and YESCRYPT_WORM"
+#elif defined(__x86_64__) || defined(__i386__)
+#warning "SSE2 not enabled.  Expect poor performance."
+#else
+#warning "Note: building generic code for non-x86.  That's OK."
+#endif
+#endif
+
+/*
+ * The SSE4 code version has fewer instructions than the generic SSE2 version,
+ * but all of the instructions are SIMD, thereby wasting the scalar execution
+ * units.  Thus, the generic SSE2 version below actually runs faster on some
+ * CPUs due to its balanced mix of SIMD and scalar instructions.
+ */
+#undef USE_SSE4_FOR_32BIT
+
+#ifdef __SSE2__
+/*
+ * GCC before 4.9 would by default unnecessarily use store/load (without
+ * SSE4.1) or (V)PEXTR (with SSE4.1 or AVX) instead of simply (V)MOV.
+ * This was tracked as GCC bug 54349.
+ * "-mtune=corei7" works around this, but is only supported for GCC 4.6+.
+ * We use inline asm for pre-4.6 GCC, further down this file.
+ */
+#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && __GNUC_MINOR__ < 9 && \
+    !defined(__clang__) && !defined(__ICC)
+#pragma GCC target ("tune=corei7")
+#endif
+#include <emmintrin.h>
+#ifdef __XOP__
+#include <x86intrin.h>
+#endif
+#elif defined(__SSE__)
+#include <xmmintrin.h>
+#endif
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "insecure_memzero.h"
+#include "sha256.h"
+#include "sysendian.h"
+
+#define YESCRYPT_INTERNAL
+#include "yescrypt.h"
+
+#include "yescrypt-platform.c"
+
+#if __STDC_VERSION__ >= 199901L
+/* Have restrict */
+#elif defined(__GNUC__)
+#define restrict __restrict
+#else
+#define restrict
+#endif
+
+#ifdef __GNUC__
+#define unlikely(exp) __builtin_expect(exp, 0)
+#else
+#define unlikely(exp) (exp)
+#endif
+
+#ifdef __SSE__
+#define PREFETCH(x, hint) _mm_prefetch((const char *)(x), (hint));
+#else
+#undef PREFETCH
+#endif
+
+typedef union {
+	uint32_t w[16];
+	uint64_t d[8];
+#ifdef __SSE2__
+	__m128i q[4];
+#endif
+} salsa20_blk_t;
+
+static inline void salsa20_simd_shuffle(const salsa20_blk_t *Bin,
+    salsa20_blk_t *Bout)
+{
+#define COMBINE(out, in1, in2) \
+	Bout->d[out] = Bin->w[in1 * 2] | ((uint64_t)Bin->w[in2 * 2 + 1] << 32);
+	COMBINE(0, 0, 2)
+	COMBINE(1, 5, 7)
+	COMBINE(2, 2, 4)
+	COMBINE(3, 7, 1)
+	COMBINE(4, 4, 6)
+	COMBINE(5, 1, 3)
+	COMBINE(6, 6, 0)
+	COMBINE(7, 3, 5)
+#undef COMBINE
+}
+
+static inline void salsa20_simd_unshuffle(const salsa20_blk_t *Bin,
+    salsa20_blk_t *Bout)
+{
+#define UNCOMBINE(out, in1, in2) \
+	Bout->w[out * 2] = Bin->d[in1]; \
+	Bout->w[out * 2 + 1] = Bin->d[in2] >> 32;
+	UNCOMBINE(0, 0, 6)
+	UNCOMBINE(1, 5, 3)
+	UNCOMBINE(2, 2, 0)
+	UNCOMBINE(3, 7, 5)
+	UNCOMBINE(4, 4, 2)
+	UNCOMBINE(5, 1, 7)
+	UNCOMBINE(6, 6, 4)
+	UNCOMBINE(7, 3, 1)
+#undef UNCOMBINE
+}
+
+#ifdef __SSE2__
+#define DECL_X \
+	__m128i X0, X1, X2, X3;
+#define DECL_Y \
+	__m128i Y0, Y1, Y2, Y3;
+#define READ_X(in) \
+	X0 = (in).q[0]; X1 = (in).q[1]; X2 = (in).q[2]; X3 = (in).q[3];
+#define WRITE_X(out) \
+	(out).q[0] = X0; (out).q[1] = X1; (out).q[2] = X2; (out).q[3] = X3;
+
+#ifdef __XOP__
+#define ARX(out, in1, in2, s) \
+	out = _mm_xor_si128(out, _mm_roti_epi32(_mm_add_epi32(in1, in2), s));
+#else
+#define ARX(out, in1, in2, s) { \
+	__m128i tmp = _mm_add_epi32(in1, in2); \
+	out = _mm_xor_si128(out, _mm_slli_epi32(tmp, s)); \
+	out = _mm_xor_si128(out, _mm_srli_epi32(tmp, 32 - s)); \
+}
+#endif
+
+#define SALSA20_2ROUNDS \
+	/* Operate on "columns" */ \
+	ARX(X1, X0, X3, 7) \
+	ARX(X2, X1, X0, 9) \
+	ARX(X3, X2, X1, 13) \
+	ARX(X0, X3, X2, 18) \
+	/* Rearrange data */ \
+	X1 = _mm_shuffle_epi32(X1, 0x93); \
+	X2 = _mm_shuffle_epi32(X2, 0x4E); \
+	X3 = _mm_shuffle_epi32(X3, 0x39); \
+	/* Operate on "rows" */ \
+	ARX(X3, X0, X1, 7) \
+	ARX(X2, X3, X0, 9) \
+	ARX(X1, X2, X3, 13) \
+	ARX(X0, X1, X2, 18) \
+	/* Rearrange data */ \
+	X1 = _mm_shuffle_epi32(X1, 0x39); \
+	X2 = _mm_shuffle_epi32(X2, 0x4E); \
+	X3 = _mm_shuffle_epi32(X3, 0x93);
+
+/**
+ * Apply the Salsa20 core to the block provided in (X0 ... X3).
+ */
+#define SALSA20_wrapper(out, rounds) { \
+	__m128i Z0 = X0, Z1 = X1, Z2 = X2, Z3 = X3; \
+	rounds \
+	(out).q[0] = X0 = _mm_add_epi32(X0, Z0); \
+	(out).q[1] = X1 = _mm_add_epi32(X1, Z1); \
+	(out).q[2] = X2 = _mm_add_epi32(X2, Z2); \
+	(out).q[3] = X3 = _mm_add_epi32(X3, Z3); \
+}
+
+/**
+ * Apply the Salsa20/2 core to the block provided in X.
+ */
+#define SALSA20_2(out) \
+	SALSA20_wrapper(out, SALSA20_2ROUNDS)
+
+#define SALSA20_8ROUNDS \
+	SALSA20_2ROUNDS SALSA20_2ROUNDS SALSA20_2ROUNDS SALSA20_2ROUNDS
+
+#define XOR_X(in) \
+	X0 = _mm_xor_si128(X0, (in).q[0]); \
+	X1 = _mm_xor_si128(X1, (in).q[1]); \
+	X2 = _mm_xor_si128(X2, (in).q[2]); \
+	X3 = _mm_xor_si128(X3, (in).q[3]);
+
+#define XOR_X_2(in1, in2) \
+	X0 = _mm_xor_si128((in1).q[0], (in2).q[0]); \
+	X1 = _mm_xor_si128((in1).q[1], (in2).q[1]); \
+	X2 = _mm_xor_si128((in1).q[2], (in2).q[2]); \
+	X3 = _mm_xor_si128((in1).q[3], (in2).q[3]);
+
+#define XOR_X_WRITE_XOR_Y_2(out, in) \
+	(out).q[0] = Y0 = _mm_xor_si128((out).q[0], (in).q[0]); \
+	(out).q[1] = Y1 = _mm_xor_si128((out).q[1], (in).q[1]); \
+	(out).q[2] = Y2 = _mm_xor_si128((out).q[2], (in).q[2]); \
+	(out).q[3] = Y3 = _mm_xor_si128((out).q[3], (in).q[3]); \
+	X0 = _mm_xor_si128(X0, Y0); \
+	X1 = _mm_xor_si128(X1, Y1); \
+	X2 = _mm_xor_si128(X2, Y2); \
+	X3 = _mm_xor_si128(X3, Y3);
+
+/**
+ * Apply the Salsa20/8 core to the block provided in X ^ in.
+ */
+#define SALSA20_8_XOR_MEM(in, out) \
+	XOR_X(in) \
+	SALSA20_wrapper(out, SALSA20_8ROUNDS)
+
+#define INTEGERIFY _mm_cvtsi128_si32(X0)
+
+#else /* !defined(__SSE2__) */
+
+#define DECL_X \
+	salsa20_blk_t X;
+#define DECL_Y \
+	salsa20_blk_t Y;
+
+#define COPY(out, in) \
+	(out).d[0] = (in).d[0]; \
+	(out).d[1] = (in).d[1]; \
+	(out).d[2] = (in).d[2]; \
+	(out).d[3] = (in).d[3]; \
+	(out).d[4] = (in).d[4]; \
+	(out).d[5] = (in).d[5]; \
+	(out).d[6] = (in).d[6]; \
+	(out).d[7] = (in).d[7];
+
+#define READ_X(in) COPY(X, in)
+#define WRITE_X(out) COPY(out, X)
+
+/**
+ * salsa20(B):
+ * Apply the Salsa20 core to the provided block.
+ */
+static inline void salsa20(salsa20_blk_t *restrict B,
+    salsa20_blk_t *restrict Bout, uint32_t doublerounds)
+{
+	salsa20_blk_t X;
+#define x X.w
+
+	salsa20_simd_unshuffle(B, &X);
+
+	do {
+#define R(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
+		/* Operate on columns */
+		x[ 4] ^= R(x[ 0]+x[12], 7);  x[ 8] ^= R(x[ 4]+x[ 0], 9);
+		x[12] ^= R(x[ 8]+x[ 4],13);  x[ 0] ^= R(x[12]+x[ 8],18);
+
+		x[ 9] ^= R(x[ 5]+x[ 1], 7);  x[13] ^= R(x[ 9]+x[ 5], 9);
+		x[ 1] ^= R(x[13]+x[ 9],13);  x[ 5] ^= R(x[ 1]+x[13],18);
+
+		x[14] ^= R(x[10]+x[ 6], 7);  x[ 2] ^= R(x[14]+x[10], 9);
+		x[ 6] ^= R(x[ 2]+x[14],13);  x[10] ^= R(x[ 6]+x[ 2],18);
+
+		x[ 3] ^= R(x[15]+x[11], 7);  x[ 7] ^= R(x[ 3]+x[15], 9);
+		x[11] ^= R(x[ 7]+x[ 3],13);  x[15] ^= R(x[11]+x[ 7],18);
+
+		/* Operate on rows */
+		x[ 1] ^= R(x[ 0]+x[ 3], 7);  x[ 2] ^= R(x[ 1]+x[ 0], 9);
+		x[ 3] ^= R(x[ 2]+x[ 1],13);  x[ 0] ^= R(x[ 3]+x[ 2],18);
+
+		x[ 6] ^= R(x[ 5]+x[ 4], 7);  x[ 7] ^= R(x[ 6]+x[ 5], 9);
+		x[ 4] ^= R(x[ 7]+x[ 6],13);  x[ 5] ^= R(x[ 4]+x[ 7],18);
+
+		x[11] ^= R(x[10]+x[ 9], 7);  x[ 8] ^= R(x[11]+x[10], 9);
+		x[ 9] ^= R(x[ 8]+x[11],13);  x[10] ^= R(x[ 9]+x[ 8],18);
+
+		x[12] ^= R(x[15]+x[14], 7);  x[13] ^= R(x[12]+x[15], 9);
+		x[14] ^= R(x[13]+x[12],13);  x[15] ^= R(x[14]+x[13],18);
+#undef R
+	} while (--doublerounds);
+#undef x
+
+	{
+		uint32_t i;
+		salsa20_simd_shuffle(&X, Bout);
+		for (i = 0; i < 16; i += 4) {
+			B->w[i] = Bout->w[i] += B->w[i];
+			B->w[i + 1] = Bout->w[i + 1] += B->w[i + 1];
+			B->w[i + 2] = Bout->w[i + 2] += B->w[i + 2];
+			B->w[i + 3] = Bout->w[i + 3] += B->w[i + 3];
+		}
+	}
+
+#if 0
+	/* Too expensive */
+	insecure_memzero(&X, sizeof(X));
+#endif
+}
+
+/**
+ * Apply the Salsa20/2 core to the block provided in X.
+ */
+#define SALSA20_2(out) \
+	salsa20(&X, &out, 1);
+
+#define XOR(out, in1, in2) \
+	(out).d[0] = (in1).d[0] ^ (in2).d[0]; \
+	(out).d[1] = (in1).d[1] ^ (in2).d[1]; \
+	(out).d[2] = (in1).d[2] ^ (in2).d[2]; \
+	(out).d[3] = (in1).d[3] ^ (in2).d[3]; \
+	(out).d[4] = (in1).d[4] ^ (in2).d[4]; \
+	(out).d[5] = (in1).d[5] ^ (in2).d[5]; \
+	(out).d[6] = (in1).d[6] ^ (in2).d[6]; \
+	(out).d[7] = (in1).d[7] ^ (in2).d[7];
+
+#define XOR_X(in) XOR(X, X, in)
+#define XOR_X_2(in1, in2) XOR(X, in1, in2)
+#define XOR_X_WRITE_XOR_Y_2(out, in) \
+	XOR(Y, out, in) \
+	COPY(out, Y) \
+	XOR(X, X, Y)
+
+/**
+ * Apply the Salsa20/8 core to the block provided in X ^ in.
+ */
+#define SALSA20_8_XOR_MEM(in, out) \
+	XOR_X(in); \
+	salsa20(&X, &out, 4);
+
+#define INTEGERIFY (uint32_t)X.d[0]
+#endif
+
+/**
+ * blockmix_salsa8(Bin, Bout, r):
+ * Compute Bout = BlockMix_{salsa20/8, r}(Bin).  The input Bin must be 128r
+ * bytes in length; the output Bout must also be the same size.
+ */
+static void blockmix_salsa8(const salsa20_blk_t *restrict Bin,
+    salsa20_blk_t *restrict Bout, size_t r)
+{
+	size_t i;
+	DECL_X
+
+	READ_X(Bin[r * 2 - 1])
+	for (i = 0; i < r; i++) {
+		SALSA20_8_XOR_MEM(Bin[i * 2], Bout[i])
+		SALSA20_8_XOR_MEM(Bin[i * 2 + 1], Bout[r + i])
+	}
+}
+
+static uint32_t blockmix_salsa8_xor(const salsa20_blk_t *restrict Bin1,
+    const salsa20_blk_t *restrict Bin2, salsa20_blk_t *restrict Bout,
+    size_t r)
+{
+	size_t i;
+	DECL_X
+
+#ifdef PREFETCH
+	PREFETCH(&Bin2[r * 2 - 1], _MM_HINT_T0)
+	for (i = 0; i < r - 1; i++) {
+		PREFETCH(&Bin2[i * 2], _MM_HINT_T0)
+		PREFETCH(&Bin2[i * 2 + 1], _MM_HINT_T0)
+	}
+	PREFETCH(&Bin2[i * 2], _MM_HINT_T0)
+#endif
+
+	XOR_X_2(Bin1[r * 2 - 1], Bin2[r * 2 - 1])
+	for (i = 0; i < r; i++) {
+		XOR_X(Bin1[i * 2])
+		SALSA20_8_XOR_MEM(Bin2[i * 2], Bout[i])
+		XOR_X(Bin1[i * 2 + 1])
+		SALSA20_8_XOR_MEM(Bin2[i * 2 + 1], Bout[r + i])
+	}
+
+	return INTEGERIFY;
+}
+
+/* This is tunable */
+#define Swidth 8
+
+/* Not tunable in this implementation, hard-coded in a few places */
+#define PWXsimple 2
+#define PWXgather 4
+
+/* Derived values.  Not tunable except via Swidth above. */
+#define PWXbytes (PWXgather * PWXsimple * 8)
+#define Sbytes (3 * (1 << Swidth) * PWXsimple * 8)
+#define Smask (((1 << Swidth) - 1) * PWXsimple * 8)
+#define Smask2 (((uint64_t)Smask << 32) | Smask)
+
+#define DECL_SMASK2REG /* empty */
+#define FORCE_REGALLOC_3 /* empty */
+#define MAYBE_MEMORY_BARRIER /* empty */
+
+#ifdef __SSE2__
+/*
+ * (V)PSRLDQ and (V)PSHUFD have higher throughput than (V)PSRLQ on some CPUs
+ * starting with Sandy Bridge.  Additionally, PSHUFD uses separate source and
+ * destination registers, whereas the shifts would require an extra move
+ * instruction for our code when building without AVX.  Unfortunately, PSHUFD
+ * is much slower on Conroe (4 cycles latency vs. 1 cycle latency for PSRLQ)
+ * and somewhat slower on some non-Intel CPUs (luckily not including AMD
+ * Bulldozer and Piledriver).
+ */
+#ifdef __AVX__
+#define HI32(X) \
+	_mm_srli_si128((X), 4)
+#elif 1 /* As an option, check for __SSE4_1__ here not to hurt Conroe */
+#define HI32(X) \
+	_mm_shuffle_epi32((X), _MM_SHUFFLE(2,3,0,1))
+#else
+#define HI32(X) \
+	_mm_srli_epi64((X), 32)
+#endif
+
+#if defined(__x86_64__) && \
+    __GNUC__ == 4 && __GNUC_MINOR__ < 6 && !defined(__ICC)
+#ifdef __AVX__
+#define MOVQ "vmovq"
+#else
+/* "movq" would be more correct, but "movd" is supported by older binutils
+ * due to an error in AMD's spec for x86-64. */
+#define MOVQ "movd"
+#endif
+#define EXTRACT64(X) ({ \
+	uint64_t result; \
+	__asm__(MOVQ " %1, %0" : "=r" (result) : "x" (X)); \
+	result; \
+})
+#elif defined(__x86_64__) && !defined(_MSC_VER) && !defined(__OPEN64__)
+/* MSVC and Open64 had bugs */
+#define EXTRACT64(X) _mm_cvtsi128_si64(X)
+#elif defined(__x86_64__) && defined(__SSE4_1__)
+/* No known bugs for this intrinsic */
+#include <smmintrin.h>
+#define EXTRACT64(X) _mm_extract_epi64((X), 0)
+#elif defined(USE_SSE4_FOR_32BIT) && defined(__SSE4_1__)
+/* 32-bit */
+#include <smmintrin.h>
+#if 0
+/* This is currently unused by the code below, which instead uses these two
+ * intrinsics explicitly when (!defined(__x86_64__) && defined(__SSE4_1__)) */
+#define EXTRACT64(X) \
+	((uint64_t)(uint32_t)_mm_cvtsi128_si32(X) | \
+	((uint64_t)(uint32_t)_mm_extract_epi32((X), 1) << 32))
+#endif
+#else
+/* 32-bit or compilers with known past bugs in _mm_cvtsi128_si64() */
+#define EXTRACT64(X) \
+	((uint64_t)(uint32_t)_mm_cvtsi128_si32(X) | \
+	((uint64_t)(uint32_t)_mm_cvtsi128_si32(HI32(X)) << 32))
+#endif
+
+#if defined(__x86_64__) && (defined(__AVX__) || !defined(__GNUC__))
+/* 64-bit with AVX */
+/* Force use of 64-bit AND instead of two 32-bit ANDs */
+#undef DECL_SMASK2REG
+#if defined(__GNUC__) && !defined(__ICC)
+#define DECL_SMASK2REG uint64_t Smask2reg = Smask2;
+/* Force use of lower-numbered registers to reduce number of prefixes, relying
+ * on out-of-order execution and register renaming. */
+#define FORCE_REGALLOC_1 \
+	__asm__("" : "=a" (x), "+d" (Smask2reg), "+S" (S0), "+D" (S1));
+#define FORCE_REGALLOC_2 \
+	__asm__("" : : "c" (lo));
+#else
+static volatile uint64_t Smask2var = Smask2;
+#define DECL_SMASK2REG uint64_t Smask2reg = Smask2var;
+#define FORCE_REGALLOC_1 /* empty */
+#define FORCE_REGALLOC_2 /* empty */
+#endif
+#define PWXFORM_SIMD(X) { \
+	uint64_t x; \
+	FORCE_REGALLOC_1 \
+	uint32_t lo = x = EXTRACT64(X) & Smask2reg; \
+	FORCE_REGALLOC_2 \
+	uint32_t hi = x >> 32; \
+	X = _mm_mul_epu32(HI32(X), X); \
+	X = _mm_add_epi64(X, *(__m128i *)(S0 + lo)); \
+	X = _mm_xor_si128(X, *(__m128i *)(S1 + hi)); \
+}
+#elif defined(__x86_64__)
+/* 64-bit without AVX.  This relies on out-of-order execution and register
+ * renaming.  It may actually be fastest on CPUs with AVX(2) as well - e.g.,
+ * it runs great on Haswell. */
+/* PHP change: Commented out warning */
+/*#warning "Note: using x86-64 inline assembly for YESCRYPT_RW.  That's great."*/
+/* We need a compiler memory barrier between sub-blocks to ensure that none of
+ * the writes into what was S2 during processing of the previous sub-block are
+ * postponed until after a read from S0 or S1 in the inline asm code below. */
+#undef MAYBE_MEMORY_BARRIER
+#define MAYBE_MEMORY_BARRIER \
+	__asm__("" : : : "memory");
+#ifdef __ILP32__ /* x32 */
+#define REGISTER_PREFIX "e"
+#else
+#define REGISTER_PREFIX "r"
+#endif
+#define PWXFORM_SIMD(X) { \
+	__m128i H; \
+	__asm__( \
+	    "movd %0, %%rax\n\t" \
+	    "pshufd $0xb1, %0, %1\n\t" \
+	    "andq %2, %%rax\n\t" \
+	    "pmuludq %1, %0\n\t" \
+	    "movl %%eax, %%ecx\n\t" \
+	    "shrq $0x20, %%rax\n\t" \
+	    "paddq (%3,%%" REGISTER_PREFIX "cx), %0\n\t" \
+	    "pxor (%4,%%" REGISTER_PREFIX "ax), %0\n\t" \
+	    : "+x" (X), "=x" (H) \
+	    : "d" (Smask2), "S" (S0), "D" (S1) \
+	    : "cc", "ax", "cx"); \
+}
+#elif defined(USE_SSE4_FOR_32BIT) && defined(__SSE4_1__)
+/* 32-bit with SSE4.1 */
+#define PWXFORM_SIMD(X) { \
+	__m128i x = _mm_and_si128(X, _mm_set1_epi64x(Smask2)); \
+	__m128i s0 = *(__m128i *)(S0 + (uint32_t)_mm_cvtsi128_si32(x)); \
+	__m128i s1 = *(__m128i *)(S1 + (uint32_t)_mm_extract_epi32(x, 1)); \
+	X = _mm_mul_epu32(HI32(X), X); \
+	X = _mm_add_epi64(X, s0); \
+	X = _mm_xor_si128(X, s1); \
+}
+#else
+/* 32-bit without SSE4.1 */
+#define PWXFORM_SIMD(X) { \
+	uint64_t x = EXTRACT64(X) & Smask2; \
+	__m128i s0 = *(__m128i *)(S0 + (uint32_t)x); \
+	__m128i s1 = *(__m128i *)(S1 + (x >> 32)); \
+	X = _mm_mul_epu32(HI32(X), X); \
+	X = _mm_add_epi64(X, s0); \
+	X = _mm_xor_si128(X, s1); \
+}
+#endif
+
+#define PWXFORM_ROUND \
+	PWXFORM_SIMD(X0) \
+	PWXFORM_SIMD(X1) \
+	PWXFORM_SIMD(X2) \
+	PWXFORM_SIMD(X3)
+
+#if defined(__x86_64__) && defined(__GNUC__) && !defined(__ICC)
+#undef FORCE_REGALLOC_3
+#define FORCE_REGALLOC_3 __asm__("" : : "b" (Sw));
+#endif
+
+#else /* !defined(__SSE2__) */
+
+#define PWXFORM_SIMD(x0, x1) { \
+	uint64_t x = x0 & Smask2; \
+	uint64_t *p0 = (uint64_t *)(S0 + (uint32_t)x); \
+	uint64_t *p1 = (uint64_t *)(S1 + (x >> 32)); \
+	x0 = ((x0 >> 32) * (uint32_t)x0 + p0[0]) ^ p1[0]; \
+	x1 = ((x1 >> 32) * (uint32_t)x1 + p0[1]) ^ p1[1]; \
+}
+
+#define PWXFORM_ROUND \
+	PWXFORM_SIMD(X.d[0], X.d[1]) \
+	PWXFORM_SIMD(X.d[2], X.d[3]) \
+	PWXFORM_SIMD(X.d[4], X.d[5]) \
+	PWXFORM_SIMD(X.d[6], X.d[7])
+#endif
+
+/*
+ * This offset helps address the 256-byte write block via the single-byte
+ * displacements encodable in x86(-64) instructions.  It is needed because the
+ * displacements are signed.  Without it, we'd get 4-byte displacements for
+ * half of the writes.  Setting it to 0x80 instead of 0x7c would avoid needing
+ * a displacement for one of the writes, but then the LEA instruction would
+ * need a 4-byte displacement.
+ */
+#define PWXFORM_WRITE_OFFSET 0x7c
+
+#define PWXFORM_WRITE \
+	WRITE_X(*(salsa20_blk_t *)(Sw - PWXFORM_WRITE_OFFSET)) \
+	Sw += 64;
+
+#define PWXFORM { \
+	uint8_t *Sw = S2 + w + PWXFORM_WRITE_OFFSET; \
+	FORCE_REGALLOC_3 \
+	MAYBE_MEMORY_BARRIER \
+	PWXFORM_ROUND \
+	PWXFORM_ROUND PWXFORM_WRITE \
+	PWXFORM_ROUND PWXFORM_WRITE \
+	PWXFORM_ROUND PWXFORM_WRITE \
+	PWXFORM_ROUND PWXFORM_WRITE \
+	PWXFORM_ROUND \
+	w = (w + 64 * 4) & Smask2; \
+	{ \
+		uint8_t *Stmp = S2; \
+		S2 = S1; \
+		S1 = S0; \
+		S0 = Stmp; \
+	} \
+}
+
+typedef struct {
+	uint8_t *S0, *S1, *S2;
+	size_t w;
+} pwxform_ctx_t;
+
+#define Salloc (Sbytes + ((sizeof(pwxform_ctx_t) + 63) & ~63U))
+
+/**
+ * blockmix_pwxform(Bin, Bout, r, S):
+ * Compute Bout = BlockMix_pwxform{salsa20/2, r, S}(Bin).  The input Bin must
+ * be 128r bytes in length; the output Bout must also be the same size.
+ */
+static void blockmix(const salsa20_blk_t *restrict Bin,
+    salsa20_blk_t *restrict Bout, size_t r, pwxform_ctx_t *restrict ctx)
+{
+	uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2;
+	size_t w = ctx->w;
+	size_t i;
+	DECL_X
+
+	/* Convert count of 128-byte blocks to max index of 64-byte block */
+	r = r * 2 - 1;
+
+	READ_X(Bin[r])
+
+	DECL_SMASK2REG
+
+	i = 0;
+	do {
+		XOR_X(Bin[i])
+		PWXFORM
+		if (unlikely(i >= r))
+			break;
+		WRITE_X(Bout[i])
+		i++;
+	} while (1);
+
+	ctx->S0 = S0; ctx->S1 = S1; ctx->S2 = S2;
+	ctx->w = w;
+
+	SALSA20_2(Bout[i])
+}
+
+static uint32_t blockmix_xor(const salsa20_blk_t *Bin1,
+    const salsa20_blk_t *restrict Bin2, salsa20_blk_t *Bout,
+    size_t r, int Bin2_in_ROM, pwxform_ctx_t *restrict ctx)
+{
+	uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2;
+	size_t w = ctx->w;
+	size_t i;
+	DECL_X
+
+	/* Convert count of 128-byte blocks to max index of 64-byte block */
+	r = r * 2 - 1;
+
+#ifdef PREFETCH
+	if (Bin2_in_ROM) {
+		PREFETCH(&Bin2[r], _MM_HINT_NTA)
+		for (i = 0; i < r; i++) {
+			PREFETCH(&Bin2[i], _MM_HINT_NTA)
+		}
+	} else {
+		PREFETCH(&Bin2[r], _MM_HINT_T0)
+		for (i = 0; i < r; i++) {
+			PREFETCH(&Bin2[i], _MM_HINT_T0)
+		}
+	}
+#else
+	(void)Bin2_in_ROM; /* unused */
+#endif
+
+	XOR_X_2(Bin1[r], Bin2[r])
+
+	DECL_SMASK2REG
+
+	i = 0;
+	r--;
+	do {
+		XOR_X(Bin1[i])
+		XOR_X(Bin2[i])
+		PWXFORM
+		WRITE_X(Bout[i])
+
+		XOR_X(Bin1[i + 1])
+		XOR_X(Bin2[i + 1])
+		PWXFORM
+
+		if (unlikely(i >= r))
+			break;
+
+		WRITE_X(Bout[i + 1])
+
+		i += 2;
+	} while (1);
+	i++;
+
+	ctx->S0 = S0; ctx->S1 = S1; ctx->S2 = S2;
+	ctx->w = w;
+
+	SALSA20_2(Bout[i])
+
+	return INTEGERIFY;
+}
+
+static uint32_t blockmix_xor_save(salsa20_blk_t *restrict Bin1out,
+    salsa20_blk_t *restrict Bin2,
+    size_t r, pwxform_ctx_t *restrict ctx)
+{
+	uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2;
+	size_t w = ctx->w;
+	size_t i;
+	DECL_X
+	DECL_Y
+
+	/* Convert count of 128-byte blocks to max index of 64-byte block */
+	r = r * 2 - 1;
+
+#ifdef PREFETCH
+	PREFETCH(&Bin2[r], _MM_HINT_T0)
+	for (i = 0; i < r; i++) {
+		PREFETCH(&Bin2[i], _MM_HINT_T0)
+	}
+#endif
+
+	XOR_X_2(Bin1out[r], Bin2[r])
+
+	DECL_SMASK2REG
+
+	i = 0;
+	r--;
+	do {
+		XOR_X_WRITE_XOR_Y_2(Bin2[i], Bin1out[i])
+		PWXFORM
+		WRITE_X(Bin1out[i])
+
+		XOR_X_WRITE_XOR_Y_2(Bin2[i + 1], Bin1out[i + 1])
+		PWXFORM
+
+		if (unlikely(i >= r))
+			break;
+
+		WRITE_X(Bin1out[i + 1])
+
+		i += 2;
+	} while (1);
+	i++;
+
+	ctx->S0 = S0; ctx->S1 = S1; ctx->S2 = S2;
+	ctx->w = w;
+
+	SALSA20_2(Bin1out[i])
+
+	return INTEGERIFY;
+}
+
+/**
+ * integerify(B, r):
+ * Return the result of parsing B_{2r-1} as a little-endian integer.
+ */
+static inline uint32_t integerify(const salsa20_blk_t *B, size_t r)
+{
+/*
+ * Our 64-bit words are in host byte order, which is why we don't just read
+ * w[0] here (would be wrong on big-endian).  Also, our 32-bit words are
+ * SIMD-shuffled (so the next 32 bits would be part of d[6]), but currently
+ * this does not matter as we only care about the least significant 32 bits.
+ */
+	return (uint32_t)B[2 * r - 1].d[0];
+}
+
+/**
+ * smix1(B, r, N, flags, V, NROM, VROM, XY, ctx):
+ * Compute first loop of B = SMix_r(B, N).  The input B must be 128r bytes in
+ * length; the temporary storage V must be 128rN bytes in length; the temporary
+ * storage XY must be 128r+64 bytes in length.  N must be even and at least 4.
+ * The array V must be aligned to a multiple of 64 bytes, and arrays B and XY
+ * to a multiple of at least 16 bytes.
+ */
+static void smix1(uint8_t *B, size_t r, uint32_t N, yescrypt_flags_t flags,
+    salsa20_blk_t *V, uint32_t NROM, const salsa20_blk_t *VROM,
+    salsa20_blk_t *XY, pwxform_ctx_t *ctx)
+{
+	size_t s = 2 * r;
+	salsa20_blk_t *X = V, *Y = &V[s];
+	uint32_t i, j;
+
+	for (i = 0; i < 2 * r; i++) {
+		const salsa20_blk_t *src = (salsa20_blk_t *)&B[i * 64];
+		salsa20_blk_t *tmp = Y;
+		salsa20_blk_t *dst = &X[i];
+		size_t k;
+		for (k = 0; k < 16; k++)
+			tmp->w[k] = le32dec(&src->w[k]);
+		salsa20_simd_shuffle(tmp, dst);
+	}
+
+	if (VROM) {
+		uint32_t n;
+		const salsa20_blk_t *V_j;
+
+		V_j = &VROM[(NROM - 1) * s];
+		j = blockmix_xor(X, V_j, Y, r, 1, ctx) & (NROM - 1);
+		V_j = &VROM[j * s];
+		X = Y + s;
+		j = blockmix_xor(Y, V_j, X, r, 1, ctx);
+
+		for (n = 2; n < N; n <<= 1) {
+			uint32_t m = (n < N / 2) ? n : (N - 1 - n);
+			for (i = 1; i < m; i += 2) {
+				j &= n - 1;
+				j += i - 1;
+				V_j = &V[j * s];
+				Y = X + s;
+				j = blockmix_xor(X, V_j, Y, r, 0, ctx) & (NROM - 1);
+				V_j = &VROM[j * s];
+				X = Y + s;
+				j = blockmix_xor(Y, V_j, X, r, 1, ctx);
+			}
+		}
+		n >>= 1;
+
+		j &= n - 1;
+		j += N - 2 - n;
+		V_j = &V[j * s];
+		Y = X + s;
+		j = blockmix_xor(X, V_j, Y, r, 0, ctx) & (NROM - 1);
+		V_j = &VROM[j * s];
+		blockmix_xor(Y, V_j, XY, r, 1, ctx);
+	} else if (flags & YESCRYPT_RW) {
+		uint32_t n;
+		salsa20_blk_t *V_j;
+
+		blockmix(X, Y, r, ctx);
+		X = Y + s;
+		blockmix(Y, X, r, ctx);
+		j = integerify(X, r);
+
+		for (n = 2; n < N; n <<= 1) {
+			uint32_t m = (n < N / 2) ? n : (N - 1 - n);
+			for (i = 1; i < m; i += 2) {
+				Y = X + s;
+				j &= n - 1;
+				j += i - 1;
+				V_j = &V[j * s];
+				j = blockmix_xor(X, V_j, Y, r, 0, ctx);
+				j &= n - 1;
+				j += i;
+				V_j = &V[j * s];
+				X = Y + s;
+				j = blockmix_xor(Y, V_j, X, r, 0, ctx);
+			}
+		}
+		n >>= 1;
+
+		j &= n - 1;
+		j += N - 2 - n;
+		V_j = &V[j * s];
+		Y = X + s;
+		j = blockmix_xor(X, V_j, Y, r, 0, ctx);
+		j &= n - 1;
+		j += N - 1 - n;
+		V_j = &V[j * s];
+		blockmix_xor(Y, V_j, XY, r, 0, ctx);
+	} else {
+		N -= 2;
+		do {
+			blockmix_salsa8(X, Y, r);
+			X = Y + s;
+			blockmix_salsa8(Y, X, r);
+			Y = X + s;
+		} while ((N -= 2));
+
+		blockmix_salsa8(X, Y, r);
+		blockmix_salsa8(Y, XY, r);
+	}
+
+	for (i = 0; i < 2 * r; i++) {
+		const salsa20_blk_t *src = &XY[i];
+		salsa20_blk_t *tmp = &XY[s];
+		salsa20_blk_t *dst = (salsa20_blk_t *)&B[i * 64];
+		size_t k;
+		for (k = 0; k < 16; k++)
+			le32enc(&tmp->w[k], src->w[k]);
+		salsa20_simd_unshuffle(tmp, dst);
+	}
+}
+
+/**
+ * smix2(B, r, N, Nloop, flags, V, NROM, VROM, XY, ctx):
+ * Compute second loop of B = SMix_r(B, N).  The input B must be 128r bytes in
+ * length; the temporary storage V must be 128rN bytes in length; the temporary
+ * storage XY must be 256r bytes in length.  N must be a power of 2 and at
+ * least 2.  Nloop must be even.  The array V must be aligned to a multiple of
+ * 64 bytes, and arrays B and XY to a multiple of at least 16 bytes.
+ */
+static void smix2(uint8_t *B, size_t r, uint32_t N, uint64_t Nloop,
+    yescrypt_flags_t flags, salsa20_blk_t *V, uint32_t NROM,
+    const salsa20_blk_t *VROM, salsa20_blk_t *XY, pwxform_ctx_t *ctx)
+{
+	size_t s = 2 * r;
+	salsa20_blk_t *X = XY, *Y = &XY[s];
+	uint32_t i, j;
+
+	if (Nloop == 0)
+		return;
+
+	for (i = 0; i < 2 * r; i++) {
+		const salsa20_blk_t *src = (salsa20_blk_t *)&B[i * 64];
+		salsa20_blk_t *tmp = Y;
+		salsa20_blk_t *dst = &X[i];
+		size_t k;
+		for (k = 0; k < 16; k++)
+			tmp->w[k] = le32dec(&src->w[k]);
+		salsa20_simd_shuffle(tmp, dst);
+	}
+
+	j = integerify(X, r) & (N - 1);
+
+/*
+ * Normally, VROM implies YESCRYPT_RW, but we check for these separately
+ * because our SMix resets YESCRYPT_RW for the smix2() calls operating on the
+ * entire V when p > 1.
+ */
+	if (VROM && (flags & YESCRYPT_RW)) {
+		do {
+			salsa20_blk_t *V_j = &V[j * s];
+			const salsa20_blk_t *VROM_j;
+			j = blockmix_xor_save(X, V_j, r, ctx) & (NROM - 1);
+			VROM_j = &VROM[j * s];
+			j = blockmix_xor(X, VROM_j, X, r, 1, ctx) & (N - 1);
+		} while (Nloop -= 2);
+	} else if (VROM) {
+		do {
+			const salsa20_blk_t *V_j = &V[j * s];
+			j = blockmix_xor(X, V_j, X, r, 0, ctx) & (NROM - 1);
+			V_j = &VROM[j * s];
+			j = blockmix_xor(X, V_j, X, r, 1, ctx) & (N - 1);
+		} while (Nloop -= 2);
+	} else if (flags & YESCRYPT_RW) {
+		do {
+			salsa20_blk_t *V_j = &V[j * s];
+			j = blockmix_xor_save(X, V_j, r, ctx) & (N - 1);
+			V_j = &V[j * s];
+			j = blockmix_xor_save(X, V_j, r, ctx) & (N - 1);
+		} while (Nloop -= 2);
+	} else if (ctx) {
+		do {
+			const salsa20_blk_t *V_j = &V[j * s];
+			j = blockmix_xor(X, V_j, X, r, 0, ctx) & (N - 1);
+			V_j = &V[j * s];
+			j = blockmix_xor(X, V_j, X, r, 0, ctx) & (N - 1);
+		} while (Nloop -= 2);
+	} else {
+		do {
+			const salsa20_blk_t *V_j = &V[j * s];
+			j = blockmix_salsa8_xor(X, V_j, Y, r) & (N - 1);
+			V_j = &V[j * s];
+			j = blockmix_salsa8_xor(Y, V_j, X, r) & (N - 1);
+		} while (Nloop -= 2);
+	}
+
+	for (i = 0; i < 2 * r; i++) {
+		const salsa20_blk_t *src = &X[i];
+		salsa20_blk_t *tmp = Y;
+		salsa20_blk_t *dst = (salsa20_blk_t *)&B[i * 64];
+		size_t k;
+		for (k = 0; k < 16; k++)
+			le32enc(&tmp->w[k], src->w[k]);
+		salsa20_simd_unshuffle(tmp, dst);
+	}
+}
+
+/**
+ * p2floor(x):
+ * Largest power of 2 not greater than argument.
+ */
+static uint64_t p2floor(uint64_t x)
+{
+	uint64_t y;
+	while ((y = x & (x - 1)))
+		x = y;
+	return x;
+}
+
+/**
+ * smix(B, r, N, p, t, flags, V, NROM, VROM, XY, S, passwd):
+ * Compute B = SMix_r(B, N).  The input B must be 128rp bytes in length; the
+ * temporary storage V must be 128rN bytes in length; the temporary storage
+ * XY must be 256r or 256rp bytes in length (the larger size is required with
+ * OpenMP-enabled builds).  N must be a power of 2 and at least 4.  The array V
+ * must be aligned to a multiple of 64 bytes, and arrays B and XY to a multiple
+ * of at least 16 bytes (aligning them to 64 bytes as well saves cache lines
+ * and helps avoid false sharing in OpenMP-enabled builds when p > 1, but it
+ * might also result in cache bank conflicts).
+ */
+static void smix(uint8_t *B, size_t r, uint32_t N, uint32_t p, uint32_t t,
+    yescrypt_flags_t flags,
+    salsa20_blk_t *V, uint32_t NROM, const salsa20_blk_t *VROM,
+    salsa20_blk_t *XY, uint8_t *S, uint8_t *passwd)
+{
+	size_t s = 2 * r;
+	uint32_t Nchunk;
+	uint64_t Nloop_all, Nloop_rw;
+	uint32_t i;
+
+	Nchunk = N / p;
+	Nloop_all = Nchunk;
+	if (flags & YESCRYPT_RW) {
+		if (t <= 1) {
+			if (t)
+				Nloop_all *= 2; /* 2/3 */
+			Nloop_all = (Nloop_all + 2) / 3; /* 1/3, round up */
+		} else {
+			Nloop_all *= t - 1;
+		}
+	} else if (t) {
+		if (t == 1)
+			Nloop_all += (Nloop_all + 1) / 2; /* 1.5, round up */
+		Nloop_all *= t;
+	}
+
+	Nloop_rw = 0;
+	if (flags & YESCRYPT_INIT_SHARED)
+		Nloop_rw = Nloop_all;
+	else if (flags & YESCRYPT_RW)
+		Nloop_rw = Nloop_all / p;
+
+	Nchunk &= ~(uint32_t)1; /* round down to even */
+	Nloop_all++; Nloop_all &= ~(uint64_t)1; /* round up to even */
+	Nloop_rw++; Nloop_rw &= ~(uint64_t)1; /* round up to even */
+
+#ifdef _OPENMP
+#pragma omp parallel if (p > 1) default(none) private(i) shared(B, r, N, p, flags, V, NROM, VROM, XY, S, passwd, s, Nchunk, Nloop_all, Nloop_rw)
+	{
+#pragma omp for
+#endif
+	for (i = 0; i < p; i++) {
+		uint32_t Vchunk = i * Nchunk;
+		uint32_t Np = (i < p - 1) ? Nchunk : (N - Vchunk);
+		uint8_t *Bp = &B[128 * r * i];
+		salsa20_blk_t *Vp = &V[Vchunk * s];
+#ifdef _OPENMP
+		salsa20_blk_t *XYp = &XY[i * (2 * s)];
+#else
+		salsa20_blk_t *XYp = XY;
+#endif
+		pwxform_ctx_t *ctx_i = NULL;
+		if (flags & YESCRYPT_RW) {
+			uint8_t *Si = S + i * Salloc;
+			smix1(Bp, 1, Sbytes / 128, 0 /* no flags */,
+			    (salsa20_blk_t *)Si, 0, NULL, XYp, NULL);
+			ctx_i = (pwxform_ctx_t *)(Si + Sbytes);
+			ctx_i->S2 = Si;
+			ctx_i->S1 = Si + Sbytes / 3;
+			ctx_i->S0 = Si + Sbytes / 3 * 2;
+			ctx_i->w = 0;
+			if (i == 0)
+				HMAC_SHA256_Buf(Bp + (128 * r - 64), 64,
+				    passwd, 32, passwd);
+		}
+		smix1(Bp, r, Np, flags, Vp, NROM, VROM, XYp, ctx_i);
+		smix2(Bp, r, p2floor(Np), Nloop_rw, flags, Vp,
+		    NROM, VROM, XYp, ctx_i);
+	}
+
+	if (Nloop_all > Nloop_rw) {
+#ifdef _OPENMP
+#pragma omp for
+#endif
+		for (i = 0; i < p; i++) {
+			uint8_t *Bp = &B[128 * r * i];
+#ifdef _OPENMP
+			salsa20_blk_t *XYp = &XY[i * (2 * s)];
+#else
+			salsa20_blk_t *XYp = XY;
+#endif
+			pwxform_ctx_t *ctx_i = NULL;
+			if (flags & YESCRYPT_RW) {
+				uint8_t *Si = S + i * Salloc;
+				ctx_i = (pwxform_ctx_t *)(Si + Sbytes);
+			}
+			smix2(Bp, r, N, Nloop_all - Nloop_rw,
+			    flags & ~YESCRYPT_RW, V, NROM, VROM, XYp, ctx_i);
+		}
+	}
+#ifdef _OPENMP
+	}
+#endif
+}
+
+/**
+ * yescrypt_kdf_body(shared, local, passwd, passwdlen, salt, saltlen,
+ *     flags, N, r, p, t, NROM, buf, buflen):
+ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
+ * p, buflen), or a revision of scrypt as requested by flags and shared, and
+ * write the result into buf.
+ *
+ * shared and flags may request special modes as described in yescrypt.h.
+ *
+ * local is the thread-local data structure, allowing to preserve and reuse a
+ * memory allocation across calls, thereby reducing its overhead.
+ *
+ * t controls computation time while not affecting peak memory usage.
+ *
+ * Return 0 on success; or -1 on error.
+ *
+ * This optimized implementation currently limits N to the range from 4 to
+ * 2^31, but other implementations might not.
+ */
+static int yescrypt_kdf_body(const yescrypt_shared_t *shared,
+    yescrypt_local_t *local,
+    const uint8_t *passwd, size_t passwdlen,
+    const uint8_t *salt, size_t saltlen,
+    yescrypt_flags_t flags, uint64_t N, uint32_t r, uint32_t p, uint32_t t,
+    uint64_t NROM,
+    uint8_t *buf, size_t buflen)
+{
+	yescrypt_region_t tmp;
+	const salsa20_blk_t *VROM;
+	size_t B_size, V_size, XY_size, need;
+	uint8_t *B, *S;
+	salsa20_blk_t *V, *XY;
+	uint8_t sha256[32];
+	uint8_t dk[sizeof(sha256)], *dkp = buf;
+
+	/* Sanity-check parameters */
+	switch (flags & YESCRYPT_MODE_MASK) {
+	case 0: /* classic scrypt - can't have anything non-standard */
+		if (flags || t || NROM)
+			goto out_EINVAL;
+		break;
+	case YESCRYPT_WORM:
+		if (flags != YESCRYPT_WORM || NROM)
+			goto out_EINVAL;
+		break;
+	case YESCRYPT_RW:
+		if (flags != (flags & YESCRYPT_KNOWN_FLAGS))
+			goto out_EINVAL;
+#if PWXsimple == 2 && PWXgather == 4 && Sbytes == 12288
+		if ((flags & YESCRYPT_RW_FLAVOR_MASK) ==
+		    (YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 |
+		    YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K))
+			break;
+#else
+#error "Unsupported pwxform settings"
+#endif
+		/* FALLTHRU */
+	default:
+		goto out_EINVAL;
+	}
+#if SIZE_MAX > UINT32_MAX
+	if (buflen > (((uint64_t)1 << 32) - 1) * 32)
+		goto out_EINVAL;
+#endif
+	if ((uint64_t)r * (uint64_t)p >= 1 << 30)
+		goto out_EINVAL;
+	if (N > UINT32_MAX)
+		goto out_EINVAL;
+	if ((N & (N - 1)) != 0 || N <= 3 || r < 1 || p < 1)
+		goto out_EINVAL;
+	if (r > SIZE_MAX / 256 / p ||
+	    N > SIZE_MAX / 128 / r)
+		goto out_EINVAL;
+	if (flags & YESCRYPT_RW) {
+		/* PHP change: fix compile warning */
+#if SIZEOF_SIZE_T == 8
+		if (N / p <= 3)
+			goto out_EINVAL;
+#else
+		if (N / p <= 3 || p > SIZE_MAX / Salloc)
+			goto out_EINVAL;
+#endif
+	}
+#ifdef _OPENMP
+	else if (N > SIZE_MAX / 128 / (r * p)) {
+		goto out_EINVAL;
+	}
+#endif
+
+	VROM = NULL;
+	if (shared) {
+		uint64_t expected_size = (size_t)128 * r * NROM;
+		if ((NROM & (NROM - 1)) != 0 ||
+		    NROM <= 1 || NROM > UINT32_MAX ||
+		    shared->aligned_size < expected_size)
+			goto out_EINVAL;
+		if (!(flags & YESCRYPT_INIT_SHARED)) {
+			uint64_t *tag = (uint64_t *)
+			    ((uint8_t *)shared->aligned + expected_size - 48);
+			if (tag[0] != YESCRYPT_ROM_TAG1 || tag[1] != YESCRYPT_ROM_TAG2)
+				goto out_EINVAL;
+		}
+		VROM = shared->aligned;
+	} else {
+		if (NROM)
+			goto out_EINVAL;
+	}
+
+	/* Allocate memory */
+	V = NULL;
+	V_size = (size_t)128 * r * N;
+#ifdef _OPENMP
+	if (!(flags & YESCRYPT_RW))
+		V_size *= p;
+#endif
+	need = V_size;
+	if (flags & YESCRYPT_INIT_SHARED) {
+		if (local->aligned_size < need) {
+			if (local->base || local->aligned ||
+			    local->base_size || local->aligned_size)
+				goto out_EINVAL;
+			if (!alloc_region(local, need))
+				return -1;
+		}
+		if (flags & YESCRYPT_ALLOC_ONLY)
+			return -2; /* expected "failure" */
+		V = (salsa20_blk_t *)local->aligned;
+		need = 0;
+	}
+	B_size = (size_t)128 * r * p;
+	need += B_size;
+	if (need < B_size)
+		goto out_EINVAL;
+	XY_size = (size_t)256 * r;
+#ifdef _OPENMP
+	XY_size *= p;
+#endif
+	need += XY_size;
+	if (need < XY_size)
+		goto out_EINVAL;
+	if (flags & YESCRYPT_RW) {
+		size_t S_size = (size_t)Salloc * p;
+		need += S_size;
+		if (need < S_size)
+			goto out_EINVAL;
+	}
+	if (flags & YESCRYPT_INIT_SHARED) {
+		if (!alloc_region(&tmp, need))
+			return -1;
+		B = (uint8_t *)tmp.aligned;
+		XY = (salsa20_blk_t *)((uint8_t *)B + B_size);
+	} else {
+		init_region(&tmp);
+		if (local->aligned_size < need) {
+			if (free_region(local))
+				return -1;
+			if (!alloc_region(local, need))
+				return -1;
+		}
+		if (flags & YESCRYPT_ALLOC_ONLY)
+			return -3; /* expected "failure" */
+		B = (uint8_t *)local->aligned;
+		V = (salsa20_blk_t *)((uint8_t *)B + B_size);
+		XY = (salsa20_blk_t *)((uint8_t *)V + V_size);
+	}
+	S = NULL;
+	if (flags & YESCRYPT_RW)
+		S = (uint8_t *)XY + XY_size;
+
+	if (flags) {
+		HMAC_SHA256_Buf("yescrypt-prehash",
+		    (flags & YESCRYPT_PREHASH) ? 16 : 8,
+		    passwd, passwdlen, sha256);
+		passwd = sha256;
+		passwdlen = sizeof(sha256);
+	}
+
+	PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, B_size);
+
+	if (flags)
+		memcpy(sha256, B, sizeof(sha256));
+
+	if (p == 1 || (flags & YESCRYPT_RW)) {
+		smix(B, r, N, p, t, flags, V, NROM, VROM, XY, S, sha256);
+	} else {
+		uint32_t i;
+#ifdef _OPENMP
+#pragma omp parallel for default(none) private(i) shared(B, r, N, p, t, flags, V, NROM, VROM, XY, S)
+#endif
+		for (i = 0; i < p; i++) {
+#ifdef _OPENMP
+			smix(&B[(size_t)128 * r * i], r, N, 1, t, flags,
+			    &V[(size_t)2 * r * i * N],
+			    NROM, VROM,
+			    &XY[(size_t)4 * r * i], NULL, NULL);
+#else
+			smix(&B[(size_t)128 * r * i], r, N, 1, t, flags, V,
+			    NROM, VROM, XY, NULL, NULL);
+#endif
+		}
+	}
+
+	dkp = buf;
+	if (flags && buflen < sizeof(dk)) {
+		PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, dk, sizeof(dk));
+		dkp = dk;
+	}
+
+	PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, buf, buflen);
+
+	/*
+	 * Except when computing classic scrypt, allow all computation so far
+	 * to be performed on the client.  The final steps below match those of
+	 * SCRAM (RFC 5802), so that an extension of SCRAM (with the steps so
+	 * far in place of SCRAM's use of PBKDF2 and with SHA-256 in place of
+	 * SCRAM's use of SHA-1) would be usable with yescrypt hashes.
+	 */
+	if (flags && !(flags & YESCRYPT_PREHASH)) {
+		/* Compute ClientKey */
+		HMAC_SHA256_Buf(dkp, sizeof(dk), "Client Key", 10, sha256);
+		/* Compute StoredKey */
+		{
+			size_t clen = buflen;
+			if (clen > sizeof(dk))
+				clen = sizeof(dk);
+			SHA256_Buf(sha256, sizeof(sha256), dk);
+			memcpy(buf, dk, clen);
+		}
+	}
+
+	if (flags) {
+		insecure_memzero(sha256, sizeof(sha256));
+		insecure_memzero(dk, sizeof(dk));
+	}
+
+	if (free_region(&tmp)) {
+		insecure_memzero(buf, buflen); /* must preserve errno */
+		return -1;
+	}
+
+	/* Success! */
+	return 0;
+
+out_EINVAL:
+	errno = EINVAL;
+	return -1;
+}
+
+/**
+ * yescrypt_kdf(shared, local, passwd, passwdlen, salt, saltlen, params,
+ *     buf, buflen):
+ * Compute scrypt or its revision as requested by the parameters.  The inputs
+ * to this function are the same as those for yescrypt_kdf_body() above, with
+ * the addition of g, which controls hash upgrades (0 for no upgrades so far).
+ */
+int yescrypt_kdf(const yescrypt_shared_t *shared, yescrypt_local_t *local,
+    const uint8_t *passwd, size_t passwdlen,
+    const uint8_t *salt, size_t saltlen,
+    const yescrypt_params_t *params,
+    uint8_t *buf, size_t buflen)
+{
+	yescrypt_flags_t flags = params->flags;
+	uint64_t N = params->N;
+	uint32_t r = params->r;
+	uint32_t p = params->p;
+	uint32_t t = params->t;
+	uint32_t g = params->g;
+	uint64_t NROM = params->NROM;
+	uint8_t dk[32];
+	int retval;
+
+	/* Support for hash upgrades has been temporarily removed */
+	if (g) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if ((flags & (YESCRYPT_RW | YESCRYPT_INIT_SHARED)) == YESCRYPT_RW &&
+	    p >= 1 && N / p >= 0x100 && N / p * r >= 0x20000) {
+		if (yescrypt_kdf_body(shared, local,
+		    passwd, passwdlen, salt, saltlen,
+		    flags | YESCRYPT_ALLOC_ONLY, N, r, p, t, NROM,
+		    buf, buflen) != -3) {
+			errno = EINVAL;
+			return -1;
+		}
+		if ((retval = yescrypt_kdf_body(shared, local,
+		    passwd, passwdlen, salt, saltlen,
+		    flags | YESCRYPT_PREHASH, N >> 6, r, p, 0, NROM,
+		    dk, sizeof(dk))))
+			return retval;
+		passwd = dk;
+		passwdlen = sizeof(dk);
+	}
+
+	retval = yescrypt_kdf_body(shared, local,
+	    passwd, passwdlen, salt, saltlen,
+	    flags, N, r, p, t, NROM, buf, buflen);
+#ifndef SKIP_MEMZERO
+	if (passwd == dk)
+		insecure_memzero(dk, sizeof(dk));
+#endif
+	return retval;
+}
+
+int yescrypt_init_shared(yescrypt_shared_t *shared,
+    const uint8_t *seed, size_t seedlen,
+    const yescrypt_params_t *params)
+{
+	yescrypt_params_t subparams;
+	yescrypt_shared_t half1, half2;
+	uint8_t salt[32];
+	uint64_t *tag;
+
+	subparams = *params;
+	subparams.flags |= YESCRYPT_INIT_SHARED;
+	subparams.N = params->NROM;
+	subparams.NROM = 0;
+
+	if (!(params->flags & YESCRYPT_RW) || params->N || params->g)
+		return -1;
+
+	if (params->flags & YESCRYPT_SHARED_PREALLOCATED) {
+		if (!shared->aligned || !shared->aligned_size)
+			return -1;
+
+/* Overwrite a possible old ROM tag before we overwrite the rest */
+		tag = (uint64_t *)
+		    ((uint8_t *)shared->aligned + shared->aligned_size - 48);
+		memset(tag, 0, 48);
+	} else {
+		init_region(shared);
+
+		subparams.flags |= YESCRYPT_ALLOC_ONLY;
+		if (yescrypt_kdf(NULL, shared, NULL, 0, NULL, 0, &subparams,
+		    NULL, 0) != -2 || !shared->aligned)
+			return -1;
+		subparams.flags -= YESCRYPT_ALLOC_ONLY;
+	}
+
+	subparams.N /= 2;
+
+	half1 = *shared;
+	half1.aligned_size /= 2;
+	half2 = half1;
+	half2.aligned = (uint8_t *)half2.aligned + half1.aligned_size;
+
+	if (yescrypt_kdf(NULL, &half1,
+	    seed, seedlen, (const uint8_t *)"yescrypt-ROMhash", 16, &subparams,
+	    salt, sizeof(salt)))
+		goto fail;
+
+	subparams.NROM = subparams.N;
+
+	if (yescrypt_kdf(&half1, &half2,
+	    seed, seedlen, salt, sizeof(salt), &subparams, salt, sizeof(salt)))
+		goto fail;
+
+	if (yescrypt_kdf(&half2, &half1,
+	    seed, seedlen, salt, sizeof(salt), &subparams, salt, sizeof(salt)))
+		goto fail;
+
+	tag = (uint64_t *)
+	    ((uint8_t *)shared->aligned + shared->aligned_size - 48);
+	tag[0] = YESCRYPT_ROM_TAG1;
+	tag[1] = YESCRYPT_ROM_TAG2;
+	tag[2] = le64dec(salt);
+	tag[3] = le64dec(salt + 8);
+	tag[4] = le64dec(salt + 16);
+	tag[5] = le64dec(salt + 24);
+
+	insecure_memzero(salt, sizeof(salt));
+	return 0;
+
+fail:
+	insecure_memzero(salt, sizeof(salt));
+	if (!(params->flags & YESCRYPT_SHARED_PREALLOCATED))
+		free_region(shared);
+	return -1;
+}
+
+yescrypt_binary_t *yescrypt_digest_shared(yescrypt_shared_t *shared)
+{
+	static yescrypt_binary_t digest;
+	uint64_t *tag;
+
+	if (shared->aligned_size < 48)
+		return NULL;
+
+	tag = (uint64_t *)
+	    ((uint8_t *)shared->aligned + shared->aligned_size - 48);
+
+	if (tag[0] != YESCRYPT_ROM_TAG1 || tag[1] != YESCRYPT_ROM_TAG2)
+		return NULL;
+
+	le64enc(digest.uc, tag[2]);
+	le64enc(digest.uc + 8, tag[3]);
+	le64enc(digest.uc + 16, tag[4]);
+	le64enc(digest.uc + 24, tag[5]);
+
+	return &digest;
+}
+
+int yescrypt_free_shared(yescrypt_shared_t *shared)
+{
+	return free_region(shared);
+}
+
+int yescrypt_init_local(yescrypt_local_t *local)
+{
+	init_region(local);
+	return 0;
+}
+
+int yescrypt_free_local(yescrypt_local_t *local)
+{
+	return free_region(local);
+}
diff --git a/ext/standard/yescrypt/yescrypt-platform.c b/ext/standard/yescrypt/yescrypt-platform.c
new file mode 100644
index 0000000000000..4fdcf272fd229
--- /dev/null
+++ b/ext/standard/yescrypt/yescrypt-platform.c
@@ -0,0 +1,107 @@
+/*-
+ * Copyright 2013-2018 Alexander Peslyak
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef __unix__
+#include <sys/mman.h>
+#endif
+
+#define HUGEPAGE_THRESHOLD		(32 * 1024 * 1024)
+
+#ifdef __x86_64__
+#define HUGEPAGE_SIZE			(2 * 1024 * 1024)
+#else
+#undef HUGEPAGE_SIZE
+#endif
+
+static void *alloc_region(yescrypt_region_t *region, size_t size)
+{
+	size_t base_size = size;
+	uint8_t *base, *aligned;
+#ifdef MAP_ANON
+	int flags =
+#ifdef MAP_NOCORE
+			MAP_NOCORE |
+#endif
+			MAP_ANON | MAP_PRIVATE;
+#if defined(MAP_HUGETLB) && defined(HUGEPAGE_SIZE)
+	size_t new_size = size;
+	const size_t hugepage_mask = (size_t)HUGEPAGE_SIZE - 1;
+	if (size >= HUGEPAGE_THRESHOLD && size + hugepage_mask >= size) {
+		flags |= MAP_HUGETLB;
+/*
+ * Linux's munmap() fails on MAP_HUGETLB mappings if size is not a multiple of
+ * huge page size, so let's round up to huge page size here.
+ */
+		new_size = size + hugepage_mask;
+		new_size &= ~hugepage_mask;
+	}
+	base = mmap(NULL, new_size, PROT_READ | PROT_WRITE, flags, -1, 0);
+	if (base != MAP_FAILED) {
+		base_size = new_size;
+	} else if (flags & MAP_HUGETLB) {
+		flags &= ~MAP_HUGETLB;
+		base = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0);
+	}
+
+#else
+	base = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0);
+#endif
+	if (base == MAP_FAILED)
+		base = NULL;
+	aligned = base;
+#elif defined(HAVE_POSIX_MEMALIGN)
+	if ((errno = posix_memalign((void **)&base, 64, size)) != 0)
+		base = NULL;
+	aligned = base;
+#else
+	base = aligned = NULL;
+	if (size + 63 < size) {
+		errno = ENOMEM;
+	} else if ((base = malloc(size + 63)) != NULL) {
+		aligned = base + 63;
+		aligned -= (uintptr_t)aligned & 63;
+	}
+#endif
+	region->base = base;
+	region->aligned = aligned;
+	region->base_size = base ? base_size : 0;
+	region->aligned_size = base ? size : 0;
+	return aligned;
+}
+
+static inline void init_region(yescrypt_region_t *region)
+{
+	region->base = region->aligned = NULL;
+	region->base_size = region->aligned_size = 0;
+}
+
+static int free_region(yescrypt_region_t *region)
+{
+	if (region->base) {
+#ifdef MAP_ANON
+		if (munmap(region->base, region->base_size))
+			return -1;
+#else
+		free(region->base);
+#endif
+	}
+	init_region(region);
+	return 0;
+}
diff --git a/ext/standard/yescrypt/yescrypt.h b/ext/standard/yescrypt/yescrypt.h
new file mode 100644
index 0000000000000..9313420047277
--- /dev/null
+++ b/ext/standard/yescrypt/yescrypt.h
@@ -0,0 +1,367 @@
+/*-
+ * Copyright 2009 Colin Percival
+ * Copyright 2013-2018 Alexander Peslyak
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+#ifndef _YESCRYPT_H_
+#define _YESCRYPT_H_
+
+#include <stdint.h>
+#include <stdlib.h> /* for size_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen):
+ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
+ * p, buflen) and write the result into buf.  The parameters r, p, and buflen
+ * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32.  The parameter N
+ * must be a power of 2 greater than 1.
+ *
+ * Return 0 on success; or -1 on error.
+ *
+ * MT-safe as long as buf is local to the thread.
+ */
+extern int crypto_scrypt(const uint8_t *passwd, size_t passwdlen,
+    const uint8_t *salt, size_t saltlen,
+    uint64_t N, uint32_t r, uint32_t p, uint8_t *buf, size_t buflen);
+
+/**
+ * Internal type used by the memory allocator.  Please do not use it directly.
+ * Use yescrypt_shared_t and yescrypt_local_t as appropriate instead, since
+ * they might differ from each other in a future version.
+ */
+typedef struct {
+	void *base, *aligned;
+	size_t base_size, aligned_size;
+} yescrypt_region_t;
+
+/**
+ * Types for shared (ROM) and thread-local (RAM) data structures.
+ */
+typedef yescrypt_region_t yescrypt_shared_t;
+typedef yescrypt_region_t yescrypt_local_t;
+
+/**
+ * Two 64-bit tags placed 48 bytes to the end of a ROM in host byte endianness
+ * (and followed by 32 bytes of the ROM digest).
+ */
+#define YESCRYPT_ROM_TAG1 0x7470797263736579ULL /* "yescrypt" */
+#define YESCRYPT_ROM_TAG2 0x687361684d4f522dULL /* "-ROMhash" */
+
+/**
+ * Type and possible values for the flags argument of yescrypt_kdf(),
+ * yescrypt_encode_params_r(), yescrypt_encode_params().  Most of these may be
+ * OR'ed together, except that YESCRYPT_WORM stands on its own.
+ * Please refer to the description of yescrypt_kdf() below for the meaning of
+ * these flags.
+ */
+typedef uint32_t yescrypt_flags_t;
+/* Public */
+#define YESCRYPT_WORM			1
+#define YESCRYPT_RW			0x002
+#define YESCRYPT_ROUNDS_3		0x000
+#define YESCRYPT_ROUNDS_6		0x004
+#define YESCRYPT_GATHER_1		0x000
+#define YESCRYPT_GATHER_2		0x008
+#define YESCRYPT_GATHER_4		0x010
+#define YESCRYPT_GATHER_8		0x018
+#define YESCRYPT_SIMPLE_1		0x000
+#define YESCRYPT_SIMPLE_2		0x020
+#define YESCRYPT_SIMPLE_4		0x040
+#define YESCRYPT_SIMPLE_8		0x060
+#define YESCRYPT_SBOX_6K		0x000
+#define YESCRYPT_SBOX_12K		0x080
+#define YESCRYPT_SBOX_24K		0x100
+#define YESCRYPT_SBOX_48K		0x180
+#define YESCRYPT_SBOX_96K		0x200
+#define YESCRYPT_SBOX_192K		0x280
+#define YESCRYPT_SBOX_384K		0x300
+#define YESCRYPT_SBOX_768K		0x380
+/* Only valid for yescrypt_init_shared() */
+#define YESCRYPT_SHARED_PREALLOCATED	0x10000
+#ifdef YESCRYPT_INTERNAL
+/* Private */
+#define YESCRYPT_MODE_MASK		0x003
+#define YESCRYPT_RW_FLAVOR_MASK		0x3fc
+#define YESCRYPT_INIT_SHARED		0x01000000
+#define YESCRYPT_ALLOC_ONLY		0x08000000
+#define YESCRYPT_PREHASH		0x10000000
+#endif
+
+#define YESCRYPT_RW_DEFAULTS \
+	(YESCRYPT_RW | \
+	YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | \
+	YESCRYPT_SBOX_12K)
+
+#define YESCRYPT_DEFAULTS YESCRYPT_RW_DEFAULTS
+
+#ifdef YESCRYPT_INTERNAL
+#define YESCRYPT_KNOWN_FLAGS \
+	(YESCRYPT_MODE_MASK | YESCRYPT_RW_FLAVOR_MASK | \
+	YESCRYPT_SHARED_PREALLOCATED | \
+	YESCRYPT_INIT_SHARED | YESCRYPT_ALLOC_ONLY | YESCRYPT_PREHASH)
+#endif
+
+/**
+ * yescrypt parameters combined into one struct.  N, r, p are the same as in
+ * classic scrypt, except that the meaning of p changes when YESCRYPT_RW is
+ * set.  flags, t, g, NROM are special to yescrypt.
+ */
+typedef struct {
+	yescrypt_flags_t flags;
+	uint64_t N;
+	uint32_t r, p, t, g;
+	uint64_t NROM;
+} yescrypt_params_t;
+
+/**
+ * A 256-bit yescrypt hash, or a hash encryption key (which may itself have
+ * been derived as a yescrypt hash of a human-specified key string).
+ */
+typedef union {
+	unsigned char uc[32];
+	uint64_t u64[4];
+} yescrypt_binary_t;
+
+/**
+ * yescrypt_init_shared(shared, seed, seedlen, params):
+ * Optionally allocate memory for and initialize the shared (ROM) data
+ * structure.  The parameters flags, NROM, r, p, and t specify how the ROM is
+ * to be initialized, and seed and seedlen specify the initial seed affecting
+ * the data with which the ROM is filled.
+ *
+ * Return 0 on success; or -1 on error.
+ *
+ * If bit YESCRYPT_SHARED_PREALLOCATED in flags is set, then memory for the
+ * ROM is assumed to have been preallocated by the caller, with shared->aligned
+ * being the start address of the ROM and shared->aligned_size being its size
+ * (which must be sufficient for NROM, r, p).  This may be used e.g. when the
+ * ROM is to be placed in a SysV shared memory segment allocated by the caller.
+ *
+ * MT-safe as long as shared is local to the thread.
+ */
+extern int yescrypt_init_shared(yescrypt_shared_t *shared,
+    const uint8_t *seed, size_t seedlen, const yescrypt_params_t *params);
+
+/**
+ * yescrypt_digest_shared(shared):
+ * Extract the previously stored message digest of the provided yescrypt ROM.
+ *
+ * Return pointer to the message digest on success; or NULL on error.
+ *
+ * MT-unsafe.
+ */
+extern yescrypt_binary_t *yescrypt_digest_shared(yescrypt_shared_t *shared);
+
+/**
+ * yescrypt_free_shared(shared):
+ * Free memory that had been allocated with yescrypt_init_shared().
+ *
+ * Return 0 on success; or -1 on error.
+ *
+ * MT-safe as long as shared is local to the thread.
+ */
+extern int yescrypt_free_shared(yescrypt_shared_t *shared);
+
+/**
+ * yescrypt_init_local(local):
+ * Initialize the thread-local (RAM) data structure.  Actual memory allocation
+ * is currently fully postponed until a call to yescrypt_kdf() or yescrypt_r().
+ *
+ * Return 0 on success; or -1 on error.
+ *
+ * MT-safe as long as local is local to the thread.
+ */
+extern int yescrypt_init_local(yescrypt_local_t *local);
+
+/**
+ * yescrypt_free_local(local):
+ * Free memory that may have been allocated for an initialized thread-local
+ * (RAM) data structure.
+ *
+ * Return 0 on success; or -1 on error.
+ *
+ * MT-safe as long as local is local to the thread.
+ */
+extern int yescrypt_free_local(yescrypt_local_t *local);
+
+/**
+ * yescrypt_kdf(shared, local, passwd, passwdlen, salt, saltlen, params,
+ *     buf, buflen):
+ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
+ * p, buflen), or a revision of scrypt as requested by flags and shared, and
+ * write the result into buf.  The parameters N, r, p, and buflen must satisfy
+ * the same conditions as with crypto_scrypt().  t controls computation time
+ * while not affecting peak memory usage (t = 0 is optimal unless higher N*r
+ * is not affordable while higher t is).  g controls hash upgrades (g = 0 for
+ * no upgrades so far).  shared and flags may request special modes.  local is
+ * the thread-local data structure, allowing to preserve and reuse a memory
+ * allocation across calls, thereby reducing processing overhead.
+ *
+ * Return 0 on success; or -1 on error.
+ *
+ * Classic scrypt is available by setting shared = NULL, flags = 0, and t = 0.
+ *
+ * Setting YESCRYPT_WORM enables only minimal deviations from classic scrypt:
+ * support for the t parameter, and pre- and post-hashing.
+ *
+ * Setting YESCRYPT_RW fully enables yescrypt.  As a side effect of differences
+ * between the algorithms, it also prevents p > 1 from growing the threads'
+ * combined processing time and memory allocation (like it did with classic
+ * scrypt and YESCRYPT_WORM), treating p as a divider rather than a multiplier.
+ *
+ * Passing a shared structure, with ROM contents previously computed by
+ * yescrypt_init_shared(), enables the use of ROM and requires YESCRYPT_RW.
+ *
+ * In order to allow for initialization of the ROM to be split into a separate
+ * program (or separate invocation of the same program), the shared->aligned
+ * and shared->aligned_size fields may optionally be set by the caller directly
+ * (e.g., to a mapped SysV shm segment), without using yescrypt_init_shared().
+ *
+ * local must be initialized with yescrypt_init_local().
+ *
+ * MT-safe as long as local and buf are local to the thread.
+ */
+extern int yescrypt_kdf(const yescrypt_shared_t *shared,
+    yescrypt_local_t *local,
+    const uint8_t *passwd, size_t passwdlen,
+    const uint8_t *salt, size_t saltlen,
+    const yescrypt_params_t *params,
+    uint8_t *buf, size_t buflen);
+
+/**
+ * yescrypt_r(shared, local, passwd, passwdlen, setting, key, buf, buflen):
+ * Compute and encode an scrypt or enhanced scrypt hash of passwd given the
+ * parameters and salt value encoded in setting.  If shared is not NULL, a ROM
+ * is used and YESCRYPT_RW is required.  Otherwise, whether to compute classic
+ * scrypt, YESCRYPT_WORM (a slight deviation from classic scrypt), or
+ * YESCRYPT_RW (time-memory tradeoff discouraging modification) is determined
+ * by the setting string.  shared (if not NULL) and local must be initialized
+ * as described above for yescrypt_kdf().  buf must be large enough (as
+ * indicated by buflen) to hold the encoded hash string.
+ *
+ * Return the encoded hash string on success; or NULL on error.
+ *
+ * MT-safe as long as local and buf are local to the thread.
+ */
+extern uint8_t *yescrypt_r(const yescrypt_shared_t *shared,
+    yescrypt_local_t *local,
+    const uint8_t *passwd, size_t passwdlen,
+    const uint8_t *setting,
+    const yescrypt_binary_t *key,
+    uint8_t *buf, size_t buflen);
+
+/**
+ * yescrypt(passwd, setting):
+ * Compute and encode an scrypt or enhanced scrypt hash of passwd given the
+ * parameters and salt value encoded in setting.  Whether to compute classic
+ * scrypt, YESCRYPT_WORM (a slight deviation from classic scrypt), or
+ * YESCRYPT_RW (time-memory tradeoff discouraging modification) is determined
+ * by the setting string.
+ *
+ * Return the encoded hash string on success; or NULL on error.
+ *
+ * This is a crypt(3)-like interface, which is simpler to use than
+ * yescrypt_r(), but it is not MT-safe, it does not allow for the use of a ROM,
+ * and it is slower than yescrypt_r() for repeated calls because it allocates
+ * and frees memory on each call.
+ *
+ * MT-unsafe.
+ */
+extern uint8_t *yescrypt(const uint8_t *passwd, const uint8_t *setting);
+
+/**
+ * yescrypt_reencrypt(hash, from_key, to_key):
+ * Re-encrypt a yescrypt hash from one key to another.  Either key may be NULL
+ * to indicate unencrypted hash.  The encoded hash string is modified in-place.
+ *
+ * Return the hash pointer on success; or NULL on error (in which case the hash
+ * string is left unmodified).
+ *
+ * MT-safe as long as hash is local to the thread.
+ */
+extern uint8_t *yescrypt_reencrypt(uint8_t *hash,
+    const yescrypt_binary_t *from_key,
+    const yescrypt_binary_t *to_key);
+
+/**
+ * yescrypt_encode_params_r(params, src, srclen, buf, buflen):
+ * Generate a setting string for use with yescrypt_r() and yescrypt() by
+ * encoding into it the parameters flags, N, r, p, t, g, and a salt given by
+ * src (of srclen bytes).  buf must be large enough (as indicated by buflen)
+ * to hold the setting string.
+ *
+ * Return the setting string on success; or NULL on error.
+ *
+ * MT-safe as long as buf is local to the thread.
+ */
+extern uint8_t *yescrypt_encode_params_r(const yescrypt_params_t *params,
+    const uint8_t *src, size_t srclen,
+    uint8_t *buf, size_t buflen);
+
+/**
+ * yescrypt_encode_params(params, src, srclen):
+ * Generate a setting string for use with yescrypt_r() and yescrypt().  This
+ * function is the same as yescrypt_encode_params_r() except that it uses a
+ * static buffer and thus is not MT-safe.
+ *
+ * Return the setting string on success; or NULL on error.
+ *
+ * MT-unsafe.
+ */
+extern uint8_t *yescrypt_encode_params(const yescrypt_params_t *params,
+    const uint8_t *src, size_t srclen);
+
+/* PHP changes: expose some macros and functions */
+const uint8_t *yescrypt_parse_settings(const uint8_t *setting, yescrypt_params_t *params, const yescrypt_binary_t *key);
+
+const uint8_t *yescrypt_decode64(uint8_t *dst, size_t *dstlen,
+								 const uint8_t *src, size_t srclen);
+
+uint8_t *yescrypt_encode64_uint32_fixed(uint8_t *dst, size_t dstlen,
+										uint32_t src, uint32_t srcbits);
+
+#define BYTES2CHARS(bytes) ((((bytes) * 8) + 5) / 6)
+
+#define HASH_SIZE sizeof(yescrypt_binary_t) /* bytes */
+#define HASH_LEN BYTES2CHARS(HASH_SIZE) /* base-64 chars */
+
+/*
+ * "$y$", up to 8 params of up to 6 chars each, '$', salt
+ * Alternatively, but that's smaller:
+ * "$7$", 3 params encoded as 1+5+5 chars, salt
+ */
+#define PREFIX_LEN (3 + 8 * 6 + 1 + BYTES2CHARS(32))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_YESCRYPT_H_ */