28
src/ed25519.rs
Normal file
28
src/ed25519.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use ed25519_dalek::Signature;
|
||||
use ed25519_dalek::Signer;
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
pub struct ChallengeSigner {
|
||||
signing_key: SigningKey,
|
||||
}
|
||||
|
||||
impl ChallengeSigner {
|
||||
pub fn new() -> Self {
|
||||
let signing_key = SigningKey::generate(&mut rand::thread_rng());
|
||||
ChallengeSigner { signing_key }
|
||||
}
|
||||
|
||||
pub fn new_from_bytes(bytes: [u8; 32]) -> Self {
|
||||
let signing_key = SigningKey::from_bytes(&bytes);
|
||||
ChallengeSigner { signing_key }
|
||||
}
|
||||
|
||||
pub fn sign(&self, message: &[u8]) -> Signature {
|
||||
self.signing_key.sign(message)
|
||||
}
|
||||
|
||||
pub fn verify(&self, message: &[u8], signature: &[u8; 64]) -> bool {
|
||||
let signature_result = Signature::from_bytes(signature);
|
||||
self.signing_key.verify(message, &signature_result).is_ok()
|
||||
}
|
||||
}
|
||||
401
src/lib.rs
Normal file
401
src/lib.rs
Normal file
@@ -0,0 +1,401 @@
|
||||
pub mod sloth;
|
||||
|
||||
use base64::{prelude::BASE64_URL_SAFE, Engine};
|
||||
use num_bigint::BigUint;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_arch = "wasm32")] {
|
||||
} else {
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use rand::Rng;
|
||||
pub mod prime;
|
||||
pub mod ed25519;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
static DIFFICULTY_SCALE: f64 = 0.04;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Puzzle {
|
||||
pub ticket: u64,
|
||||
pub issued_at: u64,
|
||||
pub prime: BigUint,
|
||||
pub challenge: BigUint,
|
||||
pub difficulty: u32,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct ChallengerBuilder {
|
||||
difficulty: Option<u32>,
|
||||
algorithm: Option<crate::sloth::Sloth>,
|
||||
signer: Option<crate::ed25519::ChallengeSigner>,
|
||||
valid_time_window: Option<u64>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl ChallengerBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
difficulty: None,
|
||||
algorithm: None,
|
||||
signer: None,
|
||||
valid_time_window: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_difficulty(mut self, difficulty: u32) -> Self {
|
||||
self.difficulty = Some((difficulty as f64 * DIFFICULTY_SCALE) as u32);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_prime(mut self, prime: BigUint) -> Self {
|
||||
self.algorithm = Some(crate::sloth::Sloth::new(prime));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_sign_key(mut self, sign_key: [u8; 32]) -> Self {
|
||||
self.signer = Some(crate::ed25519::ChallengeSigner::new_from_bytes(sign_key));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_valid_time_window(mut self, valid_time_window: u64) -> Self {
|
||||
self.valid_time_window = Some(valid_time_window);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Challenger {
|
||||
let difficulty = self
|
||||
.difficulty
|
||||
.unwrap_or((1000.0 * DIFFICULTY_SCALE) as u32);
|
||||
|
||||
let algorithm = self.algorithm.unwrap_or_else(|| {
|
||||
crate::sloth::Sloth::new(crate::prime::generate_prime_mod_3_4(4096, 64))
|
||||
});
|
||||
|
||||
let signer = self
|
||||
.signer
|
||||
.unwrap_or_else(|| crate::ed25519::ChallengeSigner::new());
|
||||
|
||||
let tickets_current = Arc::new(RwLock::new(HashSet::new()));
|
||||
let tickets_previous = Arc::new(RwLock::new(HashSet::new()));
|
||||
|
||||
let valid_time_window = self.valid_time_window.unwrap_or(300);
|
||||
let last_rotation = Arc::new(RwLock::new(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
));
|
||||
|
||||
Challenger {
|
||||
difficulty,
|
||||
algorithm,
|
||||
signer,
|
||||
tickets_current,
|
||||
tickets_previous,
|
||||
valid_time_window,
|
||||
last_rotation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct Challenger {
|
||||
pub difficulty: u32,
|
||||
algorithm: crate::sloth::Sloth,
|
||||
signer: crate::ed25519::ChallengeSigner,
|
||||
tickets_current: Arc<RwLock<HashSet<u64>>>,
|
||||
tickets_previous: Arc<RwLock<HashSet<u64>>>,
|
||||
valid_time_window: u64,
|
||||
last_rotation: Arc<RwLock<u64>>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Challenger {
|
||||
pub fn set_difficulty(&mut self, difficulty: u32) {
|
||||
self.difficulty = (difficulty as f64 * DIFFICULTY_SCALE) as u32;
|
||||
}
|
||||
|
||||
pub fn issue_challenge(&mut self) -> String {
|
||||
let (_x, y, p) = self.algorithm.create(self.difficulty);
|
||||
|
||||
let puzzle = Puzzle {
|
||||
ticket: rand::thread_rng().gen(),
|
||||
issued_at: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
prime: p,
|
||||
challenge: y,
|
||||
difficulty: self.difficulty,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&puzzle).unwrap();
|
||||
let json_payload = BASE64_URL_SAFE.encode(json);
|
||||
let signature = self.signer.sign(json_payload.as_bytes());
|
||||
let signature_payload = BASE64_URL_SAFE.encode(signature.to_bytes());
|
||||
|
||||
let challenge = json_payload + "." + signature_payload.as_str();
|
||||
challenge
|
||||
}
|
||||
|
||||
pub fn verify_challenge(&mut self, challenge: &str, solution: &str) -> bool {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let need_rotation = {
|
||||
let last_rotation = self.last_rotation.read().unwrap();
|
||||
now - *last_rotation > self.valid_time_window
|
||||
};
|
||||
|
||||
if need_rotation {
|
||||
let mut last_rotation = self.last_rotation.write().unwrap();
|
||||
let mut tickets_previous = self.tickets_previous.write().unwrap();
|
||||
let mut tickets_current = self.tickets_current.write().unwrap();
|
||||
*tickets_previous = std::mem::take(&mut *tickets_current);
|
||||
*last_rotation = now;
|
||||
}
|
||||
|
||||
// Reject challenges that are too long
|
||||
if challenge.len() > 10240 || solution.len() > 1024 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split the challenge into the JSON payload and the signature
|
||||
let parts: Vec<&str> = challenge.split('.').collect();
|
||||
if parts.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let json_payload = parts[0];
|
||||
let signature_payload = parts[1];
|
||||
|
||||
// Decode the signature
|
||||
let signature_bytes: [u8; 64] = match BASE64_URL_SAFE.decode(signature_payload.as_bytes()) {
|
||||
Ok(bytes) => match bytes.try_into() {
|
||||
Ok(b) => b,
|
||||
Err(_) => return false,
|
||||
},
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
// Verify the signature on the JSON payload
|
||||
if !self
|
||||
.signer
|
||||
.verify(json_payload.as_bytes(), &signature_bytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the JSON payload
|
||||
let puzzle: Puzzle = match BASE64_URL_SAFE.decode(json_payload.as_bytes()) {
|
||||
Ok(bytes) => match serde_json::from_slice(&bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return false,
|
||||
},
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
// Reject challenges that are too old
|
||||
if puzzle.issued_at < now - self.valid_time_window {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject challenges that have already been solved
|
||||
{
|
||||
if self
|
||||
.tickets_current
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains(&puzzle.ticket)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if self
|
||||
.tickets_previous
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains(&puzzle.ticket)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode the solution
|
||||
let solution: BigUint = match BASE64_URL_SAFE.decode(solution.as_bytes()) {
|
||||
Ok(bytes) => BigUint::from_bytes_be(&bytes),
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
// Verify the solution
|
||||
let solution_result =
|
||||
self.algorithm
|
||||
.verify(&solution, &puzzle.challenge, puzzle.difficulty);
|
||||
|
||||
if solution_result {
|
||||
self.tickets_current.write().unwrap().insert(puzzle.ticket);
|
||||
}
|
||||
|
||||
solution_result
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn solve_challenge(challenge: &str) -> String {
|
||||
let parts: Vec<&str> = challenge.split('.').collect();
|
||||
if parts.len() != 2 {
|
||||
return "ERROR".to_string();
|
||||
}
|
||||
|
||||
let json_payload = parts[0];
|
||||
let puzzle: Puzzle = match BASE64_URL_SAFE.decode(json_payload.as_bytes()) {
|
||||
Ok(bytes) => match serde_json::from_slice(&bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return "ERROR".to_string(),
|
||||
},
|
||||
Err(_) => return "ERROR".to_string(),
|
||||
};
|
||||
|
||||
let y = puzzle.challenge.clone();
|
||||
let p = puzzle.prime.clone();
|
||||
let t = puzzle.difficulty;
|
||||
|
||||
let solution = crate::sloth::Sloth::decode(&y, &p, t);
|
||||
|
||||
BASE64_URL_SAFE.encode(solution.to_bytes_be())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let p = crate::prime::generate_prime_mod_3_4(4096, 64);
|
||||
println!("prime: {}", p);
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(p)
|
||||
.with_difficulty(1000)
|
||||
.build();
|
||||
let challenge = challenger.issue_challenge();
|
||||
let solution = solve_challenge(&challenge);
|
||||
println!("challenge: {}", challenge);
|
||||
println!("solution: {}", solution);
|
||||
assert!(challenger.verify_challenge(&challenge, &solution));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_incorrect() {
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(crate::prime::generate_prime_mod_3_4(2048, 64))
|
||||
.build();
|
||||
let challenge = challenger.issue_challenge();
|
||||
let mut solution = solve_challenge(&challenge);
|
||||
solution.replace_range(1..4, "abcd");
|
||||
assert_eq!(false, challenger.verify_challenge(&challenge, &solution));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_invaild_challenge() {
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(crate::prime::generate_prime_mod_3_4(2048, 64))
|
||||
.build();
|
||||
let mut challenge = challenger.issue_challenge();
|
||||
let solution = solve_challenge(&challenge);
|
||||
challenge.replace_range(1..4, "abcd");
|
||||
assert_eq!(false, challenger.verify_challenge(&challenge, &solution));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_invaild_signature() {
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(crate::prime::generate_prime_mod_3_4(2048, 64))
|
||||
.build();
|
||||
let mut challenge = challenger.issue_challenge();
|
||||
let solution = solve_challenge(&challenge);
|
||||
challenge.replace_range(challenge.len() - 5..challenge.len() - 1, "abcd");
|
||||
assert_eq!(false, challenger.verify_challenge(&challenge, &solution));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_reuse_attack() {
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(crate::prime::generate_prime_mod_3_4(2048, 64))
|
||||
.build();
|
||||
let challenge = challenger.issue_challenge();
|
||||
let solution = solve_challenge(&challenge);
|
||||
|
||||
assert!(challenger.verify_challenge(&challenge, &solution));
|
||||
assert_eq!(false, challenger.verify_challenge(&challenge, &solution));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_invaild_data() {
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(crate::prime::generate_prime_mod_3_4(2048, 64))
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
false,
|
||||
challenger.verify_challenge(&"".to_string(), &"".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
false,
|
||||
challenger.verify_challenge(&"rin".to_string(), &"cat".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_expired() {
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(crate::prime::generate_prime_mod_3_4(2048, 64))
|
||||
.with_valid_time_window(1)
|
||||
.build();
|
||||
|
||||
let challenge = challenger.issue_challenge();
|
||||
let solution = solve_challenge(&challenge);
|
||||
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
assert_eq!(false, challenger.verify_challenge(&challenge, &solution));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_tickets() {
|
||||
let mut challenger = ChallengerBuilder::new()
|
||||
.with_prime(crate::prime::generate_prime_mod_3_4(2048, 64))
|
||||
.with_valid_time_window(5)
|
||||
.build();
|
||||
|
||||
// Normal case
|
||||
let challenge = challenger.issue_challenge();
|
||||
let solution = solve_challenge(&challenge);
|
||||
|
||||
assert_eq!(true, challenger.verify_challenge(&challenge, &solution));
|
||||
|
||||
// expired case
|
||||
let challenge1 = challenger.issue_challenge();
|
||||
let solution1 = solve_challenge(&challenge1);
|
||||
|
||||
thread::sleep(std::time::Duration::from_secs(6));
|
||||
|
||||
// Normal case
|
||||
let challenge2 = challenger.issue_challenge();
|
||||
let solution2 = solve_challenge(&challenge2);
|
||||
|
||||
assert_eq!(false, challenger.verify_challenge(&challenge1, &solution1));
|
||||
assert_eq!(true, challenger.verify_challenge(&challenge2, &solution2));
|
||||
}
|
||||
}
|
||||
161
src/prime.rs
Normal file
161
src/prime.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use num_bigint::{BigUint, RandBigInt, ToBigUint};
|
||||
use num_integer::Integer;
|
||||
use num_traits::One;
|
||||
use rand::thread_rng;
|
||||
|
||||
/// Miller–Rabin primality test
|
||||
/// https://en.wikipedia.org/wiki/Miller-Rabin_primality_test
|
||||
///
|
||||
/// `k` is the number of iterations (more iterations → higher confidence).
|
||||
/// This is a basic implementation for odd candidate `n` greater than 2.
|
||||
pub fn is_probably_prime(n: &BigUint, k: u32) -> bool {
|
||||
// Handle small numbers.
|
||||
if n == &2u32.to_biguint().unwrap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if n < &2u32.to_biguint().unwrap() || n.is_even() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write n − 1 as d * 2^r.
|
||||
let one: BigUint = One::one();
|
||||
let two: BigUint = 2u32.to_biguint().unwrap();
|
||||
|
||||
let n_minus_one = n - &one;
|
||||
|
||||
let mut d = n_minus_one.clone();
|
||||
let mut r = 0u32;
|
||||
|
||||
while d.is_even() {
|
||||
d /= &two;
|
||||
r += 1;
|
||||
}
|
||||
|
||||
// Try k random witnesses.
|
||||
let mut rng = thread_rng();
|
||||
|
||||
'witness_loop: for _ in 0..k {
|
||||
// Choose a random a in [2, n-2]
|
||||
let a = rng.gen_biguint_range(&two, &(n - &two));
|
||||
|
||||
// Compute x = a^d mod n.
|
||||
let mut x = a.modpow(&d, n);
|
||||
|
||||
if x == one || x == n_minus_one {
|
||||
continue 'witness_loop;
|
||||
}
|
||||
|
||||
// Repeat squaring x up to r − 1 times.
|
||||
for _ in 0..(r - 1) {
|
||||
x = x.modpow(&two, n);
|
||||
if x == n_minus_one {
|
||||
continue 'witness_loop;
|
||||
}
|
||||
}
|
||||
|
||||
// If we never hit n − 1, then composite.
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn generate_prime_mod_3_4(bits: u64, rounds: u32) -> BigUint {
|
||||
let mut rng = thread_rng();
|
||||
let four: BigUint = 4u32.to_biguint().unwrap();
|
||||
let three: BigUint = 3u32.to_biguint().unwrap();
|
||||
|
||||
loop {
|
||||
// Generate a random number of the required bit-length.
|
||||
let mut candidate = rng.gen_biguint(bits);
|
||||
|
||||
// Force the most significant bit to ensure it is exactly `bits` long.
|
||||
candidate.set_bit(bits - 1, true);
|
||||
// Force the number to be odd.
|
||||
candidate.set_bit(0, true);
|
||||
|
||||
// Adjust candidate so that candidate mod 4 == 3.
|
||||
// Compute candidate mod 4.
|
||||
let rem = &candidate % &four;
|
||||
if rem != three {
|
||||
// Calculate the adjustment needed.
|
||||
// We want candidate + adj ≡ 3 (mod 4); that is, adj ≡ (3 - rem) mod 4.
|
||||
let adj = if three >= rem {
|
||||
&three - &rem
|
||||
} else {
|
||||
&three + &four - &rem
|
||||
};
|
||||
candidate += &adj;
|
||||
}
|
||||
|
||||
// Now candidate mod 4 should equal 3.
|
||||
if &candidate % &four != three {
|
||||
continue; // Should not happen, but be safe.
|
||||
}
|
||||
|
||||
// Use Miller–Rabin to test candidate for primality.
|
||||
if is_probably_prime(&candidate, rounds) {
|
||||
return candidate;
|
||||
}
|
||||
// Otherwise, try another candidate.
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// This test has chance of false positives since it's probabilistic.
|
||||
#[test]
|
||||
fn test_is_probably_prime() {
|
||||
let primes: Vec<BigUint> = vec![
|
||||
BigUint::parse_bytes(b"723646214863847842402314246121044767400617866176733021174245260448070467161519753555151305391831172396032179266088879736498934532967238875067731186605319314486487094813782345277515046149035823394700558031365128080643117834402421935144013956523482034192169360458395261772557972018417296402072764848759", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"268263962333296278340388301081833650583348564229009436402694247537863120457689419730666587700766950987474095807568632415133217325374788947918148879191904084190506645642271822238922848768059332086841470078498514866531550241226838886983034780850983546266212727522552823301120544245546076442247019621281", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"780316979179005788838703471892068793371544768939561076378203820641657870400087138649016384860083841124919558376637164999621109684339044217326208775683279745886968384731662626620658025388083028243454834224644661932974516055783125538555017192574377520720286008648938568231979020364719643484647961902129", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"783671025198992682112959634964787905698619719557738373194519536366273453103042115144772610507228390203926825435595621395808116267173935555322692156198469029023864294375800526944184191931093378457858882431220722817775243587676744651835166948761514278051800658105050374249135721906202152414030775826309", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"114288406837499406547552732741383061052894863572677735626102568465969919170114402741547085384743856605779595326952640244199273564193341729111945397295523754661407891360178372372116312851760319552575377963248694758420962490016988707765874984334711407905393818994588867816690596598910287994599226773623", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"961901548037893583081258840295866034390773310176366253775445667810599851064250586050396339468859430600645234941487066774638210060293941746865942337333205982508422664447189855574952850359167644652778348230933876780699478432665987194022576502478048244264535150875567551113136844280086912348903874971289", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"810327961783413779923342902853291047828538610393840061747638982695517616363172610276265437838324082720309171557216227906072585908285037423898327771759304451581073313718250825722376959089099901152629294365285556214142316224671295775214712025501417340129162614998356447407556284614940830419343154596069", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"454714144122862528239309790149196885616593324828166685793258814905365447620370501511767118061263049961557700030750456903650018330037078841617668619030424125469578524711042124583419218861229437961029104448364686795365893249168551162671874789888134646816513322705015243576954880991642886515757494254667", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"299912922453745379330945382700853905095713797966780365239274055194104040679024227632579691012245229549449540626544013218573948479074373395273033811581117143614653378585775286148244890322917470335843727405772749436431799738228530579087880286816355000236580795379259714642327676881458540815945300666161", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"376003947134918788492667049006858112525261477602024892881243407277975729638079808355736777378039431492585839429794604176393826090578267002411236819553297345047777740428504027180799354733010246832276383653403857249078308266896127980484004668688670243017355407377733246174535348005452263544141849401979", 10).unwrap(),
|
||||
];
|
||||
|
||||
for p in primes.iter() {
|
||||
assert!(is_probably_prime(&p, 64));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_probably_prime_composites() {
|
||||
let composites: Vec<BigUint> = vec![
|
||||
BigUint::parse_bytes(b"376003947134918788492667049006858112525261477602024892881243407277975729638079808355736777378039431492585839429794604176393826090578267002411236819553297345047777740428504027180799354733010246832276383653403857249078308266896127980484004668688670243017355407377733246174535348005452263544141849401978", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"215231561558920190548208026347283855893349614579652886715109041246408707968244796209282350597895810733095104924634951652047270639587011501367107805226413278625593440341692931130648759932592989139786464960914139157114834353651728654113090571685005692273343539839816029944416477359630133856900278127196", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"749740332669263695852915686823796627478248903975029332069160620969447428491611565267586339403720492925006681992807328998947128762023955011291116754751950431300094251835880417511251321687004478207880237612242291350451356839420408200814283441229483559638270099621526259859618448556951589488935694922070", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"169634000834057905687114185586149242948139045677318813563962611831640519981755545740448510103217026033366710697221717847963076219297087916994382041061663714750161811176003440851268811309999453614154110754445690358783609141283608528146586702857821814537749897378979649737926839924019514134727091160596", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"391405423376058365603029493927540848246012397355482118997842261901662598117428494623814607612916906669639217808344280832395767938093281898200653708872133101732068607931282089810121182927184303240249371585726676226781755240078640338182784311458749444597839572385973551700960543208703821367720798778072", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"145039293606441299096899867755014610341218018203667264841882003871966393556065187657468909971071812669502147831045878456015615749981859567162502647235340765709384621383450245978156289943326007863506929089750917534169817946092363165262671439744821303778355013113872010222371955198429114197736184765704", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"338068116382585626410927100767745326752329150652235801367160858233547248828235927245043642851496668587705981602011646708907509708706737409743516100542296115834435744682149323258036975223060910642424686044835405231352594163800211063010578142263828931218684092895126045712869105445627601057623777552090", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"733953124218193165395301551993038545406364629775221343783594613066305176245028773006556151249847554247355736690261867825939967080139988834452933842378528382325603700037881199289040333697671258970839307337751547799788219379733886335171402446580393040976829094147103849993165402411253055490791297710398", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"913203980385282094763740857489413119990150673887971726908661546534253494037072205386715229605079865032590651982202933180796243044488005878787438030787026922448019409247593693512864665427511952738234654431289764435314410753974607679676812065736404760888000826732781882360969951775729150509866946286836", 10).unwrap(),
|
||||
BigUint::parse_bytes(b"232672468411571864128377684111610045835673835338188777956578101157986193934981310713021354012943502884801943521144084387464428198189142123559463576210566925881741632803965311463508844160091019108613782710387285819179522210515150555890570342556044250014755588708967402313183475521240406597634471243814", 10).unwrap(),
|
||||
];
|
||||
|
||||
for c in composites.iter() {
|
||||
assert!(!is_probably_prime(&c, 64));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_prime_mod_3_4() {
|
||||
let bits = 2048;
|
||||
let rounds = 64;
|
||||
let prime = generate_prime_mod_3_4(bits, rounds);
|
||||
assert!(is_probably_prime(&prime, rounds));
|
||||
assert_eq!(prime.bits(), bits);
|
||||
assert_eq!(
|
||||
&prime % 4u32.to_biguint().unwrap(),
|
||||
3u32.to_biguint().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
111
src/sloth.rs
Normal file
111
src/sloth.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use num_bigint::{BigUint, ToBigUint};
|
||||
use num_traits::One;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use rand::thread_rng;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use num_bigint::RandBigInt;
|
||||
|
||||
/// Sloth is a verifiable delay function (VDF) that is designed to enforce
|
||||
/// a predetermined amount of sequential work.
|
||||
|
||||
pub struct Sloth {
|
||||
/// The modulus `p` is a large prime number.
|
||||
pub p: BigUint,
|
||||
}
|
||||
|
||||
impl Sloth {
|
||||
/// Create a new Sloth VDF with the given modulus `p` and iteration count `t`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new(p: BigUint) -> Self {
|
||||
Sloth { p }
|
||||
}
|
||||
|
||||
/// Encode a message `x` using the Sloth VDF.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn encode(&self, x: &BigUint, t: u32) -> BigUint {
|
||||
let mut y = x.clone();
|
||||
|
||||
// Repeatedly square `x` T times mod `p`.
|
||||
for _ in 0..t {
|
||||
// y = y^2 mod p
|
||||
y = (&y * &y) % &self.p;
|
||||
}
|
||||
y
|
||||
}
|
||||
|
||||
/// Decode a message `y` using the Sloth VDF.
|
||||
pub fn decode(y: &BigUint, p: &BigUint, t: u32) -> BigUint {
|
||||
let mut x = y.clone();
|
||||
for _ in 0..t {
|
||||
x = Sloth::mod_sqrt(&x, p);
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
/// Create a new tuple `(x, y)` where `x` is a random secret number
|
||||
/// and `y` is the encoded challenge value.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn create(&self, t: u32) -> (BigUint, BigUint, BigUint) {
|
||||
let x = thread_rng().gen_biguint_range(&BigUint::one(), &self.p);
|
||||
let y = self.encode(&x, t);
|
||||
(x, y, self.p.clone())
|
||||
}
|
||||
|
||||
/// Verify that the encoded value `y` was computed from the secret `x`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn verify(&self, x: &BigUint, y: &BigUint, t: u32) -> bool {
|
||||
let check = self.encode(x, t);
|
||||
&check == y
|
||||
}
|
||||
|
||||
/// Compute a modular square root when p ≡ 3 mod 4:
|
||||
/// sqrt(a) mod p = a^((p+1)/4) mod p
|
||||
/// We'll pick the "lower root" consistently to ensure uniqueness.
|
||||
fn mod_sqrt(a: &BigUint, p: &BigUint) -> BigUint {
|
||||
// exponent = (p + 1) / 4
|
||||
let exp = (p + BigUint::one()) / 4_u32.to_biguint().unwrap();
|
||||
let root = a.modpow(&exp, p);
|
||||
|
||||
// A prime p ≡ 3 mod 4 has exactly two roots: `r` and `p-r`.
|
||||
// We'll choose the smaller one to ensure uniqueness.
|
||||
let p_minus_root = (p - &root) % p;
|
||||
if root <= p_minus_root {
|
||||
root
|
||||
} else {
|
||||
p_minus_root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_sloth() {
|
||||
let p = crate::prime::generate_prime_mod_3_4(2048, 64);
|
||||
let t = 100;
|
||||
let sloth = Sloth::new(p);
|
||||
|
||||
let (x, y, p) = sloth.create(t);
|
||||
assert!(sloth.verify(&x, &y, t));
|
||||
|
||||
let decoded = Sloth::decode(&y, &p, t);
|
||||
assert!(sloth.verify(&decoded, &y, t));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sloth_incorrect() {
|
||||
let p = crate::prime::generate_prime_mod_3_4(2048, 64);
|
||||
let t = 100;
|
||||
let sloth = Sloth::new(p);
|
||||
|
||||
let (x, y, _p) = sloth.create(t);
|
||||
assert!(sloth.verify(&x, &y, t));
|
||||
|
||||
let decoded = 1024_u32.to_biguint().unwrap();
|
||||
assert!(!sloth.verify(&decoded, &y, t));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user