|
@@ -19,8 +19,4320 @@
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
|
*/
|
|
|
|
|
|
+/* Based on:
|
|
|
+ * o RFC 8391 - XMSS: eXtended Merkle Signature Scheme
|
|
|
+ * o [HDSS] "Hash-based Digital Signature Schemes", Buchmann, Dahmen and Szydlo
|
|
|
+ * from "Post Quantum Cryptography", Springer 2009.
|
|
|
+ * o [OPX] "Optimal Parameters for XMSS^MT", Hulsing, Rausch and Buchmann
|
|
|
+ *
|
|
|
+ * TODO: "Simple and Memory-efficient Signature Generation of XMSS^MT"
|
|
|
+ * (https://ece.engr.uvic.ca/~raltawy/SAC2021/9.pdf)
|
|
|
+ */
|
|
|
+
|
|
|
+#ifdef HAVE_CONFIG_H
|
|
|
+ #include <config.h>
|
|
|
+#endif
|
|
|
+
|
|
|
#include <wolfssl/wolfcrypt/settings.h>
|
|
|
+#include <wolfssl/wolfcrypt/error-crypt.h>
|
|
|
+#include <wolfssl/wolfcrypt/logging.h>
|
|
|
+
|
|
|
+#include <wolfssl/wolfcrypt/wc_xmss.h>
|
|
|
+#include <wolfssl/wolfcrypt/hash.h>
|
|
|
+
|
|
|
+#ifdef NO_INLINE
|
|
|
+ #include <wolfssl/wolfcrypt/misc.h>
|
|
|
+#else
|
|
|
+ #define WOLFSSL_MISC_INCLUDED
|
|
|
+ #include <wolfcrypt/src/misc.c>
|
|
|
+#endif
|
|
|
+
|
|
|
+#if defined(WOLFSSL_HAVE_XMSS)
|
|
|
+
|
|
|
+/* Indices into Hash Address. */
|
|
|
+#define XMSS_ADDR_LAYER 0
|
|
|
+#define XMSS_ADDR_TREE_HI 1
|
|
|
+#define XMSS_ADDR_TREE 2
|
|
|
+#define XMSS_ADDR_TYPE 3
|
|
|
+#define XMSS_ADDR_OTS 4
|
|
|
+#define XMSS_ADDR_LTREE 4
|
|
|
+#define XMSS_ADDR_TREE_ZERO 4
|
|
|
+#define XMSS_ADDR_CHAIN 5
|
|
|
+#define XMSS_ADDR_TREE_HEIGHT 5
|
|
|
+#define XMSS_ADDR_HASH 6
|
|
|
+#define XMSS_ADDR_TREE_INDEX 6
|
|
|
+#define XMSS_ADDR_KEY_MASK 7
|
|
|
+
|
|
|
+/* Types of hash addresses. */
|
|
|
+#define WC_XMSS_ADDR_TYPE_OTS 0
|
|
|
+#define WC_XMSS_ADDR_TYPE_LTREE 1
|
|
|
+#define WC_XMSS_ADDR_TYPE_TREE 2
|
|
|
+
|
|
|
+/* Byte to include in hash to create unique sequence. */
|
|
|
+#define XMSS_HASH_PADDING_F 0
|
|
|
+#define XMSS_HASH_PADDING_H 1
|
|
|
+#define XMSS_HASH_PADDING_HASH 2
|
|
|
+#define XMSS_HASH_PADDING_PRF 3
|
|
|
+#define XMSS_HASH_PADDING_PRF_KEYGEN 4
|
|
|
+
|
|
|
+/* Fixed parameter values. */
|
|
|
+#define XMSS_WOTS_W 16
|
|
|
+#define XMSS_WOTS_LOG_W 4
|
|
|
+#define XMSS_WOTS_LEN2 3
|
|
|
+#define XMSS_CSUM_SHIFT 4
|
|
|
+#define XMSS_CSUM_LEN 2
|
|
|
+
|
|
|
+/* Length of the message to the PRF. */
|
|
|
+#define XMSS_PRF_M_LEN 32
|
|
|
+
|
|
|
+/* Length of index encoding when doing XMSS. */
|
|
|
+#define XMSS_IDX_LEN 4
|
|
|
+
|
|
|
+/* Size of the N when using SHA-256 and 32 byte padding. */
|
|
|
+#define XMSS_SHA256_32_N WC_SHA256_DIGEST_SIZE
|
|
|
+/* Size of the padding when using SHA-256 and 32 byte padding. */
|
|
|
+#define XMSS_SHA256_32_PAD_LEN 32
|
|
|
+
|
|
|
+/* Calculate PRF data length for parameters. */
|
|
|
+#define XMSS_HASH_PRF_DATA_LEN(params) \
|
|
|
+ ((params)->pad_len + (params)->n + WC_XMSS_ADDR_LEN)
|
|
|
+/* PRF data length when using SHA-256 with 32 byte padding. */
|
|
|
+#define XMSS_HASH_PRF_DATA_LEN_SHA256_32 \
|
|
|
+ (XMSS_SHA256_32_PAD_LEN + XMSS_SHA256_32_N + WC_XMSS_ADDR_LEN)
|
|
|
+
|
|
|
+/* Calculate chain hash data length for parameters. */
|
|
|
+#define XMSS_CHAIN_HASH_DATA_LEN(params) \
|
|
|
+ ((params)->pad_len + 2 * (params)->n)
|
|
|
+/* Chain hash data length when using SHA-256 with 32 byte padding. */
|
|
|
+#define XMSS_CHAIN_HASH_DATA_LEN_SHA256_32 \
|
|
|
+ (XMSS_SHA256_32_PAD_LEN + 2 * XMSS_SHA256_32_N)
|
|
|
+
|
|
|
+/* Calculate rand hash data length for parameters. */
|
|
|
+#define XMSS_RAND_HASH_DATA_LEN(params) \
|
|
|
+ ((params)->pad_len + 3 * (params)->n)
|
|
|
+/* Rand hash data length when using SHA-256 with 32 byte padding. */
|
|
|
+#define XMSS_RAND_HASH_DATA_LEN_SHA256_32 \
|
|
|
+ (XMSS_SHA256_32_PAD_LEN + 3 * XMSS_SHA256_32_N)
|
|
|
+
|
|
|
+/* Encode pad value into byte array. Front fill with 0s.
|
|
|
+ *
|
|
|
+ * RFC 8391: 2.4
|
|
|
+ *
|
|
|
+ * @param [in] n Number to encode.
|
|
|
+ * @param [out] a Array to hold encoding.
|
|
|
+ * @param [in] l Length of array.
|
|
|
+ */
|
|
|
+#define XMSS_PAD_ENC(n, a, l) \
|
|
|
+do { \
|
|
|
+ XMEMSET(a, 0, l); \
|
|
|
+ (a)[(l) - 1] = (n); \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * Index 32/64 bits
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Index of 32 or 64 bits. */
|
|
|
+typedef union wc_Idx {
|
|
|
+#if WOLFSSL_XMSS_MAX_HEIGHT > 32
|
|
|
+ /* 64-bit representation. */
|
|
|
+ w64wrapper u64;
|
|
|
+#endif
|
|
|
+#if WOLFSSL_XMSS_MIN_HEIGHT <= 32
|
|
|
+ /* 32-bit representation. */
|
|
|
+ word32 u32;
|
|
|
+#endif
|
|
|
+} wc_Idx;
|
|
|
+
|
|
|
+#if WOLFSSL_XMSS_MAX_HEIGHT > 32
|
|
|
+/* Set index to zero.
|
|
|
+ *
|
|
|
+ * Index is up to 64-bits.
|
|
|
+ *
|
|
|
+ * @param [out] idx 32/64-bit index to zero.
|
|
|
+ */
|
|
|
+#define WC_IDX_ZERO(idx) w64Zero(&(idx).u64)
|
|
|
+#else
|
|
|
+/* Set index to zero.
|
|
|
+ *
|
|
|
+ * Index is no more than 32-bits.
|
|
|
+ *
|
|
|
+ * @param [out] idx 32/64-bit index to zero.
|
|
|
+ */
|
|
|
+#define WC_IDX_ZERO(idx) idx.u32 = 0
|
|
|
+#endif
|
|
|
+
|
|
|
+#if WOLFSSL_XMSS_MAX_HEIGHT > 32
|
|
|
+/* Decode 64-bit index.
|
|
|
+ *
|
|
|
+ * @param [out] i Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ * @param [out] ret Return value.
|
|
|
+ */
|
|
|
+#define IDX64_DECODE(i, c, a, ret) \
|
|
|
+ if ((c) == 5) { \
|
|
|
+ word32 t; \
|
|
|
+ ato32((a) + 1, &t); \
|
|
|
+ (i) = w64From32((a)[0], t); \
|
|
|
+ } \
|
|
|
+ else if ((c) == 8) { \
|
|
|
+ ato64(a, &(i)); \
|
|
|
+ }
|
|
|
+
|
|
|
+/* Decode 64-bit index.
|
|
|
+ *
|
|
|
+ * @param [out] i Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ * @param [out] ret Return value.
|
|
|
+ */
|
|
|
+#define XMSS_IDX64_DECODE(i, c, a, ret) \
|
|
|
+do { \
|
|
|
+ IDX64_DECODE(i, c, a, ret) \
|
|
|
+ else { \
|
|
|
+ (ret) = NOT_COMPILED_IN; \
|
|
|
+ } \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Check whether index is valid.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to check.
|
|
|
+ * @param [in] c Count of bytes i was encoded in.
|
|
|
+ * @param [in] h Full tree Height.
|
|
|
+ */
|
|
|
+#define IDX64_INVALID(i, c, h) \
|
|
|
+ ((w64GetHigh32(w64Add32(i, 1, NULL)) >> ((h) - 32)) != 0)
|
|
|
+
|
|
|
+/* Set 64-bit index as hash address value for tree.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to set.
|
|
|
+ * @param [in] c Count of bytes to encode into.
|
|
|
+ * @param [in] h Height of tree.
|
|
|
+ * @param [out] a Hash address to encode into.
|
|
|
+ * @param [out] l Index of leaf.
|
|
|
+ */
|
|
|
+#define IDX64_SET_ADDR_TREE(i, c, h, a, l) \
|
|
|
+ if ((c) > 4) { \
|
|
|
+ (l) = w64GetLow32(i) & (((word32)1 << (h)) - 1);\
|
|
|
+ (i) = w64ShiftRight(i, h); \
|
|
|
+ (a)[XMSS_ADDR_TREE_HI] = w64GetHigh32(i); \
|
|
|
+ (a)[XMSS_ADDR_TREE] = w64GetLow32(i); \
|
|
|
+ }
|
|
|
+#endif /* WOLFSSL_XMSS_MAX_HEIGHT > 32 */
|
|
|
+
|
|
|
+#if WOLFSSL_XMSS_MIN_HEIGHT <= 32
|
|
|
+/* Decode 32-bit index.
|
|
|
+ *
|
|
|
+ * @param [out] i Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ * @param [out] ret Return value.
|
|
|
+ */
|
|
|
+#define IDX32_DECODE(i, c, a, ret) \
|
|
|
+ if ((c) == 4) { \
|
|
|
+ ato32(a, &(i)); \
|
|
|
+ } \
|
|
|
+ else if ((c) == 3) { \
|
|
|
+ ato24(a, &(i)); \
|
|
|
+ }
|
|
|
+
|
|
|
+/* Decode 32-bit index.
|
|
|
+ *
|
|
|
+ * @param [out] i Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ * @param [out] ret Return value.
|
|
|
+ */
|
|
|
+#define XMSS_IDX32_DECODE(i, c, a, ret) \
|
|
|
+do { \
|
|
|
+ IDX32_DECODE(i, c, a, ret) \
|
|
|
+ else { \
|
|
|
+ (ret) = NOT_COMPILED_IN; \
|
|
|
+ } \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Check whether 32-bit index is valid.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to check.
|
|
|
+ * @param [in] c Count of bytes i was encoded in.
|
|
|
+ * @param [in] h Full tree Height.
|
|
|
+ */
|
|
|
+#define IDX32_INVALID(i, c, h) \
|
|
|
+ ((((i) + 1) >> (h)) != 0)
|
|
|
+
|
|
|
+/* Set 32-bit index as hash address value for tree.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to set.
|
|
|
+ * @param [in] c Count of bytes to encode into.
|
|
|
+ * @param [in] h Height of tree.
|
|
|
+ * @param [out] a Hash address to encode into.
|
|
|
+ * @param [out] l Index of leaf.
|
|
|
+ */
|
|
|
+#define IDX32_SET_ADDR_TREE(i, c, h, a, l) \
|
|
|
+ if ((c) <= 4) { \
|
|
|
+ (l) = ((i) & ((1 << (h)) - 1)); \
|
|
|
+ (i) >>= params->sub_h; \
|
|
|
+ (a)[XMSS_ADDR_TREE] = (i); \
|
|
|
+ }
|
|
|
+
|
|
|
+#endif /* WOLFSSL_XMSS_MIN_HEIGHT <= 32 */
|
|
|
+
|
|
|
+#if (WOLFSSL_XMSS_MAX_HEIGHT > 32) && (WOLFSSL_XMSS_MIN_HEIGHT <= 32)
|
|
|
+
|
|
|
+/* Decode 32/64-bit index.
|
|
|
+ *
|
|
|
+ * @param [out] idx Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ * @param [out] ret Return value.
|
|
|
+ */
|
|
|
+#define WC_IDX_DECODE(idx, c, a, ret) \
|
|
|
+do { \
|
|
|
+ IDX64_DECODE((idx).u64, c, a, ret) \
|
|
|
+ else \
|
|
|
+ IDX32_DECODE((idx).u32, c, a, ret) \
|
|
|
+ else { \
|
|
|
+ (ret) = NOT_COMPILED_IN; \
|
|
|
+ } \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Check whether index is valid.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to check.
|
|
|
+ * @param [in] c Count of bytes i was encoded in.
|
|
|
+ * @param [in] h Full tree Height.
|
|
|
+ */
|
|
|
+#define WC_IDX_INVALID(i, c, h) \
|
|
|
+ ((((c) > 4) && IDX64_INVALID((i).u64, c, h)) || \
|
|
|
+ (((c) <= 4) && IDX32_INVALID((i).u32, c, h)))
|
|
|
+
|
|
|
+/* Set 32/64-bit index as hash address value for tree.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to set.
|
|
|
+ * @param [in] c Count of bytes to encode into.
|
|
|
+ * @param [in] h Height of tree.
|
|
|
+ * @param [out] a Hash address to encode into.
|
|
|
+ * @param [out] l Index of leaf.
|
|
|
+ */
|
|
|
+#define WC_IDX_SET_ADDR_TREE(idx, c, h, a, l) \
|
|
|
+do { \
|
|
|
+ IDX64_SET_ADDR_TREE((idx).u64, c, h, a, l) \
|
|
|
+ else \
|
|
|
+ IDX32_SET_ADDR_TREE((idx).u32, c, h, a, l) \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+#elif WOLFSSL_XMSS_MAX_HEIGHT > 32
|
|
|
+
|
|
|
+/* Decode 64-bit index.
|
|
|
+ *
|
|
|
+ * @param [out] idx Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ * @param [out] ret Return value.
|
|
|
+ */
|
|
|
+#define WC_IDX_DECODE(idx, c, a, ret) \
|
|
|
+do { \
|
|
|
+ IDX64_DECODE((idx).u64, c, a, ret) \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Check whether index is valid.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to check.
|
|
|
+ * @param [in] c Count of bytes i was encoded in.
|
|
|
+ * @param [in] h Full tree Height.
|
|
|
+ */
|
|
|
+#define WC_IDX_INVALID(i, c, h) \
|
|
|
+ IDX64_INVALID((i).u64, c, h)
|
|
|
+
|
|
|
+/* Set 64-bit index as hash address value for tree.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to set.
|
|
|
+ * @param [in] c Count of bytes to encode into.
|
|
|
+ * @param [in] h Height of tree.
|
|
|
+ * @param [out] a Hash address to encode into.
|
|
|
+ * @param [out] l Index of leaf.
|
|
|
+ */
|
|
|
+#define WC_IDX_SET_ADDR_TREE(idx, c, h, a, l) \
|
|
|
+do { \
|
|
|
+ IDX64_SET_ADDR_TREE((idx).u64, c, h, a, l) \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+#else
|
|
|
+
|
|
|
+/* Decode 32-bit index.
|
|
|
+ *
|
|
|
+ * @param [out] idx Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ * @param [out] ret Return value.
|
|
|
+ */
|
|
|
+#define WC_IDX_DECODE(idx, c, a, ret) \
|
|
|
+do { \
|
|
|
+ IDX32_DECODE((idx).u32, c, a, ret) \
|
|
|
+ else { \
|
|
|
+ (ret) = NOT_COMPILED_IN; \
|
|
|
+ } \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Check whether index is valid.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to check.
|
|
|
+ * @param [in] c Count of bytes i was encoded in.
|
|
|
+ * @param [in] h Full tree Height.
|
|
|
+ */
|
|
|
+#define WC_IDX_INVALID(i, c, h) \
|
|
|
+ IDX32_INVALID((i).u32, c, h)
|
|
|
+
|
|
|
+/* Set 32-bit index as hash address value for tree.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to set.
|
|
|
+ * @param [in] c Count of bytes to encode into.
|
|
|
+ * @param [in] h Height of tree.
|
|
|
+ * @param [out] a Hash address to encode into.
|
|
|
+ * @param [out] l Index of leaf.
|
|
|
+ */
|
|
|
+#define WC_IDX_SET_ADDR_TREE(idx, c, h, a, l) \
|
|
|
+do { \
|
|
|
+ IDX32_SET_ADDR_TREE(idx.u32, c, h, a, l) \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+#endif /* (WOLFSSL_XMSS_MAX_HEIGHT > 32) && (WOLFSSL_XMSS_MIN_HEIGHT <= 32) */
|
|
|
+
|
|
|
+#ifndef WOLFSSL_XMSS_VERIFY_ONLY
|
|
|
+/* Update index by adding one to big-endian encoded value.
|
|
|
+ *
|
|
|
+ * @param [in, out] a Array index is encoded in.
|
|
|
+ * @param [in] l Length of encoded index.
|
|
|
+ */
|
|
|
+static void wc_idx_update(unsigned char* a, word8 l)
|
|
|
+{
|
|
|
+ sword8 i;
|
|
|
+
|
|
|
+ for (i = l - 1; i >= 0; i--) {
|
|
|
+ if ((++a[i]) != 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Copy index from source buffer to destination buffer.
|
|
|
+ *
|
|
|
+ * Index is put into the front of the destination buffer with the length of the
|
|
|
+ * source.
|
|
|
+ *
|
|
|
+ * @param [in] s Source buffer.
|
|
|
+ * @param [in] sl Length of index in source.
|
|
|
+ * @param [in, out] d Destination buffer.
|
|
|
+ * @param [in] dl Length of destination buffer.
|
|
|
+ */
|
|
|
+static void wc_idx_copy(const unsigned char* s, word8 sl, unsigned char* d,
|
|
|
+ word8 dl)
|
|
|
+{
|
|
|
+ XMEMCPY(d, s, sl);
|
|
|
+ XMEMSET(d + sl, 0, dl - sl);
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * Hash Address.
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Set the hash address based on subtree.
|
|
|
+ *
|
|
|
+ * @param [out] a Hash address.
|
|
|
+ * @param [in] s Subtree hash address.
|
|
|
+ * @param [in] t Type of hash address.
|
|
|
+ */
|
|
|
+#define XMSS_ADDR_SET_SUBTREE(a, s, t) \
|
|
|
+do { \
|
|
|
+ (a)[XMSS_ADDR_LAYER] = (s)[XMSS_ADDR_LAYER]; \
|
|
|
+ (a)[XMSS_ADDR_TREE_HI] = (s)[XMSS_ADDR_TREE_HI]; \
|
|
|
+ (a)[XMSS_ADDR_TREE] = (s)[XMSS_ADDR_TREE]; \
|
|
|
+ (a)[XMSS_ADDR_TYPE] = (t); \
|
|
|
+ XMEMSET((a) + 4, 0, sizeof(a) - 4 * sizeof(*(a)));\
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Set the OTS hash address based on subtree.
|
|
|
+ *
|
|
|
+ * @param [out] a Hash address.
|
|
|
+ * @param [in] s Subtree hash address.
|
|
|
+ */
|
|
|
+#define XMSS_ADDR_OTS_SET_SUBTREE(a, s) \
|
|
|
+ XMSS_ADDR_SET_SUBTREE(a, s, WC_XMSS_ADDR_TYPE_OTS)
|
|
|
+/* Set the L-tree address based on subtree.
|
|
|
+ *
|
|
|
+ * @param [out] a Hash address.
|
|
|
+ * @param [in] s Subtree hash address.
|
|
|
+ */
|
|
|
+#define XMSS_ADDR_LTREE_SET_SUBTREE(a, s) \
|
|
|
+ XMSS_ADDR_SET_SUBTREE(a, s, WC_XMSS_ADDR_TYPE_LTREE)
|
|
|
+/* Set the hash tree address based on subtree.
|
|
|
+ *
|
|
|
+ * @param [out] a Hash address.
|
|
|
+ * @param [in] s Subtree hash address.
|
|
|
+ */
|
|
|
+#define XMSS_ADDR_TREE_SET_SUBTREE(a, s) \
|
|
|
+ XMSS_ADDR_SET_SUBTREE(a, s, WC_XMSS_ADDR_TYPE_TREE)
|
|
|
+
|
|
|
+#ifdef LITTLE_ENDIAN_ORDER
|
|
|
+
|
|
|
+/* Set a byte value into a word of an encoded address.
|
|
|
+ *
|
|
|
+ * @param [in, out] a Encoded hash address.
|
|
|
+ * @param [in] i Index of word.
|
|
|
+ * @param [in] b Byte to set.
|
|
|
+ */
|
|
|
+#define XMSS_ADDR_SET_BYTE(a, i, b) \
|
|
|
+ ((word32*)(a))[i] = (word32)(b) << 24
|
|
|
+
|
|
|
+#else
|
|
|
+
|
|
|
+/* Set a byte value into a word of an encoded address.
|
|
|
+ *
|
|
|
+ * @param [in, out] a Encoded hash address.
|
|
|
+ * @param [in] i Index of word.
|
|
|
+ * @param [in] b Byte to set.
|
|
|
+ */
|
|
|
+#define XMSS_ADDR_SET_BYTE(a, i, b) \
|
|
|
+ ((word32*)(a))[i] = (b)
|
|
|
+
|
|
|
+#endif /* LITTLE_ENDIAN_ORDER */
|
|
|
+
|
|
|
+/* Convert hash address to bytes.
|
|
|
+ *
|
|
|
+ * @param [out] bytes Array to encode into.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ */
|
|
|
+static void wc_xmss_addr_encode(const HashAddress addr, byte* bytes)
|
|
|
+{
|
|
|
+ c32toa((addr)[0], (bytes) + (0 * 4));
|
|
|
+ c32toa((addr)[1], (bytes) + (1 * 4));
|
|
|
+ c32toa((addr)[2], (bytes) + (2 * 4));
|
|
|
+ c32toa((addr)[3], (bytes) + (3 * 4));
|
|
|
+ c32toa((addr)[4], (bytes) + (4 * 4));
|
|
|
+ c32toa((addr)[5], (bytes) + (5 * 4));
|
|
|
+ c32toa((addr)[6], (bytes) + (6 * 4));
|
|
|
+ c32toa((addr)[7], (bytes) + (7 * 4));
|
|
|
+}
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * HASHING
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256) && \
|
|
|
+ !defined(WC_XMSS_FULL_HASH)
|
|
|
+
|
|
|
+/* Set hash data and length into SHA-256 digest.
|
|
|
+ *
|
|
|
+ * @param [in, out] state XMSS/MT state with SHA-256 digest.
|
|
|
+ * @param [in] data Data to add to hash.
|
|
|
+ * @param [in] len Number of bytes in data.
|
|
|
+ * Must be less than a block.
|
|
|
+ * @param [in] total_len Number of bytes updated so far.
|
|
|
+ */
|
|
|
+#define XMSS_SHA256_SET_DATA(state, data, len, total_len) \
|
|
|
+do { \
|
|
|
+ XMEMCPY((state)->digest.sha256.buffer, data, len); \
|
|
|
+ (state)->digest.sha256.buffLen = (len); \
|
|
|
+ (state)->digest.sha256.loLen = (total_len); \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Save the SHA-256 state to cache.
|
|
|
+ *
|
|
|
+ * @param [in, out] state XMSS/MT state with SHA-256 digest and state cache.
|
|
|
+ */
|
|
|
+#define XMSS_SHA256_STATE_CACHE(state) \
|
|
|
+ (state)->dgst_state[0] = (state)->digest.sha256.digest[0]; \
|
|
|
+ (state)->dgst_state[1] = (state)->digest.sha256.digest[1]; \
|
|
|
+ (state)->dgst_state[2] = (state)->digest.sha256.digest[2]; \
|
|
|
+ (state)->dgst_state[3] = (state)->digest.sha256.digest[3]; \
|
|
|
+ (state)->dgst_state[4] = (state)->digest.sha256.digest[4]; \
|
|
|
+ (state)->dgst_state[5] = (state)->digest.sha256.digest[5]; \
|
|
|
+ (state)->dgst_state[6] = (state)->digest.sha256.digest[6]; \
|
|
|
+ (state)->dgst_state[7] = (state)->digest.sha256.digest[7]; \
|
|
|
+
|
|
|
+/* Restore the SHA-256 state from cache and set length.
|
|
|
+ *
|
|
|
+ * @param [in, out] state XMSS/MT state with SHA-256 digest and state cache.
|
|
|
+ * @param [in] len Number of bytes of data hashed so far.
|
|
|
+ */
|
|
|
+#define XMSS_SHA256_STATE_RESTORE(state, len) \
|
|
|
+do { \
|
|
|
+ (state)->digest.sha256.digest[0] = (state)->dgst_state[0]; \
|
|
|
+ (state)->digest.sha256.digest[1] = (state)->dgst_state[1]; \
|
|
|
+ (state)->digest.sha256.digest[2] = (state)->dgst_state[2]; \
|
|
|
+ (state)->digest.sha256.digest[3] = (state)->dgst_state[3]; \
|
|
|
+ (state)->digest.sha256.digest[4] = (state)->dgst_state[4]; \
|
|
|
+ (state)->digest.sha256.digest[5] = (state)->dgst_state[5]; \
|
|
|
+ (state)->digest.sha256.digest[6] = (state)->dgst_state[6]; \
|
|
|
+ (state)->digest.sha256.digest[7] = (state)->dgst_state[7]; \
|
|
|
+ (state)->digest.sha256.loLen = (len); \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+/* Restore the SHA-256 state from cache and set data and length.
|
|
|
+ *
|
|
|
+ * @param [in, out] state XMSS/MT state with SHA-256 digest and cache.
|
|
|
+ * @param [in] data Data to add to hash.
|
|
|
+ * @param [in] len Number of bytes in data.
|
|
|
+ * Must be less than a block.
|
|
|
+ * @param [in] total_len Number of bytes updated so far.
|
|
|
+ */
|
|
|
+#define XMSS_SHA256_STATE_RESTORE_DATA(state, data, len, total_len) \
|
|
|
+do { \
|
|
|
+ (state)->digest.sha256.digest[0] = (state)->dgst_state[0]; \
|
|
|
+ (state)->digest.sha256.digest[1] = (state)->dgst_state[1]; \
|
|
|
+ (state)->digest.sha256.digest[2] = (state)->dgst_state[2]; \
|
|
|
+ (state)->digest.sha256.digest[3] = (state)->dgst_state[3]; \
|
|
|
+ (state)->digest.sha256.digest[4] = (state)->dgst_state[4]; \
|
|
|
+ (state)->digest.sha256.digest[5] = (state)->dgst_state[5]; \
|
|
|
+ (state)->digest.sha256.digest[6] = (state)->dgst_state[6]; \
|
|
|
+ (state)->digest.sha256.digest[7] = (state)->dgst_state[7]; \
|
|
|
+ XMSS_SHA256_SET_DATA(state, data, len, total_len); \
|
|
|
+} while (0)
|
|
|
+
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 && !WC_XMSS_FULL_HASH */
|
|
|
+
|
|
|
+/* Hash the data into output buffer.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] in Data to digest.
|
|
|
+ * @param [in] inlen Length of data to digest in bytes.
|
|
|
+ * @param [out] out Buffer to put digest into.
|
|
|
+ */
|
|
|
+static WC_INLINE void wc_xmss_hash(XmssState* state, const byte* in,
|
|
|
+ word32 inlen, byte* out)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
|
|
|
-#ifdef WOLFSSL_HAVE_XMSS
|
|
|
- #error "Contact wolfSSL to get the implementation of this file"
|
|
|
+#ifdef WC_XMSS_SHA256
|
|
|
+ /* Full SHA-256 digest. */
|
|
|
+ if ((params->hash == WC_HASH_TYPE_SHA256) &&
|
|
|
+ (params->n == WC_SHA256_DIGEST_SIZE)) {
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, in, inlen);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, out);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#if WOLFSSL_WC_XMSS_MIN_HASH_SIZE <= 192 && WOLFSSL_WC_XMSS_MAX_HASH_SIZE >= 192
|
|
|
+ /* Partial SHA-256 digest. */
|
|
|
+ else if (params->hash == WC_HASH_TYPE_SHA256) {
|
|
|
+ byte buf[WC_SHA256_DIGEST_SIZE];
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, in, inlen);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, buf);
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ XMEMCPY(out, buf, params->n);
|
|
|
+ }
|
|
|
+ }
|
|
|
#endif
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHA256 */
|
|
|
+#ifdef WC_XMSS_SHA512
|
|
|
+ /* Full SHA-512 digest. */
|
|
|
+ if (params->hash == WC_HASH_TYPE_SHA512) {
|
|
|
+ ret = wc_Sha512Update(&state->digest.sha512, in, inlen);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha512Final(&state->digest.sha512, out);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHA512 */
|
|
|
+#ifdef WC_XMSS_SHAKE128
|
|
|
+ /* Digest with SHAKE-128. */
|
|
|
+ if (params->hash == WC_HASH_TYPE_SHAKE128) {
|
|
|
+ ret = wc_Shake128_Update(&state->digest.shake, in, inlen);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Shake128_Final(&state->digest.shake, out, params->n);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHAKE128 */
|
|
|
+#ifdef WC_XMSS_SHAKE256
|
|
|
+ /* Digest with SHAKE-256. */
|
|
|
+ if (params->hash == WC_HASH_TYPE_SHAKE256) {
|
|
|
+ ret = wc_Shake256_Update(&state->digest.shake, in, inlen);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Shake256_Final(&state->digest.shake, out, params->n);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHAKE256 */
|
|
|
+ {
|
|
|
+ /* Unsupported digest function. */
|
|
|
+ ret = NOT_COMPILED_IN;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+#ifndef WC_XMSS_FULL_HASH
|
|
|
+/* Chain hashing.
|
|
|
+ *
|
|
|
+ * RFC 8391: 3.1.2, Algorithm 2: chain - Chaining Function
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM = PRF(SEED, ADRS);
|
|
|
+ * tmp = F(KEY, tmp XOR BM);
|
|
|
+ * return tmp;
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] tmp Temporary buffer holding chain data.
|
|
|
+ * @param [in] addr Hash address as a byte array.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_chain_hash_sha256_32(XmssState* state, const byte* tmp,
|
|
|
+ byte* addr, byte* hash)
|
|
|
+{
|
|
|
+ /* Offsets into chain hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + XMSS_SHA256_32_PAD_LEN;
|
|
|
+ byte* bm = key + XMSS_SHA256_32_N;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ ((word32*)addr)[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, key);
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Calculate n-byte bit mask - BM. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, bm);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Function padding set in caller. */
|
|
|
+ xorbuf(bm, tmp, XMSS_SHA256_32_N);
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->buf,
|
|
|
+ XMSS_CHAIN_HASH_DATA_LEN_SHA256_32);
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Calculate the chain hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, hash);
|
|
|
+ }
|
|
|
+ if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+}
|
|
|
+#else
|
|
|
+/* Chain hashing.
|
|
|
+ *
|
|
|
+ * Padding, seed, addr for PRF set by caller into prf_buf.
|
|
|
+ *
|
|
|
+ * RFC 8391: 3.1.2, Algorithm 2: chain - Chaining Function
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM = PRF(SEED, ADRS);
|
|
|
+ * tmp = F(KEY, tmp XOR BM);
|
|
|
+ * return tmp;
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] tmp Temporary buffer holding chain data.
|
|
|
+ * @param [out] out Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_chain_hash_sha256_32(XmssState* state, const byte* tmp,
|
|
|
+ byte* hash)
|
|
|
+{
|
|
|
+ byte* addr = state->prf_buf + XMSS_SHA256_32_PAD_LEN + XMSS_SHA256_32_N;
|
|
|
+ /* Offsets into chain hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + XMSS_SHA256_32_PAD_LEN;
|
|
|
+ byte* bm = key + XMSS_SHA256_32_N;
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ ((word32*)addr)[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, key);
|
|
|
+ /* Calculate the n-byte mask. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, bm);
|
|
|
+
|
|
|
+ /* Function padding set in caller. */
|
|
|
+ xorbuf(bm, tmp, XMSS_SHA256_32_N);
|
|
|
+ /* Calculate the chain hash. */
|
|
|
+ wc_xmss_hash(state, state->buf, XMSS_CHAIN_HASH_DATA_LEN_SHA256_32, hash);
|
|
|
+}
|
|
|
+#endif /* !WC_XMSS_FULL_HASH */
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+
|
|
|
+/* Chain hashing.
|
|
|
+ *
|
|
|
+ * Padding, seed, addr for PRF set by caller into prf_buf.
|
|
|
+ *
|
|
|
+ * RFC 8391: 3.1.2, Algorithm 2: chain - Chaining Function
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM = PRF(SEED, ADRS);
|
|
|
+ * tmp = F(KEY, tmp XOR BM);
|
|
|
+ * return tmp;
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] tmp Temporary buffer holding chain data.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_chain_hash(XmssState* state, const byte* tmp, byte* hash)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* addr = state->prf_buf + params->pad_len + params->n;
|
|
|
+ /* Offsets into chain hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + params->pad_len;
|
|
|
+ byte* bm = key + params->n;
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ ((word32*)addr)[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN(params), key);
|
|
|
+ /* Calculate n-byte bit mask - BM. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN(params), bm);
|
|
|
+
|
|
|
+ /* Function padding set in caller. */
|
|
|
+ xorbuf(bm, tmp, params->n);
|
|
|
+ /* Calculate the chain hash. */
|
|
|
+ wc_xmss_hash(state, state->buf, XMSS_CHAIN_HASH_DATA_LEN(params), hash);
|
|
|
+}
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+#ifndef WC_XMSS_FULL_HASH
|
|
|
+/* Randomized tree hashing.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.4, Algorithm 7: RAND_HASH
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM_0 = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(2);
|
|
|
+ * BM_1 = PRF(SEED, ADRS);
|
|
|
+ * return H(KEY, (LEFT XOR BM_0) || (RIGHT XOR BM_1));
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] data Input data.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_rand_hash_sha256_32_prehash(XmssState* state,
|
|
|
+ const byte* data, HashAddress addr, byte* hash)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ /* Offsets into rand hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + XMSS_SHA256_32_PAD_LEN;
|
|
|
+ byte* bm0 = key + XMSS_SHA256_32_N;
|
|
|
+ byte* bm1 = bm0 + XMSS_SHA256_32_N;
|
|
|
+ byte addr_buf[WC_XMSS_ADDR_LEN];
|
|
|
+
|
|
|
+ addr[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, key);
|
|
|
+
|
|
|
+ /* Calculate n-byte mask - BM_0. */
|
|
|
+ if (ret == 0) {
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, bm0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate n-byte mask - BM_1. */
|
|
|
+ if (ret == 0) {
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 2;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, bm1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_H, pad, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ /* XOR into bm0 and bm1. */
|
|
|
+ xorbuf(bm0, data, XMSS_SHA256_32_N * 2);
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->buf,
|
|
|
+ XMSS_RAND_HASH_DATA_LEN_SHA256_32);
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, hash);
|
|
|
+ }
|
|
|
+ if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+}
|
|
|
+#endif /* !WC_XMSS_FULL_HASH */
|
|
|
+
|
|
|
+/* Randomized tree hashing.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.4, Algorithm 7: RAND_HASH
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM_0 = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(2);
|
|
|
+ * BM_1 = PRF(SEED, ADRS);
|
|
|
+ * return H(KEY, (LEFT XOR BM_0) || (RIGHT XOR BM_1));
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] data Input data.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_rand_hash_sha256_32(XmssState* state, const byte* data,
|
|
|
+ const byte* pk_seed, HashAddress addr, byte* hash)
|
|
|
+{
|
|
|
+ byte* addr_buf = state->prf_buf + XMSS_SHA256_32_PAD_LEN +
|
|
|
+ XMSS_SHA256_32_N;
|
|
|
+ /* Offsets into rand hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + XMSS_SHA256_32_PAD_LEN;
|
|
|
+ byte* bm0 = key + XMSS_SHA256_32_N;
|
|
|
+ byte* bm1 = bm0 + XMSS_SHA256_32_N;
|
|
|
+#ifndef WC_XMSS_FULL_HASH
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Encode padding byte for PRF. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, state->prf_buf, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ /* Append public seed for PRF. */
|
|
|
+ XMEMCPY(state->prf_buf + XMSS_SHA256_32_PAD_LEN, pk_seed,
|
|
|
+ XMSS_SHA256_32_N);
|
|
|
+
|
|
|
+ /* Set key mask to initial value and append encoding. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->prf_buf,
|
|
|
+ XMSS_SHA256_32_PAD_LEN + XMSS_SHA256_32_N);
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Copy state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_CACHE(state);
|
|
|
+ /* Copy in remaining 32 bytes to buffer. */
|
|
|
+ XMSS_SHA256_SET_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, key);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate n-byte mask - BM_0. */
|
|
|
+ if (ret == 0) {
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, bm0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate n-byte mask - BM_1. */
|
|
|
+ if (ret == 0) {
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 2;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, bm1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_H, pad, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ /* XOR into bm0 and bm1. */
|
|
|
+ xorbuf(bm0, data, 2 * XMSS_SHA256_32_N);
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->buf,
|
|
|
+ XMSS_RAND_HASH_DATA_LEN_SHA256_32);
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, hash);
|
|
|
+ }
|
|
|
+ if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ /* Encode padding byte for PRF. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, state->prf_buf, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ /* Append public seed for PRF. */
|
|
|
+ XMEMCPY(state->prf_buf + XMSS_SHA256_32_PAD_LEN, pk_seed,
|
|
|
+ XMSS_SHA256_32_N);
|
|
|
+
|
|
|
+ /* Set key mask to initial value and append encoding. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, key);
|
|
|
+ /* Calculate n-byte mask - BM_0. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, bm0);
|
|
|
+ /* Calculate n-byte mask - BM_1. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 2;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, bm1);
|
|
|
+
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_H, state->buf, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ xorbuf(bm0, data, 2 * XMSS_SHA256_32_N);
|
|
|
+ wc_xmss_hash(state, state->buf, XMSS_RAND_HASH_DATA_LEN_SHA256_32, hash);
|
|
|
+#endif /* WC_XMSS_FULL_HASH */
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+
|
|
|
+/* Randomized tree hashing.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.4, Algorithm 7: RAND_HASH
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM_0 = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(2);
|
|
|
+ * BM_1 = PRF(SEED, ADRS);
|
|
|
+ * return H(KEY, (LEFT XOR BM_0) || (RIGHT XOR BM_1));
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] data Input data.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_rand_hash(XmssState* state, const byte* data,
|
|
|
+ const byte* pk_seed, HashAddress addr, byte* hash)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+ if ((params->pad_len == XMSS_SHA256_32_PAD_LEN) &&
|
|
|
+ (params->n == XMSS_SHA256_32_N) &&
|
|
|
+ (params->hash == WC_HASH_TYPE_SHA256)) {
|
|
|
+ wc_xmss_rand_hash_sha256_32(state, data, pk_seed, addr, hash);
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+ {
|
|
|
+ byte* addr_buf = state->prf_buf + params->pad_len + params->n;
|
|
|
+ /* Offsets into rand hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + params->pad_len;
|
|
|
+ byte* bm0 = key + params->n;
|
|
|
+ byte* bm1 = bm0 + params->n;
|
|
|
+ const word32 len = params->pad_len + params->n + WC_XMSS_ADDR_LEN;
|
|
|
+
|
|
|
+ /* Encode padding byte for PRF. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, state->prf_buf, params->pad_len);
|
|
|
+ /* Append public seed for PRF. */
|
|
|
+ XMEMCPY(state->prf_buf + params->pad_len, pk_seed, params->n);
|
|
|
+
|
|
|
+ /* Set key mask to initial value and append encoding. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, key);
|
|
|
+ /* Calculate n-byte mask - BM_0. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, bm0);
|
|
|
+ /* Calculate n-byte mask - BM_1. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 2;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, bm1);
|
|
|
+
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_H, pad, params->pad_len);
|
|
|
+ xorbuf(bm0, data, 2 * params->n);
|
|
|
+ wc_xmss_hash(state, state->buf, params->pad_len + 3 * params->n,
|
|
|
+ hash);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) || defined(WOLFSSL_XMSS_VERIFY_ONLY)
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+/* Randomized tree hashing.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.4, Algorithm 7: RAND_HASH
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM_0 = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(2);
|
|
|
+ * BM_1 = PRF(SEED, ADRS);
|
|
|
+ * return H(KEY, (LEFT XOR BM_0) || (RIGHT XOR BM_1));
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] left First half of data.
|
|
|
+ * @param [in] right Second half of data.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_rand_hash_lr_sha256_32(XmssState* state, const byte* left,
|
|
|
+ const byte* right, const byte* pk_seed, HashAddress addr, byte* hash)
|
|
|
+{
|
|
|
+ byte* addr_buf = state->prf_buf + XMSS_SHA256_32_PAD_LEN +
|
|
|
+ XMSS_SHA256_32_N;
|
|
|
+ /* Offsets into rand hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + XMSS_SHA256_32_PAD_LEN;
|
|
|
+ byte* bm0 = key + XMSS_SHA256_32_N;
|
|
|
+ byte* bm1 = bm0 + XMSS_SHA256_32_N;
|
|
|
+#ifndef WC_XMSS_FULL_HASH
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Encode padding byte for PRF. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, state->prf_buf, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ /* Append public seed for PRF. */
|
|
|
+ XMEMCPY(state->prf_buf + XMSS_SHA256_32_PAD_LEN, pk_seed,
|
|
|
+ XMSS_SHA256_32_N);
|
|
|
+
|
|
|
+ /* Set key mask to initial value and append encoding. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->prf_buf,
|
|
|
+ XMSS_SHA256_32_PAD_LEN + XMSS_SHA256_32_N);
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Copy state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_CACHE(state);
|
|
|
+ /* Copy in remaining 32 bytes to buffer. */
|
|
|
+ XMSS_SHA256_SET_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, key);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate n-byte mask - BM_0. */
|
|
|
+ if (ret == 0) {
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, bm0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate n-byte mask - BM_1. */
|
|
|
+ if (ret == 0) {
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 2;
|
|
|
+ /* Copy back state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_RESTORE_DATA(state, addr_buf, WC_XMSS_ADDR_LEN,
|
|
|
+ XMSS_HASH_PRF_DATA_LEN_SHA256_32);
|
|
|
+ /* Calculate hash. */
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, bm1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_H, pad, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ /* XOR into bm0 and bm1. */
|
|
|
+ XMEMCPY(state->prf_buf, left, XMSS_SHA256_32_N);
|
|
|
+ XMEMCPY(state->prf_buf + XMSS_SHA256_32_N, right, XMSS_SHA256_32_N);
|
|
|
+ xorbuf(bm0, state->prf_buf, 2 * XMSS_SHA256_32_N);
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->buf,
|
|
|
+ XMSS_RAND_HASH_DATA_LEN_SHA256_32);
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, hash);
|
|
|
+ }
|
|
|
+ if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ /* Encode padding byte for PRF. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, state->prf_buf, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ /* Append public seed for PRF. */
|
|
|
+ XMEMCPY(state->prf_buf + XMSS_SHA256_32_PAD_LEN, pk_seed, XMSS_SHA256_32_N);
|
|
|
+
|
|
|
+ /* Set key mask to initial value and append encoding. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, key);
|
|
|
+ /* Calculate n-byte mask - BM_0. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, bm0);
|
|
|
+ /* Calculate n-byte mask - BM_1. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 2;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, XMSS_HASH_PRF_DATA_LEN_SHA256_32, bm1);
|
|
|
+
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_H, state->buf, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ XMEMCPY(state->prf_buf, left, XMSS_SHA256_32_N);
|
|
|
+ XMEMCPY(state->prf_buf + XMSS_SHA256_32_N, right, XMSS_SHA256_32_N);
|
|
|
+ xorbuf(bm0, state->prf_buf, 2 * XMSS_SHA256_32_N);
|
|
|
+ wc_xmss_hash(state, state->buf, XMSS_RAND_HASH_DATA_LEN_SHA256_32, hash);
|
|
|
+#endif /* WC_XMSS_FULL_HASH */
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+/* Randomized tree hashing - left and right separate parameters.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.4, Algorithm 7: RAND_HASH
|
|
|
+ * ...
|
|
|
+ * ADRS.setKeyAndMask(0);
|
|
|
+ * KEY = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(1);
|
|
|
+ * BM_0 = PRF(SEED, ADRS);
|
|
|
+ * ADRS.setKeyAndMask(2);
|
|
|
+ * BM_1 = PRF(SEED, ADRS);
|
|
|
+ * return H(KEY, (LEFT XOR BM_0) || (RIGHT XOR BM_1));
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] left First half of data.
|
|
|
+ * @param [in] right Second half of data.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_rand_hash_lr(XmssState* state, const byte* left,
|
|
|
+ const byte* right, const byte* pk_seed, HashAddress addr, byte* hash)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+ if ((params->pad_len == XMSS_SHA256_32_PAD_LEN) &&
|
|
|
+ (params->n == XMSS_SHA256_32_N) &&
|
|
|
+ (params->hash == WC_HASH_TYPE_SHA256)) {
|
|
|
+ wc_xmss_rand_hash_lr_sha256_32(state, left, right, pk_seed, addr, hash);
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+ {
|
|
|
+ byte* addr_buf = state->prf_buf + params->pad_len + params->n;
|
|
|
+ /* Offsets into rand hash data. */
|
|
|
+ byte* pad = state->buf;
|
|
|
+ byte* key = pad + params->pad_len;
|
|
|
+ byte* bm0 = key + params->n;
|
|
|
+ byte* bm1 = bm0 + params->n;
|
|
|
+ const word32 len = params->pad_len + params->n + WC_XMSS_ADDR_LEN;
|
|
|
+
|
|
|
+ /* Encode padding byte for PRF. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, state->prf_buf, params->pad_len);
|
|
|
+ /* Append public seed for PRF. */
|
|
|
+ XMEMCPY(state->prf_buf + params->pad_len, pk_seed, params->n);
|
|
|
+
|
|
|
+ /* Set key mask to initial value and append encoding. */
|
|
|
+ addr[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+ /* Calculate n-byte key - KEY. */
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, key);
|
|
|
+ /* Calculate n-byte mask - BM_0. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 1;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, bm0);
|
|
|
+ /* Calculate n-byte mask - BM_1. */
|
|
|
+ addr_buf[XMSS_ADDR_KEY_MASK * 4 + 3] = 2;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, bm1);
|
|
|
+
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_H, pad, params->pad_len);
|
|
|
+ XMEMCPY(state->prf_buf, left, params->n);
|
|
|
+ XMEMCPY(state->prf_buf + params->n, right, params->n);
|
|
|
+ xorbuf(bm0, state->prf_buf, 2 * params->n);
|
|
|
+ wc_xmss_hash(state, state->buf, params->pad_len + 3 * params->n,
|
|
|
+ hash);
|
|
|
+ }
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL || WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
+
|
|
|
+/* Compute message hash from the random r, root, index and message.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.9, Algorithm 12: XMSS_sign
|
|
|
+ * ...
|
|
|
+ * byte[n] M' = H_msg(r || getRoot(SK) || (toByte(idx_sig, n)), M);
|
|
|
+ * RFC 8391: 5.1
|
|
|
+ * H_msg: SHA2-256(toByte(2, 32) || KEY || M)
|
|
|
+ * H_msg: SHA2-512(toByte(2, 64) || KEY || M)
|
|
|
+ * H_msg: SHAKE128(toByte(2, 32) || KEY || M, 256)
|
|
|
+ * H_msg: SHAKE256(toByte(2, 64) || KEY || M, 512)
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] random Random value of n bytes.
|
|
|
+ * @param [in] root Public root.
|
|
|
+ * @param [in] idx Buffer holding encoded index.
|
|
|
+ * @param [in] idx_len Length of encoded index in bytes.
|
|
|
+ * @param [in] m Message to hash.
|
|
|
+ * @param [in] mlen Length of message.
|
|
|
+ * @param [out] hash Buffer to hold hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_hash_message(XmssState* state, const byte* random,
|
|
|
+ const byte* root, const byte* idx, word8 idx_len, const byte* m,
|
|
|
+ word32 mlen, byte* hash)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ word32 padKeyLen = params->pad_len + 3 * params->n;
|
|
|
+ /* Offsets into message hash data. */
|
|
|
+ byte* padKey = state->buf;
|
|
|
+ byte* pad = padKey;
|
|
|
+ byte* key = pad + params->pad_len;
|
|
|
+ byte* root_sk = key + params->n;
|
|
|
+ byte* idx_sig = root_sk + params->n;
|
|
|
+
|
|
|
+ /* Set prefix data before message. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_HASH, pad, params->pad_len);
|
|
|
+ XMEMCPY(key, random, params->n);
|
|
|
+ XMEMCPY(root_sk, root, params->n);
|
|
|
+ XMEMSET(idx_sig, 0, params->n - idx_len);
|
|
|
+ XMEMCPY(idx_sig + params->n - idx_len, idx, idx_len);
|
|
|
+
|
|
|
+ /* Hash the padding and key first. */
|
|
|
+#ifdef WC_XMSS_SHA256
|
|
|
+ if (params->hash == WC_HASH_TYPE_SHA256) {
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, padKey, padKeyLen);
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHA256 */
|
|
|
+#ifdef WC_XMSS_SHA512
|
|
|
+ if (params->hash == WC_HASH_TYPE_SHA512) {
|
|
|
+ ret = wc_Sha512Update(&state->digest.sha512, padKey, padKeyLen);
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHA512 */
|
|
|
+#ifdef WC_XMSS_SHAKE128
|
|
|
+ if (params->hash == WC_HASH_TYPE_SHAKE128) {
|
|
|
+ ret = wc_Shake128_Update(&state->digest.shake, padKey, padKeyLen);
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHAKE128 */
|
|
|
+#ifdef WC_XMSS_SHAKE256
|
|
|
+ if (params->hash == WC_HASH_TYPE_SHAKE256) {
|
|
|
+ ret = wc_Shake256_Update(&state->digest.shake, padKey, padKeyLen);
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* WC_XMSS_SHAKE256 */
|
|
|
+ {
|
|
|
+ /* Unsupported digest function. */
|
|
|
+ ret = NOT_COMPILED_IN;
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Generate hash of message - M'. */
|
|
|
+ wc_xmss_hash(state, m, mlen, hash);
|
|
|
+ }
|
|
|
+ else if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#ifndef WOLFSSL_XMSS_VERIFY_ONLY
|
|
|
+
|
|
|
+/* Compute PRF with key and message.
|
|
|
+ *
|
|
|
+ * RFC 8391: 5.1
|
|
|
+ * PRF: SHA2-256(toByte(3, 32) || KEY || M)
|
|
|
+ * PRF: SHA2-512(toByte(3, 64) || KEY || M)
|
|
|
+ * PRF: SHAKE128(toByte(3, 32) || KEY || M, 256)
|
|
|
+ * PRF: SHAKE256(toByte(3, 64) || KEY || M, 512)
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] key Key used to derive pseudo-random from.
|
|
|
+ * @param [in] m 32 bytes of data to derive pseudo-random from.
|
|
|
+ * @param [out] prf Buffer to hold pseudo-random data.
|
|
|
+ */
|
|
|
+static void wc_xmss_prf(XmssState* state, const byte* key, const byte* m,
|
|
|
+ byte* prf)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* pad = state->prf_buf;
|
|
|
+ byte* key_buf = pad + params->pad_len;
|
|
|
+ byte* m_buf = key_buf + params->n;
|
|
|
+
|
|
|
+ /* 00[0..pl-1] || 03 || key[0..n-1] || m[0..31] */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, pad, params->pad_len);
|
|
|
+ XMEMCPY(key_buf, key, params->n);
|
|
|
+ XMEMCPY(m_buf, m, XMSS_PRF_M_LEN);
|
|
|
+
|
|
|
+ /* Hash the PRF data. */
|
|
|
+ wc_xmss_hash(state, state->prf_buf, params->pad_len + params->n +
|
|
|
+ XMSS_PRF_M_LEN, prf);
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef XMSS_CALL_PRF_KEYGEN
|
|
|
+/* Compute PRF for keygen with key and message.
|
|
|
+ *
|
|
|
+ * NIST SP 800-208: 5.1, 5.2, 5.3, 5.4
|
|
|
+ * PRFkeygen (KEY, M): SHA-256(toByte(4, 32) || KEY || M)
|
|
|
+ * PRFkeygen (KEY, M): T192(SHA-256(toByte(4, 4) || KEY || M))
|
|
|
+ * PRFkeygen (KEY, M): SHAKE256(toByte(4, 32) || KEY || M, 256)
|
|
|
+ * PRFkeygen (KEY, M): SHAKE256(toByte(4, 4) || KEY || M, 192)
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] key Key of n bytes used to derive pseudo-random from.
|
|
|
+ * @param [in] m n + 32 bytes of data to derive pseudo-random from.
|
|
|
+ * @param [out] prf Buffer to hold pseudo-random data.
|
|
|
+ */
|
|
|
+static void wc_xmss_prf_keygen(XmssState* state, const byte* key,
|
|
|
+ const byte* m, byte* prf)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* pad = state->prf_buf;
|
|
|
+ byte* key_buf = pad + params->pad_len;
|
|
|
+ byte* m_buf = key_buf + params->n;
|
|
|
+
|
|
|
+ /* 00[0..pl-1] || 04 || key[0..n-1] || m[0..n+31] */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF_KEYGEN, pad, params->pad_len);
|
|
|
+ XMEMCPY(key_buf, key, params->n);
|
|
|
+ XMEMCPY(m_buf, m, params->n + XMSS_PRF_M_LEN);
|
|
|
+
|
|
|
+ /* Hash the PRF keygen data. */
|
|
|
+ wc_xmss_hash(state, state->prf_buf, params->pad_len + 2 * params->n +
|
|
|
+ XMSS_PRF_M_LEN, prf);
|
|
|
+}
|
|
|
+#endif /* XMSS_CALL_PRF_KEYGEN */
|
|
|
+
|
|
|
+#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * WOTS
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+#ifndef WOLFSSL_XMSS_VERIFY_ONLY
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+/* Expand private seed with PRF keygen.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.3
|
|
|
+ * "the existence of a method getWOTS_SK(SK, i) is assumed"
|
|
|
+ * NIST SP 800-208: 7.2.1, Algorithm 10'
|
|
|
+ * ...
|
|
|
+ * for ( j=0; j < len; j++) {
|
|
|
+ * ADRS.setChainAddress(j);
|
|
|
+ * sk[j] = PRFkeygen(S_XMSS, SEED || ADRS);
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] sk_seed Buffer holding private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address as a byte array.
|
|
|
+ * @param [out] gen_seed Buffer to hold seeds.
|
|
|
+ */
|
|
|
+static void wc_xmss_wots_get_wots_sk_sha256_32(XmssState* state,
|
|
|
+ const byte* sk_seed, const byte* pk_seed, byte* addr, byte* gen_seed)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ word32 i;
|
|
|
+ byte* pad = state->prf_buf;
|
|
|
+ byte* s_xmss = pad + XMSS_SHA256_32_PAD_LEN;
|
|
|
+ byte* seed = s_xmss + XMSS_SHA256_32_N;
|
|
|
+ byte* addr_buf = seed + XMSS_SHA256_32_N;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ((word32*)addr)[XMSS_ADDR_CHAIN] = 0;
|
|
|
+ ((word32*)addr)[XMSS_ADDR_HASH] = 0;
|
|
|
+ ((word32*)addr)[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF_KEYGEN, pad, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ XMEMCPY(s_xmss, sk_seed, XMSS_SHA256_32_N);
|
|
|
+ XMEMCPY(seed, pk_seed, XMSS_SHA256_32_N);
|
|
|
+ XMEMCPY(addr_buf, addr, WC_XMSS_ADDR_LEN);
|
|
|
+
|
|
|
+#ifndef WC_XMSS_FULL_HASH
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, pad, XMSS_SHA256_32_PAD_LEN +
|
|
|
+ XMSS_SHA256_32_N);
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Copy state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_CACHE(state);
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, seed, XMSS_SHA256_32_N +
|
|
|
+ WC_XMSS_ADDR_LEN);
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, gen_seed);
|
|
|
+ }
|
|
|
+ for (i = 1; (ret == 0) && (i < params->wots_len); i++) {
|
|
|
+ gen_seed += XMSS_SHA256_32_N;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ XMSS_SHA256_STATE_RESTORE(state, 64);
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, seed, XMSS_SHA256_32_N +
|
|
|
+ WC_XMSS_ADDR_LEN);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, gen_seed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#else
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->prf_buf,
|
|
|
+ XMSS_SHA256_32_PAD_LEN + 2 * XMSS_SHA256_32_N + WC_XMSS_ADDR_LEN);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, gen_seed);
|
|
|
+ }
|
|
|
+ for (i = 1; (ret == 0) && i < params->wots_len; i++) {
|
|
|
+ gen_seed += XMSS_SHA256_32_N;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->prf_buf,
|
|
|
+ XMSS_SHA256_32_PAD_LEN + 2 * XMSS_SHA256_32_N + WC_XMSS_ADDR_LEN);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = wc_Sha256Final(&state->digest.sha256, gen_seed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif /* WC_XMSS_FULL_HASH*/
|
|
|
+
|
|
|
+ if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+
|
|
|
+/* Expand private seed with PRF keygen.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.3
|
|
|
+ * "the existence of a method getWOTS_SK(SK, i) is assumed"
|
|
|
+ * NIST SP 800-208: 7.2.1
|
|
|
+ * Algorithm 10'
|
|
|
+ * ...
|
|
|
+ * for ( j=0; j < len; j++) {
|
|
|
+ * ADRS.setChainAddress(j);
|
|
|
+ * sk[j] = PRFkeygen(S_XMSS, SEED || ADRS);
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] sk_seed Buffer holding private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address as a byte array.
|
|
|
+ * @param [out] gen_seed Buffer to hold seeds.
|
|
|
+ */
|
|
|
+static void wc_xmss_wots_get_wots_sk(XmssState* state, const byte* sk_seed,
|
|
|
+ const byte* pk_seed, byte* addr, byte* gen_seed)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ word32 i;
|
|
|
+#ifdef XMSS_CALL_PRF_KEYGEN
|
|
|
+ byte* seed = state->buf;
|
|
|
+ byte* addr_buf = seed + params->n;
|
|
|
+#else
|
|
|
+ byte* pad = state->prf_buf;
|
|
|
+ byte* s_xmss = pad + params->pad_len;
|
|
|
+ byte* seed = s_xmss + params->n;
|
|
|
+ byte* addr_buf = seed + params->n;
|
|
|
+ const word32 len = params->pad_len + params->n * 2 + WC_XMSS_ADDR_LEN;
|
|
|
+#endif /* XMSS_CALL_PRF_KEYGEN */
|
|
|
+
|
|
|
+ /* Ensure hash address fields are 0. */
|
|
|
+ ((word32*)addr)[XMSS_ADDR_CHAIN] = 0;
|
|
|
+ ((word32*)addr)[XMSS_ADDR_HASH] = 0;
|
|
|
+ ((word32*)addr)[XMSS_ADDR_KEY_MASK] = 0;
|
|
|
+
|
|
|
+#ifdef XMSS_CALL_PRF_KEYGEN
|
|
|
+ /* Copy the seed and address into PRF keygen message buffer. */
|
|
|
+ XMEMCPY(seed, pk_seed, params->n);
|
|
|
+ XMEMCPY(addr_buf, addr, WC_XMSS_ADDR_LEN);
|
|
|
+
|
|
|
+ wc_xmss_prf_keygen(state, sk_seed, state->buf, gen_seed);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ gen_seed += params->n;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_prf_keygen(state, sk_seed, state->buf, gen_seed);
|
|
|
+ }
|
|
|
+#else
|
|
|
+ /* Copy the PRF keygen fields into one buffer. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF_KEYGEN, pad, params->pad_len);
|
|
|
+ XMEMCPY(s_xmss, sk_seed, params->n);
|
|
|
+ XMEMCPY(seed, pk_seed, params->n);
|
|
|
+ XMEMCPY(addr_buf, addr, WC_XMSS_ADDR_LEN);
|
|
|
+
|
|
|
+ /* Fill output with hashes of different chain hash addresses. */
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, gen_seed);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ gen_seed += params->n;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_hash(state, state->prf_buf, len, gen_seed);
|
|
|
+ }
|
|
|
+#endif /* XMSS_CALL_PRF_KEYGEN */
|
|
|
+}
|
|
|
+
|
|
|
+#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+/* Chain hashing to calculate node hash.
|
|
|
+ *
|
|
|
+ * RFC 8391: 3.1.2, Algorithm 2 - recursive.
|
|
|
+ * This function is an iterative version.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] data Initial data to hash.
|
|
|
+ * @param [in] start Starting hash value in hash address.
|
|
|
+ * @param [in] steps Size of step.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address as a byte array.
|
|
|
+ * @param [out] hash Chained hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_chain_sha256_32(XmssState* state, const byte* data,
|
|
|
+ unsigned int start, unsigned int steps, const byte* pk_seed, byte* addr,
|
|
|
+ byte* hash)
|
|
|
+{
|
|
|
+ if (steps > 0) {
|
|
|
+ word32 i;
|
|
|
+ byte* pad = state->prf_buf;
|
|
|
+ byte* seed = pad + XMSS_SHA256_32_PAD_LEN;
|
|
|
+#ifndef WC_XMSS_FULL_HASH
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Set data for PRF hash. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, pad, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ XMEMCPY(seed, pk_seed, XMSS_SHA256_32_N);
|
|
|
+
|
|
|
+ /* Hash first 64 bytes. */
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, state->prf_buf,
|
|
|
+ XMSS_SHA256_32_PAD_LEN + XMSS_SHA256_32_N);
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Copy state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_CACHE(state);
|
|
|
+ /* Only do this once for all chain hash calls. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_F, state->buf,
|
|
|
+ state->params->pad_len);
|
|
|
+
|
|
|
+ /* Set address. */
|
|
|
+ XMSS_ADDR_SET_BYTE(addr, XMSS_ADDR_HASH, start);
|
|
|
+ wc_xmss_chain_hash_sha256_32(state, data, addr, hash);
|
|
|
+ /* Iterate 'steps' calls to the hash function. */
|
|
|
+ for (i = start+1; i < (start+steps) && i < XMSS_WOTS_W; i++) {
|
|
|
+ addr[XMSS_ADDR_HASH * 4 + 3] = i;
|
|
|
+ wc_xmss_chain_hash_sha256_32(state, hash, addr, hash);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* addr_buf = seed + XMSS_SHA256_32_N;
|
|
|
+
|
|
|
+ /* Set data for PRF hash. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, pad, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ XMEMCPY(seed, pk_seed, params->n);
|
|
|
+ XMEMCPY(addr_buf, addr, WC_XMSS_ADDR_LEN);
|
|
|
+
|
|
|
+ /* Only do this once for all chain hash calls. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_F, state->buf, params->pad_len);
|
|
|
+
|
|
|
+ /* Set address. */
|
|
|
+ XMSS_ADDR_SET_BYTE(addr_buf, XMSS_ADDR_HASH, start);
|
|
|
+ wc_xmss_chain_hash_sha256_32(state, data, hash);
|
|
|
+ /* Iterate 'steps' calls to the hash function. */
|
|
|
+ for (i = start+1; i < (start+steps) && i < XMSS_WOTS_W; i++) {
|
|
|
+ addr_buf[XMSS_ADDR_HASH * 4 + 3] = i;
|
|
|
+ wc_xmss_chain_hash_sha256_32(state, hash, hash);
|
|
|
+ }
|
|
|
+#endif /* !WC_XMSS_FULL_HASH */
|
|
|
+ }
|
|
|
+ else if (hash != data) {
|
|
|
+ XMEMCPY(hash, data, XMSS_SHA256_32_N);
|
|
|
+ }
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+
|
|
|
+/* Chain hashing to calculate node hash.
|
|
|
+ *
|
|
|
+ * RFC 8391: 3.1.2, Algorithm 2 - recursive.
|
|
|
+ * This function is an iterative version.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] data Initial data to hash.
|
|
|
+ * @param [in] start Starting hash value in hash address.
|
|
|
+ * @param [in] steps Size of step.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address as a byte array.
|
|
|
+ * @param [out] hash Chained hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_chain(XmssState* state, const byte* data,
|
|
|
+ unsigned int start, unsigned int steps, const byte* pk_seed, byte* addr,
|
|
|
+ byte* hash)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+
|
|
|
+ if (steps > 0) {
|
|
|
+ word32 i;
|
|
|
+ byte* pad = state->prf_buf;
|
|
|
+ byte* seed = pad + params->pad_len;
|
|
|
+ byte* addr_buf = seed + params->n;
|
|
|
+
|
|
|
+ /* Set data for PRF hash. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, pad, params->pad_len);
|
|
|
+ XMEMCPY(seed, pk_seed, params->n);
|
|
|
+ XMEMCPY(addr_buf, addr, 32);
|
|
|
+
|
|
|
+ /* Only do this once for all chain hash calls. */
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_F, state->buf, params->pad_len);
|
|
|
+
|
|
|
+ /* Set address. */
|
|
|
+ XMSS_ADDR_SET_BYTE(addr_buf, XMSS_ADDR_HASH, start);
|
|
|
+ wc_xmss_chain_hash(state, data, hash);
|
|
|
+ /* Iterate 'steps' calls to the hash function. */
|
|
|
+ for (i = start+1; i < (start+steps) && i < XMSS_WOTS_W; i++) {
|
|
|
+ addr_buf[XMSS_ADDR_HASH * 4 + 3] = i;
|
|
|
+ wc_xmss_chain_hash(state, hash, hash);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (hash != data) {
|
|
|
+ XMEMCPY(hash, data, params->n);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Convert base on message and add checksum.
|
|
|
+ *
|
|
|
+ * RFC 8391:, 2.6, Algorithm 1: base_w
|
|
|
+ * int in = 0;
|
|
|
+ * int out = 0;
|
|
|
+ * unsigned int total = 0;
|
|
|
+ * int bits = 0;
|
|
|
+ * int consumed;
|
|
|
+ *
|
|
|
+ * for ( consumed = 0; consumed < out_len; consumed++ ) {
|
|
|
+ * if ( bits == 0 ) {
|
|
|
+ * total = X[in];
|
|
|
+ * in++;
|
|
|
+ * bits += 8;
|
|
|
+ * }
|
|
|
+ * bits -= lg(w);
|
|
|
+ * basew[out] = (total >> bits) AND (w - 1);
|
|
|
+ * out++;
|
|
|
+ * }
|
|
|
+ * return basew;
|
|
|
+ *
|
|
|
+ * base_w implemented for w == 16 (lg(w) == 4).
|
|
|
+ *
|
|
|
+ * RFC 8391: 3.1.5, Algorithm 5:
|
|
|
+ * ...
|
|
|
+ * csum = 0;
|
|
|
+ *
|
|
|
+ * # Convert message to base w
|
|
|
+ * msg = base_w(M, w, len_1);
|
|
|
+ * # Compute checksum
|
|
|
+ * for ( i = 0; i < len_1; i++ ) {
|
|
|
+ * csum = csum + w - 1 - msg[i];
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * # Convert csum to base w
|
|
|
+ * csum = csum << ( 8 - ( ( len_2 * lg(w) ) % 8 ));
|
|
|
+ * len_2_bytes = ceil( ( len_2 * lg(w) ) / 8 );
|
|
|
+ * msg = msg || base_w(toByte(csum, len_2_bytes), w, len_2);
|
|
|
+ *
|
|
|
+ * len_1 == 8 * n / 4 = n * 2
|
|
|
+ * Implemented for len_2 == 3
|
|
|
+ *
|
|
|
+ * @param [in] m Message data.
|
|
|
+ * @param [in] n Number of bytes in hash.
|
|
|
+ * @param [out] msg Message in new base.
|
|
|
+ */
|
|
|
+static void wc_xmss_msg_convert(const byte* m, word8 n, word8* msg)
|
|
|
+{
|
|
|
+ word8 i;
|
|
|
+ word16 csum = 0;
|
|
|
+
|
|
|
+ /* Split each full byte of m into two bytes of msg. */
|
|
|
+ for (i = 0; i < n; i++) {
|
|
|
+ msg[0] = m[i] >> 4;
|
|
|
+ msg[1] = m[i] & 0xf;
|
|
|
+ csum += XMSS_WOTS_W - 1 - msg[0];
|
|
|
+ csum += XMSS_WOTS_W - 1 - msg[1];
|
|
|
+ msg += 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Append checksum to message. (Maximum value: 1920 = 64 * 2 * 15) */
|
|
|
+ msg[0] = (csum >> 8) ;
|
|
|
+ msg[1] = (csum >> 4) & 0x0f;
|
|
|
+ msg[2] = (csum ) & 0x0f;
|
|
|
+}
|
|
|
+
|
|
|
+#ifndef WOLFSSL_XMSS_VERIFY_ONLY
|
|
|
+
|
|
|
+/* WOTS+ generate public key with private seed.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.6, Algorithm 9:
|
|
|
+ * ...
|
|
|
+ * pk = WOTS_genPK (getWOTS_SK(SK, s + i), SEED, ADRS);
|
|
|
+ * RFC 8391, 3.1.4, Algorithm 4: WOTS_genPK
|
|
|
+ * ...
|
|
|
+ * for ( i = 0; i < len; i++ ) {
|
|
|
+ * ADRS.setChainAddress(i);
|
|
|
+ * pk[i] = chain(sk[i], 0, w - 1, SEED, ADRS);
|
|
|
+ * }
|
|
|
+ * return pk;
|
|
|
+ *
|
|
|
+ * WOTS_genPK only used in Algorithm 9 and it is convenient to combine with
|
|
|
+ * getWOTS_SK due to parameter specific implementations.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] sk Random private seed.
|
|
|
+ * @param [in] seed Random public seed.
|
|
|
+ * @param [in] addr Hashing address.
|
|
|
+ * @param [out] pk Public key.
|
|
|
+ */
|
|
|
+static void wc_xmss_wots_gen_pk(XmssState* state, const byte* sk,
|
|
|
+ const byte* seed, HashAddress addr, byte* pk)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* addr_buf = state->encMsg;
|
|
|
+ word32 i;
|
|
|
+
|
|
|
+ /* Ensure chain address is 0 and encode into a buffer. */
|
|
|
+ addr[XMSS_ADDR_CHAIN] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+ if ((params->pad_len == XMSS_SHA256_32_PAD_LEN) &&
|
|
|
+ (params->n == XMSS_SHA256_32_N) &&
|
|
|
+ (params->hash == WC_HASH_TYPE_SHA256)) {
|
|
|
+ /* Expand the private seed - getWOTS_SK */
|
|
|
+ wc_xmss_wots_get_wots_sk_sha256_32(state, sk, seed, addr_buf,
|
|
|
+ pk);
|
|
|
+
|
|
|
+ /* Calculate chain hash. */
|
|
|
+ wc_xmss_chain_sha256_32(state, pk, 0, XMSS_WOTS_W - 1, seed, addr_buf,
|
|
|
+ pk);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ pk += params->n;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_chain_sha256_32(state, pk, 0, XMSS_WOTS_W - 1, seed,
|
|
|
+ addr_buf, pk);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+ {
|
|
|
+ /* Expand the private seed - getWOTS_SK */
|
|
|
+ wc_xmss_wots_get_wots_sk(state, sk, seed, addr_buf, pk);
|
|
|
+
|
|
|
+ /* Calculate chain hash. */
|
|
|
+ wc_xmss_chain(state, pk, 0, XMSS_WOTS_W - 1, seed, addr_buf, pk);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ pk += params->n;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_chain(state, pk, 0, XMSS_WOTS_W - 1, seed, addr_buf, pk);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+/* Generate a signature from a privatge key and message.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.9, Algorithm 11: treeSig
|
|
|
+ * sig_ots = WOTS_sign(getWOTS_SK(SK, idx_sig),
|
|
|
+ * M', getSEED(SK), ADRS);
|
|
|
+ * RFC 8391: 3.1.5, Algorithm 5: WOTS_sign
|
|
|
+ * (Convert message to base w and append checksum in base w)
|
|
|
+ * ...
|
|
|
+ * for ( i = 0; i < len; i++ ) {
|
|
|
+ * ADRS.setChainAddress(i);
|
|
|
+ * sig[i] = chain(sk[i], 0, msg[i], SEED, ADRS);
|
|
|
+ * }
|
|
|
+ * return sig;
|
|
|
+ *
|
|
|
+ * WOTS_sign only used in Algorithm 11 and convenient to do getWOTS_SK due to
|
|
|
+ * hash address reuse and parameter specific implementations.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] m Message hash to sign.
|
|
|
+ * @param [in] sk Random private seed.
|
|
|
+ * @param [in] seed Random public seed.
|
|
|
+ * @param [in] addr Hashing address.
|
|
|
+ * @param [out] sig Calculated XMSS/MT signature.
|
|
|
+ */
|
|
|
+static void wc_xmss_wots_sign(XmssState* state, const byte* m,
|
|
|
+ const byte* sk, const byte* seed, HashAddress addr, byte* sig)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* addr_buf = state->pk;
|
|
|
+ word32 i;
|
|
|
+
|
|
|
+ /* Convert message to base w and append checksum in base w. */
|
|
|
+ wc_xmss_msg_convert(m, params->n, state->encMsg);
|
|
|
+
|
|
|
+ /* Set initial chain value and encode hash address. */
|
|
|
+ addr[XMSS_ADDR_CHAIN] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+ if ((params->pad_len == XMSS_SHA256_32_PAD_LEN) &&
|
|
|
+ (params->n == XMSS_SHA256_32_N) &&
|
|
|
+ (params->hash == WC_HASH_TYPE_SHA256)) {
|
|
|
+ /* Expand the private seed - getWOTS_SK */
|
|
|
+ wc_xmss_wots_get_wots_sk_sha256_32(state, sk, seed, addr_buf, sig);
|
|
|
+
|
|
|
+ /* Calculate chain hash. */
|
|
|
+ wc_xmss_chain_sha256_32(state, sig, 0, state->encMsg[0], seed, addr_buf,
|
|
|
+ sig);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ sig += params->n;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_chain_sha256_32(state, sig, 0, state->encMsg[i], seed,
|
|
|
+ addr_buf, sig);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+ {
|
|
|
+ /* Expand the private seed - getWOTS_SK */
|
|
|
+ wc_xmss_wots_get_wots_sk(state, sk, seed, addr_buf, sig);
|
|
|
+
|
|
|
+ /* Calculate chain hash. */
|
|
|
+ wc_xmss_chain(state, sig, 0, state->encMsg[0], seed, addr_buf, sig);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ sig += params->n;
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_chain(state, sig, 0, state->encMsg[i], seed, addr_buf, sig);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
+
|
|
|
+/* Compute WOTS+ public key value from signature and message.
|
|
|
+ *
|
|
|
+ * RFC 8319: 3.1.6
|
|
|
+ * Algorithm 6: WOTS_pkFromSig
|
|
|
+ * (Convert message to base w and append checksum in base w)
|
|
|
+ * ...
|
|
|
+ * for ( i = 0; i < len; i++ ) {
|
|
|
+ * ADRS.setChainAddress(i);
|
|
|
+ * tmp_pk[i] = chain(sig[i], msg[i], w - 1 - msg[i], SEED, ADRS);
|
|
|
+ * }
|
|
|
+ * return tmp_pk;
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] sig XMSS/MT Signature.
|
|
|
+ * @param [in] m Message to verify.
|
|
|
+ * @param [in] seed Random public seed.
|
|
|
+ * @param [in] addr Hashing address.
|
|
|
+ * @param [out] pk Public key.
|
|
|
+ */
|
|
|
+static void wc_xmss_wots_pk_from_sig(XmssState* state, const byte* sig,
|
|
|
+ const byte* m, const byte* seed, HashAddress addr, byte* pk)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* addr_buf = state->stack;
|
|
|
+ word32 i;
|
|
|
+
|
|
|
+ /* Convert message to base w and append checksum in base w. */
|
|
|
+ wc_xmss_msg_convert(m, params->n, state->encMsg);
|
|
|
+
|
|
|
+ /* Start with address with chain value of 0. */
|
|
|
+ addr[XMSS_ADDR_CHAIN] = 0;
|
|
|
+ wc_xmss_addr_encode(addr, addr_buf);
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256)
|
|
|
+ if ((params->pad_len == XMSS_SHA256_32_PAD_LEN) &&
|
|
|
+ (params->n == XMSS_SHA256_32_N) &&
|
|
|
+ (params->hash == WC_HASH_TYPE_SHA256)) {
|
|
|
+ /* Calculate chain hash. */
|
|
|
+ wc_xmss_chain_sha256_32(state, sig, state->encMsg[0],
|
|
|
+ XMSS_WOTS_W - 1 - state->encMsg[0], seed, addr_buf, pk);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ sig += params->n;
|
|
|
+ pk += params->n;
|
|
|
+ /* Update chain. */
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_chain_sha256_32(state, sig, state->encMsg[i],
|
|
|
+ XMSS_WOTS_W - 1 - state->encMsg[i], seed, addr_buf, pk);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 */
|
|
|
+ {
|
|
|
+ /* Calculate chain hash. */
|
|
|
+ wc_xmss_chain(state, sig, state->encMsg[0],
|
|
|
+ XMSS_WOTS_W - 1 - state->encMsg[0], seed, addr_buf, pk);
|
|
|
+ for (i = 1; i < params->wots_len; i++) {
|
|
|
+ sig += params->n;
|
|
|
+ pk += params->n;
|
|
|
+ /* Update chain. */
|
|
|
+ addr_buf[XMSS_ADDR_CHAIN * 4 + 3] = i;
|
|
|
+ wc_xmss_chain(state, sig, state->encMsg[i],
|
|
|
+ XMSS_WOTS_W - 1 - state->encMsg[i], seed, addr_buf, pk);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * L-TREE - unbalanced binary hash tree
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Compute leaves of L-tree from WOTS+ public key and compress to single value.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.5, Algorithm 8: ltree
|
|
|
+ * unsigned int len' = len;
|
|
|
+ * ADRS.setTreeHeight(0);
|
|
|
+ * while ( len' > 1 ) {
|
|
|
+ * for ( i = 0; i < floor(len' / 2); i++ ) {
|
|
|
+ * ADRS.setTreeIndex(i);
|
|
|
+ * pk[i] = RAND_HASH(pk[2i], pk[2i + 1], SEED, ADRS);
|
|
|
+ * }
|
|
|
+ * if ( len' % 2 == 1 ) {
|
|
|
+ * pk[floor(len' / 2)] = pk[len' - 1];
|
|
|
+ * }
|
|
|
+ * len' = ceil(len' / 2);
|
|
|
+ * ADRS.setTreeHeight(ADRS.getTreeHeight() + 1);
|
|
|
+ * }
|
|
|
+ * return pk[0];
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] pk WOTS+ public key.
|
|
|
+ * @param [in] seed Random public seed.
|
|
|
+ * @param [in] addr Hashing address.
|
|
|
+ * @param [out] pk0 N-byte compressed public key value pk[0].
|
|
|
+ */
|
|
|
+static void wc_xmss_ltree(XmssState* state, byte* pk, const byte* seed,
|
|
|
+ HashAddress addr, byte* pk0)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ word8 len = params->wots_len;
|
|
|
+ word32 h = 0;
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256) && \
|
|
|
+ !defined(WC_XMSS_FULL_HASH)
|
|
|
+ /* Precompute hash state after first 64 bytes (common to all hashes). */
|
|
|
+ if ((params->pad_len == XMSS_SHA256_32_PAD_LEN) &&
|
|
|
+ (params->n == XMSS_SHA256_32_N) &&
|
|
|
+ (params->hash == WC_HASH_TYPE_SHA256)) {
|
|
|
+ byte* prf_buf = state->prf_buf;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ XMSS_PAD_ENC(XMSS_HASH_PADDING_PRF, prf_buf, XMSS_SHA256_32_PAD_LEN);
|
|
|
+ XMEMCPY(prf_buf + XMSS_SHA256_32_PAD_LEN, seed, XMSS_SHA256_32_N);
|
|
|
+
|
|
|
+ ret = wc_Sha256Update(&state->digest.sha256, prf_buf,
|
|
|
+ XMSS_SHA256_32_PAD_LEN + XMSS_SHA256_32_N);
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Copy state after first 64 bytes. */
|
|
|
+ XMSS_SHA256_STATE_CACHE(state);
|
|
|
+ }
|
|
|
+ else if (state->ret == 0) {
|
|
|
+ /* Store any digest failures for public APIs to return. */
|
|
|
+ state->ret = ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 && !WC_XMSS_FULL_HASH */
|
|
|
+ while (len > 1) {
|
|
|
+ word8 i;
|
|
|
+ word8 len2 = len >> 1;
|
|
|
+
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = h++;
|
|
|
+
|
|
|
+ for (i = 0; i < len2; i++) {
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = i;
|
|
|
+ #if !defined(WOLFSSL_WC_XMSS_SMALL) && defined(WC_XMSS_SHA256) && \
|
|
|
+ !defined(WC_XMSS_FULL_HASH)
|
|
|
+ if ((params->pad_len == XMSS_SHA256_32_PAD_LEN) &&
|
|
|
+ (params->n == XMSS_SHA256_32_N) &&
|
|
|
+ (params->hash == WC_HASH_TYPE_SHA256)) {
|
|
|
+ wc_xmss_rand_hash_sha256_32_prehash(state,
|
|
|
+ pk + i * 2 * XMSS_SHA256_32_N, addr,
|
|
|
+ pk + i * XMSS_SHA256_32_N);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ #endif /* !WOLFSSL_WC_XMSS_SMALL && WC_XMSS_SHA256 &&
|
|
|
+ * !WC_XMSS_FULL_HASH */
|
|
|
+ {
|
|
|
+ wc_xmss_rand_hash(state, pk + i * 2 * params->n,
|
|
|
+ seed, addr, pk + i * params->n);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (len & 1) {
|
|
|
+ XMEMCPY(pk + len2 * params->n, pk + (len - 1) * params->n,
|
|
|
+ params->n);
|
|
|
+ }
|
|
|
+ len = len2 + (len & 1);
|
|
|
+ }
|
|
|
+ /* Return compressed public key value pk[0]. */
|
|
|
+ XMEMCPY(pk0, pk, params->n);
|
|
|
+}
|
|
|
+
|
|
|
+#ifndef WOLFSSL_XMSS_VERIFY_ONLY
|
|
|
+
|
|
|
+#ifdef WOLFSSL_WC_XMSS_SMALL
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * TREE HASH
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+#ifndef WOLFSSL_SMALL_STACK
|
|
|
+/* Compute internal nodes of Merkle tree.
|
|
|
+ *
|
|
|
+ * Implementation always starts at index 0. (s = 0)
|
|
|
+ *
|
|
|
+ * Build authentication path, if required, rather than duplicating work.
|
|
|
+ * When node is generated, copy out to authentication path array of nodes.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.6, Algorithm 9: treeHash
|
|
|
+ * if( s % (1 << t) != 0 ) return -1;
|
|
|
+ * for ( i = 0; i < 2^t; i++ ) {
|
|
|
+ * SEED = getSEED(SK);
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(s + i);
|
|
|
+ * pk = WOTS_genPK (getWOTS_SK(SK, s + i), SEED, ADRS);
|
|
|
+ * ADRS.setType(1); # Type = L-tree address
|
|
|
+ * ADRS.setLTreeAddress(s + i);
|
|
|
+ * node = ltree(pk, SEED, ADRS);
|
|
|
+ * ADRS.setType(2); # Type = hash tree address
|
|
|
+ * ADRS.setTreeHeight(0);
|
|
|
+ * ADRS.setTreeIndex(i + s);
|
|
|
+ * while ( Top node on Stack has same height t' as node ) {
|
|
|
+ * ADRS.setTreeIndex((ADRS.getTreeIndex() - 1) / 2);
|
|
|
+ * node = RAND_HASH(Stack.pop(), node, SEED, ADRS);
|
|
|
+ * ADRS.setTreeHeight(ADRS.getTreeHeight() + 1);
|
|
|
+ * }
|
|
|
+ * Stack.push(node);
|
|
|
+ * }
|
|
|
+ * return Stack.pop();
|
|
|
+ * RFC 8391: 4.1.9, (Example) buildAuth
|
|
|
+ * for ( j = 0; j < h; j++ ) {
|
|
|
+ * k = floor(i / (2^j)) XOR 1;
|
|
|
+ * auth[j] = treeHash(SK, k * 2^j, j, ADRS);
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] sk_seed Random private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] leafIdx Index of lead node.
|
|
|
+ * @param [in] subtree_addr Address of subtree.
|
|
|
+ * @param [out] root Root node of the tree.
|
|
|
+ * @param [out] auth_path Nodes of the authentication path.
|
|
|
+ */
|
|
|
+static void wc_xmss_treehash(XmssState* state, const byte* sk_seed,
|
|
|
+ const byte* pk_seed, word32 leafIdx, const word32* subtree, byte* root,
|
|
|
+ byte* auth_path)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ byte* node = state->stack;
|
|
|
+ HashAddress ots;
|
|
|
+ HashAddress ltree;
|
|
|
+ HashAddress tree;
|
|
|
+ word8 height[WC_XMSS_MAX_TREE_HEIGHT + 1];
|
|
|
+ word8 offset = 0;
|
|
|
+ word32 max = (word32)1 << params->sub_h;
|
|
|
+ word32 i;
|
|
|
+
|
|
|
+ /* Copy hash address into one for each purpose. */
|
|
|
+ XMSS_ADDR_OTS_SET_SUBTREE(ots, subtree);
|
|
|
+ XMSS_ADDR_LTREE_SET_SUBTREE(ltree, subtree);
|
|
|
+ XMSS_ADDR_TREE_SET_SUBTREE(tree, subtree);
|
|
|
+
|
|
|
+ for (i = 0; i < max; i++) {
|
|
|
+ word8 h;
|
|
|
+
|
|
|
+ /* Calculate WOTS+ public key. */
|
|
|
+ ots[XMSS_ADDR_OTS] = i;
|
|
|
+ wc_xmss_wots_gen_pk(state, sk_seed, pk_seed, ots, state->pk);
|
|
|
+ /* Calculate public value. */
|
|
|
+ ltree[XMSS_ADDR_LTREE] = i;
|
|
|
+ wc_xmss_ltree(state, state->pk, pk_seed, ltree, node);
|
|
|
+
|
|
|
+ /* Initial height at this offset is 0. */
|
|
|
+ h = height[offset] = 0;
|
|
|
+ /* Copy node, at height 0, out if on authentication path. */
|
|
|
+ if ((auth_path != NULL) && ((leafIdx ^ 0x1) == i)) {
|
|
|
+ XMEMCPY(auth_path, node, n);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Top node on Stack has same height t' as node. */
|
|
|
+ while ((offset >= 1) && (h == height[offset - 1])) {
|
|
|
+ word32 tree_idx = i >> (h + 1);
|
|
|
+
|
|
|
+ node -= n;
|
|
|
+ /* Calculate hash of node. */
|
|
|
+ tree[XMSS_ADDR_TREE_HEIGHT] = h;
|
|
|
+ tree[XMSS_ADDR_TREE_INDEX] = tree_idx;
|
|
|
+ wc_xmss_rand_hash(state, node, pk_seed, tree, node);
|
|
|
+
|
|
|
+ /* Update offset and height. */
|
|
|
+ offset--;
|
|
|
+ h = ++height[offset];
|
|
|
+
|
|
|
+ /* Copy node out if on authentication path. */
|
|
|
+ if ((auth_path != NULL) && (((leafIdx >> h) ^ 0x1) == tree_idx)) {
|
|
|
+ XMEMCPY(auth_path + h * n, node, n);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ offset++;
|
|
|
+ node += n;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Copy the root node. */
|
|
|
+ XMEMCPY(root, state->stack, n);
|
|
|
+}
|
|
|
+#else
|
|
|
+/* Compute internal nodes of Merkle tree.
|
|
|
+ *
|
|
|
+ * Implementation always starts at index 0. (s = 0)
|
|
|
+ *
|
|
|
+ * Build authentication path, if required, rather than duplicating work.
|
|
|
+ * When node is generated, copy out to authentication path array of nodes.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.6, Algorithm 9: treeHash
|
|
|
+ * if( s % (1 << t) != 0 ) return -1;
|
|
|
+ * for ( i = 0; i < 2^t; i++ ) {
|
|
|
+ * SEED = getSEED(SK);
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(s + i);
|
|
|
+ * pk = WOTS_genPK (getWOTS_SK(SK, s + i), SEED, ADRS);
|
|
|
+ * ADRS.setType(1); # Type = L-tree address
|
|
|
+ * ADRS.setLTreeAddress(s + i);
|
|
|
+ * node = ltree(pk, SEED, ADRS);
|
|
|
+ * ADRS.setType(2); # Type = hash tree address
|
|
|
+ * ADRS.setTreeHeight(0);
|
|
|
+ * ADRS.setTreeIndex(i + s);
|
|
|
+ * while ( Top node on Stack has same height t' as node ) {
|
|
|
+ * ADRS.setTreeIndex((ADRS.getTreeIndex() - 1) / 2);
|
|
|
+ * node = RAND_HASH(Stack.pop(), node, SEED, ADRS);
|
|
|
+ * ADRS.setTreeHeight(ADRS.getTreeHeight() + 1);
|
|
|
+ * }
|
|
|
+ * Stack.push(node);
|
|
|
+ * }
|
|
|
+ * return Stack.pop();
|
|
|
+ * RFC 8391: 4.1.9, (Example) buildAuth
|
|
|
+ * for ( j = 0; j < h; j++ ) {
|
|
|
+ * k = floor(i / (2^j)) XOR 1;
|
|
|
+ * auth[j] = treeHash(SK, k * 2^j, j, ADRS);
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] sk_seed Random private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] leafIdx Index of lead node.
|
|
|
+ * @param [in] subtree_addr Address of subtree.
|
|
|
+ * @param [out] root Root node of the tree.
|
|
|
+ * @param [out] auth_path Nodes of the authentication path.
|
|
|
+ */
|
|
|
+static void wc_xmss_treehash(XmssState* state, const byte* sk_seed,
|
|
|
+ const byte* pk_seed, word32 leafIdx, const word32* subtree, byte* root,
|
|
|
+ byte* auth_path)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ byte* node = state->stack;
|
|
|
+ HashAddress addr;
|
|
|
+ word8 height[WC_XMSS_MAX_TREE_HEIGHT + 1];
|
|
|
+ word8 offset = 0;
|
|
|
+ word32 max = (word32)1 << params->sub_h;
|
|
|
+ word32 i;
|
|
|
+
|
|
|
+ XMSS_ADDR_SET_SUBTREE(addr, subtree, 0);
|
|
|
+
|
|
|
+ for (i = 0; i < max; i++) {
|
|
|
+ word8 h;
|
|
|
+
|
|
|
+ /* Calculate WOTS+ public key. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_OTS;
|
|
|
+ addr[XMSS_ADDR_LTREE] = i;
|
|
|
+ wc_xmss_wots_gen_pk(state, sk_seed, pk_seed, addr, state->pk);
|
|
|
+ /* Calculate public value. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_LTREE;
|
|
|
+ wc_xmss_ltree(state, state->pk, pk_seed, addr, node);
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_TREE;
|
|
|
+ addr[XMSS_ADDR_TREE_ZERO] = 0;
|
|
|
+
|
|
|
+ /* Initial height at this offset is 0. */
|
|
|
+ h = height[offset] = 0;
|
|
|
+ /* Copy node out if on authentication path. */
|
|
|
+ if ((auth_path != NULL) && ((leafIdx ^ 0x1) == i)) {
|
|
|
+ XMEMCPY(auth_path, node, n);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Top node on Stack has same height t' as node. */
|
|
|
+ while ((offset >= 1) && (h == height[offset - 1])) {
|
|
|
+ word32 tree_idx = i >> (h + 1);
|
|
|
+
|
|
|
+ node -= n;
|
|
|
+ /* Calculate hash of node. */
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = h;
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = tree_idx;
|
|
|
+ wc_xmss_rand_hash(state, node, pk_seed, addr, node);
|
|
|
+
|
|
|
+ /* Update offset and height. */
|
|
|
+ offset--;
|
|
|
+ h = ++height[offset];
|
|
|
+
|
|
|
+ /* Copy node out if on authentication path. */
|
|
|
+ if ((auth_path != NULL) && (((leafIdx >> h) ^ 0x1) == tree_idx)) {
|
|
|
+ XMEMCPY(auth_path + h * n, node, n);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ offset++;
|
|
|
+ node += n;
|
|
|
+ /* Reset hash address ready for use as OTS and LTREE. */
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = 0;
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Copy the root node. */
|
|
|
+ XMEMCPY(root, state->stack, n);
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_SMALL_STACK */
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * MAKE KEY
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Derives XMSSMT (and XMSS) key pair from seeds.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.7, Algorithm 10: XMSS_keyGen.
|
|
|
+ * ...
|
|
|
+ * initialize SK_PRF with a uniformly random n-byte string;
|
|
|
+ * setSK_PRF(SK, SK_PRF);
|
|
|
+ *
|
|
|
+ * # Initialization for common contents
|
|
|
+ * initialize SEED with a uniformly random n-byte string;
|
|
|
+ * setSEED(SK, SEED);
|
|
|
+ * setWOTS_SK(SK, wots_sk));
|
|
|
+ * ADRS = toByte(0, 32);
|
|
|
+ * root = treeHash(SK, 0, h, ADRS);
|
|
|
+ *
|
|
|
+ * SK = idx || wots_sk || SK_PRF || root || SEED;
|
|
|
+ * PK = OID || root || SEED;
|
|
|
+ * return (SK || PK);
|
|
|
+ *
|
|
|
+ * wots_sk, SK_PRF and SEED passed in as seed.
|
|
|
+ * Store seed for wots_sk instead of generated wots_sk.
|
|
|
+ * OID not stored in PK this is handled in upper layer.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] seed Random seeds.
|
|
|
+ * @param [out] sk Secret/Private key.
|
|
|
+ * @param [out] pk Public key.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+int wc_xmssmt_keygen(XmssState* state, const unsigned char* seed,
|
|
|
+ unsigned char* sk, unsigned char* pk)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const byte* seed_priv = seed;
|
|
|
+ const byte* seed_pub = seed + 2 * n;
|
|
|
+ /* Offsets into secret/private key. */
|
|
|
+ byte* sk_idx = sk;
|
|
|
+ byte* sk_seed = sk_idx + params->idx_len;
|
|
|
+ byte* sk_pub = sk_seed + 2 * n;
|
|
|
+ /* Offsets into public key. */
|
|
|
+ byte* pk_root = pk;
|
|
|
+ byte* pk_seed = pk_root + n;
|
|
|
+
|
|
|
+ /* Set first index to 0 in private key. */
|
|
|
+ XMEMSET(sk_idx, 0, params->idx_len);
|
|
|
+ /* Set private key seed and private key for PRF in to private key. */
|
|
|
+ XMEMCPY(sk_seed, seed_priv, 2 * n);
|
|
|
+ /* Set public key seed into public key. */
|
|
|
+ XMEMCPY(pk_seed, seed_pub, n);
|
|
|
+
|
|
|
+ /* Set all address values to zero. */
|
|
|
+ XMEMSET(state->addr, 0, sizeof(HashAddress));
|
|
|
+ /* Set depth into address. */
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = params->d - 1;
|
|
|
+ /* Compute root node into public key. */
|
|
|
+ wc_xmss_treehash(state, sk_seed, pk_seed, 0, state->addr, pk_root, NULL);
|
|
|
+
|
|
|
+ /* Append public key (root node and public seed) to private key. */
|
|
|
+ XMEMCPY(sk_pub, pk_root, 2 * n);
|
|
|
+
|
|
|
+ /* Return any errors that occurred during hashing. */
|
|
|
+ return state->ret;
|
|
|
+}
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * SIGN
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/**
|
|
|
+ * Sign message using XMSS/XMSS^MT.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.9, Algorithm 11: treeSig
|
|
|
+ * auth = buildAuth(SK, idx_sig, ADRS);
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(idx_sig);
|
|
|
+ * sig_ots = WOTS_sign(getWOTS_SK(SK, idx_sig),
|
|
|
+ * M', getSEED(SK), ADRS);
|
|
|
+ * Sig = sig_ots || auth;
|
|
|
+ * return Sig;
|
|
|
+ * RFC 8391: 4.2.4, Algorithm 16: XMSSMT_sign
|
|
|
+ * # Init
|
|
|
+ * ADRS = toByte(0, 32);
|
|
|
+ * SEED = getSEED(SK_MT);
|
|
|
+ * SK_PRF = getSK_PRF(SK_MT);
|
|
|
+ * idx_sig = getIdx(SK_MT);
|
|
|
+ *
|
|
|
+ * # Update SK_MT
|
|
|
+ * setIdx(SK_MT, idx_sig + 1);
|
|
|
+ *
|
|
|
+ * # Message compression
|
|
|
+ * byte[n] r = PRF(SK_PRF, toByte(idx_sig, 32));
|
|
|
+ * byte[n] M' = H_msg(r || getRoot(SK_MT) || (toByte(idx_sig, n)), M);
|
|
|
+ *
|
|
|
+ * # Sign
|
|
|
+ * Sig_MT = idx_sig;
|
|
|
+ * unsigned int idx_tree
|
|
|
+ * = (h - h / d) most significant bits of idx_sig;
|
|
|
+ * unsigned int idx_leaf = (h / d) least significant bits of idx_sig;
|
|
|
+ * SK = idx_leaf || getXMSS_SK(SK_MT, idx_tree, 0) || SK_PRF
|
|
|
+ * || toByte(0, n) || SEED;
|
|
|
+ * ADRS.setLayerAddress(0);
|
|
|
+ * ADRS.setTreeAddress(idx_tree);
|
|
|
+ * Sig_tmp = treeSig(M', SK, idx_leaf, ADRS);
|
|
|
+ * Sig_MT = Sig_MT || r || Sig_tmp;
|
|
|
+ * for ( j = 1; j < d; j++ ) {
|
|
|
+ * root = treeHash(SK, 0, h / d, ADRS);
|
|
|
+ * idx_leaf = (h / d) least significant bits of idx_tree;
|
|
|
+ * idx_tree = (h - j * (h / d)) most significant bits of idx_tree;
|
|
|
+ * SK = idx_leaf || getXMSS_SK(SK_MT, idx_tree, j) || SK_PRF
|
|
|
+ * || toByte(0, n) || SEED;
|
|
|
+ * ADRS.setLayerAddress(j);
|
|
|
+ * ADRS.setTreeAddress(idx_tree);
|
|
|
+ * Sig_tmp = treeSig(root, SK, idx_leaf, ADRS);
|
|
|
+ * Sig_MT = Sig_MT || Sig_tmp;
|
|
|
+ * }
|
|
|
+ * return SK_MT || Sig_MT
|
|
|
+ *
|
|
|
+ * buildAuth from treeSig done inside treeHash as this is more efficient.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] m Buffer holding message.
|
|
|
+ * @param [in] mlen Length of message in buffer.
|
|
|
+ * @param [in, out] sk Secret/Private key.
|
|
|
+ * @param [out] sig Signature.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+int wc_xmssmt_sign(XmssState* state, const unsigned char* m, word32 mlen,
|
|
|
+ unsigned char* sk, unsigned char* sig)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word16 hsn = (word16)hs * n;
|
|
|
+ const byte* sk_seed = sk + params->idx_len;
|
|
|
+ const byte* pk_seed = sk + params->idx_len + 3 * n;
|
|
|
+ wc_Idx idx;
|
|
|
+ byte* sig_r = sig + params->idx_len;
|
|
|
+ byte root[WC_XMSS_MAX_N];
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ WC_IDX_ZERO(idx);
|
|
|
+ /* Set all address values to zero and set type to OTS. */
|
|
|
+ XMEMSET(state->addr, 0, sizeof(HashAddress));
|
|
|
+ state->addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_OTS;
|
|
|
+
|
|
|
+ /* Copy the index into the signature data: Sig_MT = idx_sig. */
|
|
|
+ XMEMCPY(sig, sk, params->idx_len);
|
|
|
+
|
|
|
+ /* Read index from the secret key. */
|
|
|
+ WC_IDX_DECODE(idx, params->idx_len, sk, ret);
|
|
|
+ /* Validate index in secret key. */
|
|
|
+ if ((ret == 0) && (WC_IDX_INVALID(idx, params->idx_len, params->h))) {
|
|
|
+ /* Set index to maximum value to distinguish from valid value. */
|
|
|
+ XMEMSET(sk, 0xFF, params->idx_len);
|
|
|
+ /* Zeroize the secret key. */
|
|
|
+ ForceZero(sk + params->idx_len, params->sk_len - params->idx_len);
|
|
|
+ ret = KEY_EXHAUSTED_E;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Update SK_MT */
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Increment the index in the secret key. */
|
|
|
+ wc_idx_update(sk, params->idx_len);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Message compression */
|
|
|
+ if (ret == 0) {
|
|
|
+ const byte* sk_prf = sk + params->idx_len + n;
|
|
|
+
|
|
|
+ /* byte[n] r = PRF(SK_PRF, toByte(idx_sig, 32)); */
|
|
|
+ wc_idx_copy(sig, params->idx_len, state->buf, XMSS_PRF_M_LEN);
|
|
|
+ wc_xmss_prf(state, sk_prf, state->buf, sig_r);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ const byte* pub_root = sk + params->idx_len + 2 * n;
|
|
|
+ /* byte[n] M' = H_msg(r || getRoot(SK_MT) || (toByte(idx_sig, n)), M);
|
|
|
+ */
|
|
|
+ wc_xmss_hash_message(state, sig_r, pub_root, sig, params->idx_len, m,
|
|
|
+ mlen, root);
|
|
|
+ ret = state->ret;
|
|
|
+ /* Place WOTS+ signatures after index and 'r'. */
|
|
|
+ sig += params->idx_len + n;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Sign. */
|
|
|
+ for (i = 0; (ret == 0) && (i < params->d); i++) {
|
|
|
+ word32 idx_leaf = 0;
|
|
|
+
|
|
|
+ /* Set layer, tree and OTS leaf index into hash address. */
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = i;
|
|
|
+ WC_IDX_SET_ADDR_TREE(idx, params->idx_len, hs, state->addr, idx_leaf);
|
|
|
+ /* treeSig || treeHash = sig_ots || auth */
|
|
|
+ state->addr[XMSS_ADDR_OTS] = idx_leaf;
|
|
|
+ /* Create WOTS+ signature for tree into signature (sig_ots). */
|
|
|
+ wc_xmss_wots_sign(state, root, sk_seed, pk_seed, state->addr, sig);
|
|
|
+ ret = state->ret;
|
|
|
+ if (ret == 0) {
|
|
|
+ sig += params->wots_sig_len;
|
|
|
+ /* Add authentication path (auth) and calc new root. */
|
|
|
+ wc_xmss_treehash(state, sk_seed, pk_seed, idx_leaf, state->addr,
|
|
|
+ root, sig);
|
|
|
+ ret = state->ret;
|
|
|
+ sig += hsn;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+#else
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * Fast C implementation
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Tree hash data - needs to be unpacked from binary. */
|
|
|
+typedef struct TreeHash {
|
|
|
+ /* Next index to update in tree - max 20 bits. */
|
|
|
+ word32 nextIdx;
|
|
|
+ /* Number of stack entries used by tree - 0..<subtree height>. */
|
|
|
+ word8 used;
|
|
|
+ /* Tree is finished. */
|
|
|
+ word8 completed;
|
|
|
+} TreeHash;
|
|
|
+
|
|
|
+/* BDS state. */
|
|
|
+typedef struct BdsState {
|
|
|
+ /* Stack of nodes - subtree height + 1 nodes. */
|
|
|
+ byte* stack;
|
|
|
+ /* Height of stack node - subtree height + 1 of 0..<subtree height - 1>. */
|
|
|
+ byte* height;
|
|
|
+ /* Authentication path for next index - subtree height nodes. */
|
|
|
+ byte* authPath;
|
|
|
+ /* Hashes of nodes kept - subtree height / 2 nodes. */
|
|
|
+ byte* keep;
|
|
|
+ /* Tree hash instances - subtree height minus K instances. */
|
|
|
+ byte* treeHash;
|
|
|
+ /* Hashes of nodes for tree hash - one for each tree hash instance. */
|
|
|
+ byte* treeHashNode;
|
|
|
+ /* Hashes of nodes to retain - based on K parameter. */
|
|
|
+ byte* retain;
|
|
|
+ /* Next leaf to calculate - max 20 bits. */
|
|
|
+ word32 next;
|
|
|
+ /* Current offset into stack - 0..<subtree height>. */
|
|
|
+ word8 offset;
|
|
|
+} BdsState;
|
|
|
+
|
|
|
+/* Index to BDS state accounting for swapping.
|
|
|
+ *
|
|
|
+ * @param [in] idx Index of node.
|
|
|
+ * @param [in] i Depth of tree.
|
|
|
+ * @param [in] hs Height of subtree.
|
|
|
+ * @param [in] d Depth/number of trees.
|
|
|
+ * @return Index of working BDS state.
|
|
|
+ */
|
|
|
+#define BDS_IDX(idx, i, hs, d) \
|
|
|
+ (((((idx) >> ((hs) * ((i) + 1))) & 1) == 0) ? (i) : ((d) + (i)))
|
|
|
+/* Index to alternate BDS state accounting for swapping.
|
|
|
+ *
|
|
|
+ * @param [in] idx Index of node.
|
|
|
+ * @param [in] i Depth of tree.
|
|
|
+ * @param [in] hs Height of subtree.
|
|
|
+ * @param [in] d Depth/number of trees.
|
|
|
+ * @return Index of alternate BDS state.
|
|
|
+ */
|
|
|
+#define BDS_ALT_IDX(idx, i, hs, d) \
|
|
|
+ (((((idx) >> ((hs) * ((i) + 1))) & 1) == 0) ? ((d) + (i)) : (i))
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * Tree Hash APIs
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Initialize the tree hash data at specified index for the BDS state.
|
|
|
+ *
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] i Index of tree hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_treehash_init(BdsState* bds, int i)
|
|
|
+{
|
|
|
+ byte* sk = bds->treeHash + i * 4;
|
|
|
+ c32to24(0, sk);
|
|
|
+ sk[3] = 0 | (1 << 7);
|
|
|
+}
|
|
|
+
|
|
|
+/* Set next index into tree hash data at specified index for the BDS state.
|
|
|
+ *
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] i Index of tree hash.
|
|
|
+ * @param [in] nextIdx Next index for tree hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_treehash_set_next_idx(BdsState* bds, int i,
|
|
|
+ word32 nextIdx)
|
|
|
+{
|
|
|
+ byte* sk = bds->treeHash + i * 4;
|
|
|
+ c32to24(nextIdx, sk);
|
|
|
+ sk[3] = 0 | (0 << 7);
|
|
|
+}
|
|
|
+
|
|
|
+/* Mark tree hash, at specified index for the BDS state, as complete.
|
|
|
+ *
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] i Index of tree hash.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_treehash_complete(BdsState* bds, int i)
|
|
|
+{
|
|
|
+ byte* sk = bds->treeHash + i * 4;
|
|
|
+ sk[3] |= 1 << 7;
|
|
|
+}
|
|
|
+
|
|
|
+/* Get the tree hash data at specified index for the BDS state.
|
|
|
+ *
|
|
|
+ * @param [in] bds BDS state.
|
|
|
+ * @param [in] i Index of tree hash.
|
|
|
+ * @param [out] treeHash Tree hash instance to fill out.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_treehash_get(BdsState* bds, int i,
|
|
|
+ TreeHash* treeHash)
|
|
|
+{
|
|
|
+ byte* sk = bds->treeHash + i * 4;
|
|
|
+ ato24(sk, &treeHash->nextIdx);
|
|
|
+ treeHash->used = sk[3] & 0x7f;
|
|
|
+ treeHash->completed = sk[3] >> 7;
|
|
|
+}
|
|
|
+
|
|
|
+/* Set the tree hash data at specified index for the BDS state.
|
|
|
+ *
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] i Index of tree hash.
|
|
|
+ * @param [in] treeHash Tree hash data.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_treehash_set(BdsState* bds, int i,
|
|
|
+ TreeHash* treeHash)
|
|
|
+{
|
|
|
+ byte* sk = bds->treeHash + i * 4;
|
|
|
+ c32to24(treeHash->nextIdx, sk);
|
|
|
+ sk[3] = treeHash->used | (treeHash->completed << 7);
|
|
|
+}
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * BDS State APIs
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Allocate memory for BDS state.
|
|
|
+ *
|
|
|
+ * When using a static BDS state (XMSS) then pass in handle to data for bds.
|
|
|
+ *
|
|
|
+ * @param [in] params XMSS/MT parameters.
|
|
|
+ * @param [in, out] bds Handle to BDS state. May be NULL if not allocated.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return MEMORY_E on dynamic memory allocation failure.
|
|
|
+ */
|
|
|
+static int wc_xmss_bds_state_alloc(const XmssParams* params, BdsState** bds)
|
|
|
+{
|
|
|
+ const word8 cnt = 2 * params->d - 1;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (*bds == NULL) {
|
|
|
+ /* Allocate memory for BDS states. */
|
|
|
+ *bds = (BdsState*)XMALLOC(sizeof(BdsState) * cnt, NULL,
|
|
|
+ DYNAMIC_TYPE_TMP_BUFFER);
|
|
|
+ if (*bds == NULL) {
|
|
|
+ ret = MEMORY_E;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/* Dispose of allocated memory associated with BDS state.
|
|
|
+ *
|
|
|
+ * @param [in] bds BDS state.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_free(BdsState* bds)
|
|
|
+{
|
|
|
+ /* BDS states was allocated - must free. */
|
|
|
+ XFREE(bds, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
|
+}
|
|
|
+
|
|
|
+/* Load the BDS state from the secret/private key.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] sk Secret/private key.
|
|
|
+ * @param [out] bds BDS states.
|
|
|
+ * @param [out] wots_sigs WOTS signatures when XMSS^MT.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_load(const XmssState* state, byte* sk,
|
|
|
+ BdsState* bds, byte** wots_sigs)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word8 hsk = params->sub_h - params->bds_k;
|
|
|
+ const word8 k = params->bds_k;
|
|
|
+ const word32 retainLen = XMSS_RETAIN_LEN(k, n);
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* Skip past standard SK = idx || wots_sk || SK_PRF || root || SEED; */
|
|
|
+ sk += params->idx_len + 4 * n;
|
|
|
+
|
|
|
+ for (i = 0; i < 2 * (int)params->d - 1; i++) {
|
|
|
+ /* Set pointers into SK. */
|
|
|
+ bds[i].stack = sk;
|
|
|
+ sk += (hs + 1) * n;
|
|
|
+ bds[i].height = sk;
|
|
|
+ sk += hs + 1;
|
|
|
+ bds[i].authPath = sk;
|
|
|
+ sk += hs * n;
|
|
|
+ bds[i].keep = sk;
|
|
|
+ sk += (hs >> 1) * n;
|
|
|
+ bds[i].treeHash = sk;
|
|
|
+ sk += hsk * 4;
|
|
|
+ bds[i].treeHashNode = sk;
|
|
|
+ sk += hsk * n;
|
|
|
+ bds[i].retain = sk;
|
|
|
+ sk += retainLen;
|
|
|
+ /* Load values - big-endian encoded. */
|
|
|
+ ato24(sk, &bds[i].next);
|
|
|
+ sk += 3;
|
|
|
+ bds[i].offset = sk[0];
|
|
|
+ sk += 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (wots_sigs != NULL) {
|
|
|
+ *wots_sigs = sk;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Store the BDS state into the secret/private key.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in, out] sk Secret/private key.
|
|
|
+ * @param [in] bds BDS states.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_state_store(const XmssState* state, byte* sk,
|
|
|
+ BdsState* bds)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word8 hsk = params->sub_h - params->bds_k;
|
|
|
+ const word8 k = params->bds_k;
|
|
|
+ const word32 skip = (hs + 1) * n + /* BdsState.stack */
|
|
|
+ hs + 1 + /* BdsState.height */
|
|
|
+ hs * n + /* BdsState.authPath */
|
|
|
+ (hs >> 1) * n + /* BdsState.keep */
|
|
|
+ hsk * 4 + /* BdsState.treeHash */
|
|
|
+ hsk * n + /* BdsState.treeHashNode */
|
|
|
+ XMSS_RETAIN_LEN(k, n); /* BdsState.retain */
|
|
|
+
|
|
|
+ /* Ignore standard SK = idx || wots_sk || SK_PRF || root || SEED; */
|
|
|
+ sk += params->idx_len + 4 * n;
|
|
|
+
|
|
|
+ for (i = 0; i < 2 * (int)params->d - 1; i++) {
|
|
|
+ /* Skip pointers into sk. */
|
|
|
+ sk += skip;
|
|
|
+ /* Save values - big-endian encoded. */
|
|
|
+ c32to24(bds[i].next, sk);
|
|
|
+ sk += 3;
|
|
|
+ sk[0] = bds[i].offset;
|
|
|
+ sk += 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * BDS
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Compute node at next index.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.6, Algorithm 9: treeHash
|
|
|
+ * ...
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(s + i);
|
|
|
+ * pk = WOTS_genPK (getWOTS_SK(SK, s + i), SEED, ADRS);
|
|
|
+ * ADRS.setType(1); # Type = L-tree address
|
|
|
+ * ADRS.setLTreeAddress(s + i);
|
|
|
+ * node = ltree(pk, SEED, ADRS);
|
|
|
+ * ADRS.setType(2); # Type = hash tree address
|
|
|
+ * ADRS.setTreeHeight(0);
|
|
|
+ * ADRS.setTreeIndex(i + s);
|
|
|
+ * while ( Top node on Stack has same height t' as node ) {
|
|
|
+ * ADRS.setTreeIndex((ADRS.getTreeIndex() - 1) / 2);
|
|
|
+ * node = RAND_HASH(Stack.pop(), node, SEED, ADRS);
|
|
|
+ * ADRS.setTreeHeight(ADRS.getTreeHeight() + 1);
|
|
|
+ * }
|
|
|
+ * Stack.push(node);
|
|
|
+ * ...
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] bds BDS state.
|
|
|
+ * @param [in] sk_seed Random secret/private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [out] root Root node.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_next_idx(XmssState* state, BdsState* bds,
|
|
|
+ const byte* sk_seed, const byte* pk_seed, HashAddress addr, int i,
|
|
|
+ word8* height, word8* offset, word8** sp)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word8 hsk = params->sub_h - params->bds_k;
|
|
|
+ const word8 n = params->n;
|
|
|
+ word8 o = *offset;
|
|
|
+ word8* node = *sp;
|
|
|
+ word8 h;
|
|
|
+
|
|
|
+ /* Calculate WOTS+ public key. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_OTS;
|
|
|
+ addr[XMSS_ADDR_OTS] = i;
|
|
|
+ wc_xmss_wots_gen_pk(state, sk_seed, pk_seed, addr, state->pk);
|
|
|
+ /* Calculate public value. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_LTREE;
|
|
|
+ wc_xmss_ltree(state, state->pk, pk_seed, addr, node);
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_TREE;
|
|
|
+ addr[XMSS_ADDR_TREE_ZERO] = 0;
|
|
|
+
|
|
|
+ /* Initial height at this offset is 0. */
|
|
|
+ h = height[o] = 0;
|
|
|
+ /* HDSS, Section 4.5, 2: TREEHASH[h].push(v[h][3])
|
|
|
+ * Copy right node to tree hash nodes if second right node. */
|
|
|
+ if ((hsk > 0) && (i == 3)) {
|
|
|
+ XMEMCPY(bds->treeHashNode, node + n, n);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Top node on Stack has same height t' as node. */
|
|
|
+ while ((o >= 1) && (h == height[o - 1])) {
|
|
|
+ /* HDSS, Section 4.5, 1: AUTH[h] = v[h][1], h = 0,...,H-1.
|
|
|
+ * Cache left node if on authentication path. */
|
|
|
+ if ((i >> h) == 1) {
|
|
|
+ XMEMCPY(bds->authPath + h * n, node, n);
|
|
|
+ }
|
|
|
+ /* This is a right node. */
|
|
|
+ else if (h < hsk) {
|
|
|
+ /* HDSS, Section 4.5, 2: TREEHASH[h].push(v[h][3])
|
|
|
+ * Copy right node to tree hash if second right node. */
|
|
|
+ if ((i >> h) == 3) {
|
|
|
+ XMEMCPY(bds->treeHashNode + h * n, node, n);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ /* HDSS, Section 4.5, 3: RETAIN[h].push(v[j][2j+3] for
|
|
|
+ * h = H-K,...,H-2 and j = 2^(H-h-1)-2,...,0.
|
|
|
+ * Retain high right nodes.
|
|
|
+ */
|
|
|
+ word32 ro = (1 << (hs - 1 - h)) + h - hs + (((i >> h) - 3) >> 1);
|
|
|
+ XMEMCPY(bds->retain + ro * n, node, n);
|
|
|
+ }
|
|
|
+
|
|
|
+ node -= n;
|
|
|
+ /* Calculate hash of node. */
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = h;
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = i >> (h + 1);
|
|
|
+ wc_xmss_rand_hash(state, node, pk_seed, addr, node);
|
|
|
+
|
|
|
+ /* Update offset and height. */
|
|
|
+ o--;
|
|
|
+ h = ++height[o];
|
|
|
+ }
|
|
|
+
|
|
|
+ *offset = o;
|
|
|
+ *sp = node;
|
|
|
+}
|
|
|
+
|
|
|
+/* Compute initial Merkle tree and store nodes.
|
|
|
+ *
|
|
|
+ * HDSS, Section 4.5, The algorithm, Initialization.
|
|
|
+ * 1. We store the authentication path for the first leaf (s = 0):
|
|
|
+ * AUTH[h] = v[h][1], h = 0,...,H-1.
|
|
|
+ * 2. Depending on the parameter K, we store the next right authentication
|
|
|
+ * node for each height h = 0,...,H-K-1 in the treehash instances:
|
|
|
+ * TREEHASH[h].push(v[h][3]).
|
|
|
+ * 3. Finally we store the right authentication nodes clode to the root using
|
|
|
+ * the stacks RETAIN[h]:
|
|
|
+ * RETAIN[h].push(v[j][2j+3] for h = H-K,...,H-2 and j = 2^(H-h-1)-2,...,0.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.6, Algorithm 9: treeHash
|
|
|
+ * if( s % (1 << t) != 0 ) return -1;
|
|
|
+ * for ( i = 0; i < 2^t; i++ ) {
|
|
|
+ * SEED = getSEED(SK);
|
|
|
+ * [Compute node at next index]
|
|
|
+ * }
|
|
|
+ * return Stack.pop();
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] bds BDS state.
|
|
|
+ * @param [in] sk_seed Random secret/private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [out] root Root node.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_treehash_initial(XmssState* state, BdsState* bds,
|
|
|
+ const byte* sk_seed, const byte* pk_seed, const HashAddress addr,
|
|
|
+ byte* root)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 hsk = params->sub_h - params->bds_k;
|
|
|
+ const word8 n = params->n;
|
|
|
+ word8* node = state->stack;
|
|
|
+ HashAddress addrCopy;
|
|
|
+ word8 height[WC_XMSS_MAX_TREE_HEIGHT + 1];
|
|
|
+ word8 offset = 0;
|
|
|
+ word32 maxIdx = (word32)1 << params->sub_h;
|
|
|
+ word32 i;
|
|
|
+
|
|
|
+ /* First signing index will be 0 - setup BDS state. */
|
|
|
+ bds->offset = 0;
|
|
|
+ bds->next = 0;
|
|
|
+ /* Reset the hash tree status. */
|
|
|
+ for (i = 0; i < hsk; i++) {
|
|
|
+ wc_xmss_bds_state_treehash_init(bds, i);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Copy hash address into local. */
|
|
|
+ XMSS_ADDR_OTS_SET_SUBTREE(addrCopy, addr);
|
|
|
+
|
|
|
+ /* Compute each node in tree. */
|
|
|
+ for (i = 0; i < maxIdx; i++) {
|
|
|
+ wc_xmss_bds_next_idx(state, bds, sk_seed, pk_seed, addrCopy, i, height,
|
|
|
+ &offset, &node);
|
|
|
+ offset++;
|
|
|
+ node += n;
|
|
|
+ /* Rest the hash address for reuse. */
|
|
|
+ addrCopy[XMSS_ADDR_TREE_HEIGHT] = 0;
|
|
|
+ addrCopy[XMSS_ADDR_TREE_INDEX] = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Copy the root node. */
|
|
|
+ XMEMCPY(root, state->stack, n);
|
|
|
+}
|
|
|
+
|
|
|
+/* Update internal nodes of Merkle tree at next index.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.6, Algorithm 9: treeHash
|
|
|
+ * ...
|
|
|
+ * SEED = getSEED(SK);
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(s + i);
|
|
|
+ * pk = WOTS_genPK (getWOTS_SK(SK, s + i), SEED, ADRS);
|
|
|
+ * ADRS.setType(1); # Type = L-tree address
|
|
|
+ * ADRS.setLTreeAddress(s + i);
|
|
|
+ * node = ltree(pk, SEED, ADRS);
|
|
|
+ * ADRS.setType(2); # Type = hash tree address
|
|
|
+ * ADRS.setTreeHeight(0);
|
|
|
+ * ADRS.setTreeIndex(i + s);
|
|
|
+ * while ( Top node on Stack has same height t' as node ) {
|
|
|
+ * ADRS.setTreeIndex((ADRS.getTreeIndex() - 1) / 2);
|
|
|
+ * node = RAND_HASH(Stack.pop(), node, SEED, ADRS);
|
|
|
+ * ADRS.setTreeHeight(ADRS.getTreeHeight() + 1);
|
|
|
+ * }
|
|
|
+ * Stack.push(node);
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] height Height of nodes to update.
|
|
|
+ * @param [in] sk_seed Random secret/private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_treehash_update(XmssState* state, BdsState* bds,
|
|
|
+ word8 height, const byte* sk_seed, const byte* pk_seed,
|
|
|
+ const HashAddress addr)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ HashAddress addrLocal;
|
|
|
+ TreeHash treeHash[1];
|
|
|
+ byte* sp = bds->stack + bds->offset * n;
|
|
|
+ byte* node = state->stack + WC_XMSS_MAX_STACK_LEN - n;
|
|
|
+ word8 h;
|
|
|
+
|
|
|
+ /* Get the tree hash data. */
|
|
|
+ wc_xmss_bds_state_treehash_get(bds, height, treeHash);
|
|
|
+ /* Copy hash address into local as OTS type. */
|
|
|
+ XMSS_ADDR_OTS_SET_SUBTREE(addrLocal, addr);
|
|
|
+ /* Calculate WOTS+ public key. */
|
|
|
+ addrLocal[XMSS_ADDR_OTS] = treeHash->nextIdx;
|
|
|
+ wc_xmss_wots_gen_pk(state, sk_seed, pk_seed, addrLocal, state->pk);
|
|
|
+ /* Calculate public value. */
|
|
|
+ addrLocal[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_LTREE;
|
|
|
+ wc_xmss_ltree(state, state->pk, pk_seed, addrLocal, node);
|
|
|
+ addrLocal[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_TREE;
|
|
|
+ addrLocal[XMSS_ADDR_TREE_ZERO] = 0;
|
|
|
+
|
|
|
+ /* Initial height is 0. */
|
|
|
+ h = 0;
|
|
|
+
|
|
|
+ /* Top node on Stack has same height t' as node. */
|
|
|
+ while ((treeHash->used > 0) && (h == bds->height[bds->offset - 1])) {
|
|
|
+ sp -= n;
|
|
|
+ /* Copy from stack to before last calculated node. */
|
|
|
+ node -= n;
|
|
|
+ XMEMCPY(node, sp, n);
|
|
|
+
|
|
|
+ /* Calculate hash of node. */
|
|
|
+ addrLocal[XMSS_ADDR_TREE_HEIGHT] = h;
|
|
|
+ addrLocal[XMSS_ADDR_TREE_INDEX] = treeHash->nextIdx >> (h + 1);
|
|
|
+ wc_xmss_rand_hash(state, node, pk_seed, addrLocal, node);
|
|
|
+
|
|
|
+ /* Update used, offset and height. */
|
|
|
+ treeHash->used--;
|
|
|
+ bds->offset--;
|
|
|
+ h++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check whether we reached the height we wanted to update. */
|
|
|
+ if (h == height) {
|
|
|
+ /* Cache node. */
|
|
|
+ XMEMCPY(bds->treeHashNode + height * n, node, n);
|
|
|
+ treeHash->completed = 1;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ /* Push calculated node onto stack. */
|
|
|
+ XMEMCPY(sp, node, n);
|
|
|
+ treeHash->used++;
|
|
|
+ /* Update BDS state. */
|
|
|
+ bds->height[bds->offset] = h;
|
|
|
+ bds->offset++;
|
|
|
+ treeHash->nextIdx++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set the tree hash data back. */
|
|
|
+ wc_xmss_bds_state_treehash_set(bds, height, treeHash);
|
|
|
+}
|
|
|
+
|
|
|
+/* Updates hash trees that need it most.
|
|
|
+ *
|
|
|
+ * Algorithm 4.6: Authentication path computation, Step 5.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] updates Current number of updates.
|
|
|
+ * @param [in] sk_seed Random secret/private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @return Number of available updates.
|
|
|
+ */
|
|
|
+static word8 wc_xmss_bds_treehash_updates(XmssState* state, BdsState* bds,
|
|
|
+ word8 updates, const byte* sk_seed, const byte* pk_seed,
|
|
|
+ const HashAddress addr)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word8 hsk = params->sub_h - params->bds_k;
|
|
|
+
|
|
|
+ while (updates > 0) {
|
|
|
+ word8 minH = hs;
|
|
|
+ word8 h = hsk;
|
|
|
+ word8 i;
|
|
|
+
|
|
|
+ /* Step 5.a. k <- min{ h: TREEHASH(h).height() =
|
|
|
+ min[j=0..H-K-1]{TREEHASH(j.height()} } */
|
|
|
+ for (i = 0; i < hsk; i++) {
|
|
|
+ TreeHash treeHash[1];
|
|
|
+
|
|
|
+ wc_xmss_bds_state_treehash_get(bds, i, treeHash);
|
|
|
+
|
|
|
+ if (treeHash->completed) {
|
|
|
+ /* Finished - ignore. */
|
|
|
+ }
|
|
|
+ else if (treeHash->used == 0) {
|
|
|
+ /* None used, low height. */
|
|
|
+ if (i < minH) {
|
|
|
+ h = i;
|
|
|
+ minH = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Find the height of lowest in cache. */
|
|
|
+ else {
|
|
|
+ word8 j;
|
|
|
+ word8 lowH = hs;
|
|
|
+ byte* height = bds->height + bds->offset - treeHash->used;
|
|
|
+
|
|
|
+ for (j = 0; j < treeHash->used; j++) {
|
|
|
+ lowH = min(height[j], lowH);
|
|
|
+ }
|
|
|
+ if (lowH < minH) {
|
|
|
+ /* New lowest height. */
|
|
|
+ h = i;
|
|
|
+ minH = lowH;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* If none lower, then stop. */
|
|
|
+ if (h == hsk) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Step 5.b. TREEHASH(k).update() */
|
|
|
+ /* Update tree to the lowest height. */
|
|
|
+ wc_xmss_bds_treehash_update(state, bds, h, sk_seed, pk_seed, addr);
|
|
|
+ updates--;
|
|
|
+ }
|
|
|
+ return updates;
|
|
|
+}
|
|
|
+
|
|
|
+/* Update BDS at next leaf.
|
|
|
+ *
|
|
|
+ * Don't do anything if processed all leaves.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] sk_seed Random secret/private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_update(XmssState* state, BdsState* bds,
|
|
|
+ const byte* sk_seed, const byte* pk_seed, const HashAddress addr)
|
|
|
+{
|
|
|
+ if (bds->next < ((word32)1 << state->params->sub_h)) {
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* sp = bds->stack + bds->offset * params->n;
|
|
|
+ HashAddress addrCopy;
|
|
|
+
|
|
|
+ XMSS_ADDR_OTS_SET_SUBTREE(addrCopy, addr);
|
|
|
+ wc_xmss_bds_next_idx(state, bds, sk_seed, pk_seed, addrCopy, bds->next,
|
|
|
+ bds->height, &bds->offset, &sp);
|
|
|
+ bds->offset++;
|
|
|
+ bds->next++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Find index of lowest zero bit.
|
|
|
+ *
|
|
|
+ * Supports max up to 31.
|
|
|
+ *
|
|
|
+ * @param [in] n Number to evaluate.
|
|
|
+ * @param [in] max Max number of bits.
|
|
|
+ * @param [out] b Next bit above first zero bit.
|
|
|
+ * @return Index of lowest bit that is zero.
|
|
|
+ */
|
|
|
+static word8 wc_xmss_lowest_zero_bit_index(word32 n, word8 max, word8* b)
|
|
|
+{
|
|
|
+ word8 i;
|
|
|
+
|
|
|
+ /* Check each bit from lowest for a zero bit. */
|
|
|
+ for (i = 0; i < max; i++) {
|
|
|
+ if ((n & 1) == 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ n >>= 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Return next bit after 0 bit. */
|
|
|
+ *b = (n >> 1) & 1;
|
|
|
+ return i;
|
|
|
+}
|
|
|
+
|
|
|
+/* Returns auth path for node leafIdx and computes for next leaf node.
|
|
|
+ *
|
|
|
+ * HDSS, Algorithm 4.6: Authentication path computation, Steps 1-4.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] leafIdx Current leaf index.
|
|
|
+ * @param [in] sk_seed Random secret/private seed.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ */
|
|
|
+static void wc_xmss_bds_auth_path(XmssState* state, BdsState* bds,
|
|
|
+ const word32 leafIdx, const byte* sk_seed, const byte* pk_seed,
|
|
|
+ HashAddress addr)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word8 hsk = params->sub_h - params->bds_k;
|
|
|
+ word8 tau;
|
|
|
+ byte* node = state->encMsg;
|
|
|
+ word8 parent;
|
|
|
+
|
|
|
+ /* Step 1. Find the height of first left node in authentication path. */
|
|
|
+ tau = wc_xmss_lowest_zero_bit_index(leafIdx, hs, &parent);
|
|
|
+ if (tau == 0) {
|
|
|
+ /* Step 2. Keep node if parent is a left node.
|
|
|
+ * if s/(2^tau+1) is even and tau < H-1 then KEEP[tau] <- AUTH[tau]
|
|
|
+ */
|
|
|
+ if (parent == 0) {
|
|
|
+ XMEMCPY(bds->keep, bds->authPath, n);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Step 3. if tau = 0 then AUTH[0] <- LEAFCALC(s) */
|
|
|
+ /* Calculate WOTS+ public key. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_OTS;
|
|
|
+ addr[XMSS_ADDR_OTS] = leafIdx;
|
|
|
+ wc_xmss_wots_gen_pk(state, sk_seed, pk_seed, addr, state->pk);
|
|
|
+ /* Calculate public value. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_LTREE;
|
|
|
+ wc_xmss_ltree(state, state->pk, pk_seed, addr, bds->authPath);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ byte* authPath;
|
|
|
+ byte* nodes;
|
|
|
+ word8 i;
|
|
|
+
|
|
|
+ authPath = bds->authPath + tau * n;
|
|
|
+ /* Step 4.a. <node> = AUTH[tau-1] || KEEP[tau-1]
|
|
|
+ * Only keeping half of nodes, so need to copy out before updating.
|
|
|
+ */
|
|
|
+ XMEMCPY(node, authPath - n, n);
|
|
|
+ XMEMCPY(node + n, bds->keep + ((tau - 1) >> 1) * n, n);
|
|
|
+
|
|
|
+ /* Step 2. Keep node if parent is a left node.
|
|
|
+ * if s/(2^tau+1) is even and tau < H-1 then KEEP[tau] <- AUTH[tau]
|
|
|
+ */
|
|
|
+ if ((tau < hs - 1) && (parent == 0)) {
|
|
|
+ XMEMCPY(bds->keep + (tau >> 1) * n, authPath, n);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Step 4.a. AUTH[tau] <- g(<node>) */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_TREE;
|
|
|
+ addr[XMSS_ADDR_TREE_ZERO] = 0;
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = tau - 1;
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = leafIdx >> tau;
|
|
|
+ wc_xmss_rand_hash(state, node, pk_seed, addr, authPath);
|
|
|
+
|
|
|
+ /* Step 4.b. <Calculate new right nodes on lower heights> */
|
|
|
+ authPath = bds->authPath;
|
|
|
+ nodes = bds->treeHashNode;
|
|
|
+ /* for h = 0 to tau - 1 do */
|
|
|
+ for (i = 0; i < tau; i++) {
|
|
|
+ /* if h < H - K then AUTH[h] <- TREEHASH[h].pop()*/
|
|
|
+ if (i < hsk) {
|
|
|
+ XMEMCPY(authPath, nodes, n);
|
|
|
+ nodes += n;
|
|
|
+ }
|
|
|
+ /* if h >= H - K then AUTH[h] <- RETAIN[h].pop()*/
|
|
|
+ else {
|
|
|
+ word32 o = (1 << (hs - 1 - i)) + i - hs +
|
|
|
+ (((leafIdx >> i) - 1) >> 1);
|
|
|
+ XMEMCPY(authPath, bds->retain + o * n, n);
|
|
|
+ }
|
|
|
+ authPath += n;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Step 4.c. Initialize treehash instances for heights:
|
|
|
+ * 0, ..., min{tau-1, H - K - 1} */
|
|
|
+ tau = min(tau, hsk);
|
|
|
+ for (i = 0; i < tau; i++) {
|
|
|
+ word32 startIdx = leafIdx + 1 + 3 * (1 << i);
|
|
|
+ if (startIdx < ((word32)1 << hs)) {
|
|
|
+ wc_xmss_bds_state_treehash_set_next_idx(bds, i, startIdx);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * XMSS
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Derives XMSS key pair from seeds.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.7, Algorithm 10: XMSS_keyGen.
|
|
|
+ * ...
|
|
|
+ * initialize SK_PRF with a uniformly random n-byte string;
|
|
|
+ * setSK_PRF(SK, SK_PRF);
|
|
|
+ *
|
|
|
+ * # Initialization for common contents
|
|
|
+ * initialize SEED with a uniformly random n-byte string;
|
|
|
+ * setSEED(SK, SEED);
|
|
|
+ * setWOTS_SK(SK, wots_sk));
|
|
|
+ * ADRS = toByte(0, 32);
|
|
|
+ * root = treeHash(SK, 0, h, ADRS);
|
|
|
+ *
|
|
|
+ * SK = idx || wots_sk || SK_PRF || root || SEED;
|
|
|
+ * PK = OID || root || SEED;
|
|
|
+ * return (SK || PK);
|
|
|
+ *
|
|
|
+ * HDSS, Section 4.5, The algorithm, Initialization.
|
|
|
+ *
|
|
|
+ * wots_sk, SK_PRF and SEED passed in as seed.
|
|
|
+ * Store seed for wots_sk instead of generated wots_sk.
|
|
|
+ * OID not stored in PK this is handled in upper layer.
|
|
|
+ * BDS state is appended to SK:
|
|
|
+ * SK = idx || wots_sk || SK_PRF || root || SEED || BDS_STATE;
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] seed Secret/Private and public seed.
|
|
|
+ * @param [out] sk Secret key.
|
|
|
+ * @param [out] pk Public key.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return MEMORY_E on dynamic memory allocation failure.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+int wc_xmss_keygen(XmssState* state, const unsigned char* seed,
|
|
|
+ unsigned char* sk, unsigned char* pk)
|
|
|
+{
|
|
|
+#if WOLFSSL_XMSS_MIN_HEIGHT <= 32
|
|
|
+ int ret = 0;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ /* Offset of root node in public key. */
|
|
|
+ byte* pk_root = pk;
|
|
|
+#ifdef WOLFSSL_SMALL_STACK
|
|
|
+ BdsState* bds = NULL;
|
|
|
+#else
|
|
|
+ BdsState bds[1];
|
|
|
+#endif
|
|
|
+
|
|
|
+#ifdef WOLFSSL_SMALL_STACK
|
|
|
+ /* Allocate memory for tree hash instances and put in BDS state. */
|
|
|
+ ret = wc_xmss_bds_state_alloc(params, &bds);
|
|
|
+ if (ret == 0)
|
|
|
+#endif
|
|
|
+ {
|
|
|
+ /* Offsets into seed. */
|
|
|
+ const byte* seed_priv = seed;
|
|
|
+ const byte* seed_pub = seed + 2 * n;
|
|
|
+ /* Offsets into secret/private key. */
|
|
|
+ word32* sk_idx = (word32*)sk;
|
|
|
+ byte* sk_seeds = sk + params->idx_len;
|
|
|
+ /* Offsets into public key. */
|
|
|
+ byte* pk_seed = pk + n;
|
|
|
+
|
|
|
+ /* Setup pointers into sk - assumes sk is initialized to zeros. */
|
|
|
+ wc_xmss_bds_state_load(state, sk, bds, NULL);
|
|
|
+
|
|
|
+ /* Set first index to 0 in private key. idx_len always 4. */
|
|
|
+ *sk_idx = 0;
|
|
|
+ /* Set private key seed and private key for PRF in to private key. */
|
|
|
+ XMEMCPY(sk_seeds, seed_priv, 2 * n);
|
|
|
+ /* Set public key seed into public key. */
|
|
|
+ XMEMCPY(pk_seed, seed_pub, n);
|
|
|
+
|
|
|
+ /* Set all address values to zero. */
|
|
|
+ XMEMSET(state->addr, 0, sizeof(HashAddress));
|
|
|
+ /* Hash address layer is 0. */
|
|
|
+ /* Compute root node into public key. */
|
|
|
+ wc_xmss_bds_treehash_initial(state, bds, sk_seeds, pk_seed,
|
|
|
+ state->addr, pk_root);
|
|
|
+ /* Return any errors that occurred during hashing. */
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Offset of root node in private key. */
|
|
|
+ byte* sk_root = sk + params->idx_len + 2 * n;
|
|
|
+
|
|
|
+ /* Append public key (root node and public seed) to private key. */
|
|
|
+ XMEMCPY(sk_root, pk_root, 2 * n);
|
|
|
+
|
|
|
+ /* Store BDS state back into secret/private key. */
|
|
|
+ wc_xmss_bds_state_store(state, sk, bds);
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef WOLFSSL_SMALL_STACK
|
|
|
+ /* Dispose of allocated data of BDS states. */
|
|
|
+ wc_xmss_bds_state_free(bds);
|
|
|
+#endif
|
|
|
+ return ret;
|
|
|
+#else
|
|
|
+ (void)state;
|
|
|
+ (void)pk;
|
|
|
+ (void)sk;
|
|
|
+ (void)seed;
|
|
|
+
|
|
|
+ return NOT_COMPILED_IN;
|
|
|
+#endif /* WOLFSSL_XMSS_MIN_HEIGHT <= 32 */
|
|
|
+}
|
|
|
+
|
|
|
+/* Sign a message with XMSS.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.9, Algorithm 11: treeSig
|
|
|
+ * ...
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(idx_sig);
|
|
|
+ * sig_ots = WOTS_sign(getWOTS_SK(SK, idx_sig),
|
|
|
+ * M', getSEED(SK), ADRS);
|
|
|
+ * Sig = sig_ots || auth;
|
|
|
+ * return Sig;
|
|
|
+ * RFC 8391: 4.1.9, Algorithm 12: XMSS_sign
|
|
|
+ * idx_sig = getIdx(SK);
|
|
|
+ * setIdx(SK, idx_sig + 1);
|
|
|
+ * ADRS = toByte(0, 32);
|
|
|
+ * byte[n] r = PRF(getSK_PRF(SK), toByte(idx_sig, 32));
|
|
|
+ * byte[n] M' = H_msg(r || getRoot(SK) || (toByte(idx_sig, n)), M);
|
|
|
+ * Sig = idx_sig || r || treeSig(M', SK, idx_sig, ADRS);
|
|
|
+ * return (SK || Sig);
|
|
|
+ *
|
|
|
+ * HDSS, Section 4.5, The algorithm, Update and output phase.
|
|
|
+ *
|
|
|
+ * 'auth' was built at key generation or after computing previous signature.
|
|
|
+ * Build next authentication path after signature created.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] m Buffer holding message.
|
|
|
+ * @param [in] mlen Length of message in buffer.
|
|
|
+ * @param [in, out] sk Secret/Private key.
|
|
|
+ * @param [out] sm Signature and message data.
|
|
|
+ * @param [in, out] smlen On in, length of signature and message buffer.
|
|
|
+ * On out, length of signature and message data.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+int wc_xmss_sign(XmssState* state, const unsigned char* m, word32 mlen,
|
|
|
+ unsigned char* sk, unsigned char* sig)
|
|
|
+{
|
|
|
+#if WOLFSSL_XMSS_MIN_HEIGHT <= 32
|
|
|
+ int ret = 0;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const word8 h = params->h;
|
|
|
+ const word8 hk = params->h - params->bds_k;
|
|
|
+ const byte* sk_seed = sk + XMSS_IDX_LEN;
|
|
|
+ const byte* pk_seed = sk + XMSS_IDX_LEN + 3 * n;
|
|
|
+ byte node[WC_XMSS_MAX_N];
|
|
|
+ word32 idx;
|
|
|
+ byte* sig_r = sig + XMSS_IDX_LEN;
|
|
|
+#ifdef WOLFSSL_SMALL_STACK
|
|
|
+ BdsState* bds = NULL;
|
|
|
+#else
|
|
|
+ BdsState bds[1];
|
|
|
+#endif
|
|
|
+
|
|
|
+#ifdef WOLFSSL_SMALL_STACK
|
|
|
+ /* Allocate memory for tree hash instances and put in BDS state. */
|
|
|
+ ret = wc_xmss_bds_state_alloc(params, &bds);
|
|
|
+ if (ret == 0)
|
|
|
+#endif
|
|
|
+ {
|
|
|
+ /* Load the BDS state from secret/private key. */
|
|
|
+ wc_xmss_bds_state_load(state, sk, bds, NULL);
|
|
|
+
|
|
|
+ /* Copy the index into the signature data: Sig = idx_sig || ... */
|
|
|
+ *((word32*)sig) = *((word32*)sk);
|
|
|
+ /* Read index from the secret key. */
|
|
|
+ ato32(sk, &idx);
|
|
|
+
|
|
|
+ /* Check index is valid. */
|
|
|
+ if (IDX32_INVALID(idx, XMSS_IDX_LEN, h)) {
|
|
|
+ /* Set index to maximum value to distinguish from valid value. */
|
|
|
+ XMEMSET(sk, 0xFF, XMSS_IDX_LEN);
|
|
|
+ /* Zeroize the secret key. */
|
|
|
+ ForceZero(sk + XMSS_IDX_LEN, params->sk_len - XMSS_IDX_LEN);
|
|
|
+ ret = KEY_EXHAUSTED_E;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Update SK_MT */
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Increment the index in the secret key. */
|
|
|
+ c32toa(idx + 1, sk);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Message compression */
|
|
|
+ if (ret == 0) {
|
|
|
+ const byte* sk_prf = sk + XMSS_IDX_LEN + n;
|
|
|
+
|
|
|
+ /* byte[n] r = PRF(SK_PRF, toByte(idx_sig, 32)); */
|
|
|
+ wc_idx_copy(sig, params->idx_len, state->buf, XMSS_PRF_M_LEN);
|
|
|
+ wc_xmss_prf(state, sk_prf, state->buf, sig_r);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ const byte* pub_root = sk + XMSS_IDX_LEN + 2 * n;
|
|
|
+
|
|
|
+ /* Compute the message hash. */
|
|
|
+ wc_xmss_hash_message(state, sig_r, pub_root, sig, XMSS_IDX_LEN, m, mlen,
|
|
|
+ node);
|
|
|
+ ret = state->ret;
|
|
|
+ /* Place new signature data after index and 'r'. */
|
|
|
+ sig += XMSS_IDX_LEN + n;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Set all address values to zero and set type to OTS. */
|
|
|
+ XMEMSET(state->addr, 0, sizeof(HashAddress));
|
|
|
+ state->addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_OTS;
|
|
|
+ /* treeSig || treeHash = sig_ots || auth */
|
|
|
+ state->addr[XMSS_ADDR_OTS] = idx;
|
|
|
+ /* Create WOTS+ signature for tree into signature (sig_ots). */
|
|
|
+ wc_xmss_wots_sign(state, node, sk_seed, pk_seed, state->addr, sig);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ sig += params->wots_sig_len;
|
|
|
+ /* Add authentication path (auth) and calc new root. */
|
|
|
+ XMEMCPY(sig, bds->authPath, h * n);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Update BDS state - update authentication path for next index. */
|
|
|
+ /* Check not last node. */
|
|
|
+ if (idx < ((word32)1 << h) - 1) {
|
|
|
+ /* Calculate next authentication path node. */
|
|
|
+ wc_xmss_bds_auth_path(state, bds, idx, sk_seed, pk_seed,
|
|
|
+ state->addr);
|
|
|
+ ret = state->ret;
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Algorithm 4.6: Step 5. */
|
|
|
+ wc_xmss_bds_treehash_updates(state, bds, hk >> 1, sk_seed,
|
|
|
+ pk_seed, state->addr);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Store BDS state back into secret/private key. */
|
|
|
+ wc_xmss_bds_state_store(state, sk, bds);
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef WOLFSSL_SMALL_STACK
|
|
|
+ /* Dispose of allocated data of BDS states. */
|
|
|
+ wc_xmss_bds_state_free(bds);
|
|
|
+#endif
|
|
|
+ return ret;
|
|
|
+#else
|
|
|
+ (void)state;
|
|
|
+ (void)m;
|
|
|
+ (void)mlen;
|
|
|
+ (void)sk;
|
|
|
+ (void)sig;
|
|
|
+
|
|
|
+ return NOT_COMPILED_IN;
|
|
|
+#endif /* WOLFSSL_XMSS_MIN_HEIGHT <= 32 */
|
|
|
+}
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * XMSS^MT
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+/* Generate a XMSS^MT key pair from seeds.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.2.2, Algorithm 15: XMSS^MT_keyGen.
|
|
|
+ * ...
|
|
|
+ * # Example initialization
|
|
|
+ * idx_MT = 0;
|
|
|
+ * setIdx(SK_MT, idx_MT);
|
|
|
+ * initialize SK_PRF with a uniformly random n-byte string;
|
|
|
+ * setSK_PRF(SK_MT, SK_PRF);
|
|
|
+ * initialize SEED with a uniformly random n-byte string;
|
|
|
+ * setSEED(SK_MT, SEED);
|
|
|
+ *
|
|
|
+ * # Generate reduced XMSS private keys
|
|
|
+ * ADRS = toByte(0, 32);
|
|
|
+ * for ( layer = 0; layer < d; layer++ ) {
|
|
|
+ * ADRS.setLayerAddress(layer);
|
|
|
+ * for ( tree = 0; tree <
|
|
|
+ * (1 << ((d - 1 - layer) * (h / d)));
|
|
|
+ * tree++ ) {
|
|
|
+ * ADRS.setTreeAddress(tree);
|
|
|
+ * for ( i = 0; i < 2^(h / d); i++ ) {
|
|
|
+ * wots_sk[i] = WOTS_genSK();
|
|
|
+ * }
|
|
|
+ * setXMSS_SK(SK_MT, wots_sk, tree, layer);
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * SK = getXMSS_SK(SK_MT, 0, d - 1);
|
|
|
+ * setSEED(SK, SEED);
|
|
|
+ * root = treeHash(SK, 0, h / d, ADRS);
|
|
|
+ * setRoot(SK_MT, root);
|
|
|
+ *
|
|
|
+ * PK_MT = OID || root || SEED;
|
|
|
+ * return (SK_MT || PK_MT);
|
|
|
+ *
|
|
|
+ * HDSS, Section 4.5, The algorithm, Initialization.
|
|
|
+ * OPX, Section 2, Key Generation.
|
|
|
+ *
|
|
|
+ * wots_sk, SK_PRF and SEED passed in as seed.
|
|
|
+ * Store seed for wots_sk instead of generated wots_sk.
|
|
|
+ * OID not stored in PK this is handled in upper layer.
|
|
|
+ * BDS state is appended to SK:
|
|
|
+ * SK = idx || wots_sk || SK_PRF || root || SEED || BDS_STATE;
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] seed Secret/Private and public seed.
|
|
|
+ * @param [out] sk Secret key.
|
|
|
+ * @param [out] pk Public key.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return MEMORY_E on dynamic memory allocation failure.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+int wc_xmssmt_keygen(XmssState* state, const unsigned char* seed,
|
|
|
+ unsigned char* sk, unsigned char* pk)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ unsigned char* sk_seed = sk + params->idx_len;
|
|
|
+ unsigned char* pk_root = pk;
|
|
|
+ unsigned char* pk_seed = pk + n;
|
|
|
+ word8 i;
|
|
|
+ byte* wots_sigs;
|
|
|
+ BdsState* bds = NULL;
|
|
|
+
|
|
|
+ /* Allocate memory for BDS states and tree hash instances. */
|
|
|
+ ret = wc_xmss_bds_state_alloc(params, &bds);
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Offsets into seed. */
|
|
|
+ const byte* seed_priv = seed;
|
|
|
+ const byte* seed_pub = seed + 2 * params->n;
|
|
|
+
|
|
|
+ /* Load the BDS state from secret/private key. */
|
|
|
+ wc_xmss_bds_state_load(state, sk, bds, &wots_sigs);
|
|
|
+
|
|
|
+ /* Set first index to 0 in private key. */
|
|
|
+ XMEMSET(sk, 0, params->idx_len);
|
|
|
+ /* Set private key seed and private key for PRF in to private key. */
|
|
|
+ XMEMCPY(sk_seed, seed_priv, 2 * n);
|
|
|
+ /* Set public key seed into public key. */
|
|
|
+ XMEMCPY(pk_seed, seed_pub, n);
|
|
|
+
|
|
|
+ /* Set all address values to zero. */
|
|
|
+ XMEMSET(state->addr, 0, sizeof(HashAddress));
|
|
|
+ /* Hash address layer is 0 = bottom-most layer. */
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Setup state and compute WOTS+ signatures for all but top-most subtree. */
|
|
|
+ for (i = 0; (ret == 0) && (i < params->d - 1); i++) {
|
|
|
+ /* Compute root for subtree. */
|
|
|
+ wc_xmss_bds_treehash_initial(state, bds + i, sk_seed, pk_seed,
|
|
|
+ state->addr, pk_root);
|
|
|
+ ret = state->ret;
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Create signature for subtree for first index. */
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = i+1;
|
|
|
+ wc_xmss_wots_sign(state, pk_root, sk_seed, pk_seed, state->addr,
|
|
|
+ wots_sigs + i * params->wots_sig_len);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Compute root for top-most subtree. */
|
|
|
+ wc_xmss_bds_treehash_initial(state, bds + i, sk_seed, pk_seed,
|
|
|
+ state->addr, pk_root);
|
|
|
+ /* Return any errors that occurred during hashing. */
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Offset of root node in private key. */
|
|
|
+ unsigned char* sk_root = sk_seed + 2 * n;
|
|
|
+
|
|
|
+ /* Append public key (root node and public seed) to private key. */
|
|
|
+ XMEMCPY(sk_root, pk_root, 2 * n);
|
|
|
+
|
|
|
+ /* Store BDS state back into secret/private key. */
|
|
|
+ wc_xmss_bds_state_store(state, sk, bds);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Dispose of allocated data of BDS states. */
|
|
|
+ wc_xmss_bds_state_free(bds);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#if !defined(WORD64_AVAILABLE) && (WOLFSSL_XMSS_MAX_HEIGHT > 32)
|
|
|
+ #error "Support not available - use XMSS small code option"
|
|
|
+#endif
|
|
|
+
|
|
|
+#if (WOLFSSL_XMSS_MAX_HEIGHT > 32)
|
|
|
+ typedef word64 XmssIdx;
|
|
|
+ #define IDX_MAX_BITS 64
|
|
|
+#else
|
|
|
+ typedef word32 XmssIdx;
|
|
|
+ #define IDX_MAX_BITS 32
|
|
|
+#endif
|
|
|
+
|
|
|
+/* Decode index into word.
|
|
|
+ *
|
|
|
+ * @param [out] idx Index from encoding.
|
|
|
+ * @param [in] c Count of bytes to decode to index.
|
|
|
+ * @param [in] a Array to decode from.
|
|
|
+ */
|
|
|
+static void xmss_idx_decode(XmssIdx* idx, word8 c, const unsigned char* a)
|
|
|
+{
|
|
|
+ word8 i;
|
|
|
+ XmssIdx n = 0;
|
|
|
+
|
|
|
+ for (i = 0; i < c; i++) {
|
|
|
+ n <<= 8;
|
|
|
+ n += a[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ *idx = n;
|
|
|
+}
|
|
|
+
|
|
|
+/* Check whether index is valid.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to check.
|
|
|
+ * @param [in] h Full tree Height.
|
|
|
+ */
|
|
|
+static int xmss_idx_invalid(XmssIdx i, word8 h)
|
|
|
+{
|
|
|
+ return ((i + 1) >> h) != 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Get tree and leaf index from index.
|
|
|
+ *
|
|
|
+ * @param [in] i Index to split.
|
|
|
+ * @param [in] h Tree height.
|
|
|
+ * @param [out] t Tree index.
|
|
|
+ * @param [out] l Leaf index.
|
|
|
+ */
|
|
|
+static void xmss_idx_get_tree_leaf(XmssIdx i, word8 h, XmssIdx* t, word32* l)
|
|
|
+{
|
|
|
+ *l = (word32)i & (((word32)1 << h) - 1);
|
|
|
+ *t = i >> h;
|
|
|
+}
|
|
|
+
|
|
|
+/* Set the index into address as the tree index.
|
|
|
+ *
|
|
|
+ * @param [in] i Tree index.
|
|
|
+ * @param [in, out] a Hash address.
|
|
|
+ */
|
|
|
+static void xmss_idx_set_addr_tree(XmssIdx i, HashAddress a)
|
|
|
+{
|
|
|
+#if IDX_MAX_BITS == 32
|
|
|
+ a[XMSS_ADDR_TREE_HI] = 0;
|
|
|
+ a[XMSS_ADDR_TREE] = i;
|
|
|
+#else
|
|
|
+ a[XMSS_ADDR_TREE_HI] = (word32)(i >> 32);
|
|
|
+ a[XMSS_ADDR_TREE] = (word32)(i );
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+/* Sign message with XMSS^MT.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.9, Algorithm 11: treeSig
|
|
|
+ * ...
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(idx_sig);
|
|
|
+ * sig_ots = WOTS_sign(getWOTS_SK(SK, idx_sig),
|
|
|
+ * M', getSEED(SK), ADRS);
|
|
|
+ * Sig = sig_ots || auth;
|
|
|
+ * return Sig;
|
|
|
+ * RFC 8391: 4.2.4, Algorithm 16: XMSS^MT_sign.
|
|
|
+ * ...
|
|
|
+ * # Init
|
|
|
+ * ADRS = toByte(0, 32);
|
|
|
+ * SEED = getSEED(SK_MT);
|
|
|
+ * SK_PRF = getSK_PRF(SK_MT);
|
|
|
+ * idx_sig = getIdx(SK_MT);
|
|
|
+ *
|
|
|
+ * # Update SK_MT
|
|
|
+ * setIdx(SK_MT, idx_sig + 1);
|
|
|
+ *
|
|
|
+ * # Message compression
|
|
|
+ * byte[n] r = PRF(SK_PRF, toByte(idx_sig, 32));
|
|
|
+ * byte[n] M' = H_msg(r || getRoot(SK_MT) || (toByte(idx_sig, n)), M);
|
|
|
+ *
|
|
|
+ * # Sign
|
|
|
+ * Sig_MT = idx_sig;
|
|
|
+ * unsigned int idx_tree
|
|
|
+ * = (h - h / d) most significant bits of idx_sig;
|
|
|
+ * unsigned int idx_leaf = (h / d) least significant bits of idx_sig;
|
|
|
+ * SK = idx_leaf || getXMSS_SK(SK_MT, idx_tree, 0) || SK_PRF
|
|
|
+ * || toByte(0, n) || SEED;
|
|
|
+ * ADRS.setLayerAddress(0);
|
|
|
+ * ADRS.setTreeAddress(idx_tree);
|
|
|
+ * Sig_tmp = treeSig(M', SK, idx_leaf, ADRS);
|
|
|
+ * Sig_MT = Sig_MT || r || Sig_tmp;
|
|
|
+ * for ( j = 1; j < d; j++ ) {
|
|
|
+ * root = treeHash(SK, 0, h / d, ADRS);
|
|
|
+ * idx_leaf = (h / d) least significant bits of idx_tree;
|
|
|
+ * idx_tree = (h - j * (h / d)) most significant bits of idx_tree;
|
|
|
+ * SK = idx_leaf || getXMSS_SK(SK_MT, idx_tree, j) || SK_PRF
|
|
|
+ * || toByte(0, n) || SEED;
|
|
|
+ * ADRS.setLayerAddress(j);
|
|
|
+ * ADRS.setTreeAddress(idx_tree);
|
|
|
+ * Sig_tmp = treeSig(root, SK, idx_leaf, ADRS);
|
|
|
+ * Sig_MT = Sig_MT || Sig_tmp;
|
|
|
+ * }
|
|
|
+ * return SK_MT || Sig_MT;
|
|
|
+ *
|
|
|
+ * 'auth' was built at key generation or after computing previous signature.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] idx Index to sign with.
|
|
|
+ * @param [in] wots_sigs Pre-computed WOTS+ signatures.
|
|
|
+ * @param [in] m Buffer holding message.
|
|
|
+ * @param [in] mlen Length of message in buffer.
|
|
|
+ * @param [in, out] sk Secret/Private key.
|
|
|
+ * @param [out] sig Signature and message data.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+static int wc_xmssmt_sign_msg(XmssState* state, BdsState* bds, XmssIdx idx,
|
|
|
+ byte* wots_sigs, const unsigned char* m, word32 mlen, unsigned char* sk,
|
|
|
+ unsigned char* sig)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word8 idx_len = params->idx_len;
|
|
|
+ const byte* sk_prf = sk + idx_len + n;
|
|
|
+ byte* sig_mt = sig;
|
|
|
+ byte* sig_r = sig + idx_len;
|
|
|
+ byte node[WC_XMSS_MAX_N];
|
|
|
+
|
|
|
+ /* Message compression */
|
|
|
+ /* byte[n] r = PRF(SK_PRF, toByte(idx_sig, 32)); */
|
|
|
+ wc_idx_copy(sig_mt, idx_len, state->buf, XMSS_PRF_M_LEN);
|
|
|
+ wc_xmss_prf(state, sk_prf, state->buf, sig_r);
|
|
|
+ ret = state->ret;
|
|
|
+ if (ret == 0) {
|
|
|
+ const byte* pub_root = sk + idx_len + 2 * n;
|
|
|
+ /* byte[n] M' = H_msg(r || getRoot(SK_MT) || (toByte(idx_sig, n)), M);
|
|
|
+ */
|
|
|
+ wc_xmss_hash_message(state, sig_r, pub_root, sig, idx_len, m, mlen,
|
|
|
+ node);
|
|
|
+ ret = state->ret;
|
|
|
+ /* Place new signature data after index and 'r'. */
|
|
|
+ sig += idx_len + n;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Sign */
|
|
|
+ if (ret == 0) {
|
|
|
+ const byte* sk_seed = sk + idx_len;
|
|
|
+ const byte* pk_seed = sk + idx_len + 3 * n;
|
|
|
+ XmssIdx idx_tree;
|
|
|
+ word32 idx_leaf;
|
|
|
+
|
|
|
+ /* Set all address values to zero and set type to OTS. */
|
|
|
+ XMEMSET(state->addr, 0, sizeof(HashAddress));
|
|
|
+ state->addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_OTS;
|
|
|
+
|
|
|
+ /* Fist iteration - calculate signature. */
|
|
|
+ /* Set layer, tree and OTS leaf index into hash address. */
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = 0;
|
|
|
+ xmss_idx_get_tree_leaf(idx, hs, &idx_tree, &idx_leaf);
|
|
|
+ xmss_idx_set_addr_tree(idx_tree, state->addr);
|
|
|
+ /* treeSig || treeHash = sig_ots || auth */
|
|
|
+ state->addr[XMSS_ADDR_OTS] = idx_leaf;
|
|
|
+ /* Create WOTS+ signature for tree into signature (sig_ots). */
|
|
|
+ wc_xmss_wots_sign(state, node, sk_seed, pk_seed, state->addr, sig);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ if (ret == 0) {
|
|
|
+ word8 i;
|
|
|
+
|
|
|
+ sig += params->wots_sig_len;
|
|
|
+ /* Add authentication path. */
|
|
|
+ XMEMCPY(sig, bds[BDS_IDX(idx, 0, hs, params->d)].authPath, hs * n);
|
|
|
+ sig += hs * n;
|
|
|
+
|
|
|
+ /* Remaining iterations from storage. */
|
|
|
+ for (i = 1; i < params->d; i++) {
|
|
|
+ /* Copy out precomputed signature into signature (sig_ots). */
|
|
|
+ XMEMCPY(sig, wots_sigs + (i - 1) * params->wots_sig_len,
|
|
|
+ params->wots_sig_len);
|
|
|
+ sig += params->wots_sig_len;
|
|
|
+ /* Add authentication path (auth) and calc new root. */
|
|
|
+ XMEMCPY(sig, bds[BDS_IDX(idx, i, hs, params->d)].authPath, hs * n);
|
|
|
+ sig += hs * n;
|
|
|
+ }
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/* Compute BDS state for signing next index.
|
|
|
+ *
|
|
|
+ * HDSS, Section 4.5, The algorithm, Update and output phase.
|
|
|
+ * OPX, Section 2, Signature Generation. Para 2 and 3.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in, out] bds BDS state.
|
|
|
+ * @param [in] idx Index to sign with.
|
|
|
+ * @param [in] wots_sigs Pre-computed WOTS+ signatures.
|
|
|
+ * @param [in] m Buffer holding message.
|
|
|
+ * @param [in] mlen Length of message in buffer.
|
|
|
+ * @param [in, out] sk Secret/Private key.
|
|
|
+ * @param [out] sig Signature and message data.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+static int wc_xmssmt_sign_next_idx(XmssState* state, BdsState* bds, XmssIdx idx,
|
|
|
+ byte* wots_sigs, unsigned char* sk)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const word8 h = params->h;
|
|
|
+ const word8 hs = params->sub_h;
|
|
|
+ const word8 hsk = params->sub_h - params->bds_k;
|
|
|
+ const byte* sk_seed = sk + params->idx_len;
|
|
|
+ const byte* pk_seed = sk + params->idx_len + 3 * n;
|
|
|
+ XmssIdx idx_tree;
|
|
|
+ int computeAuthPath = 1;
|
|
|
+ unsigned int updates;
|
|
|
+ word8 i;
|
|
|
+
|
|
|
+ /* Update BDS state - update authentication path for next index. */
|
|
|
+ /* HDSS, Algorithm 4.6, Step 5: repeat (H - K) / 2 times. */
|
|
|
+ updates = hsk >> 1;
|
|
|
+
|
|
|
+ idx_tree = (idx >> hs) + 1;
|
|
|
+ /* Check whether last tree. */
|
|
|
+ if (idx_tree < ((XmssIdx)1 << (h - hs))) {
|
|
|
+ /* Set hash address to next tree. */
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = 0;
|
|
|
+ xmss_idx_set_addr_tree(idx_tree, state->addr);
|
|
|
+ /* Update BDS state. */
|
|
|
+ wc_xmss_bds_update(state, &bds[BDS_ALT_IDX(idx, 0, hs, params->d)],
|
|
|
+ sk_seed, pk_seed, state->addr);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; (ret == 0) && (i < params->d); i++) {
|
|
|
+ word32 idx_leaf;
|
|
|
+ word8 bds_i = BDS_IDX(idx, i, hs, params->d);
|
|
|
+ word8 alt_i = BDS_ALT_IDX(idx, i, hs, params->d);
|
|
|
+
|
|
|
+ /* Check not last at height. */
|
|
|
+ if (((idx + 1) << (IDX_MAX_BITS - ((i + 1) * hs))) != 0) {
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = i;
|
|
|
+ xmss_idx_get_tree_leaf(idx >> (hs * i), hs, &idx_tree, &idx_leaf);
|
|
|
+ xmss_idx_set_addr_tree(idx_tree, state->addr);
|
|
|
+ idx_tree++;
|
|
|
+
|
|
|
+ if (computeAuthPath) {
|
|
|
+ /* Compute authentication path for tree. */
|
|
|
+ wc_xmss_bds_auth_path(state, &bds[bds_i], idx_leaf, sk_seed,
|
|
|
+ pk_seed, state->addr);
|
|
|
+ ret = state->ret;
|
|
|
+ computeAuthPath = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* HDSS, Algorithm 4.6: Step 5. */
|
|
|
+ updates = wc_xmss_bds_treehash_updates(state, &bds[bds_i],
|
|
|
+ updates, sk_seed, pk_seed, state->addr);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check tree not first, updates to do, tree not last at height and
|
|
|
+ * next leaf in alt state is not last. */
|
|
|
+ if ((ret == 0) && (i > 0) && (updates > 0) &&
|
|
|
+ (idx_tree < ((XmssIdx)1 << (h - (hs * (i + 1))))) &&
|
|
|
+ (bds[alt_i].next < ((word32)1 << h))) {
|
|
|
+ xmss_idx_set_addr_tree(idx_tree, state->addr);
|
|
|
+ /* Update alternative BDS state. */
|
|
|
+ wc_xmss_bds_update(state, &bds[alt_i], sk_seed, pk_seed,
|
|
|
+ state->addr);
|
|
|
+ ret = state->ret;
|
|
|
+ updates--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Last at height. */
|
|
|
+ else {
|
|
|
+ /* Set layer, tree and OTS leaf index into hash address. */
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = i + 1;
|
|
|
+ idx_tree = (idx + 1) >> ((i + 1) * hs);
|
|
|
+ xmss_idx_get_tree_leaf(idx_tree, hs, &idx_tree, &idx_leaf);
|
|
|
+ xmss_idx_set_addr_tree(idx_tree, state->addr);
|
|
|
+ /* Cache WOTS+ signature for new tree. */
|
|
|
+ state->addr[XMSS_ADDR_OTS] = idx_leaf;
|
|
|
+ wc_xmss_wots_sign(state, bds[alt_i].stack, sk_seed, pk_seed,
|
|
|
+ state->addr, wots_sigs + i * params->wots_sig_len);
|
|
|
+ ret = state->ret;
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ word8 d;
|
|
|
+
|
|
|
+ /* Reset old BDS state. */
|
|
|
+ bds[bds_i].offset = 0;
|
|
|
+ bds[bds_i].next = 0;
|
|
|
+
|
|
|
+ /* Done an update. */
|
|
|
+ updates--;
|
|
|
+ /* Need to compute authentication path in next tree up. */
|
|
|
+ computeAuthPath = 1;
|
|
|
+ /* Mark the tree hashes as complete in new BDS state. */
|
|
|
+ for (d = 0; d < hsk; d++) {
|
|
|
+ wc_xmss_bds_state_treehash_complete(&bds[alt_i], d);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/* Sign a message with XMSS^MT and update BDS state for signing next index.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.2.4, Algorithm 16: XMSS^MT_sign.
|
|
|
+ * HDSS, Section 4.5, The algorithm, Update and output phase.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] m Buffer holding message.
|
|
|
+ * @param [in] mlen Length of message in buffer.
|
|
|
+ * @param [in, out] sk Secret/Private key.
|
|
|
+ * @param [out] sig Signature and message data.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return MEMORY_E on dynamic memory allocation failure.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+int wc_xmssmt_sign(XmssState* state, const unsigned char* m, word32 mlen,
|
|
|
+ unsigned char* sk, unsigned char* sig)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 h = params->h;
|
|
|
+ const word8 idx_len = params->idx_len;
|
|
|
+ XmssIdx idx = 0;
|
|
|
+ byte* sig_mt = sig;
|
|
|
+ byte* wots_sigs;
|
|
|
+ BdsState* bds = NULL;
|
|
|
+
|
|
|
+ /* Allocate memory for BDS states and tree hash instances. */
|
|
|
+ ret = wc_xmss_bds_state_alloc(params, &bds);
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Load the BDS state from secret/private key. */
|
|
|
+ wc_xmss_bds_state_load(state, sk, bds, &wots_sigs);
|
|
|
+
|
|
|
+ /* Copy the index into the signature data: Sig_MT = idx_sig. */
|
|
|
+ XMEMCPY(sig_mt, sk, idx_len);
|
|
|
+
|
|
|
+ /* Read index from the secret key. */
|
|
|
+ xmss_idx_decode(&idx, idx_len, sk);
|
|
|
+ }
|
|
|
+ if ((ret == 0) && xmss_idx_invalid(idx, h)) {
|
|
|
+ /* Set index to maximum value to distinguish from valid value. */
|
|
|
+ XMEMSET(sk, 0xFF, idx_len);
|
|
|
+ /* Zeroize the secret key. */
|
|
|
+ ForceZero(sk + idx_len, params->sk_len - idx_len);
|
|
|
+ ret = KEY_EXHAUSTED_E;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Increment the index in the secret key. */
|
|
|
+ wc_idx_update(sk, idx_len);
|
|
|
+
|
|
|
+ /* Compute signature. */
|
|
|
+ ret = wc_xmssmt_sign_msg(state, bds, idx, wots_sigs, m, mlen, sk, sig);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Only update if not last index. */
|
|
|
+ if ((ret == 0) && (idx < (((XmssIdx)1 << h) - 1))) {
|
|
|
+ /* Update BDS state for signing next index. */
|
|
|
+ ret = wc_xmssmt_sign_next_idx(state, bds, idx, wots_sigs, sk);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Store BDS state back into secret/private key. */
|
|
|
+ wc_xmss_bds_state_store(state, sk, bds);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Dispose of allocated data of BDS states. */
|
|
|
+ wc_xmss_bds_state_free(bds);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+#endif /* WOLFSSL_WC_XMSS_SMALL */
|
|
|
+
|
|
|
+/* Check if more signatures are possible with secret/private key.
|
|
|
+ *
|
|
|
+ * @param [in] params XMSS parameters
|
|
|
+ * @param [in] sk Secret/private key.
|
|
|
+ * @return 1 when signatures possible.
|
|
|
+ * @return 0 when key exhausted.
|
|
|
+ */
|
|
|
+
|
|
|
+int wc_xmss_sigsleft(const XmssParams* params, unsigned char* sk)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ wc_Idx idx;
|
|
|
+
|
|
|
+ /* Read index from the secret key. */
|
|
|
+ WC_IDX_DECODE(idx, params->idx_len, sk, ret);
|
|
|
+ /* Check validity of index. */
|
|
|
+ if ((ret == 0) && (WC_IDX_INVALID(idx, params->idx_len, params->h))) {
|
|
|
+ ret = KEY_EXHAUSTED_E;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret == 0;
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
+
|
|
|
+/********************************************
|
|
|
+ * SIGN OPEN - Verify
|
|
|
+ ********************************************/
|
|
|
+
|
|
|
+#if !defined(WOLFSSL_WC_XMSS_SMALL) || defined(WOLFSSL_XMSS_VERIFY_ONLY)
|
|
|
+/* Compute root node with leaf and authentication path.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.10, Algorithm 13: XMSS_rootFromSig
|
|
|
+ * ...
|
|
|
+ * for ( k = 0; k < h; k++ ) {
|
|
|
+ * ADRS.setTreeHeight(k);
|
|
|
+ * if ( (floor(idx_sig / (2^k)) % 2) == 0 ) {
|
|
|
+ * ADRS.setTreeIndex(ADRS.getTreeIndex() / 2);
|
|
|
+ * node[1] = RAND_HASH(node[0], auth[k], SEED, ADRS);
|
|
|
+ * } else {
|
|
|
+ * ADRS.setTreeIndex((ADRS.getTreeIndex() - 1) / 2);
|
|
|
+ * node[1] = RAND_HASH(auth[k], node[0], SEED, ADRS);
|
|
|
+ * }
|
|
|
+ * node[0] = node[1];
|
|
|
+ * }
|
|
|
+ * return node[0];
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] idx_leaf Index of leaf node.
|
|
|
+ * @param [in] auth_path Authentication path.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [in, out] root On in, leaf node. On out, root node.
|
|
|
+ */
|
|
|
+static void wc_xmss_compute_root(XmssState* state, word32 idx_leaf,
|
|
|
+ const byte* auth_path, const byte* pk_seed, HashAddress addr, byte* root)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ const byte* b[2][2] = { { root, auth_path }, { auth_path, root } };
|
|
|
+ word8 i;
|
|
|
+
|
|
|
+ for (i = 0; i < params->sub_h; i++) {
|
|
|
+ /* Get which side the leaf is on. */
|
|
|
+ word8 s = idx_leaf & 1;
|
|
|
+ /* Set tree height and index. */
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = i;
|
|
|
+ idx_leaf >>= 1;
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = idx_leaf;
|
|
|
+
|
|
|
+ /* Put the result into buffer position for next RAND_HASH. */
|
|
|
+ wc_xmss_rand_hash_lr(state, b[s][0], b[s][1], pk_seed, addr, root);
|
|
|
+ /* Move to next auth path node. */
|
|
|
+ b[0][1] += n;
|
|
|
+ b[1][0] += n;
|
|
|
+ }
|
|
|
+}
|
|
|
+#else
|
|
|
+/* Compute root node with leaf and authentication path.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.10, Algorithm 13: XMSS_rootFromSig
|
|
|
+ * ...
|
|
|
+ * for ( k = 0; k < h; k++ ) {
|
|
|
+ * ADRS.setTreeHeight(k);
|
|
|
+ * if ( (floor(idx_sig / (2^k)) % 2) == 0 ) {
|
|
|
+ * ADRS.setTreeIndex(ADRS.getTreeIndex() / 2);
|
|
|
+ * node[1] = RAND_HASH(node[0], auth[k], SEED, ADRS);
|
|
|
+ * } else {
|
|
|
+ * ADRS.setTreeIndex((ADRS.getTreeIndex() - 1) / 2);
|
|
|
+ * node[1] = RAND_HASH(auth[k], node[0], SEED, ADRS);
|
|
|
+ * }
|
|
|
+ * node[0] = node[1];
|
|
|
+ * }
|
|
|
+ * return node[0];
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] idx_leaf Index of leaf node.
|
|
|
+ * @param [in] auth_path Authentication path.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] addr Hash address.
|
|
|
+ * @param [in, out] node On in, leaf node. On out, root node.
|
|
|
+ */
|
|
|
+static void wc_xmss_compute_root(XmssState* state, word32 idx_leaf,
|
|
|
+ const byte* auth_path, const byte* pk_seed, HashAddress addr, byte* node)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ byte buffer[2 * WC_XMSS_MAX_N];
|
|
|
+ byte* b[2][2] = { { buffer, buffer + n }, { buffer + n, buffer } };
|
|
|
+ word8 i;
|
|
|
+
|
|
|
+ /* Setup buffer for first RAND_HASH. */
|
|
|
+ XMEMCPY(b[idx_leaf & 1][0], node, n);
|
|
|
+ XMEMCPY(b[idx_leaf & 1][1], auth_path, n);
|
|
|
+ auth_path += n;
|
|
|
+
|
|
|
+ for (i = 0; i < params->sub_h - 1; i++) {
|
|
|
+ /* Set tree height and index. */
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = i;
|
|
|
+ idx_leaf >>= 1;
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = idx_leaf;
|
|
|
+
|
|
|
+ /* Put the result into buffer position for next RAND_HASH. */
|
|
|
+ wc_xmss_rand_hash(state, buffer, pk_seed, addr, b[idx_leaf & 1][0]);
|
|
|
+ /* Put auth path node into other half of buffer. */
|
|
|
+ XMEMCPY(b[idx_leaf & 1][1], auth_path, n);
|
|
|
+ /* Move to next auth path node. */
|
|
|
+ auth_path += n;
|
|
|
+ }
|
|
|
+
|
|
|
+ addr[XMSS_ADDR_TREE_HEIGHT] = i;
|
|
|
+ idx_leaf >>= 1;
|
|
|
+ addr[XMSS_ADDR_TREE_INDEX] = idx_leaf;
|
|
|
+ /* Last iteration into output node. */
|
|
|
+ wc_xmss_rand_hash(state, buffer, pk_seed, addr, node);
|
|
|
+}
|
|
|
+#endif /* !WOLFSSL_WC_XMSS_SMALL || WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
+
|
|
|
+/* Compute a root node from a tree signature.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.1.10, Algorithm 13: XMSS_rootFromSig
|
|
|
+ * ADRS.setType(0); # Type = OTS hash address
|
|
|
+ * ADRS.setOTSAddress(idx_sig);
|
|
|
+ * pk_ots = WOTS_pkFromSig(sig_ots, M', SEED, ADRS);
|
|
|
+ * ADRS.setType(1); # Type = L-tree address
|
|
|
+ * ADRS.setLTreeAddress(idx_sig);
|
|
|
+ * byte[n][2] node;
|
|
|
+ * node[0] = ltree(pk_ots, SEED, ADRS);
|
|
|
+ * ADRS.setType(2); # Type = hash tree address
|
|
|
+ * ADRS.setTreeIndex(idx_sig);
|
|
|
+ * [Compute root with leaf and authentication path]
|
|
|
+ *
|
|
|
+ * Computing the root from the leaf and authentication path can be implemented
|
|
|
+ * in different ways and is therefore extracted to its own function.
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] pk_seed Random public seed.
|
|
|
+ * @param [in] sig WOTS+ signature for this tree.
|
|
|
+ * @param [in] idx_sig Index of signature leaf in this tree.
|
|
|
+ * @param [in, out] addr Hash address.
|
|
|
+ * @param [in, out] node On in, previous root node.
|
|
|
+ * On out, root node of this subtree.
|
|
|
+ */
|
|
|
+static void wc_xmss_root_from_sig(XmssState* state, const byte* pk_seed,
|
|
|
+ const byte* sig, word32 idx_sig, HashAddress addr, byte* node)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ byte* wots_pk = state->pk;
|
|
|
+ const byte* auth_path = sig + params->wots_sig_len;
|
|
|
+
|
|
|
+ /* Compute WOTS+ public key value from signature. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_OTS;
|
|
|
+ addr[XMSS_ADDR_OTS] = idx_sig;
|
|
|
+ wc_xmss_wots_pk_from_sig(state, sig, node, pk_seed, addr, wots_pk);
|
|
|
+
|
|
|
+ /* Compute leaves of L-tree from WOTS+ public key. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_LTREE;
|
|
|
+ /* XMSS_ADDR_LTREE is same as XMSS_ADDR_OTS in index and value. */
|
|
|
+ wc_xmss_ltree(state, wots_pk, pk_seed, addr, node);
|
|
|
+
|
|
|
+ /* Compute root node from leaf and authentication path. */
|
|
|
+ addr[XMSS_ADDR_TYPE] = WC_XMSS_ADDR_TYPE_TREE;
|
|
|
+ addr[XMSS_ADDR_TREE_ZERO] = 0;
|
|
|
+ wc_xmss_compute_root(state, idx_sig, auth_path, pk_seed, addr, node);
|
|
|
+}
|
|
|
+
|
|
|
+/* Verify message with signature using XMSS/MT.
|
|
|
+ *
|
|
|
+ * RFC 8391: 4.2.5, Algorithm 17: XMSSMT_verify
|
|
|
+ * idx_sig = getIdx(Sig_MT);
|
|
|
+ * SEED = getSEED(PK_MT);
|
|
|
+ * ADRS = toByte(0, 32);
|
|
|
+ *
|
|
|
+ * byte[n] M' = H_msg(getR(Sig_MT) || getRoot(PK_MT)
|
|
|
+ * || (toByte(idx_sig, n)), M);
|
|
|
+ *
|
|
|
+ * unsigned int idx_leaf
|
|
|
+ * = (h / d) least significant bits of idx_sig;
|
|
|
+ * unsigned int idx_tree
|
|
|
+ * = (h - h / d) most significant bits of idx_sig;
|
|
|
+ * Sig' = getXMSSSignature(Sig_MT, 0);
|
|
|
+ * ADRS.setLayerAddress(0);
|
|
|
+ * ADRS.setTreeAddress(idx_tree);
|
|
|
+ * byte[n] node = XMSS_rootFromSig(idx_leaf, getSig_ots(Sig'),
|
|
|
+ * getAuth(Sig'), M', SEED, ADRS);
|
|
|
+ * for ( j = 1; j < d; j++ ) {
|
|
|
+ * idx_leaf = (h / d) least significant bits of idx_tree;
|
|
|
+ * idx_tree = (h - j * h / d) most significant bits of idx_tree;
|
|
|
+ * Sig' = getXMSSSignature(Sig_MT, j);
|
|
|
+ * ADRS.setLayerAddress(j);
|
|
|
+ * ADRS.setTreeAddress(idx_tree);
|
|
|
+ * node = XMSS_rootFromSig(idx_leaf, getSig_ots(Sig'),
|
|
|
+ * getAuth(Sig'), node, SEED, ADRS);
|
|
|
+ * }
|
|
|
+ * if ( node == getRoot(PK_MT) ) {
|
|
|
+ * return true;
|
|
|
+ * } else {
|
|
|
+ * return false;
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * @param [in] state XMSS/MT state including digest and parameters.
|
|
|
+ * @param [in] m Message buffer.
|
|
|
+ * @param [in] mlen Length of message in bytes.
|
|
|
+ * @param [in] sig Buffer holding signature.
|
|
|
+ * @param [in] pk Public key.
|
|
|
+ * @return 0 on success.
|
|
|
+ * @return MEMORY_E on dynamic memory allocation failure.
|
|
|
+ * @return SIG_VERIFY_E on verification failure.
|
|
|
+ * @return <0 on digest failure.
|
|
|
+ */
|
|
|
+int wc_xmssmt_verify(XmssState* state, const unsigned char* m, word32 mlen,
|
|
|
+ const unsigned char* sig, const unsigned char* pk)
|
|
|
+{
|
|
|
+ const XmssParams* params = state->params;
|
|
|
+ const word8 n = params->n;
|
|
|
+ int ret = 0;
|
|
|
+ const byte* pub_root = pk;
|
|
|
+ const byte* pk_seed = pk + n;
|
|
|
+ byte node[WC_XMSS_MAX_N];
|
|
|
+ wc_Idx idx;
|
|
|
+ word32 idx_leaf = 0;
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ /* Set 32/64-bit index to 0. */
|
|
|
+ WC_IDX_ZERO(idx);
|
|
|
+ /* Set all address values to zero. */
|
|
|
+ XMEMSET(state->addr, 0, sizeof(HashAddress));
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Convert the index bytes from the signature to an integer. */
|
|
|
+ WC_IDX_DECODE(idx, params->idx_len, sig, ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ const byte* sig_r = sig + params->idx_len;
|
|
|
+ /* byte[n] M' = H_msg(getR(Sig_MT) || getRoot(PK_MT) ||
|
|
|
+ * (toByte(idx_sig, n)), M);
|
|
|
+ */
|
|
|
+ wc_xmss_hash_message(state, sig_r, pub_root, sig, params->idx_len, m,
|
|
|
+ mlen, node);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Set tree of hash address. */
|
|
|
+ WC_IDX_SET_ADDR_TREE(idx, params->idx_len, params->sub_h, state->addr,
|
|
|
+ idx_leaf);
|
|
|
+
|
|
|
+ /* Skip to first WOTS+ signature and derive root. */
|
|
|
+ sig += params->idx_len + n;
|
|
|
+ wc_xmss_root_from_sig(state, pk_seed, sig, idx_leaf, state->addr,
|
|
|
+ node);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ /* Calculate root of remaining subtrees up to top. */
|
|
|
+ for (i = 1; (ret == 0) && (i < params->d); i++) {
|
|
|
+ /* Set layer and tree. */
|
|
|
+ state->addr[XMSS_ADDR_LAYER] = i;
|
|
|
+ WC_IDX_SET_ADDR_TREE(idx, params->idx_len, params->sub_h, state->addr,
|
|
|
+ idx_leaf);
|
|
|
+ /* Skip to next WOTS+ signature and derive root. */
|
|
|
+ sig += params->wots_sig_len + params->sub_h * n;
|
|
|
+ wc_xmss_root_from_sig(state, pk_seed, sig, idx_leaf, state->addr,
|
|
|
+ node);
|
|
|
+ ret = state->ret;
|
|
|
+ }
|
|
|
+ /* Compare calculated node with public key root. */
|
|
|
+ if ((ret == 0) && (XMEMCMP(node, pub_root, n) != 0)) {
|
|
|
+ ret = SIG_VERIFY_E;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+#endif /* WOLFSSL_HAVE_XMSS */
|
|
|
+
|