From 1909c1323acf56ca71c526a3f4ebb4eadac0a864 Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 2 Oct 2025 16:37:37 +0200 Subject: [PATCH 1/2] Added draft for test vectors for PoP and sig. --- .gitignore | 2 + .../sig-pop-test-vectors/Cargo.toml | 15 + .../sig-pop-test-vectors/README.md | 135 +++++++ .../sig-pop-test-vectors/src/main.rs | 352 ++++++++++++++++++ 4 files changed, 504 insertions(+) create mode 100644 cardano-crypto-tests/sig-pop-test-vectors/Cargo.toml create mode 100644 cardano-crypto-tests/sig-pop-test-vectors/README.md create mode 100644 cardano-crypto-tests/sig-pop-test-vectors/src/main.rs diff --git a/.gitignore b/.gitignore index c96c3829d..e91ccac81 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ stack-local.yaml /result-* cardano-crypto-class/output cardano-crypto-praos/output +cardano-crypto-tests/sig-pop-test-vector/target +cardano-crypto-tests/sig-pop-test-vector/Cargo.lock # ghcid **/.ghcid diff --git a/cardano-crypto-tests/sig-pop-test-vectors/Cargo.toml b/cardano-crypto-tests/sig-pop-test-vectors/Cargo.toml new file mode 100644 index 000000000..aaf5ca0cd --- /dev/null +++ b/cardano-crypto-tests/sig-pop-test-vectors/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sig-pop-test-vectors" +version = "0.1.0" +edition = "2021" + +[dependencies] +bls12_381 = { version = "0.8.0", features = ["default", "experimental"] } +rand_chacha = "0.3.1" +ff = "0.13.0" +group = "0.13.0" +hex = "0.4.3" +blst = "0.3.10" +sha2 = "0.9" +mithril = "1.0.0" +mithril-stm = "0.5.0" diff --git a/cardano-crypto-tests/sig-pop-test-vectors/README.md b/cardano-crypto-tests/sig-pop-test-vectors/README.md new file mode 100644 index 000000000..977fb81d8 --- /dev/null +++ b/cardano-crypto-tests/sig-pop-test-vectors/README.md @@ -0,0 +1,135 @@ +## Test vectors for signatures and Proof of possession + +TODO: Modify the README + +This is a rust script to generate test vectors for the following: +- Using [bls12_381](https://github.com/zkcrypto/bls12_381) + - Pairing properties + - Elliptic curve operations + - Deserialization/decompression +- Using [blst](https://github.com/supranational/blst) bindings from [bls12_381](https://github.com/zkcrypto/bls12_381) BLS signature with `aug` and `dst`. + The results are in hex encoding and stored under the folder `test_vectors`. + +### 1- Test vectors for pairing properties +The properties to be tested: +- `e([a]P, Q) = e(P, [a]Q)` +- `e([a]P, [b]Q) = e([b]P, [a]Q)` +- `e([a]P, [b]Q) = e([a * b]P, Q)` +- `e([a]P, Q) * e([b]P, Q) = e([a + b]P, Q)` +- `e([a]P, [b]Q) = e(P, [a * b]Q)` +- `e(P, [a]Q) * e(P, [b]Q) = e(P, [a + b]Q)` + +The values used to generate test vectors: +``` +a = 0x0e51216fa879b2ce727b596d065dd9b7fd8a84d94ffacf9ca30ad114304272d3 // scalar +b = 0x437c2d7d852637c2ef23645a5abcbb308d6150bfcccbf3a8fdbc9daaa91496ef // scalar +aplusb = 0x51cd4eed2d9fea91619ebdc7611a94e88aebd5991cc6c345a0c76ebed95709c2 // scalar +atimesb = 0x2d70bbc706812d56e805ae67934b3275ff67f304a76ea9b3c96d31b9c0d607ba // scalar +``` + +Order of the values printed on `pairing_test_vectors`: +- `P` +- `[a]P` +- `[b]P` +- `[a + b]P` +- `[a * b]P` +- `Q` +- `[a]Q` +- `[b]Q` +- `[a + b]Q` +- `[a * b]Q` + + + +### 2- Test vectors for elliptic curve operations +Operations to be tested: +- Addition +- Subtraction +- Scalar multiplication +- Negation + +The scalar used in scalar multiplication: +``` +0x40df499974f62e2f268cd5096b0d952073900054122ffce0a27c9d96932891a5 +``` + +Order of the values printed on `ec_operations_test_vectors`: + +- `G1_P` - random point on `G_1` +- `G1_Q` - random point on `G_1` +- `G1_ADD = G1_P + G1_Q` +- `G1_SUB = G1_P - G1_Q` +- `G1_MULL = [scalar]G1_Q` +- `G1_NEG = -G1_P` +- `G2_P` - random point on `G_2` +- `G2_Q` - random point on `G_2` +- `G2_ADD = G2_P + G2_Q` +- `G2_SUB = G2_P - G2_Q` +- `G2_MULL = [scalar]G2_Q` +- `G2_NEG = -G2_P` + + +### 3- Test vectors for deserialization/decompression +- Point not in curve should fail deserialisation +- Point not in group should fail deserialisation +- Point not in curve should fail decompression +- Point not in group should fail decompression + +Order of the values printed on `serde_test_vectors`: +- `G1_uncomp_not_on_curve` +- `G1_comp_not_on_curve` +- `G1_comp_not_on_group` +- `G1_uncomp_not_on_group` +- `G2_uncomp_not_on_curve` +- `G2_comp_not_on_curve` +- `G2_comp_not_on_group` +- `G2_uncomp_not_on_group` + +### 4- BLS Signature +Test vectors for BLS signature, using `blst` bindings. + +The explicit usage of `aug` is not allowed in Cardano-base bindings. Therefore, before verification, they need to be appended into the message by following [hash-to-curve](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve#name-expand_message) spec. + +`DST` and `msg` values used to generate test vectors: + +```rust +let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; +let msg = b"blst is such a blast"; +let aug = b"Random value for test aug"; +``` +Order of the values printed on the files `bls_sig_aug_test_vectors` (BLS signature with `aug`): + +- `sig` +- `pk` + +To validate these test vectors, one needs to proceed as follows: +``` +let hashed_msg = HashToG1Curve(aug || msg, dst); + +assert!(pairing(sig, G2Generator) == pairing(hashed_msg, pk)) +``` + +### 5- Hash to curve with large DST +The plutus bindings bound the DST to be at most 255 bytes, following the standard draft specification. If +applications require a domain separation tag that is longer than 255 bytes, they should convert it to a smaller +DST following the instructions of the standard draft (see [section 5.3.3](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve#name-using-dsts-longer-than-255-)). + +We create test vectors to ensure that hashing to a curve by first hashing a large DST with SHA256, and then +hashing to the curve works as expected. The test vectors of this file are stored in `h2c_large_dst` with the +following order: + +- 'msg' +- 'large_dst' +- Compressed G1 'output' + +To validate these test vectors, one needs to proceed as follows: + +``` +let hashed_dst = Sha256(b"H2C-OVERSIZE-DST-" | large_dst); + +let hashed_output = HashToG1Curve(msg, hashed_dst); + +let expected_output = G1FromCompressed(output); + +assert!(expected_oputput == hashed_output); +``` diff --git a/cardano-crypto-tests/sig-pop-test-vectors/src/main.rs b/cardano-crypto-tests/sig-pop-test-vectors/src/main.rs new file mode 100644 index 000000000..1db9a3f6a --- /dev/null +++ b/cardano-crypto-tests/sig-pop-test-vectors/src/main.rs @@ -0,0 +1,352 @@ +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +#![allow(non_snake_case)] + +use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve}; +use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; +use blst::{blst_p1, blst_scalar, blst_sk_to_pk_in_g1, min_sig::*}; +use blst::min_sig::SecretKey as BlstSk; +use blst::BLST_ERROR; +use ff::Field; +use group::{Curve, Group}; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use std::fs::File; +use std::io::prelude::*; +use sha2::{Digest, Sha256}; + + +fn generate_sk_and_pk(mut rng: R) -> std::io::Result<()> { + // Save the ikm, sk and pk + let mut ikm = [0u8; 32]; + rng.fill_bytes(&mut ikm); + + let sk = BlstSk::key_gen(&ikm, &[]) + .expect("Error occurs when the length of ikm < 32. This will not happen here."); + + + let pk = sk.sk_to_pk(); + + println!("sk: {:?}, pk: {:?}", sk, pk); + let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; + let aug = b"Random value for test aug. "; + let msg = b"blst is such a blast"; + + let sig = sk.sign(msg, dst, aug); + + // Should we keep the verify signature here? + let verif = sig.verify(false, msg, dst, aug, &pk, false); + + // Proof of Possession + const POP: &[u8] = &[80, 111, 80]; + let k1 = sk.sign(POP, &[], &[]); + let k2 = unsafe { + let sk_scalar = std::mem::transmute::<&BlstSk, &blst_scalar>(&sk); + let mut out = blst_p1::default(); + blst_sk_to_pk_in_g1(&mut out, sk_scalar); + out + }; + let pop = (k1, k2); + + // Verify PoP + let _ = pk.validate().unwrap(); + + // let result = verify_pairing(&pk, &pop); + + + Ok(()) + +} + + +fn pairing_properties(mut rng: R) -> std::io::Result<()> { + let P = G1Projective::random(&mut rng); + let Q = G2Projective::random(&mut rng); + + let a = Scalar::random(&mut rng); + let b = Scalar::random(&mut rng); + let aplusb = a + b; + let atimesb = a * b; + + let aP = a * P; + let bQ = b * Q; + let bP = b * P; + let aQ = a * Q; + + let aplusbP = aplusb * P; + let atimesbP = atimesb * P; + let aplusbQ = aplusb * Q; + let atimesbQ = atimesb * Q; + + write_hex_to_file( + "././test_vectors/pairing_test_vectors", + &[ + [P, aP, bP, aplusbP, atimesbP].map(|a| hex::encode(G1Affine::from(a).to_compressed())), + [Q, aQ, bQ, aplusbQ, atimesbQ].map(|a| hex::encode(G2Affine::from(a).to_compressed())), + ] + .concat(), + ) +} + +fn ec_operations(mut rng: R) -> std::io::Result<()> { + let scalar = Scalar::random(&mut rng); + + let G1_P = G1Projective::random(&mut rng); + let G1_Q = G1Projective::random(&mut rng); + let G1_ADD = G1_P + G1_Q; + let G1_SUB = G1_P - G1_Q; + let G1_MUL = scalar * G1_Q; + let G1_NEG = -G1_P; + + let G2_P = G2Projective::random(&mut rng); + let G2_Q = G2Projective::random(&mut rng); + let G2_ADD = G2_P + G2_Q; + let G2_SUB = G2_P - G2_Q; + let G2_MUL = scalar * G2_Q; + let G2_NEG = -G2_P; + + write_hex_to_file( + "././test_vectors/ec_operations_test_vectors", + &[ + [G1_P, G1_Q, G1_ADD, G1_SUB, G1_MUL, G1_NEG] + .map(|a| hex::encode(G1Affine::from(a).to_compressed())), + [G2_P, G2_Q, G2_ADD, G2_SUB, G2_MUL, G2_NEG] + .map(|a| hex::encode(G2Affine::from(a).to_compressed())), + ] + .concat(), + ) +} + +fn serde(mut rng: R) -> std::io::Result<()> { + // vector to store the hex strings of invalid points + let mut hex_strings = Vec::new(); + + //---- G1---- + // Uncompressed not on curve + let mut uncompressed_bytes = [0u8; 96]; + loop { + rng.fill_bytes(&mut uncompressed_bytes); + // We set the flags for the bytes + uncompressed_bytes[0] &= 0b00011111; // Uncompressed point, not at infinity + let G1_try_out_curve = G1Affine::from_uncompressed_unchecked(&uncompressed_bytes); + if G1_try_out_curve.is_some().unwrap_u8() == 1 && G1_try_out_curve.unwrap().is_on_curve().unwrap_u8() == 0 { + hex_strings.push(hex::encode(uncompressed_bytes)); + break; + } + } + + // Compressed not on curve + let mut compressed_bytes = [0u8; 48]; + loop { + rng.fill_bytes(&mut compressed_bytes); + // We set the flags for the bytes + compressed_bytes[0] |= 0b10000000; + compressed_bytes[0] &= 0b10001111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be on curve) + // We unset the 4th bit to make sure that the x-coordinate is canonical + if G1Affine::from_compressed_unchecked(&compressed_bytes).is_none().unwrap_u8() == 1 { + hex_strings.push(hex::encode(compressed_bytes)); + break; + } + } + + // Compressed not in group + loop { + rng.fill_bytes(&mut compressed_bytes); + // We set the flags for the bytes + compressed_bytes[0] |= 0b10000000; + compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) + let G1_try_out_group = G1Affine::from_compressed_unchecked(&compressed_bytes); + if G1_try_out_group.is_some().unwrap_u8() == 1 + && G1_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 + { + hex_strings.push(hex::encode(compressed_bytes)); + break; + } + } + + // Uncompressed not in group + loop { + rng.fill_bytes(&mut compressed_bytes); + // We set the flags for the bytes + compressed_bytes[0] |= 0b10000000; + compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) + let G1_try_out_group = G1Affine::from_compressed_unchecked(&compressed_bytes); + if G1_try_out_group.is_some().unwrap_u8() == 1 + && G1_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 + { + hex_strings.push(hex::encode(G1_try_out_group.unwrap().to_uncompressed())); + break; + } + } + //----------------------------------------------------------- + + //---- G2---- + // Uncompressed not on curve + let mut uncompressed_bytes = [0u8; 192]; + loop { + rng.fill_bytes(&mut uncompressed_bytes); + // We set the flags for the bytes + uncompressed_bytes[0] &= 0b00011111; // Uncompressed point, not at infinity + let G2_try_out_curve = G2Affine::from_uncompressed_unchecked(&uncompressed_bytes); + if G2_try_out_curve.is_some().unwrap_u8() == 1 && G2_try_out_curve.unwrap().is_on_curve().unwrap_u8() == 0 { + hex_strings.push(hex::encode(uncompressed_bytes)); + break; + } + } + + // Compressed not on curve + let mut compressed_bytes = [0u8; 96]; + loop { + rng.fill_bytes(&mut compressed_bytes); + // We set the flags for the bytes + compressed_bytes[0] |= 0b10000000; + compressed_bytes[0] &= 0b10001111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in the curve) + // We unset the fourth bit to make sure that the first `Fp` of the x coordinate is canonical + compressed_bytes[48] &= 0b00001111; // We unset the fourth bit of the 48th byte to make sure that the second `Fp` of the x coordinate is canonical + + if G2Affine::from_compressed_unchecked(&compressed_bytes).is_none().unwrap_u8() == 1 { + hex_strings.push(hex::encode(compressed_bytes)); + break; + } + } + + // Compressed not in group + loop { + rng.fill_bytes(&mut compressed_bytes); + // We set the flags for the bytes + compressed_bytes[0] |= 0b10000000; + compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) + let G2_try_out_group = G2Affine::from_compressed_unchecked(&compressed_bytes); + if G2_try_out_group.is_some().unwrap_u8() == 1 + && G2_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 + { + hex_strings.push(hex::encode(compressed_bytes)); + break; + } + } + + // Uncompressed not in group + loop { + rng.fill_bytes(&mut compressed_bytes); + // We set the flags for the bytes + compressed_bytes[0] |= 0b10000000; + compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) + let G2_try_out_group = G2Affine::from_compressed_unchecked(&compressed_bytes); + if G2_try_out_group.is_some().unwrap_u8() == 1 + && G2_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 + { + hex_strings.push(hex::encode(G2_try_out_group.unwrap().to_uncompressed())); + break; + } + } + + //----------------------------------------------------------- + + write_hex_to_file("././test_vectors/serde_test_vectors", &hex_strings) +} + +fn bls_sig_with_dst_aug(mut rng: R) -> std::io::Result<()> { + let mut ikm = [0u8; 32]; + rng.fill_bytes(&mut ikm); + + let sk = Scalar::random(rng); + let pk = sk * G2Projective::generator(); + + let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; + let aug = b"Random value for test aug. "; + let msg = b"blst is such a blast"; + let mut concat_msg_aug = Vec::new(); + concat_msg_aug.extend_from_slice(aug); + concat_msg_aug.extend_from_slice(msg); + let hashed_msg = >>::hash_to_curve( + concat_msg_aug, + dst, + ); + + let sig = sk * hashed_msg; + + let blst_sig = Signature::from_bytes(&sig.to_affine().to_compressed()) + .expect("Invalid conversion from zkcrypto to blst"); + let blst_pk = PublicKey::from_bytes(&pk.to_affine().to_compressed()) + .expect("Invalid conversion from zkcrypto to blst"); + let err = blst_sig.verify(true, msg, dst, aug, &blst_pk, true); + assert_eq!(err, BLST_ERROR::BLST_SUCCESS); + + let sig_hex = hex::encode(sig.to_affine().to_compressed()); + let pk_hex = hex::encode(pk.to_affine().to_compressed()); + let mut file = File::create("././test_vectors/bls_sig_aug_test_vectors")?; + file.write_all(sig_hex.as_ref())?; + file.write_all(b"\n")?; + file.write_all(pk_hex.as_ref())?; + file.write_all(b"\n")?; + + Ok(()) +} + +fn h2c_large_dst(rng: &mut R) -> std::io::Result<()> { + let msg = b"Testing large dst."; + let mut large_dst = [0u8; 300]; + rng.fill_bytes(&mut large_dst); + + let hash_output = >>::hash_to_curve( + msg, + &large_dst, + ); + + // Given that the DST is larger than 255 bytes, it will first be hashed. Here we test that we can perform that action + // manually. + // Sanity check + let hashed_dst = Sha256::new().chain(b"H2C-OVERSIZE-DST-").chain(&large_dst).finalize(); + + let manually_hashed_output = >>::hash_to_curve( + msg, + &hashed_dst, + ); + + assert_eq!(hash_output, manually_hashed_output); + + // Sanity check with blst lib + use blst::{blst_hash_to_g1, blst_p1, blst_p1_compress}; + + let mut out = blst_p1::default(); + unsafe { blst_hash_to_g1(&mut out, msg.as_ptr(), msg.len(), hashed_dst.as_ptr(), hashed_dst.len(), hashed_dst.as_ptr(), 0) }; + + let mut bytes = [0u8; 48]; + unsafe { blst_p1_compress(bytes.as_mut_ptr(), &out) } + + assert_eq!(bytes, hash_output.to_affine().to_compressed()); + + let msg_hex = hex::encode(msg); + let large_dst_hex = hex::encode(large_dst); + let hash_output_hex = hex::encode(hash_output.to_affine().to_compressed()); + + let mut file = File::create("././test_vectors/h2c_large_dst")?; + file.write_all(msg_hex.as_ref())?; + file.write_all(b"\n")?; + file.write_all(large_dst_hex.as_ref())?; + file.write_all(b"\n")?; + file.write_all(hash_output_hex.as_ref())?; + file.write_all(b"\n")?; + + Ok(()) +} + +fn write_hex_to_file(file_name: &str, hex_strings: &[String]) -> std::io::Result<()> { + let mut file = File::create(file_name)?; + + for string in hex_strings { + file.write_all(string.as_ref())?; + file.write_all(b"\n")?; + } + Ok(()) +} + +fn main() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + // pairing_properties(&mut rng).expect("Failed to create test vectors!"); + // ec_operations(&mut rng).expect("Failed to create test vectors!"); + // serde(&mut rng).expect("Failed to create test vectors!"); + // bls_sig_with_dst_aug(&mut rng).expect("Failed to create test vectors!"); + // h2c_large_dst(&mut rng).expect("Failed to create large dst test vectors!"); + generate_sk_and_pk(&mut rng).expect("Failed to create large dst test vectors!"); +} From 5289da2bccf57a17c00a4395e90f2bdbc736dd6b Mon Sep 17 00:00:00 2001 From: Dam Date: Thu, 2 Oct 2025 17:08:47 +0200 Subject: [PATCH 2/2] Cleaned main to generate sig-PoP test vectors. --- .../sig-pop-test-vectors/src/main.rs | 304 +----------------- 1 file changed, 14 insertions(+), 290 deletions(-) diff --git a/cardano-crypto-tests/sig-pop-test-vectors/src/main.rs b/cardano-crypto-tests/sig-pop-test-vectors/src/main.rs index 1db9a3f6a..edbca8d4d 100644 --- a/cardano-crypto-tests/sig-pop-test-vectors/src/main.rs +++ b/cardano-crypto-tests/sig-pop-test-vectors/src/main.rs @@ -2,22 +2,16 @@ #![doc = include_str!("../README.md")] #![allow(non_snake_case)] -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve}; -use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; -use blst::{blst_p1, blst_scalar, blst_sk_to_pk_in_g1, min_sig::*}; +use blst::{blst_p1, blst_scalar, blst_sk_to_pk_in_g1}; use blst::min_sig::SecretKey as BlstSk; -use blst::BLST_ERROR; -use ff::Field; -use group::{Curve, Group}; use rand_chacha::rand_core::{RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; use std::fs::File; use std::io::prelude::*; -use sha2::{Digest, Sha256}; + fn generate_sk_and_pk(mut rng: R) -> std::io::Result<()> { - // Save the ikm, sk and pk let mut ikm = [0u8; 32]; rng.fill_bytes(&mut ikm); @@ -27,7 +21,6 @@ fn generate_sk_and_pk(mut rng: R) -> std::io::Result<()> { let pk = sk.sk_to_pk(); - println!("sk: {:?}, pk: {:?}", sk, pk); let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; let aug = b"Random value for test aug. "; let msg = b"blst is such a blast"; @@ -35,7 +28,7 @@ fn generate_sk_and_pk(mut rng: R) -> std::io::Result<()> { let sig = sk.sign(msg, dst, aug); // Should we keep the verify signature here? - let verif = sig.verify(false, msg, dst, aug, &pk, false); + let _verif = sig.verify(false, msg, dst, aug, &pk, false); // Proof of Possession const POP: &[u8] = &[80, 111, 80]; @@ -46,12 +39,20 @@ fn generate_sk_and_pk(mut rng: R) -> std::io::Result<()> { blst_sk_to_pk_in_g1(&mut out, sk_scalar); out }; - let pop = (k1, k2); + let _pop = (k1, k2); // Verify PoP - let _ = pk.validate().unwrap(); + // let _ = pk.validate().unwrap(); - // let result = verify_pairing(&pk, &pop); + // Write values to file to create test vectors + // I believe we need to save the following values: + // - ikm + // - sk + // - pk + // - sig + // - k1 + // - k2 + // - What else? Ok(()) @@ -59,278 +60,6 @@ fn generate_sk_and_pk(mut rng: R) -> std::io::Result<()> { } -fn pairing_properties(mut rng: R) -> std::io::Result<()> { - let P = G1Projective::random(&mut rng); - let Q = G2Projective::random(&mut rng); - - let a = Scalar::random(&mut rng); - let b = Scalar::random(&mut rng); - let aplusb = a + b; - let atimesb = a * b; - - let aP = a * P; - let bQ = b * Q; - let bP = b * P; - let aQ = a * Q; - - let aplusbP = aplusb * P; - let atimesbP = atimesb * P; - let aplusbQ = aplusb * Q; - let atimesbQ = atimesb * Q; - - write_hex_to_file( - "././test_vectors/pairing_test_vectors", - &[ - [P, aP, bP, aplusbP, atimesbP].map(|a| hex::encode(G1Affine::from(a).to_compressed())), - [Q, aQ, bQ, aplusbQ, atimesbQ].map(|a| hex::encode(G2Affine::from(a).to_compressed())), - ] - .concat(), - ) -} - -fn ec_operations(mut rng: R) -> std::io::Result<()> { - let scalar = Scalar::random(&mut rng); - - let G1_P = G1Projective::random(&mut rng); - let G1_Q = G1Projective::random(&mut rng); - let G1_ADD = G1_P + G1_Q; - let G1_SUB = G1_P - G1_Q; - let G1_MUL = scalar * G1_Q; - let G1_NEG = -G1_P; - - let G2_P = G2Projective::random(&mut rng); - let G2_Q = G2Projective::random(&mut rng); - let G2_ADD = G2_P + G2_Q; - let G2_SUB = G2_P - G2_Q; - let G2_MUL = scalar * G2_Q; - let G2_NEG = -G2_P; - - write_hex_to_file( - "././test_vectors/ec_operations_test_vectors", - &[ - [G1_P, G1_Q, G1_ADD, G1_SUB, G1_MUL, G1_NEG] - .map(|a| hex::encode(G1Affine::from(a).to_compressed())), - [G2_P, G2_Q, G2_ADD, G2_SUB, G2_MUL, G2_NEG] - .map(|a| hex::encode(G2Affine::from(a).to_compressed())), - ] - .concat(), - ) -} - -fn serde(mut rng: R) -> std::io::Result<()> { - // vector to store the hex strings of invalid points - let mut hex_strings = Vec::new(); - - //---- G1---- - // Uncompressed not on curve - let mut uncompressed_bytes = [0u8; 96]; - loop { - rng.fill_bytes(&mut uncompressed_bytes); - // We set the flags for the bytes - uncompressed_bytes[0] &= 0b00011111; // Uncompressed point, not at infinity - let G1_try_out_curve = G1Affine::from_uncompressed_unchecked(&uncompressed_bytes); - if G1_try_out_curve.is_some().unwrap_u8() == 1 && G1_try_out_curve.unwrap().is_on_curve().unwrap_u8() == 0 { - hex_strings.push(hex::encode(uncompressed_bytes)); - break; - } - } - - // Compressed not on curve - let mut compressed_bytes = [0u8; 48]; - loop { - rng.fill_bytes(&mut compressed_bytes); - // We set the flags for the bytes - compressed_bytes[0] |= 0b10000000; - compressed_bytes[0] &= 0b10001111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be on curve) - // We unset the 4th bit to make sure that the x-coordinate is canonical - if G1Affine::from_compressed_unchecked(&compressed_bytes).is_none().unwrap_u8() == 1 { - hex_strings.push(hex::encode(compressed_bytes)); - break; - } - } - - // Compressed not in group - loop { - rng.fill_bytes(&mut compressed_bytes); - // We set the flags for the bytes - compressed_bytes[0] |= 0b10000000; - compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) - let G1_try_out_group = G1Affine::from_compressed_unchecked(&compressed_bytes); - if G1_try_out_group.is_some().unwrap_u8() == 1 - && G1_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 - { - hex_strings.push(hex::encode(compressed_bytes)); - break; - } - } - - // Uncompressed not in group - loop { - rng.fill_bytes(&mut compressed_bytes); - // We set the flags for the bytes - compressed_bytes[0] |= 0b10000000; - compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) - let G1_try_out_group = G1Affine::from_compressed_unchecked(&compressed_bytes); - if G1_try_out_group.is_some().unwrap_u8() == 1 - && G1_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 - { - hex_strings.push(hex::encode(G1_try_out_group.unwrap().to_uncompressed())); - break; - } - } - //----------------------------------------------------------- - - //---- G2---- - // Uncompressed not on curve - let mut uncompressed_bytes = [0u8; 192]; - loop { - rng.fill_bytes(&mut uncompressed_bytes); - // We set the flags for the bytes - uncompressed_bytes[0] &= 0b00011111; // Uncompressed point, not at infinity - let G2_try_out_curve = G2Affine::from_uncompressed_unchecked(&uncompressed_bytes); - if G2_try_out_curve.is_some().unwrap_u8() == 1 && G2_try_out_curve.unwrap().is_on_curve().unwrap_u8() == 0 { - hex_strings.push(hex::encode(uncompressed_bytes)); - break; - } - } - - // Compressed not on curve - let mut compressed_bytes = [0u8; 96]; - loop { - rng.fill_bytes(&mut compressed_bytes); - // We set the flags for the bytes - compressed_bytes[0] |= 0b10000000; - compressed_bytes[0] &= 0b10001111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in the curve) - // We unset the fourth bit to make sure that the first `Fp` of the x coordinate is canonical - compressed_bytes[48] &= 0b00001111; // We unset the fourth bit of the 48th byte to make sure that the second `Fp` of the x coordinate is canonical - - if G2Affine::from_compressed_unchecked(&compressed_bytes).is_none().unwrap_u8() == 1 { - hex_strings.push(hex::encode(compressed_bytes)); - break; - } - } - - // Compressed not in group - loop { - rng.fill_bytes(&mut compressed_bytes); - // We set the flags for the bytes - compressed_bytes[0] |= 0b10000000; - compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) - let G2_try_out_group = G2Affine::from_compressed_unchecked(&compressed_bytes); - if G2_try_out_group.is_some().unwrap_u8() == 1 - && G2_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 - { - hex_strings.push(hex::encode(compressed_bytes)); - break; - } - } - - // Uncompressed not in group - loop { - rng.fill_bytes(&mut compressed_bytes); - // We set the flags for the bytes - compressed_bytes[0] |= 0b10000000; - compressed_bytes[0] &= 0b10011111; // Compressed point, not at infinity. We don't care about the y sign (either both or neither will be in group) - let G2_try_out_group = G2Affine::from_compressed_unchecked(&compressed_bytes); - if G2_try_out_group.is_some().unwrap_u8() == 1 - && G2_try_out_group.unwrap().is_torsion_free().unwrap_u8() == 0 - { - hex_strings.push(hex::encode(G2_try_out_group.unwrap().to_uncompressed())); - break; - } - } - - //----------------------------------------------------------- - - write_hex_to_file("././test_vectors/serde_test_vectors", &hex_strings) -} - -fn bls_sig_with_dst_aug(mut rng: R) -> std::io::Result<()> { - let mut ikm = [0u8; 32]; - rng.fill_bytes(&mut ikm); - - let sk = Scalar::random(rng); - let pk = sk * G2Projective::generator(); - - let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; - let aug = b"Random value for test aug. "; - let msg = b"blst is such a blast"; - let mut concat_msg_aug = Vec::new(); - concat_msg_aug.extend_from_slice(aug); - concat_msg_aug.extend_from_slice(msg); - let hashed_msg = >>::hash_to_curve( - concat_msg_aug, - dst, - ); - - let sig = sk * hashed_msg; - - let blst_sig = Signature::from_bytes(&sig.to_affine().to_compressed()) - .expect("Invalid conversion from zkcrypto to blst"); - let blst_pk = PublicKey::from_bytes(&pk.to_affine().to_compressed()) - .expect("Invalid conversion from zkcrypto to blst"); - let err = blst_sig.verify(true, msg, dst, aug, &blst_pk, true); - assert_eq!(err, BLST_ERROR::BLST_SUCCESS); - - let sig_hex = hex::encode(sig.to_affine().to_compressed()); - let pk_hex = hex::encode(pk.to_affine().to_compressed()); - let mut file = File::create("././test_vectors/bls_sig_aug_test_vectors")?; - file.write_all(sig_hex.as_ref())?; - file.write_all(b"\n")?; - file.write_all(pk_hex.as_ref())?; - file.write_all(b"\n")?; - - Ok(()) -} - -fn h2c_large_dst(rng: &mut R) -> std::io::Result<()> { - let msg = b"Testing large dst."; - let mut large_dst = [0u8; 300]; - rng.fill_bytes(&mut large_dst); - - let hash_output = >>::hash_to_curve( - msg, - &large_dst, - ); - - // Given that the DST is larger than 255 bytes, it will first be hashed. Here we test that we can perform that action - // manually. - // Sanity check - let hashed_dst = Sha256::new().chain(b"H2C-OVERSIZE-DST-").chain(&large_dst).finalize(); - - let manually_hashed_output = >>::hash_to_curve( - msg, - &hashed_dst, - ); - - assert_eq!(hash_output, manually_hashed_output); - - // Sanity check with blst lib - use blst::{blst_hash_to_g1, blst_p1, blst_p1_compress}; - - let mut out = blst_p1::default(); - unsafe { blst_hash_to_g1(&mut out, msg.as_ptr(), msg.len(), hashed_dst.as_ptr(), hashed_dst.len(), hashed_dst.as_ptr(), 0) }; - - let mut bytes = [0u8; 48]; - unsafe { blst_p1_compress(bytes.as_mut_ptr(), &out) } - - assert_eq!(bytes, hash_output.to_affine().to_compressed()); - - let msg_hex = hex::encode(msg); - let large_dst_hex = hex::encode(large_dst); - let hash_output_hex = hex::encode(hash_output.to_affine().to_compressed()); - - let mut file = File::create("././test_vectors/h2c_large_dst")?; - file.write_all(msg_hex.as_ref())?; - file.write_all(b"\n")?; - file.write_all(large_dst_hex.as_ref())?; - file.write_all(b"\n")?; - file.write_all(hash_output_hex.as_ref())?; - file.write_all(b"\n")?; - - Ok(()) -} - fn write_hex_to_file(file_name: &str, hex_strings: &[String]) -> std::io::Result<()> { let mut file = File::create(file_name)?; @@ -343,10 +72,5 @@ fn write_hex_to_file(file_name: &str, hex_strings: &[String]) -> std::io::Result fn main() { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - // pairing_properties(&mut rng).expect("Failed to create test vectors!"); - // ec_operations(&mut rng).expect("Failed to create test vectors!"); - // serde(&mut rng).expect("Failed to create test vectors!"); - // bls_sig_with_dst_aug(&mut rng).expect("Failed to create test vectors!"); - // h2c_large_dst(&mut rng).expect("Failed to create large dst test vectors!"); generate_sk_and_pk(&mut rng).expect("Failed to create large dst test vectors!"); }