Skip to content

Commit ba5df63

Browse files
committed
sign: Add configurable signing hooks (resume/attempt/finish)
Add three optional, independently configurable hooks into the ML-DSA signing rejection-sampling loop, declared in context.h and reached from sign.c via the names mld_sign_resume / mld_sign_attempt / mld_sign_finish. Each appends (or drops) the trailing context argument via MLD_CONTEXT_PARAMETERS_n, and is callable with or without MLD_CONFIG_CONTEXT_PARAMETER. - MLD_CONFIG_SIGN_HOOK_RESUME: mld_sign_hook_resume() returns the nonce to resume from, making signing resumable. Combined with a small MLD_CONFIG_MAX_SIGNING_ATTEMPTS bound, a timing-constrained caller can keep a predictable per-call runtime and, on MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED, re-invoke signing to continue -- producing the bit-for-bit FIPS 204 signature without a non-compliant attempt cap. Requires MLD_CONFIG_NO_RANDOMIZED_API, since resuming is only sound with fixed rnd. - MLD_CONFIG_SIGN_HOOK_ATTEMPT: mld_sign_hook_attempt(nonce) is called once per attempt. Observe-only (logging/benchmarking); randomized-API compatible. - MLD_CONFIG_SIGN_HOOK_FINISH: mld_sign_hook_finish() is called on success. Observe-only; randomized-API compatible. When an option is unset the corresponding hook is a no-op (resume -> 0), leaving the default build -- and, with all three unset, its proofs -- byte-for-byte unchanged. The hooks carry CBMC contracts; add trivial proofs for each and use them by contract in the sign_signature_internal and attempt_signature_generation proofs. Add examples/restartable_sign demonstrating resumption under per-call attempt budgets of 1/2/4. Signed-off-by: Hanno Becker <beckphan@amazon.co.uk>
1 parent cb51844 commit ba5df63

58 files changed

Lines changed: 5233 additions & 237 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/config-variations/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ runs:
3535
acvp: true
3636
opt: ${{ inputs.opt }}
3737
examples: true
38-
extra_args: "--exclude-example basic_deterministic"
38+
extra_args: "--exclude-example basic_deterministic --exclude-example restartable_sign"
3939
- name: "REDUCE_RAM"
4040
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'reduce-ram') }}
4141
uses: ./.github/actions/multi-functest
@@ -64,7 +64,7 @@ runs:
6464
alloc: true
6565
opt: ${{ inputs.opt }}
6666
examples: true
67-
extra_args: "--exclude-example basic_deterministic"
67+
extra_args: "--exclude-example basic_deterministic --exclude-example restartable_sign"
6868
- name: "PCT enabled + broken"
6969
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'pct-enabled-broken') }}
7070
shell: bash

.github/workflows/base.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ jobs:
233233
- name: basic_lowram
234234
run: |
235235
CFLAGS="-O0" make run -C examples/basic_lowram
236+
- name: restartable_sign
237+
run: |
238+
CFLAGS="-O0" make run -C examples/restartable_sign
236239
- name: bring_your_own_fips202
237240
run: |
238241
CFLAGS="-O0" make run -C examples/bring_your_own_fips202

BIBLIOGRAPHY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ source code and documentation.
8282
- [examples/monolithic_build_native/mldsa_native/mldsa_native_config.h](examples/monolithic_build_native/mldsa_native/mldsa_native_config.h)
8383
- [examples/multilevel_build/mldsa_native/mldsa_native_config.h](examples/multilevel_build/mldsa_native/mldsa_native_config.h)
8484
- [examples/multilevel_build_native/mldsa_native/mldsa_native_config.h](examples/multilevel_build_native/mldsa_native/mldsa_native_config.h)
85+
- [examples/restartable_sign/mldsa_native/mldsa_native_config.h](examples/restartable_sign/mldsa_native/mldsa_native_config.h)
8586
- [integration/liboqs/config_aarch64.h](integration/liboqs/config_aarch64.h)
8687
- [integration/liboqs/config_c.h](integration/liboqs/config_c.h)
8788
- [integration/liboqs/config_x86_64.h](integration/liboqs/config_x86_64.h)
@@ -133,6 +134,7 @@ source code and documentation.
133134
- [examples/monolithic_build_native/mldsa_native/mldsa_native_config.h](examples/monolithic_build_native/mldsa_native/mldsa_native_config.h)
134135
- [examples/multilevel_build/mldsa_native/mldsa_native_config.h](examples/multilevel_build/mldsa_native/mldsa_native_config.h)
135136
- [examples/multilevel_build_native/mldsa_native/mldsa_native_config.h](examples/multilevel_build_native/mldsa_native/mldsa_native_config.h)
137+
- [examples/restartable_sign/mldsa_native/mldsa_native_config.h](examples/restartable_sign/mldsa_native/mldsa_native_config.h)
136138
- [mldsa/mldsa_native.h](mldsa/mldsa_native.h)
137139
- [mldsa/mldsa_native_config.h](mldsa/mldsa_native_config.h)
138140
- [mldsa/src/ct.h](mldsa/src/ct.h)

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ EXAMPLE_DIRS := \
297297
examples/basic \
298298
examples/basic_deterministic \
299299
examples/basic_lowram \
300+
examples/restartable_sign \
300301
examples/monolithic_build \
301302
examples/monolithic_build_native \
302303
examples/monolithic_build_multilevel \

