aboutsummaryrefslogtreecommitdiff
path: root/guests/dvt-circuits/bls_utils
diff options
context:
space:
mode:
authorKamen Mladenov <kamen@syndamia.com>2025-02-17 18:37:08 +0200
committerKamen Mladenov <kamen@syndamia.com>2025-04-15 14:39:37 +0300
commitefdd6857ab7e9b25e6c3e173fc32ca63812ac21c (patch)
tree04d5dd673a77bc267b07775e9ef1bad577d53c54 /guests/dvt-circuits/bls_utils
parent32b2f8b3a630ebf04259457c5f9522b6bc71b7ff (diff)
downloadzkVMs-benchmarks-dvt-circuits-port.tar
zkVMs-benchmarks-dvt-circuits-port.tar.gz
zkVMs-benchmarks-dvt-circuits-port.zip
feat(guests): Add dvt-circuits cratedvt-circuits-port
This is a port of https://github.com/metacraft-labs/dvt-circuits Co-authored-by: Marto <martindobrev0@gmail.com>
Diffstat (limited to 'guests/dvt-circuits/bls_utils')
-rw-r--r--guests/dvt-circuits/bls_utils/Cargo.toml15
-rw-r--r--guests/dvt-circuits/bls_utils/src/bls.rs410
-rw-r--r--guests/dvt-circuits/bls_utils/src/lib.rs6
-rw-r--r--guests/dvt-circuits/bls_utils/src/verification.rs338
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(())
+}