-
Notifications
You must be signed in to change notification settings - Fork 33
xmss: scaffolding for the signature spec #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8b57bb8
6a063fe
b4152ce
d47eebd
9a466a2
6460f6e
9295e3d
dd5fcba
be0d0ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| """ | ||
| This package provides a Python specification for the Generalized XMSS | ||
| hash-based signature scheme. | ||
|
|
||
| It exposes the core data structures and the main interface functions. | ||
| """ | ||
|
|
||
| from .constants import LIFETIME, MESSAGE_LENGTH | ||
| from .interface import key_gen, sign, verify | ||
| from .structures import HashTreeOpening, PublicKey, SecretKey, Signature | ||
|
|
||
| __all__ = [ | ||
| "key_gen", | ||
| "sign", | ||
| "verify", | ||
| "PublicKey", | ||
| "Signature", | ||
| "SecretKey", | ||
| "HashTreeOpening", | ||
| "LIFETIME", | ||
| "MESSAGE_LENGTH", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| """ | ||
| Defines the cryptographic constants for the XMSS specification. | ||
|
|
||
| This specification corresponds to the "hashing-optimized" Top Level Target Sum | ||
| instantiation from the canonical Rust implementation. | ||
|
|
||
| .. note:: | ||
| This specification uses the **KoalaBear** prime field, which is consistent | ||
| with the formal analysis in the reference papers (e.g., Section 5 of the | ||
| "LeanSig" technical note: https://eprint.iacr.org/2025/1332). | ||
|
|
||
| The canonical Rust implementation currently uses the `BabyBear` field for | ||
| practical reasons but is expected to align with this | ||
| specification in the future. | ||
| """ | ||
|
|
||
| from ..koalabear import Fp | ||
|
|
||
| # ================================================================= | ||
| # Core Scheme Configuration | ||
| # ================================================================= | ||
|
|
||
| MESSAGE_LENGTH: int = 32 | ||
| """The length in bytes for all messages to be signed.""" | ||
|
|
||
| LOG_LIFETIME: int = 32 | ||
| """The base-2 logarithm of the scheme's maximum lifetime.""" | ||
|
|
||
| LIFETIME: int = 1 << LOG_LIFETIME | ||
| """ | ||
| The maximum number of epochs supported by this configuration. | ||
|
|
||
| An individual key pair can be active for a smaller sub-range. | ||
| """ | ||
|
|
||
|
|
||
| # ================================================================= | ||
| # Target Sum WOTS Parameters | ||
| # ================================================================= | ||
|
|
||
| DIMENSION: int = 64 | ||
| """The total number of hash chains, `v`.""" | ||
|
|
||
| BASE: int = 8 | ||
| """The alphabet size for the digits of the encoded message.""" | ||
|
|
||
| FINAL_LAYER: int = 77 | ||
| """The number of top layers of the hypercube to map the hash output into.""" | ||
|
|
||
| TARGET_SUM: int = 375 | ||
| """The required sum of all codeword chunks for a signature to be valid.""" | ||
|
|
||
|
|
||
| # ================================================================= | ||
| # Hash and Encoding Length Parameters (in field elements) | ||
| # ================================================================= | ||
|
|
||
| PARAMETER_LEN: int = 5 | ||
| """ | ||
| The length of the public parameter `P`. | ||
|
|
||
| It is used to specialize the hash function. | ||
| """ | ||
|
|
||
| TWEAK_LEN_FE: int = 2 | ||
| """The length of a domain-separating tweak.""" | ||
|
|
||
| MSG_LEN_FE: int = 9 | ||
| """The length of a message after being encoded into field elements.""" | ||
|
|
||
| RAND_LEN_FE: int = 7 | ||
| """The length of the randomness `rho` used during message encoding.""" | ||
|
|
||
| HASH_LEN_FE: int = 8 | ||
| """The output length of the main tweakable hash function.""" | ||
|
|
||
| CAPACITY: int = 9 | ||
| """The capacity of the Poseidon2 sponge, defining its security level.""" | ||
|
|
||
| POS_OUTPUT_LEN_PER_INV_FE: int = 15 | ||
| """Output length per invocation for the message hash.""" | ||
|
|
||
| POS_INVOCATIONS: int = 1 | ||
| """Number of invocations for the message hash.""" | ||
|
|
||
| POS_OUTPUT_LEN_FE: int = POS_OUTPUT_LEN_PER_INV_FE * POS_INVOCATIONS | ||
| """Total output length for the message hash.""" | ||
|
|
||
|
|
||
| # ================================================================= | ||
| # Domain Separator Prefixes for Tweaks | ||
| # ================================================================= | ||
|
|
||
| TWEAK_PREFIX_CHAIN = Fp(value=0x00) | ||
| """The unique prefix for tweaks used in Winternitz-style hash chains.""" | ||
|
|
||
| TWEAK_PREFIX_TREE = Fp(value=0x01) | ||
| """The unique prefix for tweaks used when hashing Merkle tree nodes.""" | ||
|
|
||
| TWEAK_PREFIX_MESSAGE = Fp(value=0x02) | ||
| """The unique prefix for tweaks used in the initial message hashing step.""" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| """ | ||
| Defines the core interface for the Generalized XMSS signature scheme. | ||
|
|
||
| Specification for the high-level functions (`key_gen`, `sign`, `verify`) | ||
| that constitute the public API of the signature scheme. For the purpose of this | ||
| specification, these are defined as placeholders with detailed documentation. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Tuple | ||
|
|
||
| from .structures import PublicKey, SecretKey, Signature | ||
|
|
||
|
|
||
| def key_gen( | ||
| activation_epoch: int, num_active_epochs: int | ||
| ) -> Tuple[PublicKey, SecretKey]: | ||
| """ | ||
| Generates a new cryptographic key pair. This is a **randomized** algorithm. | ||
|
|
||
| This function is a placeholder. In a real implementation, it would involve | ||
| generating a master secret, deriving all one-time keys, and constructing | ||
| the full Merkle tree. | ||
|
|
||
| Args: | ||
| activation_epoch: The starting epoch for which this key is active. | ||
| num_active_epochs: The number of consecutive epochs | ||
| the key is active for. | ||
|
|
||
| For the formal specification of this process, please refer to: | ||
| - "Hash-Based Multi-Signatures for Post-Quantum Ethereum": https://eprint.iacr.org/2025/055 | ||
| - "Technical Note: LeanSig for Post-Quantum Ethereum": https://eprint.iacr.org/2025/1332 | ||
| - The canonical Rust implementation: https://github.com/b-wagn/hash-sig | ||
| """ | ||
| raise NotImplementedError( | ||
| "key_gen is not part of this specification. " | ||
| "See the Rust reference implementation." | ||
| ) | ||
|
|
||
|
|
||
| def sign(sk: SecretKey, epoch: int, message: bytes) -> Signature: | ||
tcoratger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """ | ||
| Produces a digital signature for a given message at a specific epoch. This | ||
| is a **randomized** algorithm. | ||
|
|
||
| This function is a placeholder. The signing process involves encoding the | ||
| message, generating a one-time signature, and providing a Merkle path. | ||
|
|
||
| **CRITICAL**: This function must never be called twice with the same secret | ||
| key and epoch for different messages, as this would compromise security. | ||
|
|
||
| For the formal specification of this process, please refer to: | ||
| - "Hash-Based Multi-Signatures for Post-Quantum Ethereum": https://eprint.iacr.org/2025/055 | ||
| - "Technical Note: LeanSig for Post-Quantum Ethereum": https://eprint.iacr.org/2025/1332 | ||
| - The canonical Rust implementation: https://github.com/b-wagn/hash-sig | ||
| """ | ||
| raise NotImplementedError( | ||
| "sign is not part of this specification. " | ||
| "See the Rust reference implementation." | ||
| ) | ||
|
|
||
|
|
||
| def verify(pk: PublicKey, epoch: int, message: bytes, sig: Signature) -> bool: | ||
| r""" | ||
| Verifies a digital signature against a public key, message, and epoch. This | ||
| is a **deterministic** algorithm. | ||
|
|
||
| This function is a placeholder. The complete verification logic is detailed | ||
| below and will be implemented in a future update. | ||
|
|
||
| ### Verification Algorithm | ||
|
|
||
| 1. **Re-encode Message**: The verifier uses the randomness `rho` from the | ||
| signature to re-compute the codeword $x = (x_1, \dots, x_v)$ from the | ||
| message `m`. | ||
| This includes calculating the checksum or checking the target sum. | ||
|
|
||
| 2. **Reconstruct One-Time Public Key**: For each intermediate hash $y_i$ | ||
| in the signature, the verifier completes the corresponding hash chain. | ||
| Since $y_i$ was computed with $x_i$ steps, the verifier applies the | ||
| hash function an additional $w - 1 - x_i$ times to arrive at the | ||
| one-time public key component $pk_{ep,i}$. | ||
|
|
||
| 3. **Compute Merkle Leaf**: The verifier hashes the reconstructed one-time | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: in a future impl I would maybe use the same sub-algorithms as in either the rust code or in the paper, and then maybe even point to the pages of the paper.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok that's fine, maybe once done, we can just modify the doc here to point to this. |
||
| public key components to compute the expected Merkle leaf for `epoch`. | ||
|
|
||
| 4. **Verify Merkle Path**: The verifier uses the `path` from the signature | ||
| to compute a candidate Merkle root starting from the computed leaf. | ||
| Verification succeeds if and only if this candidate root matches the | ||
| `root` in the `PublicKey`. | ||
|
|
||
| Args: | ||
| pk: The public key to verify against. | ||
| epoch: The epoch the signature corresponds to. | ||
| message: The message that was supposedly signed. | ||
| sig: The signature object to be verified. | ||
|
|
||
| Returns: | ||
| `True` if the signature is valid, `False` otherwise. | ||
|
|
||
| For the formal specification of this process, please refer to: | ||
| - "Hash-Based Multi-Signatures for Post-Quantum Ethereum": https://eprint.iacr.org/2025/055 | ||
| - "Technical Note: LeanSig for Post-Quantum Ethereum": https://eprint.iacr.org/2025/1332 | ||
| - The canonical Rust implementation: https://github.com/b-wagn/hash-sig | ||
| """ | ||
| raise NotImplementedError( | ||
| "verify will be implemented in a future update to the specification." | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| """Defines the data structures for the Generalized XMSS signature scheme.""" | ||
|
|
||
| from typing import Annotated, List | ||
|
|
||
| from pydantic import BaseModel, ConfigDict, Field | ||
|
|
||
| from ..koalabear import Fp | ||
| from .constants import HASH_LEN_FE, PARAMETER_LEN, RAND_LEN_FE | ||
|
|
||
| HashDigest = Annotated[ | ||
| List[Fp], Field(min_length=HASH_LEN_FE, max_length=HASH_LEN_FE) | ||
| ] | ||
| """ | ||
| A type alias representing a hash digest. | ||
| """ | ||
|
|
||
| Parameter = Annotated[ | ||
| List[Fp], Field(min_length=PARAMETER_LEN, max_length=PARAMETER_LEN) | ||
| ] | ||
| """ | ||
| A type alias representing the public parameter `P`. | ||
| """ | ||
|
|
||
| Randomness = Annotated[ | ||
| List[Fp], Field(min_length=RAND_LEN_FE, max_length=RAND_LEN_FE) | ||
| ] | ||
| """ | ||
| A type alias representing the randomness `rho`. | ||
| """ | ||
|
|
||
|
|
||
| class HashTreeOpening(BaseModel): | ||
| """ | ||
| A Merkle authentication path. | ||
|
|
||
| It contains a list of sibling nodes required to reconstruct the path | ||
| from a leaf node up to the Merkle root. | ||
| """ | ||
|
|
||
| model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True) | ||
| siblings: List[HashDigest] = Field( | ||
| ..., description="List of sibling hashes, from bottom to top." | ||
| ) | ||
|
|
||
|
|
||
| class PublicKey(BaseModel): | ||
| """The public key for the Generalized XMSS scheme.""" | ||
|
|
||
| model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True) | ||
| root: List[Fp] = Field(..., max_length=HASH_LEN_FE, min_length=HASH_LEN_FE) | ||
| parameter: Parameter = Field( | ||
| ..., max_length=PARAMETER_LEN, min_length=PARAMETER_LEN | ||
| ) | ||
|
|
||
|
|
||
| class Signature(BaseModel): | ||
| """A signature in the Generalized XMSS scheme.""" | ||
|
|
||
| model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True) | ||
| path: HashTreeOpening | ||
| rho: Randomness = Field( | ||
| ..., max_length=RAND_LEN_FE, min_length=RAND_LEN_FE | ||
| ) | ||
| hashes: List[HashDigest] | ||
|
|
||
|
|
||
| class SecretKey(BaseModel): | ||
| """ | ||
| Placeholder for the secret key. | ||
|
|
||
| Note: The full secret key structure is not specified here as it is not | ||
| needed for verification. | ||
| """ | ||
|
|
||
| pass |
Uh oh!
There was an error while loading. Please reload this page.