From c6f25e99292793e3ab6e5fcac7e416ba706b776c Mon Sep 17 00:00:00 2001 From: John Tromp Date: Mon, 6 Dec 2021 13:22:44 +0100 Subject: [PATCH] Spdupverifypow (#3672) * speed up cuckatoo verify * speed up cuckaroo verify * speed up cuckarood verify * speed up cuckaroom verify * speed up cuckarooz verify --- core/src/pow/cuckaroo.rs | 48 +++++++++++++++++++++++++++++------- core/src/pow/cuckarood.rs | 43 ++++++++++++++++++++++++-------- core/src/pow/cuckaroom.rs | 40 +++++++++++++++++++----------- core/src/pow/cuckarooz.rs | 35 +++++++++++++++++++++----- core/src/pow/cuckatoo.rs | 52 ++++++++++++++++++++++++++++++--------- 5 files changed, 167 insertions(+), 51 deletions(-) diff --git a/core/src/pow/cuckaroo.rs b/core/src/pow/cuckaroo.rs index ee7f06484..ae89763ac 100644 --- a/core/src/pow/cuckaroo.rs +++ b/core/src/pow/cuckaroo.rs @@ -62,15 +62,21 @@ impl PoWContext for CuckarooContext { } fn verify(&self, proof: &Proof) -> Result<(), Error> { - if proof.proof_size() != global::proofsize() { + let size = proof.proof_size(); + if size != global::proofsize() { return Err(ErrorKind::Verification("wrong cycle length".to_owned()).into()); } let nonces = &proof.nonces; - let mut uvs = vec![0u64; 2 * proof.proof_size()]; + let mut uvs = vec![0u64; 2 * size]; let mut xor0: u64 = 0; let mut xor1: u64 = 0; + let mask = u64::MAX >> size.leading_zeros(); // round size up to 2-power - 1 + // the next three arrays form a linked list of nodes with matching bits 6..1 + let mut headu = vec![2 * size; 1 + mask as usize]; + let mut headv = vec![2 * size; 1 + mask as usize]; + let mut prev = vec![0usize; 2 * size]; - for n in 0..proof.proof_size() { + for n in 0..size { if nonces[n] > self.params.edge_mask { return Err(ErrorKind::Verification("edge too big".to_owned()).into()); } @@ -79,14 +85,36 @@ impl PoWContext for CuckarooContext { } // 21 is standard siphash rotation constant let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 21, false); - uvs[2 * n] = edge & self.params.node_mask; - xor0 ^= uvs[2 * n]; - uvs[2 * n + 1] = (edge >> 32) & self.params.node_mask; - xor1 ^= uvs[2 * n + 1]; + let u = edge & self.params.node_mask; + let v = (edge >> 32) & self.params.node_mask; + + uvs[2 * n] = u; + let ubits = (u & mask) as usize; + prev[2 * n] = headu[ubits]; + headu[ubits] = 2 * n; + + uvs[2 * n + 1] = v; + let vbits = (v & mask) as usize; + prev[2 * n + 1] = headv[vbits]; + headv[vbits] = 2 * n + 1; + + xor0 ^= u; + xor1 ^= v; } if xor0 | xor1 != 0 { return Err(ErrorKind::Verification("endpoints don't match up".to_owned()).into()); } + // make prev lists circular + for n in 0..size { + if prev[2 * n] == 2 * size { + let ubits = (uvs[2 * n] & mask) as usize; + prev[2 * n] = headu[ubits]; + } + if prev[2 * n + 1] == 2 * size { + let vbits = (uvs[2 * n + 1] & mask) as usize; + prev[2 * n + 1] = headv[vbits]; + } + } let mut n = 0; let mut i = 0; let mut j; @@ -95,7 +123,7 @@ impl PoWContext for CuckarooContext { j = i; let mut k = j; loop { - k = (k + 2) % (2 * self.params.proof_size); + k = prev[k]; if k == i { break; } @@ -116,7 +144,7 @@ impl PoWContext for CuckarooContext { break; } } - if n == self.params.proof_size { + if n == size { Ok(()) } else { Err(ErrorKind::Verification("cycle too short".to_owned()).into()) @@ -164,7 +192,9 @@ mod test { let mut ctx = new_impl(19, 42); ctx.params.siphash_keys = V1_19_HASH; assert!(ctx.verify(&Proof::new(V1_19_SOL.to_vec())).is_ok()); + assert!(ctx.verify(&Proof::new(V2_19_SOL.to_vec())).is_err()); ctx.params.siphash_keys = V2_19_HASH.clone(); + assert!(ctx.verify(&Proof::new(V1_19_SOL.to_vec())).is_err()); assert!(ctx.verify(&Proof::new(V2_19_SOL.to_vec())).is_ok()); assert!(ctx.verify(&Proof::zero(42)).is_err()); } diff --git a/core/src/pow/cuckarood.rs b/core/src/pow/cuckarood.rs index 16c9128cf..867308a0a 100644 --- a/core/src/pow/cuckarood.rs +++ b/core/src/pow/cuckarood.rs @@ -56,18 +56,24 @@ impl PoWContext for CuckaroodContext { } fn verify(&self, proof: &Proof) -> Result<(), Error> { - if proof.proof_size() != global::proofsize() { + let size = proof.proof_size(); + if size != global::proofsize() { return Err(ErrorKind::Verification("wrong cycle length".to_owned()).into()); } let nonces = &proof.nonces; - let mut uvs = vec![0u64; 2 * proof.proof_size()]; + let mut uvs = vec![0u64; 2 * size]; let mut ndir = vec![0usize; 2]; let mut xor0: u64 = 0; let mut xor1: u64 = 0; + let mask = u64::MAX >> size.leading_zeros(); // round size up to 2-power - 1 + // the next two arrays form a linked list of nodes with matching bits 4..0|dir + let mut headu = vec![2 * size; 1 + mask as usize]; + let mut headv = vec![2 * size; 1 + mask as usize]; + let mut prev = vec![0usize; 2 * size]; - for n in 0..proof.proof_size() { + for n in 0..size { let dir = (nonces[n] & 1) as usize; - if ndir[dir] >= proof.proof_size() / 2 { + if ndir[dir] >= size / 2 { return Err(ErrorKind::Verification("edges not balanced".to_owned()).into()); } if nonces[n] > self.params.edge_mask { @@ -79,10 +85,21 @@ impl PoWContext for CuckaroodContext { // cuckarood uses a non-standard siphash rotation constant 25 as anti-ASIC tweak let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 25, false); let idx = 4 * ndir[dir] + 2 * dir; - uvs[idx] = edge & self.params.node_mask; - xor0 ^= uvs[idx]; - uvs[idx + 1] = (edge >> 32) & self.params.node_mask; - xor1 ^= uvs[idx + 1]; + let u = edge & self.params.node_mask; + let v = (edge >> 32) & self.params.node_mask; + + uvs[idx] = u; + let ubits = ((u << 1 | dir as u64) & mask) as usize; + prev[idx] = headu[ubits]; + headu[ubits] = idx; + + uvs[idx + 1] = v; + let vbits = ((v << 1 | dir as u64) & mask) as usize; + prev[idx + 1] = headv[vbits]; + headv[vbits] = idx + 1; + + xor0 ^= u; + xor1 ^= v; ndir[dir] += 1; } if xor0 | xor1 != 0 { @@ -94,7 +111,12 @@ impl PoWContext for CuckaroodContext { loop { // follow cycle j = i; - for k in (((i % 4) ^ 2)..(2 * self.params.proof_size)).step_by(4) { + let mut k = if i & 1 == 0 { + headu[((uvs[i] << 1 | 1) & mask) as usize] + } else { + headv[((uvs[i] << 1 | 0) & mask) as usize] + }; + while k != 2 * size { if uvs[k] == uvs[i] { // find reverse edge endpoint identical to one at i if j != i { @@ -102,6 +124,7 @@ impl PoWContext for CuckaroodContext { } j = k; } + k = prev[k]; } if j == i { return Err(ErrorKind::Verification("cycle dead ends".to_owned()).into()); @@ -112,7 +135,7 @@ impl PoWContext for CuckaroodContext { break; } } - if n == self.params.proof_size { + if n == size { Ok(()) } else { Err(ErrorKind::Verification("cycle too short".to_owned()).into()) diff --git a/core/src/pow/cuckaroom.rs b/core/src/pow/cuckaroom.rs index c8814a0af..1ca95d983 100644 --- a/core/src/pow/cuckaroom.rs +++ b/core/src/pow/cuckaroom.rs @@ -55,17 +55,21 @@ impl PoWContext for CuckaroomContext { } fn verify(&self, proof: &Proof) -> Result<(), Error> { - let proofsize = proof.proof_size(); - if proofsize != global::proofsize() { + let size = proof.proof_size(); + if size != global::proofsize() { return Err(ErrorKind::Verification("wrong cycle length".to_owned()).into()); } let nonces = &proof.nonces; - let mut from = vec![0u64; proofsize]; - let mut to = vec![0u64; proofsize]; + let mut from = vec![0u64; size]; + let mut to = vec![0u64; size]; let mut xor_from: u64 = 0; let mut xor_to: u64 = 0; + let mask = u64::MAX >> size.leading_zeros(); // round size up to 2-power - 1 + // the next two arrays form a linked list of nodes with matching bits 6..1 + let mut head = vec![size; 1 + mask as usize]; + let mut prev = vec![0usize; size]; - for n in 0..proofsize { + for n in 0..size { if nonces[n] > self.params.edge_mask { return Err(ErrorKind::Verification("edge too big".to_owned()).into()); } @@ -74,15 +78,20 @@ impl PoWContext for CuckaroomContext { } // 21 is standard siphash rotation constant let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 21, true); - from[n] = edge & self.params.node_mask; + let u = edge & self.params.node_mask; + let v = (edge >> 32) & self.params.node_mask; + from[n] = u; + let bits = (u & mask) as usize; + prev[n] = head[bits]; + head[bits] = n; + to[n] = v; xor_from ^= from[n]; - to[n] = (edge >> 32) & self.params.node_mask; xor_to ^= to[n]; } if xor_from != xor_to { return Err(ErrorKind::Verification("endpoints don't match up".to_owned()).into()); } - let mut visited = vec![false; proofsize]; + let mut visited = vec![false; size]; let mut n = 0; let mut i = 0; loop { @@ -91,21 +100,24 @@ impl PoWContext for CuckaroomContext { return Err(ErrorKind::Verification("branch in cycle".to_owned()).into()); } visited[i] = true; - let mut nexti = 0; - while from[nexti] != to[i] { - nexti += 1; - if nexti == proofsize { + let mut k = head[(to[i] & mask) as usize]; + loop { + if k == size { return Err(ErrorKind::Verification("cycle dead ends".to_owned()).into()); } + if from[k] == to[i] { + break; + } + k = prev[k]; } - i = nexti; + i = k; n += 1; if i == 0 { // must cycle back to start or find branch break; } } - if n == proofsize { + if n == size { Ok(()) } else { Err(ErrorKind::Verification("cycle too short".to_owned()).into()) diff --git a/core/src/pow/cuckarooz.rs b/core/src/pow/cuckarooz.rs index 414adc8a5..26f0e2f67 100644 --- a/core/src/pow/cuckarooz.rs +++ b/core/src/pow/cuckarooz.rs @@ -56,14 +56,19 @@ impl PoWContext for CuckaroozContext { } fn verify(&self, proof: &Proof) -> Result<(), Error> { - if proof.proof_size() != global::proofsize() { + let size = proof.proof_size(); + if size != global::proofsize() { return Err(ErrorKind::Verification("wrong cycle length".to_owned()).into()); } let nonces = &proof.nonces; - let mut uvs = vec![0u64; 2 * proof.proof_size()]; + let mut uvs = vec![0u64; 2 * size]; let mut xoruv: u64 = 0; + let mask = u64::MAX >> size.leading_zeros(); // round size up to 2-power - 1 + // the next two arrays form a linked list of nodes with matching bits 6..1 + let mut head = vec![2 * size; 1 + mask as usize]; + let mut prev = vec![0usize; 2 * size]; - for n in 0..proof.proof_size() { + for n in 0..size { if nonces[n] > self.params.edge_mask { return Err(ErrorKind::Verification("edge too big".to_owned()).into()); } @@ -72,13 +77,31 @@ impl PoWContext for CuckaroozContext { } // 21 is standard siphash rotation constant let edge: u64 = siphash_block(&self.params.siphash_keys, nonces[n], 21, true); - uvs[2 * n] = edge & self.params.node_mask; - uvs[2 * n + 1] = (edge >> 32) & self.params.node_mask; + let u = edge & self.params.node_mask; + let v = (edge >> 32) & self.params.node_mask; + + uvs[2 * n] = u; + let bits = (u & mask) as usize; + prev[2 * n] = head[bits]; + head[bits] = 2 * n; + + uvs[2 * n + 1] = v; + let bits = (v & mask) as usize; + prev[2 * n + 1] = head[bits]; + head[bits] = 2 * n + 1; + xoruv ^= uvs[2 * n] ^ uvs[2 * n + 1]; } if xoruv != 0 { return Err(ErrorKind::Verification("endpoints don't match up".to_owned()).into()); } + // make prev lists circular + for n in 0..(2 * size) { + if prev[n] == 2 * size { + let bits = (uvs[n] & mask) as usize; + prev[n] = head[bits]; + } + } let mut n = 0; let mut i = 0; let mut j; @@ -87,7 +110,7 @@ impl PoWContext for CuckaroozContext { j = i; let mut k = j; loop { - k = (k + 1) % (2 * self.params.proof_size); + k = prev[k]; if k == i { break; } diff --git a/core/src/pow/cuckatoo.rs b/core/src/pow/cuckatoo.rs index 2c8e3e82b..05c0f71fc 100644 --- a/core/src/pow/cuckatoo.rs +++ b/core/src/pow/cuckatoo.rs @@ -255,29 +255,57 @@ impl CuckatooContext { /// Verify that given edges are ascending and form a cycle in a header-generated /// graph pub fn verify_impl(&self, proof: &Proof) -> Result<(), Error> { - if proof.proof_size() != global::proofsize() { + let size = proof.proof_size(); + if size != global::proofsize() { return Err(ErrorKind::Verification("wrong cycle length".to_owned()).into()); } let nonces = &proof.nonces; - let mut uvs = vec![0u64; 2 * proof.proof_size()]; - let mut xor0: u64 = (self.params.proof_size as u64 / 2) & 1; + let mut uvs = vec![0u64; 2 * size]; + let mask = u64::MAX >> size.leading_zeros(); // round size up to 2-power - 1 + let mut xor0: u64 = (size as u64 / 2) & 1; let mut xor1: u64 = xor0; + // the next two arrays form a linked list of nodes with matching bits 6..1 + let mut headu = vec![2 * size; 1 + mask as usize]; + let mut headv = vec![2 * size; 1 + mask as usize]; + let mut prev = vec![0usize; 2 * size]; - for n in 0..proof.proof_size() { + for n in 0..size { if nonces[n] > self.params.edge_mask { return Err(ErrorKind::Verification("edge too big".to_owned()).into()); } if n > 0 && nonces[n] <= nonces[n - 1] { return Err(ErrorKind::Verification("edges not ascending".to_owned()).into()); } - uvs[2 * n] = self.params.sipnode(nonces[n], 0)?; - uvs[2 * n + 1] = self.params.sipnode(nonces[n], 1)?; - xor0 ^= uvs[2 * n]; - xor1 ^= uvs[2 * n + 1]; + let u = self.params.sipnode(nonces[n], 0)?; + let v = self.params.sipnode(nonces[n], 1)?; + + uvs[2 * n] = u; + let ubits = (u >> 1 & mask) as usize; // larger shifts work too, up to edgebits-6 + prev[2 * n] = headu[ubits]; + headu[ubits] = 2 * n; + + uvs[2 * n + 1] = v; + let vbits = (v >> 1 & mask) as usize; + prev[2 * n + 1] = headv[vbits]; + headv[vbits] = 2 * n + 1; + + xor0 ^= u; + xor1 ^= v; } if xor0 | xor1 != 0 { return Err(ErrorKind::Verification("endpoints don't match up".to_owned()).into()); } + // make prev lists circular + for n in 0..size { + if prev[2 * n] == 2 * size { + let ubits = (uvs[2 * n] >> 1 & mask) as usize; + prev[2 * n] = headu[ubits]; + } + if prev[2 * n + 1] == 2 * size { + let vbits = (uvs[2 * n + 1] >> 1 & mask) as usize; + prev[2 * n + 1] = headv[vbits]; + } + } let mut n = 0; let mut i = 0; let mut j; @@ -286,7 +314,7 @@ impl CuckatooContext { j = i; let mut k = j; loop { - k = (k + 2) % (2 * self.params.proof_size); + k = prev[k]; if k == i { break; } @@ -307,7 +335,7 @@ impl CuckatooContext { break; } } - if n == self.params.proof_size { + if n == size { Ok(()) } else { Err(ErrorKind::Verification("cycle too short".to_owned()).into()) @@ -457,13 +485,13 @@ mod test { let mut header = [0u8; 80]; header[0] = 1u8; ctx.set_header_nonce(header.to_vec(), Some(20), false)?; - assert!(!ctx.verify(&Proof::new(V1_29.to_vec())).is_ok()); + assert!(ctx.verify(&Proof::new(V1_29.to_vec())).is_err()); header[0] = 0u8; ctx.set_header_nonce(header.to_vec(), Some(20), false)?; assert!(ctx.verify(&Proof::new(V1_29.to_vec())).is_ok()); let mut bad_proof = V1_29; bad_proof[0] = 0x48a9e1; - assert!(!ctx.verify(&Proof::new(bad_proof.to_vec())).is_ok()); + assert!(ctx.verify(&Proof::new(bad_proof.to_vec())).is_err()); Ok(()) }