Skip to content

Commit ec4cdff

Browse files
committed
whirlaway: cp xmss crate
1 parent d2e4414 commit ec4cdff

File tree

3 files changed

+288
-0
lines changed

3 files changed

+288
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ members = [
1616
"crates/sumcheck",
1717
"crates/pcs",
1818
"crates/lookup",
19+
"crates/xmss",
1920
]
2021
resolver = "3"
2122

@@ -52,6 +53,7 @@ air = { path = "crates/air" }
5253
sumcheck = { path = "crates/sumcheck" }
5354
pcs = { path = "crates/pcs" }
5455
lookup = { path = "crates/lookup" }
56+
xmss = { path = "crates/xmss" }
5557

5658
rand = "0.9.2"
5759
sha3 = "0.10.8"

crates/xmss/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "xmss"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
license.workspace = true
7+
8+
[lints]
9+
workspace = true
10+
11+
[dependencies]
12+
p3-koala-bear.workspace = true
13+
rand.workspace = true
14+
utils.workspace = true
15+
p3-symmetric.workspace = true

crates/xmss/src/lib.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
2+
3+
use p3_koala_bear::KoalaBear;
4+
use p3_symmetric::Permutation;
5+
use rand::Rng;
6+
use utils::{Poseidon16, Poseidon24};
7+
8+
type F = KoalaBear;
9+
pub type Digest = [F; 8];
10+
pub type Message = [u8; N_CHAINS]; // each value is < CHAIN_LOG_LENGTH
11+
12+
pub const N_CHAINS: usize = 64;
13+
pub const CHAIN_LOG_LENGTH: usize = 3;
14+
pub const CHAIN_LENGTH: usize = 1 << CHAIN_LOG_LENGTH;
15+
16+
pub const XMSS_MERKLE_HEIGHT: usize = 5;
17+
18+
#[derive(Debug)]
19+
pub struct WotsSecretKey {
20+
pre_images: [Digest; N_CHAINS],
21+
public_key: WotsPublicKey,
22+
}
23+
24+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25+
pub struct WotsPublicKey(pub [Digest; N_CHAINS]);
26+
#[derive(Debug)]
27+
pub struct WotsSignature(pub [Digest; N_CHAINS]);
28+
29+
impl WotsSecretKey {
30+
pub fn random<R: Rng>(rng: &mut R, poseidon16: &Poseidon16) -> Self {
31+
let mut pre_images = [Default::default(); N_CHAINS];
32+
for i in 0..N_CHAINS {
33+
let mut pre_image = [F::default(); 8];
34+
for j in 0..8 {
35+
pre_image[j] = rng.random();
36+
}
37+
pre_images[i] = pre_image;
38+
}
39+
Self::new(pre_images, poseidon16)
40+
}
41+
42+
pub fn new(pre_images: [Digest; N_CHAINS], poseidon16: &Poseidon16) -> Self {
43+
let mut public_key = [Default::default(); N_CHAINS];
44+
for i in 0..N_CHAINS {
45+
public_key[i] = iterate_hash(&pre_images[i], CHAIN_LENGTH, poseidon16);
46+
}
47+
Self {
48+
pre_images,
49+
public_key: WotsPublicKey(public_key),
50+
}
51+
}
52+
53+
pub fn public_key(&self) -> &WotsPublicKey {
54+
&self.public_key
55+
}
56+
57+
pub fn sign(&self, message: &Message, poseidon16: &Poseidon16) -> WotsSignature {
58+
let mut signature = [Default::default(); N_CHAINS];
59+
for i in 0..N_CHAINS {
60+
assert!(
61+
(message[i] as usize) < CHAIN_LENGTH,
62+
"Message value out of bounds"
63+
);
64+
signature[i] = iterate_hash(&self.pre_images[i], message[i] as usize, poseidon16);
65+
}
66+
WotsSignature(signature)
67+
}
68+
}
69+
70+
impl WotsSignature {
71+
pub fn recover_public_key(
72+
&self,
73+
message: &Message,
74+
signature: &WotsSignature,
75+
poseidon16: &Poseidon16,
76+
) -> WotsPublicKey {
77+
let mut public_key = [Default::default(); N_CHAINS];
78+
for i in 0..N_CHAINS {
79+
assert!(
80+
(message[i] as usize) < CHAIN_LENGTH,
81+
"Message value out of bounds"
82+
);
83+
public_key[i] = iterate_hash(
84+
&signature.0[i],
85+
CHAIN_LENGTH - message[i] as usize,
86+
poseidon16,
87+
);
88+
}
89+
WotsPublicKey(public_key)
90+
}
91+
}
92+
93+
impl WotsPublicKey {
94+
pub fn hash(&self, poseidon24: &Poseidon24) -> Digest {
95+
assert!(N_CHAINS % 2 == 0, "TODO");
96+
let mut digest = Default::default();
97+
for (a, b) in self.0.chunks(2).map(|chunk| (chunk[0], chunk[1])) {
98+
digest = poseidon24.permute([a, b, digest].concat().try_into().unwrap())[16..24]
99+
.try_into()
100+
.unwrap();
101+
}
102+
digest
103+
}
104+
}
105+
106+
#[derive(Debug)]
107+
pub struct XmssSecretKey {
108+
pub wots_secret_keys: Vec<WotsSecretKey>,
109+
pub merkle_tree: Vec<Vec<Digest>>,
110+
}
111+
112+
#[derive(Debug)]
113+
pub struct XmssSignature {
114+
pub wots_signature: WotsSignature,
115+
pub merkle_proof: Vec<(bool, Digest)>,
116+
}
117+
118+
#[derive(Debug)]
119+
pub struct XmssPublicKey {
120+
pub root: Digest,
121+
}
122+
123+
impl XmssSecretKey {
124+
pub fn random<R: Rng>(rng: &mut R, poseidon16: &Poseidon16, poseidon24: &Poseidon24) -> Self {
125+
let mut wots_secret_keys = Vec::new();
126+
for _ in 0..1 << XMSS_MERKLE_HEIGHT {
127+
wots_secret_keys.push(WotsSecretKey::random(rng, poseidon16));
128+
}
129+
let leaves = wots_secret_keys
130+
.iter()
131+
.map(|w| w.public_key().hash(poseidon24))
132+
.collect::<Vec<_>>();
133+
let mut merkle_tree = vec![leaves];
134+
for _ in 0..XMSS_MERKLE_HEIGHT {
135+
let mut next_level = Vec::new();
136+
let current_level = merkle_tree.last().unwrap();
137+
for (left, right) in current_level.chunks(2).map(|chunk| (chunk[0], chunk[1])) {
138+
next_level.push(
139+
poseidon16.permute([left, right].concat().try_into().unwrap())[0..8]
140+
.try_into()
141+
.unwrap(),
142+
);
143+
}
144+
merkle_tree.push(next_level);
145+
}
146+
Self {
147+
wots_secret_keys,
148+
merkle_tree,
149+
}
150+
}
151+
152+
pub fn sign(&self, message: &Message, index: usize, poseidon16: &Poseidon16) -> XmssSignature {
153+
assert!(
154+
index < (1 << XMSS_MERKLE_HEIGHT),
155+
"Index out of bounds for XMSS signature"
156+
);
157+
let wots_signature = self.wots_secret_keys[index].sign(message, poseidon16);
158+
let mut merkle_proof = Vec::new();
159+
let mut current_index = index;
160+
for level in 0..XMSS_MERKLE_HEIGHT {
161+
let is_left = current_index % 2 == 0;
162+
let neighbour_index = if is_left {
163+
current_index + 1
164+
} else {
165+
current_index - 1
166+
};
167+
let neighbour = self.merkle_tree[level][neighbour_index];
168+
merkle_proof.push((is_left, neighbour));
169+
current_index /= 2;
170+
}
171+
XmssSignature {
172+
wots_signature,
173+
merkle_proof,
174+
}
175+
}
176+
177+
pub fn public_key(&self) -> XmssPublicKey {
178+
XmssPublicKey {
179+
root: self.merkle_tree.last().unwrap()[0],
180+
}
181+
}
182+
}
183+
184+
impl XmssPublicKey {
185+
pub fn verify(
186+
&self,
187+
message: &Message,
188+
signature: &XmssSignature,
189+
poseidon16: &Poseidon16,
190+
poseidon24: &Poseidon24,
191+
) -> bool {
192+
let wots_public_key = signature.wots_signature.recover_public_key(
193+
message,
194+
&signature.wots_signature,
195+
poseidon16,
196+
);
197+
// merkle root verification
198+
let mut current_hash = wots_public_key.hash(poseidon24);
199+
if signature.merkle_proof.len() != XMSS_MERKLE_HEIGHT {
200+
return false;
201+
}
202+
for (is_left, neighbour) in &signature.merkle_proof {
203+
if *is_left {
204+
current_hash = poseidon16
205+
.permute([current_hash, *neighbour].concat().try_into().unwrap())
206+
[0..8]
207+
.try_into()
208+
.unwrap();
209+
} else {
210+
current_hash = poseidon16
211+
.permute([*neighbour, current_hash].concat().try_into().unwrap())
212+
[0..8]
213+
.try_into()
214+
.unwrap();
215+
}
216+
}
217+
current_hash == self.root
218+
}
219+
}
220+
221+
fn iterate_hash(a: &Digest, n: usize, poseidon16: &Poseidon16) -> Digest {
222+
let mut res = *a;
223+
for _ in 0..n {
224+
res = poseidon16.permute([res, Default::default()].concat().try_into().unwrap())[0..8]
225+
.try_into()
226+
.unwrap();
227+
}
228+
res
229+
}
230+
231+
pub fn random_message<R: Rng>(rng: &mut R) -> Message {
232+
let mut message = [0u8; N_CHAINS];
233+
for i in 0..N_CHAINS {
234+
message[i] = rng.random_range(0..CHAIN_LENGTH) as u8;
235+
}
236+
message
237+
}
238+
239+
#[cfg(test)]
240+
mod tests {
241+
use rand::{SeedableRng, rngs::StdRng};
242+
use utils::{build_poseidon16, build_poseidon24};
243+
244+
use super::*;
245+
246+
#[test]
247+
fn test_wots_signature() {
248+
let mut rng = StdRng::seed_from_u64(0);
249+
let poseidon16 = build_poseidon16();
250+
let sk = WotsSecretKey::random(&mut rng, &poseidon16);
251+
let message = random_message(&mut rng);
252+
let signature = sk.sign(&message, &poseidon16);
253+
assert_eq!(
254+
signature.recover_public_key(&message, &signature, &poseidon16),
255+
*sk.public_key()
256+
);
257+
}
258+
259+
#[test]
260+
fn test_xmss_signature() {
261+
let mut rng = StdRng::seed_from_u64(0);
262+
let poseidon16 = build_poseidon16();
263+
let poseidon24 = build_poseidon24();
264+
let sk = XmssSecretKey::random(&mut rng, &poseidon16, &poseidon24);
265+
let message = random_message(&mut rng);
266+
let index = rng.random_range(0..(1 << XMSS_MERKLE_HEIGHT));
267+
let signature = sk.sign(&message, index, &poseidon16);
268+
let public_key = sk.public_key();
269+
assert!(public_key.verify(&message, &signature, &poseidon16, &poseidon24));
270+
}
271+
}

0 commit comments

Comments
 (0)