-
Notifications
You must be signed in to change notification settings - Fork 228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Failing test for verify_batch #117
base: main
Are you sure you want to change the base?
Conversation
Is this maybe verifying because I pushed w3f/schnorrkel@b89b0d2 to schnorrkel that achieves the same goal with randomized signatures |
I see, your test's failure is intermittent, oops. If replace it with this code then I always get the batch verify_batch error, never the single verified error, but with numbers ranging from 0-12 or possibly higher.
Anyways In other words, you've independently found #115 for which one good explanation is https://moderncrypto.org/mail-archive/curves/2016/000836.html We're ostensibly not worried about signatures that fail single verification, but pass batch verification. We really do not want valid signatures that fail batch verification intermittently though. We run into trouble because of reduction in the multiplication It's possible Isis' recent commits indicate |
First, thanks for taking the time to file this issue, @NikVolf, and also thanks @burdges for the comments. While the deterministic batch verification does fix this specific intermittent batch verification failure (the reasons why it fixes it in this specific case, I'll get to in a moment), this isn't why I worked on that; instead those reasons are explained by @kchalkias here. Let's look at why it fails intermittently. The batch verification equation is: -\sum\limits_{i=0}^n z_i s_i (mod \ell) B + \sum\limits_{i=0}^n z_i R_i + \sum\limits_{i=0}^n \bigg( z_i \mathcal{H}(R \| A \| M) (mod \ell) \bigg) A_i = 0
Point 3 above is why the deterministic batch verification would fix it, for this specific input, however no amount of protection against a bad RNG is going to recover from a malicious input of points of small order and the scalar additive identity. That is, if we instead compile your test with The only way I can see to prevent this issue from happening would be to check that neither the |
Example code to better see what's happening here: Cargo.toml: [package]
name = "foobar"
version = "0.1.0"
authors = ["Isis Lovecruft <[email protected]>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
curve25519-dalek = { version = "2"}
rand = "*"
sha2 = "0.8" src/main.rs: use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
use curve25519_dalek::constants::EIGHT_TORSION;
use curve25519_dalek::edwards::CompressedEdwardsY;
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::IsIdentity;
use rand::thread_rng;
use sha2::Digest;
use sha2::Sha512;
fn main() {
let mut rng = thread_rng();
let nonce = Scalar::random(&mut rng);
// An example of a `z` which causes the verification to pass.
//let nonce = Scalar::from(127u8);
println!("nonce = {:?}", nonce);
let first = &(&nonce * &Scalar::zero()) * &ED25519_BASEPOINT_TABLE;
println!("first = {:?}", first.compress());
let second = CompressedEdwardsY([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]).decompress().unwrap() * &nonce;
println!("second = {:?}", second);
println!("second = {:?}", second.compress());
println!("second is identity: {}", second.is_identity());
println!("second is small order: {}", second.is_small_order());
println!("second is torsion free: {}", second.is_torsion_free());
let mut h: Sha512 = Sha512::default();
h.input([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]);
h.input([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]);
h.input(&[]);
let hram = Scalar::from_hash(h);
// I was trying to find a specific divisor/bit pattern here. The Scalar::bits()
// function in dalek is private so you'll have to fork it and make it pub if you
// want to debug this.
//print!("hram bits = ");
//for i in 0..256 {
// print!("{}", hram.bits()[i]);
//}
//println!();
let third = &(&nonce * &hram) * CompressedEdwardsY([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]).decompress().unwrap();
println!("third = {:?}", third);
println!("third = {:?}", third.compress());
println!("third is small order: {}", third.is_small_order());
println!("third is torsion free: {}", third.is_torsion_free());
let sum = &first + &second + &third;
println!("sum equals identity: {}", sum.is_identity());
if sum.is_identity() {
for i in 0..8 {
if EIGHT_TORSION[i].compress() == second.compress() {
println!{"second is E[8] index {}", i};
}
if EIGHT_TORSION[i].compress() == third.compress() {
println!{"third is E[8] index {}", i};
}
}
}
} Example output:
|
Will this be enough though? I think the only solution that makes batch verification work and makes it compatible with individual signature verification is to multiply by a cofactor in all the equations. |
I might be misremembering what we concluded when talking with Dan Boneh, but after we verified a sketch of a proof that removing one of the linearisation factors was equally secure as single-signature verification, someone had the idea to simply change the first linearisation factor to the cofactor. That is, rather than -\sum\limits_{i=0}^n z_i s_i (mod \ell) B + \sum\limits_{i=0}^n z_i R_i + \sum\limits_{i=0}^n \bigg( z_i \mathcal{H}(R \| A \| M) (mod \ell) \bigg) A_i = 0 To instead do -8 s_0 (mod \ell) B -\sum\limits_{i=1}^n z_i s_i (mod \ell) B + 8 R_0 + \sum\limits_{i=1}^n z_i R_i + 8 \mathcall{H}(R \| A \| M) (mod \ell) A_0 + \sum\limits_{i=1}^n \bigg( z_i \mathcal{H}(R \| A \| M) (mod \ell) \bigg) A_i = 0 The latter idea we then decided was bad because it allowed the first signature to have e.g. Instead, just for clarity's sake since it seems like people are paying attention to this issue, I propose we check if any for (key, signature) in public_keys.iter().zip(signatures.iter()) {
// Check if the key or signature.R multiplied by the cofactor is the additive identity and if so return an error.
if key.point.is_small_order() || signature.R.is_small_order() {
return Err();
}
} |
You'll need 256ish bit scalar multiplications everywhere else if the first delinearisation factor is 0 or 3 bits. It's faster to use 128 bit scalar multiplications everywhere. I doubt As @valerini says, adversaries who exploit this cofactor multiplication create signatures that pass derandomized batch verification, but maybe fail single verification, and worse pass-or-fail non-deterministic batch verification intermittently. Afaik you address the single verification best with some
It's interesting you made Could you just have In fact, blockchains could even upgrade from |
I think it's important to insist that in the above, Lera means "the only solution that makes batch verification work and makes it compatible with cofactored individual signature verification". For more on this specific point, see table 3 of https://eprint.iacr.org/2020/1244 |
sometimes
verify_batch
returnOk(_)
on this input, sometimesErr(_)
Not sure if it is as designed