Skip to content

Commit 6a063fe

Browse files
committed
xmss: scaffolding for the signature spec
1 parent 8b57bb8 commit 6a063fe

File tree

4 files changed

+70
-343
lines changed

4 files changed

+70
-343
lines changed

src/lean_spec/subspecs/xmss/constants.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
MESSAGE_LENGTH: int = 32
1010
"""The length in bytes for all messages to be signed."""
1111

12-
LOG_LIFETIME: int = 32
12+
LOG_LIFETIME: int = 18
1313
"""The base-2 logarithm of the scheme's maximum lifetime."""
1414

1515
LIFETIME: int = 1 << LOG_LIFETIME
@@ -19,6 +19,7 @@
1919
An individual key pair can be active for a smaller sub-range.
2020
"""
2121

22+
2223
# =================================================================
2324
# Target Sum WOTS Parameters
2425
# =================================================================
@@ -29,12 +30,12 @@
2930
BASE: int = 8
3031
"""The alphabet size for the digits of the encoded message."""
3132

33+
FINAL_LAYER: int = 77
34+
"""The number of top layers of the hypercube to map the hash output into."""
35+
3236
TARGET_SUM: int = 375
3337
"""The required sum of all codeword chunks for a signature to be valid."""
3438

35-
CHUNK_SIZE: int = 3
36-
"""The number of bits per chunk, calculated as ceil(log2(BASE))."""
37-
3839

3940
# =================================================================
4041
# Hash and Encoding Length Parameters (in field elements)
@@ -44,27 +45,33 @@
4445
"""
4546
The length of the public parameter `P`.
4647
47-
It isused to specialize the hash function.
48+
It is used to specialize the hash function.
4849
"""
4950

50-
HASH_LEN: int = 8
51-
"""The output length of the main tweakable hash function."""
52-
53-
RAND_LEN: int = 7
54-
"""The length of the randomness `rho` used during message encoding."""
55-
56-
TWEAK_LEN: int = 2
51+
TWEAK_LEN_FE: int = 2
5752
"""The length of a domain-separating tweak."""
5853

59-
MSG_LEN: int = 9
54+
MSG_LEN_FE: int = 9
6055
"""The length of a message after being encoded into field elements."""
6156

62-
MSG_HASH_LEN: int = 15
63-
"""The output length of the hash function used to digest the message."""
57+
RAND_LEN_FE: int = 6
58+
"""The length of the randomness `rho` used during message encoding."""
59+
60+
HASH_LEN_FE: int = 7
61+
"""The output length of the main tweakable hash function."""
6462

6563
CAPACITY: int = 9
6664
"""The capacity of the Poseidon2 sponge, defining its security level."""
6765

66+
POS_OUTPUT_LEN_PER_INV_FE: int = 15
67+
"""Output length per invocation for the message hash."""
68+
69+
POS_INVOCATIONS: int = 1
70+
"""Number of invocations for the message hash."""
71+
72+
POS_OUTPUT_LEN_FE: int = POS_OUTPUT_LEN_PER_INV_FE * POS_INVOCATIONS
73+
"""Total output length for the message hash."""
74+
6875

6976
# =================================================================
7077
# Domain Separator Prefixes for Tweaks

src/lean_spec/subspecs/xmss/interface.py

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
"""
22
Defines the core interface for the Generalized XMSS signature scheme.
33
4-
This file specifies the high-level functions (`key_gen`, `sign`, `verify`)
4+
Specification for the high-level functions (`key_gen`, `sign`, `verify`)
55
that constitute the public API of the signature scheme. For the purpose of this
66
specification, these are defined as placeholders with detailed documentation.
77
"""
88

9-
from typing import List, Tuple
9+
from __future__ import annotations
10+
11+
from typing import Tuple
1012

11-
from ..koalabear import Fp
12-
from .constants import BASE, CHUNK_SIZE, DIMENSION, LIFETIME, MESSAGE_LENGTH
1313
from .structures import PublicKey, SecretKey, Signature
14-
from .utils import chain, encode, hash_tree_verify, tweakable_hash
1514

1615

1716
def key_gen() -> Tuple[PublicKey, SecretKey]:
@@ -55,34 +54,46 @@ def sign(sk: SecretKey, epoch: int, message: bytes) -> Signature:
5554

5655

5756
def verify(pk: PublicKey, epoch: int, message: bytes, sig: Signature) -> bool:
57+
r"""
58+
Verifies a digital signature against a public key, message, and epoch.
59+
60+
This function is a placeholder. The complete verification logic is detailed
61+
below and will be implemented in a future update.
62+
63+
### Verification Algorithm
64+
65+
1. **Re-encode Message**: The verifier uses the randomness `rho` from the
66+
signature to re-compute the codeword $x = (x_1, \dots, x_v)$ from the
67+
message `m`. This includes calculating the checksum or checking the target sum.
68+
69+
2. **Reconstruct One-Time Public Key**: For each intermediate hash `y_i`
70+
in the signature, the verifier completes the corresponding hash chain.
71+
Since `y_i` was computed with $x_i$ steps, the verifier applies the
72+
hash function an additional $w - 1 - x_i$ times to arrive at the
73+
one-time public key component `otpk_{ep,i}`.
74+
75+
3. **Compute Merkle Leaf**: The verifier hashes the reconstructed one-time
76+
public key components to compute the expected Merkle leaf for `epoch`.
77+
78+
4. **Verify Merkle Path**: The verifier uses the `path` from the signature
79+
to compute a candidate Merkle root starting from the computed leaf.
80+
Verification succeeds if and only if this candidate root matches the
81+
`root` in the `PublicKey`.
82+
83+
Args:
84+
pk: The public key to verify against.
85+
epoch: The epoch the signature corresponds to.
86+
message: The message that was supposedly signed.
87+
sig: The signature object to be verified.
88+
89+
Returns:
90+
`True` if the signature is valid, `False` otherwise.
91+
92+
For the formal specification of this process, please refer to:
93+
- "Hash-Based Multi-Signatures for Post-Quantum Ethereum" [DKKW25a]
94+
- "Technical Note: LeanSig for Post-Quantum Ethereum" [DKKW25b]
95+
- The canonical Rust implementation: https://github.com/b-wagn/hash-sig
5896
"""
59-
Verifies a digital signature against:
60-
- a public key,
61-
- a message,
62-
- epoch.
63-
"""
64-
assert len(message) == MESSAGE_LENGTH, "Invalid message length"
65-
assert 0 <= epoch < LIFETIME, "Epoch out of valid range"
66-
67-
# Re-encode the message to get the expected codeword.
68-
codeword = encode(pk.parameter, message, sig.rho, epoch)
69-
if codeword is None:
70-
return False
71-
72-
# Reconstruct the one-time public key from the signature's hashes.
73-
chain_ends: List[List[Fp]] = []
74-
for i in range(DIMENSION):
75-
steps_to_end = (BASE**CHUNK_SIZE - 1) - codeword[i]
76-
end_of_chain = chain(
77-
pk.parameter, epoch, i, codeword[i], steps_to_end, sig.hashes[i]
78-
)
79-
chain_ends.append(end_of_chain)
80-
81-
# Compute the Merkle leaf by hashing the reconstructed one-time public key.
82-
# Note: A proper tweak would be used here. For simplicity, we omit it.
83-
computed_leaf = tweakable_hash(pk.parameter, [], chain_ends)
84-
85-
# Verify the Merkle path against the public key's root.
86-
return hash_tree_verify(
87-
pk.parameter, pk.root, epoch, computed_leaf, sig.path
97+
raise NotImplementedError(
98+
"verify will be implemented in a future update to the specification."
8899
)

src/lean_spec/subspecs/xmss/structures.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import BaseModel, ConfigDict, Field
66

77
from ..koalabear import Fp
8-
from .constants import HASH_LEN, PARAMETER_LEN, RAND_LEN
8+
from .constants import HASH_LEN_FE, PARAMETER_LEN, RAND_LEN_FE
99

1010

1111
class HashTreeOpening(BaseModel):
@@ -26,7 +26,7 @@ class PublicKey(BaseModel):
2626
"""The public key for the Generalized XMSS scheme."""
2727

2828
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
29-
root: List[Fp] = Field(..., max_length=HASH_LEN, min_length=HASH_LEN)
29+
root: List[Fp] = Field(..., max_length=HASH_LEN_FE, min_length=HASH_LEN_FE)
3030
parameter: List[Fp] = Field(
3131
..., max_length=PARAMETER_LEN, min_length=PARAMETER_LEN
3232
)
@@ -37,7 +37,7 @@ class Signature(BaseModel):
3737

3838
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
3939
path: HashTreeOpening
40-
rho: List[Fp] = Field(..., max_length=RAND_LEN, min_length=RAND_LEN)
40+
rho: List[Fp] = Field(..., max_length=RAND_LEN_FE, min_length=RAND_LEN_FE)
4141
hashes: List[List[Fp]]
4242

4343

0 commit comments

Comments
 (0)