diff --git a/soroban-env-host/src/test.rs b/soroban-env-host/src/test.rs index 6d8b6433f..7a607c93c 100644 --- a/soroban-env-host/src/test.rs +++ b/soroban-env-host/src/test.rs @@ -3,6 +3,7 @@ pub(crate) mod observe; mod address; mod auth; mod basic; +mod bls12_381; mod budget_metering; mod bytes; mod complex; diff --git a/soroban-env-host/src/test/bls12_381.rs b/soroban-env-host/src/test/bls12_381.rs new file mode 100644 index 000000000..3e04993bf --- /dev/null +++ b/soroban-env-host/src/test/bls12_381.rs @@ -0,0 +1,1277 @@ +use crate::{ + crypto::bls12_381::{ + FP2_SERIALIZED_SIZE, FP_SERIALIZED_SIZE, G1_SERIALIZED_SIZE, G2_SERIALIZED_SIZE, + }, + xdr::{ScErrorCode, ScErrorType}, + BytesObject, Env, EnvBase, Host, HostError, U256Val, U32Val, Val, VecObject, +}; +use ark_bls12_381::{Fq, Fq2, G1Affine, G2Affine}; +use ark_ec::AffineRepr; +use ark_ff::UniformRand; +use ark_serialize::CanonicalSerialize; +use hex::FromHex; +use rand::{rngs::StdRng, SeedableRng}; +use std::cmp::Ordering; + +enum InvalidPointTypes { + TooManyBytes, + TooFewBytes, + CompressionFlagSet, + InfinityFlagSetBitsNotAllZero, + SortFlagSet, + PointNotOnCurve, + PointNotInSubgroup, +} + +use serde::Deserialize; + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct Field { + m: String, + p: String, +} + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct Map { + name: String, +} + +#[derive(Deserialize, Debug)] +struct Point { + x: String, + y: String, +} + +#[allow(non_snake_case)] +#[derive(Deserialize, Debug)] +struct TestCase { + P: Point, + Q0: Point, + Q1: Point, + msg: String, + u: [String; 2], +} + +#[allow(unused, non_snake_case)] +#[derive(Deserialize, Debug)] +struct HashToCurveTestSuite { + L: String, + Z: String, + ciphersuite: String, + curve: String, + dst: String, + expand: String, + field: Field, + hash: String, + k: String, + map: Map, + randomOracle: bool, + vectors: Vec, +} + +fn parse_hex(s: &str) -> Vec { + Vec::from_hex(s.trim_start_matches("0x")).unwrap() +} + +fn sample_g1(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + host.g1_affine_serialize_uncompressed(G1Affine::rand(&mut rng)) +} + +fn sample_g1_not_on_curve(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + loop { + let x = Fq::rand(&mut rng); + let y = Fq::rand(&mut rng); + let p = G1Affine::new_unchecked(x, y); + if !p.is_on_curve() { + return host.g1_affine_serialize_uncompressed(p); + } + } +} + +fn sample_g1_not_in_subgroup(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + loop { + let x = Fq::rand(&mut rng); + if let Some(p) = G1Affine::get_point_from_x_unchecked(x, true) { + assert!(p.is_on_curve()); + if !p.is_in_correct_subgroup_assuming_on_curve() { + return host.g1_affine_serialize_uncompressed(p); + } + } + } +} + +fn g1_zero(host: &Host) -> Result { + host.g1_affine_serialize_uncompressed(G1Affine::zero()) +} + +fn neg_g1(bo: BytesObject, host: &Host) -> Result { + let g1 = host.g1_affine_deserialize_from_bytesobj(bo)?; + host.g1_affine_serialize_uncompressed(-g1) +} + +fn invalid_g1(host: &Host, ty: InvalidPointTypes) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let affine = G1Affine::rand(&mut rng); + assert!(!affine.is_zero()); + let bo = host.g1_affine_serialize_uncompressed(affine)?; + match ty { + InvalidPointTypes::TooManyBytes => { + // insert an empty byte to the end + host.bytes_insert(bo, U32Val::from(G1_SERIALIZED_SIZE as u32), U32Val::from(0)) + } + InvalidPointTypes::TooFewBytes => { + // delete the last byte + host.bytes_del(bo, U32Val::from(G1_SERIALIZED_SIZE as u32 - 1)) + } + InvalidPointTypes::CompressionFlagSet => { + let mut first_byte: u32 = host.bytes_get(bo, U32Val::from(0))?.into(); + first_byte = ((first_byte as u8) | (1 << 7)) as u32; + host.bytes_put(bo, U32Val::from(0), U32Val::from(first_byte)) + } + InvalidPointTypes::InfinityFlagSetBitsNotAllZero => { + let mut first_byte: u32 = host.bytes_get(bo, U32Val::from(0))?.into(); + first_byte = ((first_byte as u8) | (1 << 6)) as u32; + host.bytes_put(bo, U32Val::from(0), U32Val::from(first_byte)) + } + InvalidPointTypes::SortFlagSet => { + let mut first_byte: u32 = host.bytes_get(bo, U32Val::from(0))?.into(); + first_byte = ((first_byte as u8) | (1 << 5)) as u32; + host.bytes_put(bo, U32Val::from(0), U32Val::from(first_byte)) + } + InvalidPointTypes::PointNotOnCurve => sample_g1_not_on_curve(host), + InvalidPointTypes::PointNotInSubgroup => sample_g1_not_in_subgroup(host), + } +} + +fn sample_g2(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + host.g2_affine_serialize_uncompressed(G2Affine::rand(&mut rng)) +} + +fn sample_g2_not_on_curve(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + loop { + let x = Fq2::rand(&mut rng); + let y = Fq2::rand(&mut rng); + let p = G2Affine::new_unchecked(x, y); + if !p.is_on_curve() { + return host.g2_affine_serialize_uncompressed(p); + } + } +} + +fn sample_g2_not_in_subgroup(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + loop { + let x = Fq2::rand(&mut rng); + if let Some(p) = G2Affine::get_point_from_x_unchecked(x, true) { + assert!(p.is_on_curve()); + if !p.is_in_correct_subgroup_assuming_on_curve() { + return host.g2_affine_serialize_uncompressed(p); + } + } + } +} + +fn g2_zero(host: &Host) -> Result { + host.g2_affine_serialize_uncompressed(G2Affine::zero()) +} + +fn neg_g2(bo: BytesObject, host: &Host) -> Result { + let g2 = host.g2_affine_deserialize_from_bytesobj(bo)?; + host.g2_affine_serialize_uncompressed(-g2) +} + +fn invalid_g2(host: &Host, ty: InvalidPointTypes) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let affine = G2Affine::rand(&mut rng); + assert!(!affine.is_zero()); + let bo = host.g2_affine_serialize_uncompressed(affine)?; + match ty { + InvalidPointTypes::TooManyBytes => { + // insert an empty byte to the end + host.bytes_insert(bo, U32Val::from(G2_SERIALIZED_SIZE as u32), U32Val::from(0)) + } + InvalidPointTypes::TooFewBytes => { + // delete the last byte + host.bytes_del(bo, U32Val::from(G2_SERIALIZED_SIZE as u32 - 1)) + } + InvalidPointTypes::CompressionFlagSet => { + let mut first_byte: u32 = host.bytes_get(bo, U32Val::from(0))?.into(); + first_byte = ((first_byte as u8) | (1 << 7)) as u32; + host.bytes_put(bo, U32Val::from(0), U32Val::from(first_byte)) + } + InvalidPointTypes::InfinityFlagSetBitsNotAllZero => { + let mut first_byte: u32 = host.bytes_get(bo, U32Val::from(0))?.into(); + first_byte = ((first_byte as u8) | (1 << 6)) as u32; + host.bytes_put(bo, U32Val::from(0), U32Val::from(first_byte)) + } + InvalidPointTypes::SortFlagSet => { + let mut first_byte: u32 = host.bytes_get(bo, U32Val::from(0))?.into(); + first_byte = ((first_byte as u8) | (1 << 5)) as u32; + host.bytes_put(bo, U32Val::from(0), U32Val::from(first_byte)) + } + InvalidPointTypes::PointNotOnCurve => sample_g2_not_on_curve(host), + InvalidPointTypes::PointNotInSubgroup => sample_g2_not_in_subgroup(host), + } +} + +fn parse_g2_point_test_case(host: &Host, p: Point) -> Result { + let mut p_bytes = [0u8; 192]; + // the input point format in each coordinate is (c0,c1), each part + // being a hex string starting '0x'. So we need to split it by comma, + // reverse the order, and parse each part (each part is already + // big-endian, so all we need to do is to strip the prefix) + let qx: Vec<_> = p.x.split(',').collect(); + let qy: Vec<_> = p.y.split(',').collect(); + p_bytes[0..48].copy_from_slice(&parse_hex(qx[1])); + p_bytes[48..96].copy_from_slice(&parse_hex(qx[0])); + p_bytes[96..144].copy_from_slice(&parse_hex(qy[1])); + p_bytes[144..192].copy_from_slice(&parse_hex(qy[0])); + host.bytes_new_from_slice(&p_bytes) +} + +#[allow(unused)] +fn sample_fp(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let fp = Fq::rand(&mut rng); + let mut buf = [0u8; FP_SERIALIZED_SIZE]; + host.serialize_uncompressed_into_slice(&fp, &mut buf, 1, "test")?; + host.bytes_new_from_slice(&buf) +} + +fn invalid_fp(host: &Host, ty: InvalidPointTypes) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let fp = Fq::rand(&mut rng); + match ty { + InvalidPointTypes::TooManyBytes => { + let mut buf = [0u8; FP_SERIALIZED_SIZE + 1]; // one extra zero byte + host.serialize_uncompressed_into_slice(&fp, &mut buf, 1, "test")?; + host.bytes_new_from_slice(&buf) + } + InvalidPointTypes::TooFewBytes => { + let mut buf = [0u8; FP_SERIALIZED_SIZE]; + host.serialize_uncompressed_into_slice(&fp, &mut buf, 1, "test")?; + host.bytes_new_from_slice(&buf[0..FP_SERIALIZED_SIZE - 1]) // take one less byte + } + _ => panic!("not available"), + } +} + +#[allow(unused)] +fn sample_fp2(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let fp = Fq2::rand(&mut rng); + let mut buf = [0u8; FP2_SERIALIZED_SIZE]; + host.serialize_uncompressed_into_slice(&fp, &mut buf, 1, "test")?; + host.bytes_new_from_slice(&buf) +} + +fn invalid_fp2(host: &Host, ty: InvalidPointTypes) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let fp = Fq::rand(&mut rng); + match ty { + InvalidPointTypes::TooManyBytes => { + let mut buf = [0u8; FP2_SERIALIZED_SIZE + 1]; // one extra zero byte + host.serialize_uncompressed_into_slice(&fp, &mut buf, 1, "test")?; + host.bytes_new_from_slice(&buf) + } + InvalidPointTypes::TooFewBytes => { + let mut buf = [0u8; FP2_SERIALIZED_SIZE]; + host.serialize_uncompressed_into_slice(&fp, &mut buf, 1, "test")?; + host.bytes_new_from_slice(&buf[0..FP2_SERIALIZED_SIZE - 1]) // take one less byte + } + _ => panic!("not available"), + } +} + +fn sample_fr(host: &Host) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let obj = host.obj_from_u256_pieces( + u64::rand(&mut rng), + u64::rand(&mut rng), + u64::rand(&mut rng), + u64::rand(&mut rng), + )?; + Ok(obj.into()) +} + +fn sample_host_vec( + host: &Host, + buf_size: usize, + vec_len: usize, +) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let vals: Vec = (0..vec_len) + .into_iter() + .map(|_| { + let t = T::rand(&mut rng); + let mut buf = vec![0; buf_size]; + host.serialize_uncompressed_into_slice(&t, &mut buf, 1, "test") + .unwrap(); + host.bytes_new_from_slice(&buf).unwrap().to_val() + }) + .collect(); + host.vec_new_from_slice(&vals) +} + +fn sample_fr_vec(host: &Host, vec_len: usize) -> Result { + let mut rng = StdRng::from_seed([0xff; 32]); + let vals: Vec = (0..vec_len) + .into_iter() + .map(|_| { + host.obj_from_u256_pieces( + u64::rand(&mut rng), + u64::rand(&mut rng), + u64::rand(&mut rng), + u64::rand(&mut rng), + ) + .unwrap() + .to_val() + }) + .collect(); + host.vec_new_from_slice(&vals) +} + +#[test] +fn g1_add() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // invalid p1 + { + let p2 = sample_g1(&host)?; + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(invalid_g1(&host, InvalidPointTypes::TooManyBytes)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(invalid_g1(&host, InvalidPointTypes::TooFewBytes)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add( + invalid_g1(&host, InvalidPointTypes::CompressionFlagSet)?, + p2 + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add( + invalid_g1(&host, InvalidPointTypes::InfinityFlagSetBitsNotAllZero)?, + p2 + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(invalid_g1(&host, InvalidPointTypes::SortFlagSet)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(invalid_g1(&host, InvalidPointTypes::PointNotOnCurve)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add( + invalid_g1(&host, InvalidPointTypes::PointNotInSubgroup)?, + p2 + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // invalid p2 + { + let p1 = sample_g1(&host)?; + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(p1, invalid_g1(&host, InvalidPointTypes::TooManyBytes)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(p1, invalid_g1(&host, InvalidPointTypes::TooFewBytes)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add( + p1, + invalid_g1(&host, InvalidPointTypes::CompressionFlagSet)? + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add( + p1, + invalid_g1(&host, InvalidPointTypes::InfinityFlagSetBitsNotAllZero)? + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(p1, invalid_g1(&host, InvalidPointTypes::SortFlagSet)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add(p1, invalid_g1(&host, InvalidPointTypes::PointNotOnCurve)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g1_add( + p1, + invalid_g1(&host, InvalidPointTypes::PointNotInSubgroup)? + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 3. lhs.add(zero) = lhs + { + let p1 = sample_g1(&host)?; + let res = host.bls12_381_g1_add(p1, g1_zero(&host)?)?; + assert_eq!(host.obj_cmp(p1.into(), res.into())?, Ordering::Equal as i64); + } + // 4. zero.add(rhs) = rhs + { + let p2 = sample_g1(&host)?; + let res = host.bls12_381_g1_add(g1_zero(&host)?, p2)?; + assert_eq!(host.obj_cmp(p2.into(), res.into())?, Ordering::Equal as i64); + } + // 5. communitive a + b = b + a + { + let a = sample_g1(&host)?; + let b = sample_g1(&host)?; + let a_plus_b = host.bls12_381_g1_add(a, b)?; + let b_plus_a = host.bls12_381_g1_add(b, a)?; + assert_eq!( + host.obj_cmp(a_plus_b.into(), b_plus_a.into())?, + Ordering::Equal as i64 + ); + } + // 6. associative (a + b) + c = a + (b + c) + { + let a = sample_g1(&host)?; + let b = sample_g1(&host)?; + let c = sample_g1(&host)?; + let aplusb = host.bls12_381_g1_add(a, b)?; + let aplusb_plus_c = host.bls12_381_g1_add(aplusb, c)?; + let bplusc = host.bls12_381_g1_add(b, c)?; + let a_plus_bplusc = host.bls12_381_g1_add(a, bplusc)?; + assert_eq!( + host.obj_cmp(aplusb_plus_c.into(), a_plus_bplusc.into())?, + Ordering::Equal as i64 + ); + } + // 7. a - a = zero + { + let a = sample_g1(&host)?; + let neg_a = neg_g1(a.clone(), &host)?; + let res = host.bls12_381_g1_add(a, neg_a)?; + let zero = g1_zero(&host)?; + assert_eq!( + host.obj_cmp(res.into(), zero.into())?, + Ordering::Equal as i64 + ); + } + Ok(()) +} + +#[test] +fn g1_mul() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // 2. lhs * 0 = 0 + { + let lhs = sample_g1(&host)?; + let rhs = host.obj_from_u256_pieces(0, 0, 0, 0)?; + let res = host.bls12_381_g1_mul(lhs, rhs.into())?; + let zero = g1_zero(&host)?; + assert_eq!( + host.obj_cmp(res.into(), zero.into())?, + Ordering::Equal as i64 + ); + } + // 3. lhs * 1 = lhs + { + let lhs = sample_g1(&host)?; + let rhs = U256Val::from_u32(1); + let res = host.bls12_381_g1_mul(lhs, rhs.into())?; + assert_eq!( + host.obj_cmp(res.into(), lhs.into())?, + Ordering::Equal as i64 + ); + } + // 4. associative P * a * b = P * b * a + { + let p = sample_g1(&host)?; + let a = sample_fr(&host)?; + let b = sample_fr(&host)?; + let pa = host.bls12_381_g1_mul(p, a)?; + let pab = host.bls12_381_g1_mul(pa, b)?; + let pb = host.bls12_381_g1_mul(p, b)?; + let pba = host.bls12_381_g1_mul(pb, a)?; + assert_eq!( + host.obj_cmp(pab.into(), pba.into())?, + Ordering::Equal as i64 + ); + } + Ok(()) +} + +#[test] +fn g1_msm() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // vector lengths are zero + { + let vp = host.vec_new()?; + let vs = host.vec_new()?; + assert!(HostError::result_matches_err( + host.bls12_381_g1_msm(vp, vs), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // vector lengths not equal + { + let vp = sample_host_vec::(&host, G1_SERIALIZED_SIZE, 2)?; + let vs = sample_fr_vec(&host, 3)?; + assert!(HostError::result_matches_err( + host.bls12_381_g1_msm(vp, vs), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // vector g1 not valid + { + let vp = host.vec_new_from_slice(&[ + sample_g1(&host)?.to_val(), + invalid_g1(&host, InvalidPointTypes::PointNotInSubgroup)?.to_val(), + sample_g1(&host)?.to_val(), + ])?; + let vs = sample_fr_vec(&host, 3)?; + assert!(HostError::result_matches_err( + host.bls12_381_g1_msm(vp, vs), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // vector of zero points result zero + { + let vp = host.vec_new_from_slice(&[g1_zero(&host)?.to_val(); 3])?; + let vs = sample_fr_vec(&host, 3)?; + let res = host.bls12_381_g1_msm(vp, vs)?; + assert_eq!( + host.obj_cmp(res.into(), g1_zero(&host)?.into())?, + Ordering::Equal as i64 + ); + } + // vector of zero scalars result in zero point + { + let vp = sample_host_vec::(&host, G1_SERIALIZED_SIZE, 3)?; + let vs = host.vec_new_from_slice(&[U256Val::from_u32(0).to_val(); 3])?; + let res = host.bls12_381_g1_msm(vp, vs)?; + assert_eq!( + host.obj_cmp(res.into(), g1_zero(&host)?.into())?, + Ordering::Equal as i64 + ); + } + // 6. g1 * (1) + g1 (-1) = 0 + { + let pt = sample_g1(&host)?; + let zero = g1_zero(&host)?; + assert_ne!( + host.obj_cmp(pt.into(), zero.into())?, + Ordering::Equal as i64 + ); + let neg_pt = neg_g1(pt, &host)?; + let vp = host.vec_new_from_slice(&[pt.to_val(), neg_pt.to_val()])?; + let vs = host.vec_new_from_slice(&[U256Val::from_u32(1).to_val(); 2])?; + let res = host.bls12_381_g1_msm(vp, vs)?; + assert_eq!( + host.obj_cmp(res.into(), g1_zero(&host)?.into())?, + Ordering::Equal as i64 + ); + } + // 7. associative: shuffle points orders results stay the same + { + host.budget_ref().reset_default()?; + let mut vp = vec![ + sample_g1(&host)?.to_val(), + sample_g1(&host)?.to_val(), + sample_g1(&host)?.to_val(), + sample_g1(&host)?.to_val(), + ]; + let mut vs = vec![ + sample_fr(&host)?.to_val(), + sample_fr(&host)?.to_val(), + sample_fr(&host)?.to_val(), + sample_fr(&host)?.to_val(), + ]; + let ref_res = + host.bls12_381_g1_msm(host.vec_new_from_slice(&vp)?, host.vec_new_from_slice(&vs)?)?; + let mut rng = StdRng::from_seed([0xff; 32]); + let mut shuffle_with_order = |v1: &mut Vec, v2: &mut Vec| { + use rand::seq::SliceRandom; + assert_eq!(v1.len(), v2.len()); + let mut indices: Vec = (0..v1.len()).collect(); + indices.shuffle(&mut rng); + let v1_shuffled: Vec = indices.iter().map(|&i| v1[i]).collect(); + let v2_shuffled: Vec = indices.iter().map(|&i| v2[i]).collect(); + *v1 = v1_shuffled; + *v2 = v2_shuffled; + }; + + for _ in 0..10 { + shuffle_with_order(&mut vp, &mut vs); + let vp_obj = host.vec_new_from_slice(&vp)?; + let vs_obj = host.vec_new_from_slice(&vs)?; + let res = host.bls12_381_g1_msm(vp_obj, vs_obj)?; + assert_eq!( + host.obj_cmp(res.into(), ref_res.into())?, + Ordering::Equal as i64 + ); + } + } + // 8. msm result is same as invidial mul and add + { + host.budget_ref().reset_default()?; + let vp = sample_host_vec::(&host, G1_SERIALIZED_SIZE, 10)?; + let vs = sample_fr_vec(&host, 10)?; + let ref_res = host.bls12_381_g1_msm(vp, vs)?; + let mut res = g1_zero(&host)?; + for i in 0..10 { + let p: BytesObject = host.vec_get(vp, U32Val::from(i))?.try_into()?; + let s: U256Val = host.vec_get(vs, U32Val::from(i))?.try_into()?; + let rhs = host.bls12_381_g1_mul(p, s)?; + res = host.bls12_381_g1_add(res, rhs)?; + } + assert_eq!( + host.obj_cmp(res.into(), ref_res.into())?, + Ordering::Equal as i64 + ); + } + Ok(()) +} + +#[test] +fn map_fp_to_g1() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // invalid fp: wrongth length + { + let p1 = invalid_fp(&host, InvalidPointTypes::TooFewBytes)?; + assert!(HostError::result_matches_err( + host.bls12_381_map_fp_to_g1(p1), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + let p2 = invalid_fp(&host, InvalidPointTypes::TooManyBytes)?; + assert!(HostError::result_matches_err( + host.bls12_381_map_fp_to_g1(p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // Test cases from https://datatracker.ietf.org/doc/html/rfc9380#name-bls12381g1_xmdsha-256_sswu_ + // To interpret the results, understand the steps it takes to hash a msg to curve + // 1. u = hash_to_field(msg, 2) + // 2. Q0 = map_to_curve(u[0]) + // 3. Q1 = map_to_curve(u[1]) + // 4. R = Q0 + Q1 # Point addition + // 5. P = clear_cofactor(R) + // 6. return P + { + host.budget_ref().reset_default()?; + let test_map_fp_to_curve_inner = |u: String, q: Point| -> Result<(), HostError> { + let mut q_bytes = [0u8; 96]; + q_bytes[0..48].copy_from_slice(&parse_hex(&q.x)); + q_bytes[48..].copy_from_slice(&parse_hex(&q.y)); + let g1 = host.bytes_new_from_slice(&q_bytes)?; + let fp = host.bytes_new_from_slice(&parse_hex(&u))?; + let res = host.bls12_381_map_fp_to_g1(fp)?; + assert_eq!(host.obj_cmp(res.into(), g1.into())?, Ordering::Equal as i64); + Ok(()) + }; + + let test_suite: HashToCurveTestSuite = serde_json::from_slice( + &std::fs::read("./src/test/data/BLS12381G1_XMD_SHA-256_SSWU_RO_.json").unwrap(), + ) + .unwrap(); + println!("{test_suite:?}"); + for case in test_suite.vectors { + let [u0, u1] = case.u; + test_map_fp_to_curve_inner(u0, case.Q0)?; + test_map_fp_to_curve_inner(u1, case.Q1)?; + } + } + Ok(()) +} + +#[test] +fn hash_to_g1() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // 1. invalid input dst length = 0 + { + let dst = host.bytes_new_from_slice(&[])?; + let msg = host.bytes_new_from_slice("some message".as_bytes())?; + assert!(HostError::result_matches_err( + host.bls12_381_hash_to_g1(msg, dst), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 2. invalid input dst length > 255 + { + let dst = host.bytes_new_from_slice(&[0; 256])?; + let msg = host.bytes_new_from_slice("some message".as_bytes())?; + assert!(HostError::result_matches_err( + host.bls12_381_hash_to_g1(msg, dst), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 3. test vectors from https://datatracker.ietf.org/doc/html/rfc9380#name-bls12381g1_xmdsha-256_sswu_ + { + let test_suite: HashToCurveTestSuite = serde_json::from_slice( + &std::fs::read("./src/test/data/BLS12381G1_XMD_SHA-256_SSWU_RO_.json").unwrap(), + ) + .unwrap(); + let dst = host.bytes_new_from_slice(test_suite.dst.as_bytes())?; + let parse_g1 = |p: Point| -> Result { + let mut p_bytes = [0u8; 96]; + p_bytes[0..48].copy_from_slice(&parse_hex(&p.x)); + p_bytes[48..].copy_from_slice(&parse_hex(&p.y)); + host.bytes_new_from_slice(&p_bytes) + }; + + for case in test_suite.vectors { + let msg = host.bytes_new_from_slice(case.msg.as_bytes())?; + let g1 = host.bls12_381_hash_to_g1(msg, dst)?; + let g1_ref = parse_g1(case.P)?; + assert_eq!( + host.obj_cmp(g1.into(), g1_ref.into())?, + Ordering::Equal as i64 + ); + } + } + Ok(()) +} + +// g2 tests +#[test] +fn g2_add() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // invalid p1 + { + let p2 = sample_g2(&host)?; + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(invalid_g2(&host, InvalidPointTypes::TooManyBytes)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(invalid_g2(&host, InvalidPointTypes::TooFewBytes)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add( + invalid_g2(&host, InvalidPointTypes::CompressionFlagSet)?, + p2 + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add( + invalid_g2(&host, InvalidPointTypes::InfinityFlagSetBitsNotAllZero)?, + p2 + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(invalid_g2(&host, InvalidPointTypes::SortFlagSet)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(invalid_g2(&host, InvalidPointTypes::PointNotOnCurve)?, p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add( + invalid_g2(&host, InvalidPointTypes::PointNotInSubgroup)?, + p2 + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // invalid p2 + { + let p1 = sample_g2(&host)?; + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(p1, invalid_g2(&host, InvalidPointTypes::TooManyBytes)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(p1, invalid_g2(&host, InvalidPointTypes::TooFewBytes)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add( + p1, + invalid_g2(&host, InvalidPointTypes::CompressionFlagSet)? + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add( + p1, + invalid_g2(&host, InvalidPointTypes::InfinityFlagSetBitsNotAllZero)? + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(p1, invalid_g2(&host, InvalidPointTypes::SortFlagSet)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add(p1, invalid_g2(&host, InvalidPointTypes::PointNotOnCurve)?), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + assert!(HostError::result_matches_err( + host.bls12_381_g2_add( + p1, + invalid_g2(&host, InvalidPointTypes::PointNotInSubgroup)? + ), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 3. lhs.add(zero) = lhs + { + let p1 = sample_g2(&host)?; + let res = host.bls12_381_g2_add(p1, g2_zero(&host)?)?; + assert_eq!(host.obj_cmp(p1.into(), res.into())?, Ordering::Equal as i64); + } + // 4. zero.add(rhs) = rhs + { + let p2 = sample_g2(&host)?; + let res = host.bls12_381_g2_add(g2_zero(&host)?, p2)?; + assert_eq!(host.obj_cmp(p2.into(), res.into())?, Ordering::Equal as i64); + } + // 5. communitive a + b = b + a + { + let a = sample_g2(&host)?; + let b = sample_g2(&host)?; + let a_plus_b = host.bls12_381_g2_add(a, b)?; + let b_plus_a = host.bls12_381_g2_add(b, a)?; + assert_eq!( + host.obj_cmp(a_plus_b.into(), b_plus_a.into())?, + Ordering::Equal as i64 + ); + } + // 6. associative (a + b) + c = a + (b + c) + { + let a = sample_g2(&host)?; + let b = sample_g2(&host)?; + let c = sample_g2(&host)?; + let aplusb = host.bls12_381_g2_add(a, b)?; + let aplusb_plus_c = host.bls12_381_g2_add(aplusb, c)?; + let bplusc = host.bls12_381_g2_add(b, c)?; + let a_plus_bplusc = host.bls12_381_g2_add(a, bplusc)?; + assert_eq!( + host.obj_cmp(aplusb_plus_c.into(), a_plus_bplusc.into())?, + Ordering::Equal as i64 + ); + } + // 7. a - a = zero + { + let a = sample_g2(&host)?; + let neg_a = neg_g2(a.clone(), &host)?; + let res = host.bls12_381_g2_add(a, neg_a)?; + let zero = g2_zero(&host)?; + assert_eq!( + host.obj_cmp(res.into(), zero.into())?, + Ordering::Equal as i64 + ); + } + Ok(()) +} + +#[test] +fn g2_mul() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // 2. lhs * 0 = 0 + { + let lhs = sample_g2(&host)?; + let rhs = host.obj_from_u256_pieces(0, 0, 0, 0)?; + let res = host.bls12_381_g2_mul(lhs, rhs.into())?; + let zero = g2_zero(&host)?; + assert_eq!( + host.obj_cmp(res.into(), zero.into())?, + Ordering::Equal as i64 + ); + } + // 3. lhs * 1 = lhs + { + let lhs = sample_g2(&host)?; + let rhs = U256Val::from_u32(1); + let res = host.bls12_381_g2_mul(lhs, rhs.into())?; + assert_eq!( + host.obj_cmp(res.into(), lhs.into())?, + Ordering::Equal as i64 + ); + } + // 4. associative P * a * b = P * b * a + { + let p = sample_g2(&host)?; + let a = sample_fr(&host)?; + let b = sample_fr(&host)?; + let pa = host.bls12_381_g2_mul(p, a)?; + let pab = host.bls12_381_g2_mul(pa, b)?; + let pb = host.bls12_381_g2_mul(p, b)?; + let pba = host.bls12_381_g2_mul(pb, a)?; + assert_eq!( + host.obj_cmp(pab.into(), pba.into())?, + Ordering::Equal as i64 + ); + } + Ok(()) +} + +#[test] +fn g2_msm() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // vector lengths are zero + { + let vp = host.vec_new()?; + let vs = host.vec_new()?; + assert!(HostError::result_matches_err( + host.bls12_381_g2_msm(vp, vs), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // vector lengths not equal + { + let vp = sample_host_vec::(&host, G2_SERIALIZED_SIZE, 2)?; + let vs = sample_fr_vec(&host, 3)?; + assert!(HostError::result_matches_err( + host.bls12_381_g2_msm(vp, vs), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // vector g2 not valid + { + let vp = host.vec_new_from_slice(&[ + sample_g2(&host)?.to_val(), + invalid_g2(&host, InvalidPointTypes::PointNotInSubgroup)?.to_val(), + sample_g2(&host)?.to_val(), + ])?; + let vs = sample_fr_vec(&host, 3)?; + assert!(HostError::result_matches_err( + host.bls12_381_g2_msm(vp, vs), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // vector of zero points result zero + { + let vp = host.vec_new_from_slice(&[g2_zero(&host)?.to_val(); 3])?; + let vs = sample_fr_vec(&host, 3)?; + let res = host.bls12_381_g2_msm(vp, vs)?; + assert_eq!( + host.obj_cmp(res.into(), g2_zero(&host)?.into())?, + Ordering::Equal as i64 + ); + } + // vector of zero scalars result in zero point + { + let vp = sample_host_vec::(&host, G2_SERIALIZED_SIZE, 3)?; + let vs = host.vec_new_from_slice(&[U256Val::from_u32(0).to_val(); 3])?; + let res = host.bls12_381_g2_msm(vp, vs)?; + assert_eq!( + host.obj_cmp(res.into(), g2_zero(&host)?.into())?, + Ordering::Equal as i64 + ); + } + // 6. g2 * (1) + g2 (-1) = 0 + { + let pt = sample_g2(&host)?; + let zero = g2_zero(&host)?; + assert_ne!( + host.obj_cmp(pt.into(), zero.into())?, + Ordering::Equal as i64 + ); + let neg_pt = neg_g2(pt, &host)?; + let vp = host.vec_new_from_slice(&[pt.to_val(), neg_pt.to_val()])?; + let vs = host.vec_new_from_slice(&[U256Val::from_u32(1).to_val(); 2])?; + let res = host.bls12_381_g2_msm(vp, vs)?; + assert_eq!( + host.obj_cmp(res.into(), g2_zero(&host)?.into())?, + Ordering::Equal as i64 + ); + } + // 7. associative: shuffle points orders results stay the same + { + let mut vp = vec![ + sample_g2(&host)?.to_val(), + sample_g2(&host)?.to_val(), + sample_g2(&host)?.to_val(), + sample_g2(&host)?.to_val(), + ]; + let mut vs = vec![ + sample_fr(&host)?.to_val(), + sample_fr(&host)?.to_val(), + sample_fr(&host)?.to_val(), + sample_fr(&host)?.to_val(), + ]; + let ref_res = + host.bls12_381_g2_msm(host.vec_new_from_slice(&vp)?, host.vec_new_from_slice(&vs)?)?; + let mut rng = StdRng::from_seed([0xff; 32]); + let mut shuffle_with_order = |v1: &mut Vec, v2: &mut Vec| { + use rand::seq::SliceRandom; + assert_eq!(v1.len(), v2.len()); + let mut indices: Vec = (0..v1.len()).collect(); + indices.shuffle(&mut rng); + let v1_shuffled: Vec = indices.iter().map(|&i| v1[i]).collect(); + let v2_shuffled: Vec = indices.iter().map(|&i| v2[i]).collect(); + *v1 = v1_shuffled; + *v2 = v2_shuffled; + }; + + for _ in 0..10 { + host.budget_ref().reset_default()?; + shuffle_with_order(&mut vp, &mut vs); + let vp_obj = host.vec_new_from_slice(&vp)?; + let vs_obj = host.vec_new_from_slice(&vs)?; + let res = host.bls12_381_g2_msm(vp_obj, vs_obj)?; + assert_eq!( + host.obj_cmp(res.into(), ref_res.into())?, + Ordering::Equal as i64 + ); + } + } + // 8. msm result is same as invidial mul and add + { + host.budget_ref().reset_default()?; + let vp = sample_host_vec::(&host, G2_SERIALIZED_SIZE, 5)?; + let vs = sample_fr_vec(&host, 5)?; + let ref_res = host.bls12_381_g2_msm(vp, vs)?; + let mut res = g2_zero(&host)?; + for i in 0..5 { + let p: BytesObject = host.vec_get(vp, U32Val::from(i))?.try_into()?; + let s: U256Val = host.vec_get(vs, U32Val::from(i))?.try_into()?; + let rhs = host.bls12_381_g2_mul(p, s)?; + res = host.bls12_381_g2_add(res, rhs)?; + } + assert_eq!( + host.obj_cmp(res.into(), ref_res.into())?, + Ordering::Equal as i64 + ); + } + Ok(()) +} + +#[test] +fn map_fp2_to_g2() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // invalid fp2: wrongth length + { + let p1 = invalid_fp2(&host, InvalidPointTypes::TooFewBytes)?; + assert!(HostError::result_matches_err( + host.bls12_381_map_fp2_to_g2(p1), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + let p2 = invalid_fp2(&host, InvalidPointTypes::TooManyBytes)?; + assert!(HostError::result_matches_err( + host.bls12_381_map_fp2_to_g2(p2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // Test cases from https://datatracker.ietf.org/doc/html/rfc9380#name-bls12381g2_xmdsha-256_sswu_ + // To interpret the results, understand the steps it takes to hash a msg to curve + // 1. u = hash_to_field(msg, 2) + // 2. Q0 = map_to_curve(u[0]) + // 3. Q1 = map_to_curve(u[1]) + // 4. R = Q0 + Q1 # Point addition + // 5. P = clear_cofactor(R) + // 6. return P + { + host.budget_ref().reset_default()?; + let test_map_fp2_to_curve_inner = |u: String, q: Point| -> Result<(), HostError> { + let g2 = parse_g2_point_test_case(&host, q)?; + let mut u_bytes = [0u8; 96]; + let uu: Vec<_> = u.split(',').collect(); + u_bytes[0..48].copy_from_slice(&parse_hex(uu[1])); + u_bytes[48..96].copy_from_slice(&parse_hex(uu[0])); + let fp2 = host.bytes_new_from_slice(&u_bytes)?; + let res = host.bls12_381_map_fp2_to_g2(fp2)?; + assert_eq!(host.obj_cmp(res.into(), g2.into())?, Ordering::Equal as i64); + Ok(()) + }; + + let test_suite: HashToCurveTestSuite = serde_json::from_slice( + &std::fs::read("./src/test/data/BLS12381G2_XMD_SHA-256_SSWU_RO_.json").unwrap(), + ) + .unwrap(); + for case in test_suite.vectors { + let [u0, u1] = case.u; + test_map_fp2_to_curve_inner(u0, case.Q0)?; + test_map_fp2_to_curve_inner(u1, case.Q1)?; + } + } + Ok(()) +} + +#[test] +fn hash_to_g2() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // 1. invalid input dst length = 0 + { + let dst = host.bytes_new_from_slice(&[])?; + let msg = host.bytes_new_from_slice("some message".as_bytes())?; + assert!(HostError::result_matches_err( + host.bls12_381_hash_to_g2(msg, dst), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 2. invalid input dst length > 255 + { + let dst = host.bytes_new_from_slice(&[0; 256])?; + let msg = host.bytes_new_from_slice("some message".as_bytes())?; + assert!(HostError::result_matches_err( + host.bls12_381_hash_to_g2(msg, dst), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 3. test vectors from https://datatracker.ietf.org/doc/html/rfc9380#name-bls12381g2_xmdsha-256_sswu_ + { + let test_suite: HashToCurveTestSuite = serde_json::from_slice( + &std::fs::read("./src/test/data/BLS12381G2_XMD_SHA-256_SSWU_RO_.json").unwrap(), + ) + .unwrap(); + let dst = host.bytes_new_from_slice(test_suite.dst.as_bytes())?; + for case in test_suite.vectors { + let msg = host.bytes_new_from_slice(case.msg.as_bytes())?; + let g2 = host.bls12_381_hash_to_g2(msg, dst)?; + let g2_ref = parse_g2_point_test_case(&host, case.P)?; + assert_eq!( + host.obj_cmp(g2.into(), g2_ref.into())?, + Ordering::Equal as i64 + ); + } + } + Ok(()) +} + +// pairing checks +#[test] +fn pairing() -> Result<(), HostError> { + let host = observe_host!(Host::test_host()); + host.enable_debug()?; + // 1. vector lengths don't match + { + let vp1 = sample_host_vec::(&host, G1_SERIALIZED_SIZE, 3)?; + let vp2 = sample_host_vec::(&host, G2_SERIALIZED_SIZE, 2)?; + assert!(HostError::result_matches_err( + host.bls12_381_multi_pairing_check(vp1, vp2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 2. vector length is 0 + { + let vp1 = sample_host_vec::(&host, G1_SERIALIZED_SIZE, 0)?; + let vp2 = sample_host_vec::(&host, G2_SERIALIZED_SIZE, 0)?; + assert!(HostError::result_matches_err( + host.bls12_381_multi_pairing_check(vp1, vp2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 3. any g1 is invalid + { + let mut vp1 = sample_host_vec::(&host, G1_SERIALIZED_SIZE, 3)?; + vp1 = host.vec_put( + vp1, + U32Val::from(1), + sample_g1_not_in_subgroup(&host)?.to_val(), + )?; + let vp2 = sample_host_vec::(&host, G2_SERIALIZED_SIZE, 2)?; + assert!(HostError::result_matches_err( + host.bls12_381_multi_pairing_check(vp1, vp2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 4. any g2 is invalid + { + let vp1 = sample_host_vec::(&host, G1_SERIALIZED_SIZE, 3)?; + let mut vp2 = sample_host_vec::(&host, G2_SERIALIZED_SIZE, 3)?; + vp2 = host.vec_put( + vp2, + U32Val::from(1), + sample_g2_not_on_curve(&host)?.to_val(), + )?; + assert!(HostError::result_matches_err( + host.bls12_381_multi_pairing_check(vp1, vp2), + (ScErrorType::Crypto, ScErrorCode::InvalidInput) + )); + } + // 5. e(P, Q+R) = e(P, Q)*e(P, R) + { + host.budget_ref().reset_default()?; + let p = sample_g1(&host)?; + let neg_p = neg_g1(p, &host)?; + let q = sample_g2(&host)?; + let r = sample_g2(&host)?; + let q_plus_r = host.bls12_381_g2_add(q, r)?; + + //check e(-P, Q+R)*e(P, Q)*e(P, R) == 1 + let g1_vec = host.vec_new_from_slice(&[neg_p.to_val(), p.to_val(), p.to_val()])?; + let g2_vec = host.vec_new_from_slice(&[q_plus_r.to_val(), q.to_val(), r.to_val()])?; + let res = host.bls12_381_multi_pairing_check(g1_vec, g2_vec)?; + assert!(res.as_val().is_true()) + } + // 6. e(P+S, R) = e(P, R)*e(S, R) + { + host.budget_ref().reset_default()?; + let p = sample_g1(&host)?; + let s = sample_g1(&host)?; + let r = sample_g2(&host)?; + let neg_r = neg_g2(r, &host)?; + let p_plus_s = host.bls12_381_g1_add(p, s)?; + // check e(P+S, -R) * e(P, R)*e(S, R) == 1 + let g1_vec = host.vec_new_from_slice(&[p_plus_s.to_val(), p.to_val(), s.to_val()])?; + let g2_vec = host.vec_new_from_slice(&[neg_r.to_val(), r.to_val(), r.to_val()])?; + let res = host.bls12_381_multi_pairing_check(g1_vec, g2_vec)?; + assert!(res.as_val().is_true()) + } + + // 7. e([a]P, [b]Q) = e([b]P, [a]Q) = e([ab]P, Q)= e(P, [ab]Q) + { + host.budget_ref().reset_default()?; + let a = sample_fr(&host)?; + let b = sample_fr(&host)?; + let p = sample_g1(&host)?; + let neg_p = neg_g1(p, &host)?; + let q = sample_g2(&host)?; + let neg_q = neg_g2(q, &host)?; + let a_p = host.bls12_381_g1_mul(p, a)?; + let b_p = host.bls12_381_g1_mul(p, b)?; + let a_q = host.bls12_381_g2_mul(q, a)?; + let b_q = host.bls12_381_g2_mul(q, b)?; + let ab = host.bls12_381_fr_mul(a, b)?; + let ab_p = host.bls12_381_g1_mul(p, ab)?; + let ab_q = host.bls12_381_g2_mul(q, ab)?; + // check e([a]P, [b]Q) * e([b]P, [a]Q) * e([ab]P, -Q) * e(-P, [ab]Q) == 1 + let g1_vec = + host.vec_new_from_slice(&[a_p.to_val(), b_p.to_val(), ab_p.to_val(), neg_p.to_val()])?; + let g2_vec = + host.vec_new_from_slice(&[b_q.to_val(), a_q.to_val(), neg_q.to_val(), ab_q.to_val()])?; + let res = host.bls12_381_multi_pairing_check(g1_vec, g2_vec)?; + assert!(res.as_val().is_true()) + } + Ok(()) +} + +// fr arithmetics + +// serialization roundtrip + +// bls signature + +// ethereum test + +// fuzzing tests diff --git a/soroban-env-host/src/test/data/BLS12381G1_XMD_SHA-256_SSWU_RO_.json b/soroban-env-host/src/test/data/BLS12381G1_XMD_SHA-256_SSWU_RO_.json new file mode 100644 index 000000000..46c7574f0 --- /dev/null +++ b/soroban-env-host/src/test/data/BLS12381G1_XMD_SHA-256_SSWU_RO_.json @@ -0,0 +1,115 @@ +{ + "L": "0x40", + "Z": "0xb", + "ciphersuite": "BLS12381G1_XMD:SHA-256_SSWU_RO_", + "curve": "BLS12-381 G1", + "dst": "QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_", + "expand": "XMD", + "field": { + "m": "0x1", + "p": "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" + }, + "hash": "sha256", + "k": "0x80", + "map": { + "name": "SSWU" + }, + "randomOracle": true, + "vectors": [ + { + "P": { + "x": "0x052926add2207b76ca4fa57a8734416c8dc95e24501772c814278700eed6d1e4e8cf62d9c09db0fac349612b759e79a1", + "y": "0x08ba738453bfed09cb546dbb0783dbb3a5f1f566ed67bb6be0e8c67e2e81a4cc68ee29813bb7994998f3eae0c9c6a265" + }, + "Q0": { + "x": "0x11a3cce7e1d90975990066b2f2643b9540fa40d6137780df4e753a8054d07580db3b7f1f03396333d4a359d1fe3766fe", + "y": "0x0eeaf6d794e479e270da10fdaf768db4c96b650a74518fc67b04b03927754bac66f3ac720404f339ecdcc028afa091b7" + }, + "Q1": { + "x": "0x160003aaf1632b13396dbad518effa00fff532f604de1a7fc2082ff4cb0afa2d63b2c32da1bef2bf6c5ca62dc6b72f9c", + "y": "0x0d8bb2d14e20cf9f6036152ed386d79189415b6d015a20133acb4e019139b94e9c146aaad5817f866c95d609a361735e" + }, + "msg": "", + "u": [ + "0x0ba14bd907ad64a016293ee7c2d276b8eae71f25a4b941eece7b0d89f17f75cb3ae5438a614fb61d6835ad59f29c564f", + "0x019b9bd7979f12657976de2884c7cce192b82c177c80e0ec604436a7f538d231552f0d96d9f7babe5fa3b19b3ff25ac9" + ] + }, + { + "P": { + "x": "0x03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903", + "y": "0x0b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfaaa154fa6b88176c229f2885d" + }, + "Q0": { + "x": "0x125435adce8e1cbd1c803e7123f45392dc6e326d292499c2c45c5865985fd74fe8f042ecdeeec5ecac80680d04317d80", + "y": "0x0e8828948c989126595ee30e4f7c931cbd6f4570735624fd25aef2fa41d3f79cfb4b4ee7b7e55a8ce013af2a5ba20bf2" + }, + "Q1": { + "x": "0x11def93719829ecda3b46aa8c31fc3ac9c34b428982b898369608e4f042babee6c77ab9218aad5c87ba785481eff8ae4", + "y": "0x0007c9cef122ccf2efd233d6eb9bfc680aa276652b0661f4f820a653cec1db7ff69899f8e52b8e92b025a12c822a6ce6" + }, + "msg": "abc", + "u": [ + "0x0d921c33f2bad966478a03ca35d05719bdf92d347557ea166e5bba579eea9b83e9afa5c088573c2281410369fbd32951", + "0x003574a00b109ada2f26a37a91f9d1e740dffd8d69ec0c35e1e9f4652c7dba61123e9dd2e76c655d956e2b3462611139" + ] + }, + { + "P": { + "x": "0x11e0b079dea29a68f0383ee94fed1b940995272407e3bb916bbf268c263ddd57a6a27200a784cbc248e84f357ce82d98", + "y": "0x03a87ae2caf14e8ee52e51fa2ed8eefe80f02457004ba4d486d6aa1f517c0889501dc7413753f9599b099ebcbbd2d709" + }, + "Q0": { + "x": "0x08834484878c217682f6d09a4b51444802fdba3d7f2df9903a0ddadb92130ebbfa807fffa0eabf257d7b48272410afff", + "y": "0x0b318f7ecf77f45a0f038e62d7098221d2dbbca2a394164e2e3fe953dc714ac2cde412d8f2d7f0c03b259e6795a2508e" + }, + "Q1": { + "x": "0x158418ed6b27e2549f05531a8281b5822b31c3bf3144277fbb977f8d6e2694fedceb7011b3c2b192f23e2a44b2bd106e", + "y": "0x1879074f344471fac5f839e2b4920789643c075792bec5af4282c73f7941cda5aa77b00085eb10e206171b9787c4169f" + }, + "msg": "abcdef0123456789", + "u": [ + "0x062d1865eb80ebfa73dcfc45db1ad4266b9f3a93219976a3790ab8d52d3e5f1e62f3b01795e36834b17b70e7b76246d4", + "0x0cdc3e2f271f29c4ff75020857ce6c5d36008c9b48385ea2f2bf6f96f428a3deb798aa033cd482d1cdc8b30178b08e3a" + ] + }, + { + "P": { + "x": "0x15f68eaa693b95ccb85215dc65fa81038d69629f70aeee0d0f677cf22285e7bf58d7cb86eefe8f2e9bc3f8cb84fac488", + "y": "0x1807a1d50c29f430b8cafc4f8638dfeeadf51211e1602a5f184443076715f91bb90a48ba1e370edce6ae1062f5e6dd38" + }, + "Q0": { + "x": "0x0cbd7f84ad2c99643fea7a7ac8f52d63d66cefa06d9a56148e58b984b3dd25e1f41ff47154543343949c64f88d48a710", + "y": "0x052c00e4ed52d000d94881a5638ae9274d3efc8bc77bc0e5c650de04a000b2c334a9e80b85282a00f3148dfdface0865" + }, + "Q1": { + "x": "0x06493fb68f0d513af08be0372f849436a787e7b701ae31cb964d968021d6ba6bd7d26a38aaa5a68e8c21a6b17dc8b579", + "y": "0x02e98f2ccf5802b05ffaac7c20018bc0c0b2fd580216c4aa2275d2909dc0c92d0d0bdc979226adeb57a29933536b6bb4" + }, + "msg": "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "u": [ + "0x010476f6a060453c0b1ad0b628f3e57c23039ee16eea5e71bb87c3b5419b1255dc0e5883322e563b84a29543823c0e86", + "0x0b1a912064fb0554b180e07af7e787f1f883a0470759c03c1b6509eb8ce980d1670305ae7b928226bb58fdc0a419f46e" + ] + }, + { + "P": { + "x": "0x082aabae8b7dedb0e78aeb619ad3bfd9277a2f77ba7fad20ef6aabdc6c31d19ba5a6d12283553294c1825c4b3ca2dcfe", + "y": "0x05b84ae5a942248eea39e1d91030458c40153f3b654ab7872d779ad1e942856a20c438e8d99bc8abfbf74729ce1f7ac8" + }, + "Q0": { + "x": "0x0cf97e6dbd0947857f3e578231d07b309c622ade08f2c08b32ff372bd90db19467b2563cc997d4407968d4ac80e154f8", + "y": "0x127f0cddf2613058101a5701f4cb9d0861fd6c2a1b8e0afe194fccf586a3201a53874a2761a9ab6d7220c68661a35ab3" + }, + "Q1": { + "x": "0x092f1acfa62b05f95884c6791fba989bbe58044ee6355d100973bf9553ade52b47929264e6ae770fb264582d8dce512a", + "y": "0x028e6d0169a72cfedb737be45db6c401d3adfb12c58c619c82b93a5dfcccef12290de530b0480575ddc8397cda0bbebf" + }, + "msg": "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "u": [ + "0x0a8ffa7447f6be1c5a2ea4b959c9454b431e29ccc0802bc052413a9c5b4f9aac67a93431bd480d15be1e057c8a08e8c6", + "0x05d487032f602c90fa7625dbafe0f4a49ef4a6b0b33d7bb349ff4cf5410d297fd6241876e3e77b651cfc8191e40a68b7" + ] + } + ] +} diff --git a/soroban-env-host/src/test/data/BLS12381G2_XMD_SHA-256_SSWU_RO_.json b/soroban-env-host/src/test/data/BLS12381G2_XMD_SHA-256_SSWU_RO_.json new file mode 100644 index 000000000..5807ee6f6 --- /dev/null +++ b/soroban-env-host/src/test/data/BLS12381G2_XMD_SHA-256_SSWU_RO_.json @@ -0,0 +1,115 @@ +{ + "L": "0x40", + "Z": "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaa9,0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa", + "ciphersuite": "BLS12381G2_XMD:SHA-256_SSWU_RO_", + "curve": "BLS12-381 G2", + "dst": "QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_", + "expand": "XMD", + "field": { + "m": "0x2", + "p": "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" + }, + "hash": "sha256", + "k": "0x80", + "map": { + "name": "SSWU" + }, + "randomOracle": true, + "vectors": [ + { + "P": { + "x": "0x0141ebfbdca40eb85b87142e130ab689c673cf60f1a3e98d69335266f30d9b8d4ac44c1038e9dcdd5393faf5c41fb78a,0x05cb8437535e20ecffaef7752baddf98034139c38452458baeefab379ba13dff5bf5dd71b72418717047f5b0f37da03d", + "y": "0x0503921d7f6a12805e72940b963c0cf3471c7b2a524950ca195d11062ee75ec076daf2d4bc358c4b190c0c98064fdd92,0x12424ac32561493f3fe3c260708a12b7c620e7be00099a974e259ddc7d1f6395c3c811cdd19f1e8dbf3e9ecfdcbab8d6" + }, + "Q0": { + "x": "0x019ad3fc9c72425a998d7ab1ea0e646a1f6093444fc6965f1cad5a3195a7b1e099c050d57f45e3fa191cc6d75ed7458c,0x171c88b0b0efb5eb2b88913a9e74fe111a4f68867b59db252ce5868af4d1254bfab77ebde5d61cd1a86fb2fe4a5a1c1d", + "y": "0x0ba10604e62bdd9eeeb4156652066167b72c8d743b050fb4c1016c31b505129374f76e03fa127d6a156213576910fef3,0x0eb22c7a543d3d376e9716a49b72e79a89c9bfe9feee8533ed931cbb5373dde1fbcd7411d8052e02693654f71e15410a" + }, + "Q1": { + "x": "0x113d2b9cd4bd98aee53470b27abc658d91b47a78a51584f3d4b950677cfb8a3e99c24222c406128c91296ef6b45608be,0x13855912321c5cb793e9d1e88f6f8d342d49c0b0dbac613ee9e17e3c0b3c97dfbb5a49cc3fb45102fdbaf65e0efe2632", + "y": "0x0fd3def0b7574a1d801be44fde617162aa2e89da47f464317d9bb5abc3a7071763ce74180883ad7ad9a723a9afafcdca,0x056f617902b3c0d0f78a9a8cbda43a26b65f602f8786540b9469b060db7b38417915b413ca65f875c130bebfaa59790c" + }, + "msg": "", + "u": [ + "0x03dbc2cce174e91ba93cbb08f26b917f98194a2ea08d1cce75b2b9cc9f21689d80bd79b594a613d0a68eb807dfdc1cf8,0x05a2acec64114845711a54199ea339abd125ba38253b70a92c876df10598bd1986b739cad67961eb94f7076511b3b39a", + "0x02f99798e8a5acdeed60d7e18e9120521ba1f47ec090984662846bc825de191b5b7641148c0dbc237726a334473eee94,0x145a81e418d4010cc027a68f14391b30074e89e60ee7a22f87217b2f6eb0c4b94c9115b436e6fa4607e95a98de30a435" + ] + }, + { + "P": { + "x": "0x02c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6,0x139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd8", + "y": "0x1787327b68159716a37440985269cf584bcb1e621d3a7202be6ea05c4cfe244aeb197642555a0645fb87bf7466b2ba48,0x00aa65dae3c8d732d10ecd2c50f8a1baf3001578f71c694e03866e9f3d49ac1e1ce70dd94a733534f106d4cec0eddd16" + }, + "Q0": { + "x": "0x12b2e525281b5f4d2276954e84ac4f42cf4e13b6ac4228624e17760faf94ce5706d53f0ca1952f1c5ef75239aeed55ad,0x05d8a724db78e570e34100c0bc4a5fa84ad5839359b40398151f37cff5a51de945c563463c9efbdda569850ee5a53e77", + "y": "0x02eacdc556d0bdb5d18d22f23dcb086dd106cad713777c7e6407943edbe0b3d1efe391eedf11e977fac55f9b94f2489c,0x04bbe48bfd5814648d0b9e30f0717b34015d45a861425fabc1ee06fdfce36384ae2c808185e693ae97dcde118f34de41" + }, + "Q1": { + "x": "0x19f18cc5ec0c2f055e47c802acc3b0e40c337256a208001dde14b25afced146f37ea3d3ce16834c78175b3ed61f3c537,0x15b0dadc256a258b4c68ea43605dffa6d312eef215c19e6474b3e101d33b661dfee43b51abbf96fee68fc6043ac56a58", + "y": "0x05e47c1781286e61c7ade887512bd9c2cb9f640d3be9cf87ea0bad24bd0ebfe946497b48a581ab6c7d4ca74b5147287f,0x19f98db2f4a1fcdf56a9ced7b320ea9deecf57c8e59236b0dc21f6ee7229aa9705ce9ac7fe7a31c72edca0d92370c096" + }, + "msg": "abc", + "u": [ + "0x15f7c0aa8f6b296ab5ff9c2c7581ade64f4ee6f1bf18f55179ff44a2cf355fa53dd2a2158c5ecb17d7c52f63e7195771,0x01c8067bf4c0ba709aa8b9abc3d1cef589a4758e09ef53732d670fd8739a7274e111ba2fcaa71b3d33df2a3a0c8529dd", + "0x187111d5e088b6b9acfdfad078c4dacf72dcd17ca17c82be35e79f8c372a693f60a033b461d81b025864a0ad051a06e4,0x08b852331c96ed983e497ebc6dee9b75e373d923b729194af8e72a051ea586f3538a6ebb1e80881a082fa2b24df9f566" + ] + }, + { + "P": { + "x": "0x121982811d2491fde9ba7ed31ef9ca474f0e1501297f68c298e9f4c0028add35aea8bb83d53c08cfc007c1e005723cd0,0x190d119345b94fbd15497bcba94ecf7db2cbfd1e1fe7da034d26cbba169fb3968288b3fafb265f9ebd380512a71c3f2c", + "y": "0x05571a0f8d3c08d094576981f4a3b8eda0a8e771fcdcc8ecceaf1356a6acf17574518acb506e435b639353c2e14827c8,0x0bb5e7572275c567462d91807de765611490205a941a5a6af3b1691bfe596c31225d3aabdf15faff860cb4ef17c7c3be" + }, + "Q0": { + "x": "0x0f48f1ea1318ddb713697708f7327781fb39718971d72a9245b9731faaca4dbaa7cca433d6c434a820c28b18e20ea208,0x06051467c8f85da5ba2540974758f7a1e0239a5981de441fdd87680a995649c211054869c50edbac1f3a86c561ba3162", + "y": "0x168b3d6df80069dbbedb714d41b32961ad064c227355e1ce5fac8e105de5e49d77f0c64867f3834848f152497eb76333,0x134e0e8331cee8cb12f9c2d0742714ed9eee78a84d634c9a95f6a7391b37125ed48bfc6e90bf3546e99930ff67cc97bc" + }, + "Q1": { + "x": "0x004fd03968cd1c99a0dd84551f44c206c84dcbdb78076c5bfee24e89a92c8508b52b88b68a92258403cbe1ea2da3495f,0x1674338ea298281b636b2eb0fe593008d03171195fd6dcd4531e8a1ed1f02a72da238a17a635de307d7d24aa2d969a47", + "y": "0x0dc7fa13fff6b12558419e0a1e94bfc3cfaf67238009991c5f24ee94b632c3d09e27eca329989aee348a67b50d5e236c,0x169585e164c131103d85324f2d7747b23b91d66ae5d947c449c8194a347969fc6bbd967729768da485ba71868df8aed2" + }, + "msg": "abcdef0123456789", + "u": [ + "0x0313d9325081b415bfd4e5364efaef392ecf69b087496973b229303e1816d2080971470f7da112c4eb43053130b785e1,0x062f84cb21ed89406890c051a0e8b9cf6c575cf6e8e18ecf63ba86826b0ae02548d83b483b79e48512b82a6c0686df8f", + "0x1739123845406baa7be5c5dc74492051b6d42504de008c635f3535bb831d478a341420e67dcc7b46b2e8cba5379cca97,0x01897665d9cb5db16a27657760bbea7951f67ad68f8d55f7113f24ba6ddd82caef240a9bfa627972279974894701d975" + ] + }, + { + "P": { + "x": "0x19a84dd7248a1066f737cc34502ee5555bd3c19f2ecdb3c7d9e24dc65d4e25e50d83f0f77105e955d78f4762d33c17da,0x0934aba516a52d8ae479939a91998299c76d39cc0c035cd18813bec433f587e2d7a4fef038260eef0cef4d02aae3eb91", + "y": "0x14f81cd421617428bc3b9fe25afbb751d934a00493524bc4e065635b0555084dd54679df1536101b2c979c0152d09192,0x09bcccfa036b4847c9950780733633f13619994394c23ff0b32fa6b795844f4a0673e20282d07bc69641cee04f5e5662" + }, + "Q0": { + "x": "0x09eccbc53df677f0e5814e3f86e41e146422834854a224bf5a83a50e4cc0a77bfc56718e8166ad180f53526ea9194b57,0x0c3633943f91daee715277bd644fba585168a72f96ded64fc5a384cce4ec884a4c3c30f08e09cd2129335dc8f67840ec", + "y": "0x0eb6186a0457d5b12d132902d4468bfeb7315d83320b6c32f1c875f344efcba979952b4aa418589cb01af712f98cc555,0x119e3cf167e69eb16c1c7830e8df88856d48be12e3ff0a40791a5cd2f7221311d4bf13b1847f371f467357b3f3c0b4c7" + }, + "Q1": { + "x": "0x0eb3aabc1ddfce17ff18455fcc7167d15ce6b60ddc9eb9b59f8d40ab49420d35558686293d046fc1e42f864b7f60e381,0x198bdfb19d7441ebcca61e8ff774b29d17da16547d2c10c273227a635cacea3f16826322ae85717630f0867539b5ed8b", + "y": "0x0aaf1dee3adf3ed4c80e481c09b57ea4c705e1b8d25b897f0ceeec3990748716575f92abff22a1c8f4582aff7b872d52,0x0d058d9061ed27d4259848a06c96c5ca68921a5d269b078650c882cb3c2bd424a8702b7a6ee4e0ead9982baf6843e924" + }, + "msg": "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "u": [ + "0x025820cefc7d06fd38de7d8e370e0da8a52498be9b53cba9927b2ef5c6de1e12e12f188bbc7bc923864883c57e49e253,0x034147b77ce337a52e5948f66db0bab47a8d038e712123bb381899b6ab5ad20f02805601e6104c29df18c254b8618c7b", + "0x0930315cae1f9a6017c3f0c8f2314baa130e1cf13f6532bff0a8a1790cd70af918088c3db94bda214e896e1543629795,0x10c4df2cacf67ea3cb3108b00d4cbd0b3968031ebc8eac4b1ebcefe84d6b715fde66bef0219951ece29d1facc8a520ef" + ] + }, + { + "P": { + "x": "0x01a6ba2f9a11fa5598b2d8ace0fbe0a0eacb65deceb476fbbcb64fd24557c2f4b18ecfc5663e54ae16a84f5ab7f62534,0x11fca2ff525572795a801eed17eb12785887c7b63fb77a42be46ce4a34131d71f7a73e95fee3f812aea3de78b4d01569", + "y": "0x0b6798718c8aed24bc19cb27f866f1c9effcdbf92397ad6448b5c9db90d2b9da6cbabf48adc1adf59a1a28344e79d57e,0x03a47f8e6d1763ba0cad63d6114c0accbef65707825a511b251a660a9b3994249ae4e63fac38b23da0c398689ee2ab52" + }, + "Q0": { + "x": "0x17cadf8d04a1a170f8347d42856526a24cc466cb2ddfd506cff01191666b7f944e31244d662c904de5440516a2b09004,0x0d13ba91f2a8b0051cf3279ea0ee63a9f19bc9cb8bfcc7d78b3cbd8cc4fc43ba726774b28038213acf2b0095391c523e", + "y": "0x17ef19497d6d9246fa94d35575c0f8d06ee02f21a284dbeaa78768cb1e25abd564e3381de87bda26acd04f41181610c5,0x12c3c913ba4ed03c24f0721a81a6be7430f2971ffca8fd1729aafe496bb725807531b44b34b59b3ae5495e5a2dcbd5c8" + }, + "Q1": { + "x": "0x16ec57b7fe04c71dfe34fb5ad84dbce5a2dbbd6ee085f1d8cd17f45e8868976fc3c51ad9eeda682c7869024d24579bfd,0x13103f7aace1ae1420d208a537f7d3a9679c287208026e4e3439ab8cd534c12856284d95e27f5e1f33eec2ce656533b0", + "y": "0x0958b2c4c2c10fcef5a6c59b9e92c4a67b0fae3e2e0f1b6b5edad9c940b8f3524ba9ebbc3f2ceb3cfe377655b3163bd7,0x0ccb594ed8bd14ca64ed9cb4e0aba221be540f25dd0d6ba15a4a4be5d67bcf35df7853b2d8dad3ba245f1ea3697f66aa" + }, + "msg": "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "u": [ + "0x190b513da3e66fc9a3587b78c76d1d132b1152174d0b83e3c1114066392579a45824c5fa17649ab89299ddd4bda54935,0x12ab625b0fe0ebd1367fe9fac57bb1168891846039b4216b9d94007b674de2d79126870e88aeef54b2ec717a887dcf39", + "0x0e6a42010cf435fb5bacc156a585e1ea3294cc81d0ceb81924d95040298380b164f702275892cedd81b62de3aba3f6b5,0x117d9a0defc57a33ed208428cb84e54c85a6840e7648480ae428838989d25d97a0af8e3255be62b25c2a85630d2dddd8" + ] + } + ] +}