Spdupverifypow (#3672)

* speed up cuckatoo verify

* speed up cuckaroo verify

* speed up cuckarood verify

* speed up cuckaroom verify

* speed up cuckarooz verify
This commit is contained in:
John Tromp 2021-12-06 13:22:44 +01:00 committed by GitHub
parent 2f5cfbe4eb
commit c6f25e9929
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 167 additions and 51 deletions

View file

@ -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());
}

View file

@ -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())

View file

@ -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())

View file

@ -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;
}

View file

@ -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(())
}