Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ postcard = { version = "1.1.1", features = ["use-std"] }
zstd = "0.13.3"
bytes = "1.10.1"
proptest = "1.6.0"
zerocopy = "0.8.25"


# Noir lang: make sure it matches installed version `noirup -C v1.0.0-beta.3`
Expand Down
2 changes: 2 additions & 0 deletions noir-r1cs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ postcard.workspace = true
zstd.workspace = true
bytes.workspace = true
bytemuck.workspace = true
skyscraper = { path = "../skyscraper" }
zerocopy.workspace = true

# Ark
rand08 = { package = "rand", version = "0.8" }
Expand Down
126 changes: 8 additions & 118 deletions noir-r1cs/src/skyscraper/skyscraper_pow.rs
Original file line number Diff line number Diff line change
@@ -1,141 +1,31 @@
use {
crate::{
skyscraper::skyscraper::{bigint_from_bytes_le, compress},
utils::uint_to_field,
},
ruint::{aliases::U256, uint},
skyscraper::pow::{solve, verify},
spongefish_pow::PowStrategy,
whir::crypto::fields::Field256,
zerocopy::transmute,
};

/// Skyscraper proof of work
#[derive(Clone, Copy)]
pub struct SkyscraperPoW {
challenge: Field256,
threshold: Field256,
challenge: [u64; 4],
bits: f64,
}

const D0: Field256 = uint_to_field(uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495617_U256
));
const D1: Field256 = uint_to_field(uint!(
10944121435919637611123202872628637544274182200208017171849102093287904247808_U256
));
const D2: Field256 = uint_to_field(uint!(
5472060717959818805561601436314318772137091100104008585924551046643952123904_U256
));
const D3: Field256 = uint_to_field(uint!(
2736030358979909402780800718157159386068545550052004292962275523321976061952_U256
));
const D4: Field256 = uint_to_field(uint!(
1368015179489954701390400359078579693034272775026002146481137761660988030976_U256
));
const D5: Field256 = uint_to_field(uint!(
684007589744977350695200179539289846517136387513001073240568880830494015488_U256
));
const D6: Field256 = uint_to_field(uint!(
342003794872488675347600089769644923258568193756500536620284440415247007744_U256
));
const D7: Field256 = uint_to_field(uint!(
171001897436244337673800044884822461629284096878250268310142220207623503872_U256
));
const D8: Field256 = uint_to_field(uint!(
85500948718122168836900022442411230814642048439125134155071110103811751936_U256
));
const D9: Field256 = uint_to_field(uint!(
42750474359061084418450011221205615407321024219562567077535555051905875968_U256
));
const D10: Field256 = uint_to_field(uint!(
21375237179530542209225005610602807703660512109781283538767777525952937984_U256
));
const D11: Field256 = uint_to_field(uint!(
10687618589765271104612502805301403851830256054890641769383888762976468992_U256
));
const D12: Field256 = uint_to_field(uint!(
5343809294882635552306251402650701925915128027445320884691944381488234496_U256
));
const D13: Field256 = uint_to_field(uint!(
2671904647441317776153125701325350962957564013722660442345972190744117248_U256
));
const D14: Field256 = uint_to_field(uint!(
1335952323720658888076562850662675481478782006861330221172986095372058624_U256
));
const D15: Field256 = uint_to_field(uint!(
667976161860329444038281425331337740739391003430665110586493047686029312_U256
));
const D16: Field256 = uint_to_field(uint!(
333988080930164722019140712665668870369695501715332555293246523843014656_U256
));
const D17: Field256 = uint_to_field(uint!(
166994040465082361009570356332834435184847750857666277646623261921507328_U256
));
const D18: Field256 = uint_to_field(uint!(
83497020232541180504785178166417217592423875428833138823311630960753664_U256
));
const D19: Field256 = uint_to_field(uint!(
41748510116270590252392589083208608796211937714416569411655815480376832_U256
));
const D20: Field256 = uint_to_field(uint!(
20874255058135295126196294541604304398105968857208284705827907740188416_U256
));
const D21: Field256 = uint_to_field(uint!(
10437127529067647563098147270802152199052984428604142352913953870094208_U256
));
const D22: Field256 = uint_to_field(uint!(
5218563764533823781549073635401076099526492214302071176456976935047104_U256
));
const D23: Field256 = uint_to_field(uint!(
2609281882266911890774536817700538049763246107151035588228488467523552_U256
));
const D24: Field256 = uint_to_field(uint!(
1304640941133455945387268408850269024881623053575517794114244233761776_U256
));
const D25: Field256 = uint_to_field(uint!(
652320470566727972693634204425134512440811526787758897057122116880888_U256
));
const D26: Field256 = uint_to_field(uint!(
326160235283363986346817102212567256220405763393879448528561058440444_U256
));
const D27: Field256 = uint_to_field(uint!(
163080117641681993173408551106283628110202881696939724264280529220222_U256
));

