Skip to content

Commit 310ba30

Browse files
committed
Faster PoT verification without allocation or std
1 parent 085ec66 commit 310ba30

File tree

9 files changed

+72
-57
lines changed

9 files changed

+72
-57
lines changed

Cargo.lock

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ cc = "1.1.23"
4141
chacha20 = { version = "0.9.1", default-features = false }
4242
clap = "4.5.18"
4343
core_affinity = "0.8.1"
44+
cpufeatures = "0.2.17"
4445
criterion = { version = "0.5.1", default-features = false }
4546
cross-domain-message-gossip = { version = "0.1.0", path = "domains/client/cross-domain-message-gossip" }
4647
derive_more = { version = "1.0.0", default-features = false }

crates/sc-proof-of-time/src/verifier.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ impl PotVerifier {
272272
drop(cache);
273273

274274
let verified_successfully =
275-
subspace_proof_of_time::verify(seed, slot_iterations, checkpoints.as_slice())
275+
subspace_proof_of_time::verify(seed, slot_iterations, checkpoints)
276276
.unwrap_or_default();
277277

278278
if !verified_successfully {

crates/subspace-core-primitives/src/pot.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
33
use crate::hashes::{blake3_hash, blake3_hash_list, Blake3Hash};
44
use crate::Randomness;
5-
use core::fmt;
65
use core::num::NonZeroU8;
76
use core::str::FromStr;
7+
use core::{fmt, mem};
88
use derive_more::{AsMut, AsRef, Deref, DerefMut, From};
99
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
1010
use scale_info::TypeInfo;
@@ -213,6 +213,7 @@ impl PotSeed {
213213
TypeInfo,
214214
MaxEncodedLen,
215215
)]
216+
#[repr(C)]
216217
pub struct PotOutput([u8; Self::SIZE]);
217218

218219
impl fmt::Debug for PotOutput {
@@ -291,6 +292,20 @@ impl PotOutput {
291292
seed.copy_from_slice(&hash[..Self::SIZE]);
292293
seed
293294
}
295+
296+
/// Convenient conversion from slice of underlying representation for efficiency purposes
297+
#[inline(always)]
298+
pub const fn slice_from_repr(value: &[[u8; Self::SIZE]]) -> &[Self] {
299+
// SAFETY: `PotOutput` is `#[repr(C)]` and guaranteed to have the same memory layout
300+
unsafe { mem::transmute(value) }
301+
}
302+
303+
/// Convenient conversion to slice of underlying representation for efficiency purposes
304+
#[inline(always)]
305+
pub const fn repr_from_slice(value: &[Self]) -> &[[u8; Self::SIZE]] {
306+
// SAFETY: `PotOutput` is `#[repr(C)]` and guaranteed to have the same memory layout
307+
unsafe { mem::transmute(value) }
308+
}
294309
}
295310

296311
/// Proof of time checkpoints, result of proving

crates/subspace-proof-of-time/Cargo.toml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "subspace-proof-of-time"
33
description = "Subspace proof of time implementation"
44
license = "0BSD"
55
version = "0.1.0"
6-
authors = ["Rahul Subramaniyam <rahulksnv@gmail.com>"]
6+
authors = ["Nazar Mokrynskyi <nazar@mokrynskyi.com>"]
77
edition = "2021"
88
include = [
99
"/src",
@@ -19,13 +19,14 @@ aes.workspace = true
1919
subspace-core-primitives.workspace = true
2020
thiserror.workspace = true
2121

22-
# This is required to for benchmark dependency features to work correctly
23-
rand = { workspace = true, optional = true }
22+
[target.'cfg(target_arch = "x86_64")'.dependencies]
23+
cpufeatures = { workspace = true }
2424

2525
[dev-dependencies]
2626
core_affinity.workspace = true
2727
criterion.workspace = true
28-
rand.workspace = true
28+
rand_core = { workspace = true }
29+
rand_chacha = { workspace = true }
2930

3031
[[bench]]
3132
name = "pot"
@@ -34,12 +35,3 @@ harness = false
3435
[[bench]]
3536
name = "pot-compare-cpu-cores"
3637
harness = false
37-
38-
[features]
39-
default = ["std"]
40-
std = [
41-
"subspace-core-primitives/std",
42-
"thiserror/std",
43-
"rand?/std",
44-
"rand?/std_rng",
45-
]

crates/subspace-proof-of-time/benches/pot-compare-cpu-cores.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use core::num::NonZeroU32;
2-
use criterion::{black_box, criterion_group, criterion_main, Criterion};
3-
use rand::{thread_rng, Rng};
2+
use criterion::{criterion_group, criterion_main, Criterion};
3+
use rand_chacha::ChaCha8Rng;
4+
use rand_core::{RngCore, SeedableRng};
5+
use std::hint::black_box;
46
use subspace_core_primitives::pot::PotSeed;
57
use subspace_proof_of_time::prove;
68

79
fn criterion_benchmark(c: &mut Criterion) {
10+
let mut rng = ChaCha8Rng::from_seed(Default::default());
811
let mut seed = PotSeed::default();
9-
thread_rng().fill(seed.as_mut());
12+
rng.fill_bytes(seed.as_mut());
1013
// About 1s on 6.0 GHz Raptor Lake CPU (14900K)
1114
let pot_iterations = NonZeroU32::new(200_032_000).expect("Not zero; qed");
1215

crates/subspace-proof-of-time/benches/pot.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use core::num::NonZeroU32;
2-
use criterion::{black_box, criterion_group, criterion_main, Criterion};
3-
use rand::{thread_rng, Rng};
2+
use criterion::{criterion_group, criterion_main, Criterion};
3+
use rand_chacha::ChaCha8Rng;
4+
use rand_core::{RngCore, SeedableRng};
5+
use std::hint::black_box;
46
use subspace_core_primitives::pot::PotSeed;
57
use subspace_proof_of_time::{prove, verify};
68

79
fn criterion_benchmark(c: &mut Criterion) {
10+
let mut rng = ChaCha8Rng::from_seed(Default::default());
811
let mut seed = PotSeed::default();
9-
thread_rng().fill(seed.as_mut());
12+
rng.fill_bytes(seed.as_mut());
1013
// About 1s on 6.0 GHz Raptor Lake CPU (14900K)
1114
let pot_iterations = NonZeroU32::new(200_032_000).expect("Not zero; qed");
1215

@@ -23,7 +26,7 @@ fn criterion_benchmark(c: &mut Criterion) {
2326
black_box(verify(
2427
black_box(seed),
2528
black_box(pot_iterations),
26-
black_box(&*checkpoints),
29+
black_box(&checkpoints),
2730
))
2831
.unwrap();
2932
})

crates/subspace-proof-of-time/src/aes.rs

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
//! AES related functionality.
22
3-
#[cfg(all(feature = "std", target_arch = "x86_64"))]
3+
#[cfg(target_arch = "x86_64")]
44
mod x86_64;
55

6-
#[cfg(not(feature = "std"))]
7-
extern crate alloc;
8-
96
use aes::cipher::array::Array;
107
use aes::cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
118
use aes::Aes128;
12-
#[cfg(not(feature = "std"))]
13-
use alloc::vec::Vec;
149
use subspace_core_primitives::pot::{PotCheckpoints, PotKey, PotOutput, PotSeed};
1510

1611
/// Creates the AES based proof.
1712
#[inline(always)]
1813
pub(crate) fn create(seed: PotSeed, key: PotKey, checkpoint_iterations: u32) -> PotCheckpoints {
19-
#[cfg(all(feature = "std", target_arch = "x86_64"))]
20-
if std::is_x86_feature_detected!("aes") {
21-
return unsafe { x86_64::create(seed.as_ref(), key.as_ref(), checkpoint_iterations) };
14+
#[cfg(target_arch = "x86_64")]
15+
{
16+
cpufeatures::new!(has_aes, "aes");
17+
if has_aes::get() {
18+
return unsafe { x86_64::create(seed.as_ref(), key.as_ref(), checkpoint_iterations) };
19+
}
2220
}
2321

2422
create_generic(seed, key, checkpoint_iterations)
@@ -48,27 +46,26 @@ fn create_generic(seed: PotSeed, key: PotKey, checkpoint_iterations: u32) -> Pot
4846
pub(crate) fn verify_sequential(
4947
seed: PotSeed,
5048
key: PotKey,
51-
checkpoints: &[PotOutput],
49+
checkpoints: &PotCheckpoints,
5250
checkpoint_iterations: u32,
5351
) -> bool {
5452
assert_eq!(checkpoint_iterations % 2, 0);
5553

5654
let key = Array::from(*key);
5755
let cipher = Aes128::new(&key);
5856

59-
let mut inputs = Vec::with_capacity(checkpoints.len());
60-
inputs.push(Array::from(*seed));
61-
for &checkpoint in checkpoints.iter().rev().skip(1).rev() {
62-
inputs.push(Array::from(*checkpoint));
63-
}
64-
let mut outputs = checkpoints
65-
.iter()
66-
.map(|&checkpoint| Array::from(*checkpoint))
67-
.collect::<Vec<_>>();
57+
let mut inputs = [[0u8; 16]; PotCheckpoints::NUM_CHECKPOINTS.get() as usize];
58+
inputs[0] = *seed;
59+
inputs[1..].copy_from_slice(PotOutput::repr_from_slice(
60+
&checkpoints[..PotCheckpoints::NUM_CHECKPOINTS.get() as usize - 1],
61+
));
62+
63+
let mut outputs = [[0u8; 16]; PotCheckpoints::NUM_CHECKPOINTS.get() as usize];
64+
outputs.copy_from_slice(PotOutput::repr_from_slice(checkpoints.as_slice()));
6865

6966
for _ in 0..checkpoint_iterations / 2 {
70-
cipher.encrypt_blocks(&mut inputs);
71-
cipher.decrypt_blocks(&mut outputs);
67+
cipher.encrypt_blocks(Array::cast_slice_from_core_mut(&mut inputs));
68+
cipher.decrypt_blocks(Array::cast_slice_from_core_mut(&mut outputs));
7269
}
7370

7471
inputs == outputs
@@ -77,6 +74,7 @@ pub(crate) fn verify_sequential(
7774
#[cfg(test)]
7875
mod tests {
7976
use super::*;
77+
use subspace_core_primitives::pot::PotOutput;
8078

8179
const SEED: [u8; 16] = [
8280
0xd6, 0x66, 0xcc, 0xd8, 0xd5, 0x93, 0xc2, 0x3d, 0xa8, 0xdb, 0x6b, 0x5b, 0x14, 0x13, 0xb1,
@@ -100,7 +98,7 @@ mod tests {
10098
fn test_create_verify() {
10199
let seed = PotSeed::from(SEED);
102100
let key = PotKey::from(KEY);
103-
let checkpoint_iterations = 100;
101+
let checkpoint_iterations = 20;
104102

105103
// Can encrypt/decrypt.
106104
let checkpoints = create(seed, key, checkpoint_iterations);
@@ -112,7 +110,7 @@ mod tests {
112110
assert!(verify_sequential(
113111
seed,
114112
key,
115-
&*checkpoints,
113+
&checkpoints,
116114
checkpoint_iterations,
117115
));
118116

@@ -122,37 +120,37 @@ mod tests {
122120
assert!(!verify_sequential(
123121
seed,
124122
key,
125-
&*checkpoints_1,
123+
&checkpoints_1,
126124
checkpoint_iterations,
127125
));
128126

129127
// Decryption with wrong number of iterations fails.
130128
assert!(!verify_sequential(
131129
seed,
132130
key,
133-
&*checkpoints,
131+
&checkpoints,
134132
checkpoint_iterations + 2,
135133
));
136134
assert!(!verify_sequential(
137135
seed,
138136
key,
139-
&*checkpoints,
137+
&checkpoints,
140138
checkpoint_iterations - 2,
141139
));
142140

143141
// Decryption with wrong seed fails.
144142
assert!(!verify_sequential(
145143
PotSeed::from(SEED_1),
146144
key,
147-
&*checkpoints,
145+
&checkpoints,
148146
checkpoint_iterations,
149147
));
150148

151149
// Decryption with wrong key fails.
152150
assert!(!verify_sequential(
153151
seed,
154152
PotKey::from(KEY_1),
155-
&*checkpoints,
153+
&checkpoints,
156154
checkpoint_iterations,
157155
));
158156
}

crates/subspace-proof-of-time/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Proof of time implementation.
22
3-
#![cfg_attr(not(feature = "std"), no_std)]
3+
#![no_std]
4+
45
mod aes;
56

67
use core::num::NonZeroU32;
7-
use subspace_core_primitives::pot::{PotCheckpoints, PotOutput, PotSeed};
8+
use subspace_core_primitives::pot::{PotCheckpoints, PotSeed};
89

910
/// Proof of time error
1011
#[derive(Debug, thiserror::Error)]
@@ -46,7 +47,7 @@ pub fn prove(seed: PotSeed, iterations: NonZeroU32) -> Result<PotCheckpoints, Po
4647
pub fn verify(
4748
seed: PotSeed,
4849
iterations: NonZeroU32,
49-
checkpoints: &[PotOutput],
50+
checkpoints: &PotCheckpoints,
5051
) -> Result<bool, PotError> {
5152
let num_checkpoints = checkpoints.len() as u32;
5253
if iterations.get() % (num_checkpoints * 2) != 0 {

0 commit comments

Comments
 (0)