examples/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ 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 an example of how to bound the per-call signing time while staying FIPS 204 compliant, by making signing restartable: each call performs at most `MLD_CONFIG_MAX_SIGNING_ATTEMPTS` rejection-sampling iterations and, on exhaustion, the caller resumes the operation (via the `MLD_CONFIG_SIGN_HOOK_*` hooks). This is useful in timing-constrained environments.
22+
1923
## Multi-level build (C only)
2024

2125
See [multilevel_build](multilevel_build) for an example of how to build one instance of mldsa-native per security level,

examples/basic_deterministic/mldsa_native/mldsa_native_config.h

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -714,24 +714,142 @@
714714
/**
715715
* MLD_CONFIG_CONTEXT_PARAMETER
716716
*
717-
* Set this to add a context parameter that is provided to public
718-
* API functions and is then available in custom callbacks.
719-
*
720-
* The type of the context parameter is configured via
721-
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE.
717+
* Set this to add a caller-supplied context parameter to the public API
718+
* functions, which is then forwarded unchanged to the custom callbacks
719+
* (allocation, and the restartable-signing hooks below).
720+
*
721+
* When this option is set, every public API function gains a trailing
722+
* parameter
723+
*
724+
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE context
725+
*
726+
* as its last argument; its type is configured via
727+
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE (see below). mldsa-native treats this
728+
* value as opaque: it never dereferences it and only passes it on to the
729+
* configurable hook macros. It is meant to carry per-caller state -- e.g. a
730+
* pointer to a memory pool for the allocation hooks, or the resume state for
731+
* the restartable-signing hooks -- into those hooks.
732+
*
733+
* When this option is unset (the default), no extra parameter is added and
734+
* the hook macros never receive a context argument.
735+
*
736+
* Memory-allocation hooks (MLD_ALLOC / MLD_FREE):
737+
*
738+
* Internally, mldsa-native allocates and releases its large temporary
739+
* buffers via two macros, MLD_ALLOC(v, T, N, context) and
740+
* MLD_FREE(v, T, N, context), where `v` is the pointer variable to
741+
* declare/clear, `T` its element type, and `N` the element count. By
742+
* default these allocate `N` elements of type `T` on the stack and, on
743+
* free, zeroize the buffer. If MLD_CONFIG_CUSTOM_ALLOC_FREE is set, they
744+
* instead expand to the user-provided MLD_CUSTOM_ALLOC / MLD_CUSTOM_FREE,
745+
* forwarding `context` as the extra argument when (and only when)
746+
* MLD_CONFIG_CONTEXT_PARAMETER is set. This is the mechanism by which a
747+
* caller's allocator gains access to per-call state. See
748+
* MLD_CONFIG_CUSTOM_ALLOC_FREE for the macro signatures.
749+
*
750+
* Signing hooks (mld_sign_hook_resume / _attempt / _finish):
751+
*
752+
* The MLD_CONFIG_SIGN_HOOK_RESUME / _ATTEMPT / _FINISH options let signing
753+
* call into caller-provided functions around its rejection-sampling loop.
754+
* Each takes `context` only if MLD_CONFIG_CONTEXT_PARAMETER is also set (the
755+
* context is then its last argument); otherwise it takes no context. See
756+
* those options below for the full contract.
722757
*/
723758
/* #define MLD_CONFIG_CONTEXT_PARAMETER */
724759

