Skip to content

Commit e0b3cce

Browse files
committed
Examples: Add example for restartable signing via signing hooks
Signed-off-by: Hanno Becker <beckphan@amazon.co.uk>
1 parent 078f551 commit e0b3cce

13 files changed

Lines changed: 1249 additions & 0 deletions

File tree

.github/workflows/base.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ jobs:
243243
- name: basic_lowram
244244
run: |
245245
CFLAGS="-O0" make run -C examples/basic_lowram
246+
- name: restartable_sign
247+
run: |
248+
CFLAGS="-O0" make run -C examples/restartable_sign
246249
- name: bring_your_own_fips202
247250
run: |
248251
CFLAGS="-O0" make run -C examples/bring_your_own_fips202

examples/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ See [basic_deterministic](basic_deterministic) for a basic example of how to bui
1616

1717
See [basic_lowram](basic_lowram) for a basic example of how to build a single instance of mldsa-native with reduced RAM usage (`MLD_CONFIG_REDUCE_RAM`). This is useful for embedded systems with tight RAM constraints.
1818

19+
## Restartable signing
20+
21+
See [restartable_sign](restartable_sign) for a minimal example of restartable signing using the signing hooks
22+
(`MLD_CONFIG_SIGN_HOOK_RESUME` / `_ATTEMPT` / `_FINISH`). It pauses the rejection-sampling loop and resumes it on a
23+
later call -- bounding per-call runtime while reproducing the same signature as an uninterrupted run -- keeping the
24+
resume state in a global variable.
25+
1926
## Multi-level build (C only)
2027

2128
See [multilevel_build](multilevel_build) for an example of how to build one instance of mldsa-native per security level,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
2+
3+
build

