diff --git a/.gitignore b/.gitignore
index 36c5375d..86369f9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -414,3 +414,5 @@ stsafe/wolfssl_stsafe_full_test
# uefi-library generated filesystem content
uefi-library/efifs
+
+puf/Build
diff --git a/README.md b/README.md
index acbde482..327127eb 100644
--- a/README.md
+++ b/README.md
@@ -247,6 +247,17 @@ verifying/decrypting operations.
Please see the [pkcs7/README.md](pkcs7/README.md) for further usage and details.
+
+
+#### PUF (SRAM Physically Unclonable Function)
+
+This directory contains a bare-metal example demonstrating wolfCrypt's SRAM PUF
+support. It derives device-unique cryptographic keys from the power-on state of
+SRAM memory using a BCH(127,64,t=10) fuzzy extractor with HKDF key derivation.
+Tested on NUCLEO-H563ZI (Cortex-M33).
+
+Please see the [puf/README.md](puf/README.md) for further usage and details.
+
#### PSK (Pre-Shared Keys)
diff --git a/puf/Makefile b/puf/Makefile
new file mode 100644
index 00000000..7935f605
--- /dev/null
+++ b/puf/Makefile
@@ -0,0 +1,98 @@
+# Makefile for wolfCrypt SRAM PUF bare-metal example
+#
+# Target: Cortex-M (tested on NUCLEO-H563ZI)
+# Usage: make - build puf_example.elf and puf_example.hex
+# make clean - remove build artifacts
+# make PUF_TEST=0 - build for real hardware SRAM (no test mode)
+#
+# Copyright (C) 2006-2026 wolfSSL Inc.
+
+# Toolchain
+TOOLCHAIN ?= arm-none-eabi-
+CC = $(TOOLCHAIN)gcc
+LD = $(TOOLCHAIN)gcc
+AR = $(TOOLCHAIN)ar
+OBJCOPY = $(TOOLCHAIN)objcopy
+OBJDUMP = $(TOOLCHAIN)objdump
+SIZE = $(TOOLCHAIN)size
+NM = $(TOOLCHAIN)nm
+
+# wolfSSL root (relative to this directory)
+WOLFSSL_ROOT ?= ../../wolfssl
+
+# Build output
+BUILD_DIR = ./Build
+BIN = puf_example
+
+# Architecture
+ARCHFLAGS = -mcpu=cortex-m33 -mthumb -mabi=aapcs
+
+# Compiler flags
+CFLAGS = $(ARCHFLAGS) -std=gnu99 -Wall -Os
+CFLAGS += -ffunction-sections -fdata-sections -fno-builtin
+CFLAGS += -DWOLFSSL_USER_SETTINGS
+CFLAGS += -I. -I$(WOLFSSL_ROOT)
+
+# Linker flags
+LDFLAGS = $(ARCHFLAGS)
+LDFLAGS += --specs=nosys.specs --specs=nano.specs
+LDFLAGS += -Wl,--gc-sections
+LDFLAGS += -Wl,-Map=$(BUILD_DIR)/$(BIN).map
+LDFLAGS += -T./linker.ld
+
+# Math lib
+LIBS = -lm
+
+# Source files
+SRC_C = main.c
+SRC_C += startup.c
+SRC_C += stm32.c
+
+# wolfCrypt sources needed for PUF
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/puf.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/sha256.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/kdf.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/hmac.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/hash.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/memory.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/wc_port.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/error.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/misc.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/logging.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/random.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/sp_int.c
+SRC_C += $(WOLFSSL_ROOT)/wolfcrypt/src/sha3.c
+
+# Object files
+FILENAMES_C = $(notdir $(SRC_C))
+OBJS_C = $(addprefix $(BUILD_DIR)/, $(FILENAMES_C:.c=.o))
+vpath %.c $(dir $(SRC_C))
+
+# Targets
+.PHONY: all clean
+
+all: $(BUILD_DIR) $(BUILD_DIR)/$(BIN).hex
+ @echo ""
+ $(SIZE) $(BUILD_DIR)/$(BIN).elf
+
+$(BUILD_DIR):
+ mkdir -p $(BUILD_DIR)
+
+$(BUILD_DIR)/%.o: %.c
+ @echo "Compiling: $(notdir $<)"
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+$(BUILD_DIR)/$(BIN).elf: $(OBJS_C)
+ @echo "Linking: $(notdir $@)"
+ $(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
+ @echo ""
+ $(NM) -n $@ > $(BUILD_DIR)/$(BIN).sym
+ $(OBJDUMP) -S $@ > $(BUILD_DIR)/$(BIN).disasm
+
+$(BUILD_DIR)/$(BIN).hex: $(BUILD_DIR)/$(BIN).elf
+ @echo "Generating HEX: $(notdir $@)"
+ $(OBJCOPY) -O ihex $< $@
+
+clean:
+ rm -f $(BUILD_DIR)/*.elf $(BUILD_DIR)/*.hex $(BUILD_DIR)/*.map
+ rm -f $(BUILD_DIR)/*.o $(BUILD_DIR)/*.sym $(BUILD_DIR)/*.disasm
diff --git a/puf/README.md b/puf/README.md
new file mode 100644
index 00000000..510e7115
--- /dev/null
+++ b/puf/README.md
@@ -0,0 +1,150 @@
+# wolfCrypt SRAM PUF Example
+
+Bare-metal example demonstrating SRAM PUF (Physically Unclonable Function)
+on Cortex-M targets (tested on NUCLEO-H563ZI).
+
+## Overview
+
+SRAM PUF exploits the random power-on state of SRAM memory cells to derive
+device-unique cryptographic keys. Each chip has a unique SRAM "fingerprint"
+caused by manufacturing variations.
+
+This example demonstrates:
+
+1. **Enrollment** - Read raw SRAM, generate helper data using BCH(127,64,t=10)
+ error-correcting codes
+2. **Reconstruction** - Re-read (noisy) SRAM, use helper data to recover the
+ same stable bits despite bit flips (corrects up to 10 per 127-bit codeword)
+3. **Key derivation** - Use HKDF-SHA256 to derive a 256-bit cryptographic key
+4. **Device identity** - SHA-256 hash of stable bits serves as a unique device ID
+
+## Building
+
+### Requirements
+
+- `arm-none-eabi-gcc` toolchain
+
+### Test Mode (default)
+
+Uses synthetic SRAM data -- runs on any target, no real hardware needed:
+
+```bash
+make
+```
+
+### Real Hardware Mode
+
+For actual SRAM PUF on hardware, edit `user_settings.h` and comment out
+`WOLFSSL_PUF_TEST`, then build:
+
+```bash
+make
+```
+
+### Output
+
+Build output is placed in `./Build/`:
+- `puf_example.elf` - Loadable ELF binary
+- `puf_example.hex` - Intel HEX for flash programmers
+
+## Flashing
+
+### OpenOCD (NUCLEO-H563ZI)
+
+```bash
+openocd -f interface/stlink.cfg -f target/stm32h5x.cfg \
+ -c "program Build/puf_example.elf verify reset exit"
+```
+
+When multiple ST-Links are connected, specify the serial number:
+
+```bash
+openocd -f interface/stlink.cfg \
+ -c "adapter serial " \
+ -f target/stm32h5x.cfg \
+ -c "program Build/puf_example.elf verify reset exit"
+```
+
+## UART Output
+
+Connect to the board's UART (typically 115200 baud) to see output:
+
+```
+--- wolfCrypt SRAM PUF Example ---
+
+PUF initialized.
+Mode: TEST (synthetic SRAM data)
+
+Enrollment complete.
+Identity (enrollment): 3ad99904f92897bad1a21bc9cbc3ab8f2dc4bc40dfe6e161c741f98ef8dd7e01
+Derived key (enrollment): aa8573f70a3253ca567500bdcd610face6a140e5fc68047e02d3f13958dcc480
+
+--- Simulating power cycle (noisy SRAM) ---
+
+Reconstruction complete (BCH corrected noisy bits).
+Identity (reconstructed): 3ad99904f92897bad1a21bc9cbc3ab8f2dc4bc40dfe6e161c741f98ef8dd7e01
+PASS: Identity matches after reconstruction.
+Derived key (reconstructed): aa8573f70a3253ca567500bdcd610face6a140e5fc68047e02d3f13958dcc480
+PASS: Derived key matches after reconstruction.
+
+--- PUF example complete ---
+```
+
+## Customizing for Your MCU
+
+### Linker Script
+
+Edit `linker.ld` to match your MCU's memory map:
+
+```ld
+MEMORY
+{
+ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* your flash size */
+ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 636K /* total - PUF_RAM */
+ PUF_RAM (rw) : ORIGIN = 0x2009F000, LENGTH = 4K /* end of SRAM */
+}
+```
+
+The `PUF_RAM` region must be at the end of SRAM and marked `NOLOAD` so the
+startup code does not zero it.
+
+### Architecture
+
+Edit the `ARCHFLAGS` in `Makefile`:
+
+```makefile
+ARCHFLAGS = -mcpu=cortex-m33 -mthumb
+```
+
+## API Usage
+
+```c
+wc_PufCtx ctx;
+uint8_t helperData[WC_PUF_HELPER_BYTES];
+uint8_t key[WC_PUF_KEY_SZ];
+
+/* First boot: Enroll */
+wc_PufInit(&ctx);
+wc_PufReadSram(&ctx, sram_addr, sram_size);
+wc_PufEnroll(&ctx);
+memcpy(helperData, ctx.helperData, WC_PUF_HELPER_BYTES);
+/* Store helperData to flash/NVM (it is NOT secret) */
+
+/* Subsequent boots: Reconstruct */
+wc_PufInit(&ctx);
+wc_PufReadSram(&ctx, sram_addr, sram_size);
+wc_PufReconstruct(&ctx, helperData, WC_PUF_HELPER_BYTES);
+wc_PufDeriveKey(&ctx, info, infoSz, key, sizeof(key));
+
+/* Always zeroize when done */
+wc_PufZeroize(&ctx);
+```
+
+## Security Notes
+
+- **Helper data is public** - It does not reveal the key. Safe to store
+ unencrypted in flash or transmit over the network.
+- **SRAM must not be accessed before PUF read** - Any read or write to the
+ PUF SRAM region before `wc_PufReadSram()` will corrupt the power-on entropy.
+- **Production RNG** - Replace the dummy `my_rng_seed_gen()` with your
+ MCU's hardware RNG (e.g., STM32 RNG peripheral).
diff --git a/puf/linker.ld b/puf/linker.ld
new file mode 100644
index 00000000..c335659b
--- /dev/null
+++ b/puf/linker.ld
@@ -0,0 +1,137 @@
+/* linker.ld
+ *
+ * Linker script for bare-metal Cortex-M33 with PUF SRAM region.
+ * Default: NUCLEO-H563ZI (STM32H563ZI)
+ *
+ * Uses STM32-standard symbol names for compatibility with
+ * startup_stm32h563zitx.s from STM32CubeH5.
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ */
+
+ENTRY(Reset_Handler)
+
+_estack = ORIGIN(RAM) + LENGTH(RAM);
+_Min_Heap_Size = 0x400;
+_Min_Stack_Size = 0x2000;
+
+MEMORY
+{
+ /* STM32H563ZI: 2MB flash, 640KB SRAM */
+ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
+ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 636K
+ /* Reserve 4K at end of SRAM for PUF (not touched by .bss clear) */
+ PUF_RAM (rw) : ORIGIN = 0x2009F000, LENGTH = 4K
+}
+
+SECTIONS
+{
+ .isr_vector :
+ {
+ . = ALIGN(4);
+ KEEP(*(.isr_vector))
+ . = ALIGN(4);
+ } > FLASH
+
+ .text :
+ {
+ . = ALIGN(4);
+ *(.text)
+ *(.text*)
+ *(.glue_7)
+ *(.glue_7t)
+ *(.eh_frame)
+ KEEP(*(.init))
+ KEEP(*(.fini))
+ . = ALIGN(4);
+ _etext = .;
+ } > FLASH
+
+ .rodata :
+ {
+ . = ALIGN(4);
+ *(.rodata)
+ *(.rodata*)
+ . = ALIGN(4);
+ } > FLASH
+
+ .ARM.extab :
+ {
+ *(.ARM.extab* .gnu.linkonce.armextab.*)
+ } > FLASH
+
+ .ARM :
+ {
+ __exidx_start = .;
+ *(.ARM.exidx*)
+ __exidx_end = .;
+ } > FLASH
+
+ .preinit_array :
+ {
+ PROVIDE_HIDDEN (__preinit_array_start = .);
+ KEEP(*(.preinit_array*))
+ PROVIDE_HIDDEN (__preinit_array_end = .);
+ } > FLASH
+
+ .init_array :
+ {
+ PROVIDE_HIDDEN (__init_array_start = .);
+ KEEP(*(SORT(.init_array.*)))
+ KEEP(*(.init_array*))
+ PROVIDE_HIDDEN (__init_array_end = .);
+ } > FLASH
+
+ .fini_array :
+ {
+ PROVIDE_HIDDEN (__fini_array_start = .);
+ KEEP(*(SORT(.fini_array.*)))
+ KEEP(*(.fini_array*))
+ PROVIDE_HIDDEN (__fini_array_end = .);
+ } > FLASH
+
+ _sidata = LOADADDR(.data);
+
+ .data :
+ {
+ . = ALIGN(4);
+ _sdata = .;
+ *(.data)
+ *(.data*)
+ . = ALIGN(4);
+ _edata = .;
+ } > RAM AT> FLASH
+
+ .bss :
+ {
+ . = ALIGN(4);
+ _sbss = .;
+ __bss_start__ = _sbss;
+ *(.bss)
+ *(.bss*)
+ *(COMMON)
+ . = ALIGN(4);
+ _ebss = .;
+ __bss_end__ = _ebss;
+ } > RAM
+
+ ._user_heap_stack :
+ {
+ . = ALIGN(8);
+ PROVIDE(end = .);
+ . = . + _Min_Heap_Size;
+ . = . + _Min_Stack_Size;
+ . = ALIGN(8);
+ } > RAM
+
+ /* PUF SRAM section: NOLOAD prevents startup code from zeroing.
+ * The raw power-on state of these bytes is the PUF source. */
+ .puf_sram (NOLOAD) :
+ {
+ . = ALIGN(4);
+ _puf_sram_start = .;
+ KEEP(*(.puf_sram))
+ KEEP(*(.puf_sram.*))
+ _puf_sram_end = .;
+ } > PUF_RAM
+}
diff --git a/puf/main.c b/puf/main.c
new file mode 100644
index 00000000..2f9b430f
--- /dev/null
+++ b/puf/main.c
@@ -0,0 +1,275 @@
+/* main.c
+ *
+ * Bare-metal SRAM PUF example for Cortex-M.
+ *
+ * Platform-portable PUF demonstration: enrollment, reconstruction,
+ * key derivation, and device identity. HAL-specific code (UART, RNG)
+ * is in a separate file (e.g., stm32.c).
+ *
+ * Supports both test mode (synthetic SRAM data) and real hardware SRAM PUF.
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+/* Platform HAL init (implemented in stm32.c or other target file) */
+extern void hal_init(void);
+
+/* -------------------------------------------------------------------------- */
+/* PUF SRAM region (real hardware path) */
+/* -------------------------------------------------------------------------- */
+
+#ifndef WOLFSSL_PUF_TEST
+/* This buffer is placed in the .puf_sram linker section (NOLOAD).
+ * The startup code must NOT zero this region - the raw power-on SRAM
+ * state is the PUF entropy source. */
+__attribute__((section(".puf_sram")))
+static volatile uint8_t puf_sram_region[WC_PUF_RAW_BYTES];
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* Helper: print hex buffer */
+/* -------------------------------------------------------------------------- */
+
+static void print_hex(const char* label, const uint8_t* data, uint32_t len)
+{
+ uint32_t i;
+ printf("%s: ", label);
+ for (i = 0; i < len; i++)
+ printf("%02x", data[i]);
+ printf("\n");
+}
+
+/* -------------------------------------------------------------------------- */
+/* Main PUF demonstration */
+/* -------------------------------------------------------------------------- */
+
+int main(void)
+{
+ wc_PufCtx ctx;
+ int ret;
+ uint8_t identity[WC_PUF_ID_SZ];
+ uint8_t key[WC_PUF_KEY_SZ];
+ uint8_t helperData[WC_PUF_HELPER_BYTES];
+ const uint8_t info[] = "puf-example-key";
+
+ /* Initialize platform HAL (UART, clocks, etc.) */
+ hal_init();
+
+ printf("\n--- wolfCrypt SRAM PUF Example ---\n\n");
+
+ /* Initialize wolfCrypt */
+ ret = wolfCrypt_Init();
+ if (ret != 0) {
+ printf("ERROR: wolfCrypt_Init failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Step 1: Initialize PUF context */
+ ret = wc_PufInit(&ctx);
+ if (ret != 0) {
+ printf("ERROR: wc_PufInit failed: %d\n", ret);
+ goto cleanup;
+ }
+ printf("PUF initialized.\n");
+
+#ifdef WOLFSSL_PUF_TEST
+ /* ================================================================== */
+ /* TEST MODE: Use synthetic SRAM data */
+ /* ================================================================== */
+ {
+ /* Synthetic SRAM pattern for testing (deterministic) */
+ uint8_t testSram[WC_PUF_RAW_BYTES];
+ uint8_t noisySram[WC_PUF_RAW_BYTES];
+ uint8_t identity2[WC_PUF_ID_SZ];
+ uint8_t key2[WC_PUF_KEY_SZ];
+ uint32_t i;
+
+ printf("Mode: TEST (synthetic SRAM data)\n\n");
+
+ /* Generate deterministic test pattern */
+ for (i = 0; i < WC_PUF_RAW_BYTES; i++)
+ testSram[i] = (uint8_t)((i * 37 + 13) ^ (i >> 2));
+
+ /* Step 2: Load test SRAM data */
+ ret = wc_PufSetTestData(&ctx, testSram, sizeof(testSram));
+ if (ret != 0) {
+ printf("ERROR: wc_PufSetTestData failed: %d\n", ret);
+ goto cleanup;
+ }
+
+ /* Step 3: Enroll - generates helper data from SRAM pattern */
+ ret = wc_PufEnroll(&ctx);
+ if (ret != 0) {
+ printf("ERROR: wc_PufEnroll failed: %d\n", ret);
+ goto cleanup;
+ }
+ printf("Enrollment complete.\n");
+
+ /* Save helper data (in production, store to flash/NVM) */
+ memcpy(helperData, ctx.helperData, WC_PUF_HELPER_BYTES);
+
+ /* Get device identity */
+ ret = wc_PufGetIdentity(&ctx, identity, sizeof(identity));
+ if (ret != 0) {
+ printf("ERROR: wc_PufGetIdentity failed: %d\n", ret);
+ goto cleanup;
+ }
+ print_hex("Identity (enrollment)", identity, WC_PUF_ID_SZ);
+
+ /* Derive a key */
+ ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key, sizeof(key));
+ if (ret != 0) {
+ printf("ERROR: wc_PufDeriveKey failed: %d\n", ret);
+ goto cleanup;
+ }
+ print_hex("Derived key (enrollment)", key, WC_PUF_KEY_SZ);
+
+ /* ---- Simulate power cycle with noisy SRAM ---- */
+ printf("\n--- Simulating power cycle (noisy SRAM) ---\n\n");
+
+ /* Create noisy copy: flip a few bits per 128-bit block */
+ memcpy(noisySram, testSram, WC_PUF_RAW_BYTES);
+ for (i = 0; i < 16; i++) {
+ /* Flip 2 bits in each 16-byte (128-bit) block */
+ noisySram[i * 16 + 3] ^= 0x04;
+ noisySram[i * 16 + 11] ^= 0x20;
+ }
+
+ /* Re-initialize and load noisy SRAM */
+ ret = wc_PufInit(&ctx);
+ if (ret != 0) {
+ printf("ERROR: wc_PufInit failed: %d\n", ret);
+ goto cleanup;
+ }
+
+ ret = wc_PufSetTestData(&ctx, noisySram, sizeof(noisySram));
+ if (ret != 0) {
+ printf("ERROR: wc_PufSetTestData failed: %d\n", ret);
+ goto cleanup;
+ }
+
+ /* Step 4: Reconstruct stable bits using helper data */
+ ret = wc_PufReconstruct(&ctx, helperData, WC_PUF_HELPER_BYTES);
+ if (ret != 0) {
+ printf("ERROR: wc_PufReconstruct failed: %d\n", ret);
+ goto cleanup;
+ }
+ printf("Reconstruction complete (BCH corrected noisy bits).\n");
+
+ /* Verify identity matches */
+ ret = wc_PufGetIdentity(&ctx, identity2, sizeof(identity2));
+ if (ret != 0) {
+ printf("ERROR: wc_PufGetIdentity failed: %d\n", ret);
+ goto cleanup;
+ }
+ print_hex("Identity (reconstructed)", identity2, WC_PUF_ID_SZ);
+
+ if (memcmp(identity, identity2, WC_PUF_ID_SZ) == 0) {
+ printf("PASS: Identity matches after reconstruction.\n");
+ }
+ else {
+ printf("FAIL: Identity mismatch!\n");
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* Verify derived key matches */
+ ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key2, sizeof(key2));
+ if (ret != 0) {
+ printf("ERROR: wc_PufDeriveKey failed: %d\n", ret);
+ goto cleanup;
+ }
+ print_hex("Derived key (reconstructed)", key2, WC_PUF_KEY_SZ);
+
+ if (memcmp(key, key2, WC_PUF_KEY_SZ) == 0) {
+ printf("PASS: Derived key matches after reconstruction.\n");
+ }
+ else {
+ printf("FAIL: Derived key mismatch!\n");
+ ret = -1;
+ goto cleanup;
+ }
+ }
+#else
+ /* ================================================================== */
+ /* REAL HARDWARE: Read actual SRAM PUF */
+ /* ================================================================== */
+ printf("Mode: HARDWARE (real SRAM PUF)\n\n");
+
+ /* Step 2: Read raw SRAM (must be done before any other SRAM access) */
+ ret = wc_PufReadSram(&ctx, (const uint8_t*)puf_sram_region,
+ sizeof(puf_sram_region));
+ if (ret != 0) {
+ printf("ERROR: wc_PufReadSram failed: %d\n", ret);
+ goto cleanup;
+ }
+ printf("SRAM read complete (%d bytes).\n", (int)sizeof(puf_sram_region));
+
+ /* Step 3: Enroll (first boot only - save helper data to flash/NVM) */
+ ret = wc_PufEnroll(&ctx);
+ if (ret != 0) {
+ printf("ERROR: wc_PufEnroll failed: %d\n", ret);
+ goto cleanup;
+ }
+ printf("Enrollment complete.\n");
+
+ /* Save helper data for future reconstructions */
+ memcpy(helperData, ctx.helperData, WC_PUF_HELPER_BYTES);
+ print_hex("Helper data (store to NVM)", helperData, WC_PUF_HELPER_BYTES);
+
+ /* Get device identity */
+ ret = wc_PufGetIdentity(&ctx, identity, sizeof(identity));
+ if (ret != 0) {
+ printf("ERROR: wc_PufGetIdentity failed: %d\n", ret);
+ goto cleanup;
+ }
+ print_hex("Device identity", identity, WC_PUF_ID_SZ);
+
+ /* Derive a key */
+ ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key, sizeof(key));
+ if (ret != 0) {
+ printf("ERROR: wc_PufDeriveKey failed: %d\n", ret);
+ goto cleanup;
+ }
+ print_hex("Derived key", key, WC_PUF_KEY_SZ);
+
+ /* Note: On subsequent boots, use wc_PufReconstruct() with the stored
+ * helper data instead of wc_PufEnroll(). The BCH error correction
+ * (t=10, corrects up to 10 bit flips per 127-bit codeword) will
+ * recover the same stable bits even with noisy SRAM. */
+#endif /* WOLFSSL_PUF_TEST */
+
+ printf("\n--- PUF example complete ---\n");
+
+cleanup:
+ /* Securely zeroize all PUF secrets */
+ wc_PufZeroize(&ctx);
+ wolfCrypt_Cleanup();
+
+ return ret;
+}
diff --git a/puf/startup.c b/puf/startup.c
new file mode 100644
index 00000000..27aa0521
--- /dev/null
+++ b/puf/startup.c
@@ -0,0 +1,136 @@
+/* startup.c
+ *
+ * Minimal Cortex-M startup for PUF example.
+ * Provides vector table, Reset_Handler, and SystemInit.
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#include
+#include
+
+/* Linker-provided symbols (STM32-standard names) */
+extern uint32_t _estack;
+extern uint32_t _sidata, _sdata, _edata;
+extern uint32_t _sbss, _ebss;
+extern void __libc_init_array(void);
+extern int main(void);
+
+/* Forward declarations */
+void Reset_Handler(void);
+void Default_Handler(void);
+void SystemInit(void);
+
+/* -------------------------------------------------------------------------- */
+/* SystemInit - minimal clock setup */
+/* -------------------------------------------------------------------------- */
+
+void SystemInit(void)
+{
+ /* Set VTOR to flash base */
+ *(volatile uint32_t *)0xE000ED08 = 0x08000000;
+ /* Default HSI clock (64 MHz) is sufficient for this example */
+}
+
+/* -------------------------------------------------------------------------- */
+/* Reset Handler */
+/* -------------------------------------------------------------------------- */
+
+void __attribute__((naked)) Reset_Handler(void)
+{
+ __asm volatile (
+ "ldr r0, =_estack \n"
+ "mov sp, r0 \n"
+ "bl SystemInit \n"
+
+ /* Copy .data from flash to SRAM */
+ "ldr r0, =_sdata \n"
+ "ldr r1, =_edata \n"
+ "ldr r2, =_sidata \n"
+ "movs r3, #0 \n"
+ "b .Ldata_check \n"
+ ".Ldata_copy: \n"
+ "ldr r4, [r2, r3] \n"
+ "str r4, [r0, r3] \n"
+ "adds r3, r3, #4 \n"
+ ".Ldata_check: \n"
+ "adds r4, r0, r3 \n"
+ "cmp r4, r1 \n"
+ "bcc .Ldata_copy \n"
+
+ /* Zero .bss */
+ "ldr r2, =_sbss \n"
+ "ldr r4, =_ebss \n"
+ "movs r3, #0 \n"
+ "b .Lbss_check \n"
+ ".Lbss_zero: \n"
+ "str r3, [r2] \n"
+ "adds r2, r2, #4 \n"
+ ".Lbss_check: \n"
+ "cmp r2, r4 \n"
+ "bcc .Lbss_zero \n"
+
+ /* Call static constructors, then main */
+ "bl __libc_init_array \n"
+ "bl main \n"
+ ".Lhang: \n"
+ "b .Lhang \n"
+ );
+}
+
+void Default_Handler(void)
+{
+ while (1) { }
+}
+
+/* -------------------------------------------------------------------------- */
+/* Vector table */
+/* -------------------------------------------------------------------------- */
+
+/* Weak aliases so the application can override individual handlers */
+void __attribute__((weak, alias("Default_Handler"))) NMI_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) HardFault_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) MemManage_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) BusFault_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) UsageFault_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) SecureFault_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) SVC_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) DebugMon_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) PendSV_Handler(void);
+void __attribute__((weak, alias("Default_Handler"))) SysTick_Handler(void);
+
+typedef void (*vector_fn)(void);
+
+const vector_fn __isr_vector[] __attribute__((section(".isr_vector"), used)) = {
+ (vector_fn)(uintptr_t)&_estack, /* Initial SP */
+ Reset_Handler, /* Reset */
+ NMI_Handler, /* NMI */
+ HardFault_Handler, /* Hard Fault */
+ MemManage_Handler, /* MPU Fault */
+ BusFault_Handler, /* Bus Fault */
+ UsageFault_Handler, /* Usage Fault */
+ SecureFault_Handler, /* Secure Fault (M33) */
+ 0, 0, 0, /* Reserved */
+ SVC_Handler, /* SVCall */
+ DebugMon_Handler, /* Debug Monitor */
+ 0, /* Reserved */
+ PendSV_Handler, /* PendSV */
+ SysTick_Handler, /* SysTick */
+ /* Peripheral IRQs default to Default_Handler via unused slots */
+};
diff --git a/puf/stm32.c b/puf/stm32.c
new file mode 100644
index 00000000..8ab17b39
--- /dev/null
+++ b/puf/stm32.c
@@ -0,0 +1,244 @@
+/* stm32.c
+ *
+ * STM32H5 HAL support for PUF example (tested on NUCLEO-H563ZI).
+ * Provides USART3 init/output, printf retarget, RNG stub, and time stub.
+ *
+ * To port to a different MCU, replace this file with your platform's
+ * UART and RNG implementation. The interface is:
+ * void hal_init(void) - called once at startup before printf
+ * int my_rng_seed_gen(uint8_t* output, uint32_t sz) - RNG seed
+ * unsigned long my_time(unsigned long* timer) - monotonic time
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#include
+
+/* -------------------------------------------------------------------------- */
+/* STM32H5 USART3 (ST-LINK VCP) bare-metal driver */
+/* -------------------------------------------------------------------------- */
+
+/* STM32H563 register bases.
+ * TZEN=0: use non-secure aliases (0x4xxx).
+ * TZEN=1: use secure aliases (0x5xxx). */
+#ifdef STM32H5_TZEN
+#define RCC_BASE 0x54020C00u
+#define GPIOD_BASE 0x52020C00u
+#define USART3_BASE 0x50004800u
+#else
+#define RCC_BASE 0x44020C00u
+#define GPIOD_BASE 0x42020C00u
+#define USART3_BASE 0x40004800u
+#endif
+
+#define RCC_AHB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x8Cu))
+#define RCC_APB1ENR (*(volatile uint32_t *)(RCC_BASE + 0x9Cu))
+
+/* GPIO registers */
+#define GPIO_MODER(b) (*(volatile uint32_t *)((b) + 0x00u))
+#define GPIO_OSPEEDR(b) (*(volatile uint32_t *)((b) + 0x08u))
+#define GPIO_AFRH(b) (*(volatile uint32_t *)((b) + 0x24u))
+
+/* USART3 registers */
+#define USART3_CR1 (*(volatile uint32_t *)(USART3_BASE + 0x00u))
+#define USART3_CR2 (*(volatile uint32_t *)(USART3_BASE + 0x04u))
+#define USART3_CR3 (*(volatile uint32_t *)(USART3_BASE + 0x08u))
+#define USART3_BRR (*(volatile uint32_t *)(USART3_BASE + 0x0Cu))
+#define USART3_ISR (*(volatile uint32_t *)(USART3_BASE + 0x1Cu))
+#define USART3_TDR (*(volatile uint32_t *)(USART3_BASE + 0x28u))
+#define USART3_PRESC (*(volatile uint32_t *)(USART3_BASE + 0x2Cu))
+
+static void delay(volatile uint32_t n)
+{
+ while (n--) { }
+}
+
+static void uart_init(void)
+{
+ uint32_t moder, afr;
+
+ /* Enable GPIOD clock */
+ RCC_AHB2ENR |= (1u << 3);
+ /* Enable USART3 clock (APB1LENR bit 18) */
+ RCC_APB1ENR |= (1u << 18);
+ delay(100);
+
+ /* Configure PD8 (TX) as AF7, push-pull, high speed */
+ moder = GPIO_MODER(GPIOD_BASE);
+ moder &= ~(3u << 16);
+ moder |= (2u << 16); /* Alternate function */
+ GPIO_MODER(GPIOD_BASE) = moder;
+ GPIO_OSPEEDR(GPIOD_BASE) |= (3u << 16); /* High speed for PD8 */
+ afr = GPIO_AFRH(GPIOD_BASE);
+ afr &= ~(0xFu << 0);
+ afr |= (7u << 0); /* AF7 = USART3 */
+ GPIO_AFRH(GPIOD_BASE) = afr;
+
+ /* Configure USART3: 115200 baud at default 32 MHz PCLK1 */
+ USART3_CR1 = 0;
+ USART3_CR2 = 0;
+ USART3_CR3 = 0;
+ USART3_PRESC = 0;
+ USART3_BRR = 32000000u / 115200u; /* ~278 */
+ USART3_CR1 = (1u << 3); /* TE */
+ delay(10);
+ USART3_CR1 |= (1u << 0); /* UE */
+ delay(100);
+}
+
+static void uart_putc(char c)
+{
+ while ((USART3_ISR & (1u << 7)) == 0) { }
+ USART3_TDR = (uint32_t)c;
+}
+
+/* Retarget _write for printf via USART3 */
+int _write(int fd, const char *buf, int len)
+{
+ int i;
+ (void)fd;
+ for (i = 0; i < len; i++) {
+ if (buf[i] == '\n')
+ uart_putc('\r');
+ uart_putc(buf[i]);
+ }
+ return len;
+}
+
+/* -------------------------------------------------------------------------- */
+/* STM32H5 Hardware RNG (TRNG) driver */
+/* -------------------------------------------------------------------------- */
+
+/* RCC clock control */
+#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x00u))
+#define RCC_CR_HSI48ON (1u << 12)
+#define RCC_CR_HSI48RDY (1u << 13)
+#define RCC_CCIPR5 (*(volatile uint32_t *)(RCC_BASE + 0xE8u))
+#define RCC_CCIPR5_RNGSEL_Msk (3u << 4)
+
+/* RNG peripheral */
+#ifdef STM32H5_TZEN
+#define RNG_BASE 0x520C0800u
+#else
+#define RNG_BASE 0x420C0800u
+#endif
+
+#define RNG_CR (*(volatile uint32_t *)(RNG_BASE + 0x00u))
+#define RNG_SR (*(volatile uint32_t *)(RNG_BASE + 0x04u))
+#define RNG_DR (*(volatile uint32_t *)(RNG_BASE + 0x08u))
+#define RNG_CR_RNGEN (1u << 2)
+#define RNG_CR_CONDRST (1u << 30)
+#define RNG_CR_CONFIG3_SHIFT 8u
+#define RNG_CR_CONFIG2_SHIFT 13u
+#define RNG_CR_CLKDIV_SHIFT 16u
+#define RNG_CR_CONFIG1_SHIFT 20u
+#define RNG_SR_DRDY (1u << 0)
+#define RNG_SR_CECS (1u << 1)
+#define RNG_SR_SECS (1u << 2)
+#define RNG_SR_CEIS (1u << 5)
+#define RNG_SR_SEIS (1u << 6)
+
+static void rng_init(void)
+{
+ uint32_t rng_cr;
+
+ /* Enable HSI48 as RNG kernel clock source */
+ RCC_CR |= RCC_CR_HSI48ON;
+ while ((RCC_CR & RCC_CR_HSI48RDY) == 0u) { }
+
+ /* Select HSI48 for RNG clock */
+ RCC_CCIPR5 &= ~RCC_CCIPR5_RNGSEL_Msk;
+ RCC_AHB2ENR |= (1u << 18); /* RNG clock enable */
+ delay(100);
+
+ /* Configure and enable RNG with conditioning reset */
+ rng_cr = RNG_CR;
+ rng_cr &= ~(0x1Fu << RNG_CR_CONFIG1_SHIFT);
+ rng_cr &= ~(0x7u << RNG_CR_CLKDIV_SHIFT);
+ rng_cr &= ~(0x3u << RNG_CR_CONFIG2_SHIFT);
+ rng_cr &= ~(0x7u << RNG_CR_CONFIG3_SHIFT);
+ rng_cr |= 0x0Fu << RNG_CR_CONFIG1_SHIFT;
+ rng_cr |= 0x0Du << RNG_CR_CONFIG3_SHIFT;
+
+ RNG_CR = RNG_CR_CONDRST | rng_cr;
+ while ((RNG_CR & RNG_CR_CONDRST) == 0u) { }
+ RNG_CR = rng_cr | RNG_CR_RNGEN;
+ while ((RNG_SR & RNG_SR_DRDY) == 0u) { }
+}
+
+static int rng_get_word(uint32_t *out)
+{
+ uint32_t timeout = 100000u;
+ while ((RNG_SR & RNG_SR_DRDY) == 0u) {
+ if ((RNG_SR & (RNG_SR_CECS | RNG_SR_SECS | RNG_SR_CEIS | RNG_SR_SEIS))
+ != 0u) {
+ rng_init();
+ timeout = 100000u;
+ continue;
+ }
+ if (--timeout == 0u)
+ return -1;
+ }
+ *out = RNG_DR;
+ return 0;
+}
+
+/* wolfCrypt custom RNG block generator using STM32H5 TRNG */
+int custom_rand_gen_block(unsigned char *output, unsigned int sz)
+{
+ uint32_t word;
+ while (sz >= 4u) {
+ if (rng_get_word(&word) != 0)
+ return -1;
+ output[0] = (unsigned char)word;
+ output[1] = (unsigned char)(word >> 8);
+ output[2] = (unsigned char)(word >> 16);
+ output[3] = (unsigned char)(word >> 24);
+ output += 4;
+ sz -= 4;
+ }
+ if (sz > 0u) {
+ if (rng_get_word(&word) != 0)
+ return -1;
+ while (sz-- > 0u) {
+ *output++ = (unsigned char)word;
+ word >>= 8;
+ }
+ }
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* hal_init - platform initialization entry point */
+/* -------------------------------------------------------------------------- */
+
+void hal_init(void)
+{
+ uart_init();
+ rng_init();
+}
+
+/* Custom time function */
+unsigned long my_time(unsigned long* timer)
+{
+ static unsigned long t = 1000;
+ if (timer)
+ *timer = t;
+ return t++;
+}
diff --git a/puf/user_settings.h b/puf/user_settings.h
new file mode 100644
index 00000000..94175bda
--- /dev/null
+++ b/puf/user_settings.h
@@ -0,0 +1,116 @@
+/* user_settings.h
+ *
+ * Minimal wolfCrypt configuration for bare-metal SRAM PUF example.
+ * Target: Cortex-M (tested on NUCLEO-H563ZI)
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#ifndef WOLFSSL_USER_SETTINGS_H
+#define WOLFSSL_USER_SETTINGS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ------------------------------------------------------------------------- */
+/* Platform */
+/* ------------------------------------------------------------------------- */
+#define WOLFCRYPT_ONLY
+#define SINGLE_THREADED
+#define WOLFSSL_SMALL_STACK
+#define WOLFSSL_GENERAL_ALIGNMENT 4
+#define WOLFSSL_USER_IO
+#ifndef USE_WOLF_ARM_STARTUP
+#define USE_WOLF_ARM_STARTUP
+#endif
+
+/* ------------------------------------------------------------------------- */
+/* PUF (Physically Unclonable Function) */
+/* ------------------------------------------------------------------------- */
+#define WOLFSSL_PUF
+#define WOLFSSL_PUF_SRAM
+
+/* Enable test mode: allows synthetic SRAM data for testing without hardware.
+ * Comment out to use real SRAM PUF on actual hardware. */
+#define WOLFSSL_PUF_TEST
+
+/* ------------------------------------------------------------------------- */
+/* Required Dependencies */
+/* ------------------------------------------------------------------------- */
+/* HKDF is required for PUF key derivation (wc_PufDeriveKey) */
+#define HAVE_HKDF
+
+/* SHA-256 is required for PUF identity and HKDF */
+/* (enabled by default, do not define NO_SHA256) */
+
+/* Uncomment to use SHA3-256 instead of SHA-256 for PUF identity and HKDF */
+/* #define WC_PUF_SHA3 */
+/* #define WOLFSSL_SHA3 */
+
+/* ------------------------------------------------------------------------- */
+/* Math Configuration */
+/* ------------------------------------------------------------------------- */
+#define WOLFSSL_SP_MATH_ALL
+#define WOLFSSL_SP_SMALL
+#define SP_WORD_SIZE 32
+
+/* ------------------------------------------------------------------------- */
+/* Disable Unused Features */
+/* ------------------------------------------------------------------------- */
+#define NO_RSA
+#define NO_DH
+#define NO_DSA
+#define NO_DES3
+#define NO_RC4
+#define NO_MD4
+#define NO_MD5
+#define NO_SHA
+#define NO_PSK
+#define NO_OLD_TLS
+#define NO_PWDBASED
+#define NO_CERTS
+#define NO_ASN
+#define NO_CODING
+#define NO_SIG_WRAPPER
+#define NO_AES
+
+/* ------------------------------------------------------------------------- */
+/* System */
+/* ------------------------------------------------------------------------- */
+#define NO_FILESYSTEM
+#define NO_WRITEV
+#define NO_MAIN_DRIVER
+#define NO_DEV_RANDOM
+
+#define BENCH_EMBEDDED
+#define WOLFSSL_IGNORE_FILE_WARN
+
+/* Provide custom time function */
+#define USER_TICKS
+
+/* Use STM32H5 hardware TRNG (implemented in stm32.c) */
+extern int custom_rand_gen_block(unsigned char *output, unsigned int sz);
+#define CUSTOM_RAND_GENERATE_BLOCK custom_rand_gen_block
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WOLFSSL_USER_SETTINGS_H */