mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
crypto: add TurboSHAKE and KangarooTwelve Web Cryptography algorithms
PR-URL: https://github.com/nodejs/node/pull/62183 Refs: https://wicg.github.io/webcrypto-modern-algos/#kangarootwelve Refs: https://wicg.github.io/webcrypto-modern-algos/#turboshake Refs: https://www.rfc-editor.org/rfc/rfc9861.html Refs: https://redirect.github.com/openssl/openssl/issues/30304 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
13 files changed
+1521
-8
No files matched your search
+85
-1
@@ -2,6 +2,10 @@
|
||||
|
||||
<!-- YAML
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/62183
|
||||
description: TurboSHAKE and KangarooTwelve algorithms
|
||||
are now supported.
|
||||
- version: v24.8.0
|
||||
pr-url: https://github.com/nodejs/node/pull/59647
|
||||
description: KMAC algorithms are now supported.
|
||||
@@ -122,6 +126,8 @@ Algorithms:
|
||||
* `'cSHAKE256'`
|
||||
* `'KMAC128'`[^openssl30]
|
||||
* `'KMAC256'`[^openssl30]
|
||||
* `'KT128'`
|
||||
* `'KT256'`
|
||||
* `'ML-DSA-44'`[^openssl35]
|
||||
* `'ML-DSA-65'`[^openssl35]
|
||||
* `'ML-DSA-87'`[^openssl35]
|
||||
@@ -131,6 +137,8 @@ Algorithms:
|
||||
* `'SHA3-256'`
|
||||
* `'SHA3-384'`
|
||||
* `'SHA3-512'`
|
||||
* `'TurboSHAKE128'`
|
||||
* `'TurboSHAKE256'`
|
||||
|
||||
Key Formats:
|
||||
|
||||
@@ -575,6 +583,8 @@ implementation and the APIs supported for each:
|
||||
| `'HMAC'` | | ✔ | | | | |
|
||||
| `'KMAC128'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'KMAC256'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'KT128'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'KT256'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'ML-DSA-44'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-DSA-65'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-DSA-87'`[^modern-algos] | | ✔ | | | | |
|
||||
@@ -592,6 +602,8 @@ implementation and the APIs supported for each:
|
||||
| `'SHA3-256'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'SHA3-384'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'SHA3-512'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'TurboSHAKE128'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'TurboSHAKE256'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'X25519'` | | | ✔ | | | |
|
||||
| `'X448'`[^secure-curves] | | | ✔ | | | |
|
||||
|
||||
@@ -999,6 +1011,10 @@ The algorithms currently supported include:
|
||||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/62183
|
||||
description: TurboSHAKE and KangarooTwelve algorithms
|
||||
are now supported.
|
||||
- version: v24.7.0
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: SHA-3 algorithms are now supported.
|
||||
@@ -1007,7 +1023,7 @@ changes:
|
||||
description: SHAKE algorithms are now supported.
|
||||
-->
|
||||
|
||||
* `algorithm` {string|Algorithm|CShakeParams}
|
||||
* `algorithm` {string|Algorithm|CShakeParams|TurboShakeParams|KangarooTwelveParams}
|
||||
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
|
||||
|
||||
@@ -1019,6 +1035,8 @@ If `algorithm` is provided as a {string}, it must be one of:
|
||||
|
||||
* `'cSHAKE128'`[^modern-algos]
|
||||
* `'cSHAKE256'`[^modern-algos]
|
||||
* `'KT128'`[^modern-algos]
|
||||
* `'KT256'`[^modern-algos]
|
||||
* `'SHA-1'`
|
||||
* `'SHA-256'`
|
||||
* `'SHA-384'`
|
||||
@@ -1026,6 +1044,8 @@ If `algorithm` is provided as a {string}, it must be one of:
|
||||
* `'SHA3-256'`[^modern-algos]
|
||||
* `'SHA3-384'`[^modern-algos]
|
||||
* `'SHA3-512'`[^modern-algos]
|
||||
* `'TurboSHAKE128'`[^modern-algos]
|
||||
* `'TurboSHAKE256'`[^modern-algos]
|
||||
|
||||
If `algorithm` is provided as an {Object}, it must have a `name` property
|
||||
whose value is one of the above.
|
||||
@@ -2316,6 +2336,38 @@ added: v15.0.0
|
||||
|
||||
* Type: {string}
|
||||
|
||||
### Class: `KangarooTwelveParams`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
#### `kangarooTwelveParams.customization`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
|
||||
|
||||
The optional customization string for KangarooTwelve.
|
||||
|
||||
#### `kangarooTwelveParams.name`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {string} Must be `'KT128'`[^modern-algos] or `'KT256'`[^modern-algos]
|
||||
|
||||
#### `kangarooTwelveParams.outputLength`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {number} represents the requested output length in bits.
|
||||
|
||||
### Class: `KmacImportParams`
|
||||
|
||||
<!-- YAML
|
||||
@@ -2686,6 +2738,38 @@ added: v15.0.0
|
||||
|
||||
The length (in bytes) of the random salt to use.
|
||||
|
||||
### Class: `TurboShakeParams`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
#### `turboShakeParams.domainSeparation`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {number|undefined}
|
||||
|
||||
The optional domain separation byte (0x01-0x7f). Defaults to `0x1f`.
|
||||
|
||||
#### `turboShakeParams.name`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {string} Must be `'TurboSHAKE128'`[^modern-algos] or `'TurboSHAKE256'`[^modern-algos]
|
||||
|
||||
#### `turboShakeParams.outputLength`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {number} represents the requested output length in bits.
|
||||
|
||||
[^secure-curves]: See [Secure Curves in the Web Cryptography API][]
|
||||
|
||||
[^modern-algos]: See [Modern Algorithms in the Web Cryptography API][]
|
||||
|
||||
@@ -14,6 +14,8 @@ const {
|
||||
Hmac: _Hmac,
|
||||
kCryptoJobAsync,
|
||||
oneShotDigest,
|
||||
TurboShakeJob,
|
||||
KangarooTwelveJob,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const {
|
||||
@@ -224,6 +226,24 @@ async function asyncDigest(algorithm, data) {
|
||||
normalizeHashName(algorithm.name),
|
||||
data,
|
||||
algorithm.outputLength));
|
||||
case 'TurboSHAKE128':
|
||||
// Fall through
|
||||
case 'TurboSHAKE256':
|
||||
return await jobPromise(() => new TurboShakeJob(
|
||||
kCryptoJobAsync,
|
||||
algorithm.name,
|
||||
algorithm.domainSeparation ?? 0x1f,
|
||||
algorithm.outputLength / 8,
|
||||
data));
|
||||
case 'KT128':
|
||||
// Fall through
|
||||
case 'KT256':
|
||||
return await jobPromise(() => new KangarooTwelveJob(
|
||||
kCryptoJobAsync,
|
||||
algorithm.name,
|
||||
algorithm.customization,
|
||||
algorithm.outputLength / 8,
|
||||
data));
|
||||
}
|
||||
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
|
||||
@@ -244,6 +244,10 @@ const kAlgorithmDefinitions = {
|
||||
},
|
||||
'cSHAKE128': { 'digest': 'CShakeParams' },
|
||||
'cSHAKE256': { 'digest': 'CShakeParams' },
|
||||
'KT128': { 'digest': 'KangarooTwelveParams' },
|
||||
'KT256': { 'digest': 'KangarooTwelveParams' },
|
||||
'TurboSHAKE128': { 'digest': 'TurboShakeParams' },
|
||||
'TurboSHAKE256': { 'digest': 'TurboShakeParams' },
|
||||
'ECDH': {
|
||||
'generateKey': 'EcKeyGenParams',
|
||||
'exportKey': null,
|
||||
@@ -441,6 +445,10 @@ const experimentalAlgorithms = [
|
||||
'SHA3-256',
|
||||
'SHA3-384',
|
||||
'SHA3-512',
|
||||
'TurboSHAKE128',
|
||||
'TurboSHAKE256',
|
||||
'KT128',
|
||||
'KT256',
|
||||
'X448',
|
||||
];
|
||||
|
||||
@@ -513,6 +521,10 @@ const simpleAlgorithmDictionaries = {
|
||||
KmacParams: {
|
||||
customization: 'BufferSource',
|
||||
},
|
||||
KangarooTwelveParams: {
|
||||
customization: 'BufferSource',
|
||||
},
|
||||
TurboShakeParams: {},
|
||||
};
|
||||
|
||||
function validateMaxBufferLength(data, name) {
|
||||
|
||||
@@ -897,6 +897,52 @@ converters.KmacParams = createDictionaryConverter(
|
||||
},
|
||||
]);
|
||||
|
||||
converters.KangarooTwelveParams = createDictionaryConverter(
|
||||
'KangarooTwelveParams', [
|
||||
...new SafeArrayIterator(dictAlgorithm),
|
||||
{
|
||||
key: 'outputLength',
|
||||
converter: (V, opts) =>
|
||||
converters['unsigned long'](V, { ...opts, enforceRange: true }),
|
||||
validator: (V, opts) => {
|
||||
if (V === 0 || V % 8)
|
||||
throw lazyDOMException('Invalid KangarooTwelveParams outputLength', 'OperationError');
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'customization',
|
||||
converter: converters.BufferSource,
|
||||
},
|
||||
]);
|
||||
|
||||
converters.TurboShakeParams = createDictionaryConverter(
|
||||
'TurboShakeParams', [
|
||||
...new SafeArrayIterator(dictAlgorithm),
|
||||
{
|
||||
key: 'outputLength',
|
||||
converter: (V, opts) =>
|
||||
converters['unsigned long'](V, { ...opts, enforceRange: true }),
|
||||
validator: (V, opts) => {
|
||||
if (V === 0 || V % 8)
|
||||
throw lazyDOMException('Invalid TurboShakeParams outputLength', 'OperationError');
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'domainSeparation',
|
||||
converter: (V, opts) =>
|
||||
converters.octet(V, { ...opts, enforceRange: true }),
|
||||
validator: (V) => {
|
||||
if (V < 0x01 || V > 0x7F) {
|
||||
throw lazyDOMException(
|
||||
'TurboShakeParams.domainSeparation must be in range 0x01-0x7f',
|
||||
'OperationError');
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
converters,
|
||||
requiredArguments,
|
||||
|
||||
@@ -390,6 +390,7 @@
|
||||
'src/crypto/crypto_kem.cc',
|
||||
'src/crypto/crypto_hmac.cc',
|
||||
'src/crypto/crypto_kmac.cc',
|
||||
'src/crypto/crypto_turboshake.cc',
|
||||
'src/crypto/crypto_random.cc',
|
||||
'src/crypto/crypto_rsa.cc',
|
||||
'src/crypto/crypto_spkac.cc',
|
||||
@@ -408,6 +409,7 @@
|
||||
'src/crypto/crypto_dh.h',
|
||||
'src/crypto/crypto_hmac.h',
|
||||
'src/crypto/crypto_kmac.h',
|
||||
'src/crypto/crypto_turboshake.h',
|
||||
'src/crypto/crypto_rsa.h',
|
||||
'src/crypto/crypto_spkac.h',
|
||||
'src/crypto/crypto_util.h',
|
||||
|
||||
@@ -0,0 +1,642 @@
|
||||
#include "crypto/crypto_turboshake.h"
|
||||
#include "async_wrap-inl.h"
|
||||
#include "node_internals.h"
|
||||
#include "threadpoolwork-inl.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace node::crypto {
|
||||
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::JustVoid;
|
||||
using v8::Local;
|
||||
using v8::Maybe;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Nothing;
|
||||
using v8::Object;
|
||||
using v8::Uint32;
|
||||
using v8::Value;
|
||||
|
||||
// ============================================================================
|
||||
// Keccak-p[1600, n_r=12] permutation
|
||||
// Reference: FIPS 202, Section 3.3 and 3.4; RFC 9861 Section 2.2
|
||||
// Adapted from OpenSSL's keccak1600.c (KECCAK_REF variant)
|
||||
// ============================================================================
|
||||
namespace {
|
||||
|
||||
inline uint64_t ROL64(uint64_t val, int offset) {
|
||||
DCHECK(offset >= 0 && offset < 64);
|
||||
if (offset == 0) return val;
|
||||
return (val << offset) | (val >> (64 - offset));
|
||||
}
|
||||
|
||||
// Load/store 64-bit lanes in little-endian byte order.
|
||||
// The Keccak state uses LE lane encoding (FIPS 202 Section 1, B.1).
|
||||
// These helpers ensure correctness on both LE and BE platforms.
|
||||
inline uint64_t LoadLE64(const uint8_t* src) {
|
||||
return static_cast<uint64_t>(src[0]) | (static_cast<uint64_t>(src[1]) << 8) |
|
||||
(static_cast<uint64_t>(src[2]) << 16) |
|
||||
(static_cast<uint64_t>(src[3]) << 24) |
|
||||
(static_cast<uint64_t>(src[4]) << 32) |
|
||||
(static_cast<uint64_t>(src[5]) << 40) |
|
||||
(static_cast<uint64_t>(src[6]) << 48) |
|
||||
(static_cast<uint64_t>(src[7]) << 56);
|
||||
}
|
||||
|
||||
inline void StoreLE64(uint8_t* dst, uint64_t val) {
|
||||
dst[0] = static_cast<uint8_t>(val);
|
||||
dst[1] = static_cast<uint8_t>(val >> 8);
|
||||
dst[2] = static_cast<uint8_t>(val >> 16);
|
||||
dst[3] = static_cast<uint8_t>(val >> 24);
|
||||
dst[4] = static_cast<uint8_t>(val >> 32);
|
||||
dst[5] = static_cast<uint8_t>(val >> 40);
|
||||
dst[6] = static_cast<uint8_t>(val >> 48);
|
||||
dst[7] = static_cast<uint8_t>(val >> 56);
|
||||
}
|
||||
|
||||
static const unsigned char rhotates[5][5] = {
|
||||
{0, 1, 62, 28, 27},
|
||||
{36, 44, 6, 55, 20},
|
||||
{3, 10, 43, 25, 39},
|
||||
{41, 45, 15, 21, 8},
|
||||
{18, 2, 61, 56, 14},
|
||||
};
|
||||
|
||||
// Round constants for Keccak-f[1600].
|
||||
// TurboSHAKE uses the last 12 rounds (indices 12..23).
|
||||
static const uint64_t iotas[24] = {
|
||||
0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL,
|
||||
0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL,
|
||||
0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL,
|
||||
0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL,
|
||||
0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL,
|
||||
0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL,
|
||||
0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL,
|
||||
0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL,
|
||||
};
|
||||
|
||||
// Keccak-p[1600, 12]: the reduced-round permutation used by TurboSHAKE.
|
||||
void KeccakP1600_12(uint64_t A[5][5]) {
|
||||
for (size_t round = 12; round < 24; round++) {
|
||||
// Theta
|
||||
uint64_t C[5];
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
C[x] = A[0][x] ^ A[1][x] ^ A[2][x] ^ A[3][x] ^ A[4][x];
|
||||
}
|
||||
uint64_t D[5];
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
D[x] = C[(x + 4) % 5] ^ ROL64(C[(x + 1) % 5], 1);
|
||||
}
|
||||
for (size_t y = 0; y < 5; y++) {
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
A[y][x] ^= D[x];
|
||||
}
|
||||
}
|
||||
|
||||
// Rho
|
||||
for (size_t y = 0; y < 5; y++) {
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
A[y][x] = ROL64(A[y][x], rhotates[y][x]);
|
||||
}
|
||||
}
|
||||
|
||||
// Pi
|
||||
uint64_t T[5][5];
|
||||
memcpy(T, A, sizeof(T));
|
||||
for (size_t y = 0; y < 5; y++) {
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
A[y][x] = T[x][(3 * y + x) % 5];
|
||||
}
|
||||
}
|
||||
|
||||
// Chi
|
||||
for (size_t y = 0; y < 5; y++) {
|
||||
uint64_t row[5];
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
row[x] = A[y][x] ^ (~A[y][(x + 1) % 5] & A[y][(x + 2) % 5]);
|
||||
}
|
||||
memcpy(A[y], row, sizeof(row));
|
||||
}
|
||||
|
||||
// Iota
|
||||
A[0][0] ^= iotas[round];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TurboSHAKE sponge construction
|
||||
// RFC 9861 Section 2.2, Appendix A.2/A.3
|
||||
// ============================================================================
|
||||
|
||||
// TurboSHAKE128 rate = 168 bytes (1344 bits), capacity = 256 bits
|
||||
// TurboSHAKE256 rate = 136 bytes (1088 bits), capacity = 512 bits
|
||||
static constexpr size_t kTurboSHAKE128Rate = 168;
|
||||
static constexpr size_t kTurboSHAKE256Rate = 136;
|
||||
|
||||
void TurboSHAKE(const uint8_t* input,
|
||||
size_t input_len,
|
||||
size_t rate,
|
||||
uint8_t domain_sep,
|
||||
uint8_t* output,
|
||||
size_t output_len) {
|
||||
uint64_t A[5][5] = {};
|
||||
// Both rates (168, 136) are multiples of 8
|
||||
size_t lane_count = rate / 8;
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
// Absorb complete blocks from input
|
||||
while (offset + rate <= input_len) {
|
||||
for (size_t i = 0; i < lane_count; i++) {
|
||||
A[i / 5][i % 5] ^= LoadLE64(input + offset + i * 8);
|
||||
}
|
||||
KeccakP1600_12(A);
|
||||
offset += rate;
|
||||
}
|
||||
|
||||
// Absorb last (partial) block: remaining input bytes + domain_sep + padding
|
||||
size_t remaining = input_len - offset;
|
||||
uint8_t pad[168] = {}; // sized for max rate (TurboSHAKE128)
|
||||
if (remaining > 0) {
|
||||
memcpy(pad, input + offset, remaining);
|
||||
}
|
||||
pad[remaining] ^= domain_sep;
|
||||
pad[rate - 1] ^= 0x80;
|
||||
|
||||
for (size_t i = 0; i < lane_count; i++) {
|
||||
A[i / 5][i % 5] ^= LoadLE64(pad + i * 8);
|
||||
}
|
||||
KeccakP1600_12(A);
|
||||
|
||||
// Squeeze output
|
||||
size_t out_offset = 0;
|
||||
while (out_offset < output_len) {
|
||||
size_t block = output_len - out_offset;
|
||||
if (block > rate) block = rate;
|
||||
size_t full_lanes = block / 8;
|
||||
for (size_t i = 0; i < full_lanes; i++) {
|
||||
StoreLE64(output + out_offset + i * 8, A[i / 5][i % 5]);
|
||||
}
|
||||
size_t rem = block % 8;
|
||||
if (rem > 0) {
|
||||
uint8_t tmp[8];
|
||||
StoreLE64(tmp, A[full_lanes / 5][full_lanes % 5]);
|
||||
memcpy(output + out_offset + full_lanes * 8, tmp, rem);
|
||||
}
|
||||
out_offset += block;
|
||||
if (out_offset < output_len) {
|
||||
KeccakP1600_12(A);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience wrappers
|
||||
void TurboSHAKE128(const uint8_t* input,
|
||||
size_t input_len,
|
||||
uint8_t domain_sep,
|
||||
uint8_t* output,
|
||||
size_t output_len) {
|
||||
TurboSHAKE(
|
||||
input, input_len, kTurboSHAKE128Rate, domain_sep, output, output_len);
|
||||
}
|
||||
|
||||
void TurboSHAKE256(const uint8_t* input,
|
||||
size_t input_len,
|
||||
uint8_t domain_sep,
|
||||
uint8_t* output,
|
||||
size_t output_len) {
|
||||
TurboSHAKE(
|
||||
input, input_len, kTurboSHAKE256Rate, domain_sep, output, output_len);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// KangarooTwelve tree hashing (RFC 9861 Section 3)
|
||||
// ============================================================================
|
||||
|
||||
static constexpr size_t kChunkSize = 8192;
|
||||
|
||||
// length_encode(x): RFC 9861 Section 3.3
|
||||
// Returns byte string x_(n-1) || ... || x_0 || n
|
||||
// where x = sum of 256^i * x_i, n is smallest such that x < 256^n
|
||||
std::vector<uint8_t> LengthEncode(size_t x) {
|
||||
if (x == 0) {
|
||||
return {0x00};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> result;
|
||||
size_t val = x;
|
||||
while (val > 0) {
|
||||
result.push_back(static_cast<uint8_t>(val & 0xFF));
|
||||
val >>= 8;
|
||||
}
|
||||
|
||||
// Reverse to get big-endian: x_(n-1) || ... || x_0
|
||||
size_t n = result.size();
|
||||
for (size_t i = 0; i < n / 2; i++) {
|
||||
std::swap(result[i], result[n - 1 - i]);
|
||||
}
|
||||
|
||||
// Append n (the length of the encoding)
|
||||
result.push_back(static_cast<uint8_t>(n));
|
||||
return result;
|
||||
}
|
||||
|
||||
using TurboSHAKEFn = void (*)(const uint8_t* input,
|
||||
size_t input_len,
|
||||
uint8_t domain_sep,
|
||||
uint8_t* output,
|
||||
size_t output_len);
|
||||
|
||||
void KangarooTwelve(const uint8_t* message,
|
||||
size_t msg_len,
|
||||
const uint8_t* customization,
|
||||
size_t custom_len,
|
||||
uint8_t* output,
|
||||
size_t output_len,
|
||||
TurboSHAKEFn turboshake,
|
||||
size_t cv_len) {
|
||||
// Build S = M || C || length_encode(|C|)
|
||||
auto len_enc = LengthEncode(custom_len);
|
||||
size_t s_len = msg_len + custom_len + len_enc.size();
|
||||
|
||||
// Short message path: |S| <= 8192
|
||||
if (s_len <= kChunkSize) {
|
||||
// Build S in a contiguous buffer
|
||||
std::vector<uint8_t> s(s_len);
|
||||
size_t pos = 0;
|
||||
if (msg_len > 0) {
|
||||
memcpy(s.data() + pos, message, msg_len);
|
||||
pos += msg_len;
|
||||
}
|
||||
if (custom_len > 0) {
|
||||
memcpy(s.data() + pos, customization, custom_len);
|
||||
pos += custom_len;
|
||||
}
|
||||
memcpy(s.data() + pos, len_enc.data(), len_enc.size());
|
||||
|
||||
turboshake(s.data(), s_len, 0x07, output, output_len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Long message path: tree hashing
|
||||
// We need to process S in chunks, but S is virtual (M || C || length_encode)
|
||||
// Build a helper to read from this virtual concatenation.
|
||||
|
||||
// First chunk is S[0:8192], compute chaining values for rest
|
||||
// FinalNode = S[0:8192] || 0x03 || 0x00^7
|
||||
|
||||
// We need to read from S = M || C || length_encode(|C|)
|
||||
// Helper lambda to copy from virtual S
|
||||
auto read_s = [&](size_t s_offset, uint8_t* buf, size_t len) {
|
||||
size_t copied = 0;
|
||||
// Part 1: message
|
||||
if (s_offset < msg_len && copied < len) {
|
||||
size_t avail = msg_len - s_offset;
|
||||
size_t to_copy = avail < (len - copied) ? avail : (len - copied);
|
||||
memcpy(buf + copied, message + s_offset, to_copy);
|
||||
copied += to_copy;
|
||||
s_offset += to_copy;
|
||||
}
|
||||
// Part 2: customization
|
||||
size_t custom_start = msg_len;
|
||||
if (s_offset < custom_start + custom_len && copied < len) {
|
||||
size_t off_in_custom = s_offset - custom_start;
|
||||
size_t avail = custom_len - off_in_custom;
|
||||
size_t to_copy = avail < (len - copied) ? avail : (len - copied);
|
||||
memcpy(buf + copied, customization + off_in_custom, to_copy);
|
||||
copied += to_copy;
|
||||
s_offset += to_copy;
|
||||
}
|
||||
// Part 3: length_encode
|
||||
size_t le_start = msg_len + custom_len;
|
||||
if (s_offset < le_start + len_enc.size() && copied < len) {
|
||||
size_t off_in_le = s_offset - le_start;
|
||||
size_t avail = len_enc.size() - off_in_le;
|
||||
size_t to_copy = avail < (len - copied) ? avail : (len - copied);
|
||||
memcpy(buf + copied, len_enc.data() + off_in_le, to_copy);
|
||||
copied += to_copy;
|
||||
}
|
||||
};
|
||||
|
||||
// Start building FinalNode
|
||||
// FinalNode = S_0 || 0x03 0x00^7 || CV_1 || CV_2 || ... || CV_(n-1)
|
||||
// || length_encode(n-1) || 0xFF 0xFF
|
||||
|
||||
// Read first chunk S_0
|
||||
std::vector<uint8_t> first_chunk(kChunkSize);
|
||||
read_s(0, first_chunk.data(), kChunkSize);
|
||||
|
||||
// Start FinalNode with S_0 || 0x03 || 0x00^7
|
||||
std::vector<uint8_t> final_node;
|
||||
final_node.reserve(kChunkSize + 8 + ((s_len / kChunkSize) * cv_len) + 16);
|
||||
final_node.insert(final_node.end(), first_chunk.begin(), first_chunk.end());
|
||||
final_node.push_back(0x03);
|
||||
final_node.insert(final_node.end(), 7, 0x00);
|
||||
|
||||
// Process remaining chunks
|
||||
size_t offset = kChunkSize;
|
||||
size_t num_blocks = 0;
|
||||
std::vector<uint8_t> chunk(kChunkSize);
|
||||
std::vector<uint8_t> cv(cv_len);
|
||||
|
||||
while (offset < s_len) {
|
||||
size_t block_size = s_len - offset;
|
||||
if (block_size > kChunkSize) block_size = kChunkSize;
|
||||
|
||||
chunk.resize(block_size);
|
||||
read_s(offset, chunk.data(), block_size);
|
||||
|
||||
// CV = TurboSHAKE(chunk, 0x0B, cv_len)
|
||||
turboshake(chunk.data(), block_size, 0x0B, cv.data(), cv_len);
|
||||
|
||||
final_node.insert(final_node.end(), cv.begin(), cv.end());
|
||||
num_blocks++;
|
||||
offset += block_size;
|
||||
}
|
||||
|
||||
// Append length_encode(num_blocks) || 0xFF 0xFF
|
||||
auto num_blocks_enc = LengthEncode(num_blocks);
|
||||
final_node.insert(
|
||||
final_node.end(), num_blocks_enc.begin(), num_blocks_enc.end());
|
||||
final_node.push_back(0xFF);
|
||||
final_node.push_back(0xFF);
|
||||
|
||||
// Final hash
|
||||
turboshake(final_node.data(), final_node.size(), 0x06, output, output_len);
|
||||
}
|
||||
|
||||
void KT128(const uint8_t* message,
|
||||
size_t msg_len,
|
||||
const uint8_t* customization,
|
||||
size_t custom_len,
|
||||
uint8_t* output,
|
||||
size_t output_len) {
|
||||
KangarooTwelve(message,
|
||||
msg_len,
|
||||
customization,
|
||||
custom_len,
|
||||
output,
|
||||
output_len,
|
||||
TurboSHAKE128,
|
||||
32);
|
||||
}
|
||||
|
||||
void KT256(const uint8_t* message,
|
||||
size_t msg_len,
|
||||
const uint8_t* customization,
|
||||
size_t custom_len,
|
||||
uint8_t* output,
|
||||
size_t output_len) {
|
||||
KangarooTwelve(message,
|
||||
msg_len,
|
||||
customization,
|
||||
custom_len,
|
||||
output,
|
||||
output_len,
|
||||
TurboSHAKE256,
|
||||
64);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ============================================================================
|
||||
// TurboShake bindings
|
||||
// ============================================================================
|
||||
|
||||
TurboShakeConfig::TurboShakeConfig(TurboShakeConfig&& other) noexcept
|
||||
: job_mode(other.job_mode),
|
||||
variant(other.variant),
|
||||
output_length(other.output_length),
|
||||
domain_separation(other.domain_separation),
|
||||
data(std::move(other.data)) {}
|
||||
|
||||
TurboShakeConfig& TurboShakeConfig::operator=(
|
||||
TurboShakeConfig&& other) noexcept {
|
||||
if (&other == this) return *this;
|
||||
this->~TurboShakeConfig();
|
||||
return *new (this) TurboShakeConfig(std::move(other));
|
||||
}
|
||||
|
||||
void TurboShakeConfig::MemoryInfo(MemoryTracker* tracker) const {
|
||||
if (job_mode == kCryptoJobAsync) {
|
||||
// TODO(addaleax): Implement MemoryRetainer protocol for ByteSource
|
||||
tracker->TrackFieldWithSize("data", data.size());
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<void> TurboShakeTraits::AdditionalConfig(
|
||||
CryptoJobMode mode,
|
||||
const FunctionCallbackInfo<Value>& args,
|
||||
unsigned int offset,
|
||||
TurboShakeConfig* params) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
params->job_mode = mode;
|
||||
|
||||
// args[offset + 0] = algorithm name (string)
|
||||
CHECK(args[offset]->IsString());
|
||||
Utf8Value algorithm_name(env->isolate(), args[offset]);
|
||||
std::string_view alg = algorithm_name.ToStringView();
|
||||
|
||||
if (alg == "TurboSHAKE128") {
|
||||
params->variant = TurboShakeVariant::TurboSHAKE128;
|
||||
} else if (alg == "TurboSHAKE256") {
|
||||
params->variant = TurboShakeVariant::TurboSHAKE256;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
// args[offset + 1] = domain separation byte (uint32)
|
||||
CHECK(args[offset + 1]->IsUint32());
|
||||
params->domain_separation =
|
||||
static_cast<uint8_t>(args[offset + 1].As<Uint32>()->Value());
|
||||
CHECK_GE(params->domain_separation, 0x01);
|
||||
CHECK_LE(params->domain_separation, 0x7F);
|
||||
|
||||
// args[offset + 2] = output length in bytes (uint32)
|
||||
CHECK(args[offset + 2]->IsUint32());
|
||||
params->output_length = args[offset + 2].As<Uint32>()->Value();
|
||||
|
||||
// args[offset + 3] = data (ArrayBuffer/View)
|
||||
ArrayBufferOrViewContents<char> data(args[offset + 3]);
|
||||
if (!data.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
|
||||
return Nothing<void>();
|
||||
}
|
||||
params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource();
|
||||
|
||||
return JustVoid();
|
||||
}
|
||||
|
||||
bool TurboShakeTraits::DeriveBits(Environment* env,
|
||||
const TurboShakeConfig& params,
|
||||
ByteSource* out,
|
||||
CryptoJobMode mode) {
|
||||
CHECK_GT(params.output_length, 0);
|
||||
char* buf = MallocOpenSSL<char>(params.output_length);
|
||||
|
||||
const uint8_t* input = reinterpret_cast<const uint8_t*>(params.data.data());
|
||||
size_t input_len = params.data.size();
|
||||
|
||||
switch (params.variant) {
|
||||
case TurboShakeVariant::TurboSHAKE128:
|
||||
TurboSHAKE128(input,
|
||||
input_len,
|
||||
params.domain_separation,
|
||||
reinterpret_cast<uint8_t*>(buf),
|
||||
params.output_length);
|
||||
break;
|
||||
case TurboShakeVariant::TurboSHAKE256:
|
||||
TurboSHAKE256(input,
|
||||
input_len,
|
||||
params.domain_separation,
|
||||
reinterpret_cast<uint8_t*>(buf),
|
||||
params.output_length);
|
||||
break;
|
||||
}
|
||||
|
||||
*out = ByteSource::Allocated(buf, params.output_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
MaybeLocal<Value> TurboShakeTraits::EncodeOutput(Environment* env,
|
||||
const TurboShakeConfig& params,
|
||||
ByteSource* out) {
|
||||
return out->ToArrayBuffer(env);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// KangarooTwelve bindings
|
||||
// ============================================================================
|
||||
|
||||
KangarooTwelveConfig::KangarooTwelveConfig(
|
||||
KangarooTwelveConfig&& other) noexcept
|
||||
: job_mode(other.job_mode),
|
||||
variant(other.variant),
|
||||
output_length(other.output_length),
|
||||
data(std::move(other.data)),
|
||||
customization(std::move(other.customization)) {}
|
||||
|
||||
KangarooTwelveConfig& KangarooTwelveConfig::operator=(
|
||||
KangarooTwelveConfig&& other) noexcept {
|
||||
if (&other == this) return *this;
|
||||
this->~KangarooTwelveConfig();
|
||||
return *new (this) KangarooTwelveConfig(std::move(other));
|
||||
}
|
||||
|
||||
void KangarooTwelveConfig::MemoryInfo(MemoryTracker* tracker) const {
|
||||
if (job_mode == kCryptoJobAsync) {
|
||||
// TODO(addaleax): Implement MemoryRetainer protocol for ByteSource
|
||||
tracker->TrackFieldWithSize("data", data.size());
|
||||
tracker->TrackFieldWithSize("customization", customization.size());
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<void> KangarooTwelveTraits::AdditionalConfig(
|
||||
CryptoJobMode mode,
|
||||
const FunctionCallbackInfo<Value>& args,
|
||||
unsigned int offset,
|
||||
KangarooTwelveConfig* params) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
params->job_mode = mode;
|
||||
|
||||
// args[offset + 0] = algorithm name (string)
|
||||
CHECK(args[offset]->IsString());
|
||||
Utf8Value algorithm_name(env->isolate(), args[offset]);
|
||||
std::string_view alg = algorithm_name.ToStringView();
|
||||
|
||||
if (alg == "KT128") {
|
||||
params->variant = KangarooTwelveVariant::KT128;
|
||||
} else if (alg == "KT256") {
|
||||
params->variant = KangarooTwelveVariant::KT256;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
// args[offset + 1] = customization (BufferSource or undefined)
|
||||
if (!args[offset + 1]->IsUndefined()) {
|
||||
ArrayBufferOrViewContents<char> customization(args[offset + 1]);
|
||||
if (!customization.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "customization is too big");
|
||||
return Nothing<void>();
|
||||
}
|
||||
params->customization = mode == kCryptoJobAsync
|
||||
? customization.ToCopy()
|
||||
: customization.ToByteSource();
|
||||
}
|
||||
|
||||
// args[offset + 2] = output length in bytes (uint32)
|
||||
CHECK(args[offset + 2]->IsUint32());
|
||||
params->output_length = args[offset + 2].As<Uint32>()->Value();
|
||||
|
||||
// args[offset + 3] = data (ArrayBuffer/View)
|
||||
ArrayBufferOrViewContents<char> data(args[offset + 3]);
|
||||
if (!data.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
|
||||
return Nothing<void>();
|
||||
}
|
||||
params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource();
|
||||
|
||||
return JustVoid();
|
||||
}
|
||||
|
||||
bool KangarooTwelveTraits::DeriveBits(Environment* env,
|
||||
const KangarooTwelveConfig& params,
|
||||
ByteSource* out,
|
||||
CryptoJobMode mode) {
|
||||
CHECK_GT(params.output_length, 0);
|
||||
char* buf = MallocOpenSSL<char>(params.output_length);
|
||||
|
||||
const uint8_t* input = reinterpret_cast<const uint8_t*>(params.data.data());
|
||||
size_t input_len = params.data.size();
|
||||
|
||||
const uint8_t* custom =
|
||||
reinterpret_cast<const uint8_t*>(params.customization.data());
|
||||
size_t custom_len = params.customization.size();
|
||||
|
||||
switch (params.variant) {
|
||||
case KangarooTwelveVariant::KT128:
|
||||
KT128(input,
|
||||
input_len,
|
||||
custom,
|
||||
custom_len,
|
||||
reinterpret_cast<uint8_t*>(buf),
|
||||
params.output_length);
|
||||
break;
|
||||
case KangarooTwelveVariant::KT256:
|
||||
KT256(input,
|
||||
input_len,
|
||||
custom,
|
||||
custom_len,
|
||||
reinterpret_cast<uint8_t*>(buf),
|
||||
params.output_length);
|
||||
break;
|
||||
}
|
||||
|
||||
*out = ByteSource::Allocated(buf, params.output_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
MaybeLocal<Value> KangarooTwelveTraits::EncodeOutput(
|
||||
Environment* env, const KangarooTwelveConfig& params, ByteSource* out) {
|
||||
return out->ToArrayBuffer(env);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Registration
|
||||
// ============================================================================
|
||||
|
||||
void TurboShake::Initialize(Environment* env, Local<Object> target) {
|
||||
TurboShakeJob::Initialize(env, target);
|
||||
KangarooTwelveJob::Initialize(env, target);
|
||||
}
|
||||
|
||||
void TurboShake::RegisterExternalReferences(
|
||||
ExternalReferenceRegistry* registry) {
|
||||
TurboShakeJob::RegisterExternalReferences(registry);
|
||||
KangarooTwelveJob::RegisterExternalReferences(registry);
|
||||
}
|
||||
|
||||
} // namespace node::crypto
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifndef SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_
|
||||
#define SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include "crypto/crypto_util.h"
|
||||
|
||||
namespace node::crypto {
|
||||
|
||||
enum class TurboShakeVariant { TurboSHAKE128, TurboSHAKE256 };
|
||||
|
||||
struct TurboShakeConfig final : public MemoryRetainer {
|
||||
CryptoJobMode job_mode;
|
||||
TurboShakeVariant variant;
|
||||
uint32_t output_length; // Output length in bytes
|
||||
uint8_t domain_separation; // Domain separation byte (0x01–0x7F)
|
||||
ByteSource data;
|
||||
|
||||
TurboShakeConfig() = default;
|
||||
|
||||
explicit TurboShakeConfig(TurboShakeConfig&& other) noexcept;
|
||||
|
||||
TurboShakeConfig& operator=(TurboShakeConfig&& other) noexcept;
|
||||
|
||||
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||
SET_MEMORY_INFO_NAME(TurboShakeConfig)
|
||||
SET_SELF_SIZE(TurboShakeConfig)
|
||||
};
|
||||
|
||||
struct TurboShakeTraits final {
|
||||
using AdditionalParameters = TurboShakeConfig;
|
||||
static constexpr const char* JobName = "TurboShakeJob";
|
||||
static constexpr AsyncWrap::ProviderType Provider =
|
||||
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
|
||||
|
||||
static v8::Maybe<void> AdditionalConfig(
|
||||
CryptoJobMode mode,
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
unsigned int offset,
|
||||
TurboShakeConfig* params);
|
||||
|
||||
static bool DeriveBits(Environment* env,
|
||||
const TurboShakeConfig& params,
|
||||
ByteSource* out,
|
||||
CryptoJobMode mode);
|
||||
|
||||
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
|
||||
const TurboShakeConfig& params,
|
||||
ByteSource* out);
|
||||
};
|
||||
|
||||
using TurboShakeJob = DeriveBitsJob<TurboShakeTraits>;
|
||||
|
||||
enum class KangarooTwelveVariant { KT128, KT256 };
|
||||
|
||||
struct KangarooTwelveConfig final : public MemoryRetainer {
|
||||
CryptoJobMode job_mode;
|
||||
KangarooTwelveVariant variant;
|
||||
uint32_t output_length; // Output length in bytes
|
||||
ByteSource data;
|
||||
ByteSource customization;
|
||||
|
||||
KangarooTwelveConfig() = default;
|
||||
|
||||
explicit KangarooTwelveConfig(KangarooTwelveConfig&& other) noexcept;
|
||||
|
||||
KangarooTwelveConfig& operator=(KangarooTwelveConfig&& other) noexcept;
|
||||
|
||||
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||
SET_MEMORY_INFO_NAME(KangarooTwelveConfig)
|
||||
SET_SELF_SIZE(KangarooTwelveConfig)
|
||||
};
|
||||
|
||||
struct KangarooTwelveTraits final {
|
||||
using AdditionalParameters = KangarooTwelveConfig;
|
||||
static constexpr const char* JobName = "KangarooTwelveJob";
|
||||
static constexpr AsyncWrap::ProviderType Provider =
|
||||
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
|
||||
|
||||
static v8::Maybe<void> AdditionalConfig(
|
||||
CryptoJobMode mode,
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
unsigned int offset,
|
||||
KangarooTwelveConfig* params);
|
||||
|
||||
static bool DeriveBits(Environment* env,
|
||||
const KangarooTwelveConfig& params,
|
||||
ByteSource* out,
|
||||
CryptoJobMode mode);
|
||||
|
||||
static v8::MaybeLocal<v8::Value> EncodeOutput(
|
||||
Environment* env, const KangarooTwelveConfig& params, ByteSource* out);
|
||||
};
|
||||
|
||||
using KangarooTwelveJob = DeriveBitsJob<KangarooTwelveTraits>;
|
||||
|
||||
namespace TurboShake {
|
||||
void Initialize(Environment* env, v8::Local<v8::Object> target);
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
|
||||
} // namespace TurboShake
|
||||
|
||||
} // namespace node::crypto
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
#endif // SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_
|
||||
+4
-1
@@ -75,6 +75,8 @@ namespace crypto {
|
||||
#define KMAC_NAMESPACE_LIST(V)
|
||||
#endif
|
||||
|
||||
#define TURBOSHAKE_NAMESPACE_LIST(V) V(TurboShake)
|
||||
|
||||
#ifdef OPENSSL_NO_SCRYPT
|
||||
#define SCRYPT_NAMESPACE_LIST(V)
|
||||
#else
|
||||
@@ -86,7 +88,8 @@ namespace crypto {
|
||||
ARGON2_NAMESPACE_LIST(V) \
|
||||
KEM_NAMESPACE_LIST(V) \
|
||||
KMAC_NAMESPACE_LIST(V) \
|
||||
SCRYPT_NAMESPACE_LIST(V)
|
||||
SCRYPT_NAMESPACE_LIST(V) \
|
||||
TURBOSHAKE_NAMESPACE_LIST(V)
|
||||
|
||||
void Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "crypto/crypto_spkac.h"
|
||||
#include "crypto/crypto_timing.h"
|
||||
#include "crypto/crypto_tls.h"
|
||||
#include "crypto/crypto_turboshake.h"
|
||||
#include "crypto/crypto_util.h"
|
||||
#include "crypto/crypto_x509.h"
|
||||
|
||||
|
||||
@@ -28,6 +28,30 @@ export const vectors = {
|
||||
[false, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(1) }],
|
||||
[false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }],
|
||||
[false, { name: 'cSHAKE256', outputLength: 255 }],
|
||||
[false, 'TurboSHAKE128'],
|
||||
[true, { name: 'TurboSHAKE128', outputLength: 128 }],
|
||||
[true, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }],
|
||||
[false, { name: 'TurboSHAKE128', outputLength: 0 }],
|
||||
[false, { name: 'TurboSHAKE128', outputLength: 127 }],
|
||||
[false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x00 }],
|
||||
[false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x80 }],
|
||||
[false, 'TurboSHAKE256'],
|
||||
[true, { name: 'TurboSHAKE256', outputLength: 256 }],
|
||||
[true, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x07 }],
|
||||
[false, { name: 'TurboSHAKE256', outputLength: 0 }],
|
||||
[false, { name: 'TurboSHAKE256', outputLength: 255 }],
|
||||
[false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x00 }],
|
||||
[false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x80 }],
|
||||
[false, 'KT128'],
|
||||
[true, { name: 'KT128', outputLength: 128 }],
|
||||
[true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(0) }],
|
||||
[false, { name: 'KT128', outputLength: 0 }],
|
||||
[false, { name: 'KT128', outputLength: 127 }],
|
||||
[false, 'KT256'],
|
||||
[true, { name: 'KT256', outputLength: 256 }],
|
||||
[true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(0) }],
|
||||
[false, { name: 'KT256', outputLength: 0 }],
|
||||
[false, { name: 'KT256', outputLength: 255 }],
|
||||
],
|
||||
'sign': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const { subtle } = globalThis.crypto;
|
||||
|
||||
// RFC 9861 Section 5 test vectors
|
||||
|
||||
// Generates a Buffer of length n by repeating the pattern 00 01 02 .. F9 FA.
|
||||
function ptn(n) {
|
||||
const buf = Buffer.allocUnsafe(n);
|
||||
for (let i = 0; i < n; i++)
|
||||
buf[i] = i % 251;
|
||||
return buf;
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(
|
||||
ptn(17 ** 2).toString('hex'),
|
||||
'000102030405060708090a0b0c0d0e0f' +
|
||||
'101112131415161718191a1b1c1d1e1f' +
|
||||
'202122232425262728292a2b2c2d2e2f' +
|
||||
'303132333435363738393a3b3c3d3e3f' +
|
||||
'404142434445464748494a4b4c4d4e4f' +
|
||||
'505152535455565758595a5b5c5d5e5f' +
|
||||
'606162636465666768696a6b6c6d6e6f' +
|
||||
'707172737475767778797a7b7c7d7e7f' +
|
||||
'808182838485868788898a8b8c8d8e8f' +
|
||||
'909192939495969798999a9b9c9d9e9f' +
|
||||
'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
|
||||
'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
|
||||
'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
|
||||
'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' +
|
||||
'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' +
|
||||
'f0f1f2f3f4f5f6f7f8f9fa0001020304' +
|
||||
'05060708090a0b0c0d0e0f1011121314' +
|
||||
'15161718191a1b1c1d1e1f2021222324' +
|
||||
'25',
|
||||
);
|
||||
|
||||
const turboSHAKE128Vectors = [
|
||||
// [input, outputLengthBytes, expected(, domainSeparation)]
|
||||
[new Uint8Array(0), 32,
|
||||
'1e415f1c5983aff2169217277d17bb53' +
|
||||
'8cd945a397ddec541f1ce41af2c1b74c'],
|
||||
[new Uint8Array(0), 64,
|
||||
'1e415f1c5983aff2169217277d17bb53' +
|
||||
'8cd945a397ddec541f1ce41af2c1b74c' +
|
||||
'3e8ccae2a4dae56c84a04c2385c03c15' +
|
||||
'e8193bdf58737363321691c05462c8df'],
|
||||
[ptn(17 ** 0), 32,
|
||||
'55cedd6f60af7bb29a4042ae832ef3f5' +
|
||||
'8db7299f893ebb9247247d856958daa9'],
|
||||
[ptn(17 ** 1), 32,
|
||||
'9c97d036a3bac819db70ede0ca554ec6' +
|
||||
'e4c2a1a4ffbfd9ec269ca6a111161233'],
|
||||
[ptn(17 ** 2), 32,
|
||||
'96c77c279e0126f7fc07c9b07f5cdae1' +
|
||||
'e0be60bdbe10620040e75d7223a624d2'],
|
||||
[ptn(17 ** 3), 32,
|
||||
'd4976eb56bcf118520582b709f73e1d6' +
|
||||
'853e001fdaf80e1b13e0d0599d5fb372'],
|
||||
[ptn(17 ** 4), 32,
|
||||
'da67c7039e98bf530cf7a37830c6664e' +
|
||||
'14cbab7f540f58403b1b82951318ee5c'],
|
||||
[ptn(17 ** 5), 32,
|
||||
'b97a906fbf83ef7c812517abf3b2d0ae' +
|
||||
'a0c4f60318ce11cf103925127f59eecd'],
|
||||
[ptn(17 ** 6), 32,
|
||||
'35cd494adeded2f25239af09a7b8ef0c' +
|
||||
'4d1ca4fe2d1ac370fa63216fe7b4c2b1'],
|
||||
[Buffer.from('ffffff', 'hex'), 32,
|
||||
'bf323f940494e88ee1c540fe660be8a0' +
|
||||
'c93f43d15ec006998462fa994eed5dab', 0x01],
|
||||
[Buffer.from('ff', 'hex'), 32,
|
||||
'8ec9c66465ed0d4a6c35d13506718d68' +
|
||||
'7a25cb05c74cca1e42501abd83874a67', 0x06],
|
||||
[Buffer.from('ffffff', 'hex'), 32,
|
||||
'b658576001cad9b1e5f399a9f77723bb' +
|
||||
'a05458042d68206f7252682dba3663ed', 0x07],
|
||||
[Buffer.from('ffffffffffffff', 'hex'), 32,
|
||||
'8deeaa1aec47ccee569f659c21dfa8e1' +
|
||||
'12db3cee37b18178b2acd805b799cc37', 0x0b],
|
||||
[Buffer.from('ff', 'hex'), 32,
|
||||
'553122e2135e363c3292bed2c6421fa2' +
|
||||
'32bab03daa07c7d6636603286506325b', 0x30],
|
||||
[Buffer.from('ffffff', 'hex'), 32,
|
||||
'16274cc656d44cefd422395d0f9053bd' +
|
||||
'a6d28e122aba15c765e5ad0e6eaf26f9', 0x7f],
|
||||
];
|
||||
|
||||
const turboSHAKE256Vectors = [
|
||||
// [input, outputLengthBytes, expected(, domainSeparation)]
|
||||
[new Uint8Array(0), 64,
|
||||
'367a329dafea871c7802ec67f905ae13' +
|
||||
'c57695dc2c6663c61035f59a18f8e7db' +
|
||||
'11edc0e12e91ea60eb6b32df06dd7f00' +
|
||||
'2fbafabb6e13ec1cc20d995547600db0'],
|
||||
[ptn(17 ** 0), 64,
|
||||
'3e1712f928f8eaf1054632b2aa0a246e' +
|
||||
'd8b0c378728f60bc970410155c28820e' +
|
||||
'90cc90d8a3006aa2372c5c5ea176b068' +
|
||||
'2bf22bae7467ac94f74d43d39b0482e2'],
|
||||
[ptn(17 ** 1), 64,
|
||||
'b3bab0300e6a191fbe61379398359235' +
|
||||
'78794ea54843f5011090fa2f3780a9e5' +
|
||||
'cb22c59d78b40a0fbff9e672c0fbe097' +
|
||||
'0bd2c845091c6044d687054da5d8e9c7'],
|
||||
[ptn(17 ** 2), 64,
|
||||
'66b810db8e90780424c0847372fdc957' +
|
||||
'10882fde31c6df75beb9d4cd9305cfca' +
|
||||
'e35e7b83e8b7e6eb4b78605880116316' +
|
||||
'fe2c078a09b94ad7b8213c0a738b65c0'],
|
||||
[ptn(17 ** 3), 64,
|
||||
'c74ebc919a5b3b0dd1228185ba02d29e' +
|
||||
'f442d69d3d4276a93efe0bf9a16a7dc0' +
|
||||
'cd4eabadab8cd7a5edd96695f5d360ab' +
|
||||
'e09e2c6511a3ec397da3b76b9e1674fb'],
|
||||
[ptn(17 ** 4), 64,
|
||||
'02cc3a8897e6f4f6ccb6fd46631b1f52' +
|
||||
'07b66c6de9c7b55b2d1a23134a170afd' +
|
||||
'ac234eaba9a77cff88c1f020b7372461' +
|
||||
'8c5687b362c430b248cd38647f848a1d'],
|
||||
[ptn(17 ** 5), 64,
|
||||
'add53b06543e584b5823f626996aee50' +
|
||||
'fe45ed15f20243a7165485acb4aa76b4' +
|
||||
'ffda75cedf6d8cdc95c332bd56f4b986' +
|
||||
'b58bb17d1778bfc1b1a97545cdf4ec9f'],
|
||||
[ptn(17 ** 6), 64,
|
||||
'9e11bc59c24e73993c1484ec66358ef7' +
|
||||
'1db74aefd84e123f7800ba9c4853e02c' +
|
||||
'fe701d9e6bb765a304f0dc34a4ee3ba8' +
|
||||
'2c410f0da70e86bfbd90ea877c2d6104'],
|
||||
[Buffer.from('ffffff', 'hex'), 64,
|
||||
'd21c6fbbf587fa2282f29aea620175fb' +
|
||||
'0257413af78a0b1b2a87419ce031d933' +
|
||||
'ae7a4d383327a8a17641a34f8a1d1003' +
|
||||
'ad7da6b72dba84bb62fef28f62f12424', 0x01],
|
||||
[Buffer.from('ff', 'hex'), 64,
|
||||
'738d7b4e37d18b7f22ad1b5313e357e3' +
|
||||
'dd7d07056a26a303c433fa3533455280' +
|
||||
'f4f5a7d4f700efb437fe6d281405e07b' +
|
||||
'e32a0a972e22e63adc1b090daefe004b', 0x06],
|
||||
[Buffer.from('ffffff', 'hex'), 64,
|
||||
'18b3b5b7061c2e67c1753a00e6ad7ed7' +
|
||||
'ba1c906cf93efb7092eaf27fbeebb755' +
|
||||
'ae6e292493c110e48d260028492b8e09' +
|
||||
'b5500612b8f2578985ded5357d00ec67', 0x07],
|
||||
[Buffer.from('ffffffffffffff', 'hex'), 64,
|
||||
'bb36764951ec97e9d85f7ee9a67a7718' +
|
||||
'fc005cf42556be79ce12c0bde50e5736' +
|
||||
'd6632b0d0dfb202d1bbb8ffe3dd74cb0' +
|
||||
'0834fa756cb03471bab13a1e2c16b3c0', 0x0b],
|
||||
[Buffer.from('ff', 'hex'), 64,
|
||||
'f3fe12873d34bcbb2e608779d6b70e7f' +
|
||||
'86bec7e90bf113cbd4fdd0c4e2f4625e' +
|
||||
'148dd7ee1a52776cf77f240514d9ccfc' +
|
||||
'3b5ddab8ee255e39ee389072962c111a', 0x30],
|
||||
[Buffer.from('ffffff', 'hex'), 64,
|
||||
'abe569c1f77ec340f02705e7d37c9ab7' +
|
||||
'e155516e4a6a150021d70b6fac0bb40c' +
|
||||
'069f9a9828a0d575cd99f9bae435ab1a' +
|
||||
'cf7ed9110ba97ce0388d074bac768776', 0x7f],
|
||||
];
|
||||
|
||||
const kt128Vectors = [
|
||||
// [input, outputLengthBytes, expected(, customization)]
|
||||
[new Uint8Array(0), 32,
|
||||
'1ac2d450fc3b4205d19da7bfca1b3751' +
|
||||
'3c0803577ac7167f06fe2ce1f0ef39e5'],
|
||||
[new Uint8Array(0), 64,
|
||||
'1ac2d450fc3b4205d19da7bfca1b3751' +
|
||||
'3c0803577ac7167f06fe2ce1f0ef39e5' +
|
||||
'4269c056b8c82e48276038b6d292966c' +
|
||||
'c07a3d4645272e31ff38508139eb0a71'],
|
||||
[ptn(1), 32,
|
||||
'2bda92450e8b147f8a7cb629e784a058' +
|
||||
'efca7cf7d8218e02d345dfaa65244a1f'],
|
||||
[ptn(17), 32,
|
||||
'6bf75fa2239198db4772e36478f8e19b' +
|
||||
'0f371205f6a9a93a273f51df37122888'],
|
||||
[ptn(17 ** 2), 32,
|
||||
'0c315ebcdedbf61426de7dcf8fb725d1' +
|
||||
'e74675d7f5327a5067f367b108ecb67c'],
|
||||
[ptn(17 ** 3), 32,
|
||||
'cb552e2ec77d9910701d578b457ddf77' +
|
||||
'2c12e322e4ee7fe417f92c758f0d59d0'],
|
||||
[ptn(17 ** 4), 32,
|
||||
'8701045e22205345ff4dda05555cbb5c' +
|
||||
'3af1a771c2b89baef37db43d9998b9fe'],
|
||||
[ptn(17 ** 5), 32,
|
||||
'844d610933b1b9963cbdeb5ae3b6b05c' +
|
||||
'c7cbd67ceedf883eb678a0a8e0371682'],
|
||||
[ptn(17 ** 6), 32,
|
||||
'3c390782a8a4e89fa6367f72feaaf132' +
|
||||
'55c8d95878481d3cd8ce85f58e880af8'],
|
||||
[new Uint8Array(0), 32,
|
||||
'fab658db63e94a246188bf7af69a1330' +
|
||||
'45f46ee984c56e3c3328caaf1aa1a583', ptn(1)],
|
||||
[Buffer.from('ff', 'hex'), 32,
|
||||
'd848c5068ced736f4462159b9867fd4c' +
|
||||
'20b808acc3d5bc48e0b06ba0a3762ec4', ptn(41)],
|
||||
[Buffer.from('ffffff', 'hex'), 32,
|
||||
'c389e5009ae57120854c2e8c64670ac0' +
|
||||
'1358cf4c1baf89447a724234dc7ced74', ptn(41 ** 2)],
|
||||
[Buffer.from('ffffffffffffff', 'hex'), 32,
|
||||
'75d2f86a2e644566726b4fbcfc5657b9' +
|
||||
'dbcf070c7b0dca06450ab291d7443bcf', ptn(41 ** 3)],
|
||||
[ptn(8191), 32,
|
||||
'1b577636f723643e990cc7d6a6598374' +
|
||||
'36fd6a103626600eb8301cd1dbe553d6'],
|
||||
[ptn(8192), 32,
|
||||
'48f256f6772f9edfb6a8b661ec92dc93' +
|
||||
'b95ebd05a08a17b39ae3490870c926c3'],
|
||||
[ptn(8192), 32,
|
||||
'3ed12f70fb05ddb58689510ab3e4d23c' +
|
||||
'6c6033849aa01e1d8c220a297fedcd0b', ptn(8189)],
|
||||
[ptn(8192), 32,
|
||||
'6a7c1b6a5cd0d8c9ca943a4a216cc646' +
|
||||
'04559a2ea45f78570a15253d67ba00ae', ptn(8190)],
|
||||
];
|
||||
|
||||
const kt256Vectors = [
|
||||
// [input, outputLengthBytes, expected(, customization)]
|
||||
[new Uint8Array(0), 64,
|
||||
'b23d2e9cea9f4904e02bec06817fc10c' +
|
||||
'e38ce8e93ef4c89e6537076af8646404' +
|
||||
'e3e8b68107b8833a5d30490aa3348235' +
|
||||
'3fd4adc7148ecb782855003aaebde4a9'],
|
||||
[new Uint8Array(0), 128,
|
||||
'b23d2e9cea9f4904e02bec06817fc10c' +
|
||||
'e38ce8e93ef4c89e6537076af8646404' +
|
||||
'e3e8b68107b8833a5d30490aa3348235' +
|
||||
'3fd4adc7148ecb782855003aaebde4a9' +
|
||||
'b0925319d8ea1e121a609821ec19efea' +
|
||||
'89e6d08daee1662b69c840289f188ba8' +
|
||||
'60f55760b61f82114c030c97e5178449' +
|
||||
'608ccd2cd2d919fc7829ff69931ac4d0'],
|
||||
[ptn(1), 64,
|
||||
'0d005a194085360217128cf17f91e1f7' +
|
||||
'1314efa5564539d444912e3437efa17f' +
|
||||
'82db6f6ffe76e781eaa068bce01f2bbf' +
|
||||
'81eacb983d7230f2fb02834a21b1ddd0'],
|
||||
[ptn(17), 64,
|
||||
'1ba3c02b1fc514474f06c8979978a905' +
|
||||
'6c8483f4a1b63d0dccefe3a28a2f323e' +
|
||||
'1cdcca40ebf006ac76ef039715234683' +
|
||||
'7b1277d3e7faa9c9653b19075098527b'],
|
||||
[ptn(17 ** 2), 64,
|
||||
'de8ccbc63e0f133ebb4416814d4c66f6' +
|
||||
'91bbf8b6a61ec0a7700f836b086cb029' +
|
||||
'd54f12ac7159472c72db118c35b4e6aa' +
|
||||
'213c6562caaa9dcc518959e69b10f3ba'],
|
||||
[ptn(17 ** 3), 64,
|
||||
'647efb49fe9d717500171b41e7f11bd4' +
|
||||
'91544443209997ce1c2530d15eb1ffbb' +
|
||||
'598935ef954528ffc152b1e4d731ee26' +
|
||||
'83680674365cd191d562bae753b84aa5'],
|
||||
[ptn(17 ** 4), 64,
|
||||
'b06275d284cd1cf205bcbe57dccd3ec1' +
|
||||
'ff6686e3ed15776383e1f2fa3c6ac8f0' +
|
||||
'8bf8a162829db1a44b2a43ff83dd89c3' +
|
||||
'cf1ceb61ede659766d5ccf817a62ba8d'],
|
||||
[ptn(17 ** 5), 64,
|
||||
'9473831d76a4c7bf77ace45b59f1458b' +
|
||||
'1673d64bcd877a7c66b2664aa6dd149e' +
|
||||
'60eab71b5c2bab858c074ded81ddce2b' +
|
||||
'4022b5215935c0d4d19bf511aeeb0772'],
|
||||
[ptn(17 ** 6), 64,
|
||||
'0652b740d78c5e1f7c8dcc1777097382' +
|
||||
'768b7ff38f9a7a20f29f413bb1b3045b' +
|
||||
'31a5578f568f911e09cf44746da84224' +
|
||||
'a5266e96a4a535e871324e4f9c7004da'],
|
||||
[new Uint8Array(0), 64,
|
||||
'9280f5cc39b54a5a594ec63de0bb9937' +
|
||||
'1e4609d44bf845c2f5b8c316d72b1598' +
|
||||
'11f748f23e3fabbe5c3226ec96c62186' +
|
||||
'df2d33e9df74c5069ceecbb4dd10eff6', ptn(1)],
|
||||
[Buffer.from('ff', 'hex'), 64,
|
||||
'47ef96dd616f200937aa7847e34ec2fe' +
|
||||
'ae8087e3761dc0f8c1a154f51dc9ccf8' +
|
||||
'45d7adbce57ff64b639722c6a1672e3b' +
|
||||
'f5372d87e00aff89be97240756998853', ptn(41)],
|
||||
[Buffer.from('ffffff', 'hex'), 64,
|
||||
'3b48667a5051c5966c53c5d42b95de45' +
|
||||
'1e05584e7806e2fb765eda959074172c' +
|
||||
'b438a9e91dde337c98e9c41bed94c4e0' +
|
||||
'aef431d0b64ef2324f7932caa6f54969', ptn(41 ** 2)],
|
||||
[Buffer.from('ffffffffffffff', 'hex'), 64,
|
||||
'e0911cc00025e1540831e266d94add9b' +
|
||||
'98712142b80d2629e643aac4efaf5a3a' +
|
||||
'30a88cbf4ac2a91a2432743054fbcc98' +
|
||||
'97670e86ba8cec2fc2ace9c966369724', ptn(41 ** 3)],
|
||||
[ptn(8191), 64,
|
||||
'3081434d93a4108d8d8a3305b89682ce' +
|
||||
'bedc7ca4ea8a3ce869fbb73cbe4a58ee' +
|
||||
'f6f24de38ffc170514c70e7ab2d01f03' +
|
||||
'812616e863d769afb3753193ba045b20'],
|
||||
[ptn(8192), 64,
|
||||
'c6ee8e2ad3200c018ac87aaa031cdac2' +
|
||||
'2121b412d07dc6e0dccbb53423747e9a' +
|
||||
'1c18834d99df596cf0cf4b8dfafb7bf0' +
|
||||
'2d139d0c9035725adc1a01b7230a41fa'],
|
||||
[ptn(8192), 64,
|
||||
'74e47879f10a9c5d11bd2da7e194fe57' +
|
||||
'e86378bf3c3f7448eff3c576a0f18c5c' +
|
||||
'aae0999979512090a7f348af4260d4de' +
|
||||
'3c37f1ecaf8d2c2c96c1d16c64b12496', ptn(8189)],
|
||||
[ptn(8192), 64,
|
||||
'f4b5908b929ffe01e0f79ec2f21243d4' +
|
||||
'1a396b2e7303a6af1d6399cd6c7a0a2d' +
|
||||
'd7c4f607e8277f9c9b1cb4ab9ddc59d4' +
|
||||
'b92d1fc7558441f1832c3279a4241b8b', ptn(8190)],
|
||||
];
|
||||
|
||||
async function checkDigest(name, vectors) {
|
||||
const isKT = name.startsWith('KT');
|
||||
for (const [input, outputLength, expected, ...rest] of vectors) {
|
||||
const algorithm = { name, outputLength: outputLength * 8 };
|
||||
if (rest.length) {
|
||||
if (isKT)
|
||||
algorithm.customization = rest[0];
|
||||
else
|
||||
algorithm.domainSeparation = rest[0];
|
||||
}
|
||||
const result = await subtle.digest(algorithm, input);
|
||||
assert.deepStrictEqual(
|
||||
Buffer.from(result).toString('hex'),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await checkDigest('TurboSHAKE128', turboSHAKE128Vectors);
|
||||
|
||||
// TurboSHAKE128(M=00^0, D=1F, 10032), last 32 bytes
|
||||
{
|
||||
const result = await subtle.digest({
|
||||
name: 'TurboSHAKE128',
|
||||
outputLength: 10032 * 8,
|
||||
}, new Uint8Array(0));
|
||||
assert.deepStrictEqual(
|
||||
Buffer.from(result).subarray(-32).toString('hex'),
|
||||
'a3b9b0385900ce761f22aed548e754da' +
|
||||
'10a5242d62e8c658e3f3a923a7555607',
|
||||
);
|
||||
}
|
||||
|
||||
await checkDigest('TurboSHAKE256', turboSHAKE256Vectors);
|
||||
|
||||
// TurboSHAKE256(M=00^0, D=1F, 10032), last 32 bytes
|
||||
{
|
||||
const result = await subtle.digest({
|
||||
name: 'TurboSHAKE256',
|
||||
outputLength: 10032 * 8,
|
||||
}, new Uint8Array(0));
|
||||
assert.deepStrictEqual(
|
||||
Buffer.from(result).subarray(-32).toString('hex'),
|
||||
'abefa11630c661269249742685ec082f' +
|
||||
'207265dccf2f43534e9c61ba0c9d1d75',
|
||||
);
|
||||
}
|
||||
|
||||
await checkDigest('KT128', kt128Vectors);
|
||||
|
||||
// KT128(M=00^0, C=00^0, 10032), last 32 bytes
|
||||
{
|
||||
const result = await subtle.digest({
|
||||
name: 'KT128',
|
||||
outputLength: 10032 * 8,
|
||||
}, new Uint8Array(0));
|
||||
assert.deepStrictEqual(
|
||||
Buffer.from(result).subarray(-32).toString('hex'),
|
||||
'e8dc563642f7228c84684c898405d3a8' +
|
||||
'34799158c079b12880277a1d28e2ff6d',
|
||||
);
|
||||
}
|
||||
|
||||
await checkDigest('KT256', kt256Vectors);
|
||||
|
||||
// KT256(M=00^0, C=00^0, 10064), last 64 bytes
|
||||
{
|
||||
const result = await subtle.digest({
|
||||
name: 'KT256',
|
||||
outputLength: 10064 * 8,
|
||||
}, new Uint8Array(0));
|
||||
assert.deepStrictEqual(
|
||||
Buffer.from(result).subarray(-64).toString('hex'),
|
||||
'ad4a1d718cf950506709a4c33396139b' +
|
||||
'4449041fc79a05d68da35f1e453522e0' +
|
||||
'56c64fe94958e7085f2964888259b993' +
|
||||
'2752f3ccd855288efee5fcbb8b563069',
|
||||
);
|
||||
}
|
||||
})().then(common.mustCall());
|
||||
@@ -0,0 +1,181 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const { subtle } = globalThis.crypto;
|
||||
|
||||
const kSourceData = {
|
||||
empty: '',
|
||||
short: '156eea7cc14c56cb94db030a4a9d95ff',
|
||||
medium: 'b6c8f9df648cd088b70f38e74197b18cb81e1e435' +
|
||||
'0d50bccb8fb5a7379c87bb2e3d6ed5461ed1e9f36' +
|
||||
'f340a3962a446b815b794b4bd43a4403502077b22' +
|
||||
'56cc807837f3aacd118eb4b9c2baeb897068625ab' +
|
||||
'aca193',
|
||||
long: null
|
||||
};
|
||||
|
||||
kSourceData.long = kSourceData.medium.repeat(1024);
|
||||
|
||||
// Test vectors generated with PyCryptodome as a reference implementation
|
||||
const kDigestedData = {
|
||||
'turboshake128': {
|
||||
empty: '1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c',
|
||||
short: 'f8d1ebf3b48b71b0514686090eb25f1de322a00149be9b4dc5f09ac9077cd8a8',
|
||||
medium: '0d0e7eceb4ae58c3c48f6c2bad56d0f8ff3f887468d3ea55a138aedf395233c0',
|
||||
long: '5747c06f02ffd9d6c911b6453cc8b717083ab6417319a6ec5c3bb39ed0baf331',
|
||||
},
|
||||
'turboshake256': {
|
||||
empty: '367a329dafea871c7802ec67f905ae13c57695dc2c6663c61035f59a18f8e7db' +
|
||||
'11edc0e12e91ea60eb6b32df06dd7f002fbafabb6e13ec1cc20d995547600db0',
|
||||
short: 'b47aa0a5b76caf9b10cfaeff036df0cdb86362d2bd036a2fee0cd0d74e79279c' +
|
||||
'b9c57a70da1e3dd9e126a469857ba4c82b0efb3ae06d1a3781a6f102c3eb3a1d',
|
||||
medium: '7fa19fd828762d2dba6eea8407d1fb04302b5a4f1ca3d00b3672c1e3b3331d18' +
|
||||
'925b7ec380f3f04673a164dab04d2a0c5c12818046284c38d286645741a8aa3e',
|
||||
long: '12d0b90c08f588710733cc07f0a2d6ab0795a4a24904c111062226fcd9d5dcb2' +
|
||||
'1d6b5b848c9aebbcab221f031e9b4ea71e099ec785e822b1b83e73d0750ca1a7',
|
||||
},
|
||||
'kt128': {
|
||||
empty: '1ac2d450fc3b4205d19da7bfca1b37513c0803577ac7167f06fe2ce1f0ef39e5',
|
||||
short: '4719a2ac1dc1c592521cf201df3f476ea496fe461abe9a2604527f6bec047579',
|
||||
medium: '00f3add71679681720b925416953897ac62cfae97060dd5f2e1641a076580cc9',
|
||||
long: 'c05805c2736deb4be3fca6e3717b9af0aa18ceeaaeeab66b328a3ffebf0a814d',
|
||||
},
|
||||
'kt256': {
|
||||
empty: 'b23d2e9cea9f4904e02bec06817fc10ce38ce8e93ef4c89e6537076af8646404' +
|
||||
'e3e8b68107b8833a5d30490aa33482353fd4adc7148ecb782855003aaebde4a9',
|
||||
short: '6709e5e312f2dee4547ecb0ab7d42728ba57985983731afbd6c2a0676c522274' +
|
||||
'cf9153064ee07982129d3f58d4dbe00050eb28b392559bdb020aca302b7a28cb',
|
||||
medium: '9078b6ff78e9c4b3c8ff49e5b9f337b36cc6d6749d23985035886d993db69f7e' +
|
||||
'05fea97125e0889130da09fc5837761f7793e3e44d85be1ee1f6af7f4a1f50cb',
|
||||
long: '41f83b7c7d02fc6d98f1fa1474d765caff4673f90cd7204894d7da72d97403b6' +
|
||||
'2fe5c4bae2bf0ce3dcd51e80c98bd25ce5fe54040259d9466b67f1517dac0712',
|
||||
},
|
||||
};
|
||||
|
||||
function buildAlg(name) {
|
||||
const lower = name.toLowerCase();
|
||||
if (lower.startsWith('turboshake')) {
|
||||
const outputLength = lower === 'turboshake128' ? 256 : 512;
|
||||
return { name, outputLength };
|
||||
}
|
||||
if (lower.startsWith('kt')) {
|
||||
const outputLength = lower === 'kt128' ? 256 : 512;
|
||||
return { name, outputLength };
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
async function testDigest(size, alg) {
|
||||
const digest = await subtle.digest(
|
||||
alg,
|
||||
Buffer.from(kSourceData[size], 'hex'));
|
||||
|
||||
assert.strictEqual(
|
||||
Buffer.from(digest).toString('hex'),
|
||||
kDigestedData[(alg.name || alg).toLowerCase()][size]);
|
||||
}
|
||||
|
||||
// Known-answer tests
|
||||
(async function() {
|
||||
const variations = [];
|
||||
Object.keys(kSourceData).forEach((size) => {
|
||||
Object.keys(kDigestedData).forEach((alg) => {
|
||||
const upCase = alg.toUpperCase();
|
||||
const downCase = alg.toLowerCase();
|
||||
const mixedCase = upCase.slice(0, 1) + downCase.slice(1);
|
||||
|
||||
variations.push(testDigest(size, buildAlg(upCase)));
|
||||
variations.push(testDigest(size, buildAlg(downCase)));
|
||||
variations.push(testDigest(size, buildAlg(mixedCase)));
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(variations);
|
||||
})().then(common.mustCall());
|
||||
|
||||
// Edge cases: zero-length output rejects
|
||||
(async () => {
|
||||
await assert.rejects(
|
||||
subtle.digest({ name: 'TurboSHAKE128', outputLength: 0 }, Buffer.alloc(1)),
|
||||
{
|
||||
name: 'OperationError',
|
||||
message: 'Invalid TurboShakeParams outputLength',
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
subtle.digest({ name: 'KT128', outputLength: 0 }, Buffer.alloc(1)),
|
||||
{
|
||||
name: 'OperationError',
|
||||
message: 'Invalid KangarooTwelveParams outputLength',
|
||||
});
|
||||
})().then(common.mustCall());
|
||||
|
||||
// Edge case: non-byte-aligned outputLength rejects
|
||||
(async () => {
|
||||
await assert.rejects(
|
||||
subtle.digest({ name: 'TurboSHAKE128', outputLength: 7 }, Buffer.alloc(1)),
|
||||
{
|
||||
name: 'OperationError',
|
||||
message: 'Invalid TurboShakeParams outputLength',
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
subtle.digest({ name: 'KT128', outputLength: 7 }, Buffer.alloc(1)),
|
||||
{
|
||||
name: 'OperationError',
|
||||
message: 'Invalid KangarooTwelveParams outputLength',
|
||||
});
|
||||
})().then(common.mustCall());
|
||||
|
||||
// TurboSHAKE domain separation byte
|
||||
(async () => {
|
||||
// Domain separation 0x07 should produce different output than default 0x1F
|
||||
const [d07, d1f] = await Promise.all([
|
||||
subtle.digest(
|
||||
{ name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x07 },
|
||||
Buffer.alloc(0)),
|
||||
subtle.digest(
|
||||
{ name: 'TurboSHAKE128', outputLength: 256 },
|
||||
Buffer.alloc(0)),
|
||||
]);
|
||||
assert.notDeepStrictEqual(
|
||||
new Uint8Array(d07),
|
||||
new Uint8Array(d1f));
|
||||
|
||||
// Verify D=0x07 against known vector
|
||||
assert.strictEqual(
|
||||
Buffer.from(d07).toString('hex'),
|
||||
'5a223ad30b3b8c66a243048cfced430f54e7529287d15150b973133adfac6a2f');
|
||||
})().then(common.mustCall());
|
||||
|
||||
// KT128 with customization string
|
||||
(async () => {
|
||||
const digest = await subtle.digest(
|
||||
{ name: 'KT128', outputLength: 256, customization: Buffer.from('test') },
|
||||
Buffer.from('hello'));
|
||||
assert(digest instanceof ArrayBuffer);
|
||||
assert.strictEqual(digest.byteLength, 32);
|
||||
})().then(common.mustCall());
|
||||
|
||||
// TurboSHAKE domain separation out of range
|
||||
(async () => {
|
||||
await assert.rejects(
|
||||
subtle.digest(
|
||||
{ name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x00 },
|
||||
Buffer.alloc(0)),
|
||||
{
|
||||
name: 'OperationError',
|
||||
});
|
||||
await assert.rejects(
|
||||
subtle.digest(
|
||||
{ name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x80 },
|
||||
Buffer.alloc(0)),
|
||||
{
|
||||
name: 'OperationError',
|
||||
});
|
||||
})().then(common.mustCall());
|
||||
@@ -55,12 +55,6 @@ module.exports = {
|
||||
'historical.any.js': {
|
||||
'skip': 'Not relevant in Node.js context',
|
||||
},
|
||||
'digest/turboshake.tentative.https.any.js': {
|
||||
'skip': 'not implemented',
|
||||
},
|
||||
'digest/kangarootwelve.tentative.https.any.js': {
|
||||
'skip': 'not implemented',
|
||||
},
|
||||
'sign_verify/eddsa_small_order_points.https.any.js': {
|
||||
'fail': {
|
||||
'note': 'see https://github.com/nodejs/node/issues/54572',
|
||||
|
||||
Reference in new issue
Block a user