Skip to content

Commit

Permalink
Bandersnatch: ring-context generic over domain size (paritytech#2581)
Browse files Browse the repository at this point in the history
Serialized length is now statically computed depending on the domain
size.

Opens the primitive to more generic usages not related to Sassafras
expectations

Address one point of
paritytech#2364
  • Loading branch information
davxy authored Dec 1, 2023
1 parent 9fcd017 commit 24c40b5
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 55 deletions.
8 changes: 7 additions & 1 deletion substrate/primitives/consensus/sassafras/src/vrf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ use sp_consensus_slots::Slot;
use sp_std::vec::Vec;

pub use sp_core::bandersnatch::{
ring_vrf::{RingContext, RingProver, RingVerifier, RingVerifierData, RingVrfSignature},
ring_vrf::{RingProver, RingVerifier, RingVerifierData, RingVrfSignature},
vrf::{VrfInput, VrfOutput, VrfSignData, VrfSignature},
};

/// Ring VRF domain size for Sassafras consensus.
pub const RING_VRF_DOMAIN_SIZE: u32 = 2048;

/// Bandersnatch VRF [`RingContext`] specialization for Sassafras using [`RING_VRF_DOMAIN_SIZE`].
pub type RingContext = sp_core::bandersnatch::ring_vrf::RingContext<RING_VRF_DOMAIN_SIZE>;

fn vrf_input_from_data(
domain: &[u8],
data: impl IntoIterator<Item = impl AsRef<[u8]>>,
Expand Down
115 changes: 62 additions & 53 deletions substrate/primitives/core/src/bandersnatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use scale_info::TypeInfo;

use sp_runtime_interface::pass_by::PassByInner;
use sp_std::{boxed::Box, vec::Vec};
use sp_std::{vec, vec::Vec};

/// Identifier used to match public keys against bandersnatch-vrf keys.
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band");
Expand Down Expand Up @@ -622,31 +622,25 @@ pub mod ring_vrf {
pub use bandersnatch_vrfs::ring::{RingProof, RingProver, RingVerifier, KZG};
use bandersnatch_vrfs::{ring::VerifierKey, CanonicalDeserialize, PublicKey};

/// Ring max size (keyset max size).
pub const RING_MAX_SIZE: u32 = RING_DOMAIN_MAX_SIZE - RING_DOMAIN_OVERHEAD;
/// Overhead in the domain size with respect to the supported ring size.
///
/// Some bits of the domain are reserved for the zk-proof to work.
pub const RING_DOMAIN_OVERHEAD: u32 = 257;

/// Ring domain max size.
pub const RING_DOMAIN_MAX_SIZE: u32 = 2048;
// Max size of serialized ring-vrf context given `domain_len`.
pub(crate) const fn ring_context_serialized_size(domain_len: u32) -> usize {
// const G1_POINT_COMPRESSED_SIZE: usize = 48;
// const G2_POINT_COMPRESSED_SIZE: usize = 96;
const G1_POINT_UNCOMPRESSED_SIZE: usize = 96;
const G2_POINT_UNCOMPRESSED_SIZE: usize = 192;
const OVERHEAD_SIZE: usize = 20;
const G2_POINTS_NUM: usize = 2;
let g1_points_num = 3 * domain_len as usize + 1;

/// Overhead in the domain size over the max ring size.
///
/// Some bits of the domain are reserved for the zk proof to work.
pub(crate) const RING_DOMAIN_OVERHEAD: u32 = 257;

// Max size of serialized ring-vrf context params.
//
// The actual size is dependent on the ring domain size and this value
// has been computed for `RING_DOMAIN_MAX_SIZE` with compression disabled
// for performance reasons.
//
// 1024 uncompressed
// pub(crate) const RING_CONTEXT_SERIALIZED_MAX_SIZE: usize = 295412;
// 1024 compressed
// pub(crate) const RING_CONTEXT_SERIALIZED_MAX_SIZE: usize = 147716;
// 2048 uncompressed
pub(crate) const RING_CONTEXT_SERIALIZED_MAX_SIZE: usize = 590324;
// 2048 compressed
// pub(crate) const RING_CONTEXT_SERIALIZED_MAX_SIZE: usize = 295172;
OVERHEAD_SIZE +
g1_points_num * G1_POINT_UNCOMPRESSED_SIZE +
G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE
}

pub(crate) const RING_VERIFIER_DATA_SERIALIZED_SIZE: usize = 388;
pub(crate) const RING_SIGNATURE_SERIALIZED_SIZE: usize = 755;
Expand Down Expand Up @@ -705,15 +699,17 @@ pub mod ring_vrf {
}

/// Context used to construct ring prover and verifier.
///
/// Generic parameter `D` represents the ring domain size and drives
/// the max number of supported ring members [`RingContext::max_keyset_size`]
/// which is equal to `D - RING_DOMAIN_OVERHEAD`.
#[derive(Clone)]
pub struct RingContext(KZG);
pub struct RingContext<const D: u32>(KZG);

impl RingContext {
impl<const D: u32> RingContext<D> {
/// Build an dummy instance for testing purposes.
///
/// `domain_size` is set to `RING_DOMAIN_MAX_SIZE`.
pub fn new_testing() -> Self {
Self(KZG::testing_kzg_setup([0; 32], RING_DOMAIN_MAX_SIZE))
Self(KZG::testing_kzg_setup([0; 32], D))
}

/// Get the keyset max size.
Expand Down Expand Up @@ -761,38 +757,45 @@ pub mod ring_vrf {
}
}

impl Encode for RingContext {
impl<const D: u32> Encode for RingContext<D> {
fn encode(&self) -> Vec<u8> {
let mut buf = Box::new([0; RING_CONTEXT_SERIALIZED_MAX_SIZE]);
let mut buf = vec![0; ring_context_serialized_size(D)];
self.0
.serialize_uncompressed(buf.as_mut_slice())
.expect("serialization length is constant and checked by test; qed");
buf.encode()
buf
}
}

impl Decode for RingContext {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let buf = <Box<[u8; RING_CONTEXT_SERIALIZED_MAX_SIZE]>>::decode(i)?;
impl<const D: u32> Decode for RingContext<D> {
fn decode<R: codec::Input>(input: &mut R) -> Result<Self, codec::Error> {
let mut buf = vec![0; ring_context_serialized_size(D)];
input.read(&mut buf[..])?;
let kzg = KZG::deserialize_uncompressed_unchecked(buf.as_slice())
.map_err(|_| "KZG decode error")?;
Ok(RingContext(kzg))
}
}

impl EncodeLike for RingContext {}
impl<const D: u32> EncodeLike for RingContext<D> {}

impl MaxEncodedLen for RingContext {
impl<const D: u32> MaxEncodedLen for RingContext<D> {
fn max_encoded_len() -> usize {
<[u8; RING_CONTEXT_SERIALIZED_MAX_SIZE]>::max_encoded_len()
ring_context_serialized_size(D)
}
}

impl TypeInfo for RingContext {
type Identity = [u8; RING_CONTEXT_SERIALIZED_MAX_SIZE];
impl<const D: u32> TypeInfo for RingContext<D> {
type Identity = Self;

fn type_info() -> scale_info::Type {
Self::Identity::type_info()
let path = scale_info::Path::new("RingContext", module_path!());
let array_type_def = scale_info::TypeDefArray {
len: ring_context_serialized_size(D) as u32,
type_param: scale_info::MetaType::new::<u8>(),
};
let type_def = scale_info::TypeDef::Array(array_type_def);
scale_info::Type { path, type_params: Vec::new(), type_def, docs: Vec::new() }
}
}

Expand Down Expand Up @@ -903,7 +906,11 @@ pub mod ring_vrf {
mod tests {
use super::{ring_vrf::*, vrf::*, *};
use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE};

const DEV_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE];
const TEST_DOMAIN_SIZE: u32 = 1024;

type TestRingContext = RingContext<TEST_DOMAIN_SIZE>;

#[allow(unused)]
fn b2h(bytes: &[u8]) -> String {
Expand All @@ -916,10 +923,10 @@ mod tests {

#[test]
fn backend_assumptions_sanity_check() {
let kzg = KZG::testing_kzg_setup([0; 32], RING_DOMAIN_MAX_SIZE);
assert_eq!(kzg.max_keyset_size() as u32, RING_MAX_SIZE);
let kzg = KZG::testing_kzg_setup([0; 32], TEST_DOMAIN_SIZE);
assert_eq!(kzg.max_keyset_size() as u32, TEST_DOMAIN_SIZE - RING_DOMAIN_OVERHEAD);

assert_eq!(kzg.uncompressed_size(), RING_CONTEXT_SERIALIZED_MAX_SIZE);
assert_eq!(kzg.uncompressed_size(), ring_context_serialized_size(TEST_DOMAIN_SIZE));

let pks: Vec<_> = (0..16)
.map(|i| SecretKey::from_seed(&[i as u8; 32]).to_public().0.into())
Expand Down Expand Up @@ -1071,7 +1078,7 @@ mod tests {

#[test]
fn ring_vrf_sign_verify() {
let ring_ctx = RingContext::new_testing();
let ring_ctx = TestRingContext::new_testing();

let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
Expand All @@ -1097,7 +1104,7 @@ mod tests {

#[test]
fn ring_vrf_sign_verify_with_out_of_ring_key() {
let ring_ctx = RingContext::new_testing();
let ring_ctx = TestRingContext::new_testing();

let pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
let pair = Pair::from_seed(DEV_SEED);
Expand All @@ -1116,7 +1123,7 @@ mod tests {

#[test]
fn ring_vrf_make_bytes_matches() {
let ring_ctx = RingContext::new_testing();
let ring_ctx = TestRingContext::new_testing();

let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
Expand Down Expand Up @@ -1145,7 +1152,7 @@ mod tests {

#[test]
fn encode_decode_ring_vrf_signature() {
let ring_ctx = RingContext::new_testing();
let ring_ctx = TestRingContext::new_testing();

let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
Expand Down Expand Up @@ -1177,21 +1184,23 @@ mod tests {

#[test]
fn encode_decode_ring_vrf_context() {
let ctx1 = RingContext::new_testing();
let ctx1 = TestRingContext::new_testing();
let enc1 = ctx1.encode();

assert_eq!(enc1.len(), RING_CONTEXT_SERIALIZED_MAX_SIZE);
assert_eq!(RingContext::max_encoded_len(), RING_CONTEXT_SERIALIZED_MAX_SIZE);
let _ti = <TestRingContext as TypeInfo>::type_info();

assert_eq!(enc1.len(), ring_context_serialized_size(TEST_DOMAIN_SIZE));
assert_eq!(enc1.len(), TestRingContext::max_encoded_len());

let ctx2 = RingContext::decode(&mut enc1.as_slice()).unwrap();
let ctx2 = TestRingContext::decode(&mut enc1.as_slice()).unwrap();
let enc2 = ctx2.encode();

assert_eq!(enc1, enc2);
}

#[test]
fn encode_decode_verifier_data() {
let ring_ctx = RingContext::new_testing();
let ring_ctx = TestRingContext::new_testing();

let pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
Expand Down
2 changes: 1 addition & 1 deletion substrate/primitives/keystore/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ mod tests {

let store = MemoryKeystore::new();

let ring_ctx = bandersnatch::ring_vrf::RingContext::new_testing();
let ring_ctx = bandersnatch::ring_vrf::RingContext::<1024>::new_testing();

let mut pks: Vec<_> = (0..16)
.map(|i| bandersnatch::Pair::from_seed(&[i as u8; 32]).public())
Expand Down

0 comments on commit 24c40b5

Please sign in to comment.