const DIFFICULTY_ARRAY: [Field256; 28] = [
D0, D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D14, D15, D16, D17, D18, D19, D20,
D21, D22, D23, D24, D25, D26, D27,
];

impl PowStrategy for SkyscraperPoW {
fn new(challenge: [u8; 32], bits: f64) -> Self {
assert!((0.0..60.0).contains(&bits), "bits must be smaller than 60");
let threshold = bits.ceil() as usize;

Self {
challenge: Field256::new(bigint_from_bytes_le(&challenge)),
threshold: DIFFICULTY_ARRAY[threshold],
challenge: transmute!(challenge),
bits,
}
}

fn check(&mut self, nonce: u64) -> bool {
let res = compress(self.challenge, uint_to_field(U256::from(nonce)));
res < self.threshold
verify(self.challenge, self.bits, nonce)
}

fn solve(&mut self) -> Option<u64> {
// TODO: Parallel solve
(0u64..)
.step_by(1)
.find_map(|nonce| self.check_single(nonce))
}
}

impl SkyscraperPoW {
fn check_single(&mut self, nonce: u64) -> Option<u64> {
let res = compress(self.challenge, uint_to_field(U256::from(nonce)));
if res < self.threshold {
return Some(nonce);
}
None
Some(solve(self.challenge, self.bits))
}
}

Expand Down
3 changes: 2 additions & 1 deletion skyscraper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ block-multiplier = { path = "../block-multiplier" }
fp-rounding = { path = "../fp-rounding" }
ark-ff.workspace = true
ark-bn254.workspace = true
zerocopy = "0.8.25"
zerocopy.workspace = true
seq-macro = "0.3.6"
proptest.workspace = true
rayon.workspace = true

[dev-dependencies]
rand.workspace = true
Expand Down
41 changes: 41 additions & 0 deletions skyscraper/benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,47 @@ mod reduce {
}
}

#[divan::bench_group]
mod pow {
use {super::*, skyscraper::pow::solve};

#[divan::bench]
fn bits_05(bencher: Bencher) {
bencher
.with_inputs(|| rng().random())
.bench_local_values(|challenge| solve(challenge, 05.0))
}

#[divan::bench]
fn bits_10(bencher: Bencher) {
bencher
.with_inputs(|| rng().random())
.bench_local_values(|challenge| solve(challenge, 10.0))
}

#[divan::bench]
fn bits_15(bencher: Bencher) {
bencher
.with_inputs(|| rng().random())
.bench_local_values(|challenge| solve(challenge, 15.0))
}

#[divan::bench]
fn bits_20(bencher: Bencher) {
bencher
.with_inputs(|| rng().random())
.bench_local_values(|challenge| solve(challenge, 20.0))
}

#[divan::bench]
#[ignore]
fn bits_25(bencher: Bencher) {
bencher
.with_inputs(|| rng().random())
.bench_local_values(|challenge| solve(challenge, 25.0))
}
}