examples/restartable_sign/Makefile

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright (c) The mlkem-native project authors
2+
# Copyright (c) The mldsa-native project authors
3+
# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
4+
5+
.PHONY: build run clean
6+
.DEFAULT_GOAL := all
7+
8+
CC ?= gcc
9+
10+
# Adjust CFLAGS if needed
11+
CFLAGS := \
12+
-Wall \
13+
-Wextra \
14+
-Werror=unused-result \
15+
-Wpedantic \
16+
-Werror \
17+
-Wmissing-prototypes \
18+
-Wshadow \
19+
-Wpointer-arith \
20+
-Wredundant-decls \
21+
-Wconversion \
22+
-Wsign-conversion \
23+
-Wno-long-long \
24+
-Wno-unknown-pragmas \
25+
-Wno-unused-command-line-argument \
26+
-O3 \
27+
-fomit-frame-pointer \
28+
-std=c99 \
29+
-pedantic \
30+
-MMD \
31+
$(CFLAGS)
32+
33+
# If you want to use the native backends, the compiler needs to know about
34+
# the target architecture. Here, we import the default host detection from
35+
# mldsa-native's tests, but you can write your own or specialize accordingly.
36+
AUTO ?= 1
37+
include auto.mk
38+
39+
# The following only concerns the cross-compilation tests.
40+
# You can likely ignore the following for your application.
41+
#
42+
# Append cross-prefix for cross compilation
43+
# When called from the root Makefile, CROSS_PREFIX has already been added here
44+
ifeq (,$(findstring $(CROSS_PREFIX),$(CC)))
45+
CC := $(CROSS_PREFIX)$(CC)
46+
endif
47+
48+
# Part A:
49+
#
50+
# mldsa-native source and header files
51+
#
52+
# If you are not concerned about minimizing for a specific backend,
53+
# you can just include _all_ source files into your build.
54+
#
55+
# In this example, we compile the individual mldsa-native source files directly.
56+
# Alternatively, you can compile the 'monobuild' source file mldsa_native.c.
57+
# See examples/monolithic_build for that.
58+
MLD_SOURCE=$(wildcard \
59+
mldsa_native/src/*.c \
60+
mldsa_native/src/**/*.c \
61+
mldsa_native/src/**/**/*.c \
62+
mldsa_native/src/**/**/**/*.c)
63+
64+
INC=-Imldsa_native
65+
66+
# Part B:
67+
#
68+
# Your application source code
69+
APP_SOURCE=$(wildcard *.c)
70+
71+
ALL_SOURCE=$(MLD_SOURCE) $(APP_SOURCE)
72+
73+
BUILD_DIR=build
74+
BIN=test_binary
75+
76+
BINARY_NAME_FULL_44=$(BUILD_DIR)/$(BIN)44
77+
BINARY_NAME_FULL_65=$(BUILD_DIR)/$(BIN)65
78+
BINARY_NAME_FULL_87=$(BUILD_DIR)/$(BIN)87
79+
BINARIES_FULL=$(BINARY_NAME_FULL_44) $(BINARY_NAME_FULL_65) $(BINARY_NAME_FULL_87)
80+
81+
$(BINARY_NAME_FULL_44): CFLAGS += -DMLD_CONFIG_PARAMETER_SET=44
82+
$(BINARY_NAME_FULL_65): CFLAGS += -DMLD_CONFIG_PARAMETER_SET=65
83+
$(BINARY_NAME_FULL_87): CFLAGS += -DMLD_CONFIG_PARAMETER_SET=87
84+
85+
$(BINARIES_FULL): $(ALL_SOURCE)
86+
echo "$@"
87+
mkdir -p $(BUILD_DIR)
88+
$(CC) $(CFLAGS) $(INC) $^ -o $@
89+
90+
all: build
91+
92+
build: $(BINARIES_FULL)
93+
94+
run: $(BINARIES_FULL)
95+
$(EXEC_WRAPPER) ./$(BINARY_NAME_FULL_44)
96+
$(EXEC_WRAPPER) ./$(BINARY_NAME_FULL_65)
97+
$(EXEC_WRAPPER) ./$(BINARY_NAME_FULL_87)
98+
99+
clean:
100+
rm -rf $(BUILD_DIR)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[//]: # (SPDX-License-Identifier: CC-BY-4.0)
2+
3+
4+
This directory contains a minimal example of *restartable signing*: pausing the
5+
ML-DSA rejection-sampling loop and resuming it later, so that each signing call
6+
has a bounded runtime while the overall result is identical to an uninterrupted
7+
signature.
8+
9+
## Use Case
10+
11+
ML-DSA signing retries until a candidate signature passes rejection sampling,
12+
so a single call has an unbounded runtime. On a system with strict timing
13+
constraints you may want to cap the work done per call and continue later.
14+
The signing hooks let you do that while retaining functional equivalence to
15+
an uninterrupted ML-DSA signature operation.
16+
17+
## Configuration
18+
19+
The configuration file [mldsa_native_config.h](mldsa_native/mldsa_native_config.h) sets:
20+
- `MLD_CONFIG_SIGN_HOOK_RESUME` / `_ATTEMPT` / `_FINISH`: the three hooks around
21+
the rejection-sampling loop. The attempt hook may pause signing (which then
22+
returns `MLD_ERR_SIGNING_PAUSED`); the resume hook continues from the paused
23+
attempt on the next call.
24+
- `MLD_CONFIG_NO_RANDOMIZED_API`: resuming is only sound when the randomness is
25+
fixed across calls, so the caller drives the deterministic internal API.
26+
- `MLD_CONFIG_NAMESPACE_PREFIX`: Symbol prefix (set to `mldsa`)
27+
28+
## Resume state: global vs. context
29+
30+
This example keeps the resume state in a **global variable**, so the hooks take
31+
no arguments beyond the attempt counter. That is the simplest integration and
32+
suits a single-threaded caller signing one message at a time.
33+
34+
For concurrent or interleaved signing, keep the state **per caller** instead:
35+
enable `MLD_CONFIG_CONTEXT_PARAMETER`, give it your state type, and every public
36+
API function gains a trailing context argument that is forwarded to the hooks.
37+
See [test/src/test_sign_hook.c](../../test/src/test_sign_hook.c) for that
38+
variant.
39+
40+
## Notes
41+
42+
- This is incompatible with `MLD_CONFIG_KEYGEN_PCT` (pairwise consistency test):
43+
the PCT runs a single internal signature that cannot be resumed.
44+
45+
## Usage
46+
47+
```bash
48+
make build # Build the example
49+
make run # Run the example
50+
```

examples/restartable_sign/auto.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../test/mk/auto.mk
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../test/test_vectors/expected_test_vectors.h

examples/restartable_sign/main.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright (c) The mldsa-native project authors
3+
* SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
4+
*/
5+
6+
#include <stdint.h>
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <string.h>
10+
11+
#include <mldsa_native.h>
12+
#include "expected_test_vectors.h"
13+
14+
/* Convenience abbreviations for the secret-key and signature sizes, derived
15+
* from the configured parameter set. */
16+
#define MLDSA_SK_BYTES MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)
17+
#define MLDSA_SIG_BYTES MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET)
18+
19+
/* For testing, we use fixed test-vector randomness (test_vector_rnd) so that we
20+
* produce the same keys and signatures as the basic example -- and, crucially,
21+
* the same signature regardless of how the signing work is split across
22+
* restarts. A real integration must use cryptographic randomness for key
23+
* generation.
24+
*/
25+
26+
#define CHECK(x) \
27+
do \
28+
{ \
29+
int rc; \
30+
rc = (x); \
31+
if (!rc) \
32+
{ \
33+
fprintf(stderr, "ERROR (%s,%d)\n", __FILE__, __LINE__); \
34+
return 1; \
35+
} \
36+
} while (0)
37+
38+
/*
39+
* Signing hooks.
40+
*
41+
* This example keeps all restart state in a single global, so the hooks take
42+
* no context argument (the build leaves MLD_CONFIG_CONTEXT_PARAMETER unset).
43+
* This is the simplest possible integration and fine for a single-threaded
44+
* caller that signs one message at a time.
45+
*
46+
* For anything more -- concurrent or interleaved signing operations, or state
47+
* owned by a caller object -- enable MLD_CONFIG_CONTEXT_PARAMETER instead. Each
48+
* public API function then takes a trailing context argument that is forwarded
49+
* to the hooks, so the state below moves out of the global and into that
50+
* context. See test/src/test_sign_hook.c for the context-based variant.
51+
*/
52+
53+
/* The three hooks this integration must provide. mldsa-native calls them from
54+
* its signing loop; the matching declarations in the configuration file are
55+
* gated behind MLD_BUILD_INTERNAL, so we repeat them here to define the hooks
56+
* with only the public API in scope. */
57+
uint16_t mld_sign_hook_resume(void);
58+
int mld_sign_hook_attempt(uint16_t attempt);
59+
void mld_sign_hook_finish(uint16_t attempt);
60+
61+
/* How many rejection-sampling attempts each signing call runs before pausing;
62+
* set by the driver before each operation. 0 means never pause (one-shot). */
63+
static unsigned attempts_per_call = 0;
64+
65+
/* Attempt from which the next signing call resumes. Advanced by the attempt
66+
* hook when it pauses, reset to 0 by the finish hook on success. */
67+
static uint16_t resume_attempt = 0;
68+
69+
/* Resume from wherever the previous call paused (0 on a fresh operation). */
70+
uint16_t mld_sign_hook_resume(void) { return resume_attempt; }
71+
72+
/* Called before each attempt. Return non-zero to pause; signing then returns
73+
* MLD_ERR_SIGNING_PAUSED and the next call resumes from `attempt`. */
74+
int mld_sign_hook_attempt(uint16_t attempt)
75+
{
76+
if (attempts_per_call != 0 && attempt >= resume_attempt + attempts_per_call)
77+
{
78+
resume_attempt = attempt;
79+
return 1; /* pause before this attempt */
80+
}
81+
return 0; /* proceed */
82+
}
83+
84+
/* Called once signing succeeds; reset the resume point for the next operation.
85+
*/
86+
void mld_sign_hook_finish(uint16_t attempt)
87+
{
88+
(void)attempt;
89+
resume_attempt = 0;
90+
}
91+
92+
#if !defined(MLD_CONFIG_NO_SIGN_API)
93+
/*
94+
* Drive one signing operation to completion, pausing every `apc` attempts
95+
* (0 = one-shot). Repeatedly calls the deterministic signing API with the same
96+
* (message, sk, rnd); each MLD_ERR_SIGNING_PAUSED just means "call again to
97+
* continue". Returns the number of restarts (extra calls beyond the first), or
98+
* -1 on a genuine error.
99+
*/
100+
static int sign_restartable(uint8_t sig[MLDSA_SIG_BYTES], size_t *siglen,
101+
const uint8_t *pre, size_t prelen,
102+
const uint8_t *rnd, const uint8_t *sk, unsigned apc)
103+
{
104+
int restarts = 0;
105+
106+
/* Start a fresh operation and select the per-call attempt budget. */
107+
resume_attempt = 0;
108+
attempts_per_call = apc;
109+
110+
for (;;)
111+
{
112+
int rc = mldsa_signature_internal(
113+
sig, siglen, (const uint8_t *)TEST_VECTOR_MSG, TEST_VECTOR_MSG_LEN, pre,
114+
prelen, rnd, sk, 0 /* externalmu */);
115+
if (rc == 0)
116+
{
117+
return restarts; /* done */
118+
}
119+
if (rc == MLD_ERR_SIGNING_PAUSED)
120+
{
121+
restarts++;
122+
continue; /* resume from resume_attempt */
123+
}
124+
fprintf(stderr, "ERROR: signing failed with rc=%d\n", rc);
125+
return -1;
126+
}
127+
}
128+
#endif /* !MLD_CONFIG_NO_SIGN_API */
129+
130+
#if !defined(MLD_CONFIG_NO_SIGN_API)
131+
static int example_sign(const uint8_t sk[MLDSA_SK_BYTES])
132+
{
133+
uint8_t sig[MLDSA_SIG_BYTES];
134+
uint8_t ref_sig[MLDSA_SIG_BYTES];
135+
size_t siglen, ref_siglen;
136+
uint8_t pre[TEST_VECTOR_CTX_LEN + 2]; /* prefix string (0, ctxlen, ctx) */
137+
int restarts;
138+
139+
/* Prepare pre = (0, ctxlen, ctx) for pure ML-DSA domain separation. */
140+
pre[0] = 0;
141+
pre[1] = TEST_VECTOR_CTX_LEN;
142+
memcpy(pre + 2, TEST_VECTOR_CTX, TEST_VECTOR_CTX_LEN);
143+
144+
/* Single-step: pause after every attempt, so the operation is spread over as
145+
* many calls as it takes attempts. */
146+
printf("Signing message (single-step, pausing every attempt)... ");
147+
restarts =
148+
sign_restartable(sig, &siglen, pre, sizeof(pre), test_vector_rnd, sk, 1);
149+
CHECK(restarts >= 0);
150+
printf("DONE after %d restart(s)\n", restarts);
151+
152+
/* One-shot: never pause, i.e. ordinary uninterrupted signing. */
153+
printf("Signing message (one-shot, no pausing)... ");
154+
restarts = sign_restartable(ref_sig, &ref_siglen, pre, sizeof(pre),
155+
test_vector_rnd, sk, 0);
156+
CHECK(restarts == 0);
157+
printf("DONE\n");
158+
159+
/* The key property: splitting the work across restarts neither repeats nor
160+
* skips a rejection-sampling attempt, so both runs produce the very same
161+
* signature -- and it is the reference test vector. */
162+
CHECK(siglen == ref_siglen && memcmp(sig, ref_sig, siglen) == 0);
163+
CHECK(siglen == sizeof(test_vector_sig));
164+
CHECK(memcmp(sig, test_vector_sig, siglen) == 0);
165+
printf("Single-step and one-shot signatures match the reference vector.\n");
166+
return 0;
167+
}
168+
#else /* !MLD_CONFIG_NO_SIGN_API */
169+
static int example_sign(const uint8_t sk[MLDSA_SK_BYTES])
170+
{
171+
(void)sk;
172+
printf("Signing message... SKIPPED (sign API disabled)\n");
173+
return 0;
174+
}
175+
#endif /* MLD_CONFIG_NO_SIGN_API */
176+
177+
int main(void)
178+
{
179+
printf("ML-DSA-%d Restartable Signing Example\n", MLD_CONFIG_PARAMETER_SET);
180+
printf("====================================\n\n");
181+
182+
printf("Message: %s\n", TEST_VECTOR_MSG);
183+
printf("Context: %s\n\n", TEST_VECTOR_CTX);
184+
185+
/* Sign with the reference secret key; the example is solely about splitting
186+
* the signing loop across restarts and reproducing the one-shot signature. */
187+
if (example_sign(test_vector_sk) != 0)
188+
{
189+
return 1;
190+
}
191+
192+
printf("\nAll tests passed! Restartable signing works.\n");
193+
return 0;
194+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../mldsa/mldsa_native.h

0 commit comments

Comments
 (0)