|
3 | 3 | import math |
4 | 4 | from typing import Final, Self |
5 | 5 |
|
6 | | -from pydantic import model_validator |
| 6 | +from pydantic import Field, model_validator |
7 | 7 |
|
8 | 8 | from lean_spec.base import StrictBaseModel |
9 | 9 | from lean_spec.config import LEAN_ENV |
|
15 | 15 | class XmssConfig(StrictBaseModel): |
16 | 16 | """A model holding the configuration constants for an XMSS preset.""" |
17 | 17 |
|
18 | | - LOG_LIFETIME: int |
| 18 | + LOG_LIFETIME: int = Field(gt=0) |
19 | 19 | """Base-2 logarithm of the scheme's maximum lifetime, the Merkle tree height.""" |
20 | 20 |
|
21 | | - DIMENSION: int |
| 21 | + DIMENSION: int = Field(gt=0) |
22 | 22 | """Number of hash chains per signature, v. |
23 | 23 | Security-derived: it sets how many codeword chunks a signature commits to.""" |
24 | 24 |
|
25 | | - BASE: int |
| 25 | + BASE: int = Field(gt=0) |
26 | 26 | """Alphabet size for the digits of the encoded message, the Winternitz parameter.""" |
27 | 27 |
|
28 | | - Z: int |
| 28 | + Z: int = Field(gt=0) |
29 | 29 | """Number of base-BASE digits extracted from each field element.""" |
30 | 30 |
|
31 | | - Q: int |
| 31 | + Q: int = Field(gt=0) |
32 | 32 | """Quotient fixing the digit decomposition, constrained by Q * BASE^Z == P - 1.""" |
33 | 33 |
|
34 | | - TARGET_SUM: int |
| 34 | + TARGET_SUM: int = Field(gt=0) |
35 | 35 | """Required sum of all codeword chunks for a signature to be valid. |
36 | 36 | Security-derived: it tunes the forgery resistance of the encoding.""" |
37 | 37 |
|
38 | | - MAX_TRIES: int |
| 38 | + MAX_TRIES: int = Field(gt=0) |
39 | 39 | """Maximum resampling attempts when searching for a codeword that meets the target sum. |
40 | 40 | Performance knob: a higher cap trades signing time for fewer hard failures.""" |
41 | 41 |
|
42 | | - PARAMETER_LENGTH: int |
| 42 | + PARAMETER_LENGTH: int = Field(gt=0) |
43 | 43 | """Length of the public parameter P, in field elements.""" |
44 | 44 |
|
45 | | - TWEAK_LENGTH_FIELD_ELEMENTS: int |
| 45 | + TWEAK_LENGTH_FIELD_ELEMENTS: int = Field(gt=0) |
46 | 46 | """Length of a domain-separating tweak, in field elements.""" |
47 | 47 |
|
48 | | - MESSAGE_LENGTH_FIELD_ELEMENTS: int |
| 48 | + MESSAGE_LENGTH_FIELD_ELEMENTS: int = Field(gt=0) |
49 | 49 | """Length of a message after being encoded into field elements.""" |
50 | 50 |
|
51 | | - RAND_LENGTH_FIELD_ELEMENTS: int |
| 51 | + RAND_LENGTH_FIELD_ELEMENTS: int = Field(gt=0) |
52 | 52 | """Length of the randomness rho used during message encoding, in field elements.""" |
53 | 53 |
|
54 | | - HASH_LENGTH_FIELD_ELEMENTS: int |
| 54 | + HASH_LENGTH_FIELD_ELEMENTS: int = Field(gt=0) |
55 | 55 | """Output length of the main tweakable hash function, in field elements. |
56 | 56 | Security-derived: it sets the collision resistance of every digest.""" |
57 | 57 |
|
58 | | - CAPACITY: int |
| 58 | + CAPACITY: int = Field(gt=0) |
59 | 59 | """Capacity of the Poseidon sponge, in field elements. |
60 | 60 | Security-derived: the capacity sets the sponge's security level.""" |
61 | 61 |
|
62 | 62 | @model_validator(mode="after") |
63 | 63 | def _validate_decomposition(self) -> Self: |
64 | | - """Verify that Q * BASE^Z == P - 1.""" |
| 64 | + """Verify that Q * BASE^Z == P - 1 and that LOG_LIFETIME is even.""" |
65 | 65 | if self.Q * self.BASE**self.Z != P - 1: |
66 | 66 | raise ValueError(f"Q * BASE^Z must equal P-1={P - 1}") |
| 67 | + # The key splits into a top tree and bottom trees. |
| 68 | + # Each covers LOG_LIFETIME / 2 levels, so the lifetime exponent must be even. |
| 69 | + if self.LOG_LIFETIME % 2 != 0: |
| 70 | + raise ValueError(f"LOG_LIFETIME must be even, got {self.LOG_LIFETIME}") |
67 | 71 | return self |
68 | 72 |
|
69 | 73 | @property |
|
0 commit comments