#[divan::bench_group]
mod compress_many {
use super::*;
Expand Down
10 changes: 10 additions & 0 deletions skyscraper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@ pub mod bar;
pub mod block3;
pub mod block4;
pub mod constants;
pub mod pow;
pub mod reduce;
pub mod reference;
pub mod simple;
pub mod v1;

/// The least common multiple of the implementation widths.
///
/// Doing this many compressions in parallel will make optimal use of resources
/// in all implementations.
///
/// Note you might want to pick a multiple as block size to amortize the setting
/// of rounding mode.
pub const WIDTH_LCM: usize = 12;

pub type CompressManyFn = fn(&[u8], &mut [u8]);

// TODO: Some autotune method that does a small benchmark on target hardware and
Expand Down
147 changes: 147 additions & 0 deletions skyscraper/src/pow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use {
crate::{arithmetic::less_than, simple::compress, WIDTH_LCM},
ark_ff::Zero,
core::{
array,
sync::atomic::{AtomicU64, Ordering},
},
rayon,
zerocopy::IntoBytes as _,
};

const PROVER_BIAS: f64 = 0.01;

/// Returns a threshold for a given security target in bits.
///
/// The probability that a uniform random element from the field is less than
/// the threshold is at least 2 ^ -difficulty. i.e.:
///
/// |{x:F | x < threshold}| / |F| < 2^-difficulty
pub fn threshold(difficulty: f64) -> [u64; 4] {
assert!(
(0.0..80.0).contains(&difficulty),
"Difficulty must be in the range [0, 80)"
);
let modulus = (crate::constants::MODULUS[1][3] as f64) * 2.0f64.powi(192);
let prob = (-difficulty).exp2();
f64_to_u256(prob * modulus)
}

pub fn verify(challenge: [u64; 4], difficulty: f64, nonce: u64) -> bool {
difficulty.is_zero() || less_than(compress(challenge, [nonce, 0, 0, 0]), threshold(difficulty))
}

/// Multi-threaded proof of work solver.
///
/// It will add a slight bias to the difficulty to make sure the prover
/// threshold is higher than the verifier threshold and there are not rounding
/// issues affecting completeness.
pub fn solve(challenge: [u64; 4], difficulty: f64) -> u64 {
const WIDTH: usize = WIDTH_LCM * 10;
if difficulty.is_zero() {
return 0;
}
let threshold = threshold(difficulty + PROVER_BIAS);
let compress_many = crate::block4::compress_many; // TODO: autotune
let best = AtomicU64::new(u64::MAX);
rayon::broadcast(|ctx| {
let mut input: [[[u64; 4]; 2]; WIDTH] = array::from_fn(|_| [challenge, [0; 4]]);
let mut hashes = [[0_u64; 4]; WIDTH];

// Find the thread specific subset of nonces
for nonce in (0..)
.step_by(WIDTH)
.skip(ctx.index())
.step_by(ctx.num_threads())
{
// Stop if another thread found a better solution
if nonce > best.load(Ordering::Acquire) {
return;
}
for i in 0..WIDTH {
input[i][1][0] = nonce + i as u64;
}
compress_many(input.as_bytes(), hashes.as_mut_bytes());
for i in 0..WIDTH {
if less_than(hashes[i], threshold) {
best.fetch_min(nonce + i as u64, Ordering::AcqRel);
return;
}
}
}
});
let nonce = best.load(Ordering::Acquire);
debug_assert!(verify(challenge, difficulty, nonce));
nonce
}

/// Returns sign, exponent and significand of an `f64`
///
/// The significand has the implicit leading one added for normal floats.
fn f64_parts(f: f64) -> (bool, i16, u64) {
let bits = f.to_bits();
let sign = (bits >> 63) != 0;
let exp_bits = ((bits >> 52) & 0x7ff) as i16;
let frac = bits & ((1 << 52) - 1);
if exp_bits == 0 {
// Subnormals and zero (no implicit 1)
(sign, -1022, frac)
} else {
// Normal: add the implicit 1 at bit 52
(sign, exp_bits - 1023, frac + (1 << 52))
}
}

/// Convert a float to the nearest u256, clamping to zero and MAX.
fn f64_to_u256(f: f64) -> [u64; 4] {
let (sign, exp, significand) = f64_parts(f);
if sign {
return [0; 4];
}
if exp > 256 {
return [u64::MAX; 4];
}
let mut result = [0; 4];
let shift = exp - 52;
if shift < 0 {
result[0] = f.round() as u64;
} else {
let shift = shift as u32;
let (limb, shift) = ((shift / 64) as usize, shift % 64);
result[limb] = significand << shift;
if shift != 0 && limb < 3 {
result[limb + 1] = significand >> (64 - shift);
}
}
result
}

#[cfg(test)]
mod tests {
use {super::*, core::f64};

#[test]
fn test_f64_to_u256() {
assert_eq!(f64_to_u256(0.0), [0; 4]);
assert_eq!(f64_to_u256(f64::MIN), [0; 4]);
assert_eq!(f64_to_u256(0.49), [0; 4]);
assert_eq!(f64_to_u256(0.50), [1, 0, 0, 0]);
assert_eq!(f64_to_u256(1.0), [1, 0, 0, 0]);
assert_eq!(f64_to_u256(2.0_f64.powi(128)), [0, 0, 1, 0]);
assert_eq!(f64_to_u256(f64::INFINITY), [u64::MAX; 4]);
assert_eq!(f64_to_u256(-42.0), [0; 4]);
assert_eq!(
f64_to_u256(f64::from_bits(0x7ff0000000000001)),
[u64::MAX; 4]
); // NaN
}

#[test]
fn test_solve_verify() {
for difficulty in [0.0_f64, f64::consts::PI] {
let challenge = [u64::MAX; 4];
let nonce = solve(challenge, difficulty);
assert!(verify(challenge, difficulty, nonce));
}
}
}
Loading