725760
/**
726761
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE
727762
*
728-
* Set this to define the type for the context parameter used by
729-
* MLD_CONFIG_CONTEXT_PARAMETER.
763+
* Set this to define the type of the context parameter added by
764+
* MLD_CONFIG_CONTEXT_PARAMETER. It can be any C type usable as a function
765+
* parameter, e.g. `void *` or a pointer to a caller-defined struct such as
766+
* `struct my_ctx *`.
730767
*
731-
* This is only relevant if MLD_CONFIG_CONTEXT_PARAMETER is set.
768+
* This option must be defined if and only if MLD_CONFIG_CONTEXT_PARAMETER is
769+
* defined; defining one without the other is a compile-time error.
732770
*/
733771
/* #define MLD_CONFIG_CONTEXT_PARAMETER_TYPE void* */
734772

773+
/**
774+
* Signing hooks: MLD_CONFIG_SIGN_HOOK_RESUME / _ATTEMPT / _FINISH
775+
*
776+
* Three optional, independently configurable hooks into the ML-DSA signing
777+
* rejection-sampling loop. Each is enabled by defining the matching option, in
778+
* which case the integration MUST provide the corresponding function. The
779+
* functions are level-generic (one set serves all parameter sets).
780+
*
781+
* If a hook needs per-operation state, enable MLD_CONFIG_CONTEXT_PARAMETER; the
782+
* context is then appended as the LAST argument to each function. Otherwise the
783+
* functions take no context argument. Either way they must be callable for
784+
* every parameter set in the build.
785+
*
786+
* MLD_CONFIG_SIGN_HOOK_RESUME
787+
* uint16_t mld_sign_hook_resume([context])
788+
*
789+
* Returns the nonce (rejection-sampling counter) to resume the signing
790+
* operation from; must be 0 at the start of a fresh operation. This makes
791+
* signing *resumable*: combined with a small
792+
* MLD_CONFIG_MAX_SIGNING_ATTEMPTS bound, each call performs at most that many
793+
* further attempts and, on MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED, the caller
794+
* re-invokes signing -- with the *same* message, sk and rnd -- to continue from
795+
* the recorded nonce. No attempt is repeated or skipped, so the result is
796+
* bit-for-bit the FIPS 204 signature, while each call has a bounded,
797+
* predictable runtime. This lets a timing-constrained caller keep a small
798+
* per-call bound *and* stay FIPS 204 compliant (a bound < 814 is otherwise
799+
* non-compliant, as signing could give up on a message it should have signed).
800+
*
801+
* Because resuming only yields the correct signature if the randomness is
802+
* fixed across calls, this option requires MLD_CONFIG_NO_RANDOMIZED_API (a
803+
* compile-time error is raised otherwise): the caller drives signing via
804+
* the deterministic internal API and supplies `rnd` itself. Resuming is only
805+
* sound for the deterministic variants mld_sign_signature_internal and
806+
* mld_sign_signature_extmu.
807+
*
808+
* MLD_CONFIG_SIGN_HOOK_ATTEMPT
809+
* void mld_sign_hook_attempt(uint16_t nonce[, context])
810+
*
811+
* Called once per signing attempt, with the current nonce, before the
812+
* attempt runs. Observe-only -- useful for logging or benchmarking
813+
* individual attempts. A resumable integration may also use it to record
814+
* `nonce + 1` as the next resume point. It does not affect the result and
815+
* is compatible with the randomized API.
816+
*
817+
* MLD_CONFIG_SIGN_HOOK_FINISH
818+
* void mld_sign_hook_finish([context])
819+
*
820+
* Called when a signing operation completes successfully. Observe-only (a
821+
* resumable integration typically resets its stored nonce to 0 here);
822+
* compatible with the randomized API.
823+
*
824+
* When an option is unset, the corresponding hook defaults to a no-op (and
825+
* resume to a constant 0), i.e. ordinary one-shot signing.
826+
*
827+
* @warning These hooks are experimental. Their configuration and signatures may
828+
* change at any time.
829+
*
830+
* See examples/restartable_sign for a complete worked example using all three.
831+
*/
832+
/* #define MLD_CONFIG_SIGN_HOOK_RESUME
833+
#define MLD_CONFIG_SIGN_HOOK_ATTEMPT
834+
#define MLD_CONFIG_SIGN_HOOK_FINISH
835+
#if !defined(__ASSEMBLER__)
836+
#include <stdint.h>
837+
#include "src/sys.h"
838+
static MLD_INLINE uint16_t mld_sign_hook_resume(void)
839+
{
840+
... return the nonce to resume from ...
841+
}
842+
static MLD_INLINE void mld_sign_hook_attempt(uint16_t nonce)
843+
{
844+
... log the attempt; for resume, store nonce + 1 ...
845+
}
846+
static MLD_INLINE void mld_sign_hook_finish(void)
847+
{
848+
... mark the operation complete ...
849+
}
850+
#endif
851+
*/
852+
735853
/**
736854
* MLD_CONFIG_REDUCE_RAM
737855
*

examples/basic_lowram/mldsa_native/mldsa_native_config.h

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -713,24 +713,142 @@
713713
/**
714714
* MLD_CONFIG_CONTEXT_PARAMETER
715715
*
716-
* Set this to add a context parameter that is provided to public
717-
* API functions and is then available in custom callbacks.
718-
*
719-
* The type of the context parameter is configured via
720-
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE.
716+
* Set this to add a caller-supplied context parameter to the public API
717+
* functions, which is then forwarded unchanged to the custom callbacks
718+
* (allocation, and the restartable-signing hooks below).
719+
*
720+
* When this option is set, every public API function gains a trailing
721+
* parameter
722+
*
723+
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE context
724+
*
725+
* as its last argument; its type is configured via
726+
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE (see below). mldsa-native treats this
727+
* value as opaque: it never dereferences it and only passes it on to the
728+
* configurable hook macros. It is meant to carry per-caller state -- e.g. a
729+
* pointer to a memory pool for the allocation hooks, or the resume state for
730+
* the restartable-signing hooks -- into those hooks.
731+
*
732+
* When this option is unset (the default), no extra parameter is added and
733+
* the hook macros never receive a context argument.
734+
*
735+
* Memory-allocation hooks (MLD_ALLOC / MLD_FREE):
736+
*
737+
* Internally, mldsa-native allocates and releases its large temporary
738+
* buffers via two macros, MLD_ALLOC(v, T, N, context) and
739+
* MLD_FREE(v, T, N, context), where `v` is the pointer variable to
740+
* declare/clear, `T` its element type, and `N` the element count. By
741+
* default these allocate `N` elements of type `T` on the stack and, on
742+
* free, zeroize the buffer. If MLD_CONFIG_CUSTOM_ALLOC_FREE is set, they
743+
* instead expand to the user-provided MLD_CUSTOM_ALLOC / MLD_CUSTOM_FREE,
744+
* forwarding `context` as the extra argument when (and only when)
745+
* MLD_CONFIG_CONTEXT_PARAMETER is set. This is the mechanism by which a
746+
* caller's allocator gains access to per-call state. See
747+
* MLD_CONFIG_CUSTOM_ALLOC_FREE for the macro signatures.
748+
*
749+
* Signing hooks (mld_sign_hook_resume / _attempt / _finish):
750+
*
751+
* The MLD_CONFIG_SIGN_HOOK_RESUME / _ATTEMPT / _FINISH options let signing
752+
* call into caller-provided functions around its rejection-sampling loop.
753+
* Each takes `context` only if MLD_CONFIG_CONTEXT_PARAMETER is also set (the
754+
* context is then its last argument); otherwise it takes no context. See
755+
* those options below for the full contract.
721756
*/
722757
/* #define MLD_CONFIG_CONTEXT_PARAMETER */
723758

