diff --git a/sw/device/silicon_creator/lib/drivers/kmac.c b/sw/device/silicon_creator/lib/drivers/kmac.c index eae3555d8d9a4..4ec9f385d2c23 100644 --- a/sw/device/silicon_creator/lib/drivers/kmac.c +++ b/sw/device/silicon_creator/lib/drivers/kmac.c @@ -89,6 +89,10 @@ typedef struct kmac_config { * The algorithm: SHA3, SHAKE or cSHAKE */ uint8_t mode; + /** + * Hashing strength: L128, L224, L256, L384 or L512 + */ + uint8_t kstrength; } kmac_config_t; /** @@ -173,9 +177,9 @@ static rom_error_t kmac_configure(kmac_config_t config) { // poll `STATUS.fifo_depth` to avoid a specific EDN-KMAC-Ibex deadlock // scenario. See `absorb()` and the KMAC documentation for details. cfg_reg = bitfield_bit32_write(cfg_reg, KMAC_CFG_SHADOWED_KMAC_EN_BIT, 0); - // Set `CFG.KSTRENGTH` field to 256-bit strength. + // Set `CFG.KSTRENGTH` field. cfg_reg = bitfield_field32_write(cfg_reg, KMAC_CFG_SHADOWED_KSTRENGTH_FIELD, - KMAC_CFG_SHADOWED_KSTRENGTH_VALUE_L256); + config.kstrength); // Set `CFG.MODE` field to SHAKE. cfg_reg = bitfield_field32_write(cfg_reg, KMAC_CFG_SHADOWED_MODE_FIELD, config.mode); @@ -246,6 +250,7 @@ rom_error_t kmac_keymgr_configure(void) { .sideload = true, .kmac_en = false, .mode = KMAC_CFG_SHADOWED_MODE_VALUE_SHAKE, + .kstrength = KMAC_CFG_SHADOWED_KSTRENGTH_VALUE_L256, }); } @@ -257,6 +262,7 @@ rom_error_t kmac_kmac256_sw_configure(void) { .sideload = false, .kmac_en = true, .mode = KMAC_CFG_SHADOWED_MODE_VALUE_CSHAKE, + .kstrength = KMAC_CFG_SHADOWED_KSTRENGTH_VALUE_L256, }); } @@ -268,6 +274,18 @@ rom_error_t kmac_kmac256_hw_configure(void) { .sideload = true, .kmac_en = true, .mode = KMAC_CFG_SHADOWED_MODE_VALUE_CSHAKE, + .kstrength = KMAC_CFG_SHADOWED_KSTRENGTH_VALUE_L256, + }); +} + +rom_error_t kmac_shake128_configure(void) { + return kmac_configure((kmac_config_t){ + .entropy_fast_process = false, + .msg_mask = false, + .sideload = false, + .kmac_en = false, + .mode = KMAC_CFG_SHADOWED_MODE_VALUE_SHAKE, + .kstrength = KMAC_CFG_SHADOWED_KSTRENGTH_VALUE_L128, }); } @@ -278,6 +296,7 @@ rom_error_t kmac_shake256_configure(void) { .sideload = false, .kmac_en = false, .mode = KMAC_CFG_SHADOWED_MODE_VALUE_SHAKE, + .kstrength = KMAC_CFG_SHADOWED_KSTRENGTH_VALUE_L256, }); } @@ -350,11 +369,18 @@ void kmac_shake256_squeeze_start(void) { } rom_error_t kmac_shake256_squeeze_end(uint32_t *out, size_t outlen) { + HARDENED_RETURN_IF_ERROR( + kmac_squeeze_words(out, outlen, kShake256KeccakRateWords)); + return kmac_done(); +} + +rom_error_t kmac_squeeze_words(uint32_t *out, size_t out_words, + size_t rate_words) { size_t idx = 0; - while (launder32(idx) < outlen) { - // Since we always read in increments of the SHAKE-256 rate, the index at - // start should always be a multiple of the rate. - HARDENED_CHECK_EQ(idx % kShake256KeccakRateWords, 0); + while (launder32(idx) < out_words) { + // Since we always read in increments of the rate, the index at the start + // of each squeeze cycle must be a multiple of the rate. + HARDENED_CHECK_EQ(idx % rate_words, 0); // Poll the status register until in the 'squeeze' state. HARDENED_RETURN_IF_ERROR(poll_state(KMAC_STATUS_SHA3_SQUEEZE_BIT)); @@ -362,8 +388,7 @@ rom_error_t kmac_shake256_squeeze_end(uint32_t *out, size_t outlen) { // Read words from the state registers (either `outlen` or the maximum // number of words available). size_t offset = 0; - for (; launder32(idx) < outlen && offset < kShake256KeccakRateWords; - ++offset) { + for (; launder32(idx) < out_words && offset < rate_words; ++offset) { uint32_t share0 = abs_mmio_read32(kAddrStateShare0 + offset * sizeof(uint32_t)); uint32_t share1 = @@ -372,15 +397,18 @@ rom_error_t kmac_shake256_squeeze_end(uint32_t *out, size_t outlen) { ++idx; } - if (launder32(offset) == kShake256KeccakRateWords) { + if (launder32(offset) == rate_words) { // If we read all the remaining words, issue `CMD.RUN` to generate more // state. - HARDENED_CHECK_EQ(offset, kShake256KeccakRateWords); + HARDENED_CHECK_EQ(offset, rate_words); issue_command(KMAC_CMD_CMD_VALUE_RUN); } } - HARDENED_CHECK_EQ(idx, outlen); + HARDENED_CHECK_EQ(idx, out_words); + return kErrorOk; +} +rom_error_t kmac_done(void) { // Poll the status register until in the 'squeeze' state. HARDENED_RETURN_IF_ERROR(poll_state(KMAC_STATUS_SHA3_SQUEEZE_BIT)); @@ -390,6 +418,13 @@ rom_error_t kmac_shake256_squeeze_end(uint32_t *out, size_t outlen) { return kErrorOk; } +bool kmac_is_squeezing(void) { + uint32_t status = abs_mmio_read32(kBase + KMAC_STATUS_REG_OFFSET); + bool sha3_idle = bitfield_bit32_read(status, KMAC_STATUS_SHA3_IDLE_BIT); + bool sha3_absorb = bitfield_bit32_read(status, KMAC_STATUS_SHA3_ABSORB_BIT); + return !sha3_idle && !sha3_absorb; +} + #define WORD_BITS (sizeof(uint32_t) * 8) #define KEY_CASE(x_) \ case x_ / WORD_BITS: \ diff --git a/sw/device/silicon_creator/lib/drivers/kmac.h b/sw/device/silicon_creator/lib/drivers/kmac.h index 1209b3b905c2d..d837136e32534 100644 --- a/sw/device/silicon_creator/lib/drivers/kmac.h +++ b/sw/device/silicon_creator/lib/drivers/kmac.h @@ -44,6 +44,16 @@ rom_error_t kmac_kmac256_sw_configure(void); */ rom_error_t kmac_kmac256_hw_configure(void); +/** + * Configure the KMAC block at startup for SHAKE-128 operation. + * + * Sets the KMAC block to use software entropy and sets the mode to SHAKE-128. + * + * @return Error code indicating if the operation succeeded. + */ +OT_WARN_UNUSED_RESULT +rom_error_t kmac_shake128_configure(void); + /** * Configure the KMAC block at startup. * @@ -145,6 +155,37 @@ void kmac_shake256_squeeze_start(void); OT_WARN_UNUSED_RESULT rom_error_t kmac_shake256_squeeze_end(uint32_t *out, size_t outlen); +/** + * Squeeze arbitrary number of words from the Keccak state. + * + * This function will read out_words from the Keccak state, and if necessary + * triggers additional hardware permutation runs using the rate_words. + * Unlike squeeze_end, it does not mark the operation as DONE. + * + * @param out Output buffer. + * @param out_words Desired length of output in 32-bit words. + * @param rate_words Keccak rate in 32-bit words. + * @return Error code indicating if the operation succeeded. + */ +OT_WARN_UNUSED_RESULT +rom_error_t kmac_squeeze_words(uint32_t *out, size_t out_words, + size_t rate_words); + +/** + * End the squeeze phase and release the KMAC hardware block. + * + * @return Error code indicating if the operation succeeded. + */ +OT_WARN_UNUSED_RESULT +rom_error_t kmac_done(void); + +/** + * Check if the KMAC block is in the squeezing phase. + * + * @return True if KMAC is squeezing, false otherwise. + */ +bool kmac_is_squeezing(void); + /** * Load an unmasked software key into KMAC. * diff --git a/sw/device/tests/embedpqc/BUILD b/sw/device/tests/embedpqc/BUILD new file mode 100644 index 0000000000000..59ebfc93a6887 --- /dev/null +++ b/sw/device/tests/embedpqc/BUILD @@ -0,0 +1,46 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load( + "//rules/opentitan:defs.bzl", + "EARLGREY_TEST_ENVS", + "opentitan_test", +) + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "mldsa_test_utils", + srcs = [ + "mldsa_test_utils.c", + ], + hdrs = [ + "mldsa_test_utils.h", + ], + deps = [ + "//sw/device/lib/runtime:log", + ], +) + +cc_library( + name = "mldsa_testvectors", + srcs = ["mldsa_testvectors.c"], + hdrs = ["mldsa_testvectors.h"], + deps = [ + "//third_party/embedpqc:mldsa44_tiny", + ], +) + +opentitan_test( + name = "mldsa44_tiny_test", + srcs = ["mldsa44_tiny_test.c"], + exec_env = EARLGREY_TEST_ENVS, + deps = [ + ":mldsa_test_utils", + ":mldsa_testvectors", + "//sw/device/lib/runtime:log", + "//sw/device/lib/testing/test_framework:ottf_main", + "//third_party/embedpqc/ports:mldsa44_tiny_caller", + ], +) diff --git a/sw/device/tests/embedpqc/mldsa44_tiny_test.c b/sw/device/tests/embedpqc/mldsa44_tiny_test.c new file mode 100644 index 0000000000000..302384224ad7a --- /dev/null +++ b/sw/device/tests/embedpqc/mldsa44_tiny_test.c @@ -0,0 +1,66 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "third_party/embedpqc/mldsa44_tiny.h" + +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" +#include "sw/device/tests/embedpqc/mldsa_test_utils.h" +#include "sw/device/tests/embedpqc/mldsa_testvectors.h" +#include "third_party/embedpqc/ports/mldsa44_tiny_caller.h" + +OTTF_DEFINE_TEST_CONFIG(); + +bool test_main(void) { + LOG_INFO("MLDSA44-TINY Test starting..."); + + // Allocate a single static buffer to reuse for all RAM outputs to save BSS + // space. + static uint8_t buf[MLDSA44_SIGNATURE_BYTES]; + + // 1. Keygen Test + paint_stack(); + mldsa44_tiny_pub_from_seed_with_stack(buf, kMldsa44KeygenSeed, + &mldsa_stack[MLDSA_STACK_SIZE]); + size_t keygen_stack = get_max_stack_usage(); + LOG_INFO("mldsa44_tiny_pub_from_seed Max Stack Usage: %u bytes", + (unsigned int)keygen_stack); + + CHECK(check_arrays_eq_verbose(buf, kMldsa44ExpectedPublicKey, + MLDSA44_PUBLIC_KEY_BYTES), + "PublicKey mismatch!"); + LOG_INFO("Keygen Test Passed."); + + // 2. Sign Test + paint_stack(); + mldsa44_tiny_sign_deterministic_with_stack( + buf, kMldsa44SignSeed, kMldsa44Message, sizeof(kMldsa44Message), + &mldsa_stack[MLDSA_STACK_SIZE]); + size_t sign_stack = get_max_stack_usage(); + LOG_INFO("mldsa44_tiny_sign Max Stack Usage: %u bytes", + (unsigned int)sign_stack); + + CHECK(check_arrays_eq_verbose(buf, kMldsa44ExpectedSignature, + MLDSA44_SIGNATURE_BYTES), + "Signature mismatch!"); + LOG_INFO("Sign Test Passed."); + + // 3. Verify Test + mldsa44_tiny_pub_from_seed_with_stack(buf, kMldsa44SignSeed, + &mldsa_stack[MLDSA_STACK_SIZE]); + paint_stack(); + int verify_res = mldsa44_tiny_verify_with_stack( + buf, kMldsa44ExpectedSignature, kMldsa44Message, sizeof(kMldsa44Message), + &mldsa_stack[MLDSA_STACK_SIZE]); + size_t verify_stack = get_max_stack_usage(); + LOG_INFO("mldsa44_tiny_verify Max Stack Usage: %u bytes", + (unsigned int)verify_stack); + + CHECK(verify_res != 0, "mldsa44_tiny_verify failed!"); + LOG_INFO("Verify Test Passed."); + + LOG_INFO("MLDSA44-TINY Test completed successfully!"); + return true; +} diff --git a/sw/device/tests/embedpqc/mldsa_test_utils.c b/sw/device/tests/embedpqc/mldsa_test_utils.c new file mode 100644 index 0000000000000..03659bc491c7f --- /dev/null +++ b/sw/device/tests/embedpqc/mldsa_test_utils.c @@ -0,0 +1,38 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/tests/embedpqc/mldsa_test_utils.h" + +#include "sw/device/lib/runtime/log.h" + +__attribute__((aligned(16))) uint8_t mldsa_stack[MLDSA_STACK_SIZE]; + +void paint_stack(void) { + for (size_t i = 0; i < MLDSA_STACK_SIZE; i++) { + mldsa_stack[i] = 0xA5; + } +} + +size_t get_max_stack_usage(void) { + for (size_t i = 0; i < MLDSA_STACK_SIZE; i++) { + if (mldsa_stack[i] != 0xA5) { + return MLDSA_STACK_SIZE - i; + } + } + return 0; +} + +bool check_arrays_eq_verbose(const uint8_t *got, const uint8_t *expected, + size_t len) { + bool match = true; + for (size_t i = 0; i < len; ++i) { + if (got[i] != expected[i]) { + LOG_INFO("Mismatch at index %u: got 0x%02x, expected 0x%02x", + (unsigned int)i, got[i], expected[i]); + match = false; + break; + } + } + return match; +} diff --git a/sw/device/tests/embedpqc/mldsa_test_utils.h b/sw/device/tests/embedpqc/mldsa_test_utils.h new file mode 100644 index 0000000000000..5a54fe0f9b709 --- /dev/null +++ b/sw/device/tests/embedpqc/mldsa_test_utils.h @@ -0,0 +1,41 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_TESTS_EMBEDPQC_MLDSA_TEST_UTILS_H_ +#define OPENTITAN_SW_DEVICE_TESTS_EMBEDPQC_MLDSA_TEST_UTILS_H_ + +#include +#include +#include + +#define MLDSA_STACK_SIZE (64 * 1024) +extern uint8_t mldsa_stack[MLDSA_STACK_SIZE]; + +/** + * Fills the custom stack buffer with a predefined pattern (0xA5) to allow + * stack high-watermark usage measurement. + */ +void paint_stack(void); + +/** + * Analyzes the custom stack buffer to find the deepest modified address + * (non-0xA5) and returns the maximum stack usage in bytes. + * + * @return Deepest stack usage in bytes. + */ +size_t get_max_stack_usage(void); + +/** + * Verbose array comparison helper that compares two byte buffers and logs + * the index, got value, and expected value of the first mismatch encountered. + * + * @param got The byte buffer obtained. + * @param expected The expected byte buffer. + * @param len Length of the buffers to compare in bytes. + * @return True if the buffers match exactly, False otherwise. + */ +bool check_arrays_eq_verbose(const uint8_t *got, const uint8_t *expected, + size_t len); + +#endif // OPENTITAN_SW_DEVICE_TESTS_EMBEDPQC_MLDSA_TEST_UTILS_H_ diff --git a/sw/device/tests/embedpqc/mldsa_testvectors.c b/sw/device/tests/embedpqc/mldsa_testvectors.c new file mode 100644 index 0000000000000..088a89c3230b9 --- /dev/null +++ b/sw/device/tests/embedpqc/mldsa_testvectors.c @@ -0,0 +1,332 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/tests/embedpqc/mldsa_testvectors.h" + +// ML-DSA-44 vectors +const uint8_t kMldsa44KeygenSeed[MLDSA44_PRIVATE_SEED_BYTES] = { + 0xbb, 0xba, 0xf6, 0xee, 0x15, 0x3d, 0x9d, 0xe8, 0x3c, 0x99, 0x33, + 0xf8, 0x94, 0x87, 0xb0, 0x8f, 0x4c, 0x70, 0xcb, 0xea, 0xa4, 0xeb, + 0x24, 0xce, 0x70, 0x10, 0x25, 0x8e, 0x49, 0xe2, 0x62, 0xf9}; +const uint8_t kMldsa44ExpectedPublicKey[MLDSA44_PUBLIC_KEY_BYTES] = { + 0xbe, 0x85, 0xe8, 0x26, 0x7c, 0x21, 0x60, 0x3f, 0xb6, 0x3d, 0x2f, 0x20, + 0x01, 0x0c, 0xfa, 0x5d, 0xab, 0xa9, 0x5a, 0xb0, 0x9d, 0x94, 0xf6, 0x5d, + 0x97, 0x1f, 0x68, 0xa2, 0x4c, 0xba, 0xc3, 0xcc, 0xdd, 0x60, 0x15, 0x9e, + 0xbb, 0x5a, 0x8e, 0xf3, 0x4e, 0x12, 0xa9, 0x41, 0x16, 0x3c, 0xad, 0x47, + 0x40, 0xd6, 0x21, 0x5f, 0xf9, 0x6d, 0xc5, 0xb7, 0x19, 0x17, 0x3e, 0xcb, + 0xcf, 0x67, 0x28, 0x5f, 0xea, 0xa6, 0xee, 0x73, 0x40, 0x40, 0x02, 0xac, + 0x27, 0x64, 0x62, 0x98, 0xfe, 0x55, 0xc2, 0x88, 0x99, 0x22, 0x23, 0x3b, + 0x51, 0xf5, 0x33, 0x14, 0x4c, 0x8d, 0xd5, 0xbc, 0x7e, 0x0f, 0xcd, 0x4d, + 0x03, 0x6e, 0x81, 0x8f, 0xc4, 0xb7, 0x6e, 0x43, 0xf2, 0xd0, 0x4f, 0x4a, + 0xa8, 0x8e, 0xcf, 0xa0, 0x91, 0x91, 0x2b, 0xe3, 0x32, 0xeb, 0x55, 0x72, + 0xd9, 0xd4, 0xb2, 0x86, 0xd6, 0x7a, 0x46, 0x98, 0x09, 0xdd, 0x49, 0x35, + 0x84, 0xba, 0xfd, 0x62, 0x96, 0x3a, 0xe1, 0x3b, 0xca, 0x61, 0x63, 0x95, + 0x66, 0x62, 0x47, 0x53, 0x25, 0x34, 0x44, 0xed, 0xed, 0xcc, 0xdc, 0xca, + 0x10, 0x6f, 0x5b, 0x20, 0x77, 0x0a, 0x81, 0xf9, 0x84, 0xfd, 0x87, 0x9b, + 0x63, 0x6c, 0x2e, 0x49, 0xdb, 0xed, 0x52, 0xf5, 0x13, 0x54, 0x9a, 0xab, + 0x44, 0xea, 0xa6, 0x10, 0x1e, 0xf5, 0x58, 0x6d, 0x01, 0x49, 0x64, 0xba, + 0x55, 0xc9, 0xac, 0xe8, 0xee, 0xb6, 0xd2, 0x1e, 0xb3, 0x68, 0x2d, 0xfd, + 0xe3, 0x06, 0xb0, 0x6e, 0x11, 0xb7, 0xca, 0x3d, 0xe6, 0x9a, 0xc3, 0x8e, + 0x9f, 0xbe, 0x63, 0x92, 0xf6, 0x12, 0x09, 0xbc, 0xb2, 0x62, 0xf8, 0x44, + 0x11, 0x42, 0xac, 0x59, 0x49, 0x84, 0x5b, 0xe2, 0x61, 0x20, 0x76, 0xd9, + 0x3b, 0x25, 0x38, 0xa4, 0x54, 0x5a, 0xf3, 0xf8, 0xb0, 0x6b, 0xe2, 0x17, + 0xdc, 0xcb, 0x00, 0x7d, 0x9d, 0x38, 0x5e, 0xd8, 0x1f, 0x99, 0x4d, 0xfc, + 0x96, 0x43, 0x01, 0xc4, 0x81, 0x91, 0x35, 0x7c, 0x67, 0xb6, 0x5b, 0xc1, + 0x41, 0xfb, 0x85, 0xe4, 0x00, 0x1c, 0x04, 0x70, 0x45, 0x1a, 0xb6, 0x4a, + 0xde, 0x75, 0xfb, 0x95, 0x9c, 0xeb, 0x64, 0xf2, 0xdb, 0x0e, 0x7d, 0x5e, + 0x91, 0x41, 0xe4, 0x7d, 0xb8, 0xb4, 0x20, 0xa9, 0x6c, 0x28, 0xe2, 0x75, + 0xb8, 0x32, 0x7d, 0xb6, 0x89, 0xd7, 0xd8, 0xfc, 0x77, 0x44, 0x07, 0xe5, + 0xef, 0x4a, 0x7b, 0x23, 0xd1, 0x31, 0x50, 0x6d, 0x2d, 0xc7, 0x75, 0xdc, + 0xce, 0xd1, 0xa3, 0x64, 0x7d, 0x3a, 0x74, 0xb8, 0x58, 0xf3, 0xff, 0x18, + 0x50, 0x62, 0xd6, 0xfd, 0x11, 0x50, 0x76, 0xac, 0x61, 0x3f, 0xe5, 0x46, + 0xf8, 0x69, 0x4f, 0xc0, 0x63, 0x5f, 0xe8, 0x45, 0x0b, 0xd2, 0x34, 0x67, + 0xc2, 0x80, 0x0e, 0x1d, 0xd0, 0xb1, 0x96, 0x79, 0x1d, 0x30, 0x10, 0x9a, + 0xb3, 0x25, 0x64, 0x0f, 0x7d, 0xa8, 0x6a, 0x9e, 0x32, 0x89, 0x8b, 0x3c, + 0xc3, 0x1d, 0x01, 0x7a, 0xd5, 0xcf, 0x86, 0x36, 0xb0, 0x02, 0x03, 0xaf, + 0x11, 0x6a, 0x23, 0xc1, 0x9e, 0x12, 0xa2, 0x2b, 0xad, 0xbe, 0x4b, 0x6e, + 0xa3, 0x7e, 0x5c, 0x44, 0x03, 0xcb, 0xee, 0x1d, 0x93, 0xdc, 0x5c, 0x47, + 0x93, 0xca, 0xe9, 0x0b, 0x76, 0xed, 0xfe, 0xf9, 0x8c, 0x3a, 0xc5, 0x59, + 0xae, 0x14, 0x6d, 0xc2, 0xa9, 0x55, 0x2e, 0x80, 0x2a, 0xca, 0x14, 0x22, + 0x75, 0x36, 0x87, 0xd4, 0x2f, 0x1e, 0xc6, 0x0f, 0x4c, 0xa5, 0x83, 0x5d, + 0xb4, 0x34, 0x99, 0x25, 0x06, 0x38, 0x8e, 0x74, 0x4e, 0xc9, 0x50, 0xf3, + 0x2a, 0x97, 0x34, 0xdd, 0x0c, 0x92, 0x5f, 0x53, 0xc2, 0x11, 0x48, 0xa5, + 0x09, 0x81, 0x93, 0xf5, 0x27, 0xf8, 0xf9, 0xab, 0x6d, 0x56, 0xde, 0x34, + 0x57, 0x18, 0x82, 0xe1, 0x47, 0x60, 0x94, 0x99, 0x67, 0x30, 0x07, 0x11, + 0x34, 0x40, 0x56, 0xc7, 0xea, 0xae, 0x6a, 0x69, 0x69, 0x6b, 0x2e, 0xda, + 0x70, 0xcd, 0xff, 0x6c, 0x0a, 0x9c, 0xcd, 0x94, 0x1a, 0xec, 0x7d, 0xf4, + 0x38, 0x05, 0xa5, 0x45, 0x57, 0xee, 0x76, 0x1a, 0x2a, 0x55, 0x27, 0xf4, + 0xf7, 0x99, 0x43, 0x76, 0x18, 0xca, 0x5e, 0x3d, 0x6d, 0x5c, 0x09, 0x40, + 0x50, 0xea, 0x99, 0xf0, 0xcb, 0x38, 0xdb, 0xda, 0x3f, 0x1b, 0x43, 0xd8, + 0xa3, 0xb7, 0x6f, 0x14, 0x30, 0x10, 0xb5, 0x6d, 0x22, 0x37, 0xc5, 0x94, + 0x23, 0x98, 0xca, 0xf5, 0xc4, 0x98, 0xcd, 0x33, 0xe5, 0x88, 0x7c, 0x51, + 0x9a, 0x76, 0xd2, 0x3b, 0x71, 0x04, 0xb8, 0x76, 0x60, 0xcf, 0xf2, 0xf1, + 0xe0, 0x2e, 0x8c, 0x93, 0x58, 0xe7, 0xd5, 0x02, 0x97, 0xeb, 0xd3, 0xa1, + 0xd6, 0xc8, 0x80, 0xaf, 0x16, 0xdc, 0x13, 0x65, 0x16, 0x86, 0x2a, 0x05, + 0x81, 0xf6, 0x32, 0xa0, 0xa4, 0x2e, 0xa4, 0x42, 0x36, 0xa6, 0xd8, 0x25, + 0xe3, 0xc7, 0x0d, 0x78, 0x97, 0xac, 0x4c, 0xed, 0x41, 0x61, 0x06, 0x2e, + 0xba, 0xb7, 0x6c, 0xc7, 0x9c, 0x46, 0x71, 0x3b, 0x2a, 0x2c, 0x29, 0xe9, + 0xe8, 0x2e, 0x4e, 0x75, 0x57, 0xb2, 0x74, 0xfb, 0x73, 0x91, 0x0b, 0xe2, + 0xfc, 0x45, 0x40, 0xaf, 0x53, 0x3d, 0x06, 0x8a, 0xaf, 0x26, 0x92, 0x7f, + 0xe9, 0x75, 0xb9, 0xb7, 0x5f, 0x16, 0xfa, 0x8b, 0x13, 0x6d, 0x4a, 0x65, + 0xb8, 0x40, 0x06, 0xd4, 0x0e, 0xa2, 0x10, 0x6a, 0xbc, 0x5d, 0xe3, 0x33, + 0x89, 0xd7, 0xba, 0x10, 0xe9, 0xcb, 0x6a, 0xc0, 0x28, 0x30, 0x53, 0xae, + 0x83, 0xa6, 0xb4, 0xf8, 0xa5, 0x2c, 0x9f, 0x51, 0x4c, 0xaa, 0x36, 0x4c, + 0xa6, 0xf3, 0xb4, 0x5f, 0x47, 0xdf, 0x8d, 0x92, 0x6b, 0x49, 0xe7, 0x7b, + 0xde, 0x00, 0xd0, 0xbd, 0x4e, 0x0b, 0x1c, 0x16, 0x6a, 0x57, 0xf3, 0x91, + 0xb9, 0x03, 0x38, 0x0a, 0xc3, 0xae, 0xb6, 0xdd, 0xa9, 0xf2, 0x40, 0x45, + 0xa9, 0x47, 0x62, 0x31, 0xf5, 0x34, 0x47, 0x09, 0xdc, 0x24, 0xba, 0xcf, + 0xde, 0x34, 0xab, 0x2f, 0xcc, 0x46, 0x20, 0x95, 0x5a, 0x95, 0xdd, 0xc0, + 0x1c, 0xe1, 0x98, 0x71, 0xf1, 0x87, 0xf3, 0x6a, 0xd9, 0x5f, 0x20, 0x41, + 0xe4, 0xb4, 0x9b, 0x98, 0xc5, 0x6c, 0x02, 0x6f, 0x45, 0x9a, 0x4c, 0xb0, + 0x91, 0x4f, 0x67, 0xb7, 0xa7, 0x30, 0xf0, 0xa7, 0xe1, 0xbe, 0x93, 0x8b, + 0xc9, 0x35, 0xd9, 0x91, 0x55, 0xe0, 0x82, 0xf6, 0xce, 0x39, 0xe1, 0xdc, + 0x06, 0x63, 0x0e, 0x8a, 0xa9, 0x79, 0xed, 0x54, 0x0b, 0x0d, 0x71, 0x61, + 0x16, 0x63, 0x07, 0x7e, 0x21, 0xa4, 0x48, 0x4d, 0x89, 0xac, 0x62, 0xbb, + 0xb8, 0xd7, 0x8d, 0xbc, 0x09, 0x19, 0x9d, 0x0d, 0x57, 0x04, 0xc2, 0x86, + 0xea, 0x75, 0x96, 0xa0, 0x14, 0x18, 0x1d, 0x98, 0x89, 0x85, 0xaf, 0x38, + 0x42, 0x4d, 0x28, 0xac, 0xe8, 0x76, 0xa0, 0xab, 0x82, 0xde, 0xe8, 0x5e, + 0xcc, 0x1a, 0x29, 0xab, 0x3d, 0xd7, 0x46, 0xa4, 0x02, 0x8d, 0xc6, 0x3d, + 0xcd, 0x49, 0xb0, 0x01, 0x25, 0x83, 0xab, 0x4e, 0x4a, 0x6e, 0x96, 0x40, + 0xcb, 0x13, 0x70, 0x2e, 0xdf, 0xe6, 0xaf, 0xdc, 0x6e, 0x35, 0xb6, 0xf0, + 0x10, 0x06, 0xb1, 0x3b, 0x18, 0xf6, 0x4b, 0x34, 0x7b, 0x32, 0xf2, 0xb9, + 0x31, 0xd9, 0xe8, 0x47, 0xaf, 0x85, 0x10, 0xbf, 0x81, 0x83, 0xfd, 0x99, + 0xa3, 0xa2, 0x6c, 0xd2, 0x45, 0x98, 0x3a, 0xeb, 0x57, 0xd5, 0xa0, 0x29, + 0x26, 0xc6, 0xa7, 0xf5, 0x55, 0x66, 0xe5, 0x6e, 0x2a, 0x5c, 0x05, 0x0e, + 0x95, 0x7c, 0x9a, 0x05, 0x49, 0xdd, 0xc7, 0xbe, 0x29, 0xe7, 0xa7, 0xdb, + 0xf6, 0x0e, 0x75, 0xe5, 0x6e, 0x08, 0xe8, 0xcb, 0x64, 0xb3, 0x3c, 0xd2, + 0xbc, 0x53, 0xd4, 0x07, 0x58, 0xb9, 0x82, 0x6e, 0xbc, 0xee, 0xe8, 0x13, + 0x52, 0xc3, 0xf8, 0xf6, 0x40, 0x17, 0xc3, 0x70, 0x0c, 0x92, 0x9c, 0xfe, + 0x44, 0xb7, 0xa9, 0x1b, 0x4d, 0x70, 0x25, 0xef, 0x37, 0x4b, 0xdd, 0x48, + 0x65, 0xab, 0x26, 0x16, 0xd4, 0xae, 0x0c, 0xdf, 0xb8, 0xae, 0x49, 0x56, + 0xe3, 0x28, 0x79, 0x23, 0x3b, 0xe2, 0x0f, 0xef, 0xa2, 0x38, 0x0d, 0x15, + 0x24, 0x86, 0xcf, 0x42, 0xcb, 0xc0, 0xe8, 0xd0, 0x43, 0xcb, 0x96, 0x6a, + 0x40, 0x06, 0xdc, 0x10, 0xa3, 0xb3, 0x92, 0x1d, 0xd3, 0x65, 0xc8, 0xc5, + 0x1b, 0x99, 0x17, 0x23, 0xff, 0xcf, 0x27, 0x06, 0x3f, 0x16, 0xd2, 0x0c, + 0x95, 0x94, 0x44, 0xc4, 0x4a, 0x8d, 0x3d, 0x02, 0x85, 0x7f, 0xd1, 0xf0, + 0x47, 0xd9, 0xb5, 0x9b, 0xdc, 0xac, 0x97, 0x1d, 0x2a, 0x73, 0x3d, 0xdb, + 0x66, 0xca, 0xbf, 0x17, 0xeb, 0x06, 0x0a, 0x67, 0xab, 0xd1, 0x29, 0xb7, + 0x50, 0x73, 0x77, 0x89, 0xff, 0x73, 0xbb, 0x79, 0xb2, 0x85, 0x79, 0xbf, + 0x29, 0x68, 0xa8, 0x17, 0x0d, 0x68, 0xa8, 0xa6, 0x8e, 0x31, 0x79, 0x34, + 0x6e, 0xa2, 0x1f, 0x67, 0x70, 0x32, 0x15, 0xc7, 0xeb, 0x8f, 0xb0, 0x79, + 0xb5, 0x74, 0x66, 0x92, 0xee, 0xf4, 0xa9, 0x08, 0x98, 0x91, 0x73, 0xf2, + 0x66, 0xe5, 0x0d, 0xb3, 0x50, 0xdb, 0x36, 0xc8, 0x15, 0x7d, 0x01, 0x87, + 0x5d, 0xfc, 0x6b, 0x3b, 0x69, 0xc4, 0x0f, 0xea, 0xc7, 0xe7, 0xe9, 0x07, + 0x14, 0x1f, 0x3a, 0xae, 0x85, 0x02, 0x84, 0x8b, 0x04, 0x15, 0x79, 0x6d, + 0x47, 0x56, 0x3b, 0xbb, 0xab, 0x30, 0x62, 0xd0, 0x9a, 0x33, 0x9e, 0xb0, + 0x41, 0x85, 0x87, 0xb3, 0x7a, 0x68, 0x94, 0x20, 0xe4, 0xae, 0xd0, 0x6f, + 0x00, 0xe8, 0x27, 0x0c, 0xbe, 0x61, 0x76, 0xf0, 0xbc, 0xe9, 0x17, 0x00, + 0x1e, 0x1e, 0x61, 0xfc, 0x36, 0x37, 0xbb, 0xce, 0x50, 0xec, 0xcf, 0x7b, + 0xee, 0x8f, 0x19, 0xbe, 0xa5, 0x7f, 0xa8, 0x21, 0xf2, 0x4c, 0x3c, 0x4d, + 0x9c, 0xa1, 0xd0, 0xc1, 0x73, 0xc4, 0xc4, 0x82, 0xa2, 0x61, 0xe8, 0x6f, + 0x8d, 0xe3, 0x8f, 0x31}; +const uint8_t kMldsa44SignSeed[MLDSA44_PRIVATE_SEED_BYTES] = { + 0xa3, 0x70, 0x48, 0x52, 0xec, 0xd0, 0x62, 0xf5, 0xb6, 0x69, 0x8d, + 0xf5, 0xfb, 0x48, 0x53, 0x23, 0x6f, 0x00, 0x78, 0x1a, 0x38, 0x01, + 0x7b, 0x21, 0xda, 0x0c, 0x63, 0xc0, 0x72, 0x40, 0xb9, 0x22}; +const uint8_t kMldsa44Message[16] = {0x96, 0x7a, 0x6e, 0x94, 0x42, 0xea, + 0xb9, 0x70, 0x61, 0x01, 0x66, 0x6a, + 0xd8, 0x5d, 0x8c, 0x25}; +const uint8_t kMldsa44ExpectedSignature[MLDSA44_SIGNATURE_BYTES] = { + 0x5e, 0xf1, 0xaa, 0x7b, 0xe0, 0xe5, 0x04, 0x39, 0x4c, 0x5d, 0x97, 0x4c, + 0x8d, 0x41, 0xec, 0xae, 0xe6, 0x64, 0xae, 0x2d, 0x39, 0x52, 0xe6, 0xb6, + 0xdf, 0x8d, 0x4f, 0xa9, 0xa7, 0x44, 0x24, 0xd2, 0x3b, 0xc0, 0xfe, 0xf0, + 0xa9, 0xa4, 0x06, 0x44, 0xa1, 0x81, 0x17, 0xb6, 0x23, 0x2a, 0x70, 0x1b, + 0x22, 0x57, 0x1a, 0x24, 0x23, 0x11, 0xc2, 0x3d, 0xb3, 0xc8, 0xdf, 0x79, + 0xe2, 0x3d, 0xf3, 0xeb, 0x10, 0xf4, 0x2f, 0x7a, 0xab, 0x93, 0xc2, 0x10, + 0x57, 0xad, 0x5c, 0x17, 0x46, 0xe6, 0x4d, 0x4f, 0x6c, 0x23, 0x25, 0xf6, + 0xaa, 0x94, 0xa2, 0xcb, 0x57, 0xd9, 0xe2, 0x65, 0x8d, 0xf7, 0x71, 0x7e, + 0x80, 0x94, 0x7f, 0xb6, 0xae, 0xa1, 0xa7, 0xa8, 0xad, 0xb7, 0x97, 0x3d, + 0x4d, 0x60, 0x11, 0x51, 0x0e, 0x06, 0x3f, 0xf2, 0x0d, 0x9c, 0x6c, 0x94, + 0x77, 0x1f, 0xac, 0xb4, 0xf6, 0x7d, 0x1d, 0x05, 0x4a, 0xf6, 0xfa, 0x8a, + 0x2d, 0x61, 0xaf, 0x80, 0x75, 0xe8, 0x38, 0xae, 0x60, 0xd8, 0x2e, 0xb9, + 0x35, 0xb4, 0xfb, 0xed, 0xf1, 0x26, 0x33, 0x21, 0x04, 0xb0, 0xfc, 0x25, + 0xda, 0xa5, 0x68, 0x0c, 0x1e, 0x29, 0xb5, 0x8b, 0x9c, 0x10, 0x9c, 0x68, + 0xd2, 0xc1, 0xc1, 0x0e, 0x63, 0xfc, 0x19, 0xc5, 0xe8, 0x8e, 0xec, 0x6a, + 0xf4, 0x1c, 0x53, 0x97, 0xba, 0xd1, 0xa0, 0x81, 0xde, 0x94, 0x33, 0x10, + 0x37, 0xcb, 0x0e, 0xf4, 0xe4, 0x41, 0x6a, 0x29, 0x31, 0x1e, 0x58, 0xb6, + 0x26, 0x41, 0x16, 0x03, 0x5f, 0x78, 0x60, 0xba, 0x40, 0xa7, 0x49, 0xfb, + 0x86, 0xd3, 0x40, 0x9c, 0x4b, 0xe8, 0x22, 0x15, 0x2a, 0xab, 0x49, 0x4e, + 0x67, 0x06, 0x79, 0xe9, 0xd9, 0xef, 0x23, 0x04, 0x0e, 0x56, 0xf6, 0xb5, + 0xb3, 0x61, 0x3a, 0xd8, 0x19, 0x19, 0x28, 0xc7, 0xef, 0xf4, 0xb6, 0xef, + 0xdc, 0xbc, 0x9f, 0xa4, 0x16, 0xe5, 0x1a, 0xcc, 0x6c, 0xcd, 0xa7, 0x6d, + 0x86, 0xb4, 0x36, 0xa9, 0x6c, 0xc0, 0xf2, 0x8d, 0xfc, 0xed, 0x2f, 0x0e, + 0x55, 0x3d, 0x9e, 0xbb, 0x07, 0x98, 0xc0, 0x65, 0x44, 0x27, 0x9e, 0xb6, + 0x69, 0xb8, 0x92, 0x23, 0xb9, 0x71, 0x13, 0x17, 0x0d, 0x98, 0x89, 0x85, + 0x97, 0x53, 0x0a, 0xbe, 0xda, 0x93, 0xf5, 0x54, 0x28, 0xdb, 0xa7, 0x29, + 0x0d, 0x15, 0xa0, 0x0a, 0x29, 0x1f, 0xdd, 0xd9, 0xea, 0xfc, 0x27, 0x18, + 0x89, 0xa6, 0xa1, 0xf9, 0x56, 0xfc, 0xf1, 0x43, 0xb5, 0x38, 0x43, 0x66, + 0x5e, 0xc5, 0x2b, 0xe6, 0xb8, 0x9b, 0x1e, 0x03, 0xcc, 0x4e, 0x57, 0x7b, + 0xba, 0xf2, 0xd7, 0xda, 0x98, 0xc5, 0x40, 0x80, 0xf8, 0x2e, 0xc3, 0xf0, + 0x41, 0xd2, 0x78, 0xcb, 0x22, 0x8d, 0xd4, 0xe3, 0x93, 0x03, 0xbd, 0xc9, + 0xbb, 0xf0, 0x1b, 0x47, 0x6f, 0x4f, 0xe0, 0xf3, 0xb2, 0x33, 0xdc, 0xe5, + 0x86, 0x22, 0x91, 0x52, 0x24, 0x27, 0xe9, 0xdd, 0x54, 0x3e, 0x5b, 0x27, + 0x6a, 0xd3, 0xa7, 0xbb, 0xbe, 0xb4, 0xbf, 0xe5, 0x00, 0xbe, 0x0e, 0x02, + 0xaf, 0xe5, 0xff, 0x7c, 0xc1, 0x9e, 0x79, 0xcf, 0x16, 0x67, 0x5d, 0x4b, + 0x3e, 0xc1, 0x82, 0xba, 0x54, 0x5c, 0x98, 0x4a, 0xc8, 0x7f, 0x88, 0xc6, + 0x3b, 0x0d, 0x45, 0x92, 0xbd, 0xed, 0xe5, 0xbb, 0xb3, 0x53, 0x93, 0xed, + 0x6f, 0x5d, 0x47, 0xb2, 0x1e, 0x4d, 0x09, 0xba, 0xf1, 0x4a, 0xb3, 0x53, + 0x6d, 0xaf, 0xa4, 0xb9, 0xe4, 0xdc, 0xbc, 0x9c, 0xf8, 0xb5, 0x94, 0xc4, + 0xb4, 0x21, 0xe1, 0x53, 0x3b, 0x7f, 0x0f, 0x70, 0x28, 0x99, 0xe6, 0x78, + 0x3b, 0x49, 0x6a, 0x53, 0x6c, 0x4a, 0x73, 0xa8, 0xa7, 0xb2, 0xaf, 0x46, + 0x39, 0x5a, 0x63, 0xf7, 0x7c, 0x33, 0x39, 0xd8, 0x84, 0x4c, 0x08, 0xad, + 0x77, 0x1e, 0x31, 0xb4, 0xa3, 0xea, 0xd1, 0x05, 0x2c, 0x88, 0x85, 0x46, + 0x61, 0x31, 0xaa, 0xbc, 0xb1, 0x9c, 0x52, 0x75, 0x12, 0xe2, 0x8d, 0xce, + 0xd5, 0x72, 0x68, 0x5a, 0x65, 0x6e, 0x4c, 0xb3, 0x82, 0x0f, 0xe7, 0x42, + 0xec, 0x74, 0x04, 0xe0, 0x7d, 0xba, 0xa1, 0xa1, 0xee, 0xb6, 0x43, 0x79, + 0xb5, 0xac, 0xb5, 0xb0, 0x6f, 0x46, 0xdb, 0x3e, 0x45, 0x85, 0xcd, 0x7d, + 0x71, 0x80, 0x1d, 0x4c, 0x3b, 0xde, 0xd5, 0xee, 0xbc, 0xb2, 0xcd, 0x76, + 0xb1, 0x55, 0xed, 0x7e, 0x91, 0xca, 0xbb, 0x2e, 0xbd, 0x71, 0xe1, 0xc2, + 0x1a, 0xdd, 0xcd, 0xe4, 0xc7, 0xb5, 0x06, 0x80, 0x1f, 0x24, 0x2e, 0x53, + 0x13, 0xca, 0x1a, 0xfc, 0x43, 0x0e, 0x22, 0xde, 0xcb, 0xd2, 0xd6, 0x7e, + 0x2f, 0x81, 0xab, 0xac, 0xf4, 0x40, 0x3e, 0x12, 0xb0, 0x29, 0x18, 0x49, + 0x0e, 0x5b, 0x0e, 0x37, 0xc1, 0xe0, 0x62, 0x62, 0x01, 0x30, 0x1a, 0x8f, + 0xa3, 0xf7, 0x23, 0x9a, 0x83, 0x75, 0xcb, 0x92, 0x99, 0xd3, 0xbf, 0xbd, + 0xd4, 0xe2, 0x5c, 0x58, 0xe1, 0xe1, 0xeb, 0x8b, 0x4a, 0x89, 0xa8, 0xea, + 0x52, 0x6e, 0xe9, 0x79, 0xc3, 0x46, 0x79, 0x4d, 0x5b, 0x5f, 0xd6, 0x85, + 0xdf, 0xb9, 0x44, 0x5a, 0x53, 0x1b, 0x61, 0xac, 0x9c, 0xd2, 0xba, 0xd7, + 0xda, 0x7f, 0x16, 0x79, 0x96, 0x5e, 0x61, 0x82, 0x22, 0x4b, 0x7e, 0xf6, + 0x47, 0x81, 0xb7, 0x23, 0x9b, 0xa4, 0x9b, 0xb8, 0xf1, 0x30, 0x7f, 0xc7, + 0xf3, 0xfc, 0x8e, 0x66, 0x09, 0x77, 0x92, 0x26, 0x50, 0xac, 0x0e, 0xb5, + 0x9d, 0x8c, 0xf0, 0x44, 0x32, 0x3a, 0x0f, 0x4a, 0x63, 0x52, 0x22, 0x31, + 0x68, 0xf0, 0x10, 0x81, 0x6e, 0x8c, 0xef, 0x32, 0xde, 0x09, 0x12, 0xd5, + 0xc4, 0xc3, 0xcf, 0xee, 0xe0, 0xaf, 0xac, 0x8a, 0x40, 0x16, 0xea, 0xba, + 0xcc, 0x8a, 0xcf, 0xa5, 0x9a, 0x47, 0xe2, 0x32, 0x0f, 0x8c, 0x10, 0xc2, + 0x8f, 0x3d, 0xc0, 0x69, 0xe2, 0x32, 0xd8, 0x4b, 0xed, 0xb8, 0x52, 0x73, + 0x48, 0x79, 0x2f, 0xbd, 0x61, 0x68, 0x81, 0x84, 0x07, 0x71, 0xb6, 0x52, + 0x1f, 0x0c, 0x5e, 0xba, 0x8c, 0x02, 0x31, 0xc5, 0xb6, 0xe9, 0xb7, 0x43, + 0xb7, 0x82, 0xa1, 0x3d, 0x9f, 0xcd, 0x29, 0xd4, 0x91, 0xd7, 0xa8, 0x05, + 0x14, 0xff, 0x77, 0xb8, 0xbb, 0xb0, 0x02, 0xca, 0x1f, 0x8c, 0xfc, 0x12, + 0x5a, 0xb0, 0x45, 0x65, 0xd4, 0x20, 0x70, 0xd8, 0x32, 0x52, 0x5a, 0x39, + 0xf2, 0x2d, 0x69, 0x58, 0x7a, 0x44, 0x73, 0x58, 0xff, 0x8f, 0xe6, 0x6e, + 0x20, 0x5d, 0x74, 0xee, 0xdc, 0x7f, 0xea, 0x35, 0x1f, 0xe0, 0xcd, 0x96, + 0xea, 0xbc, 0xed, 0x0b, 0xb0, 0x1a, 0x7a, 0x80, 0xca, 0x51, 0x57, 0x51, + 0xc8, 0x6e, 0x81, 0xad, 0xee, 0x6f, 0x54, 0x03, 0x67, 0xac, 0x73, 0x8d, + 0xf2, 0x65, 0x03, 0xde, 0xeb, 0x12, 0xa7, 0x4a, 0x73, 0x2e, 0x7a, 0xd1, + 0xb0, 0xa2, 0xa8, 0x7d, 0xea, 0x91, 0x6b, 0xed, 0x5b, 0x78, 0xe8, 0x87, + 0xf2, 0x8d, 0x08, 0x31, 0x8d, 0x67, 0xed, 0x6e, 0xcc, 0x95, 0x32, 0xe8, + 0xe0, 0x7a, 0xcb, 0x4c, 0xaf, 0xcd, 0x4b, 0x0f, 0x27, 0xd1, 0xd8, 0x41, + 0x2d, 0x1b, 0x08, 0x5a, 0x11, 0xf1, 0xdd, 0x47, 0x1e, 0x60, 0x6d, 0x5a, + 0x8f, 0x32, 0x4f, 0x7c, 0x30, 0x7e, 0xb4, 0xac, 0xbd, 0x3a, 0x1a, 0x39, + 0x92, 0xae, 0x52, 0xce, 0x44, 0x5b, 0x63, 0xf6, 0xb2, 0x1e, 0x18, 0x0d, + 0xd6, 0x02, 0x86, 0x4d, 0xc7, 0xf7, 0x71, 0x3d, 0x94, 0xf4, 0x16, 0xf7, + 0xc5, 0xae, 0xe3, 0xcf, 0x8e, 0xb6, 0x54, 0x22, 0x22, 0xc9, 0xb1, 0x76, + 0x8a, 0x86, 0x54, 0xaa, 0xdf, 0xcd, 0xbd, 0x32, 0xb9, 0x29, 0x38, 0xa2, + 0x38, 0xcc, 0x94, 0xd0, 0xa1, 0x48, 0x4d, 0x92, 0x24, 0x9a, 0x9c, 0x73, + 0xcc, 0xbb, 0xf8, 0x3c, 0x21, 0xb4, 0x12, 0x65, 0x7c, 0xd1, 0xf2, 0x3f, + 0x07, 0xbb, 0x03, 0x4b, 0xa8, 0x3c, 0x3c, 0xff, 0x82, 0xa2, 0xd7, 0x57, + 0x58, 0xa0, 0x94, 0x93, 0xe8, 0x82, 0xd2, 0xf8, 0x14, 0x8d, 0x7f, 0xcb, + 0xb0, 0x4b, 0x28, 0x46, 0x73, 0xe6, 0x78, 0x2b, 0x5d, 0x5a, 0x83, 0xda, + 0xa7, 0x91, 0xee, 0x3c, 0xbe, 0x40, 0x18, 0x15, 0xe2, 0x18, 0xba, 0x03, + 0xd7, 0xde, 0xfd, 0x99, 0xa0, 0x97, 0xc4, 0x6d, 0xd6, 0xe9, 0x76, 0x08, + 0x41, 0xef, 0x89, 0x38, 0x78, 0xa9, 0xe5, 0x63, 0x8b, 0x7d, 0x62, 0x52, + 0xab, 0xb3, 0x59, 0x2a, 0x68, 0xa2, 0x2b, 0x32, 0x11, 0xc6, 0xfe, 0x1b, + 0xd9, 0xf3, 0xb0, 0x1f, 0x05, 0xa6, 0x0f, 0xef, 0x46, 0x29, 0x8c, 0x9a, + 0x97, 0x6b, 0x08, 0x89, 0x89, 0xa6, 0x60, 0x39, 0x16, 0x99, 0x9f, 0x00, + 0x9e, 0x8b, 0x32, 0xd8, 0x30, 0xd0, 0xe6, 0x74, 0xc5, 0x04, 0x5a, 0x69, + 0x95, 0x2f, 0xbc, 0x6a, 0x67, 0xa6, 0xc7, 0xe9, 0x3f, 0x50, 0x3a, 0xf4, + 0x88, 0xd9, 0x10, 0x7b, 0xb8, 0xb3, 0xfc, 0x02, 0x8e, 0x96, 0xc1, 0xef, + 0xb1, 0xa8, 0xdd, 0x0b, 0x25, 0xf3, 0xb8, 0xee, 0x66, 0x8f, 0x9b, 0x1b, + 0x92, 0x68, 0xb7, 0x0b, 0x3f, 0x0d, 0x72, 0xfc, 0x62, 0x6c, 0xdb, 0xba, + 0xb5, 0x79, 0x70, 0x5e, 0x61, 0x05, 0xb6, 0x9c, 0x20, 0x3a, 0xda, 0xcc, + 0x6d, 0x79, 0x8d, 0x65, 0xc3, 0xdc, 0xea, 0xdf, 0x05, 0x81, 0xa1, 0x43, + 0x98, 0x62, 0x93, 0x87, 0xba, 0x70, 0x16, 0xb8, 0xfd, 0xc4, 0x08, 0x34, + 0x4e, 0x9e, 0x54, 0xda, 0x8f, 0xb0, 0xf5, 0x5b, 0x6a, 0xfa, 0xe9, 0x7a, + 0xb6, 0xf8, 0x47, 0xd2, 0x81, 0x5c, 0x20, 0xf5, 0xb3, 0x12, 0x40, 0x63, + 0x40, 0x54, 0x97, 0xd0, 0xca, 0xf5, 0x46, 0x4f, 0xbc, 0x9f, 0x15, 0xbc, + 0xca, 0x4e, 0xad, 0x08, 0x0d, 0xe0, 0xe1, 0xdc, 0x73, 0x81, 0xaa, 0xd3, + 0x99, 0xdb, 0xe5, 0x56, 0x16, 0x98, 0xf1, 0x22, 0x8b, 0x76, 0xdb, 0x90, + 0x2c, 0x5b, 0xa0, 0x71, 0x24, 0xac, 0xda, 0x3a, 0x89, 0xd3, 0xd0, 0x55, + 0xff, 0xea, 0x18, 0xb4, 0xb5, 0xca, 0xed, 0x69, 0xf5, 0x58, 0xd2, 0x0e, + 0x6f, 0x1b, 0x41, 0x90, 0x12, 0x42, 0x90, 0xac, 0x9d, 0x7e, 0xb4, 0x95, + 0xfb, 0x89, 0x08, 0xff, 0xd5, 0x8e, 0x2d, 0xb6, 0x57, 0x1e, 0xbf, 0x4e, + 0x70, 0x45, 0x3b, 0xd2, 0xa8, 0xdb, 0x8f, 0x3c, 0xf1, 0x80, 0xe7, 0x11, + 0xfb, 0xc4, 0x3f, 0xd2, 0x3d, 0xe1, 0x0b, 0xff, 0xae, 0xe3, 0x54, 0x87, + 0x31, 0x01, 0x29, 0xb7, 0x7d, 0x85, 0x0c, 0x87, 0x64, 0xfd, 0xbc, 0x02, + 0xdc, 0x12, 0x95, 0x47, 0x57, 0x0e, 0x4c, 0x0c, 0x06, 0xd7, 0xf9, 0x9c, + 0xfb, 0x9b, 0xf4, 0x41, 0x28, 0x31, 0x9c, 0x62, 0x8f, 0xf9, 0xac, 0x99, + 0x11, 0x41, 0x9e, 0xa3, 0xb1, 0x89, 0x53, 0x90, 0xea, 0x44, 0xe7, 0x65, + 0x8d, 0xb6, 0x87, 0x61, 0x1a, 0xf2, 0x42, 0xb1, 0x08, 0x8d, 0xe4, 0x44, + 0xed, 0xdb, 0xb6, 0x28, 0x5a, 0xd1, 0xb8, 0x28, 0x45, 0x35, 0x83, 0x6e, + 0xe6, 0xe5, 0x78, 0x08, 0x89, 0xf2, 0x3e, 0x61, 0xc5, 0xea, 0x91, 0xff, + 0xab, 0xc0, 0xad, 0x32, 0xcb, 0xb1, 0x55, 0xe7, 0x6d, 0x10, 0x12, 0xf3, + 0x1a, 0x72, 0x99, 0x05, 0x3b, 0x72, 0xa7, 0x51, 0xcf, 0x91, 0x64, 0xe7, + 0xac, 0x42, 0x72, 0xc1, 0x22, 0x00, 0xd0, 0xc4, 0xa1, 0x84, 0x7a, 0x8f, + 0xc3, 0x60, 0xee, 0xe6, 0x59, 0x8c, 0x2d, 0xc2, 0xc0, 0xc7, 0x2f, 0x91, + 0xcb, 0xf3, 0xc2, 0x08, 0xb5, 0x07, 0xa1, 0xab, 0xbe, 0x45, 0xa5, 0x8e, + 0x61, 0x54, 0x10, 0x4e, 0x4b, 0x8d, 0x82, 0xb2, 0xdc, 0xf1, 0x94, 0xa3, + 0x5a, 0x22, 0x2c, 0xd6, 0xef, 0x09, 0x7e, 0x7a, 0x12, 0x25, 0x30, 0x8e, + 0x6a, 0x68, 0x6f, 0x9c, 0x05, 0x61, 0x63, 0xc6, 0x6b, 0x6e, 0xec, 0xa3, + 0x74, 0x07, 0x40, 0x90, 0x80, 0x29, 0x73, 0x6e, 0x8c, 0x6d, 0xeb, 0xa0, + 0x9e, 0x07, 0x6a, 0x80, 0x3b, 0xb2, 0xd7, 0xf3, 0x6c, 0xb6, 0x09, 0x4d, + 0xaf, 0xdc, 0xb3, 0x3a, 0x25, 0xd2, 0xfe, 0x37, 0xb1, 0xf9, 0x7e, 0x09, + 0x12, 0xd2, 0x6e, 0x57, 0xaf, 0xc7, 0x67, 0xed, 0xfc, 0xbe, 0x64, 0x2e, + 0x2c, 0x16, 0xd6, 0x99, 0x87, 0x94, 0x71, 0x0e, 0xa6, 0xbb, 0xd6, 0xd0, + 0xbb, 0xd9, 0x22, 0x85, 0xb7, 0xa3, 0xd8, 0x4f, 0x03, 0xe6, 0x38, 0x14, + 0x86, 0x56, 0x1d, 0xd2, 0x90, 0xe5, 0xc1, 0x67, 0x39, 0xd9, 0x3d, 0xc2, + 0x62, 0x92, 0xdb, 0xbc, 0x08, 0xc7, 0xca, 0x6e, 0x3d, 0xa7, 0xcf, 0x01, + 0x93, 0x00, 0x01, 0x0f, 0x1e, 0x9c, 0x07, 0xd1, 0xc6, 0x17, 0xe8, 0x69, + 0xc5, 0x90, 0x44, 0x4b, 0xb4, 0x12, 0x04, 0x48, 0x4c, 0x1e, 0xd3, 0x15, + 0x97, 0xe4, 0xbf, 0x76, 0xed, 0xf9, 0xee, 0x39, 0xe7, 0x71, 0x46, 0x4f, + 0xc9, 0xab, 0x66, 0xf1, 0x1e, 0xbe, 0x69, 0xf3, 0xfb, 0xe9, 0x20, 0xbf, + 0xc7, 0x11, 0xaa, 0x00, 0x72, 0xfa, 0x99, 0xf0, 0x8a, 0x84, 0x6b, 0x39, + 0xf2, 0xab, 0x7f, 0xde, 0xa0, 0x31, 0xd7, 0x1d, 0x84, 0x3b, 0x3e, 0xa6, + 0x1f, 0xd1, 0x29, 0x39, 0x83, 0xdf, 0x4c, 0x07, 0x38, 0xcd, 0xbd, 0x1b, + 0xfd, 0x03, 0x51, 0x3a, 0xc5, 0xea, 0xfa, 0xd5, 0x43, 0xff, 0xc0, 0x4c, + 0x0c, 0x92, 0x18, 0x67, 0x94, 0x94, 0xf9, 0x6d, 0xd2, 0x1f, 0x4a, 0xae, + 0xf3, 0xcb, 0xf9, 0x21, 0x72, 0x54, 0x22, 0x20, 0xfe, 0xd9, 0x20, 0xe1, + 0xb3, 0xff, 0x7c, 0x18, 0x3e, 0xe0, 0xd9, 0x1c, 0x45, 0x6c, 0xd1, 0x1f, + 0x55, 0x3d, 0xd5, 0x5f, 0x6b, 0xd2, 0xc6, 0x49, 0x51, 0xd4, 0xe0, 0x6c, + 0xa8, 0x43, 0xcb, 0xca, 0x12, 0x99, 0x92, 0xd8, 0xc1, 0xd4, 0x4a, 0xda, + 0x77, 0x5f, 0x8a, 0x87, 0xc6, 0xf7, 0x75, 0x87, 0xbc, 0x69, 0xd0, 0x85, + 0x14, 0x18, 0x23, 0xec, 0x24, 0x48, 0x55, 0xec, 0x91, 0x04, 0x06, 0x31, + 0x18, 0x4c, 0x4a, 0x19, 0x76, 0xfa, 0x2d, 0x33, 0x88, 0xcc, 0xfd, 0x7c, + 0xa1, 0xa7, 0xea, 0x14, 0xb6, 0x17, 0x81, 0x28, 0x58, 0x63, 0xd0, 0x6c, + 0x07, 0x2a, 0xfd, 0x61, 0x48, 0x37, 0x96, 0x02, 0x3e, 0x6c, 0x41, 0xd6, + 0x5a, 0x23, 0x3f, 0x1c, 0xb1, 0x24, 0x8c, 0x56, 0x55, 0xf0, 0x2c, 0x47, + 0x6d, 0x1d, 0x0f, 0xd0, 0x18, 0xdc, 0x9c, 0x94, 0x32, 0xf1, 0xf9, 0xf2, + 0x05, 0x54, 0xb5, 0xca, 0xdb, 0x00, 0x18, 0x04, 0x2b, 0xa8, 0xb4, 0x3a, + 0x10, 0x3a, 0x37, 0xf6, 0x7b, 0xa4, 0x43, 0x5f, 0x2e, 0x2b, 0xdb, 0x5c, + 0x68, 0x88, 0xa6, 0x3b, 0x08, 0x5b, 0xdd, 0x7c, 0x7f, 0x80, 0xc3, 0x48, + 0x30, 0x27, 0xe0, 0x35, 0x60, 0x3f, 0x10, 0x91, 0x70, 0x3c, 0x81, 0xb4, + 0x53, 0x73, 0xe4, 0x39, 0x0f, 0xea, 0x12, 0x71, 0x33, 0xa6, 0x4e, 0x47, + 0x16, 0xef, 0x64, 0xa5, 0x24, 0xce, 0xa9, 0x5a, 0x8b, 0x66, 0x77, 0xae, + 0x88, 0xb3, 0xf4, 0x7b, 0xb3, 0x6f, 0x01, 0xa3, 0x75, 0x81, 0xb4, 0x55, + 0xbc, 0x8e, 0xe3, 0xea, 0x8d, 0x96, 0xde, 0x4a, 0x6c, 0xdc, 0x07, 0xd3, + 0x80, 0x33, 0xc4, 0xd5, 0x2f, 0x19, 0x66, 0x7e, 0x67, 0x40, 0x9d, 0x6e, + 0xf9, 0x45, 0x55, 0xae, 0x1a, 0xdf, 0xb1, 0x24, 0xa4, 0x73, 0xe6, 0xc4, + 0xe9, 0x33, 0xc0, 0x28, 0x31, 0x3e, 0xf1, 0x79, 0x8f, 0xe4, 0x80, 0xc2, + 0x91, 0x3b, 0x3c, 0xfd, 0x24, 0x20, 0xd9, 0x40, 0x48, 0xc0, 0xcc, 0x91, + 0xa0, 0x56, 0xd0, 0xa5, 0x28, 0xef, 0xfc, 0xff, 0x8f, 0xac, 0x7c, 0x86, + 0x9d, 0xde, 0xea, 0x35, 0xde, 0x6a, 0x4e, 0x21, 0x7f, 0xb6, 0x6f, 0xff, + 0x87, 0xd5, 0x8f, 0x51, 0x21, 0xf4, 0x78, 0xe5, 0x9b, 0x3c, 0xa4, 0x99, + 0xdc, 0x79, 0xf3, 0xba, 0x84, 0x6b, 0x8e, 0xa9, 0xc5, 0x61, 0xd3, 0x42, + 0x7c, 0x90, 0x39, 0xea, 0x26, 0xe3, 0x84, 0x6c, 0xfa, 0x17, 0x2f, 0xb5, + 0xd4, 0xf7, 0xb2, 0x13, 0x0e, 0xb5, 0xf7, 0x41, 0x67, 0x1a, 0xd5, 0xd1, + 0x3e, 0x47, 0x1c, 0x44, 0x89, 0xd9, 0x4f, 0x54, 0x3b, 0x74, 0xee, 0xc1, + 0x79, 0xff, 0x74, 0xeb, 0x27, 0x45, 0x19, 0xc6, 0xb0, 0xb8, 0xfc, 0x8f, + 0xfc, 0xcb, 0xfa, 0x7a, 0xd2, 0xf9, 0x52, 0x1e, 0x8f, 0x41, 0x15, 0xa0, + 0x97, 0x22, 0x03, 0x69, 0xb7, 0xea, 0x4b, 0x01, 0x9f, 0x46, 0x5c, 0x39, + 0x4f, 0x4a, 0x8c, 0xfa, 0x32, 0x03, 0xca, 0x27, 0xd3, 0xc2, 0x88, 0xbf, + 0x6f, 0x37, 0xeb, 0xc8, 0xa1, 0xae, 0xde, 0xe2, 0xde, 0x65, 0x60, 0x4f, + 0x4d, 0x65, 0x13, 0x46, 0xbf, 0x37, 0x03, 0x3d, 0x5f, 0x94, 0xf6, 0x50, + 0xfb, 0x79, 0x96, 0x71, 0xa8, 0xd9, 0xb8, 0x51, 0x00, 0x54, 0x21, 0x40, + 0x58, 0x19, 0x00, 0xf6, 0x79, 0xc5, 0xef, 0xcb, 0xeb, 0xc3, 0xa4, 0x73, + 0x22, 0x3a, 0x79, 0x39, 0x40, 0xf6, 0xa8, 0xb1, 0x54, 0xe3, 0xbd, 0x98, + 0xa0, 0xb1, 0xee, 0xaa, 0x5c, 0x9c, 0x95, 0x53, 0x80, 0xdb, 0x80, 0x56, + 0xeb, 0xa0, 0xe5, 0xf8, 0x46, 0xee, 0x03, 0x0a, 0xdf, 0x7f, 0x61, 0xc8, + 0xd1, 0x15, 0x1e, 0x72, 0x5f, 0x96, 0x09, 0xbd, 0x23, 0x33, 0x13, 0x8e, + 0x8a, 0xf2, 0x4b, 0x51, 0xd7, 0xbb, 0xe8, 0x0b, 0x68, 0xef, 0x5b, 0x9e, + 0xdf, 0x23, 0xbc, 0xf9, 0x75, 0x4f, 0x84, 0x03, 0x92, 0x68, 0xe1, 0x2e, + 0xf1, 0xe5, 0xd1, 0x31, 0xf6, 0xed, 0x33, 0x2d, 0x8a, 0x52, 0x77, 0xf0, + 0xe2, 0xec, 0x5a, 0x54, 0x62, 0x59, 0xfc, 0x1f, 0x8d, 0x71, 0xc0, 0xb8, + 0xc0, 0x9a, 0xb6, 0xfe, 0x83, 0xb8, 0x96, 0x16, 0x3d, 0x67, 0xef, 0xaa, + 0x73, 0x7b, 0x3e, 0xfa, 0xdd, 0x98, 0x23, 0x34, 0xed, 0xe3, 0xfc, 0x93, + 0x4e, 0xde, 0x93, 0x20, 0xfd, 0x3e, 0x93, 0xb4, 0x27, 0x3e, 0x43, 0x49, + 0x4a, 0x4e, 0x5d, 0x65, 0x90, 0x91, 0xa6, 0xab, 0xb5, 0xd4, 0xe7, 0xef, + 0xf7, 0x06, 0x07, 0x0d, 0x13, 0x21, 0x22, 0x44, 0x84, 0x8f, 0x9a, 0xad, + 0xbb, 0x00, 0x0f, 0x28, 0x37, 0x3d, 0x47, 0x5f, 0x63, 0x78, 0x7e, 0x83, + 0x89, 0x8e, 0x93, 0x98, 0x9b, 0xab, 0xb3, 0xb6, 0xc5, 0xe7, 0xea, 0xff, + 0x1a, 0x47, 0x4a, 0x69, 0x76, 0x7c, 0x86, 0x8a, 0x8f, 0xaa, 0xad, 0xca, + 0xcb, 0xd9, 0xdd, 0xe1, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x1d, 0x34, 0x45}; diff --git a/sw/device/tests/embedpqc/mldsa_testvectors.h b/sw/device/tests/embedpqc/mldsa_testvectors.h new file mode 100644 index 0000000000000..67fc7dab0b47a --- /dev/null +++ b/sw/device/tests/embedpqc/mldsa_testvectors.h @@ -0,0 +1,19 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_SW_DEVICE_TESTS_EMBEDPQC_MLDSA_TESTVECTORS_H_ +#define OPENTITAN_SW_DEVICE_TESTS_EMBEDPQC_MLDSA_TESTVECTORS_H_ + +#include + +#include "third_party/embedpqc/mldsa44_tiny.h" + +// ML-DSA-44 vectors +extern const uint8_t kMldsa44KeygenSeed[MLDSA44_PRIVATE_SEED_BYTES]; +extern const uint8_t kMldsa44ExpectedPublicKey[MLDSA44_PUBLIC_KEY_BYTES]; +extern const uint8_t kMldsa44SignSeed[MLDSA44_PRIVATE_SEED_BYTES]; +extern const uint8_t kMldsa44Message[16]; +extern const uint8_t kMldsa44ExpectedSignature[MLDSA44_SIGNATURE_BYTES]; + +#endif // OPENTITAN_SW_DEVICE_TESTS_EMBEDPQC_MLDSA_TESTVECTORS_H_ diff --git a/third_party/embedpqc/BUILD b/third_party/embedpqc/BUILD new file mode 100644 index 0000000000000..6dac76c919f86 --- /dev/null +++ b/third_party/embedpqc/BUILD @@ -0,0 +1,43 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +package(default_visibility = ["//visibility:public"]) + +exports_files(["LICENSE"]) + +cc_library( + name = "ct", + hdrs = ["ct.h"], +) + +cc_library( + name = "mldsa_mu", + srcs = ["mldsa_mu.c"], + hdrs = ["mldsa_mu.h"], + deps = [ + "//third_party/embedpqc/ports:shake", + ], +) + +cc_library( + name = "mldsa_tiny_common", + srcs = ["mldsa_tiny_common.c"], + hdrs = ["mldsa_tiny_common.h"], + deps = [ + ":ct", + "//third_party/embedpqc/ports:shake", + ], +) + +cc_library( + name = "mldsa44_tiny", + srcs = ["mldsa44_tiny.c"], + hdrs = ["mldsa44_tiny.h"], + deps = [ + ":ct", + ":mldsa_mu", + ":mldsa_tiny_common", + "//third_party/embedpqc/ports:shake", + ], +) diff --git a/third_party/embedpqc/LICENSE b/third_party/embedpqc/LICENSE new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/third_party/embedpqc/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/embedpqc/ct.h b/third_party/embedpqc/ct.h new file mode 100644 index 0000000000000..5899a0746a0a9 --- /dev/null +++ b/third_party/embedpqc/ct.h @@ -0,0 +1,75 @@ +// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OPENTITAN_THIRD_PARTY_EMBEDPQC_CT_H_ +#define OPENTITAN_THIRD_PARTY_EMBEDPQC_CT_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint32_t ct_uint32_t; + +static inline ct_uint32_t ct_value_barrier(ct_uint32_t x) { + asm volatile("" : [x] "+r"(x) :); + return x; +} + +static inline ct_uint32_t ct_if(ct_uint32_t mask, ct_uint32_t a, + ct_uint32_t b) { + mask = ct_value_barrier(mask); + return (mask & a) | (~mask & b); +} + +static inline ct_uint32_t ct_lt(ct_uint32_t a, ct_uint32_t b) { + ct_uint32_t mask = a ^ ((a ^ b) | ((a - b) ^ a)); + return 0u - (mask >> (sizeof(mask) * 8 - 1)); +} + +static inline ct_uint32_t ct_ge(ct_uint32_t a, ct_uint32_t b) { + return ~ct_lt(a, b); +} + +// See https://www.bearssl.org/ctmul.html for more details on non-constant time +// multiplication on different CPUs. +static inline uint64_t ct_mul64(uint32_t a, uint32_t b) { +#if defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_7M__) + uint32_t a_lo = a & 0xFFFF; + uint32_t a_hi = a >> 16; + uint32_t b_lo = b & 0xFFFF; + uint32_t b_hi = b >> 16; + + uint32_t m0 = ct_value_barrier(a_lo * b_lo); + uint32_t m1 = ct_value_barrier(a_lo * b_hi); + uint32_t m2 = ct_value_barrier(a_hi * b_lo); + uint32_t m3 = ct_value_barrier(a_hi * b_hi); + + uint32_t mid = (m0 >> 16) + (m1 & 0xFFFF) + (m2 & 0xFFFF); + + uint32_t res_lo = (m0 & 0xFFFF) | ((mid & 0xFFFF) << 16); + uint32_t res_hi = m3 + (m1 >> 16) + (m2 >> 16) + (mid >> 16); + + return ((uint64_t)res_hi << 32) | res_lo; +#else + return (uint64_t)a * b; +#endif +} + +#ifdef __cplusplus +} +#endif + +#endif // OPENTITAN_THIRD_PARTY_EMBEDPQC_CT_H_ diff --git a/third_party/embedpqc/mldsa44_tiny.c b/third_party/embedpqc/mldsa44_tiny.c new file mode 100644 index 0000000000000..ccd0887b6981f --- /dev/null +++ b/third_party/embedpqc/mldsa44_tiny.c @@ -0,0 +1,567 @@ +// Copyright 2024 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "third_party/embedpqc/mldsa44_tiny.h" + +#include +#include +#include + +#include "third_party/embedpqc/ct.h" +#include "third_party/embedpqc/mldsa_mu.h" +#include "third_party/embedpqc/mldsa_tiny_common.h" +#include "third_party/embedpqc/shake.h" + +/* ML-DSA-44 parameters. */ + +#define TAU 39 +#define LAMBDA_BYTES (128 / 8) +#define GAMMA1 (1 << 17) +#define K_GAMMA_2 ((K_PRIME - 1) / 88) +#define BETA 78 +#define OMEGA 80 + +/* Fundamental types. */ + +typedef struct { + scalar_t v[4]; +} vector4_t; + +typedef struct { + uint8_t v[OMEGA]; + uint8_t num_hints; +} hint_ent_t; + +typedef struct { + hint_ent_t v[4]; +} hint_t; + +/* Complex types. */ + +typedef struct { + uint8_t rho[K_RHO_BYTES]; + uint8_t k[K_K_BYTES]; + uint8_t sigma[K_SIGMA_BYTES]; + vector4_t t0_ntt; +} private_key_t; + +/* Arithmetic. */ + +static void vector4_zero(vector4_t *out) { memset(&out->v, 0, sizeof(out->v)); } + +static void vector4_add(vector4_t *out, const vector4_t *lhs, + const vector4_t *rhs) { + for (size_t i = 0; i < 4; i++) { + scalar_add(&out->v[i], &lhs->v[i], &rhs->v[i]); + } +} + +static void vector4_sub(vector4_t *out, const vector4_t *lhs, + const vector4_t *rhs) { + for (size_t i = 0; i < 4; i++) { + scalar_sub(&out->v[i], &lhs->v[i], &rhs->v[i]); + } +} + +static void vector4_mul_scalar(vector4_t *out, const vector4_t *lhs, + const scalar_t *rhs) { + for (size_t i = 0; i < 4; i++) { + scalar_mul(&out->v[i], &lhs->v[i], rhs); + } +} + +static void vector4_ntt(vector4_t *a) { + for (size_t i = 0; i < 4; i++) { + scalar_ntt(&a->v[i]); + } +} + +static void vector4_inverse_ntt(vector4_t *a) { + for (size_t i = 0; i < 4; i++) { + scalar_inverse_ntt(&a->v[i]); + } +} + +/* Rounding and hints. */ + +static void vector4_power2_round(vector4_t *t1, vector4_t *t0, + const vector4_t *t) { + for (size_t i = 0; i < 4; i++) { + scalar_power2_round(&t1->v[i], &t0->v[i], &t->v[i]); + } +} + +static void vector4_scale_power2_round(vector4_t *out, const vector4_t *in) { + for (size_t i = 0; i < 4; i++) { + scalar_scale_power2_round(&out->v[i], &in->v[i]); + } +} + +static uint32_t vector4_max(const vector4_t *a) { + uint32_t max = 0; + for (size_t i = 0; i < 4; i++) { + scalar_max(&max, &a->v[i]); + } + return max; +} + +static void vector4_use_hint(vector4_t *out, const hint_t *h, + const vector4_t *r) { + for (size_t i = 0; i < 4; i++) { + scalar_use_hint_88(&out->v[i], h->v[i].v, h->v[i].num_hints, &r->v[i]); + } +} + +/* Expansion functions. */ + +// A combination of FIPS 204, Algorithm 32 (`ExpandA`) and matrix +// multiplication. +static void matrix44_expand_mul(vector4_t *out, const uint8_t rho[K_RHO_BYTES], + const vector4_t *a) { + uint8_t derived_seed[K_RHO_BYTES + 2]; + memcpy(derived_seed, rho, K_RHO_BYTES); + vector4_zero(out); + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + scalar_t m_ij; + // Step 1: Generate (i,j)-th matrix entry. + derived_seed[K_RHO_BYTES + 1] = (uint8_t)i; + derived_seed[K_RHO_BYTES] = (uint8_t)j; + scalar_from_keccak_vartime(&m_ij, derived_seed); + // Step 2: Multiply with right hand side and sum into output. + scalar_mul_add(&out->v[i], &out->v[i], &m_ij, &a->v[j]); + } + } +} + +static void expand_s1(vector4_t *s1, const uint8_t sigma[K_SIGMA_BYTES]) { + for (size_t i = 0; i < 4; i++) { + expand_scalar_2(&s1->v[i], sigma, i); + } +} + +static void expand_s2(vector4_t *s2, const uint8_t sigma[K_SIGMA_BYTES]) { + for (size_t i = 0; i < 4; i++) { + expand_scalar_2(&s2->v[i], sigma, i + 4); + } +} + +static void expand_s1_ntt_mul_scalar(scalar_t *out, size_t i, + const uint8_t sigma[K_SIGMA_BYTES], + const scalar_t *rhs) { + expand_scalar_2(out, sigma, (uint8_t)i); + scalar_ntt(out); + scalar_mul(out, out, rhs); +} + +static void expand_s2_ntt_mul_scalar(scalar_t *out, size_t i, + const uint8_t sigma[K_SIGMA_BYTES], + const scalar_t *rhs) { + expand_scalar_2(out, sigma, (uint8_t)i + 4); + scalar_ntt(out); + scalar_mul(out, out, rhs); +} + +// A combination of FIPS 204, Algorithm 32 (`ExpandA`), matrix +// multiplication, and FIPS 204, Algorithm 34 (`ExpandMask`). +static void matrix44_expand_mul_mask(vector4_t *out, + const uint8_t rho[K_RHO_BYTES], + const uint8_t rho_prime[K_RHO_PRIME_BYTES], + size_t kappa) { + vector4_zero(out); + uint8_t derived_seed[K_RHO_BYTES + 2]; + memcpy(derived_seed, rho, K_RHO_BYTES); + for (size_t j = 0; j < 4; j++) { + scalar_t y_j; + scalar_expand_mask_17(&y_j, rho_prime, kappa, j); + scalar_ntt(&y_j); + for (size_t i = 0; i < 4; i++) { + scalar_t m_ij; + derived_seed[K_RHO_BYTES + 1] = (uint8_t)i; + derived_seed[K_RHO_BYTES] = (uint8_t)j; + scalar_from_keccak_vartime(&m_ij, derived_seed); + scalar_mul_add(&out->v[i], &out->v[i], &m_ij, &y_j); + } + } +} + +/* Encoding. */ + +// FIPS 204, Algorithm 16 (`SimpleBitPack`). +static void vector4_encode_10(uint8_t *out, const vector4_t *a) { + for (size_t i = 0; i < 4; i++) { + scalar_encode_10(out + i * 10 * K_DEGREE / 8, &a->v[i]); + } +} + +// FIPS 204, Algorithm 18 (`SimpleBitUnpack`). +static void vector4_decode_10(vector4_t *out, const uint8_t *in) { + for (size_t i = 0; i < 4; i++) { + scalar_decode_10(&out->v[i], in + i * 10 * K_DEGREE / 8); + } +} + +static void vector4_decode_signed_18_17(vector4_t *out, const uint8_t *in) { + for (size_t i = 0; i < 4; i++) { + scalar_decode_signed_18_17(&out->v[i], in + i * 18 * K_DEGREE / 8); + } +} + +// FIPS 204, Algorithm 20 (`HintBitPack`). +static void hint_bit_pack(uint8_t out[OMEGA + 4], const vector4_t *h) { + memset(out, 0, OMEGA + 4); + int index = 0; + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < K_DEGREE; j++) { + if (h->v[i].c[j]) { + // h must have at most OMEGA non-zero coefficients. + out[index++] = (uint8_t)j; + } + } + out[OMEGA + i] = (uint8_t)index; + } +} + +// FIPS 204, Algorithm 21 (`HintBitUnpack`). +static int hint_bit_unpack(hint_t *h, const uint8_t in[OMEGA + 4]) { + int index = 0; + for (size_t i = 0; i < 4; i++) { + const int limit = in[OMEGA + i]; + if (limit < index || limit > OMEGA) { + return 0; + } + h->v[i].num_hints = 0; + int last = -1; + while (index < limit) { + int byte = in[index++]; + if (last >= 0 && byte <= last) { + return 0; + } + last = byte; + h->v[i].v[h->v[i].num_hints++] = (uint8_t)byte; + } + } + for (; index < OMEGA; index++) { + if (in[index] != 0) { + return 0; + } + } + return 1; +} + +// FIPS 204, Algorithm 27 (`sigDecode`). +static int decode_signature(uint8_t c_tilde[2 * LAMBDA_BYTES], vector4_t *z, + hint_t *h, + const uint8_t in[MLDSA44_SIGNATURE_BYTES]) { + memcpy(c_tilde, in, 2 * LAMBDA_BYTES); + + const uint8_t *z_input = &in[2 * LAMBDA_BYTES]; + vector4_decode_signed_18_17(z, z_input); + + const uint8_t *hint_input = &in[2 * LAMBDA_BYTES + 576 * 4]; + return hint_bit_unpack(h, hint_input); +} + +/* Main algorithms. */ + +// FIPS 204, Algorithm 6 (`ML-DSA.KeyGen_internal`). +static void generate_key_internal( + uint8_t out_encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + private_key_t *priv, const uint8_t entropy[MLDSA44_PRIVATE_SEED_BYTES]) { + uint8_t augmented_entropy[MLDSA44_PRIVATE_SEED_BYTES + 2]; + memcpy(augmented_entropy, entropy, MLDSA44_PRIVATE_SEED_BYTES); + // The K and L parameters are appended to the seed. + augmented_entropy[MLDSA44_PRIVATE_SEED_BYTES] = 4; + augmented_entropy[MLDSA44_PRIVATE_SEED_BYTES + 1] = 4; + + uint8_t expanded_seed[K_RHO_BYTES + K_SIGMA_BYTES + K_K_BYTES]; + SHAKE256_buffer(expanded_seed, sizeof(expanded_seed), augmented_entropy, + sizeof(augmented_entropy)); + + const uint8_t *const rho = expanded_seed; + const uint8_t *const sigma = expanded_seed + K_RHO_BYTES; + const uint8_t *const k = expanded_seed + K_RHO_BYTES + K_SIGMA_BYTES; + + memcpy(priv->rho, rho, sizeof(priv->rho)); + memcpy(priv->sigma, sigma, sizeof(priv->sigma)); + memcpy(priv->k, k, sizeof(priv->k)); + + vector4_t s1_ntt; + expand_s1(&s1_ntt, sigma); + vector4_ntt(&s1_ntt); + + // It is safe for t1 or t0 to alias t because the underlying power2_round + // takes its input by value, capturing it before any writes to the output + // occur. t1 and t0 must be distinct. + vector4_t *t = &priv->t0_ntt; + matrix44_expand_mul(t, rho, &s1_ntt); + vector4_inverse_ntt(t); + vector4_t *const s2 = &s1_ntt; + expand_s2(s2, sigma); + vector4_add(t, t, s2); + + vector4_t *const t1 = &s1_ntt; + vector4_power2_round(t1, &priv->t0_ntt, t); + vector4_ntt(&priv->t0_ntt); + + memcpy(out_encoded_public_key, rho, K_RHO_BYTES); + vector4_encode_10(&out_encoded_public_key[K_RHO_BYTES], t1); +} + +// generate_priv_internal is marked noinline to prevent the compiler from +// inlining it, which would increase the peak stack usage of the caller. +static __attribute__((noinline)) void generate_priv_internal( + private_key_t *out_priv, uint8_t out_public_key_hash[K_TR_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES]) { + uint8_t encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES]; + generate_key_internal(encoded_public_key, out_priv, private_key_seed); + if (out_public_key_hash != NULL) { + SHAKE256_buffer(out_public_key_hash, K_TR_BYTES, encoded_public_key, + sizeof(encoded_public_key)); + } +} + +// FIPS 204, Algorithm 7 (`ML-DSA.Sign_internal`). +static void sign_internal_with_mu( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const private_key_t *priv, const uint8_t mu[K_MU_BYTES], + const uint8_t randomizer[MLDSA44_RANDOMIZER_BYTES]) { + uint8_t rho_prime[K_RHO_PRIME_BYTES]; + shake256_ctxt_t shake256_ctxt; + SHAKE256_init(&shake256_ctxt); + SHAKE256_absorb(&shake256_ctxt, priv->k, sizeof(priv->k)); + SHAKE256_absorb(&shake256_ctxt, randomizer, MLDSA44_RANDOMIZER_BYTES); + SHAKE256_absorb(&shake256_ctxt, mu, K_MU_BYTES); + SHAKE256_squeeze(&shake256_ctxt, rho_prime, K_RHO_PRIME_BYTES); + SHAKE256_free(&shake256_ctxt); + + uint8_t c_tilde[2 * LAMBDA_BYTES]; + vector4_t tmp; + + // kappa must not exceed 2^16/L. But the probability of it + // exceeding even 1000 iterations is vanishingly small. + for (size_t kappa = 0;; kappa += 4) { + vector4_t *const w = &tmp; + matrix44_expand_mul_mask(w, priv->rho, rho_prime, kappa); + vector4_inverse_ntt(w); + + SHAKE256_init(&shake256_ctxt); + SHAKE256_absorb(&shake256_ctxt, mu, K_MU_BYTES); + // Encode each scalar of w1 individually and absorb into the c_tilde state. + for (size_t i = 0; i < 4; i++) { + uint8_t w1_encoded_part[192]; + scalar_t temp; + scalar_high_bits_88(&temp, &w->v[i]); + scalar_encode_6(w1_encoded_part, &temp); + SHAKE256_absorb(&shake256_ctxt, w1_encoded_part, 192); + } + SHAKE256_squeeze(&shake256_ctxt, c_tilde, 2 * LAMBDA_BYTES); + SHAKE256_free(&shake256_ctxt); + + scalar_t c_ntt; + scalar_sample_in_ball_vartime(TAU, &c_ntt, c_tilde, sizeof(c_tilde)); + scalar_ntt(&c_ntt); + + // Encode c_tilde. + memcpy(out_encoded_signature, c_tilde, 2 * LAMBDA_BYTES); + + // We compute z and update the bounds for each scalar in the vector + // separately. + uint32_t z_max = 0, r0_max = 0, ct0_max = 0; + size_t h_ones = 0; + uint8_t *z_output = &out_encoded_signature[2 * LAMBDA_BYTES]; + vector4_t *const h = &tmp; + for (size_t i = 0; i < 4; i++) { + scalar_t cs_i; + expand_s1_ntt_mul_scalar(&cs_i, i, priv->sigma, &c_ntt); + scalar_inverse_ntt(&cs_i); + + scalar_t z_i; + scalar_expand_mask_17(&z_i, rho_prime, kappa, i); + scalar_add(&z_i, &z_i, &cs_i); + + // Update z_max bound. + scalar_max(&z_max, &z_i); + + // Encode z_i. + scalar_encode_signed_18_17(z_output + i * 18 * K_DEGREE / 8, &z_i); + + expand_s2_ntt_mul_scalar(&cs_i, i, priv->sigma, &c_ntt); + scalar_inverse_ntt(&cs_i); + + scalar_t r0_i; + scalar_sub(&r0_i, &w->v[i], &cs_i); + scalar_low_bits_88(&r0_i, &r0_i); + + // Update r0_max bound. + scalar_max_signed(&r0_max, &r0_i); + + scalar_t *ct0_i = &r0_i; + scalar_mul(ct0_i, &priv->t0_ntt.v[i], &c_ntt); + scalar_inverse_ntt(ct0_i); + scalar_make_hint_88(&h->v[i], ct0_i, &cs_i, &w->v[i]); + + // Update ct0_max bounds and h_ones count. + scalar_max(&ct0_max, ct0_i); + h_ones += scalar_count_ones(&h->v[i]); + } + + // Leaking the fact that a signature was rejected is fine as the next + // attempt at a signature will be (indistinguishable from) independent of + // this one. Note we leak less than what is described by the paper; we do + // not reveal which coefficient violated the bound, and we hide which of the + // |z_max|, |r0_max|, |ct0_max|, or h_ones violated the bound. + if (ct_ge(z_max, GAMMA1 - BETA) | ct_ge(r0_max, K_GAMMA_2 - BETA) | + ct_ge(ct0_max, K_GAMMA_2) | ct_lt(OMEGA, (ct_uint32_t)h_ones)) { + continue; + } + + // Encode hint. + uint8_t *hint_output = &out_encoded_signature[2 * LAMBDA_BYTES + 576 * 4]; + hint_bit_pack(hint_output, h); + + return; + } +} + +// FIPS 204, Algorithm 8 (`ML-DSA.Verify_internal`). +static int verify_internal_with_mu( + const uint8_t encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + const uint8_t encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t mu[K_MU_BYTES]) { + uint8_t sig_c_tilde[2 * LAMBDA_BYTES]; + vector4_t z; + hint_t h; + vector4_t az_ntt; + + if (!decode_signature(sig_c_tilde, &z, &h, encoded_signature)) + return 0; + + // Compute ||z||_\infty and set z = NTT(z). + uint32_t z_max = vector4_max(&z); + vector4_ntt(&z); + + scalar_t c_ntt; + scalar_sample_in_ball_vartime(TAU, &c_ntt, sig_c_tilde, sizeof(sig_c_tilde)); + scalar_ntt(&c_ntt); + + matrix44_expand_mul(&az_ntt, encoded_public_key, &z); + + // Since sign.z is no longer needed after the matrix multiplication, we reuse + // its buffer to hold t1 and ct1_ntt (which eventually stores c * t1 * 2^d) to + // save stack space. + vector4_t *const t1 = &z; + vector4_decode_10(t1, &encoded_public_key[K_RHO_BYTES]); + vector4_t *const ct1_ntt = &z; + vector4_scale_power2_round(ct1_ntt, t1); + vector4_ntt(ct1_ntt); + + vector4_mul_scalar(ct1_ntt, ct1_ntt, &c_ntt); + + vector4_t *const w1 = &az_ntt; + vector4_sub(w1, &az_ntt, ct1_ntt); + vector4_inverse_ntt(w1); + + vector4_use_hint(w1, &h, w1); + + uint8_t c_tilde[2 * LAMBDA_BYTES]; + shake256_ctxt_t shake256_ctxt; + SHAKE256_init(&shake256_ctxt); + SHAKE256_absorb(&shake256_ctxt, mu, K_MU_BYTES); + // Encode each scalar of w1 individually and absorb into the c_tilde state. + for (size_t i = 0; i < 4; i++) { + uint8_t w1_encoded_part[192]; + scalar_encode_6(w1_encoded_part, &w1->v[i]); + SHAKE256_absorb(&shake256_ctxt, w1_encoded_part, 192); + } + SHAKE256_squeeze(&shake256_ctxt, c_tilde, 2 * LAMBDA_BYTES); + SHAKE256_free(&shake256_ctxt); + + return z_max < (uint32_t)(GAMMA1 - BETA) && + memcmp(c_tilde, sig_c_tilde, 2 * LAMBDA_BYTES) == 0; +} + +/* Public API. */ + +void mldsa44_tiny_pub_from_seed( + uint8_t out_encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES]) { + private_key_t priv; + generate_key_internal(out_encoded_public_key, &priv, private_key_seed); +} + +void mldsa44_tiny_sign( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t randomizer[MLDSA44_RANDOMIZER_BYTES], const uint8_t *msg, + size_t msg_len) { + private_key_t priv; + // Re-use the same buffer for tr (public key hash) as mu. + uint8_t tr_mu[K_TR_BYTES]; + generate_priv_internal(&priv, tr_mu, private_key_seed); + compute_mu_from_public_key_hash(tr_mu, tr_mu, NULL, 0, msg, msg_len); + sign_internal_with_mu(out_encoded_signature, &priv, tr_mu, randomizer); +} + +void mldsa44_tiny_sign_mu( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t randomizer[MLDSA44_RANDOMIZER_BYTES], + const uint8_t mu[K_MU_BYTES]) { + private_key_t priv; + generate_priv_internal(&priv, NULL, private_key_seed); + sign_internal_with_mu(out_encoded_signature, &priv, mu, randomizer); +} + +void mldsa44_tiny_sign_deterministic( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t *msg, size_t msg_len) { + private_key_t priv; + // Re-use the same buffer for tr (public key hash) as mu. + uint8_t tr_mu[K_TR_BYTES]; + generate_priv_internal(&priv, tr_mu, private_key_seed); + uint8_t randomizer[MLDSA44_RANDOMIZER_BYTES]; + memset(randomizer, 0, MLDSA44_RANDOMIZER_BYTES); + compute_mu_from_public_key_hash(tr_mu, tr_mu, NULL, 0, msg, msg_len); + sign_internal_with_mu(out_encoded_signature, &priv, tr_mu, randomizer); +} + +void mldsa44_tiny_sign_mu_deterministic( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t mu[K_MU_BYTES]) { + private_key_t priv; + uint8_t randomizer[MLDSA44_RANDOMIZER_BYTES]; + memset(randomizer, 0, MLDSA44_RANDOMIZER_BYTES); + generate_priv_internal(&priv, NULL, private_key_seed); + sign_internal_with_mu(out_encoded_signature, &priv, mu, randomizer); +} + +int mldsa44_tiny_verify( + const uint8_t encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + const uint8_t encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t *msg, size_t msg_len) { + uint8_t mu[K_MU_BYTES]; + compute_mu(mu, encoded_public_key, MLDSA44_PUBLIC_KEY_BYTES, NULL, 0, msg, + msg_len); + return verify_internal_with_mu(encoded_public_key, encoded_signature, mu); +} + +int mldsa44_tiny_verify_mu( + const uint8_t encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + const uint8_t encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t mu[K_MU_BYTES]) { + return verify_internal_with_mu(encoded_public_key, encoded_signature, mu); +} diff --git a/third_party/embedpqc/mldsa44_tiny.h b/third_party/embedpqc/mldsa44_tiny.h new file mode 100644 index 0000000000000..8e9e71c7d6ffd --- /dev/null +++ b/third_party/embedpqc/mldsa44_tiny.h @@ -0,0 +1,80 @@ +// Copyright 2024 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA44_TINY_H_ +#define OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA44_TINY_H_ + +#include +#include + +#include "third_party/embedpqc/mldsa_mu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MLDSA44_PRIVATE_SEED_BYTES +#define MLDSA44_PRIVATE_SEED_BYTES 32 +#endif +#ifndef MLDSA44_RANDOMIZER_BYTES +#define MLDSA44_RANDOMIZER_BYTES 32 +#endif +#ifndef MLDSA44_PUBLIC_KEY_BYTES +#define MLDSA44_PUBLIC_KEY_BYTES 1312 +#endif +#ifndef MLDSA44_SIGNATURE_BYTES +#define MLDSA44_SIGNATURE_BYTES 2420 +#endif + +void mldsa44_tiny_pub_from_seed( + uint8_t out_encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES]); + +void mldsa44_tiny_sign( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t randomizer[MLDSA44_RANDOMIZER_BYTES], const uint8_t *msg, + size_t msg_len); + +void mldsa44_tiny_sign_mu( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t randomizer[MLDSA44_RANDOMIZER_BYTES], + const uint8_t mu[K_MU_BYTES]); + +void mldsa44_tiny_sign_deterministic( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t *msg, size_t msg_len); + +void mldsa44_tiny_sign_mu_deterministic( + uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t private_key_seed[MLDSA44_PRIVATE_SEED_BYTES], + const uint8_t mu[K_MU_BYTES]); + +int mldsa44_tiny_verify( + const uint8_t encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + const uint8_t encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t *msg, size_t msg_len); + +int mldsa44_tiny_verify_mu( + const uint8_t encoded_public_key[MLDSA44_PUBLIC_KEY_BYTES], + const uint8_t encoded_signature[MLDSA44_SIGNATURE_BYTES], + const uint8_t mu[K_MU_BYTES]); + +#ifdef __cplusplus +} +#endif + +#endif // OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA44_TINY_H_ diff --git a/third_party/embedpqc/mldsa_mu.c b/third_party/embedpqc/mldsa_mu.c new file mode 100644 index 0000000000000..e71d3bfcf5c3c --- /dev/null +++ b/third_party/embedpqc/mldsa_mu.c @@ -0,0 +1,53 @@ +// Copyright 2024 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "third_party/embedpqc/mldsa_mu.h" + +#include +#include + +#include "third_party/embedpqc/shake.h" + +// Computes the mu value for a given public key, context, and message. +// µ is the "message representative that may optionally be computed in a +// different cryptographic module" as referenced in Algorithm 7, line 6. +void compute_mu(uint8_t out_mu[K_MU_BYTES], const uint8_t *encoded_public_key, + size_t encoded_public_key_len, const uint8_t *context, + uint8_t context_len, const uint8_t *msg, size_t msg_len) { + // Re-use out_mu to store the public key hash first. + SHAKE256_buffer(out_mu, K_MU_BYTES, encoded_public_key, + encoded_public_key_len); + compute_mu_from_public_key_hash(out_mu, out_mu, context, context_len, msg, + msg_len); +} + +// As `compute_mu`, except using the `public_key_hash` (`tr` in FIPS 204), +// which is computed as SHAKE256_512(encoded_public_key). +// +// It is safe for `out_mu` and `public_key_hash` to be the same buffer. +void compute_mu_from_public_key_hash(uint8_t out_mu[K_MU_BYTES], + const uint8_t public_key_hash[K_TR_BYTES], + const uint8_t *context, + uint8_t context_len, const uint8_t *msg, + size_t msg_len) { + shake256_ctxt_t shake256_ctxt; + SHAKE256_init(&shake256_ctxt); + SHAKE256_absorb(&shake256_ctxt, public_key_hash, K_TR_BYTES); + uint8_t context_prefix[2] = {0, context_len}; + SHAKE256_absorb(&shake256_ctxt, context_prefix, 2); + SHAKE256_absorb(&shake256_ctxt, context, context_len); + SHAKE256_absorb(&shake256_ctxt, msg, msg_len); + SHAKE256_squeeze(&shake256_ctxt, out_mu, K_MU_BYTES); + SHAKE256_free(&shake256_ctxt); +} diff --git a/third_party/embedpqc/mldsa_mu.h b/third_party/embedpqc/mldsa_mu.h new file mode 100644 index 0000000000000..d68753768ab86 --- /dev/null +++ b/third_party/embedpqc/mldsa_mu.h @@ -0,0 +1,41 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA_MU_H_ +#define OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA_MU_H_ + +#include +#include + +#define K_MU_BYTES 64 +#define K_TR_BYTES 64 + +// Computes the mu value for a given public key, context, and message. +// µ is the "message representative that may optionally be computed in a +// different cryptographic module" as referenced in Algorithm 7, line 6. +void compute_mu(uint8_t out_mu[K_MU_BYTES], const uint8_t *encoded_public_key, + size_t encoded_public_key_len, const uint8_t *context, + uint8_t context_len, const uint8_t *msg, size_t msg_len); + +// As `compute_mu`, except using the `public_key_hash` (`tr` in FIPS 204), +// which is computed as SHAKE256_512(encoded_public_key). +// +// It is safe for `out_mu` and `public_key_hash` to be the same buffer. +void compute_mu_from_public_key_hash(uint8_t out_mu[K_MU_BYTES], + const uint8_t public_key_hash[K_TR_BYTES], + const uint8_t *context, + uint8_t context_len, const uint8_t *msg, + size_t msg_len); + +#endif // OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA_MU_H_ diff --git a/third_party/embedpqc/mldsa_tiny_common.c b/third_party/embedpqc/mldsa_tiny_common.c new file mode 100644 index 0000000000000..396b38c67e72e --- /dev/null +++ b/third_party/embedpqc/mldsa_tiny_common.c @@ -0,0 +1,821 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "third_party/embedpqc/mldsa_tiny_common.h" + +#include +#include +#include + +#include "third_party/embedpqc/ct.h" +#include "third_party/embedpqc/shake.h" + +/* Arithmetic. */ + +static const uint32_t kNTTRootsMontgomery[K_DEGREE] = { + 4193792, 25847, 5771523, 7861508, 237124, 7602457, 7504169, 466468, + 1826347, 2353451, 8021166, 6288512, 3119733, 5495562, 3111497, 2680103, + 2725464, 1024112, 7300517, 3585928, 7830929, 7260833, 2619752, 6271868, + 6262231, 4520680, 6980856, 5102745, 1757237, 8360995, 4010497, 280005, + 2706023, 95776, 3077325, 3530437, 6718724, 4788269, 5842901, 3915439, + 4519302, 5336701, 3574422, 5512770, 3539968, 8079950, 2348700, 7841118, + 6681150, 6736599, 3505694, 4558682, 3507263, 6239768, 6779997, 3699596, + 811944, 531354, 954230, 3881043, 3900724, 5823537, 2071892, 5582638, + 4450022, 6851714, 4702672, 5339162, 6927966, 3475950, 2176455, 6795196, + 7122806, 1939314, 4296819, 7380215, 5190273, 5223087, 4747489, 126922, + 3412210, 7396998, 2147896, 2715295, 5412772, 4686924, 7969390, 5903370, + 7709315, 7151892, 8357436, 7072248, 7998430, 1349076, 1852771, 6949987, + 5037034, 264944, 508951, 3097992, 44288, 7280319, 904516, 3958618, + 4656075, 8371839, 1653064, 5130689, 2389356, 8169440, 759969, 7063561, + 189548, 4827145, 3159746, 6529015, 5971092, 8202977, 1315589, 1341330, + 1285669, 6795489, 7567685, 6940675, 5361315, 4499357, 4751448, 3839961, + 2091667, 3407706, 2316500, 3817976, 5037939, 2244091, 5933984, 4817955, + 266997, 2434439, 7144689, 3513181, 4860065, 4621053, 7183191, 5187039, + 900702, 1859098, 909542, 819034, 495491, 6767243, 8337157, 7857917, + 7725090, 5257975, 2031748, 3207046, 4823422, 7855319, 7611795, 4784579, + 342297, 286988, 5942594, 4108315, 3437287, 5038140, 1735879, 203044, + 2842341, 2691481, 5790267, 1265009, 4055324, 1247620, 2486353, 1595974, + 4613401, 1250494, 2635921, 4832145, 5386378, 1869119, 1903435, 7329447, + 7047359, 1237275, 5062207, 6950192, 7929317, 1312455, 3306115, 6417775, + 7100756, 1917081, 5834105, 7005614, 1500165, 777191, 2235880, 3406031, + 7838005, 5548557, 6709241, 6533464, 5796124, 4656147, 594136, 4603424, + 6366809, 2432395, 2454455, 8215696, 1957272, 3369112, 185531, 7173032, + 5196991, 162844, 1616392, 3014001, 810149, 1652634, 4686184, 6581310, + 5341501, 3523897, 3866901, 269760, 2213111, 7404533, 1717735, 472078, + 7953734, 1723600, 6577327, 1910376, 6712985, 7276084, 8119771, 4546524, + 5441381, 6144432, 7959518, 6094090, 183443, 7403526, 1612842, 4834730, + 7826001, 3919660, 8332111, 7018208, 3937738, 1400424, 7534263, 1976782}; + +// Reduces x mod K_PRIME in constant time, where 0 <= x < 2*K_PRIME. +uint32_t reduce_once(uint32_t x) { + // return x < K_PRIME ? x : x - K_PRIME; + return ct_if(ct_lt(x, K_PRIME), x, x - K_PRIME); +} + +// Returns the absolute value in constant time. +static uint32_t abs_signed(uint32_t x) { + // return is_positive(x) ? x : -x; + return ct_if(ct_lt(x, 0x80000000), x, 0u - x); +} + +// Returns the absolute value modulo K_PRIME. +static uint32_t abs_mod_prime(uint32_t x) { + // return x > K_HALF_PRIME ? K_PRIME - x : x; + return ct_if(ct_lt(K_HALF_PRIME, x), K_PRIME - x, x); +} + +// Returns the maximum of two values in constant time. +static uint32_t maximum(uint32_t x, uint32_t y) { + // return x < y ? y : x; + return ct_if(ct_lt(x, y), y, x); +} + +uint32_t mod_sub(uint32_t a, uint32_t b) { + return reduce_once(K_PRIME + a - b); +} + +void scalar_add(scalar_t *out, const scalar_t *lhs, const scalar_t *rhs) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); + } +} + +void scalar_sub(scalar_t *out, const scalar_t *lhs, const scalar_t *rhs) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = mod_sub(lhs->c[i], rhs->c[i]); + } +} + +static uint32_t mul_montgomery(uint32_t x, uint32_t y) { + uint64_t z = ct_mul64(x, y); +#if defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_7M__) + uint32_t z_lo = (uint32_t)z; + uint64_t a = -(z_lo << 26) + (z_lo << 23) - (z_lo << 13) - z_lo; + uint64_t b = z + (a << 23) - (a << 13) + a; +#else + uint32_t a = (uint32_t)ct_mul64((uint32_t)z, K_PRIME_NEG_INVERSE); + uint64_t b = z + ct_mul64(a, K_PRIME); +#endif + uint32_t c = b >> 32; + return reduce_once(c); +} + +// Multiply two scalars in NTT form. +void scalar_mul(scalar_t *out, const scalar_t *lhs, const scalar_t *rhs) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = mul_montgomery(lhs->c[i], rhs->c[i]); + } +} + +// For scalars a, b, c in NTT form, compute a + b * c. +void scalar_mul_add(scalar_t *out, const scalar_t *a, const scalar_t *b, + const scalar_t *c) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = reduce_once(a->c[i] + mul_montgomery(b->c[i], c->c[i])); + } +} + +// In place number theoretic transform of a given scalar. +// +// FIPS 204, Algorithm 41 (`NTT`). +void scalar_ntt(scalar_t *s) { + // Step: 1, 2, 4, 8, ..., 128 + // Offset: 128, 64, 32, 16, ..., 1 + int offset = K_DEGREE; + for (int step = 1; step < K_DEGREE; step <<= 1) { + offset >>= 1; + int k = 0; + for (int i = 0; i < step; i++) { + const uint32_t step_root = kNTTRootsMontgomery[step + i]; + for (int j = k; j < k + offset; j++) { + uint32_t even = s->c[j]; + // |mul_montgomery| works on values up to K_PRIME*R and R > + // 2*K_PRIME. |step_root| < K_PRIME because it's static data. + // |s->c[...]| is < K_PRIME by the invariants of that struct. + uint32_t odd = mul_montgomery(step_root, s->c[j + offset]); + s->c[j] = reduce_once(odd + even); + s->c[j + offset] = mod_sub(even, odd); + } + k += 2 * offset; + } + } +} + +// In place inverse number theoretic transform of a given scalar. +// +// FIPS 204, Algorithm 42 (`NTT^-1`). +void scalar_inverse_ntt(scalar_t *s) { + // Step: 128, 64, 32, 16, ..., 1 + // Offset: 1, 2, 4, 8, ..., 128 + int step = K_DEGREE; + for (int offset = 1; offset < K_DEGREE; offset <<= 1) { + step >>= 1; + int k = 0; + for (int i = 0; i < step; i++) { + const uint32_t step_root = + K_PRIME - kNTTRootsMontgomery[step + (step - 1 - i)]; + for (int j = k; j < k + offset; j++) { + uint32_t even = s->c[j]; + uint32_t odd = s->c[j + offset]; + s->c[j] = reduce_once(odd + even); + // |mul_montgomery| works on values up to K_PRIME*R and R > + // 2*K_PRIME. K_PRIME + even < 2*K_PRIME because |even| < K_PRIME, by + // the invariants of that structure. Thus K_PRIME + even - odd < + // 2*K_PRIME because odd >= 0, because it's unsigned and less than + // K_PRIME. Lastly step_root < K_PRIME, because |kNTTRootsMontgomery| is + // static data. + s->c[j + offset] = mul_montgomery(step_root, K_PRIME + even - odd); + } + k += 2 * offset; + } + } + for (size_t i = 0; i < K_DEGREE; i++) { + s->c[i] = mul_montgomery(s->c[i], K_INVERSE_DEGREE_MONTGOMERY); + } +} + +/* Rounding and hints. */ + +// The input vector contains only zeroes and ones. +size_t scalar_count_ones(const scalar_t *s) { + size_t count = 0; + for (size_t i = 0; i < K_DEGREE; i++) { + count += s->c[i]; + } + return count; +} + +// FIPS 204, Algorithm 35 (`Power2Round`). +void power2_round(uint32_t *r1, uint32_t *r0, uint32_t r) { + *r1 = r >> K_DROPPED_BITS; + *r0 = r - (*r1 << K_DROPPED_BITS); + + uint32_t r0_adjusted = mod_sub(*r0, 1 << K_DROPPED_BITS); + uint32_t r1_adjusted = *r1 + 1; + + // Mask is set iff r0 > 2^(dropped_bits - 1). + ct_uint32_t cond = ct_lt((uint32_t)(1 << (K_DROPPED_BITS - 1)), *r0); + // r0 = cond ? r0_adjusted : r0 + *r0 = ct_if(cond, r0_adjusted, *r0); + // r1 = cond ? r1_adjusted : r1 + *r1 = ct_if(cond, r1_adjusted, *r1); +} + +// Scale back previously rounded value. +void scale_power2_round(uint32_t *out, uint32_t r1) { + // Pre-condition: 0 <= r1 <= 2^10 - 1 + *out = r1 << K_DROPPED_BITS; + // Post-condition: 0 <= out <= 2^23 - 2^13 = K_PRIME - 1 +} + +void scalar_power2_round(scalar_t *s1, scalar_t *s0, const scalar_t *s) { + for (size_t i = 0; i < K_DEGREE; i++) { + power2_round(&s1->c[i], &s0->c[i], s->c[i]); + } +} + +void scalar_scale_power2_round(scalar_t *out, const scalar_t *in) { + for (size_t i = 0; i < K_DEGREE; i++) { + scale_power2_round(&out->c[i], in->c[i]); + } +} + +void scalar_max(uint32_t *max, const scalar_t *s) { + for (size_t i = 0; i < K_DEGREE; i++) { + uint32_t abs = abs_mod_prime(s->c[i]); + *max = maximum(*max, abs); + } +} + +void scalar_max_signed(uint32_t *max, const scalar_t *s) { + for (size_t i = 0; i < K_DEGREE; i++) { + uint32_t abs = abs_signed(s->c[i]); + *max = maximum(*max, abs); + } +} + +// FIPS 204, Algorithm 37 (`HighBits`), specialized to γ2 = (q - 1)/32. +static uint32_t high_bits_32(uint32_t x) { + // Reference description (given 0 <= x < q): + // + // ``` + // int32_t r0 = x mod+- (2 * gamma2); + // if (x - r0 == q - 1) { + // return 0; + // } else { + // return (x - r0) / (2 * gamma2); + // } + // ``` + // + // Below is the formula taken from the reference implementation. + // + // Here, gamma2 == 2^18 - 2^8 + // This returns ((ceil(x / 2^7) * (2^10 + 1) + 2^21) / 2^22) mod 2^4 + uint32_t r1 = (x + 127) >> 7; + r1 = (r1 * 1025 + (1 << 21)) >> 22; + r1 &= 15; + return r1; +} + +// FIPS 204, Algorithm 37 (`HighBits`), specialized to γ2 = (q - 1)/88. +static uint32_t high_bits_88(uint32_t x) { + // Reference description (given 0 <= x < q): + // + // ``` + // int32_t r0 = x mod+- (2 * gamma2); + // if (x - r0 == q - 1) { + // return 0; + // } else { + // return (x - r0) / (2 * gamma2); + // } + // ``` + // + // Below is the formula taken from the reference implementation. + // + uint32_t r1 = (x + 127) >> 7; + r1 = (r1 * 11275 + (1 << 23)) >> 24; + r1 ^= (uint32_t)((43 - (int32_t)r1) >> 31) & r1; + return r1; +} + +// FIPS 204, Algorithm 36 (`Decompose`), specialized to γ2 = (q - 1)/32. +static void decompose_32(uint32_t *r1, int32_t *r0, uint32_t r) { + *r1 = high_bits_32(r); + + *r0 = (int32_t)r; + *r0 -= *r1 * 2 * ((K_PRIME - 1) / 32); + *r0 -= (((int32_t)K_HALF_PRIME - *r0) >> 31) & (int32_t)K_PRIME; +} + +// FIPS 204, Algorithm 36 (`Decompose`), specialized to γ2 = (q - 1)/88. +static void decompose_88(uint32_t *r1, int32_t *r0, uint32_t r) { + *r1 = high_bits_88(r); + + *r0 = (int32_t)r; + *r0 -= *r1 * 2 * ((K_PRIME - 1) / 88); + *r0 -= (((int32_t)K_HALF_PRIME - *r0) >> 31) & (int32_t)K_PRIME; +} + +// FIPS 204, Algorithm 38 (`LowBits`), specialized to γ2 = (q - 1)/32. +static int32_t low_bits_32(uint32_t x) { + uint32_t r1; + int32_t r0; + decompose_32(&r1, &r0, x); + return r0; +} + +// FIPS 204, Algorithm 38 (`LowBits`), specialized to γ2 = (q - 1)/88. +static int32_t low_bits_88(uint32_t x) { + uint32_t r1; + int32_t r0; + decompose_88(&r1, &r0, x); + return r0; +} + +// FIPS 204, Algorithm 39 (`MakeHint`), specialized to γ2 = (q - 1)/32. +// +// In the spec this takes two arguments, z and r, and is called with +// z = -ct0 +// r = w - cs2 + ct0 +// +// It then computes HighBits (algorithm 37) of z and z+r. But z+r is just w - +// cs2, so this takes three arguments and saves an addition. +static uint32_t make_hint_32(uint32_t ct0, uint32_t cs2, uint32_t w) { + uint32_t r_plus_z = mod_sub(w, cs2); + uint32_t r = reduce_once(r_plus_z + ct0); + return high_bits_32(r) != high_bits_32(r_plus_z); +} + +// FIPS 204, Algorithm 39 (`MakeHint`), specialized to γ2 = (q - 1)/88. +static uint32_t make_hint_88(uint32_t ct0, uint32_t cs2, uint32_t w) { + uint32_t r_plus_z = mod_sub(w, cs2); + uint32_t r = reduce_once(r_plus_z + ct0); + return high_bits_88(r) != high_bits_88(r_plus_z); +} + +static uint32_t use_hint_32(uint32_t h, uint32_t r) { + uint32_t r1; + int32_t r0; + decompose_32(&r1, &r0, r); + + if (h) { + if (r0 > 0) { + // m = 16, thus |mod m| in the spec turns into |& 15|. + return (r1 + 1) & 15; + } else { + return (r1 - 1) & 15; + } + } + return r1; +} + +static uint32_t use_hint_88(uint32_t h, uint32_t r) { + uint32_t r1; + int32_t r0; + decompose_88(&r1, &r0, r); + + if (h) { + // m = 44 + if (r0 > 0) { + if (r1 == 43) { + return 0; + } else { + return r1 + 1; + } + } else { + if (r1 == 0) { + return 43; + } else { + return r1 - 1; + } + } + } + return r1; +} + +void scalar_high_bits_32(scalar_t *out, const scalar_t *in) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = high_bits_32(in->c[i]); + } +} + +void scalar_high_bits_88(scalar_t *out, const scalar_t *in) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = high_bits_88(in->c[i]); + } +} + +void scalar_low_bits_32(scalar_t *out, const scalar_t *in) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = (uint32_t)low_bits_32(in->c[i]); + } +} + +void scalar_low_bits_88(scalar_t *out, const scalar_t *in) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = (uint32_t)low_bits_88(in->c[i]); + } +} + +void scalar_make_hint_32(scalar_t *out, const scalar_t *ct0, + const scalar_t *cs2, const scalar_t *w) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = make_hint_32(ct0->c[i], cs2->c[i], w->c[i]); + } +} + +void scalar_make_hint_88(scalar_t *out, const scalar_t *ct0, + const scalar_t *cs2, const scalar_t *w) { + for (size_t i = 0; i < K_DEGREE; i++) { + out->c[i] = make_hint_88(ct0->c[i], cs2->c[i], w->c[i]); + } +} + +// FIPS 204, Algorithm 40 (`UseHint`), specialized to γ2 = (q - 1)/32. +// Note: `num_hints` is assumed to be less than or equal to the length of +// `h_bytes`. +void scalar_use_hint_32(scalar_t *out, const uint8_t *h_bytes, + uint8_t num_hints, const scalar_t *r) { + for (size_t i = 0; i < K_DEGREE; i++) { + int in_list = 0; + for (size_t j = 0; j < num_hints; j++) { + if (h_bytes[j] == i) { + in_list = 1; + break; + } + } + if (in_list) { + out->c[i] = use_hint_32(1, r->c[i]); + } else { + out->c[i] = use_hint_32(0, r->c[i]); + } + } +} + +// FIPS 204, Algorithm 40 (`UseHint`), specialized to γ2 = (q - 1)/88. +// Note: `num_hints` is assumed to be less than or equal to the length of +// `h_bytes`. +void scalar_use_hint_88(scalar_t *out, const uint8_t *h_bytes, + uint8_t num_hints, const scalar_t *r) { + for (size_t i = 0; i < K_DEGREE; i++) { + int in_list = 0; + for (size_t j = 0; j < num_hints; j++) { + if (h_bytes[j] == i) { + in_list = 1; + break; + } + } + if (in_list) { + out->c[i] = use_hint_88(1, r->c[i]); + } else { + out->c[i] = use_hint_88(0, r->c[i]); + } + } +} + +/* Bit packing. */ + +// FIPS 204, Algorithm 16 (`SimpleBitPack`). Specialized to bitlen(b) = 4. +void scalar_encode_4(uint8_t out[128], const scalar_t *s) { + for (size_t i = 0; i < K_DEGREE / 2; i++) { + uint32_t a = s->c[2 * i]; + uint32_t b = s->c[2 * i + 1]; + out[i] = (uint8_t)(a | (b << 4)); + } +} + +// FIPS 204, Algorithm 16 (`SimpleBitPack`). Specialized to bitlen(b) = 6. +void scalar_encode_6(uint8_t out[192], const scalar_t *s) { + for (int i = 0; i < K_DEGREE / 4; i++) { + uint32_t a = s->c[4 * i]; + uint32_t b = s->c[4 * i + 1]; + uint32_t c = s->c[4 * i + 2]; + uint32_t d = s->c[4 * i + 3]; + out[3 * i] = (uint8_t)(a | (b << 6)); + out[3 * i + 1] = (uint8_t)((b >> 2) | (c << 4)); + out[3 * i + 2] = (uint8_t)((c >> 4) | (d << 2)); + } +} + +// FIPS 204, Algorithm 16 (`SimpleBitPack`). Specialized to bitlen(b) = 10. +void scalar_encode_10(uint8_t out[320], const scalar_t *s) { + for (size_t i = 0; i < K_DEGREE / 4; i++) { + uint32_t a = s->c[4 * i]; + uint32_t b = s->c[4 * i + 1]; + uint32_t c = s->c[4 * i + 2]; + uint32_t d = s->c[4 * i + 3]; + out[5 * i] = (uint8_t)a; + out[5 * i + 1] = (uint8_t)((a >> 8) | (b << 2)); + out[5 * i + 2] = (uint8_t)((b >> 6) | (c << 4)); + out[5 * i + 3] = (uint8_t)((c >> 4) | (d << 6)); + out[5 * i + 4] = (uint8_t)(d >> 2); + } +} + +// FIPS 204, Algorithm 17 (`BitPack`). Specialized to bitlen(a+b) = 18 and b = +// 2^17. +void scalar_encode_signed_18_17(uint8_t out[576], const scalar_t *s) { + static const uint32_t kMax = 1u << 17; + for (int i = 0; i < K_DEGREE / 4; i++) { + uint32_t a = mod_sub(kMax, s->c[4 * i]); + uint32_t b = mod_sub(kMax, s->c[4 * i + 1]); + uint32_t c = mod_sub(kMax, s->c[4 * i + 2]); + uint32_t d = mod_sub(kMax, s->c[4 * i + 3]); + out[9 * i] = (uint8_t)a; + out[9 * i + 1] = (uint8_t)(a >> 8); + out[9 * i + 2] = (uint8_t)(a >> 16) | (uint8_t)(b << 2); + out[9 * i + 3] = (uint8_t)(b >> 6); + out[9 * i + 4] = (uint8_t)(b >> 14) | (uint8_t)(c << 4); + out[9 * i + 5] = (uint8_t)(c >> 4); + out[9 * i + 6] = (uint8_t)(c >> 12) | (uint8_t)(d << 6); + out[9 * i + 7] = (uint8_t)(d >> 2); + out[9 * i + 8] = (uint8_t)(d >> 10); + } +} + +// FIPS 204, Algorithm 17 (`BitPack`). Specialized to bitlen(a+b) = 20 and b = +// 2^19. +void scalar_encode_signed_20_19(uint8_t out[640], const scalar_t *s) { + const uint32_t kMax = 1u << 19; + for (size_t i = 0; i < K_DEGREE / 4; i++) { + uint32_t a = mod_sub(kMax, s->c[4 * i]); + uint32_t b = mod_sub(kMax, s->c[4 * i + 1]); + uint32_t c = mod_sub(kMax, s->c[4 * i + 2]); + uint32_t d = mod_sub(kMax, s->c[4 * i + 3]); + a |= b << 20; + b >>= 12; + b |= c << 8; + b |= d << 28; + d >>= 4; + memcpy(&out[10 * i], &a, sizeof(a)); + memcpy(&out[10 * i + 4], &b, sizeof(b)); + memcpy(&out[10 * i + 8], &d, 2); + } +} + +// FIPS 204, Algorithm 18 (`SimpleBitUnpack`). Specialized for bitlen(b) == 10. +void scalar_decode_10(scalar_t *out, const uint8_t in[320]) { + uint32_t v; + for (size_t i = 0; i < K_DEGREE / 4; i++) { + memcpy(&v, &in[5 * i], sizeof(v)); + out->c[4 * i] = v & 0x3FF; + out->c[4 * i + 1] = (v >> 10) & 0x3FF; + out->c[4 * i + 2] = (v >> 20) & 0x3FF; + out->c[4 * i + 3] = (v >> 30) | (((uint32_t)in[5 * i + 4]) << 2); + } +} + +// FIPS 204, Algorithm 19 (`BitUnpack`). Specialized to bitlen(a+b) = 18 and b = +// 2^17. +void scalar_decode_signed_18_17(scalar_t *out, const uint8_t in[576]) { + static const uint32_t kMax = 1u << 17; + + for (int i = 0; i < K_DEGREE / 4; i++) { + uint32_t a = (uint32_t)in[9 * i] | ((uint32_t)in[9 * i + 1] << 8) | + (((uint32_t)in[9 * i + 2] & 0x3) << 16); + uint32_t b = ((uint32_t)in[9 * i + 2] >> 2) | + ((uint32_t)in[9 * i + 3] << 6) | + (((uint32_t)in[9 * i + 4] & 0xf) << 14); + uint32_t c = ((uint32_t)in[9 * i + 4] >> 4) | + ((uint32_t)in[9 * i + 5] << 4) | + (((uint32_t)in[9 * i + 6] & 0x3f) << 12); + uint32_t d = ((uint32_t)in[9 * i + 6] >> 6) | + ((uint32_t)in[9 * i + 7] << 2) | + ((uint32_t)in[9 * i + 8] << 10); + + out->c[i * 4] = mod_sub(kMax, a); + out->c[i * 4 + 1] = mod_sub(kMax, b); + out->c[i * 4 + 2] = mod_sub(kMax, c); + out->c[i * 4 + 3] = mod_sub(kMax, d); + } +} + +// FIPS 204, Algorithm 19 (`BitUnpack`). Specialized to bitlen(a+b) = 20 and b = +// 2^19. +void scalar_decode_signed_20_19(scalar_t *out, const uint8_t in[640]) { + const uint32_t kMax = 1u << 19; + const uint32_t k20Bits = (1u << 20) - 1; + + uint32_t a, b; + uint16_t c; + for (size_t i = 0; i < K_DEGREE / 4; i++) { + memcpy(&a, &in[10 * i], sizeof(a)); + memcpy(&b, &in[10 * i + 4], sizeof(b)); + memcpy(&c, &in[10 * i + 8], sizeof(c)); + + // It's not possible for a 20-bit number to be out of range when the max is + // 2^19. + out->c[i * 4] = mod_sub(kMax, a & k20Bits); + out->c[i * 4 + 1] = mod_sub(kMax, (a >> 20) | ((b & 0xFF) << 12)); + out->c[i * 4 + 2] = mod_sub(kMax, (b >> 8) & k20Bits); + out->c[i * 4 + 3] = mod_sub(kMax, (b >> 28) | ((uint32_t)c) << 4); + } +} + +/* Expansion functions. */ + +// FIPS 204, Algorithm 29 (`SampleInBall`). +void scalar_sample_in_ball_vartime(size_t tau, scalar_t *out, + const uint8_t *seed, size_t len) { + shake256_ctxt_t shake256_ctxt; + SHAKE256_init(&shake256_ctxt); + SHAKE256_absorb(&shake256_ctxt, seed, len); + + uint8_t block[136]; + SHAKE256_squeeze(&shake256_ctxt, block, sizeof(block)); + + uint64_t signs; + memcpy(&signs, block, sizeof(signs)); + int offset = 8; + + // SampleInBall implements a Fisher–Yates shuffle, which unavoidably leaks + // where the zeros are by memory access pattern. Although this leak happens + // before bad signatures are rejected, this is safe. See + // https://boringssl-review.googlesource.com/c/boringssl/+/67747/comment/8d8f01ac_70af3f21/ + + memset(out, 0, sizeof(*out)); + for (size_t i = K_DEGREE - tau; i < K_DEGREE; i++) { + size_t byte; + for (;;) { + if (offset == 136) { + SHAKE256_squeeze(&shake256_ctxt, block, sizeof(block)); + offset = 0; + } + + byte = block[offset++]; + if (byte <= i) { + break; + } + } + + out->c[i] = out->c[byte]; + out->c[byte] = mod_sub(1, 2 * (signs & 1)); + signs >>= 1; + } + + SHAKE256_free(&shake256_ctxt); +} + +// FIPS 204, Algorithm 30 (`RejNTTPoly`). +// +// Rejection samples a Keccak stream to get uniformly distributed elements. This +// is used for matrix expansion and only operates on public inputs. +void scalar_from_keccak_vartime(scalar_t *out, + const uint8_t derived_seed[K_RHO_BYTES + 2]) { + shake128_ctxt_t shake128_ctxt; + SHAKE128_init(&shake128_ctxt); + SHAKE128_absorb(&shake128_ctxt, derived_seed, K_RHO_BYTES + 2); + + int done = 0; + while (done < K_DEGREE) { + uint8_t block[168]; + SHAKE128_squeeze(&shake128_ctxt, block, sizeof(block)); + for (size_t i = 0; i < sizeof(block) && done < K_DEGREE; i += 3) { + // FIPS 204, Algorithm 14 (`CoeffFromThreeBytes`). + uint32_t value = (uint32_t)block[i] | ((uint32_t)block[i + 1] << 8) | + (((uint32_t)block[i + 2] & 0x7F) << 16); + if (value < K_PRIME) { + out->c[done++] = value; + } + } + } + + SHAKE128_free(&shake128_ctxt); +} + +static int coefficient_from_nibble_2(uint32_t nibble, uint32_t *result) { + if (nibble < 15) { + // Constant time "nibble % 5". + nibble = nibble - 5 * ((205 * nibble) >> 10); + *result = mod_sub(2, nibble); + return 1; + } + return 0; +} + +static int coefficient_from_nibble_4(uint32_t nibble, uint32_t *result) { + if (nibble < 9) { + *result = mod_sub(4, nibble); + return 1; + } + return 0; +} + +// FIPS 204, Algorithm 31 (`RejBoundedPoly`), specialized to η = 2. +void scalar_uniform_2(scalar_t *out, + const uint8_t derived_seed[K_SIGMA_BYTES + 2]) { + shake256_ctxt_t shake256_ctxt; + SHAKE256_init(&shake256_ctxt); + SHAKE256_absorb(&shake256_ctxt, derived_seed, K_SIGMA_BYTES + 2); + + int done = 0; + while (done < K_DEGREE) { + uint8_t block[136]; + SHAKE256_squeeze(&shake256_ctxt, block, sizeof(block)); + for (size_t i = 0; i < sizeof(block) && done < K_DEGREE; ++i) { + uint32_t t0 = block[i] & 0x0F; + uint32_t t1 = block[i] >> 4; + // FIPS 204, Algorithm 15 (`CoefFromHalfByte`). Although both the input + // and output here are secret, it is OK to leak when we rejected a byte. + // Individual bytes of the SHAKE-256 stream are (indistinguishable from) + // independent of each other and the original seed, so leaking information + // about the rejected bytes does not reveal the input or output. + uint32_t v; + if (coefficient_from_nibble_2(t0, &v)) { + out->c[done++] = v; + } + if (done < K_DEGREE && coefficient_from_nibble_2(t1, &v)) { + out->c[done++] = v; + } + } + } + + SHAKE256_free(&shake256_ctxt); +} + +// FIPS 204, Algorithm 31 (`RejBoundedPoly`), specialized to η = 4. +void scalar_uniform_4(scalar_t *out, + const uint8_t derived_seed[K_SIGMA_BYTES + 2]) { + shake256_ctxt_t shake256_ctxt; + SHAKE256_init(&shake256_ctxt); + SHAKE256_absorb(&shake256_ctxt, derived_seed, K_SIGMA_BYTES + 2); + + int done = 0; + while (done < K_DEGREE) { + uint8_t block[136]; + SHAKE256_squeeze(&shake256_ctxt, block, sizeof(block)); + for (size_t i = 0; i < sizeof(block) && done < K_DEGREE; ++i) { + uint32_t t0 = block[i] & 0x0F; + uint32_t t1 = block[i] >> 4; + // FIPS 204, Algorithm 15 (`CoefFromHalfByte`). Although both the input + // and output here are secret, it is OK to leak when we rejected a byte. + // Individual bytes of the SHAKE-256 stream are (indistinguishable from) + // independent of each other and the original seed, so leaking information + // about the rejected bytes does not reveal the input or output. + uint32_t v; + if (coefficient_from_nibble_4(t0, &v)) { + out->c[done++] = v; + } + if (done < K_DEGREE && coefficient_from_nibble_4(t1, &v)) { + out->c[done++] = v; + } + } + } + + SHAKE256_free(&shake256_ctxt); +} + +// FIPS 204, Algorithm 33 (`ExpandS`), specialized to η = 2. +void expand_scalar_2(scalar_t *out, const uint8_t sigma[K_SIGMA_BYTES], + size_t i) { + uint8_t derived_seed[K_SIGMA_BYTES + 2]; + memcpy(derived_seed, sigma, K_SIGMA_BYTES); + derived_seed[K_SIGMA_BYTES] = (uint8_t)i; + derived_seed[K_SIGMA_BYTES + 1] = 0; + scalar_uniform_2(out, derived_seed); +} + +// FIPS 204, Algorithm 33 (`ExpandS`), specialized to η = 4. +void expand_scalar_4(scalar_t *out, const uint8_t sigma[K_SIGMA_BYTES], + size_t i) { + uint8_t derived_seed[K_SIGMA_BYTES + 2]; + memcpy(derived_seed, sigma, K_SIGMA_BYTES); + derived_seed[K_SIGMA_BYTES] = (uint8_t)i; + derived_seed[K_SIGMA_BYTES + 1] = 0; + scalar_uniform_4(out, derived_seed); +} + +// FIPS 204, Algorithm 34 (`ExpandMask`), but just a single step. +// Specialized to γ1 = 17. +static void scalar_sample_mask_17( + scalar_t *out, const uint8_t derived_seed[K_RHO_PRIME_BYTES + 2]) { + uint8_t buf[576]; + SHAKE256_buffer(buf, sizeof(buf), derived_seed, K_RHO_PRIME_BYTES + 2); + + scalar_decode_signed_18_17(out, buf); +} + +// FIPS 204, Algorithm 34 (`ExpandMask`) for a single scalar. +// Specialized to γ1 = 17. +void scalar_expand_mask_17(scalar_t *out, const uint8_t seed[K_RHO_PRIME_BYTES], + size_t kappa, size_t i) { + uint8_t derived_seed[K_RHO_PRIME_BYTES + 2]; + memcpy(derived_seed, seed, K_RHO_PRIME_BYTES); + size_t index = kappa + i; + derived_seed[K_RHO_PRIME_BYTES] = index & 0xFF; + derived_seed[K_RHO_PRIME_BYTES + 1] = (index >> 8) & 0xFF; + scalar_sample_mask_17(out, derived_seed); +} + +// FIPS 204, Algorithm 34 (`ExpandMask`), but just a single step. +// Specialized to γ1 = 19. +static void scalar_sample_mask_19( + scalar_t *out, const uint8_t derived_seed[K_RHO_PRIME_BYTES + 2]) { + uint8_t buf[640]; + SHAKE256_buffer(buf, sizeof(buf), derived_seed, K_RHO_PRIME_BYTES + 2); + + scalar_decode_signed_20_19(out, buf); +} + +// FIPS 204, Algorithm 34 (`ExpandMask`). +// Specialized to γ1 = 19. +void scalar_expand_mask_19(scalar_t *out, const uint8_t seed[K_RHO_PRIME_BYTES], + size_t kappa, size_t i) { + uint8_t derived_seed[K_RHO_PRIME_BYTES + 2]; + memcpy(derived_seed, seed, K_RHO_PRIME_BYTES); + size_t index = kappa + i; + derived_seed[K_RHO_PRIME_BYTES] = index & 0xFF; + derived_seed[K_RHO_PRIME_BYTES + 1] = (index >> 8) & 0xFF; + scalar_sample_mask_19(out, derived_seed); +} diff --git a/third_party/embedpqc/mldsa_tiny_common.h b/third_party/embedpqc/mldsa_tiny_common.h new file mode 100644 index 0000000000000..a762dae2e0a30 --- /dev/null +++ b/third_party/embedpqc/mldsa_tiny_common.h @@ -0,0 +1,183 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA_TINY_COMMON_H_ +#define OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA_TINY_COMMON_H_ + +#include +#include + +/* Arithmetic parameters. */ + +// 2^23 - 2^13 + 1 +#define K_PRIME 8380417 +// Inverse of -K_PRIME modulo 2^32 +#define K_PRIME_NEG_INVERSE 4236238847 +#define K_DROPPED_BITS 13 +#define K_HALF_PRIME ((K_PRIME - 1) / 2) +#define K_DEGREE 256 +// 256^-1 mod K_PRIME, in Montgomery form. +#define K_INVERSE_DEGREE_MONTGOMERY 41978 + +/* Common sizes. */ + +#define K_RHO_BYTES 32 +#define K_SIGMA_BYTES 64 +#define K_K_BYTES 32 +#define K_RHO_PRIME_BYTES 64 + +/* Fundamental types. */ + +typedef struct { + uint32_t c[K_DEGREE]; +} scalar_t; + +/* Arithmetic. */ + +// Reduces x mod K_PRIME in constant time, where 0 <= x < 2*K_PRIME. +uint32_t reduce_once(uint32_t x); + +uint32_t mod_sub(uint32_t a, uint32_t b); + +void scalar_add(scalar_t *out, const scalar_t *lhs, const scalar_t *rhs); + +void scalar_sub(scalar_t *out, const scalar_t *lhs, const scalar_t *rhs); + +// Multiply two scalars in NTT form. +void scalar_mul(scalar_t *out, const scalar_t *lhs, const scalar_t *rhs); + +// For scalars a, b, c in NTT form, compute a + b * c. +void scalar_mul_add(scalar_t *out, const scalar_t *a, const scalar_t *b, + const scalar_t *c); + +// In place number theoretic transform of a given scalar. +// +// FIPS 204, Algorithm 41 (`NTT`). +void scalar_ntt(scalar_t *s); + +// In place inverse number theoretic transform of a given scalar. +// +// FIPS 204, Algorithm 42 (`NTT^-1`). +void scalar_inverse_ntt(scalar_t *s); + +/* Rounding and hints. */ + +// The input vector contains only zeroes and ones. +size_t scalar_count_ones(const scalar_t *s); + +// FIPS 204, Algorithm 35 (`Power2Round`). +void power2_round(uint32_t *r1, uint32_t *r0, uint32_t r); + +// Scale back previously rounded value. +void scale_power2_round(uint32_t *out, uint32_t r1); + +void scalar_power2_round(scalar_t *s1, scalar_t *s0, const scalar_t *s); + +void scalar_scale_power2_round(scalar_t *out, const scalar_t *in); + +void scalar_max(uint32_t *max, const scalar_t *s); + +void scalar_max_signed(uint32_t *max, const scalar_t *s); + +void scalar_high_bits_32(scalar_t *out, const scalar_t *in); + +void scalar_high_bits_88(scalar_t *out, const scalar_t *in); + +void scalar_low_bits_32(scalar_t *out, const scalar_t *in); + +void scalar_low_bits_88(scalar_t *out, const scalar_t *in); + +void scalar_make_hint_32(scalar_t *out, const scalar_t *ct0, + const scalar_t *cs2, const scalar_t *w); + +void scalar_make_hint_88(scalar_t *out, const scalar_t *ct0, + const scalar_t *cs2, const scalar_t *w); + +// FIPS 204, Algorithm 40 (`UseHint`), specialized to γ2 = (q - 1)/32. +void scalar_use_hint_32(scalar_t *out, const uint8_t *h_bytes, + uint8_t num_hints, const scalar_t *r); + +// FIPS 204, Algorithm 40 (`UseHint`), specialized to γ2 = (q - 1)/88. +void scalar_use_hint_88(scalar_t *out, const uint8_t *h_bytes, + uint8_t num_hints, const scalar_t *r); + +/* Bit packing. */ + +// FIPS 204, Algorithm 16 (`SimpleBitPack`). Specialized to bitlen(b) = 4. +void scalar_encode_4(uint8_t out[128], const scalar_t *s); + +// FIPS 204, Algorithm 16 (`SimpleBitPack`). Specialized to bitlen(b) = 6. +void scalar_encode_6(uint8_t out[192], const scalar_t *s); + +// FIPS 204, Algorithm 16 (`SimpleBitPack`). Specialized to bitlen(b) = 10. +void scalar_encode_10(uint8_t out[320], const scalar_t *s); + +// FIPS 204, Algorithm 17 (`BitPack`). Specialized to bitlen(a+b) = 18 and b = +// 2^17. +void scalar_encode_signed_18_17(uint8_t out[576], const scalar_t *s); + +// FIPS 204, Algorithm 17 (`BitPack`). Specialized to bitlen(a+b) = 20 and b = +// 2^19. +void scalar_encode_signed_20_19(uint8_t out[640], const scalar_t *s); + +// FIPS 204, Algorithm 18 (`SimpleBitUnpack`). Specialized for bitlen(b) +// == 10. +void scalar_decode_10(scalar_t *out, const uint8_t in[320]); + +// FIPS 204, Algorithm 19 (`BitUnpack`). Specialized to bitlen(a+b) = 18 and b = +// 2^17. +void scalar_decode_signed_18_17(scalar_t *out, const uint8_t in[576]); + +// FIPS 204, Algorithm 19 (`BitUnpack`). Specialized to bitlen(a+b) = 20 and b = +// 2^19. +void scalar_decode_signed_20_19(scalar_t *out, const uint8_t in[640]); + +/* Expansion functions. */ + +// FIPS 204, Algorithm 29 (`SampleInBall`). +void scalar_sample_in_ball_vartime(size_t tau, scalar_t *out, + const uint8_t *seed, size_t len); + +// FIPS 204, Algorithm 30 (`RejNTTPoly`). +// +// Rejection samples a Keccak stream to get uniformly distributed elements. +// This is used for matrix expansion and only operates on public inputs. +void scalar_from_keccak_vartime(scalar_t *out, + const uint8_t derived_seed[K_RHO_BYTES + 2]); + +// FIPS 204, Algorithm 31 (`RejBoundedPoly`), specialized to η = 2. +void scalar_uniform_2(scalar_t *out, + const uint8_t derived_seed[K_SIGMA_BYTES + 2]); + +// FIPS 204, Algorithm 31 (`RejBoundedPoly`), specialized to η = 4. +void scalar_uniform_4(scalar_t *out, + const uint8_t derived_seed[K_SIGMA_BYTES + 2]); + +// FIPS 204, Algorithm 33 (`ExpandS`), specialized to η = 2. +void expand_scalar_2(scalar_t *out, const uint8_t sigma[K_SIGMA_BYTES], + size_t i); + +// FIPS 204, Algorithm 33 (`ExpandS`), specialized to η = 4. +void expand_scalar_4(scalar_t *out, const uint8_t sigma[K_SIGMA_BYTES], + size_t i); + +// FIPS 204, Algorithm 34 (`ExpandMask`), specialized to γ1 = 17. +void scalar_expand_mask_17(scalar_t *out, const uint8_t seed[K_RHO_PRIME_BYTES], + size_t kappa, size_t i); + +// FIPS 204, Algorithm 34 (`ExpandMask`), specialized to γ1 = 19. +void scalar_expand_mask_19(scalar_t *out, const uint8_t seed[K_RHO_PRIME_BYTES], + size_t kappa, size_t i); + +#endif // OPENTITAN_THIRD_PARTY_EMBEDPQC_MLDSA_TINY_COMMON_H_ diff --git a/third_party/embedpqc/ports/BUILD b/third_party/embedpqc/ports/BUILD new file mode 100644 index 0000000000000..ad1f1c06eb387 --- /dev/null +++ b/third_party/embedpqc/ports/BUILD @@ -0,0 +1,29 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "shake", + srcs = ["shake.c"], + hdrs = ["shake.h"], + include_prefix = "third_party/embedpqc", + strip_include_prefix = ".", + deps = [ + "//sw/device/lib/base:hardened", + "//sw/device/silicon_creator/lib/drivers:kmac", + ], +) + +cc_library( + name = "mldsa44_tiny_caller", + srcs = [ + "mldsa44_tiny_caller.S", + "mldsa_caller_common.h", + ], + hdrs = ["mldsa44_tiny_caller.h"], + deps = [ + "//third_party/embedpqc:mldsa44_tiny", + ], +) diff --git a/third_party/embedpqc/ports/README.md b/third_party/embedpqc/ports/README.md new file mode 100644 index 0000000000000..7116d6c128a75 --- /dev/null +++ b/third_party/embedpqc/ports/README.md @@ -0,0 +1 @@ +# OpenTitan integration ports for embedpqc diff --git a/third_party/embedpqc/ports/mldsa44_tiny_caller.S b/third_party/embedpqc/ports/mldsa44_tiny_caller.S new file mode 100644 index 0000000000000..1e91015d63760 --- /dev/null +++ b/third_party/embedpqc/ports/mldsa44_tiny_caller.S @@ -0,0 +1,9 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "third_party/embedpqc/ports/mldsa_caller_common.h" + +MLDSA_CALLER_WITH_STACK(mldsa44_tiny_pub_from_seed, a2) +MLDSA_CALLER_WITH_STACK(mldsa44_tiny_sign_deterministic, a4) +MLDSA_CALLER_WITH_STACK(mldsa44_tiny_verify, a4) diff --git a/third_party/embedpqc/ports/mldsa44_tiny_caller.h b/third_party/embedpqc/ports/mldsa44_tiny_caller.h new file mode 100644 index 0000000000000..6d758c3a31f1e --- /dev/null +++ b/third_party/embedpqc/ports/mldsa44_tiny_caller.h @@ -0,0 +1,32 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_MLDSA44_TINY_CALLER_H_ +#define OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_MLDSA44_TINY_CALLER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void mldsa44_tiny_pub_from_seed_with_stack(uint8_t *out_pk, const uint8_t *seed, + void *stack_top); + +void mldsa44_tiny_sign_deterministic_with_stack(uint8_t *sig, + const uint8_t *seed, + const uint8_t *msg, + size_t msg_len, + void *stack_top); + +int mldsa44_tiny_verify_with_stack(const uint8_t *pk, const uint8_t *sig, + const uint8_t *msg, size_t msg_len, + void *stack_top); + +#ifdef __cplusplus +} +#endif + +#endif // OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_MLDSA44_TINY_CALLER_H_ diff --git a/third_party/embedpqc/ports/mldsa_caller_common.h b/third_party/embedpqc/ports/mldsa_caller_common.h new file mode 100644 index 0000000000000..e574c941f5d78 --- /dev/null +++ b/third_party/embedpqc/ports/mldsa_caller_common.h @@ -0,0 +1,25 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_MLDSA_CALLER_COMMON_H_ +#define OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_MLDSA_CALLER_COMMON_H_ + +#define MLDSA_CALLER_WITH_STACK(target_name, stack_reg) \ + .global target_name##_with_stack; \ + .type target_name##_with_stack, @function; \ + .section /**/.text.target_name##_with_stack, "ax", @progbits; \ + target_name##_with_stack:; \ + /* Allocate 16B to maintain 16-byte stack alignment (RV32 ABI). */; \ + /* We save sp (4B) and ra (4B), leaving 8B of padding. */; \ + addi stack_reg, stack_reg, -16; \ + sw sp, 0(stack_reg); \ + sw ra, 4(stack_reg); \ + mv sp, stack_reg; \ + call target_name; \ + lw ra, 4(sp); \ + lw sp, 0(sp); \ + ret; \ + .size target_name##_with_stack, .- target_name##_with_stack + +#endif // OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_MLDSA_CALLER_COMMON_H_ diff --git a/third_party/embedpqc/ports/shake.c b/third_party/embedpqc/ports/shake.c new file mode 100644 index 0000000000000..0e87455f7a062 --- /dev/null +++ b/third_party/embedpqc/ports/shake.c @@ -0,0 +1,85 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "third_party/embedpqc/ports/shake.h" + +#include + +#include "sw/device/lib/base/hardened.h" +#include "sw/device/silicon_creator/lib/drivers/kmac.h" + +static void shake_squeeze_generic(uint8_t *out, size_t out_len, + size_t rate_words) { + if (!kmac_is_squeezing()) { + kmac_shake256_squeeze_start(); + } + + size_t out_words = out_len / 4; + if (kmac_squeeze_words((uint32_t *)out, out_words, rate_words) != kErrorOk) { + HARDENED_TRAP(); + } +} + +void SHAKE128_init(shake128_ctxt_t *ctxt) { + (void)ctxt; + if (kmac_shake128_configure() != kErrorOk) { + HARDENED_TRAP(); + } + if (kmac_shake256_start() != kErrorOk) { + HARDENED_TRAP(); + } +} + +void SHAKE128_absorb(shake128_ctxt_t *ctxt, const uint8_t *in, size_t in_len) { + (void)ctxt; + kmac_shake256_absorb(in, in_len); +} + +void SHAKE128_squeeze(shake128_ctxt_t *ctxt, uint8_t *out, size_t out_len) { + (void)ctxt; + shake_squeeze_generic(out, out_len, 42); +} + +void SHAKE128_free(shake128_ctxt_t *ctxt) { + (void)ctxt; + if (kmac_done() != kErrorOk) { + HARDENED_TRAP(); + } +} + +void SHAKE256_init(shake256_ctxt_t *ctxt) { + (void)ctxt; + if (kmac_shake256_configure() != kErrorOk) { + HARDENED_TRAP(); + } + if (kmac_shake256_start() != kErrorOk) { + HARDENED_TRAP(); + } +} + +void SHAKE256_absorb(shake256_ctxt_t *ctxt, const uint8_t *in, size_t in_len) { + (void)ctxt; + kmac_shake256_absorb(in, in_len); +} + +void SHAKE256_squeeze(shake256_ctxt_t *ctxt, uint8_t *out, size_t out_len) { + (void)ctxt; + shake_squeeze_generic(out, out_len, 34); +} + +void SHAKE256_free(shake256_ctxt_t *ctxt) { + (void)ctxt; + if (kmac_done() != kErrorOk) { + HARDENED_TRAP(); + } +} + +void SHAKE256_buffer(uint8_t *out, size_t out_len, const uint8_t *in, + size_t in_len) { + shake256_ctxt_t ctxt; + SHAKE256_init(&ctxt); + SHAKE256_absorb(&ctxt, in, in_len); + SHAKE256_squeeze(&ctxt, out, out_len); + SHAKE256_free(&ctxt); +} diff --git a/third_party/embedpqc/ports/shake.h b/third_party/embedpqc/ports/shake.h new file mode 100644 index 0000000000000..7971506b5f5f5 --- /dev/null +++ b/third_party/embedpqc/ports/shake.h @@ -0,0 +1,40 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_SHAKE_H_ +#define OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_SHAKE_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Stateless dummy contexts for OpenTitan hardware KMAC integration. +typedef struct { + char dummy; +} shake128_ctxt_t; + +typedef struct { + char dummy; +} shake256_ctxt_t; + +void SHAKE128_init(shake128_ctxt_t *ctxt); +void SHAKE128_absorb(shake128_ctxt_t *ctxt, const uint8_t *in, size_t in_len); +void SHAKE128_squeeze(shake128_ctxt_t *ctxt, uint8_t *out, size_t out_len); +void SHAKE128_free(shake128_ctxt_t *ctxt); + +void SHAKE256_init(shake256_ctxt_t *ctxt); +void SHAKE256_absorb(shake256_ctxt_t *ctxt, const uint8_t *in, size_t in_len); +void SHAKE256_squeeze(shake256_ctxt_t *ctxt, uint8_t *out, size_t out_len); +void SHAKE256_free(shake256_ctxt_t *ctxt); +void SHAKE256_buffer(uint8_t *out, size_t out_len, const uint8_t *in, + size_t in_len); + +#ifdef __cplusplus +} +#endif + +#endif // OPENTITAN_THIRD_PARTY_EMBEDPQC_PORTS_SHAKE_H_ diff --git a/util/fix_trailing_whitespace.py b/util/fix_trailing_whitespace.py index f582b44ef2a8a..bca7667b63e83 100755 --- a/util/fix_trailing_whitespace.py +++ b/util/fix_trailing_whitespace.py @@ -87,7 +87,7 @@ def main(): except UnicodeDecodeError: print(f'Binary file: "{path}"') continue - new_text = "\n".join([line.rstrip() for line in old_text.strip().split("\n")]) + "\n" + new_text = "\n".join([line.rstrip() for line in old_text.rstrip().split("\n")]) + "\n" if old_text != new_text: print(f'Fixing file: "{path}"', file=sys.stdout)