Skip to content

Commit 0ac7352

Browse files
authored
xmss: add full spec (leanEthereum#24)
* xmss: add full spec * add message hash and tests * utils: rm duplicate functions * small touchup * small doc touchup * complete the interface * update using classes * add end to end tests * fix linter * add util for base p decomposition * change poseidon xmss * export configs * proofreading and some adjustments
1 parent 0e1961f commit 0ac7352

18 files changed

Lines changed: 2449 additions & 240 deletions

src/lean_spec/subspecs/xmss/__init__.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@
55
It exposes the core data structures and the main interface functions.
66
"""
77

8-
from .constants import LIFETIME, MESSAGE_LENGTH
9-
from .interface import key_gen, sign, verify
10-
from .structures import HashTreeOpening, PublicKey, SecretKey, Signature
8+
from .constants import PROD_CONFIG, TEST_CONFIG
9+
from .containers import (
10+
HashTree,
11+
HashTreeOpening,
12+
PublicKey,
13+
SecretKey,
14+
Signature,
15+
)
16+
from .interface import GeneralizedXmssScheme
1117

1218
__all__ = [
13-
"key_gen",
14-
"sign",
15-
"verify",
19+
"GeneralizedXmssScheme",
1620
"PublicKey",
1721
"Signature",
1822
"SecretKey",
1923
"HashTreeOpening",
20-
"LIFETIME",
21-
"MESSAGE_LENGTH",
24+
"HashTree",
25+
"PROD_CONFIG",
26+
"TEST_CONFIG",
2227
]
Lines changed: 104 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"""
2-
Defines the cryptographic constants for the XMSS specification.
2+
Defines the cryptographic constants and configuration presets for the
3+
XMSS spec.
34
45
This specification corresponds to the "hashing-optimized" Top Level Target Sum
5-
instantiation from the canonical Rust implementation.
6+
instantiation from the canonical Rust implementation
7+
(production instantiation).
8+
9+
We also provide a test instantiation for testing purposes.
610
711
.. note::
812
This specification uses the **KoalaBear** prime field, which is consistent
@@ -14,88 +18,133 @@
1418
specification in the future.
1519
"""
1620

21+
from pydantic import BaseModel, ConfigDict
22+
from typing_extensions import Final
23+
1724
from ..koalabear import Fp
1825

19-
# =================================================================
20-
# Core Scheme Configuration
21-
# =================================================================
2226

23-
MESSAGE_LENGTH: int = 32
24-
"""The length in bytes for all messages to be signed."""
27+
class XmssConfig(BaseModel):
28+
"""A model holding the configuration constants for an XMSS preset."""
2529

26-
LOG_LIFETIME: int = 32
27-
"""The base-2 logarithm of the scheme's maximum lifetime."""
30+
model_config = ConfigDict(frozen=True, extra="forbid")
2831

29-
LIFETIME: int = 1 << LOG_LIFETIME
30-
"""
31-
The maximum number of epochs supported by this configuration.
32+
# --- Core Scheme Configuration ---
33+
MESSAGE_LENGTH: int
34+
"""The length in bytes for all messages to be signed."""
3235

33-
An individual key pair can be active for a smaller sub-range.
34-
"""
36+
LOG_LIFETIME: int
37+
"""The base-2 logarithm of the scheme's maximum lifetime."""
3538

39+
@property
40+
def LIFETIME(self) -> int: # noqa: N802
41+
"""
42+
The maximum number of epochs supported by this configuration.
3643
37-
# =================================================================
38-
# Target Sum WOTS Parameters
39-
# =================================================================
44+
An individual key pair can be active for a smaller sub-range.
45+
"""
46+
return 1 << self.LOG_LIFETIME
4047

41-
DIMENSION: int = 64
42-
"""The total number of hash chains, `v`."""
48+
DIMENSION: int
49+
"""The total number of hash chains, `v`."""
4350

44-
BASE: int = 8
45-
"""The alphabet size for the digits of the encoded message."""
51+
BASE: int
52+
"""The alphabet size for the digits of the encoded message."""
4653

47-
FINAL_LAYER: int = 77
48-
"""The number of top layers of the hypercube to map the hash output into."""
54+
FINAL_LAYER: int
55+
"""Number of top layers of the hypercube to map the hash output into."""
4956

50-
TARGET_SUM: int = 375
51-
"""The required sum of all codeword chunks for a signature to be valid."""
57+
TARGET_SUM: int
58+
"""The required sum of all codeword chunks for a signature to be valid."""
5259

60+
MAX_TRIES: int
61+
"""
62+
How often one should try at most to resample a random value.
5363
54-
# =================================================================
55-
# Hash and Encoding Length Parameters (in field elements)
56-
# =================================================================
64+
This is currently based on experiments with the Rust implementation.
65+
Should probably be modified in production.
66+
"""
5767

58-
PARAMETER_LEN: int = 5
59-
"""
60-
The length of the public parameter `P`.
68+
PARAMETER_LEN: int
69+
"""
70+
The length of the public parameter `P`.
6171
62-
It is used to specialize the hash function.
63-
"""
72+
It is used to specialize the hash function.
73+
"""
6474

65-
TWEAK_LEN_FE: int = 2
66-
"""The length of a domain-separating tweak."""
75+
TWEAK_LEN_FE: int
76+
"""The length of a domain-separating tweak."""
6777

68-
MSG_LEN_FE: int = 9
69-
"""The length of a message after being encoded into field elements."""
78+
MSG_LEN_FE: int
79+
"""The length of a message after being encoded into field elements."""
7080

71-
RAND_LEN_FE: int = 7
72-
"""The length of the randomness `rho` used during message encoding."""
81+
RAND_LEN_FE: int
82+
"""The length of the randomness `rho` used during message encoding."""
7383

74-
HASH_LEN_FE: int = 8
75-
"""The output length of the main tweakable hash function."""
84+
HASH_LEN_FE: int
85+
"""The output length of the main tweakable hash function."""
7686

77-
CAPACITY: int = 9
78-
"""The capacity of the Poseidon2 sponge, defining its security level."""
87+
CAPACITY: int
88+
"""The capacity of the Poseidon2 sponge, defining its security level."""
7989

80-
POS_OUTPUT_LEN_PER_INV_FE: int = 15
81-
"""Output length per invocation for the message hash."""
90+
POS_OUTPUT_LEN_PER_INV_FE: int
91+
"""Output length per invocation for the message hash."""
8292

83-
POS_INVOCATIONS: int = 1
84-
"""Number of invocations for the message hash."""
93+
POS_INVOCATIONS: int
94+
"""Number of invocations for the message hash."""
8595

86-
POS_OUTPUT_LEN_FE: int = POS_OUTPUT_LEN_PER_INV_FE * POS_INVOCATIONS
87-
"""Total output length for the message hash."""
96+
@property
97+
def POS_OUTPUT_LEN_FE(self) -> int: # noqa: N802
98+
"""Total output length for the message hash."""
99+
return self.POS_OUTPUT_LEN_PER_INV_FE * self.POS_INVOCATIONS
88100

89101

90-
# =================================================================
91-
# Domain Separator Prefixes for Tweaks
92-
# =================================================================
102+
PROD_CONFIG: Final = XmssConfig(
103+
MESSAGE_LENGTH=32,
104+
LOG_LIFETIME=32,
105+
DIMENSION=64,
106+
BASE=8,
107+
FINAL_LAYER=77,
108+
TARGET_SUM=375,
109+
MAX_TRIES=100_000,
110+
PARAMETER_LEN=5,
111+
TWEAK_LEN_FE=2,
112+
MSG_LEN_FE=9,
113+
RAND_LEN_FE=7,
114+
HASH_LEN_FE=8,
115+
CAPACITY=9,
116+
POS_OUTPUT_LEN_PER_INV_FE=15,
117+
POS_INVOCATIONS=1,
118+
)
93119

94-
TWEAK_PREFIX_CHAIN = Fp(value=0x00)
120+
121+
TEST_CONFIG: Final = XmssConfig(
122+
MESSAGE_LENGTH=32,
123+
LOG_LIFETIME=8,
124+
DIMENSION=16,
125+
BASE=4,
126+
FINAL_LAYER=24,
127+
TARGET_SUM=24,
128+
MAX_TRIES=100_000,
129+
PARAMETER_LEN=5,
130+
TWEAK_LEN_FE=2,
131+
MSG_LEN_FE=9,
132+
RAND_LEN_FE=7,
133+
HASH_LEN_FE=8,
134+
CAPACITY=9,
135+
POS_OUTPUT_LEN_PER_INV_FE=15,
136+
POS_INVOCATIONS=1,
137+
)
138+
139+
140+
TWEAK_PREFIX_CHAIN: Final = Fp(value=0x00)
95141
"""The unique prefix for tweaks used in Winternitz-style hash chains."""
96142

97-
TWEAK_PREFIX_TREE = Fp(value=0x01)
143+
TWEAK_PREFIX_TREE: Final = Fp(value=0x01)
98144
"""The unique prefix for tweaks used when hashing Merkle tree nodes."""
99145

100-
TWEAK_PREFIX_MESSAGE = Fp(value=0x02)
146+
TWEAK_PREFIX_MESSAGE: Final = Fp(value=0x02)
101147
"""The unique prefix for tweaks used in the initial message hashing step."""
148+
149+
PRF_KEY_LENGTH: int = 32
150+
"""The length of the PRF secret key in bytes."""
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""Defines the data containers for the Generalized XMSS signature scheme."""
2+
3+
from typing import Annotated, List
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
from ..koalabear import Fp
8+
from .constants import PRF_KEY_LENGTH
9+
10+
PRFKey = Annotated[
11+
bytes, Field(min_length=PRF_KEY_LENGTH, max_length=PRF_KEY_LENGTH)
12+
]
13+
"""
14+
A type alias for the PRF secret key.
15+
16+
It is a byte string of `PRF_KEY_LENGTH` bytes.
17+
"""
18+
19+
20+
HashDigest = List[Fp]
21+
"""
22+
A type alias representing a hash digest.
23+
"""
24+
25+
Parameter = List[Fp]
26+
"""
27+
A type alias representing the public parameter `P`.
28+
"""
29+
30+
Randomness = List[Fp]
31+
"""
32+
A type alias representing the randomness `rho`.
33+
"""
34+
35+
36+
class HashTreeOpening(BaseModel):
37+
"""
38+
A Merkle authentication path.
39+
40+
It contains a list of sibling nodes required to reconstruct the path
41+
from a leaf node up to the Merkle root.
42+
"""
43+
44+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
45+
siblings: List[HashDigest] = Field(
46+
..., description="List of sibling hashes, from bottom to top."
47+
)
48+
49+
50+
class HashTreeLayer(BaseModel):
51+
"""
52+
Represents a single layer within the sparse Merkle tree.
53+
54+
Attributes:
55+
start_index: The index of the first node in this layer within the full
56+
conceptual tree.
57+
nodes: A list of the actual hash digests stored for this layer.
58+
"""
59+
60+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
61+
start_index: int
62+
"""The starting index of the first node in this layer."""
63+
nodes: List[HashDigest]
64+
"""A list of the actual hash digests stored for this layer."""
65+
66+
67+
class HashTree(BaseModel):
68+
"""
69+
The complete sparse Merkle tree structure.
70+
71+
Attributes:
72+
depth: The total depth of the tree (e.g., 32 for a 2^32 leaf space).
73+
layers: A list of `HashTreeLayer` objects, from the leaf hashes
74+
(layer 0) up to the layer just below the root.
75+
"""
76+
77+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
78+
depth: int
79+
"""The total depth of the tree (e.g., 32 for a 2^32 leaf space)."""
80+
layers: List[HashTreeLayer]
81+
"""""A list of `HashTreeLayer` objects, from the leaf hashes
82+
(layer 0) up to the layer just below the root."""
83+
84+
85+
class PublicKey(BaseModel):
86+
"""The public key for the Generalized XMSS scheme."""
87+
88+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
89+
root: List[Fp]
90+
parameter: Parameter
91+
92+
93+
class Signature(BaseModel):
94+
"""A signature in the Generalized XMSS scheme."""
95+
96+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
97+
path: HashTreeOpening
98+
rho: Randomness
99+
hashes: List[HashDigest]
100+
101+
102+
class SecretKey(BaseModel):
103+
"""The secret key for the Generalized XMSS scheme."""
104+
105+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
106+
prf_key: PRFKey
107+
tree: HashTree
108+
parameter: Parameter
109+
activation_epoch: int
110+
num_active_epochs: int

0 commit comments

Comments
 (0)