724759
/**
725760
* MLD_CONFIG_CONTEXT_PARAMETER_TYPE
726761
*
727-
* Set this to define the type for the context parameter used by
728-
* MLD_CONFIG_CONTEXT_PARAMETER.
762+
* Set this to define the type of the context parameter added by
763+
* MLD_CONFIG_CONTEXT_PARAMETER. It can be any C type usable as a function
764+
* parameter, e.g. `void *` or a pointer to a caller-defined struct such as
765+
* `struct my_ctx *`.
729766
*
730-
* This is only relevant if MLD_CONFIG_CONTEXT_PARAMETER is set.
767+
* This option must be defined if and only if MLD_CONFIG_CONTEXT_PARAMETER is
768+
* defined; defining one without the other is a compile-time error.
731769
*/
732770
/* #define MLD_CONFIG_CONTEXT_PARAMETER_TYPE void* */
733771

772+
/**
773+
* Signing hooks: MLD_CONFIG_SIGN_HOOK_RESUME / _ATTEMPT / _FINISH
774+
*
775+
* Three optional, independently configurable hooks into the ML-DSA signing
776+
* rejection-sampling loop. Each is enabled by defining the matching option, in
777+
* which case the integration MUST provide the corresponding function. The
778+
* functions are level-generic (one set serves all parameter sets).
779+
*
780+
* If a hook needs per-operation state, enable MLD_CONFIG_CONTEXT_PARAMETER; the
781+
* context is then appended as the LAST argument to each function. Otherwise the
782+
* functions take no context argument. Either way they must be callable for
783+
* every parameter set in the build.
784+
*
785+
* MLD_CONFIG_SIGN_HOOK_RESUME
786+
* uint16_t mld_sign_hook_resume([context])
787+
*
788+
* Returns the nonce (rejection-sampling counter) to resume the signing
789+
* operation from; must be 0 at the start of a fresh operation. This makes
790+
* signing *resumable*: combined with a small
791+
* MLD_CONFIG_MAX_SIGNING_ATTEMPTS bound, each call performs at most that many
792+
* further attempts and, on MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED, the caller
793+
* re-invokes signing -- with the *same* message, sk and rnd -- to continue from
794+
* the recorded nonce. No attempt is repeated or skipped, so the result is
795+
* bit-for-bit the FIPS 204 signature, while each call has a bounded,
796+
* predictable runtime. This lets a timing-constrained caller keep a small
797+
* per-call bound *and* stay FIPS 204 compliant (a bound < 814 is otherwise
798+
* non-compliant, as signing could give up on a message it should have signed).
799+
*
800+
* Because resuming only yields the correct signature if the randomness is
801+
* fixed across calls, this option requires MLD_CONFIG_NO_RANDOMIZED_API (a
802+
* compile-time error is raised otherwise): the caller drives signing via
803+
* the deterministic internal API and supplies `rnd` itself. Resuming is only
804+
* sound for the deterministic variants mld_sign_signature_internal and
805+
* mld_sign_signature_extmu.
806+
*
807+
* MLD_CONFIG_SIGN_HOOK_ATTEMPT
808+
* void mld_sign_hook_attempt(uint16_t nonce[, context])
809+
*
810+
* Called once per signing attempt, with the current nonce, before the
811+
* attempt runs. Observe-only -- useful for logging or benchmarking
812+
* individual attempts. A resumable integration may also use it to record
813+
* `nonce + 1` as the next resume point. It does not affect the result and
814+
* is compatible with the randomized API.
815+
*
816+
* MLD_CONFIG_SIGN_HOOK_FINISH
817+
* void mld_sign_hook_finish([context])
818+
*
819+
* Called when a signing operation completes successfully. Observe-only (a
820+
* resumable integration typically resets its stored nonce to 0 here);
821+
* compatible with the randomized API.
822+
*
823+
* When an option is unset, the corresponding hook defaults to a no-op (and
824+
* resume to a constant 0), i.e. ordinary one-shot signing.
825+
*
826+
* @warning These hooks are experimental. Their configuration and signatures may
827+
* change at any time.
828+
*
829+
* See examples/restartable_sign for a complete worked example using all three.
830+
*/
831+
/* #define MLD_CONFIG_SIGN_HOOK_RESUME
832+
#define MLD_CONFIG_SIGN_HOOK_ATTEMPT
833+
#define MLD_CONFIG_SIGN_HOOK_FINISH
834+
#if !defined(__ASSEMBLER__)
835+
#include <stdint.h>
836+
#include "src/sys.h"
837+
static MLD_INLINE uint16_t mld_sign_hook_resume(void)
838+
{
839+
... return the nonce to resume from ...
840+
}
841+
static MLD_INLINE void mld_sign_hook_attempt(uint16_t nonce)
842+
{
843+
... log the attempt; for resume, store nonce + 1 ...
844+
}
845+
static MLD_INLINE void mld_sign_hook_finish(void)
846+
{
847+
... mark the operation complete ...
848+
}
849+
#endif
850+
*/
851+
734852
/**
735853
* MLD_CONFIG_REDUCE_RAM
736854
*

0 commit comments

Comments
 (0)