diff options
Diffstat (limited to 'guests/dvt-circuits/bls_utils')
| -rw-r--r-- | guests/dvt-circuits/bls_utils/Cargo.toml | 15 | ||||
| -rw-r--r-- | guests/dvt-circuits/bls_utils/src/bls.rs | 410 | ||||
| -rw-r--r-- | guests/dvt-circuits/bls_utils/src/lib.rs | 6 | ||||
| -rw-r--r-- | guests/dvt-circuits/bls_utils/src/verification.rs | 338 |
4 files changed, 769 insertions, 0 deletions
diff --git a/guests/dvt-circuits/bls_utils/Cargo.toml b/guests/dvt-circuits/bls_utils/Cargo.toml new file mode 100644 index 0000000..a6f4922 --- /dev/null +++ b/guests/dvt-circuits/bls_utils/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bls_utils" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } + +[dependencies] +bls12_381 = { git = "https://github.com/sp1-patches/bls12_381", features = ["experimental"] } +ff = "0.13.0" +rand = "0.8.5" +group = "0.13.0" +serde = "1.0.216" +dvt_abi = { path = "../dvt_abi" } +sha2 = "0.10" +hex = "0.4" diff --git a/guests/dvt-circuits/bls_utils/src/bls.rs b/guests/dvt-circuits/bls_utils/src/bls.rs new file mode 100644 index 0000000..3a6c7ba --- /dev/null +++ b/guests/dvt-circuits/bls_utils/src/bls.rs @@ -0,0 +1,410 @@ +use bls12_381::{ + hash_to_curve::{ExpandMsgXmd, HashToCurve}, + pairing, G1Affine, G1Projective, G2Affine, G2Projective, Scalar, +}; + +use dvt_abi::{self}; +use sha2::Sha256; +use std::fmt; + +// https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing +// +// In Shamir's secret sharing, a secret is encoded as a n-degree polynomial +// F where the secret value is equal to F(0). Here, F(0) is provided as mask[0]. +// +// A key share is generated by evaluating the polynomial in the `id` point. +// Later we can use at least N of these points to recover the original secret. +// +// Furthermore, if we sign some message M with at least K of the secret key +// shares we can restore from them the signature of the same message signed +// with original secret key. +// +// For a more gentle introductiont to Shamir's secret sharing, see also: +// +// https://github.com/dashpay/dips/blob/master/dip-0006/bls_m-of-n_threshold_scheme_and_dkg.md +// https://medium.com/toruslabs/what-distributed-key-generation-is-866adc79620 +pub fn evaluate_polynomial(cfs: Vec<G1Affine>, x: Scalar) -> G1Affine { + let cfst: Vec<G1Projective> = cfs.iter().map(|c| G1Projective::from(c)).collect(); + let count = cfst.len(); + if count == 0 { + return G1Affine::identity(); + } else if count == 1 { + return cfs[0]; + } else { + let mut y = cfst[count - 1]; + for i in 2..(count + 1) { + y = y * x + cfs[count - i]; + } + return G1Affine::from(y); + } +} + +pub fn lagrange_interpolation( + y_vec: &Vec<G1Affine>, + x_vec: &Vec<Scalar>, +) -> Result<G1Affine, Box<dyn std::error::Error>> { + let k = x_vec.len(); + if k == 0 || k != y_vec.len() { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "invalid inputs", + ))); + } + if k == 1 { + return Ok(y_vec[0]); + } + + // We calculate L(0) so we can simplify + // (X - X0) .. (X - Xj-1) * (X - Xj+1) .. (X - Xk) to just X0 * X1 .. Xk + // Later we can divide by Xi for each basis polynomial li(0) + let mut a = x_vec[0]; + for i in 1..k { + a *= x_vec[i]; + } + if a == Scalar::zero() { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "zero secret share id", + ))); + } + let mut r = G1Projective::identity(); + for i in 0..k { + let mut b = x_vec[i]; + for j in 0..k { + if j != i { + let v = x_vec[j] - x_vec[i]; + if v == Scalar::zero() { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "duplicate secret share id", + ))); + } + b *= v; + } + } + let li0 = a * b.invert().unwrap(); + let tmp = y_vec[i] * li0; + r = r + tmp; + } + Ok(G1Affine::from(r)) +} + +pub fn hash_message_to_g2(msg: &[u8], domain: &[u8]) -> G2Projective { + <G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve([msg], domain) +} + +pub fn bls_verify(pubkey: &G1Affine, signature: &G2Affine, message: &[u8]) -> bool { + let domain = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + let pk_projective = G1Projective::from(pubkey); + let sig_projective = G2Projective::from(signature); + + let hashed_msg = hash_message_to_g2(message, domain); + let left = pairing(&G1Affine::from(pk_projective), &G2Affine::from(hashed_msg)); + let right = pairing(&G1Affine::generator(), &G2Affine::from(sig_projective)); + + left == right +} + +pub fn bls_id_from_u32(id: u32) -> Scalar { + let unwrapped_le: [u8; 4] = (id as u32).to_le_bytes(); + let mut bytes = [0u8; 32]; + bytes[..4].copy_from_slice(&unwrapped_le); + Scalar::from_bytes(&bytes).unwrap() +} + +#[derive(PartialEq)] +pub struct PublicKey { + key: G1Affine, +} + +#[derive(PartialEq)] +pub struct SecretKey { + key: Scalar, +} + +impl PublicKey { + pub fn to_hex(&self) -> String { + hex::encode(self.key.to_compressed()) + } + + pub fn from_bytes(bytes: &dvt_abi::BLSPubkey) -> Result<PublicKey, Box<dyn std::error::Error>> { + let g1 = G1Affine::from_compressed(&bytes).into_option(); + match g1 { + Some(g1) => Ok(PublicKey { key: g1 }), + None => Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid public key", + ))), + } + } + + pub fn from_g1(g1: &G1Affine) -> PublicKey { + PublicKey { key: *g1 } + } + + pub fn verify_signature(&self, message: &[u8], signature: &dvt_abi::BLSSignature) -> bool { + bls_verify( + &self.key, + &G2Affine::from_compressed(signature).into_option().unwrap(), + message, + ) + } + + pub fn eq(&self, g1: &G1Affine) -> bool { + self.key == *g1 + } +} + +impl SecretKey { + pub fn to_public_key(&self) -> PublicKey { + PublicKey { + key: G1Affine::from(G1Affine::generator() * self.key), + } + } + + pub fn from_bytes(bytes: &[u8; 32]) -> Result<SecretKey, Box<dyn std::error::Error>> { + let mut le_bytes = bytes.clone(); + le_bytes.reverse(); + + let sk = Scalar::from_bytes(&le_bytes); + + if sk.is_none().into() { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid secret key", + ))); + } + + Ok(SecretKey { key: sk.unwrap() }) + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.key.to_bytes() + } +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PublicKey({})", self.to_hex()) + } +} + +impl fmt::Debug for SecretKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "SecretKey({})", hex::encode(self.to_bytes())) + } +} + +#[cfg(test)] +mod tests { + use dvt_abi::{BLSPubkey, BLSSignature}; + + use super::*; + + #[test] + fn test_bls_id_from_u32() { + let mut bytes: [u8; 32] = [ + 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, + ]; + assert_eq!(bls_id_from_u32(0), Scalar::from_bytes(&bytes).unwrap()); + bytes[0] = 1; + assert_eq!(bls_id_from_u32(1), Scalar::from_bytes(&bytes).unwrap()); + bytes[0] = 2; + assert_eq!(bls_id_from_u32(2), Scalar::from_bytes(&bytes).unwrap()); + } + + #[test] + fn test_bls_id_from_u32_to_hex() { + let id = bls_id_from_u32(0); + assert_eq!( + hex::encode(id.to_bytes()), + "0000000000000000000000000000000000000000000000000000000000000000" + ); + let id = bls_id_from_u32(1); + assert_eq!( + hex::encode(id.to_bytes()), + "0100000000000000000000000000000000000000000000000000000000000000" + ); + let id = bls_id_from_u32(2); + assert_eq!( + hex::encode(id.to_bytes()), + "0200000000000000000000000000000000000000000000000000000000000000" + ); + } + + #[test] + fn test_verify_signature() { + let data = hex::decode("2f901d5cec8722e44afd59e94d0a56bf1506a72a0a60709920aad714d1a2ece0") + .unwrap(); + let pk: BLSPubkey = hex::decode("90346f9c5f3c09d96ea02acd0220daa8459f03866ed938c798e3716e42c7e033c9a7ef66a10f83af06d5c00b508c6d0f").unwrap().try_into().unwrap(); + let sig:BLSSignature = hex::decode("a9c08eff13742f78f1e5929888f223b5b5b12b4836b5417c5a135cf24f4e2a4c66a6cdef91be3098b7e7a6a63903b61302e3cf2b8653101da245cf01a8d82b25debe7b18a3a2eb1778f8628fd2c59c8687f6e048a31250fbc2804c20043b8443").unwrap().try_into().unwrap(); + let pk = G1Affine::from_compressed(&pk).into_option().unwrap(); + let sig = G2Affine::from_compressed(&sig).into_option().unwrap(); + assert!(bls_verify(&pk, &sig, &data)); + + let invalida_data = hex::decode("00").unwrap(); + assert!(!bls_verify(&pk, &sig, &invalida_data)); + + let wrong_pk: BLSPubkey = hex::decode("98876a81fe982573ec5f986956bf9bf0bcb5349d95c3c8da0aefd05a49fea6215f59b0696f906547baed90ab245804e8").unwrap().try_into().unwrap(); + let wrong_pk = G1Affine::from_compressed(&wrong_pk).into_option().unwrap(); + assert!(!bls_verify(&wrong_pk, &sig, &data)); + + let bad_sig: BLSSignature = hex::decode("999e7b24bee2587d687e8f358ed10627ef57ec54935bd7a500bbbb18a57e7aa21b800f8b1f487a980d7c93918fdbd8020b66ce9a9e5788a4826e610ac937d8c2ce0ad9c0ee9a5732cf73052493e9a500cc5100a15bdbf9e5b79104db52dbf07c").unwrap().try_into().unwrap(); + let bad_sig = G2Affine::from_compressed(&bad_sig).into_option().unwrap(); + assert!(!bls_verify(&pk, &bad_sig, &data)) + } + + #[test] + fn test_evaluate_polynomial() { + let pks: Vec<G1Affine> = [ + "92cad77a95432bc1030d81b5465cb69be672c1dd0da752230bf8112f8449b03149e7fa208a6fae460a9f0a1d5bd175e9", + "98876a81fe982573ec5f986956bf9bf0bcb5349d95c3c8da0aefd05a49fea6215f59b0696f906547baed90ab245804e8", + "ad2c4e5b631fbded449ede4dca2d040b9c7eae58d1e73b3050486c1ba22c15a92d9ff13c05c356f974447e4fca84864a"] + .iter().map(|pk| -> BLSPubkey { + hex::decode(pk).unwrap().try_into().unwrap() + }) + .map(|pk| G1Affine::from_compressed(&pk).into_option().unwrap()).collect(); + + let target = "af8e0095ecc662f65b95ce57e5bd2f8739ff93b0621a1ad53f5616538d1323ff40e6e9ddd7132298710974fe6fc0344e"; + + let id = bls_id_from_u32(1); + + let result = evaluate_polynomial(pks, id); + + assert!(hex::encode(result.to_compressed()) == target); + } + + #[test] + fn test_evaluate_polynomial_bad_base_keys() { + let pks: Vec<G1Affine> = [ + "92cad77a95432bc1030d81b5465cb69be672c1dd0da752230bf8112f8449b03149e7fa208a6fae460a9f0a1d5bd175e9", + "92cad77a95432bc1030d81b5465cb69be672c1dd0da752230bf8112f8449b03149e7fa208a6fae460a9f0a1d5bd175e9", + "92cad77a95432bc1030d81b5465cb69be672c1dd0da752230bf8112f8449b03149e7fa208a6fae460a9f0a1d5bd175e9"] + .iter().map(|pk| -> BLSPubkey { + hex::decode(pk).unwrap().try_into().unwrap() + }) + .map(|pk| G1Affine::from_compressed(&pk).into_option().unwrap()).collect(); + + let target = "af8e0095ecc662f65b95ce57e5bd2f8739ff93b0621a1ad53f5616538d1323ff40e6e9ddd7132298710974fe6fc0344e"; + + let id = bls_id_from_u32(1); + + let result = evaluate_polynomial(pks, id); + + assert!(hex::encode(result.to_compressed()) != target); + } + + #[test] + fn test_lagrange_interpolation() { + let pks: Vec<G1Affine> = [ + "8da434e68daef9af33e39ab727557a3cd86d7991cd6b545746bf92c8edec37012912cfa2292a21512bce9040a1c0e502", + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903", + "8cbfb6cb7af927cfe5fb17621df7036de539b7ff4aa0620cdc218d6b7fe7f2e714a96bdeddb2a0dc24867a90594427e1", + "9892b390d9d3000c7bf04763006fbc617b7ba9c261fff35094aec3f43599f2c254ae667d9ba135747309b77cd02f1fbc", + "b255c8a66fd1a13373537e8a4ba258f4990c141fc3c06daccda0711f5ebaffc092f0e5b0e4454e6344e2f97957be4017"] + .iter().map(|pk| -> BLSPubkey { + hex::decode(pk).unwrap().try_into().unwrap() + }) + .map(|pk| G1Affine::from_compressed(&pk).into_option().unwrap()).collect(); + + let target = "a31d9a483703cd0da9873e5e76b4de5f7035d0a73d79b3be8667daa4fc7065a1bbb5bf77787fcf2a35bd327eecc4fa6b"; + + let ids = vec![ + bls_id_from_u32(1), + bls_id_from_u32(2), + bls_id_from_u32(3), + bls_id_from_u32(4), + bls_id_from_u32(5), + ]; + + let result = lagrange_interpolation(&pks, &ids); + + assert!(hex::encode(result.unwrap().to_compressed()) == target); + } + + #[test] + fn test_lagrange_interpolation_out_of_order() { + let pks: Vec<G1Affine> = [ + "b255c8a66fd1a13373537e8a4ba258f4990c141fc3c06daccda0711f5ebaffc092f0e5b0e4454e6344e2f97957be4017", + "8da434e68daef9af33e39ab727557a3cd86d7991cd6b545746bf92c8edec37012912cfa2292a21512bce9040a1c0e502", + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903", + "8cbfb6cb7af927cfe5fb17621df7036de539b7ff4aa0620cdc218d6b7fe7f2e714a96bdeddb2a0dc24867a90594427e1", + "9892b390d9d3000c7bf04763006fbc617b7ba9c261fff35094aec3f43599f2c254ae667d9ba135747309b77cd02f1fbc", + ] + .iter().map(|pk| -> BLSPubkey { + hex::decode(pk).unwrap().try_into().unwrap() + }) + .map(|pk| G1Affine::from_compressed(&pk).into_option().unwrap()).collect(); + + let target = "a31d9a483703cd0da9873e5e76b4de5f7035d0a73d79b3be8667daa4fc7065a1bbb5bf77787fcf2a35bd327eecc4fa6b"; + + let ids = vec![ + bls_id_from_u32(5), + bls_id_from_u32(1), + bls_id_from_u32(2), + bls_id_from_u32(3), + bls_id_from_u32(4), + ]; + + let result = lagrange_interpolation(&pks, &ids); + + assert!(hex::encode(result.unwrap().to_compressed()) == target); + } + + #[test] + fn test_lagrange_interpolation_wrong_order() { + let pks: Vec<G1Affine> = [ + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903", + "8da434e68daef9af33e39ab727557a3cd86d7991cd6b545746bf92c8edec37012912cfa2292a21512bce9040a1c0e502", + "8cbfb6cb7af927cfe5fb17621df7036de539b7ff4aa0620cdc218d6b7fe7f2e714a96bdeddb2a0dc24867a90594427e1", + "9892b390d9d3000c7bf04763006fbc617b7ba9c261fff35094aec3f43599f2c254ae667d9ba135747309b77cd02f1fbc", + "b255c8a66fd1a13373537e8a4ba258f4990c141fc3c06daccda0711f5ebaffc092f0e5b0e4454e6344e2f97957be4017"] + .iter().map(|pk| -> BLSPubkey { + hex::decode(pk).unwrap().try_into().unwrap() + }) + .map(|pk| G1Affine::from_compressed(&pk).into_option().unwrap()).collect(); + + let target = "a31d9a483703cd0da9873e5e76b4de5f7035d0a73d79b3be8667daa4fc7065a1bbb5bf77787fcf2a35bd327eecc4fa6b"; + + let ids = vec![ + bls_id_from_u32(1), + bls_id_from_u32(2), + bls_id_from_u32(3), + bls_id_from_u32(4), + bls_id_from_u32(5), + ]; + + let result = lagrange_interpolation(&pks, &ids); + + assert!(hex::encode(result.unwrap().to_compressed()) != target); + } + + #[test] + fn test_lagrange_interpolation_wrong_base_keys() { + let pks: Vec<G1Affine> = [ + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903", + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903", + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903", + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903", + "a3cd061aab6013f7561978959482d79e9ca636392bc94d4bcad9cb6f90fe2cdf52100f211052f1570db0ca690b6a9903"] + .iter().map(|pk| -> BLSPubkey { + hex::decode(pk).unwrap().try_into().unwrap() + }) + .map(|pk| G1Affine::from_compressed(&pk).into_option().unwrap()).collect(); + + let target = "a31d9a483703cd0da9873e5e76b4de5f7035d0a73d79b3be8667daa4fc7065a1bbb5bf77787fcf2a35bd327eecc4fa6b"; + + let ids = vec![ + bls_id_from_u32(1), + bls_id_from_u32(2), + bls_id_from_u32(3), + bls_id_from_u32(4), + bls_id_from_u32(5), + ]; + + let result = lagrange_interpolation(&pks, &ids); + + assert!(hex::encode(result.unwrap().to_compressed()) != target); + } +} diff --git a/guests/dvt-circuits/bls_utils/src/lib.rs b/guests/dvt-circuits/bls_utils/src/lib.rs new file mode 100644 index 0000000..6ae7927 --- /dev/null +++ b/guests/dvt-circuits/bls_utils/src/lib.rs @@ -0,0 +1,6 @@ +pub mod bls; +pub mod verification; + +pub use verification::{ + verify_generations, verify_initial_commitment_hash, verify_seed_exchange_commitment, VerificationErrors +}; diff --git a/guests/dvt-circuits/bls_utils/src/verification.rs b/guests/dvt-circuits/bls_utils/src/verification.rs new file mode 100644 index 0000000..ea414c5 --- /dev/null +++ b/guests/dvt-circuits/bls_utils/src/verification.rs @@ -0,0 +1,338 @@ +use bls12_381::{G1Affine, G1Projective, G2Affine, Scalar}; + +use dvt_abi::{self}; +use sha2::{Digest, Sha256}; + +use crate::bls::{ + bls_id_from_u32, bls_verify, evaluate_polynomial, lagrange_interpolation, PublicKey, SecretKey, +}; + +#[derive(Debug)] +pub enum VerificationErrors { + SlashableError(String), + UnslashableError(String), +} + +impl std::fmt::Display for VerificationErrors { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VerificationErrors::SlashableError(e) => write!(f, "{}", e), + VerificationErrors::UnslashableError(e) => write!(f, "{}", e), + } + } +} + +impl std::error::Error for VerificationErrors { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +pub fn compute_seed_exchange_hash( + seed_exchange: &dvt_abi::AbiSeedExchangeCommitment, +) -> dvt_abi::SHA256 { + let shared_secret = &seed_exchange.shared_secret; + let mut hasher = Sha256::new(); + + let sk = SecretKey::from_bytes(&shared_secret.secret).unwrap(); + + hasher.update(&seed_exchange.initial_commitment_hash); + hasher.update(&sk.to_bytes()); + hasher.update(&shared_secret.dst_base_hash); + hasher.update(&shared_secret.src_id); + hasher.update(&shared_secret.dst_id); + + hasher.finalize().to_vec().try_into().unwrap() +} + +pub fn get_index_in_commitments( + commitments: &dvt_abi::AbiVerificationHashes, + destination_id: &dvt_abi::SHA256, +) -> Result<u32, Box<dyn std::error::Error>> { + let mut sorted = commitments.clone(); + sorted.sort(); + for i in 0..sorted.len() { + if commitments[i] == *destination_id { + return Ok(i as u32); + } + } + + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Could not find destination in commitments", + ))) +} + +pub fn verify_seed_exchange_commitment( + verification_hashes: &dvt_abi::AbiVerificationHashes, + seed_exchange: &dvt_abi::AbiSeedExchangeCommitment, + initial_commitment: &dvt_abi::AbiInitialCommitment, +) -> Result<(), Box<dyn std::error::Error>> { + let commitment = &seed_exchange.commitment; + let shared_secret = &seed_exchange.shared_secret; + + if !bls_verify( + &G1Affine::from_compressed(&commitment.pubkey) + .into_option() + .unwrap(), + &G2Affine::from_compressed(&commitment.signature) + .into_option() + .unwrap(), + &commitment.hash, + ) { + // Return unslashable error + return Err(Box::new(VerificationErrors::UnslashableError( + String::from(format!( + "Invalid field seeds_exchange_commitment.commitment.signature {}\n", + hex::encode(commitment.signature) + )), + ))); + } + + let sk = SecretKey::from_bytes(&shared_secret.secret); + if sk.is_err() { + return Err(Box::new(VerificationErrors::SlashableError(String::from( + format!( + "Invalid field seeds_exchange_commitment.shared_secret.secret: {} \n", + sk.unwrap_err() + ), + )))); + } + + let sk = sk.unwrap(); + + let computed_commitment_hash = compute_seed_exchange_hash(seed_exchange); + + if computed_commitment_hash.to_vec() != seed_exchange.commitment.hash { + return Err(Box::new(VerificationErrors::SlashableError( + String::from(format!( + "Invalid field seeds_exchange_commitment.commitment.hash. Expected: {:?}, got hash: {:?}\n", + hex::encode(seed_exchange.commitment.hash), + hex::encode(computed_commitment_hash.to_vec()) + )), + ))); + } + + let dest_id = get_index_in_commitments( + verification_hashes, + &seed_exchange.shared_secret.dst_base_hash, + ); + + if dest_id.is_err() { + return Err(Box::new(VerificationErrors::SlashableError(String::from( + format!( + "Invalid field seeds_exchange_commitment.shared_secret.dst_id: {} \n", + dest_id.unwrap_err() + ), + )))); + } + + let unwraped = dest_id.unwrap() + 1; + let test_id = bls_id_from_u32(unwraped); + + let mut cfst: Vec<G1Affine> = Vec::new(); + for pubkey in &initial_commitment.verification_vector.pubkeys { + cfst.push(G1Affine::from_compressed(pubkey).into_option().unwrap()); + } + + let le_bytes = seed_exchange.shared_secret.dst_id.clone(); + + let id = Scalar::from_bytes(&le_bytes).unwrap(); + + if id != test_id { + return Err(Box::new(VerificationErrors::SlashableError(String::from( + "Invalid field seeds_exchange_commitment.shared_secret.dst_id\n", + )))); + } + let eval_result = evaluate_polynomial(cfst, id); + + if !sk.to_public_key().eq(&eval_result) { + return Err(Box::new(VerificationErrors::SlashableError(String::from( + format!( + "Bad secret field : Expected secret with public key: {:?}, got public key: {:?}\n", + PublicKey::from_g1(&eval_result), + sk.to_public_key() + ), + )))); + } + + Ok(()) +} + +pub fn verify_initial_commitment_hash(commitment: &dvt_abi::AbiInitialCommitment) -> bool { + let mut hasher = Sha256::new(); + + hasher.update([commitment.settings.n]); + hasher.update([commitment.settings.k]); + hasher.update(commitment.settings.gen_id); + for pubkey in &commitment.verification_vector.pubkeys { + hasher.update(&pubkey); + } + let computed_hash = hasher.finalize().to_vec(); + computed_hash == commitment.hash +} + +fn verify_generation_sig( + generation: &dvt_abi::AbiGeneration, +) -> Result<(), Box<dyn std::error::Error>> { + let partial_pubkey = PublicKey::from_bytes(&generation.partial_pubkey)?; + if !partial_pubkey + .verify_signature(&generation.message_cleartext, &generation.message_signature) + { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!( + "Invalid signature {}", + hex::encode(generation.partial_pubkey) + ), + ))); + } + Ok(()) +} + +fn generate_initial_commitment( + generation: &dvt_abi::AbiGeneration, + settings: &dvt_abi::AbiGenerateSettings, +) -> dvt_abi::AbiInitialCommitment { + dvt_abi::AbiInitialCommitment { + hash: generation.base_hash, + settings: dvt_abi::AbiGenerateSettings { + n: settings.n, + k: settings.k, + gen_id: settings.gen_id, + }, + verification_vector: dvt_abi::AbiVerificationVector { + pubkeys: generation.verification_vector.clone(), + }, + } +} + +fn print_vec_g1_as_hex(v: &Vec<G1Affine>) { + for i in 0..v.len() { + println!("{} ", hex::encode(v[i].to_compressed())); + } +} + +fn compute_agg_key_from_dvt( + verification_vectors: Vec<dvt_abi::AbiVerificationVector>, + ids: &Vec<Scalar>, +) -> Result<G1Affine, Box<dyn std::error::Error>> { + let verification_vectors: Vec<Vec<G1Affine>> = verification_vectors + .iter() + .map(|vector| -> Vec<G1Affine> { + vector + .pubkeys + .iter() + .map(|pk: &[u8; 48]| G1Affine::from_compressed(&pk).into_option().unwrap()) + .collect() + }) + .collect(); + + let mut all_pts = Vec::new(); + + for i in 0..verification_vectors.len() { + let mut pts = Vec::new(); + let share_id = ids[i]; + for j in 0..verification_vectors.len() { + let pt = evaluate_polynomial(verification_vectors[j].clone(), share_id); + pts.push(pt); + } + all_pts.push(pts); + } + let mut final_keys = Vec::new(); + + for i in 0..all_pts.len() { + let mut key: G1Affine = all_pts[i][0]; + for j in 1..all_pts[i].len() { + key = G1Affine::from(G1Projective::from(key) + G1Projective::from(all_pts[i][j])); + } + final_keys.push(key); + } + + let agg_key = lagrange_interpolation(&final_keys, &ids)?; + return Ok(agg_key); +} + +pub fn verify_generations( + generations: &[dvt_abi::AbiGeneration], + settings: &dvt_abi::AbiGenerateSettings, + agg_key: &dvt_abi::BLSPubkey, +) -> Result<(), Box<dyn std::error::Error>> { + + if generations.len() != settings.n as usize { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid number of generations", + ))); + } + + let mut sorted = generations.to_vec(); + sorted.sort_by(|a, b| a.base_hash.cmp(&b.base_hash)); + + let verification_vectors = sorted + .iter() + .map(|generation| -> dvt_abi::AbiVerificationVector { + dvt_abi::AbiVerificationVector { + pubkeys: generation.verification_vector.clone(), + } + }) + .collect(); + + let ids = sorted + .iter() + .enumerate() + .map(|(i, _)| -> Scalar { bls_id_from_u32((i + 1) as u32) }) + .collect(); + + let computed_key = compute_agg_key_from_dvt(verification_vectors, &ids)?; + + let agg_key = G1Affine::from_compressed(agg_key).into_option(); + + if agg_key.is_none() { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid aggregate public key", + ))); + } + + let agg_key = agg_key.unwrap(); + if computed_key != agg_key { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Computed key {} does not match aggregate public key {}", hex::encode(computed_key.to_compressed()), hex::encode(agg_key.to_compressed())), + ))); + } + + let partial_keys = sorted + .iter() + .map(|generation| -> G1Affine { + G1Affine::from_compressed(&generation.partial_pubkey) + .into_option() + .unwrap() + }) + .collect(); + + let computed_key = lagrange_interpolation(&partial_keys, &ids)?; + + if computed_key != agg_key { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Computed key {} does not match aggregate public key {}", hex::encode(computed_key.to_compressed()), hex::encode(agg_key.to_compressed())), + ))); + } + + for (_, generation) in generations.iter().enumerate() { + verify_generation_sig(generation)?; + let initial_commitment = generate_initial_commitment(generation, &settings); + let ok = verify_initial_commitment_hash(&initial_commitment); + if !ok { + return Err(Box::new(VerificationErrors::UnslashableError( + String::from(format!( + "Invalid initial commitment hash {}\n", + hex::encode(initial_commitment.hash) + )), + ))); + } + } + Ok(()) +} |
