From 25b681dd9fd9a5843f8c1edcc97cf3da696ad029 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 13 Jun 2023 09:37:17 +0200 Subject: [PATCH 01/14] Bump version to 0.3.3 --- Cargo.lock | 10 +++++----- bin/node-sassafras/node/Cargo.toml | 8 ++++---- bin/node-sassafras/runtime/Cargo.toml | 6 +++--- client/consensus/sassafras/Cargo.toml | 4 ++-- frame/sassafras/Cargo.toml | 4 ++-- primitives/consensus/sassafras/Cargo.toml | 2 +- test-utils/runtime/Cargo.toml | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52daa3dcd1826..116ac67d4255e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5662,7 +5662,7 @@ dependencies = [ [[package]] name = "node-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" dependencies = [ "clap 4.3.2", "frame-benchmarking", @@ -5705,7 +5705,7 @@ dependencies = [ [[package]] name = "node-sassafras-runtime" -version = "0.3.2-dev" +version = "0.3.3-dev" dependencies = [ "frame-benchmarking", "frame-executive", @@ -7387,7 +7387,7 @@ dependencies = [ [[package]] name = "pallet-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" dependencies = [ "array-bytes 4.2.0", "frame-benchmarking", @@ -9639,7 +9639,7 @@ dependencies = [ [[package]] name = "sc-consensus-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" dependencies = [ "async-trait", "fork-tree", @@ -11342,7 +11342,7 @@ dependencies = [ [[package]] name = "sp-consensus-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" dependencies = [ "async-trait", "merlin 2.0.1", diff --git a/bin/node-sassafras/node/Cargo.toml b/bin/node-sassafras/node/Cargo.toml index db6c93652dc5c..390e949b224fd 100644 --- a/bin/node-sassafras/node/Cargo.toml +++ b/bin/node-sassafras/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" authors = ["Parity Technologies "] description = "Node testbed for Sassafras consensus." homepage = "https://substrate.io/" @@ -27,8 +27,8 @@ sc-telemetry = { version = "4.0.0-dev", path = "../../../client/telemetry" } sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } -sc-consensus-sassafras = { version = "0.3.2-dev", path = "../../../client/consensus/sassafras" } -sp-consensus-sassafras = { version = "0.3.2-dev", path = "../../../primitives/consensus/sassafras" } +sc-consensus-sassafras = { version = "0.3.3-dev", path = "../../../client/consensus/sassafras" } +sp-consensus-sassafras = { version = "0.3.3-dev", path = "../../../primitives/consensus/sassafras" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-consensus-grandpa = { version = "0.10.0-dev", path = "../../../client/consensus/grandpa" } @@ -57,7 +57,7 @@ frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarkin frame-benchmarking-cli = { version = "4.0.0-dev", path = "../../../utils/frame/benchmarking-cli" } # Local Dependencies -node-sassafras-runtime = { version = "0.3.2-dev", path = "../runtime" } +node-sassafras-runtime = { version = "0.3.3-dev", path = "../runtime" } # CLI-specific dependencies try-runtime-cli = { version = "0.10.0-dev", optional = true, path = "../../../utils/frame/try-runtime/cli" } diff --git a/bin/node-sassafras/runtime/Cargo.toml b/bin/node-sassafras/runtime/Cargo.toml index e3298256a2388..f7fe0f0bbc966 100644 --- a/bin/node-sassafras/runtime/Cargo.toml +++ b/bin/node-sassafras/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-sassafras-runtime" -version = "0.3.2-dev" +version = "0.3.3-dev" authors = ["Parity Technologies "] description = "Runtime testbed for Sassafras consensus." homepage = "https://substrate.io/" @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -pallet-sassafras = { version = "0.3.2-dev", default-features = false, path = "../../../frame/sassafras" } +pallet-sassafras = { version = "0.3.3-dev", default-features = false, path = "../../../frame/sassafras" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../../frame/balances" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../../frame/session" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../frame/support" } @@ -28,7 +28,7 @@ pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/block-builder"} -sp-consensus-sassafras = { version = "0.3.2-dev", default-features = false, path = "../../../primitives/consensus/sassafras" } +sp-consensus-sassafras = { version = "0.3.3-dev", default-features = false, path = "../../../primitives/consensus/sassafras" } sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/inherents"} sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/offchain" } diff --git a/client/consensus/sassafras/Cargo.toml b/client/consensus/sassafras/Cargo.toml index 6037302d4a65d..5352129e31fd0 100644 --- a/client/consensus/sassafras/Cargo.toml +++ b/client/consensus/sassafras/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" authors = ["Parity Technologies "] description = "Sassafras consensus algorithm for substrate" edition = "2021" @@ -32,7 +32,7 @@ sp-application-crypto = { version = "23.0.0", path = "../../../primitives/applic sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-consensus-sassafras = { version = "0.3.2-dev", path = "../../../primitives/consensus/sassafras" } +sp-consensus-sassafras = { version = "0.3.3-dev", path = "../../../primitives/consensus/sassafras" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } diff --git a/frame/sassafras/Cargo.toml b/frame/sassafras/Cargo.toml index 64caf8081029b..80cd5b3e88553 100644 --- a/frame/sassafras/Cargo.toml +++ b/frame/sassafras/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -22,7 +22,7 @@ log = { version = "0.4.17", default-features = false } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto", features = ["serde"] } -sp-consensus-sassafras = { version = "0.3.2-dev", default-features = false, path = "../../primitives/consensus/sassafras", features = ["serde"] } +sp-consensus-sassafras = { version = "0.3.3-dev", default-features = false, path = "../../primitives/consensus/sassafras", features = ["serde"] } sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } diff --git a/primitives/consensus/sassafras/Cargo.toml b/primitives/consensus/sassafras/Cargo.toml index 1824940f0421d..5029e796735c0 100644 --- a/primitives/consensus/sassafras/Cargo.toml +++ b/primitives/consensus/sassafras/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-consensus-sassafras" -version = "0.3.2-dev" +version = "0.3.3-dev" authors = ["Parity Technologies "] description = "Primitives for Sassafras consensus" edition = "2021" diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 2b863b695561d..92c160b3868cb 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -18,7 +18,7 @@ sp-application-crypto = { version = "23.0.0", default-features = false, path = " sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } sp-consensus-beefy = { version = "4.0.0-dev", default-features = false, path = "../../primitives/consensus/beefy" } -sp-consensus-sassafras = { version = "0.3.2-dev", default-features = false, path = "../../primitives/consensus/sassafras" } +sp-consensus-sassafras = { version = "0.3.3-dev", default-features = false, path = "../../primitives/consensus/sassafras" } sp-block-builder = { version = "4.0.0-dev", default-features = false, path = "../../primitives/block-builder" } codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } @@ -36,7 +36,7 @@ sp-session = { version = "4.0.0-dev", default-features = false, path = "../../pr sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../frame/babe" } -pallet-sassafras = { version = "0.3.2-dev", default-features = false, path = "../../frame/sassafras" } +pallet-sassafras = { version = "0.3.3-dev", default-features = false, path = "../../frame/sassafras" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../frame/balances" } pallet-root-testing = { version = "1.0.0-dev", default-features = false, path = "../../frame/root-testing" } pallet-sudo = { version = "4.0.0-dev", default-features = false, path = "../../frame/sudo" } From 49fd4ab9da36240c04b082dc3fc2956d25823621 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 13 Jun 2023 12:09:09 +0200 Subject: [PATCH 02/14] Working ring-vrf zk-proof --- primitives/core/src/bandersnatch.rs | 209 +++++++++++++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 2a5013b5be662..f8a62ee500d61 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -557,9 +557,187 @@ pub mod vrf { } } +/// Ring VRF related types and operations. +pub mod ring_vrf { + use super::{vrf::*, *}; + pub use bandersnatch_vrfs::ring::{RingProof, RingProver, RingVerifier, KZG}; + use bandersnatch_vrfs::{ + CanonicalDeserialize, CanonicalSerialize, PedersenVrfSignature, PublicKey, + }; + + /// Ring VRF signature. + pub struct RingVrfSignature { + /// VRF signature + signature: PedersenVrfSignature, + /// VRF pre-outputs + vrf_outputs: VrfIosVec, + /// TODO davxy + ring_proof: RingProof, + } + + /// TODO davxy + pub struct RingVrfContext(KZG); + + impl RingVrfContext { + /// TODO davxy: This is a temporary function with temporary parameters. + /// + /// Initialization cerimony should be performed via some other means + /// For now we call this once here. + #[cfg(feature = "std")] + pub fn new_testing() -> Self { + let kzg_seed = [0; 32]; + let domain_size = 2usize.pow(10); + let kzg = KZG::insecure_kzg_setup(kzg_seed, domain_size); + Self(kzg) + } + + /// Get the keyset size + pub fn max_keyset_size(&self) -> usize { + self.0.max_keyset_size() + } + + /// TODO davxy + pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { + let mut pks = Vec::with_capacity(public_keys.len()); + if !public_keys.iter().all(|public_key| { + match PublicKey::deserialize_compressed(public_key.as_ref()) { + Ok(pk) => { + let sw_affine = pk.0 .0.into(); + pks.push(sw_affine); + true + }, + _ => false, + } + }) { + return None + }; + let prover_key = self.0.prover_key(pks); + let ring_prover = self.0.init_ring_prover(prover_key, public_idx); + + Some(ring_prover) + } + + /// TODO davxy + pub fn verifier(&self, public_keys: &[Public]) -> Option { + let mut pks = Vec::with_capacity(public_keys.len()); + if !public_keys.iter().all(|public_key| { + match PublicKey::deserialize_compressed(public_key.as_ref()) { + Ok(pk) => { + let sw_affine = pk.0 .0.into(); + pks.push(sw_affine); + true + }, + _ => false, + } + }) { + return None + }; + + let verifier_key = self.0.verifier_key(pks); + let ring_verifier = self.0.init_ring_verifier(verifier_key); + + Some(ring_verifier) + } + } + + #[cfg(feature = "full_crypto")] + impl Pair { + /// TODO davxy + pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { + // Hack used because backend signature type is generic over the number of ios + // @burdges can we provide a vec or boxed version? + match data.vrf_inputs.len() { + 0 => self.ring_vrf_sign_gen::<0>(data, prover), + 1 => self.ring_vrf_sign_gen::<1>(data, prover), + 2 => self.ring_vrf_sign_gen::<2>(data, prover), + 3 => self.ring_vrf_sign_gen::<3>(data, prover), + _ => panic!("Max VRF inputs is set to: {}", MAX_VRF_IOS), + } + } + + fn ring_vrf_sign_gen( + &self, + data: &VrfSignData, + prover: &RingProver, + ) -> RingVrfSignature { + let ios: Vec<_> = data + .vrf_inputs + .iter() + .map(|i| self.0.clone().0.vrf_inout(i.0.clone())) + .collect(); + + let signature: bandersnatch_vrfs::RingVrfSignature = + self.0.sign_ring_vrf(data.transcript.clone(), ios.as_slice(), prover); + + let outputs: Vec<_> = signature.preoutputs.into_iter().map(VrfOutput).collect(); + let outputs = VrfIosVec::truncate_from(outputs); + + RingVrfSignature { + signature: signature.signature, + vrf_outputs: outputs, + ring_proof: signature.ring_proof, + } + } + } + + impl Public { + /// TODO davxy + pub fn ring_vrf_verify( + &self, + data: &VrfSignData, + signature: &RingVrfSignature, + verifier: &RingVerifier, + ) -> bool { + let preouts_len = signature.vrf_outputs.len(); + if preouts_len != data.vrf_inputs.len() { + return false + } + // Hack used because backend signature type is generic over the number of ios + // @burdges can we provide a vec or boxed version? + match preouts_len { + 0 => self.ring_vrf_verify_gen::<0>(data, signature, verifier), + 1 => self.ring_vrf_verify_gen::<1>(data, signature, verifier), + 2 => self.ring_vrf_verify_gen::<2>(data, signature, verifier), + 3 => self.ring_vrf_verify_gen::<3>(data, signature, verifier), + _ => panic!("Max VRF input messages is set to: {}", MAX_VRF_IOS), + } + } + + fn ring_vrf_verify_gen( + &self, + data: &VrfSignData, + signature: &RingVrfSignature, + verifier: &RingVerifier, + ) -> bool { + let Ok(public) = PublicKey::deserialize_compressed(self.as_ref()) else { + return false + }; + + let Ok(preouts) = signature + .vrf_outputs + .iter() + .map(|o| o.0.clone()) + .collect::>() + .into_inner() else { + return false + }; + + let signature = bandersnatch_vrfs::RingVrfSignature { + signature: signature.signature.clone(), + preoutputs: preouts, + ring_proof: signature.ring_proof.clone(), + }; + + let inputs = data.vrf_inputs.iter().map(|i| i.0.clone()); + + signature.verify_ring_vrf(data.transcript.clone(), inputs, verifier).is_ok() + } + } +} + #[cfg(test)] mod tests { - use super::{vrf::*, *}; + use super::{ring_vrf::*, vrf::*, *}; use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; const DEV_SEED: &[u8; SEED_SERIALIZED_LEN] = &[0; SEED_SERIALIZED_LEN]; @@ -581,6 +759,7 @@ mod tests { } #[test] + #[ignore] fn derive_hard_known_pair() { let pair = Pair::from_string(&format!("{}//Alice", DEV_PHRASE), None).unwrap(); // known address of DEV_PHRASE with 1.1 @@ -589,6 +768,7 @@ mod tests { } #[test] + #[ignore] fn verify_known_signature() { let pair = Pair::from_seed(DEV_SEED); let public = pair.public(); @@ -691,4 +871,31 @@ mod tests { let decoded = VrfSignature::decode(&mut &bytes[..]).unwrap(); assert_eq!(expected, decoded); } + + #[test] + fn ring_vrf_sign_verify() { + let ring_ctx = RingVrfContext::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()); + + let pair = Pair::from_seed(DEV_SEED); + let public = pair.public(); + + // Just pick one... + let prover_idx = 3; + pks[prover_idx] = public.clone(); + + let i1 = VrfInput::new(b"in1", &[(b"dom1", b"foo"), (b"dom2", b"bar")]); + let i2 = VrfInput::new(b"in2", &[(b"domx", b"hello")]); + let i3 = VrfInput::new(b"in3", &[(b"domy", b"yay"), (b"domz", b"nay")]); + + let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], [i1, i2, i3]).unwrap(); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let verifier = ring_ctx.verifier(&pks).unwrap(); + assert!(public.ring_vrf_verify(&data, &signature, &verifier)); + } } From 95934445aa4676b1ee4b6d766298b0f37a1b6168 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 13 Jun 2023 14:49:31 +0200 Subject: [PATCH 03/14] Add ring-vrf to keystore --- client/keystore/src/local.rs | 15 +++++++++++++++ primitives/keystore/src/lib.rs | 9 +++++++++ primitives/keystore/src/testing.rs | 22 ++++++++++++++++++++-- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 0fdbb24bf70b7..dae300f0b80b5 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -277,6 +277,21 @@ impl Keystore for LocalKeystore { self.vrf_output::(key_type, public, input) } + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> std::result::Result, TraitError> { + let sig = self + .0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.ring_vrf_sign(data, prover)); + Ok(sig) + } + #[cfg(feature = "bls-experimental")] fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { self.public_keys::(key_type) diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index d67f2cb2d5c69..4c1c7d5855a95 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -209,6 +209,15 @@ pub trait Keystore: Send + Sync { input: &bandersnatch::vrf::VrfInput, ) -> Result, Error>; + /// DAVXY TODO + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> Result, Error>; + #[cfg(feature = "bls-experimental")] /// Returns all bls12-381 public keys for the given key type. fn bls381_public_keys(&self, id: KeyTypeId) -> Vec; diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index b9c685397fb6f..d34bb9658ce41 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -245,6 +245,19 @@ impl Keystore for MemoryKeystore { self.vrf_sign::(key_type, public, data) } + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> Result, Error> { + let sig = self + .pair::(key_type, public) + .map(|pair| pair.ring_vrf_sign(data, prover)); + Ok(sig) + } + fn bandersnatch_vrf_output( &self, key_type: KeyTypeId, @@ -370,7 +383,7 @@ mod tests { } #[test] - fn vrf_sign() { + fn sr25519_vrf_sign() { let store = MemoryKeystore::new(); let secret_uri = "//Alice"; @@ -399,7 +412,7 @@ mod tests { } #[test] - fn vrf_output() { + fn sr25519_vrf_output() { let store = MemoryKeystore::new(); let secret_uri = "//Alice"; @@ -446,4 +459,9 @@ mod tests { let res = store.ecdsa_sign_prehashed(ECDSA, &pair.public(), &msg).unwrap(); assert!(res.is_some()); } + + #[test] + fn bandersnatch_vrf_sign() { + panic!("TODO") + } } From 9c30b5e5dfadf46b78d0471c4ce327790e9bcc70 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 13 Jun 2023 14:50:29 +0200 Subject: [PATCH 04/14] Attempt creating ring signature during authoring --- client/consensus/sassafras/src/authorship.rs | 43 +++++++++--- primitives/consensus/sassafras/src/ticket.rs | 11 +-- primitives/core/src/bandersnatch.rs | 73 ++++++++------------ 3 files changed, 70 insertions(+), 57 deletions(-) diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index e922f17b70d3f..524f1544e6ed2 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -24,8 +24,7 @@ use sp_consensus_sassafras::{ digests::PreDigest, slot_claim_sign_data, ticket_id, ticket_id_threshold, AuthorityId, Slot, TicketClaim, TicketData, TicketEnvelope, TicketId, }; -use sp_core::{ed25519, twox_64, ByteArray}; - +use sp_core::{bandersnatch::ring_vrf::RingVrfContext, ed25519, twox_64, ByteArray}; use std::pin::Pin; /// Get secondary authority index for the given epoch and slot. @@ -92,9 +91,14 @@ pub(crate) fn claim_slot( } /// Generate the tickets for the given epoch. +/// /// Tickets additional information will be stored within the `Epoch` structure. -/// The additional information will be used later during session to claim slots. -fn generate_epoch_tickets(epoch: &mut Epoch, keystore: &KeystorePtr) -> Vec { +/// The additional information will be used later during the epoch to claim slots. +fn generate_epoch_tickets( + epoch: &mut Epoch, + keystore: &KeystorePtr, + ring_ctx: &RingVrfContext, +) -> Vec { let config = &epoch.config; let max_attempts = config.threshold_params.attempts_number; let redundancy_factor = config.threshold_params.redundancy_factor; @@ -110,12 +114,18 @@ fn generate_epoch_tickets(epoch: &mut Epoch, keystore: &KeystorePtr) -> Vec = config.authorities.iter().map(|a| *a.0.as_ref()).collect(); + + let authorities = config.authorities.iter().map(|a| &a.0).enumerate(); for (authority_idx, authority_id) in authorities { if !keystore.has_keys(&[(authority_id.to_raw_vec(), AuthorityId::ID)]) { continue } + debug!(target: LOG_TARGET, ">>> Generating new ring prover key..."); + let prover = ring_ctx.prover(&pks, authority_idx).unwrap(); + debug!(target: LOG_TARGET, ">>> ...done"); + let make_ticket = |attempt_idx| { let vrf_input = ticket_id_vrf_input(&config.randomness, attempt_idx, epoch.epoch_idx); @@ -134,9 +144,20 @@ fn generate_epoch_tickets(epoch: &mut Epoch, keystore: &KeystorePtr) -> Vec>> Creating ring proof for attempt {}", attempt_idx); + use sp_core::bandersnatch::vrf::VrfSignData; + let sign_data = VrfSignData::from_iter(b"TicketProof", &[b"tdata"], []).unwrap(); + let _ring_signature = keystore + .bandersnatch_ring_vrf_sign( + AuthorityId::ID, + authority_id.as_ref(), + &sign_data, + &prover, + ) + .ok()??; + debug!(target: LOG_TARGET, ">>> ...done"); + + let ticket_envelope = TicketEnvelope { data, vrf_preout, ring_signature: () }; let ticket_secret = TicketSecret { attempt_idx, erased_secret: erased_seed }; @@ -382,6 +403,10 @@ async fn start_tickets_worker( C::Api: SassafrasApi, SC: SelectChain + 'static, { + log::debug!(target: LOG_TARGET, ">>> Creating testing ring-vrf context..."); + let ring_ctx = RingVrfContext::new_testing(); + log::debug!(target: LOG_TARGET, ">>> ...done"); + let mut notifications = client.import_notification_stream(); while let Some(notification) = notifications.next().await { @@ -415,7 +440,7 @@ async fn start_tickets_worker( }, }; - let tickets = generate_epoch_tickets(&mut epoch, &keystore); + let tickets = generate_epoch_tickets(&mut epoch, &keystore, &ring_ctx); if tickets.is_empty() { continue } diff --git a/primitives/consensus/sassafras/src/ticket.rs b/primitives/consensus/sassafras/src/ticket.rs index 0cedae3541809..f6904bc795e85 100644 --- a/primitives/consensus/sassafras/src/ticket.rs +++ b/primitives/consensus/sassafras/src/ticket.rs @@ -21,7 +21,10 @@ use super::{Randomness, SASSAFRAS_ENGINE_ID}; use scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_consensus_slots::Slot; -use sp_core::bandersnatch::vrf::{VrfInput, VrfOutput, VrfSignData}; +use sp_core::bandersnatch::{ + ring_vrf::RingVrfSignature, + vrf::{VrfInput, VrfOutput, VrfSignData}, +}; /// Ticket identifier. /// @@ -51,10 +54,8 @@ pub struct TicketEnvelope { pub data: TicketData, /// VRF pre-output used to generate the ticket id. pub vrf_preout: VrfOutput, - // /// Pedersen VRF signature - // pub ped_signature: (), - /// Ring VRF proof. - pub ring_proof: TicketRingProof, + /// Ring signature. + pub ring_signature: TicketRingProof, } /// Ticket auxiliary information used to claim the ticket ownership. diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index f8a62ee500d61..ed70b2e593f68 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -27,6 +27,7 @@ use crate::crypto::{ #[cfg(feature = "full_crypto")] use crate::crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError, VrfSecret}; +use bandersnatch_vrfs::CanonicalSerialize; #[cfg(feature = "full_crypto")] use bandersnatch_vrfs::SecretKey; use codec::{Decode, Encode, MaxEncodedLen}; @@ -243,7 +244,9 @@ impl TraitPair for Pair { fn public(&self) -> Public { let public = self.0.to_public(); let mut raw = [0; PUBLIC_SERIALIZED_LEN]; - public.0.serialize(raw.as_mut_slice()).expect("key buffer length is good; qed"); + public + .serialize_compressed(raw.as_mut_slice()) + .expect("key buffer length is good; qed"); Public::unchecked_from(raw) } @@ -515,7 +518,7 @@ pub mod vrf { data: &VrfSignData, signature: &VrfSignature, ) -> bool { - let Ok(public) = PublicKey::deserialize_compressed(&self.0[..]) else { + let Ok(public) = PublicKey::deserialize_compressed(self.as_slice()) else { return false }; @@ -561,19 +564,7 @@ pub mod vrf { pub mod ring_vrf { use super::{vrf::*, *}; pub use bandersnatch_vrfs::ring::{RingProof, RingProver, RingVerifier, KZG}; - use bandersnatch_vrfs::{ - CanonicalDeserialize, CanonicalSerialize, PedersenVrfSignature, PublicKey, - }; - - /// Ring VRF signature. - pub struct RingVrfSignature { - /// VRF signature - signature: PedersenVrfSignature, - /// VRF pre-outputs - vrf_outputs: VrfIosVec, - /// TODO davxy - ring_proof: RingProof, - } + use bandersnatch_vrfs::{CanonicalDeserialize, PedersenVrfSignature, PublicKey}; /// TODO davxy pub struct RingVrfContext(KZG); @@ -600,7 +591,7 @@ pub mod ring_vrf { pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { let mut pks = Vec::with_capacity(public_keys.len()); if !public_keys.iter().all(|public_key| { - match PublicKey::deserialize_compressed(public_key.as_ref()) { + match PublicKey::deserialize_compressed(public_key.as_slice()) { Ok(pk) => { let sw_affine = pk.0 .0.into(); pks.push(sw_affine); @@ -621,7 +612,7 @@ pub mod ring_vrf { pub fn verifier(&self, public_keys: &[Public]) -> Option { let mut pks = Vec::with_capacity(public_keys.len()); if !public_keys.iter().all(|public_key| { - match PublicKey::deserialize_compressed(public_key.as_ref()) { + match PublicKey::deserialize_compressed(public_key.as_slice()) { Ok(pk) => { let sw_affine = pk.0 .0.into(); pks.push(sw_affine); @@ -680,40 +671,36 @@ pub mod ring_vrf { } } - impl Public { + /// Ring VRF signature. + pub struct RingVrfSignature { + /// VRF signature + signature: PedersenVrfSignature, + /// VRF pre-outputs + vrf_outputs: VrfIosVec, /// TODO davxy - pub fn ring_vrf_verify( - &self, - data: &VrfSignData, - signature: &RingVrfSignature, - verifier: &RingVerifier, - ) -> bool { - let preouts_len = signature.vrf_outputs.len(); + ring_proof: RingProof, + } + + impl RingVrfSignature { + /// TODO davxy + pub fn verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + let preouts_len = self.vrf_outputs.len(); if preouts_len != data.vrf_inputs.len() { return false } // Hack used because backend signature type is generic over the number of ios // @burdges can we provide a vec or boxed version? match preouts_len { - 0 => self.ring_vrf_verify_gen::<0>(data, signature, verifier), - 1 => self.ring_vrf_verify_gen::<1>(data, signature, verifier), - 2 => self.ring_vrf_verify_gen::<2>(data, signature, verifier), - 3 => self.ring_vrf_verify_gen::<3>(data, signature, verifier), + 0 => self.verify_gen::<0>(data, verifier), + 1 => self.verify_gen::<1>(data, verifier), + 2 => self.verify_gen::<2>(data, verifier), + 3 => self.verify_gen::<3>(data, verifier), _ => panic!("Max VRF input messages is set to: {}", MAX_VRF_IOS), } } - fn ring_vrf_verify_gen( - &self, - data: &VrfSignData, - signature: &RingVrfSignature, - verifier: &RingVerifier, - ) -> bool { - let Ok(public) = PublicKey::deserialize_compressed(self.as_ref()) else { - return false - }; - - let Ok(preouts) = signature + fn verify_gen(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + let Ok(preouts) = self .vrf_outputs .iter() .map(|o| o.0.clone()) @@ -723,9 +710,9 @@ pub mod ring_vrf { }; let signature = bandersnatch_vrfs::RingVrfSignature { - signature: signature.signature.clone(), + signature: self.signature.clone(), preoutputs: preouts, - ring_proof: signature.ring_proof.clone(), + ring_proof: self.ring_proof.clone(), }; let inputs = data.vrf_inputs.iter().map(|i| i.0.clone()); @@ -896,6 +883,6 @@ mod tests { let signature = pair.ring_vrf_sign(&data, &prover); let verifier = ring_ctx.verifier(&pks).unwrap(); - assert!(public.ring_vrf_verify(&data, &signature, &verifier)); + assert!(signature.verify(&data, &verifier)); } } From 481d2c92416d0fd722d62a5f2a1a5d8e190a2338 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 13 Jun 2023 15:38:11 +0200 Subject: [PATCH 05/14] Remove unused authority weight --- client/consensus/sassafras/src/authorship.rs | 8 ++--- client/consensus/sassafras/src/lib.rs | 6 ++-- .../consensus/sassafras/src/verification.rs | 9 +++--- frame/sassafras/src/lib.rs | 31 +++++++------------ frame/sassafras/src/mock.rs | 4 +-- frame/sassafras/src/session.rs | 6 ++-- primitives/consensus/sassafras/src/digests.rs | 4 +-- primitives/consensus/sassafras/src/lib.rs | 7 +---- 8 files changed, 30 insertions(+), 45 deletions(-) diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index 524f1544e6ed2..b455c00422469 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -78,7 +78,7 @@ pub(crate) fn claim_slot( }, }; - let authority_id = config.authorities.get(authority_idx as usize).map(|auth| &auth.0)?; + let authority_id = config.authorities.get(authority_idx as usize)?; let vrf_signature = keystore .bandersnatch_vrf_sign(AuthorityId::ID, authority_id.as_ref(), &vrf_sign_data) @@ -114,10 +114,10 @@ fn generate_epoch_tickets( log::debug!(target: LOG_TARGET, "Generating tickets for epoch {} @ slot {}", epoch.epoch_idx, epoch.start_slot); log::debug!(target: LOG_TARGET, " threshold: {threshold:016x}"); - let pks: Vec<_> = config.authorities.iter().map(|a| *a.0.as_ref()).collect(); + // We need a list of raw unwrapped keys + let pks: Vec<_> = config.authorities.iter().map(|a| *a.as_ref()).collect(); - let authorities = config.authorities.iter().map(|a| &a.0).enumerate(); - for (authority_idx, authority_id) in authorities { + for (authority_idx, authority_id) in config.authorities.iter().enumerate() { if !keystore.has_keys(&[(authority_id.to_raw_vec(), AuthorityId::ID)]) { continue } diff --git a/client/consensus/sassafras/src/lib.rs b/client/consensus/sassafras/src/lib.rs index 4635c77e1fa8b..b6ea97fed71e7 100644 --- a/client/consensus/sassafras/src/lib.rs +++ b/client/consensus/sassafras/src/lib.rs @@ -78,9 +78,9 @@ pub use sp_consensus_sassafras::{ digests::{CompatibleDigestItem, ConsensusLog, NextEpochDescriptor, PreDigest}, inherents::SassafrasInherentData, slot_claim_sign_data, slot_claim_vrf_input, ticket_id_vrf_input, AuthorityId, AuthorityIndex, - AuthorityPair, AuthoritySignature, SassafrasApi, SassafrasAuthorityWeight, - SassafrasConfiguration, SassafrasEpochConfiguration, TicketClaim, TicketData, TicketEnvelope, - TicketId, TicketSecret, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, + AuthorityPair, AuthoritySignature, SassafrasApi, SassafrasConfiguration, + SassafrasEpochConfiguration, TicketClaim, TicketData, TicketEnvelope, TicketId, TicketSecret, + RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, }; mod authorship; diff --git a/client/consensus/sassafras/src/verification.rs b/client/consensus/sassafras/src/verification.rs index f149f9758c032..3772d86b0e86f 100644 --- a/client/consensus/sassafras/src/verification.rs +++ b/client/consensus/sassafras/src/verification.rs @@ -76,9 +76,8 @@ fn check_header( return Ok(CheckedHeader::Deferred(header, pre_digest.slot)) } - let authority_id = match config.authorities.get(pre_digest.authority_idx as usize) { - Some(authority_id) => authority_id.0.clone(), - None => return Err(sassafras_err(Error::SlotAuthorNotFound)), + let Some(authority_id) = config.authorities.get(pre_digest.authority_idx as usize) else { + return Err(sassafras_err(Error::SlotAuthorNotFound)); }; // Check header signature (aka the Seal) @@ -88,7 +87,7 @@ fn check_header( .ok_or_else(|| sassafras_err(Error::HeaderBadSeal(header.hash())))?; let pre_hash = header.hash(); - if !AuthorityPair::verify(&signature, &pre_hash, &authority_id) { + if !AuthorityPair::verify(&signature, &pre_hash, authority_id) { return Err(sassafras_err(Error::BadSignature(pre_hash))) } @@ -139,7 +138,7 @@ fn check_header( return Err(sassafras_err(Error::VrfVerificationFailed)) } - let info = VerifiedHeaderInfo { authority_id, seal }; + let info = VerifiedHeaderInfo { authority_id: authority_id.clone(), seal }; Ok(CheckedHeader::Checked(header, info)) } diff --git a/frame/sassafras/src/lib.rs b/frame/sassafras/src/lib.rs index 26a9004dcb81a..8a0759c4a0d48 100644 --- a/frame/sassafras/src/lib.rs +++ b/frame/sassafras/src/lib.rs @@ -54,9 +54,9 @@ use frame_support::{traits::Get, weights::Weight, BoundedVec, WeakBoundedVec}; use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; use sp_consensus_sassafras::{ digests::{ConsensusLog, NextEpochDescriptor, PreDigest}, - AuthorityId, Epoch, EquivocationProof, Randomness, SassafrasAuthorityWeight, - SassafrasConfiguration, SassafrasEpochConfiguration, Slot, TicketData, TicketEnvelope, - TicketId, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, + AuthorityId, Epoch, EquivocationProof, Randomness, SassafrasConfiguration, + SassafrasEpochConfiguration, Slot, TicketData, TicketEnvelope, TicketId, RANDOMNESS_LENGTH, + SASSAFRAS_ENGINE_ID, }; use sp_io::hashing; use sp_runtime::{ @@ -147,20 +147,14 @@ pub mod pallet { /// Current epoch authorities. #[pallet::storage] #[pallet::getter(fn authorities)] - pub type Authorities = StorageValue< - _, - WeakBoundedVec<(AuthorityId, SassafrasAuthorityWeight), T::MaxAuthorities>, - ValueQuery, - >; + pub type Authorities = + StorageValue<_, WeakBoundedVec, ValueQuery>; /// Next epoch authorities. #[pallet::storage] #[pallet::getter(fn next_authorities)] - pub type NextAuthorities = StorageValue< - _, - WeakBoundedVec<(AuthorityId, SassafrasAuthorityWeight), T::MaxAuthorities>, - ValueQuery, - >; + pub type NextAuthorities = + StorageValue<_, WeakBoundedVec, ValueQuery>; /// The slot at which the first epoch started. /// This is `None` until the first block is imported on chain. @@ -239,7 +233,7 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { /// Genesis authorities. - pub authorities: Vec<(AuthorityId, SassafrasAuthorityWeight)>, + pub authorities: Vec, /// Genesis epoch configuration. pub epoch_config: SassafrasEpochConfiguration, } @@ -577,11 +571,8 @@ impl Pallet { /// If we detect one or more skipped epochs the policy is to use the authorities and values /// from the first skipped epoch. The tickets are invalidated. pub(crate) fn enact_epoch_change( - authorities: WeakBoundedVec<(AuthorityId, SassafrasAuthorityWeight), T::MaxAuthorities>, - next_authorities: WeakBoundedVec< - (AuthorityId, SassafrasAuthorityWeight), - T::MaxAuthorities, - >, + authorities: WeakBoundedVec, + next_authorities: WeakBoundedVec, ) { // PRECONDITION: caller has done initialization. // If using the internal trigger or the session pallet then this is guaranteed. @@ -687,7 +678,7 @@ impl Pallet { } // Initialize authorities on genesis phase. - fn initialize_genesis_authorities(authorities: &[(AuthorityId, SassafrasAuthorityWeight)]) { + fn initialize_genesis_authorities(authorities: &[AuthorityId]) { // Genesis authorities may have been initialized via other means (e.g. via session pallet). // If this function has already been called with some authorities, then the new list // should be match the previously set one. diff --git a/frame/sassafras/src/mock.rs b/frame/sassafras/src/mock.rs index fa3dfcc83593d..ca719b284339a 100644 --- a/frame/sassafras/src/mock.rs +++ b/frame/sassafras/src/mock.rs @@ -127,7 +127,7 @@ pub fn new_test_ext_with_pairs( .map(|i| AuthorityPair::from_seed(&U256::from(i).into())) .collect::>(); - let authorities = pairs.iter().map(|p| (p.public(), 1)).collect(); + let authorities = pairs.iter().map(|p| p.public()).collect(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -159,7 +159,7 @@ fn make_ticket(slot: Slot, attempt: u32, pair: &AuthorityPair) -> TicketEnvelope // TODO DAVXY: use some well known valid test keys... let data = TicketData { attempt_idx: attempt, erased_public: [0; 32], revealed_public: [0; 32] }; - TicketEnvelope { data, vrf_preout, ring_proof: () } + TicketEnvelope { data, vrf_preout, ring_signature: () } } /// Construct at most `attempts` tickets for the given `slot`. diff --git a/frame/sassafras/src/session.rs b/frame/sassafras/src/session.rs index e15fd3637b9ae..6f16941c99e80 100644 --- a/frame/sassafras/src/session.rs +++ b/frame/sassafras/src/session.rs @@ -40,7 +40,7 @@ impl OneSessionHandler for Pallet { where I: Iterator, { - let authorities = validators.map(|(_, k)| (k, 1)).collect::>(); + let authorities: Vec<_> = validators.map(|(_, k)| k).collect(); Self::initialize_genesis_authorities(&authorities); } @@ -48,7 +48,7 @@ impl OneSessionHandler for Pallet { where I: Iterator, { - let authorities = validators.map(|(_account, k)| (k, 1)).collect::>(); + let authorities = validators.map(|(_account, k)| k).collect(); let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( authorities, Some( @@ -57,7 +57,7 @@ impl OneSessionHandler for Pallet { ), ); - let next_authorities = queued_validators.map(|(_account, k)| (k, 1)).collect::>(); + let next_authorities = queued_validators.map(|(_account, k)| k).collect(); let next_bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( next_authorities, Some( diff --git a/primitives/consensus/sassafras/src/digests.rs b/primitives/consensus/sassafras/src/digests.rs index 0ffc5e998223c..966220c0f83df 100644 --- a/primitives/consensus/sassafras/src/digests.rs +++ b/primitives/consensus/sassafras/src/digests.rs @@ -19,7 +19,7 @@ use super::{ ticket::TicketClaim, AuthorityId, AuthorityIndex, AuthoritySignature, Randomness, - SassafrasAuthorityWeight, SassafrasEpochConfiguration, Slot, VrfSignature, SASSAFRAS_ENGINE_ID, + SassafrasEpochConfiguration, Slot, VrfSignature, SASSAFRAS_ENGINE_ID, }; use scale_codec::{Decode, Encode, MaxEncodedLen}; @@ -47,7 +47,7 @@ pub struct PreDigest { #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub struct NextEpochDescriptor { /// The authorities. - pub authorities: Vec<(AuthorityId, SassafrasAuthorityWeight)>, + pub authorities: Vec, /// The value of randomness to use for the slot-assignment. pub randomness: Randomness, /// Algorithm parameters. If not present, previous epoch parameters are used. diff --git a/primitives/consensus/sassafras/src/lib.rs b/primitives/consensus/sassafras/src/lib.rs index 69cc184c48228..4a67919f90bfb 100644 --- a/primitives/consensus/sassafras/src/lib.rs +++ b/primitives/consensus/sassafras/src/lib.rs @@ -72,11 +72,6 @@ pub type AuthoritySignature = app::Signature; /// the main Sassafras module. If that ever changes, then this must, too. pub type AuthorityId = app::Public; -/// The weight of an authority. -// NOTE: we use a unique name for the weight to avoid conflicts with other -// `Weight` types, since the metadata isn't able to disambiguate. -pub type SassafrasAuthorityWeight = u64; - /// Weight of a Sassafras block. /// Primary blocks have a weight of 1 whereas secondary blocks have a weight of 0. pub type SassafrasBlockWeight = u32; @@ -95,7 +90,7 @@ pub struct SassafrasConfiguration { /// The duration of epoch in slots. pub epoch_duration: u64, /// The authorities for the epoch. - pub authorities: Vec<(AuthorityId, SassafrasAuthorityWeight)>, + pub authorities: Vec, /// The randomness for the epoch. pub randomness: Randomness, /// Tickets threshold parameters. From 3bc7169cb8cb8697b54973b301a96e729468349f Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 13 Jun 2023 15:48:05 +0200 Subject: [PATCH 06/14] Fix --- bin/node-sassafras/node/src/chain_spec.rs | 4 ++-- test-utils/runtime/src/genesismap.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/node-sassafras/node/src/chain_spec.rs b/bin/node-sassafras/node/src/chain_spec.rs index 0636078bf60dd..119a46687c089 100644 --- a/bin/node-sassafras/node/src/chain_spec.rs +++ b/bin/node-sassafras/node/src/chain_spec.rs @@ -127,9 +127,9 @@ fn testnet_genesis( }, sassafras: SassafrasConfig { #[cfg(feature = "use-session-pallet")] - authorities: vec![], + authorities: Vec::new(), #[cfg(not(feature = "use-session-pallet"))] - authorities: initial_authorities.iter().map(|x| (x.1.clone(), 0)).collect(), + authorities: initial_authorities.iter().map(|x| x.1.clone()).collect(), epoch_config: SassafrasEpochConfiguration { attempts_number: SASSAFRAS_TICKETS_MAX_ATTEMPTS_NUMBER, redundancy_factor: SASSAFRAS_TICKETS_REDUNDANCY_FACTOR, diff --git a/test-utils/runtime/src/genesismap.rs b/test-utils/runtime/src/genesismap.rs index 21331d1f43854..554e6142fa75a 100644 --- a/test-utils/runtime/src/genesismap.rs +++ b/test-utils/runtime/src/genesismap.rs @@ -135,7 +135,7 @@ impl GenesisStorageBuilder { epoch_config: Some(crate::TEST_RUNTIME_BABE_EPOCH_CONFIGURATION), }, sassafras: pallet_sassafras::GenesisConfig { - authorities: authorities_bandersnatch.into_iter().map(|x| (x.into(), 1)).collect(), + authorities: authorities_bandersnatch.into_iter().map(|x| x.into()).collect(), epoch_config: sp_consensus_sassafras::SassafrasEpochConfiguration { redundancy_factor: 1, attempts_number: 32, From 004c8145e9174fed95fb5e125be4bd9a179fdf52 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 13 Jun 2023 16:12:47 +0200 Subject: [PATCH 07/14] Temporary construction of ticket body sign data --- client/consensus/sassafras/src/authorship.rs | 3 +-- client/consensus/sassafras/src/lib.rs | 8 ++++---- primitives/consensus/sassafras/src/lib.rs | 5 +++-- primitives/consensus/sassafras/src/ticket.rs | 9 +++++++++ primitives/core/src/bandersnatch.rs | 3 ++- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index b455c00422469..40456fa3a79c8 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -145,8 +145,7 @@ fn generate_epoch_tickets( let data = TicketData { attempt_idx, erased_public, revealed_public }; debug!(target: LOG_TARGET, ">>> Creating ring proof for attempt {}", attempt_idx); - use sp_core::bandersnatch::vrf::VrfSignData; - let sign_data = VrfSignData::from_iter(b"TicketProof", &[b"tdata"], []).unwrap(); + let sign_data = ticket_body_sign_data(&data); let _ring_signature = keystore .bandersnatch_ring_vrf_sign( AuthorityId::ID, diff --git a/client/consensus/sassafras/src/lib.rs b/client/consensus/sassafras/src/lib.rs index b6ea97fed71e7..e8491caeae7bb 100644 --- a/client/consensus/sassafras/src/lib.rs +++ b/client/consensus/sassafras/src/lib.rs @@ -77,10 +77,10 @@ use sp_runtime::{ pub use sp_consensus_sassafras::{ digests::{CompatibleDigestItem, ConsensusLog, NextEpochDescriptor, PreDigest}, inherents::SassafrasInherentData, - slot_claim_sign_data, slot_claim_vrf_input, ticket_id_vrf_input, AuthorityId, AuthorityIndex, - AuthorityPair, AuthoritySignature, SassafrasApi, SassafrasConfiguration, - SassafrasEpochConfiguration, TicketClaim, TicketData, TicketEnvelope, TicketId, TicketSecret, - RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, + slot_claim_sign_data, slot_claim_vrf_input, ticket_body_sign_data, ticket_id_vrf_input, + AuthorityId, AuthorityIndex, AuthorityPair, AuthoritySignature, SassafrasApi, + SassafrasConfiguration, SassafrasEpochConfiguration, TicketClaim, TicketData, TicketEnvelope, + TicketId, TicketSecret, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, }; mod authorship; diff --git a/primitives/consensus/sassafras/src/lib.rs b/primitives/consensus/sassafras/src/lib.rs index 4a67919f90bfb..59b2362849913 100644 --- a/primitives/consensus/sassafras/src/lib.rs +++ b/primitives/consensus/sassafras/src/lib.rs @@ -39,8 +39,9 @@ pub mod inherents; pub mod ticket; pub use ticket::{ - slot_claim_sign_data, slot_claim_vrf_input, ticket_id, ticket_id_threshold, - ticket_id_vrf_input, TicketClaim, TicketData, TicketEnvelope, TicketId, TicketSecret, + slot_claim_sign_data, slot_claim_vrf_input, ticket_body_sign_data, ticket_id, + ticket_id_threshold, ticket_id_vrf_input, TicketClaim, TicketData, TicketEnvelope, TicketId, + TicketSecret, }; mod app { diff --git a/primitives/consensus/sassafras/src/ticket.rs b/primitives/consensus/sassafras/src/ticket.rs index f6904bc795e85..0765e10907707 100644 --- a/primitives/consensus/sassafras/src/ticket.rs +++ b/primitives/consensus/sassafras/src/ticket.rs @@ -98,6 +98,15 @@ pub fn slot_claim_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> .expect("can't fail; qed") } +pub fn ticket_body_sign_data(ticket_body: &TicketData) -> VrfSignData { + VrfSignData::from_iter( + &SASSAFRAS_ENGINE_ID, + &[b"ticket-body-transcript", ticket_body.encode().as_slice()], + [], + ) + .expect("can't fail; qed") +} + /// VRF input to generate the ticket id. /// /// Input randomness is current epoch randomness. diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index ed70b2e593f68..4de0b32eba384 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -298,7 +298,8 @@ pub mod vrf { /// Max number of VRF inputs/outputs pub const MAX_VRF_IOS: u32 = 3; - pub(super) type VrfIosVec = BoundedVec>; + /// Bounded vector used for VRF inputs and outputs. + pub type VrfIosVec = BoundedVec>; /// Input to be used for VRF sign and verify operations. #[derive(Clone)] From 58c0cedb657d3bc9af2aeb5fc40fec98df6fb6f8 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 14 Jun 2023 14:35:12 +0200 Subject: [PATCH 08/14] ring signature encoder --- bin/node-sassafras/node/src/chain_spec.rs | 2 +- client/consensus/sassafras/src/authorship.rs | 10 +- frame/sassafras/src/lib.rs | 5 +- primitives/consensus/sassafras/src/ticket.rs | 7 +- primitives/core/src/bandersnatch.rs | 133 ++++++++++++++----- 5 files changed, 111 insertions(+), 46 deletions(-) diff --git a/bin/node-sassafras/node/src/chain_spec.rs b/bin/node-sassafras/node/src/chain_spec.rs index 119a46687c089..6db56b22805d6 100644 --- a/bin/node-sassafras/node/src/chain_spec.rs +++ b/bin/node-sassafras/node/src/chain_spec.rs @@ -11,7 +11,7 @@ use sp_core::{sr25519, Pair, Public}; use sp_runtime::traits::{IdentifyAccount, Verify}; // Genesis constants for Sassafras parameters configuration. -const SASSAFRAS_TICKETS_MAX_ATTEMPTS_NUMBER: u32 = 16; +const SASSAFRAS_TICKETS_MAX_ATTEMPTS_NUMBER: u32 = 8; const SASSAFRAS_TICKETS_REDUNDANCY_FACTOR: u32 = 1; /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index 40456fa3a79c8..d4938ec644dc6 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -146,7 +146,7 @@ fn generate_epoch_tickets( debug!(target: LOG_TARGET, ">>> Creating ring proof for attempt {}", attempt_idx); let sign_data = ticket_body_sign_data(&data); - let _ring_signature = keystore + let ring_signature = keystore .bandersnatch_ring_vrf_sign( AuthorityId::ID, authority_id.as_ref(), @@ -156,20 +156,20 @@ fn generate_epoch_tickets( .ok()??; debug!(target: LOG_TARGET, ">>> ...done"); - let ticket_envelope = TicketEnvelope { data, vrf_preout, ring_signature: () }; + let ticket_envelope = TicketEnvelope { data, vrf_preout, ring_signature }; let ticket_secret = TicketSecret { attempt_idx, erased_secret: erased_seed }; - Some((ticket_envelope, ticket_id, ticket_secret)) + Some((ticket_id, ticket_envelope, ticket_secret)) }; for attempt in 0..max_attempts { - if let Some((envelope, ticket_id, ticket_secret)) = make_ticket(attempt) { + if let Some((ticket_id, ticket_envelope, ticket_secret)) = make_ticket(attempt) { log::debug!(target: LOG_TARGET, " → {ticket_id:016x}"); epoch .tickets_aux .insert(ticket_id, (authority_idx as AuthorityIndex, ticket_secret)); - tickets.push(envelope); + tickets.push(ticket_envelope); } } } diff --git a/frame/sassafras/src/lib.rs b/frame/sassafras/src/lib.rs index 8a0759c4a0d48..312a73b244812 100644 --- a/frame/sassafras/src/lib.rs +++ b/frame/sassafras/src/lib.rs @@ -366,7 +366,7 @@ pub mod pallet { let epoch_idx = EpochIndex::::get() + 1; let mut segment = BoundedVec::with_max_capacity(); - for ticket in tickets.iter() { + for ticket in tickets { let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input( &randomness, ticket.data.attempt_idx, @@ -379,6 +379,9 @@ pub mod pallet { .try_push(ticket_id) .expect("has same length as bounded input vector; qed"); } + + // TODO davxy TEST RING PROOF + log::debug!(target: LOG_TARGET, "Checking ring proof for {:16x}", ticket_id); } if !segment.is_empty() { diff --git a/primitives/consensus/sassafras/src/ticket.rs b/primitives/consensus/sassafras/src/ticket.rs index 0765e10907707..055614409c91b 100644 --- a/primitives/consensus/sassafras/src/ticket.rs +++ b/primitives/consensus/sassafras/src/ticket.rs @@ -43,9 +43,8 @@ pub struct TicketData { pub revealed_public: [u8; 32], } -/// Ticket ring proof. -/// TODO-SASS-P3: this is a placeholder. -pub type TicketRingProof = (); +/// Ticket ring vrf signature. +pub type TicketRingSignature = RingVrfSignature; /// Ticket envelope used on during submission. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -55,7 +54,7 @@ pub struct TicketEnvelope { /// VRF pre-output used to generate the ticket id. pub vrf_preout: VrfOutput, /// Ring signature. - pub ring_signature: TicketRingProof, + pub ring_signature: TicketRingSignature, } /// Ticket auxiliary information used to claim the ticket ownership. diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 4de0b32eba384..1007307b73815 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -17,8 +17,6 @@ //! TODO DOCS. -// #![allow(unused)] - #[cfg(feature = "std")] use crate::crypto::Ss58Codec; use crate::crypto::{ @@ -41,6 +39,7 @@ pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bs38"); #[cfg(feature = "full_crypto")] const SIGNING_CTX: &[u8] = b"SigningContext"; + #[cfg(feature = "full_crypto")] const SEED_SERIALIZED_LEN: usize = 32; @@ -52,6 +51,20 @@ const SEED_SERIALIZED_LEN: usize = 32; const PUBLIC_SERIALIZED_LEN: usize = 33; const SIGNATURE_SERIALIZED_LEN: usize = 65; +// Edwards form sizes (TODO davxy: probably in the end we'll use this form) +// const PREOUT_SERIALIZED_LEN: usize = 32; + +// Short-Weierstrass form sizes +const PREOUT_SERIALIZED_LEN: usize = 33; + +// Size of serialized pedersen-vrf signature +// Short-Weierstrass form sizes +const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; + +// Size of serialized ring-proof +// Short-Weierstrass form sizes +const RING_PROOF_SERIALIZED_LEN: usize = 592; + /// XXX. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive( @@ -289,12 +302,6 @@ pub mod vrf { ThinVrfSignature, Transcript, }; - // Edwards form sizes (TODO davxy: probably in the end we'll use this form) - // const PREOUT_SERIALIZED_LEN: usize = 32; - - // Short-Weierstrass form sizes - const PREOUT_SERIALIZED_LEN: usize = 33; - /// Max number of VRF inputs/outputs pub const MAX_VRF_IOS: u32 = 3; @@ -417,10 +424,10 @@ pub mod vrf { /// VRF signature. #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct VrfSignature { - /// VRF signature - pub signature: Signature, /// VRF pre-outputs pub vrf_outputs: VrfIosVec, + /// VRF signature + pub signature: Signature, } #[cfg(feature = "full_crypto")] @@ -632,6 +639,18 @@ pub mod ring_vrf { } } + /// Ring VRF signature. + // #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct RingVrfSignature { + /// VRF (pre)outputs. + outputs: VrfIosVec, + /// Pedersen VRF signature. + signature: [u8; PEDERSEN_SIGNATURE_SERIALIZED_LEN], + /// Ring proof. + ring_proof: [u8; RING_PROOF_SERIALIZED_LEN], + } + #[cfg(feature = "full_crypto")] impl Pair { /// TODO davxy @@ -658,34 +677,32 @@ pub mod ring_vrf { .map(|i| self.0.clone().0.vrf_inout(i.0.clone())) .collect(); - let signature: bandersnatch_vrfs::RingVrfSignature = + let ring_signature: bandersnatch_vrfs::RingVrfSignature = self.0.sign_ring_vrf(data.transcript.clone(), ios.as_slice(), prover); - let outputs: Vec<_> = signature.preoutputs.into_iter().map(VrfOutput).collect(); + let outputs: Vec<_> = ring_signature.preoutputs.into_iter().map(VrfOutput).collect(); let outputs = VrfIosVec::truncate_from(outputs); - RingVrfSignature { - signature: signature.signature, - vrf_outputs: outputs, - ring_proof: signature.ring_proof, - } - } - } + let mut signature = [0; PEDERSEN_SIGNATURE_SERIALIZED_LEN]; + ring_signature + .signature + .serialize_compressed(signature.as_mut_slice()) + .expect("ped-signature serialization can't fail"); - /// Ring VRF signature. - pub struct RingVrfSignature { - /// VRF signature - signature: PedersenVrfSignature, - /// VRF pre-outputs - vrf_outputs: VrfIosVec, - /// TODO davxy - ring_proof: RingProof, + let mut ring_proof = [0; RING_PROOF_SERIALIZED_LEN]; + ring_signature + .ring_proof + .serialize_compressed(ring_proof.as_mut_slice()) + .expect("ring-proof serialization can't fail"); + + RingVrfSignature { outputs, signature, ring_proof } + } } impl RingVrfSignature { /// TODO davxy pub fn verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { - let preouts_len = self.vrf_outputs.len(); + let preouts_len = self.outputs.len(); if preouts_len != data.vrf_inputs.len() { return false } @@ -701,8 +718,8 @@ pub mod ring_vrf { } fn verify_gen(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { - let Ok(preouts) = self - .vrf_outputs + let Ok(preoutputs) = self + .outputs .iter() .map(|o| o.0.clone()) .collect::>() @@ -710,15 +727,22 @@ pub mod ring_vrf { return false }; - let signature = bandersnatch_vrfs::RingVrfSignature { - signature: self.signature.clone(), - preoutputs: preouts, - ring_proof: self.ring_proof.clone(), + let Ok(signature) = PedersenVrfSignature::deserialize_compressed(self.signature.as_slice()) else { + return false }; + let Ok(ring_proof) = RingProof::deserialize_compressed(self.ring_proof.as_slice()) else { + return false + }; + + let ring_signature = + bandersnatch_vrfs::RingVrfSignature { signature, preoutputs, ring_proof }; + let inputs = data.vrf_inputs.iter().map(|i| i.0.clone()); - signature.verify_ring_vrf(data.transcript.clone(), inputs, verifier).is_ok() + ring_signature + .verify_ring_vrf(data.transcript.clone(), inputs, verifier) + .is_ok() } } } @@ -848,6 +872,10 @@ mod tests { let bytes = expected.encode(); + let expected_len = + data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + SIGNATURE_SERIALIZED_LEN + 1; + assert_eq!(bytes.len(), expected_len); + let decoded = VrfSignature::decode(&mut &bytes[..]).unwrap(); assert_eq!(expected, decoded); @@ -886,4 +914,39 @@ mod tests { let verifier = ring_ctx.verifier(&pks).unwrap(); assert!(signature.verify(&data, &verifier)); } + + #[test] + fn encode_decode_ring_vrf_signature() { + let ring_ctx = RingVrfContext::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()); + + let pair = Pair::from_seed(DEV_SEED); + let public = pair.public(); + + // Just pick one... + let prover_idx = 3; + pks[prover_idx] = public.clone(); + + let i1 = VrfInput::new(b"in1", &[(b"dom1", b"foo"), (b"dom2", b"bar")]); + let i2 = VrfInput::new(b"in2", &[(b"domx", b"hello")]); + let i3 = VrfInput::new(b"in3", &[(b"domy", b"yay"), (b"domz", b"nay")]); + + let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], [i1, i2, i3]).unwrap(); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let expected = pair.ring_vrf_sign(&data, &prover); + + let bytes = expected.encode(); + + let expected_len = data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + + PEDERSEN_SIGNATURE_SERIALIZED_LEN + + RING_PROOF_SERIALIZED_LEN + + 1; + assert_eq!(bytes.len(), expected_len); + + let decoded = RingVrfSignature::decode(&mut &bytes[..]).unwrap(); + assert_eq!(expected, decoded); + } } From 3082e1449d82db4307c76215e863643460ac5d4c Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 15 Jun 2023 18:33:32 +0200 Subject: [PATCH 09/14] Ring proof verification --- Cargo.lock | 18 +- Cargo.toml | 20 +- bin/node-sassafras/runtime/src/lib.rs | 4 + client/consensus/sassafras/src/authorship.rs | 4 +- frame/sassafras/Cargo.toml | 2 - frame/sassafras/src/lib.rs | 39 +- frame/sassafras/src/mock.rs | 59 ++- frame/sassafras/src/tests.rs | 470 ++++++++++--------- primitives/consensus/sassafras/src/lib.rs | 8 +- primitives/consensus/sassafras/src/ticket.rs | 20 +- primitives/core/src/bandersnatch.rs | 61 ++- 11 files changed, 422 insertions(+), 283 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 525b18d489d2d..a47e46321087b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,7 +481,6 @@ dependencies = [ [[package]] name = "ark-secret-scalar" version = "0.0.2" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#6c474aaa6a9eb29a0c4e4975e1d87d664c8c5853" dependencies = [ "ark-ec", "ark-ff", @@ -529,7 +528,6 @@ dependencies = [ [[package]] name = "ark-transcript" version = "0.0.2" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#6c474aaa6a9eb29a0c4e4975e1d87d664c8c5853" dependencies = [ "ark-ff", "ark-serialize", @@ -795,7 +793,6 @@ dependencies = [ [[package]] name = "bandersnatch_vrfs" version = "0.0.1" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#6c474aaa6a9eb29a0c4e4975e1d87d664c8c5853" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1444,7 +1441,6 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" dependencies = [ "ark-ec", "ark-ff", @@ -2197,7 +2193,6 @@ checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" [[package]] name = "dleq_vrf" version = "0.0.2" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#6c474aaa6a9eb29a0c4e4975e1d87d664c8c5853" dependencies = [ "ark-ec", "ark-ff", @@ -2570,7 +2565,6 @@ dependencies = [ [[package]] name = "fflonk" version = "0.1.0" -source = "git+https://github.com/davxy/fflonk?branch=working-fork#a2664567b88d96e1dc2f82f8799b2ca60171c81d" dependencies = [ "ark-ec", "ark-ff", @@ -7396,7 +7390,6 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", "sp-consensus-sassafras", "sp-core", "sp-io", @@ -7841,9 +7834,8 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" +version = "3.6.0" +source = "git+https://github.com/koute/parity-scale-codec?branch=master_fix_stack_overflow#b47acfd9bbf2659e5f3d05357d7cbc93afe6bb14" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -7856,9 +7848,8 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +version = "3.6.0" +source = "git+https://github.com/koute/parity-scale-codec?branch=master_fix_stack_overflow#b47acfd9bbf2659e5f3d05357d7cbc93afe6bb14" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8800,7 +8791,6 @@ dependencies = [ [[package]] name = "ring" version = "0.1.0" -source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" dependencies = [ "ark-ec", "ark-ff", diff --git a/Cargo.toml b/Cargo.toml index df2a8ca903b05..5ace3d796bb81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,15 +344,19 @@ lto = "fat" codegen-units = 1 [patch."https://github.com/w3f/ring-vrf"] -bandersnatch_vrfs = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } -# bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } +# bandersnatch_vrfs = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } +bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } [patch."https://github.com/w3f/fflonk"] -fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } -# fflonk = { path = "/mnt/ssd/develop/w3f/fflonk" } +# fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } +fflonk = { path = "/mnt/ssd/develop/w3f/fflonk" } [patch."https://github.com/w3f/ring-proof"] -common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } -ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } -# common = { path = "/mnt/ssd/develop/w3f/ring-proof/common" } -# ring = { path = "/mnt/ssd/develop/w3f/ring-proof/ring" } +# common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +# ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +common = { path = "/mnt/ssd/develop/w3f/ring-proof/common" } +ring = { path = "/mnt/ssd/develop/w3f/ring-proof/ring" } + +[patch.crates-io] +#parity-scale-codec = { git = "https://github.com/koute/parity-scale-codec", branch = "master_fix_stack_overflow" } +codec = { package = "parity-scale-codec", git = "https://github.com/koute/parity-scale-codec", branch = "master_fix_stack_overflow" } \ No newline at end of file diff --git a/bin/node-sassafras/runtime/src/lib.rs b/bin/node-sassafras/runtime/src/lib.rs index 73f29a8486937..be43bce4d9335 100644 --- a/bin/node-sassafras/runtime/src/lib.rs +++ b/bin/node-sassafras/runtime/src/lib.rs @@ -387,6 +387,10 @@ impl_runtime_apis! { } impl sp_consensus_sassafras::SassafrasApi for Runtime { + fn ring_context() -> Option { + Sassafras::ring_context() + } + fn submit_tickets_unsigned_extrinsic( tickets: Vec ) -> bool { diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index d4938ec644dc6..dba282e809eaa 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -145,7 +145,9 @@ fn generate_epoch_tickets( let data = TicketData { attempt_idx, erased_public, revealed_public }; debug!(target: LOG_TARGET, ">>> Creating ring proof for attempt {}", attempt_idx); - let sign_data = ticket_body_sign_data(&data); + let mut sign_data = ticket_body_sign_data(&data); + sign_data.push_vrf_input(vrf_input).expect("Can't fail"); + let ring_signature = keystore .bandersnatch_ring_vrf_sign( AuthorityId::ID, diff --git a/frame/sassafras/Cargo.toml b/frame/sassafras/Cargo.toml index 80cd5b3e88553..08d10559caab1 100644 --- a/frame/sassafras/Cargo.toml +++ b/frame/sassafras/Cargo.toml @@ -21,7 +21,6 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys log = { version = "0.4.17", default-features = false } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto", features = ["serde"] } sp-consensus-sassafras = { version = "0.3.3-dev", default-features = false, path = "../../primitives/consensus/sassafras", features = ["serde"] } sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } @@ -42,7 +41,6 @@ std = [ "pallet-session/std", "pallet-timestamp/std", "scale-info/std", - "sp-application-crypto/std", "sp-consensus-sassafras/std", "sp-io/std", "sp-runtime/std", diff --git a/frame/sassafras/src/lib.rs b/frame/sassafras/src/lib.rs index 312a73b244812..7d94df1f31f2b 100644 --- a/frame/sassafras/src/lib.rs +++ b/frame/sassafras/src/lib.rs @@ -54,7 +54,7 @@ use frame_support::{traits::Get, weights::Weight, BoundedVec, WeakBoundedVec}; use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; use sp_consensus_sassafras::{ digests::{ConsensusLog, NextEpochDescriptor, PreDigest}, - AuthorityId, Epoch, EquivocationProof, Randomness, SassafrasConfiguration, + AuthorityId, Epoch, EquivocationProof, Randomness, RingVrfContext, SassafrasConfiguration, SassafrasEpochConfiguration, Slot, TicketData, TicketEnvelope, TicketId, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, }; @@ -228,6 +228,12 @@ pub mod pallet { pub type NextTicketsSegments = StorageMap<_, Identity, u32, BoundedVec, ValueQuery>; + /// Parameters used to verify tickets validity via ring-proof + /// In practice: Updatable Universal Reference String and the seed. + #[pallet::storage] + #[pallet::getter(fn ring_context)] + pub type RingContext = StorageValue<_, RingVrfContext>; + /// Genesis configuration for Sassafras protocol. #[derive(Default)] #[pallet::genesis_config] @@ -349,6 +355,19 @@ pub mod pallet { log::debug!(target: LOG_TARGET, "Received {} tickets", tickets.len()); + log::debug!(target: LOG_TARGET, "LOADING RING CTX"); + let Some(ring_ctx) = RingContext::::get() else { + log::info!(target: LOG_TARGET, "Ring context not initialized yet"); + return Err("Ring context not initialized".into()) + }; + log::debug!(target: LOG_TARGET, "... Loaded"); + + // TODO Probably is better to cache the epoch prover in the storage + log::debug!(target: LOG_TARGET, "Building prover"); + let pks: Vec<_> = Self::authorities().iter().map(|auth| *auth.as_ref()).collect(); + let verifier = ring_ctx.verifier(pks.as_slice()).unwrap(); + log::debug!(target: LOG_TARGET, "... Built"); + // Check tickets score let next_auth = NextAuthorities::::get(); let epoch_config = EpochConfig::::get(); @@ -367,21 +386,31 @@ pub mod pallet { let mut segment = BoundedVec::with_max_capacity(); for ticket in tickets { + log::debug!(target: LOG_TARGET, "Checking ring proof"); + let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input( &randomness, ticket.data.attempt_idx, epoch_idx, ); + let ticket_id = sp_consensus_sassafras::ticket_id(&vrf_input, &ticket.vrf_preout); - if ticket_id < ticket_threshold { + if ticket_id >= ticket_threshold { + log::debug!(target: LOG_TARGET, "Over threshold"); + continue + } + + let mut sign_data = sp_consensus_sassafras::ticket_body_sign_data(&ticket.data); + sign_data.push_vrf_input(vrf_input).expect("Can't fail"); + + if ticket.ring_signature.verify(&sign_data, &verifier) { TicketsData::::set(ticket_id, ticket.data.clone()); segment .try_push(ticket_id) .expect("has same length as bounded input vector; qed"); + } else { + log::debug!(target: LOG_TARGET, "Proof verification failure"); } - - // TODO davxy TEST RING PROOF - log::debug!(target: LOG_TARGET, "Checking ring proof for {:16x}", ticket_id); } if !segment.is_empty() { diff --git a/frame/sassafras/src/mock.rs b/frame/sassafras/src/mock.rs index ca719b284339a..20922e0222b89 100644 --- a/frame/sassafras/src/mock.rs +++ b/frame/sassafras/src/mock.rs @@ -22,8 +22,8 @@ use crate::{self as pallet_sassafras, SameAuthoritiesForever}; use frame_support::traits::{ConstU32, ConstU64, GenesisBuild, OnFinalize, OnInitialize}; use scale_codec::Encode; use sp_consensus_sassafras::{ - digests::PreDigest, AuthorityIndex, AuthorityPair, SassafrasEpochConfiguration, Slot, - TicketData, TicketEnvelope, VrfSignature, + digests::PreDigest, AuthorityIndex, AuthorityPair, RingProver, RingVrfContext, + SassafrasEpochConfiguration, Slot, TicketData, TicketEnvelope, VrfSignature, }; use sp_core::{ crypto::{Pair, VrfSecret}, @@ -142,7 +142,13 @@ pub fn new_test_ext_with_pairs( (pairs, storage.into()) } -fn make_ticket(slot: Slot, attempt: u32, pair: &AuthorityPair) -> TicketEnvelope { +fn make_ticket_with_prover( + slot: Slot, + attempt: u32, + pair: &AuthorityPair, + prover: &RingProver, +) -> TicketEnvelope { + println!("ATTEMPT: {}", attempt); let mut epoch = Sassafras::epoch_index(); let mut randomness = Sassafras::randomness(); @@ -156,19 +162,56 @@ fn make_ticket(slot: Slot, attempt: u32, pair: &AuthorityPair) -> TicketEnvelope let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input(&randomness, attempt, epoch); let vrf_preout = pair.as_ref().vrf_output(&vrf_input.into()); - // TODO DAVXY: use some well known valid test keys... + // Ticket-id can be generated via vrf-preout. + // We don't care that much about the value here. + let data = TicketData { attempt_idx: attempt, erased_public: [0; 32], revealed_public: [0; 32] }; - TicketEnvelope { data, vrf_preout, ring_signature: () } + + let sign_data = sp_consensus_sassafras::ticket_body_sign_data(&data); + let ring_signature = pair.as_ref().ring_vrf_sign(&sign_data, prover); + + TicketEnvelope { data, vrf_preout, ring_signature } +} + +pub fn make_prover(_pair: &AuthorityPair, ring_ctx: &RingVrfContext) -> RingProver { + let authorities = Sassafras::authorities(); + let pks: Vec = + authorities.iter().map(|auth| *auth.as_ref()).collect(); + + // TODO davxy: search into pks for pair.public + let prover_idx = 0; + + println!("Make prover"); + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + println!("Done"); + + prover } -/// Construct at most `attempts` tickets for the given `slot`. +// pub fn make_ticket( +// slot: Slot, +// attempt: u32, +// pair: &AuthorityPair, +// ring_ctx: &RingVrfContext, +// ) -> TicketEnvelope { +// let prover = make_prover(pair, ring_ctx); +// make_ticket_with_prover(slot, attempt, pair, &prover) +// } + +/// Construct at most `attempts` tickets envelopes for the given `slot`. /// TODO-SASS-P3: filter out invalid tickets according to test threshold. /// E.g. by passing an optional threshold -pub fn make_tickets(slot: Slot, attempts: u32, pair: &AuthorityPair) -> Vec { +pub fn make_tickets( + slot: Slot, + attempts: u32, + pair: &AuthorityPair, + ring_ctx: &RingVrfContext, +) -> Vec { + let prover = make_prover(pair, ring_ctx); (0..attempts) .into_iter() - .map(|attempt| make_ticket(slot, attempt, pair)) + .map(|attempt| make_ticket_with_prover(slot, attempt, pair, &prover)) .collect() } diff --git a/frame/sassafras/src/tests.rs b/frame/sassafras/src/tests.rs index c9e26f80fba04..7c39d75e23448 100644 --- a/frame/sassafras/src/tests.rs +++ b/frame/sassafras/src/tests.rs @@ -343,6 +343,8 @@ fn produce_epoch_change_digest_with_config() { }) } +// TODO davxy: create a read_tickets method which reads pre-constructed good tickets +// from a file. Creating this stuff "on-the-fly" is just too much expensive #[test] fn submit_segments_works() { let (pairs, mut ext) = new_test_ext_with_pairs(1); @@ -350,6 +352,10 @@ fn submit_segments_works() { // We're going to generate 14 segments. let segments_count = 3; + println!("TEST BEGIN"); + let ring_ctx = RingVrfContext::new_testing(); + println!("RING CTX BUILT"); + ext.execute_with(|| { let start_slot = Slot::from(100); let start_block = 1; @@ -362,8 +368,10 @@ fn submit_segments_works() { config.redundancy_factor = 2; EpochConfig::::set(config); + RingContext::::set(Some(ring_ctx.clone())); + // Populate the segments via the `submit_tickets` - let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair); + let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair, &ring_ctx); let segment_len = tickets.len() / segments_count as usize; for i in 0..segments_count as usize { let segment = @@ -386,233 +394,233 @@ fn submit_segments_works() { }) } -#[test] -fn segments_incremental_sortition_works() { - let (pairs, mut ext) = new_test_ext_with_pairs(1); - let pair = &pairs[0]; - let segments_count = 14; - - ext.execute_with(|| { - let start_slot = Slot::from(100); - let start_block = 1; - let max_tickets: u32 = ::MaxTickets::get(); - - initialize_block(start_block, start_slot, Default::default(), &pairs[0]); - - // Manually populate the segments to fool the threshold check - let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair); - let segment_len = tickets.len() / segments_count as usize; - - for i in 0..segments_count as usize { - let segment: Vec = tickets[i * segment_len..(i + 1) * segment_len] - .iter() - .enumerate() - .map(|(j, ticket)| { - let ticket_id = (i * segment_len + j) as TicketId; - TicketsData::::set(ticket_id, ticket.data.clone()); - ticket_id - }) - .collect(); - let segment = BoundedVec::truncate_from(segment); - NextTicketsSegments::::insert(i as u32, segment); - } - let meta = TicketsMetadata { segments_count, tickets_count: [0, 0] }; - TicketsMeta::::set(meta); - - let epoch_duration: u64 = ::EpochDuration::get(); - - // Proceed to half of the epoch (sortition should not have been started yet) - let half_epoch_block = start_block + epoch_duration / 2; - progress_to_block(half_epoch_block, pair); - - // Check that next epoch tickets sortition is not started yet - let meta = TicketsMeta::::get(); - assert_eq!(meta.segments_count, segments_count); - assert_eq!(meta.tickets_count, [0, 0]); - - // Follow incremental sortition block by block - - progress_to_block(half_epoch_block + 1, pair); - let meta = TicketsMeta::::get(); - assert_eq!(meta.segments_count, 12); - assert_eq!(meta.tickets_count, [0, 0]); - - progress_to_block(half_epoch_block + 2, pair); - let meta = TicketsMeta::::get(); - assert_eq!(meta.segments_count, 9); - assert_eq!(meta.tickets_count, [0, 0]); - - progress_to_block(half_epoch_block + 3, pair); - let meta = TicketsMeta::::get(); - assert_eq!(meta.segments_count, 6); - assert_eq!(meta.tickets_count, [0, 0]); - - progress_to_block(half_epoch_block + 4, pair); - let meta = TicketsMeta::::get(); - assert_eq!(meta.segments_count, 3); - assert_eq!(meta.tickets_count, [0, 0]); - - let header = finalize_block(half_epoch_block + 4); - - // Sort should be finished. - // Check that next epoch tickets count have the correct value (6). - // Bigger values were discarded during sortition. - let meta = TicketsMeta::::get(); - assert_eq!(meta.segments_count, 0); - assert_eq!(meta.tickets_count, [0, 6]); - assert_eq!(header.digest.logs.len(), 1); - - // The next block will be the first produced on the new epoch, - // At this point the tickets are found already sorted and ready to be used. - let slot = Sassafras::current_slot() + 1; - let number = System::block_number() + 1; - initialize_block(number, slot, header.hash(), pair); - let header = finalize_block(number); - // Epoch changes digest is also produced - assert_eq!(header.digest.logs.len(), 2); - }); -} - -#[test] -fn submit_enact_claim_tickets() { - let (pairs, mut ext) = new_test_ext_with_pairs(4); - - ext.execute_with(|| { - let start_slot = Slot::from(100); - let start_block = 1; - let max_tickets: u32 = ::MaxTickets::get(); - let pair = &pairs[0]; - - initialize_block(start_block, start_slot, Default::default(), pair); - - // We don't want to trigger an epoch change in this test. - let epoch_duration: u64 = ::EpochDuration::get(); - assert!(epoch_duration > 2); - progress_to_block(2, &pairs[0]).unwrap(); - - // // Check state before tickets submission - assert_eq!( - TicketsMeta::::get(), - TicketsMetadata { segments_count: 0, tickets_count: [0, 0] }, - ); - - // Submit authoring tickets in three different segments. - let tickets = make_tickets(start_slot + 1, 3 * max_tickets, pair); - let tickets0 = tickets[0..6].to_vec().try_into().unwrap(); - Sassafras::submit_tickets(RuntimeOrigin::none(), tickets0).unwrap(); - let tickets1 = tickets[6..12].to_vec().try_into().unwrap(); - Sassafras::submit_tickets(RuntimeOrigin::none(), tickets1).unwrap(); - let tickets2 = tickets[12..18].to_vec().try_into().unwrap(); - Sassafras::submit_tickets(RuntimeOrigin::none(), tickets2).unwrap(); - - // Check state after submit - assert_eq!( - TicketsMeta::::get(), - TicketsMetadata { segments_count: 3, tickets_count: [0, 0] }, - ); - - // Progress up to the last epoch slot (do not enact epoch change) - progress_to_block(epoch_duration, &pairs[0]).unwrap(); - - // At this point next tickets should have been sorted - // Check state after submit - assert_eq!( - TicketsMeta::::get(), - TicketsMetadata { segments_count: 0, tickets_count: [0, 6] }, - ); - - // Compute and sort the tickets ids (aka tickets scores) - let mut expected_ids: Vec<_> = tickets - .iter() - .map(|t| { - let epoch_idx = Sassafras::epoch_index() + 1; - let randomness = Sassafras::next_randomness(); - let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input( - &randomness, - t.data.attempt_idx, - epoch_idx, - ); - sp_consensus_sassafras::ticket_id(&vrf_input, &t.vrf_preout) - }) - .collect(); - expected_ids.sort(); - expected_ids.truncate(max_tickets as usize); - - // Check if we can claim next epoch tickets in outside-in fashion. - let slot = Sassafras::current_slot(); - assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[1]); - assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[3]); - assert_eq!(Sassafras::slot_ticket_id(slot + 3).unwrap(), expected_ids[5]); - assert!(Sassafras::slot_ticket_id(slot + 4).is_none()); - assert!(Sassafras::slot_ticket_id(slot + 7).is_none()); - assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[4]); - assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[2]); - assert_eq!(Sassafras::slot_ticket_id(slot + 10).unwrap(), expected_ids[0]); - assert!(Sassafras::slot_ticket_id(slot + 11).is_none()); - - // Enact epoch change by progressing one more block - - progress_to_block(epoch_duration + 1, &pairs[0]).unwrap(); - - let meta = TicketsMeta::::get(); - assert_eq!(meta.segments_count, 0); - assert_eq!(meta.tickets_count, [0, 6]); - - let slot = Sassafras::current_slot(); - assert_eq!(Sassafras::slot_ticket_id(slot).unwrap(), expected_ids[1]); - assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[3]); - assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[5]); - assert!(Sassafras::slot_ticket_id(slot + 3).is_none()); - assert!(Sassafras::slot_ticket_id(slot + 6).is_none()); - assert_eq!(Sassafras::slot_ticket_id(slot + 7).unwrap(), expected_ids[4]); - assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[2]); - assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[0]); - assert!(Sassafras::slot_ticket_id(slot + 10).is_none()); - }); -} - -#[test] -fn block_allowed_to_skip_epochs() { - let (pairs, mut ext) = new_test_ext_with_pairs(4); - - ext.execute_with(|| { - let start_slot = Slot::from(100); - let start_block = 1; - let epoch_duration: u64 = ::EpochDuration::get(); - - initialize_block(start_block, start_slot, Default::default(), &pairs[0]); - - let tickets = make_tickets(start_slot + 1, 3, &pairs[0]); - Sassafras::submit_tickets( - RuntimeOrigin::none(), - BoundedVec::truncate_from(tickets.clone()), - ) - .unwrap(); - - // Force sortition of next tickets (enactment) by explicitly querying next epoch tickets. - assert_eq!(TicketsMeta::::get().segments_count, 1); - Sassafras::slot_ticket(start_slot + epoch_duration).unwrap(); - assert_eq!(TicketsMeta::::get().segments_count, 0); - - let next_random = NextRandomness::::get(); - - // We want to trigger a skip epoch in this test. - let offset = 3 * epoch_duration; - go_to_block(start_block + offset, start_slot + offset, &pairs[0]); - - // Post-initialization status - - assert!(Initialized::::get().is_some()); - assert_eq!(Sassafras::genesis_slot(), start_slot); - assert_eq!(Sassafras::current_slot(), start_slot + offset); - assert_eq!(Sassafras::epoch_index(), 3); - assert_eq!(Sassafras::current_epoch_start(), start_slot + offset); - assert_eq!(Sassafras::current_slot_index(), 0); - - // Tickets were discarded - let meta = TicketsMeta::::get(); - assert_eq!(meta, TicketsMetadata::default()); - // We used the last known next epoch randomness as a fallback - assert_eq!(next_random, Sassafras::randomness()); - }); -} +// #[test] +// fn segments_incremental_sortition_works() { +// let (pairs, mut ext) = new_test_ext_with_pairs(1); +// let pair = &pairs[0]; +// let segments_count = 14; + +// ext.execute_with(|| { +// let start_slot = Slot::from(100); +// let start_block = 1; +// let max_tickets: u32 = ::MaxTickets::get(); + +// initialize_block(start_block, start_slot, Default::default(), &pairs[0]); + +// // Manually populate the segments to fool the threshold check +// let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair); +// let segment_len = tickets.len() / segments_count as usize; + +// for i in 0..segments_count as usize { +// let segment: Vec = tickets[i * segment_len..(i + 1) * segment_len] +// .iter() +// .enumerate() +// .map(|(j, ticket)| { +// let ticket_id = (i * segment_len + j) as TicketId; +// TicketsData::::set(ticket_id, ticket.data.clone()); +// ticket_id +// }) +// .collect(); +// let segment = BoundedVec::truncate_from(segment); +// NextTicketsSegments::::insert(i as u32, segment); +// } +// let meta = TicketsMetadata { segments_count, tickets_count: [0, 0] }; +// TicketsMeta::::set(meta); + +// let epoch_duration: u64 = ::EpochDuration::get(); + +// // Proceed to half of the epoch (sortition should not have been started yet) +// let half_epoch_block = start_block + epoch_duration / 2; +// progress_to_block(half_epoch_block, pair); + +// // Check that next epoch tickets sortition is not started yet +// let meta = TicketsMeta::::get(); +// assert_eq!(meta.segments_count, segments_count); +// assert_eq!(meta.tickets_count, [0, 0]); + +// // Follow incremental sortition block by block + +// progress_to_block(half_epoch_block + 1, pair); +// let meta = TicketsMeta::::get(); +// assert_eq!(meta.segments_count, 12); +// assert_eq!(meta.tickets_count, [0, 0]); + +// progress_to_block(half_epoch_block + 2, pair); +// let meta = TicketsMeta::::get(); +// assert_eq!(meta.segments_count, 9); +// assert_eq!(meta.tickets_count, [0, 0]); + +// progress_to_block(half_epoch_block + 3, pair); +// let meta = TicketsMeta::::get(); +// assert_eq!(meta.segments_count, 6); +// assert_eq!(meta.tickets_count, [0, 0]); + +// progress_to_block(half_epoch_block + 4, pair); +// let meta = TicketsMeta::::get(); +// assert_eq!(meta.segments_count, 3); +// assert_eq!(meta.tickets_count, [0, 0]); + +// let header = finalize_block(half_epoch_block + 4); + +// // Sort should be finished. +// // Check that next epoch tickets count have the correct value (6). +// // Bigger values were discarded during sortition. +// let meta = TicketsMeta::::get(); +// assert_eq!(meta.segments_count, 0); +// assert_eq!(meta.tickets_count, [0, 6]); +// assert_eq!(header.digest.logs.len(), 1); + +// // The next block will be the first produced on the new epoch, +// // At this point the tickets are found already sorted and ready to be used. +// let slot = Sassafras::current_slot() + 1; +// let number = System::block_number() + 1; +// initialize_block(number, slot, header.hash(), pair); +// let header = finalize_block(number); +// // Epoch changes digest is also produced +// assert_eq!(header.digest.logs.len(), 2); +// }); +// } + +// #[test] +// fn submit_enact_claim_tickets() { +// let (pairs, mut ext) = new_test_ext_with_pairs(4); + +// ext.execute_with(|| { +// let start_slot = Slot::from(100); +// let start_block = 1; +// let max_tickets: u32 = ::MaxTickets::get(); +// let pair = &pairs[0]; + +// initialize_block(start_block, start_slot, Default::default(), pair); + +// // We don't want to trigger an epoch change in this test. +// let epoch_duration: u64 = ::EpochDuration::get(); +// assert!(epoch_duration > 2); +// progress_to_block(2, &pairs[0]).unwrap(); + +// // // Check state before tickets submission +// assert_eq!( +// TicketsMeta::::get(), +// TicketsMetadata { segments_count: 0, tickets_count: [0, 0] }, +// ); + +// // Submit authoring tickets in three different segments. +// let tickets = make_tickets(start_slot + 1, 3 * max_tickets, pair); +// let tickets0 = tickets[0..6].to_vec().try_into().unwrap(); +// Sassafras::submit_tickets(RuntimeOrigin::none(), tickets0).unwrap(); +// let tickets1 = tickets[6..12].to_vec().try_into().unwrap(); +// Sassafras::submit_tickets(RuntimeOrigin::none(), tickets1).unwrap(); +// let tickets2 = tickets[12..18].to_vec().try_into().unwrap(); +// Sassafras::submit_tickets(RuntimeOrigin::none(), tickets2).unwrap(); + +// // Check state after submit +// assert_eq!( +// TicketsMeta::::get(), +// TicketsMetadata { segments_count: 3, tickets_count: [0, 0] }, +// ); + +// // Progress up to the last epoch slot (do not enact epoch change) +// progress_to_block(epoch_duration, &pairs[0]).unwrap(); + +// // At this point next tickets should have been sorted +// // Check state after submit +// assert_eq!( +// TicketsMeta::::get(), +// TicketsMetadata { segments_count: 0, tickets_count: [0, 6] }, +// ); + +// // Compute and sort the tickets ids (aka tickets scores) +// let mut expected_ids: Vec<_> = tickets +// .iter() +// .map(|t| { +// let epoch_idx = Sassafras::epoch_index() + 1; +// let randomness = Sassafras::next_randomness(); +// let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input( +// &randomness, +// t.data.attempt_idx, +// epoch_idx, +// ); +// sp_consensus_sassafras::ticket_id(&vrf_input, &t.vrf_preout) +// }) +// .collect(); +// expected_ids.sort(); +// expected_ids.truncate(max_tickets as usize); + +// // Check if we can claim next epoch tickets in outside-in fashion. +// let slot = Sassafras::current_slot(); +// assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[1]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[3]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 3).unwrap(), expected_ids[5]); +// assert!(Sassafras::slot_ticket_id(slot + 4).is_none()); +// assert!(Sassafras::slot_ticket_id(slot + 7).is_none()); +// assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[4]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[2]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 10).unwrap(), expected_ids[0]); +// assert!(Sassafras::slot_ticket_id(slot + 11).is_none()); + +// // Enact epoch change by progressing one more block + +// progress_to_block(epoch_duration + 1, &pairs[0]).unwrap(); + +// let meta = TicketsMeta::::get(); +// assert_eq!(meta.segments_count, 0); +// assert_eq!(meta.tickets_count, [0, 6]); + +// let slot = Sassafras::current_slot(); +// assert_eq!(Sassafras::slot_ticket_id(slot).unwrap(), expected_ids[1]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[3]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[5]); +// assert!(Sassafras::slot_ticket_id(slot + 3).is_none()); +// assert!(Sassafras::slot_ticket_id(slot + 6).is_none()); +// assert_eq!(Sassafras::slot_ticket_id(slot + 7).unwrap(), expected_ids[4]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[2]); +// assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[0]); +// assert!(Sassafras::slot_ticket_id(slot + 10).is_none()); +// }); +// } + +// #[test] +// fn block_allowed_to_skip_epochs() { +// let (pairs, mut ext) = new_test_ext_with_pairs(4); + +// ext.execute_with(|| { +// let start_slot = Slot::from(100); +// let start_block = 1; +// let epoch_duration: u64 = ::EpochDuration::get(); + +// initialize_block(start_block, start_slot, Default::default(), &pairs[0]); + +// let tickets = make_tickets(start_slot + 1, 3, &pairs[0]); +// Sassafras::submit_tickets( +// RuntimeOrigin::none(), +// BoundedVec::truncate_from(tickets.clone()), +// ) +// .unwrap(); + +// // Force sortition of next tickets (enactment) by explicitly querying next epoch tickets. +// assert_eq!(TicketsMeta::::get().segments_count, 1); +// Sassafras::slot_ticket(start_slot + epoch_duration).unwrap(); +// assert_eq!(TicketsMeta::::get().segments_count, 0); + +// let next_random = NextRandomness::::get(); + +// // We want to trigger a skip epoch in this test. +// let offset = 3 * epoch_duration; +// go_to_block(start_block + offset, start_slot + offset, &pairs[0]); + +// // Post-initialization status + +// assert!(Initialized::::get().is_some()); +// assert_eq!(Sassafras::genesis_slot(), start_slot); +// assert_eq!(Sassafras::current_slot(), start_slot + offset); +// assert_eq!(Sassafras::epoch_index(), 3); +// assert_eq!(Sassafras::current_epoch_start(), start_slot + offset); +// assert_eq!(Sassafras::current_slot_index(), 0); + +// // Tickets were discarded +// let meta = TicketsMeta::::get(); +// assert_eq!(meta, TicketsMetadata::default()); +// // We used the last known next epoch randomness as a fallback +// assert_eq!(next_random, Sassafras::randomness()); +// }); +// } diff --git a/primitives/consensus/sassafras/src/lib.rs b/primitives/consensus/sassafras/src/lib.rs index 59b2362849913..6d777da109358 100644 --- a/primitives/consensus/sassafras/src/lib.rs +++ b/primitives/consensus/sassafras/src/lib.rs @@ -32,7 +32,10 @@ use sp_runtime::{ConsensusEngineId, RuntimeDebug}; use sp_std::vec::Vec; pub use sp_consensus_slots::{Slot, SlotDuration}; -pub use sp_core::bandersnatch::vrf::{VrfInput, VrfOutput, VrfSignData, VrfSignature}; +pub use sp_core::bandersnatch::{ + ring_vrf::{RingProver, RingVerifier, RingVrfContext}, + vrf::{VrfInput, VrfOutput, VrfSignData, VrfSignature}, +}; pub mod digests; pub mod inherents; @@ -139,6 +142,9 @@ pub struct OpaqueKeyOwnershipProof(Vec); sp_api::decl_runtime_apis! { /// API necessary for block authorship with Sassafras. pub trait SassafrasApi { + /// Get ring context to be used for ticket construction and verification. + fn ring_context() -> Option; + /// Submit next epoch validator tickets via an unsigned extrinsic. /// This method returns `false` when creation of the extrinsics fails. fn submit_tickets_unsigned_extrinsic(tickets: Vec) -> bool; diff --git a/primitives/consensus/sassafras/src/ticket.rs b/primitives/consensus/sassafras/src/ticket.rs index 055614409c91b..31141d4060b94 100644 --- a/primitives/consensus/sassafras/src/ticket.rs +++ b/primitives/consensus/sassafras/src/ticket.rs @@ -52,6 +52,7 @@ pub struct TicketEnvelope { /// VRF output. pub data: TicketData, /// VRF pre-output used to generate the ticket id. + /// TODO davxy: this should be taken from ring signature pub vrf_preout: VrfOutput, /// Ring signature. pub ring_signature: TicketRingSignature, @@ -97,15 +98,6 @@ pub fn slot_claim_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> .expect("can't fail; qed") } -pub fn ticket_body_sign_data(ticket_body: &TicketData) -> VrfSignData { - VrfSignData::from_iter( - &SASSAFRAS_ENGINE_ID, - &[b"ticket-body-transcript", ticket_body.encode().as_slice()], - [], - ) - .expect("can't fail; qed") -} - /// VRF input to generate the ticket id. /// /// Input randomness is current epoch randomness. @@ -121,6 +113,16 @@ pub fn ticket_id_vrf_input(randomness: &Randomness, attempt: u32, epoch: u64) -> ) } +/// Data to be signed via ring-vrf. +pub fn ticket_body_sign_data(ticket_body: &TicketData) -> VrfSignData { + VrfSignData::from_iter( + &SASSAFRAS_ENGINE_ID, + &[b"ticket-body-transcript", ticket_body.encode().as_slice()], + [], + ) + .expect("can't fail; qed") +} + /// Get ticket-id for a given vrf input and output. /// /// Input generally obtained via `ticket_id_vrf_input`. diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 1007307b73815..68df3dd2b40e4 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -32,7 +32,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime_interface::pass_by::PassByInner; -use sp_std::vec::Vec; +use sp_std::{boxed::Box, vec::Vec}; /// Identifier used to match public keys against bandersnatch-vrf keys. pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bs38"); @@ -65,6 +65,9 @@ const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; // Short-Weierstrass form sizes const RING_PROOF_SERIALIZED_LEN: usize = 592; +// Sise of serialized ring-vrf context params +const RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN: usize = 147744; + /// XXX. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive( @@ -309,7 +312,7 @@ pub mod vrf { pub type VrfIosVec = BoundedVec>; /// Input to be used for VRF sign and verify operations. - #[derive(Clone)] + #[derive(Clone, Debug)] pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); impl VrfInput { @@ -575,7 +578,8 @@ pub mod ring_vrf { use bandersnatch_vrfs::{CanonicalDeserialize, PedersenVrfSignature, PublicKey}; /// TODO davxy - pub struct RingVrfContext(KZG); + #[derive(Clone)] + pub struct RingVrfContext(pub KZG); impl RingVrfContext { /// TODO davxy: This is a temporary function with temporary parameters. @@ -639,8 +643,43 @@ pub mod ring_vrf { } } + // TODO davxy: why this isn't implemented automagically, is there some other required bound??? + impl codec::EncodeLike for RingVrfContext {} + + impl Encode for RingVrfContext { + fn encode(&self) -> Vec { + let mut buf = Box::new([0; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]); + self.0 + .serialize_compressed(buf.as_mut_slice()) + .expect("preout serialization can't fail"); + buf.encode() + } + } + + impl Decode for RingVrfContext { + fn decode(i: &mut R) -> Result { + let buf = >::decode(i)?; + let kzg = + KZG::deserialize_compressed(buf.as_slice()).map_err(|_| "KZG decode error")?; + Ok(RingVrfContext(kzg)) + } + } + + impl MaxEncodedLen for RingVrfContext { + fn max_encoded_len() -> usize { + <[u8; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]>::max_encoded_len() + } + } + + impl TypeInfo for RingVrfContext { + type Identity = [u8; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } + /// Ring VRF signature. - // #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct RingVrfSignature { /// VRF (pre)outputs. @@ -949,4 +988,18 @@ mod tests { let decoded = RingVrfSignature::decode(&mut &bytes[..]).unwrap(); assert_eq!(expected, decoded); } + + #[test] + fn encode_decode_ring_vrf_context() { + let ring_ctx = RingVrfContext::new_testing(); + + let encoded = ring_ctx.encode(); + println!("SIZE: {}", encoded.len()); + + assert_eq!(encoded.len(), RingVrfContext::max_encoded_len()); + + let _decoded = RingVrfContext::decode(&mut &encoded[..]).unwrap(); + + // TODO davxy... just use unsafe pointers comparison + } } From 8df61f6338b8d198b0823f3b3695811866371c7d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 15 Jun 2023 19:29:37 +0200 Subject: [PATCH 10/14] Working ring context initialization during genesis build --- client/consensus/sassafras/src/authorship.rs | 26 +++++++++++++------- frame/sassafras/src/lib.rs | 5 +++- primitives/core/src/bandersnatch.rs | 3 +-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index dba282e809eaa..ed5a5ee3d0925 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -404,10 +404,6 @@ async fn start_tickets_worker( C::Api: SassafrasApi, SC: SelectChain + 'static, { - log::debug!(target: LOG_TARGET, ">>> Creating testing ring-vrf context..."); - let ring_ctx = RingVrfContext::new_testing(); - log::debug!(target: LOG_TARGET, ">>> ...done"); - let mut notifications = client.import_notification_stream(); while let Some(notification) = notifications.next().await { @@ -441,11 +437,6 @@ async fn start_tickets_worker( }, }; - let tickets = generate_epoch_tickets(&mut epoch, &keystore, &ring_ctx); - if tickets.is_empty() { - continue - } - // Get the best block on which we will publish the tickets. let best_hash = match select_chain.best_chain().await { Ok(header) => header.hash(), @@ -455,6 +446,23 @@ async fn start_tickets_worker( }, }; + let ring_ctx = match client.runtime_api().ring_context(best_hash) { + Ok(Some(ctx)) => ctx, + Ok(None) => { + info!(target: LOG_TARGET, "Ring context not initialized yet"); + continue + }, + Err(err) => { + error!(target: LOG_TARGET, "Unable to read ring context: {}", err); + continue + }, + }; + + let tickets = generate_epoch_tickets(&mut epoch, &keystore, &ring_ctx); + if tickets.is_empty() { + continue + } + let err = match client.runtime_api().submit_tickets_unsigned_extrinsic(best_hash, tickets) { Err(err) => Some(err.to_string()), Ok(false) => Some("Unknown reason".to_string()), diff --git a/frame/sassafras/src/lib.rs b/frame/sassafras/src/lib.rs index 7d94df1f31f2b..24e8bd84690bb 100644 --- a/frame/sassafras/src/lib.rs +++ b/frame/sassafras/src/lib.rs @@ -249,6 +249,10 @@ pub mod pallet { fn build(&self) { Pallet::::initialize_genesis_authorities(&self.authorities); EpochConfig::::put(self.epoch_config.clone()); + log::debug!(target: LOG_TARGET, "Building new testing ring context"); + let ctx = RingVrfContext::new_testing(); + RingContext::::set(Some(ctx.clone())); + log::debug!(target: LOG_TARGET, "... Building Done"); } } @@ -357,7 +361,6 @@ pub mod pallet { log::debug!(target: LOG_TARGET, "LOADING RING CTX"); let Some(ring_ctx) = RingContext::::get() else { - log::info!(target: LOG_TARGET, "Ring context not initialized yet"); return Err("Ring context not initialized".into()) }; log::debug!(target: LOG_TARGET, "... Loaded"); diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 68df3dd2b40e4..1b8d785f38abd 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -586,11 +586,10 @@ pub mod ring_vrf { /// /// Initialization cerimony should be performed via some other means /// For now we call this once here. - #[cfg(feature = "std")] pub fn new_testing() -> Self { let kzg_seed = [0; 32]; let domain_size = 2usize.pow(10); - let kzg = KZG::insecure_kzg_setup(kzg_seed, domain_size); + let kzg = KZG::testing_kzg_setup(kzg_seed, domain_size); Self(kzg) } From b84176a07250aff4e6945484aa6f54559897f54a Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 15 Jun 2023 19:55:11 +0200 Subject: [PATCH 11/14] Remove some duplicated ticket elements and cleanup --- bin/node-sassafras/runtime/src/lib.rs | 2 +- client/consensus/sassafras/src/authorship.rs | 10 ++++---- client/consensus/sassafras/src/lib.rs | 2 +- .../consensus/sassafras/src/verification.rs | 2 +- frame/sassafras/src/lib.rs | 25 +++++++++++-------- primitives/consensus/sassafras/src/lib.rs | 4 +-- primitives/consensus/sassafras/src/ticket.rs | 11 +++----- primitives/core/src/bandersnatch.rs | 2 +- 8 files changed, 30 insertions(+), 28 deletions(-) diff --git a/bin/node-sassafras/runtime/src/lib.rs b/bin/node-sassafras/runtime/src/lib.rs index be43bce4d9335..7d5062105d0f8 100644 --- a/bin/node-sassafras/runtime/src/lib.rs +++ b/bin/node-sassafras/runtime/src/lib.rs @@ -401,7 +401,7 @@ impl_runtime_apis! { Sassafras::slot_ticket_id(slot) } - fn slot_ticket(slot: sp_consensus_sassafras::Slot) -> Option<(sp_consensus_sassafras::TicketId, sp_consensus_sassafras::TicketData)> { + fn slot_ticket(slot: sp_consensus_sassafras::Slot) -> Option<(sp_consensus_sassafras::TicketId, sp_consensus_sassafras::TicketBody)> { Sassafras::slot_ticket(slot) } diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index ed5a5ee3d0925..75344b15c4f4c 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -22,7 +22,7 @@ use super::*; use sp_consensus_sassafras::{ digests::PreDigest, slot_claim_sign_data, ticket_id, ticket_id_threshold, AuthorityId, Slot, - TicketClaim, TicketData, TicketEnvelope, TicketId, + TicketBody, TicketClaim, TicketEnvelope, TicketId, }; use sp_core::{bandersnatch::ring_vrf::RingVrfContext, ed25519, twox_64, ByteArray}; use std::pin::Pin; @@ -41,7 +41,7 @@ pub(crate) fn secondary_authority_index( pub(crate) fn claim_slot( slot: Slot, epoch: &mut Epoch, - maybe_ticket: Option<(TicketId, TicketData)>, + maybe_ticket: Option<(TicketId, TicketBody)>, keystore: &KeystorePtr, ) -> Option<(PreDigest, AuthorityId)> { let config = &epoch.config; @@ -142,10 +142,10 @@ fn generate_epoch_tickets( let erased_public: [u8; 32] = *erased_pair.public().as_ref(); let revealed_public = [0; 32]; - let data = TicketData { attempt_idx, erased_public, revealed_public }; + let ticket_body = TicketBody { attempt_idx, erased_public, revealed_public }; debug!(target: LOG_TARGET, ">>> Creating ring proof for attempt {}", attempt_idx); - let mut sign_data = ticket_body_sign_data(&data); + let mut sign_data = ticket_body_sign_data(&ticket_body); sign_data.push_vrf_input(vrf_input).expect("Can't fail"); let ring_signature = keystore @@ -158,7 +158,7 @@ fn generate_epoch_tickets( .ok()??; debug!(target: LOG_TARGET, ">>> ...done"); - let ticket_envelope = TicketEnvelope { data, vrf_preout, ring_signature }; + let ticket_envelope = TicketEnvelope { body: ticket_body, ring_signature }; let ticket_secret = TicketSecret { attempt_idx, erased_secret: erased_seed }; diff --git a/client/consensus/sassafras/src/lib.rs b/client/consensus/sassafras/src/lib.rs index e8491caeae7bb..aafea3acbfcec 100644 --- a/client/consensus/sassafras/src/lib.rs +++ b/client/consensus/sassafras/src/lib.rs @@ -79,7 +79,7 @@ pub use sp_consensus_sassafras::{ inherents::SassafrasInherentData, slot_claim_sign_data, slot_claim_vrf_input, ticket_body_sign_data, ticket_id_vrf_input, AuthorityId, AuthorityIndex, AuthorityPair, AuthoritySignature, SassafrasApi, - SassafrasConfiguration, SassafrasEpochConfiguration, TicketClaim, TicketData, TicketEnvelope, + SassafrasConfiguration, SassafrasEpochConfiguration, TicketBody, TicketClaim, TicketEnvelope, TicketId, TicketSecret, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, }; diff --git a/client/consensus/sassafras/src/verification.rs b/client/consensus/sassafras/src/verification.rs index 3772d86b0e86f..4206ef195c5d0 100644 --- a/client/consensus/sassafras/src/verification.rs +++ b/client/consensus/sassafras/src/verification.rs @@ -38,7 +38,7 @@ struct VerificationParams<'a, B: 'a + BlockT> { /// Origin origin: BlockOrigin, /// Expected ticket for this block. - maybe_ticket: Option<(TicketId, TicketData)>, + maybe_ticket: Option<(TicketId, TicketBody)>, } /// Verified information diff --git a/frame/sassafras/src/lib.rs b/frame/sassafras/src/lib.rs index 24e8bd84690bb..20a4782120835 100644 --- a/frame/sassafras/src/lib.rs +++ b/frame/sassafras/src/lib.rs @@ -55,7 +55,7 @@ use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; use sp_consensus_sassafras::{ digests::{ConsensusLog, NextEpochDescriptor, PreDigest}, AuthorityId, Epoch, EquivocationProof, Randomness, RingVrfContext, SassafrasConfiguration, - SassafrasEpochConfiguration, Slot, TicketData, TicketEnvelope, TicketId, RANDOMNESS_LENGTH, + SassafrasEpochConfiguration, Slot, TicketBody, TicketEnvelope, TicketId, RANDOMNESS_LENGTH, SASSAFRAS_ENGINE_ID, }; use sp_io::hashing; @@ -218,7 +218,7 @@ pub mod pallet { /// Tickets to be used for current and next epoch. #[pallet::storage] - pub type TicketsData = StorageMap<_, Identity, TicketId, TicketData, ValueQuery>; + pub type TicketsData = StorageMap<_, Identity, TicketId, TicketBody, ValueQuery>; /// Next epoch tickets accumulator. /// Special `u32::MAX` key is reserved for a partially sorted segment. @@ -249,10 +249,11 @@ pub mod pallet { fn build(&self) { Pallet::::initialize_genesis_authorities(&self.authorities); EpochConfig::::put(self.epoch_config.clone()); + + // TODO davxy : temporary code to generate a testing ring context log::debug!(target: LOG_TARGET, "Building new testing ring context"); - let ctx = RingVrfContext::new_testing(); - RingContext::::set(Some(ctx.clone())); - log::debug!(target: LOG_TARGET, "... Building Done"); + let ring_ctx = RingVrfContext::new_testing(); + RingContext::::set(Some(ring_ctx)); } } @@ -393,21 +394,25 @@ pub mod pallet { let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input( &randomness, - ticket.data.attempt_idx, + ticket.body.attempt_idx, epoch_idx, ); - let ticket_id = sp_consensus_sassafras::ticket_id(&vrf_input, &ticket.vrf_preout); + let Some(vrf_preout) = ticket.ring_signature.outputs.get(0) else { + log::debug!(target: LOG_TARGET, "Missing ticket pre-output from ring signature"); + continue; + }; + let ticket_id = sp_consensus_sassafras::ticket_id(&vrf_input, &vrf_preout); if ticket_id >= ticket_threshold { log::debug!(target: LOG_TARGET, "Over threshold"); continue } - let mut sign_data = sp_consensus_sassafras::ticket_body_sign_data(&ticket.data); + let mut sign_data = sp_consensus_sassafras::ticket_body_sign_data(&ticket.body); sign_data.push_vrf_input(vrf_input).expect("Can't fail"); if ticket.ring_signature.verify(&sign_data, &verifier) { - TicketsData::::set(ticket_id, ticket.data.clone()); + TicketsData::::set(ticket_id, ticket.body.clone()); segment .try_push(ticket_id) .expect("has same length as bounded input vector; qed"); @@ -846,7 +851,7 @@ impl Pallet { /// /// Refer to the `slot_ticket_id` documentation for the slot-ticket association /// criteria. - pub fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketData)> { + pub fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketBody)> { Self::slot_ticket_id(slot).map(|id| (id, TicketsData::::get(id))) } diff --git a/primitives/consensus/sassafras/src/lib.rs b/primitives/consensus/sassafras/src/lib.rs index 6d777da109358..5b9e52ab28531 100644 --- a/primitives/consensus/sassafras/src/lib.rs +++ b/primitives/consensus/sassafras/src/lib.rs @@ -43,7 +43,7 @@ pub mod ticket; pub use ticket::{ slot_claim_sign_data, slot_claim_vrf_input, ticket_body_sign_data, ticket_id, - ticket_id_threshold, ticket_id_vrf_input, TicketClaim, TicketData, TicketEnvelope, TicketId, + ticket_id_threshold, ticket_id_vrf_input, TicketBody, TicketClaim, TicketEnvelope, TicketId, TicketSecret, }; @@ -153,7 +153,7 @@ sp_api::decl_runtime_apis! { fn slot_ticket_id(slot: Slot) -> Option; /// Get ticket id and data associated to the given slot. - fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketData)>; + fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketBody)>; /// Current epoch information. fn current_epoch() -> Epoch; diff --git a/primitives/consensus/sassafras/src/ticket.rs b/primitives/consensus/sassafras/src/ticket.rs index 31141d4060b94..716558f27e67a 100644 --- a/primitives/consensus/sassafras/src/ticket.rs +++ b/primitives/consensus/sassafras/src/ticket.rs @@ -34,7 +34,7 @@ pub type TicketId = u128; /// Ticket data persisted on-chain. #[derive(Debug, Default, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] -pub struct TicketData { +pub struct TicketBody { /// Attempt index. pub attempt_idx: u32, /// Ed25519 public key which gets erased when claiming the ticket. @@ -49,11 +49,8 @@ pub type TicketRingSignature = RingVrfSignature; /// Ticket envelope used on during submission. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct TicketEnvelope { - /// VRF output. - pub data: TicketData, - /// VRF pre-output used to generate the ticket id. - /// TODO davxy: this should be taken from ring signature - pub vrf_preout: VrfOutput, + /// Ticket body. + pub body: TicketBody, /// Ring signature. pub ring_signature: TicketRingSignature, } @@ -114,7 +111,7 @@ pub fn ticket_id_vrf_input(randomness: &Randomness, attempt: u32, epoch: u64) -> } /// Data to be signed via ring-vrf. -pub fn ticket_body_sign_data(ticket_body: &TicketData) -> VrfSignData { +pub fn ticket_body_sign_data(ticket_body: &TicketBody) -> VrfSignData { VrfSignData::from_iter( &SASSAFRAS_ENGINE_ID, &[b"ticket-body-transcript", ticket_body.encode().as_slice()], diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 1b8d785f38abd..ea54e157b318e 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -682,7 +682,7 @@ pub mod ring_vrf { #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct RingVrfSignature { /// VRF (pre)outputs. - outputs: VrfIosVec, + pub outputs: VrfIosVec, /// Pedersen VRF signature. signature: [u8; PEDERSEN_SIGNATURE_SERIALIZED_LEN], /// Ring proof. From 482985df4fdc1378db4d7292c22d8fd945f3f5d2 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 16 Jun 2023 09:22:15 +0200 Subject: [PATCH 12/14] Temporary removal of 'revealed-public' from ticket-body --- client/consensus/sassafras/src/authorship.rs | 3 +-- client/consensus/sassafras/src/tests.rs | 3 +-- frame/sassafras/src/benchmarking.rs | 6 +----- frame/sassafras/src/mock.rs | 3 +-- primitives/consensus/sassafras/src/ticket.rs | 2 -- primitives/io/src/lib.rs | 7 ++++++- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/client/consensus/sassafras/src/authorship.rs b/client/consensus/sassafras/src/authorship.rs index 75344b15c4f4c..c2a38e1e58e33 100644 --- a/client/consensus/sassafras/src/authorship.rs +++ b/client/consensus/sassafras/src/authorship.rs @@ -141,8 +141,7 @@ fn generate_epoch_tickets( let (erased_pair, erased_seed) = ed25519::Pair::generate(); let erased_public: [u8; 32] = *erased_pair.public().as_ref(); - let revealed_public = [0; 32]; - let ticket_body = TicketBody { attempt_idx, erased_public, revealed_public }; + let ticket_body = TicketBody { attempt_idx, erased_public }; debug!(target: LOG_TARGET, ">>> Creating ring proof for attempt {}", attempt_idx); let mut sign_data = ticket_body_sign_data(&ticket_body); diff --git a/client/consensus/sassafras/src/tests.rs b/client/consensus/sassafras/src/tests.rs index 65363388e6405..e1e327a10d2b2 100644 --- a/client/consensus/sassafras/src/tests.rs +++ b/client/consensus/sassafras/src/tests.rs @@ -426,8 +426,7 @@ fn claim_primary_slots_works() { let alice_authority_idx = 0_u32; let ticket_id = 123; - let ticket_data = - TicketData { attempt_idx: 0, erased_public: [0; 32], revealed_public: [0; 32] }; + let ticket_data = TicketData { attempt_idx: 0, erased_public: [0; 32] }; let ticket_secret = TicketSecret { attempt_idx: 0, erased_secret: [0; 32] }; // Fail if we have authority key in our keystore but not ticket aux data diff --git a/frame/sassafras/src/benchmarking.rs b/frame/sassafras/src/benchmarking.rs index fdf3f742bfb57..f4788b06b46fd 100644 --- a/frame/sassafras/src/benchmarking.rs +++ b/frame/sassafras/src/benchmarking.rs @@ -33,11 +33,7 @@ fn make_dummy_ticket(attempt_idx: u32) -> TicketEnvelope { 0xa7, 0x3d, ]; let output = VrfOutput::decode(&mut output_enc).unwrap(); - let data = TicketData { - attempt_idx, - erased_public: Default::default(), - revealed_public: Default::default(), - }; + let data = TicketData { attempt_idx, erased_public: Default::default() }; TicketEnvelope { data, vrf_preout: output, ring_proof: () } } diff --git a/frame/sassafras/src/mock.rs b/frame/sassafras/src/mock.rs index 20922e0222b89..41fdf47907722 100644 --- a/frame/sassafras/src/mock.rs +++ b/frame/sassafras/src/mock.rs @@ -165,8 +165,7 @@ fn make_ticket_with_prover( // Ticket-id can be generated via vrf-preout. // We don't care that much about the value here. - let data = - TicketData { attempt_idx: attempt, erased_public: [0; 32], revealed_public: [0; 32] }; + let data = TicketData { attempt_idx: attempt, erased_public: [0; 32] }; let sign_data = sp_consensus_sassafras::ticket_body_sign_data(&data); let ring_signature = pair.as_ref().ring_vrf_sign(&sign_data, prover); diff --git a/primitives/consensus/sassafras/src/ticket.rs b/primitives/consensus/sassafras/src/ticket.rs index 716558f27e67a..33bb40f99b5ea 100644 --- a/primitives/consensus/sassafras/src/ticket.rs +++ b/primitives/consensus/sassafras/src/ticket.rs @@ -39,8 +39,6 @@ pub struct TicketBody { pub attempt_idx: u32, /// Ed25519 public key which gets erased when claiming the ticket. pub erased_public: [u8; 32], - /// Ed25519 public key which gets exposed when claiming the ticket. - pub revealed_public: [u8; 32], } /// Ticket ring vrf signature. diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 050effbb1a003..124cebf643453 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -1142,7 +1142,12 @@ pub trait Crypto { Ok(pubkey.serialize()) } - /// DAVXY + /// Generate a `bandersnatch` key pair for the given key type using an optional + /// `seed` and store it in the keystore. + /// + /// The `seed` needs to be a valid utf8. + /// + /// Returns the public key. fn bandersnatch_generate( &mut self, id: KeyTypeId, From f3691875b766b30175078eb74ebe6f9e0a7ba73d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 16 Jun 2023 10:10:52 +0200 Subject: [PATCH 13/14] Fix (broken) tests build --- client/consensus/sassafras/src/tests.rs | 14 +- frame/sassafras/src/mock.rs | 61 ++- frame/sassafras/src/tests.rs | 479 +++++++++++----------- primitives/consensus/sassafras/src/lib.rs | 2 +- test-utils/runtime/src/lib.rs | 6 +- 5 files changed, 287 insertions(+), 275 deletions(-) diff --git a/client/consensus/sassafras/src/tests.rs b/client/consensus/sassafras/src/tests.rs index e1e327a10d2b2..6756fef2f5660 100644 --- a/client/consensus/sassafras/src/tests.rs +++ b/client/consensus/sassafras/src/tests.rs @@ -185,9 +185,9 @@ fn create_test_config() -> SassafrasConfiguration { slot_duration: SLOT_DURATION, epoch_duration: EPOCH_DURATION, authorities: vec![ - (Keyring::Alice.public().into(), 1), - (Keyring::Bob.public().into(), 1), - (Keyring::Charlie.public().into(), 1), + Keyring::Alice.public().into(), + Keyring::Bob.public().into(), + Keyring::Charlie.public().into(), ], randomness: [0; 32], threshold_params: SassafrasEpochConfiguration { redundancy_factor: 1, attempts_number: 32 }, @@ -426,7 +426,7 @@ fn claim_primary_slots_works() { let alice_authority_idx = 0_u32; let ticket_id = 123; - let ticket_data = TicketData { attempt_idx: 0, erased_public: [0; 32] }; + let ticket_body = TicketBody { attempt_idx: 0, erased_public: [0; 32] }; let ticket_secret = TicketSecret { attempt_idx: 0, erased_secret: [0; 32] }; // Fail if we have authority key in our keystore but not ticket aux data @@ -435,7 +435,7 @@ fn claim_primary_slots_works() { let claim = authorship::claim_slot( 0.into(), &mut epoch, - Some((ticket_id, ticket_data.clone())), + Some((ticket_id, ticket_body.clone())), &keystore, ); @@ -452,7 +452,7 @@ fn claim_primary_slots_works() { let (pre_digest, auth_id) = authorship::claim_slot( 0.into(), &mut epoch, - Some((ticket_id, ticket_data.clone())), + Some((ticket_id, ticket_body.clone())), &keystore, ) .unwrap(); @@ -467,7 +467,7 @@ fn claim_primary_slots_works() { epoch.tickets_aux.insert(ticket_id, (alice_authority_idx + 1, ticket_secret)); let claim = - authorship::claim_slot(0.into(), &mut epoch, Some((ticket_id, ticket_data)), &keystore); + authorship::claim_slot(0.into(), &mut epoch, Some((ticket_id, ticket_body)), &keystore); assert!(claim.is_none()); assert!(epoch.tickets_aux.is_empty()); } diff --git a/frame/sassafras/src/mock.rs b/frame/sassafras/src/mock.rs index 41fdf47907722..e3164f4132a3a 100644 --- a/frame/sassafras/src/mock.rs +++ b/frame/sassafras/src/mock.rs @@ -22,8 +22,8 @@ use crate::{self as pallet_sassafras, SameAuthoritiesForever}; use frame_support::traits::{ConstU32, ConstU64, GenesisBuild, OnFinalize, OnInitialize}; use scale_codec::Encode; use sp_consensus_sassafras::{ - digests::PreDigest, AuthorityIndex, AuthorityPair, RingProver, RingVrfContext, - SassafrasEpochConfiguration, Slot, TicketData, TicketEnvelope, VrfSignature, + digests::PreDigest, AuthorityIndex, AuthorityPair, RingProver, SassafrasEpochConfiguration, + Slot, TicketBody, TicketEnvelope, VrfSignature, }; use sp_core::{ crypto::{Pair, VrfSecret}, @@ -159,55 +159,50 @@ fn make_ticket_with_prover( randomness = crate::NextRandomness::::get(); } + let body = TicketBody { attempt_idx: attempt, erased_public: [0; 32] }; + + let mut sign_data = sp_consensus_sassafras::ticket_body_sign_data(&body); + let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input(&randomness, attempt, epoch); - let vrf_preout = pair.as_ref().vrf_output(&vrf_input.into()); + sign_data.push_vrf_input(vrf_input).unwrap(); + + let ring_signature = pair.as_ref().ring_vrf_sign(&sign_data, &prover); // Ticket-id can be generated via vrf-preout. // We don't care that much about the value here. - let data = TicketData { attempt_idx: attempt, erased_public: [0; 32] }; - - let sign_data = sp_consensus_sassafras::ticket_body_sign_data(&data); - let ring_signature = pair.as_ref().ring_vrf_sign(&sign_data, prover); - - TicketEnvelope { data, vrf_preout, ring_signature } + TicketEnvelope { body, ring_signature } } -pub fn make_prover(_pair: &AuthorityPair, ring_ctx: &RingVrfContext) -> RingProver { - let authorities = Sassafras::authorities(); - let pks: Vec = - authorities.iter().map(|auth| *auth.as_ref()).collect(); +pub fn make_prover(pair: &AuthorityPair) -> RingProver { + let public = pair.public(); + let mut prover_idx = None; - // TODO davxy: search into pks for pair.public - let prover_idx = 0; + let ring_ctx = Sassafras::ring_context().unwrap(); + + let pks: Vec = Sassafras::authorities() + .iter() + .enumerate() + .map(|(idx, auth)| { + if public == *auth { + prover_idx = Some(idx); + } + *auth.as_ref() + }) + .collect(); println!("Make prover"); - let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let prover = ring_ctx.prover(&pks, prover_idx.unwrap()).unwrap(); println!("Done"); prover } -// pub fn make_ticket( -// slot: Slot, -// attempt: u32, -// pair: &AuthorityPair, -// ring_ctx: &RingVrfContext, -// ) -> TicketEnvelope { -// let prover = make_prover(pair, ring_ctx); -// make_ticket_with_prover(slot, attempt, pair, &prover) -// } - /// Construct at most `attempts` tickets envelopes for the given `slot`. /// TODO-SASS-P3: filter out invalid tickets according to test threshold. /// E.g. by passing an optional threshold -pub fn make_tickets( - slot: Slot, - attempts: u32, - pair: &AuthorityPair, - ring_ctx: &RingVrfContext, -) -> Vec { - let prover = make_prover(pair, ring_ctx); +pub fn make_tickets(slot: Slot, attempts: u32, pair: &AuthorityPair) -> Vec { + let prover = make_prover(pair); (0..attempts) .into_iter() .map(|attempt| make_ticket_with_prover(slot, attempt, pair, &prover)) diff --git a/frame/sassafras/src/tests.rs b/frame/sassafras/src/tests.rs index 7c39d75e23448..4e0bec18de3c2 100644 --- a/frame/sassafras/src/tests.rs +++ b/frame/sassafras/src/tests.rs @@ -352,9 +352,7 @@ fn submit_segments_works() { // We're going to generate 14 segments. let segments_count = 3; - println!("TEST BEGIN"); let ring_ctx = RingVrfContext::new_testing(); - println!("RING CTX BUILT"); ext.execute_with(|| { let start_slot = Slot::from(100); @@ -371,7 +369,7 @@ fn submit_segments_works() { RingContext::::set(Some(ring_ctx.clone())); // Populate the segments via the `submit_tickets` - let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair, &ring_ctx); + let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair); let segment_len = tickets.len() / segments_count as usize; for i in 0..segments_count as usize { let segment = @@ -394,233 +392,248 @@ fn submit_segments_works() { }) } -// #[test] -// fn segments_incremental_sortition_works() { -// let (pairs, mut ext) = new_test_ext_with_pairs(1); -// let pair = &pairs[0]; -// let segments_count = 14; - -// ext.execute_with(|| { -// let start_slot = Slot::from(100); -// let start_block = 1; -// let max_tickets: u32 = ::MaxTickets::get(); - -// initialize_block(start_block, start_slot, Default::default(), &pairs[0]); - -// // Manually populate the segments to fool the threshold check -// let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair); -// let segment_len = tickets.len() / segments_count as usize; - -// for i in 0..segments_count as usize { -// let segment: Vec = tickets[i * segment_len..(i + 1) * segment_len] -// .iter() -// .enumerate() -// .map(|(j, ticket)| { -// let ticket_id = (i * segment_len + j) as TicketId; -// TicketsData::::set(ticket_id, ticket.data.clone()); -// ticket_id -// }) -// .collect(); -// let segment = BoundedVec::truncate_from(segment); -// NextTicketsSegments::::insert(i as u32, segment); -// } -// let meta = TicketsMetadata { segments_count, tickets_count: [0, 0] }; -// TicketsMeta::::set(meta); - -// let epoch_duration: u64 = ::EpochDuration::get(); - -// // Proceed to half of the epoch (sortition should not have been started yet) -// let half_epoch_block = start_block + epoch_duration / 2; -// progress_to_block(half_epoch_block, pair); - -// // Check that next epoch tickets sortition is not started yet -// let meta = TicketsMeta::::get(); -// assert_eq!(meta.segments_count, segments_count); -// assert_eq!(meta.tickets_count, [0, 0]); - -// // Follow incremental sortition block by block - -// progress_to_block(half_epoch_block + 1, pair); -// let meta = TicketsMeta::::get(); -// assert_eq!(meta.segments_count, 12); -// assert_eq!(meta.tickets_count, [0, 0]); - -// progress_to_block(half_epoch_block + 2, pair); -// let meta = TicketsMeta::::get(); -// assert_eq!(meta.segments_count, 9); -// assert_eq!(meta.tickets_count, [0, 0]); - -// progress_to_block(half_epoch_block + 3, pair); -// let meta = TicketsMeta::::get(); -// assert_eq!(meta.segments_count, 6); -// assert_eq!(meta.tickets_count, [0, 0]); - -// progress_to_block(half_epoch_block + 4, pair); -// let meta = TicketsMeta::::get(); -// assert_eq!(meta.segments_count, 3); -// assert_eq!(meta.tickets_count, [0, 0]); - -// let header = finalize_block(half_epoch_block + 4); - -// // Sort should be finished. -// // Check that next epoch tickets count have the correct value (6). -// // Bigger values were discarded during sortition. -// let meta = TicketsMeta::::get(); -// assert_eq!(meta.segments_count, 0); -// assert_eq!(meta.tickets_count, [0, 6]); -// assert_eq!(header.digest.logs.len(), 1); - -// // The next block will be the first produced on the new epoch, -// // At this point the tickets are found already sorted and ready to be used. -// let slot = Sassafras::current_slot() + 1; -// let number = System::block_number() + 1; -// initialize_block(number, slot, header.hash(), pair); -// let header = finalize_block(number); -// // Epoch changes digest is also produced -// assert_eq!(header.digest.logs.len(), 2); -// }); -// } - -// #[test] -// fn submit_enact_claim_tickets() { -// let (pairs, mut ext) = new_test_ext_with_pairs(4); - -// ext.execute_with(|| { -// let start_slot = Slot::from(100); -// let start_block = 1; -// let max_tickets: u32 = ::MaxTickets::get(); -// let pair = &pairs[0]; - -// initialize_block(start_block, start_slot, Default::default(), pair); - -// // We don't want to trigger an epoch change in this test. -// let epoch_duration: u64 = ::EpochDuration::get(); -// assert!(epoch_duration > 2); -// progress_to_block(2, &pairs[0]).unwrap(); - -// // // Check state before tickets submission -// assert_eq!( -// TicketsMeta::::get(), -// TicketsMetadata { segments_count: 0, tickets_count: [0, 0] }, -// ); - -// // Submit authoring tickets in three different segments. -// let tickets = make_tickets(start_slot + 1, 3 * max_tickets, pair); -// let tickets0 = tickets[0..6].to_vec().try_into().unwrap(); -// Sassafras::submit_tickets(RuntimeOrigin::none(), tickets0).unwrap(); -// let tickets1 = tickets[6..12].to_vec().try_into().unwrap(); -// Sassafras::submit_tickets(RuntimeOrigin::none(), tickets1).unwrap(); -// let tickets2 = tickets[12..18].to_vec().try_into().unwrap(); -// Sassafras::submit_tickets(RuntimeOrigin::none(), tickets2).unwrap(); - -// // Check state after submit -// assert_eq!( -// TicketsMeta::::get(), -// TicketsMetadata { segments_count: 3, tickets_count: [0, 0] }, -// ); - -// // Progress up to the last epoch slot (do not enact epoch change) -// progress_to_block(epoch_duration, &pairs[0]).unwrap(); - -// // At this point next tickets should have been sorted -// // Check state after submit -// assert_eq!( -// TicketsMeta::::get(), -// TicketsMetadata { segments_count: 0, tickets_count: [0, 6] }, -// ); - -// // Compute and sort the tickets ids (aka tickets scores) -// let mut expected_ids: Vec<_> = tickets -// .iter() -// .map(|t| { -// let epoch_idx = Sassafras::epoch_index() + 1; -// let randomness = Sassafras::next_randomness(); -// let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input( -// &randomness, -// t.data.attempt_idx, -// epoch_idx, -// ); -// sp_consensus_sassafras::ticket_id(&vrf_input, &t.vrf_preout) -// }) -// .collect(); -// expected_ids.sort(); -// expected_ids.truncate(max_tickets as usize); - -// // Check if we can claim next epoch tickets in outside-in fashion. -// let slot = Sassafras::current_slot(); -// assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[1]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[3]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 3).unwrap(), expected_ids[5]); -// assert!(Sassafras::slot_ticket_id(slot + 4).is_none()); -// assert!(Sassafras::slot_ticket_id(slot + 7).is_none()); -// assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[4]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[2]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 10).unwrap(), expected_ids[0]); -// assert!(Sassafras::slot_ticket_id(slot + 11).is_none()); - -// // Enact epoch change by progressing one more block - -// progress_to_block(epoch_duration + 1, &pairs[0]).unwrap(); - -// let meta = TicketsMeta::::get(); -// assert_eq!(meta.segments_count, 0); -// assert_eq!(meta.tickets_count, [0, 6]); - -// let slot = Sassafras::current_slot(); -// assert_eq!(Sassafras::slot_ticket_id(slot).unwrap(), expected_ids[1]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[3]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[5]); -// assert!(Sassafras::slot_ticket_id(slot + 3).is_none()); -// assert!(Sassafras::slot_ticket_id(slot + 6).is_none()); -// assert_eq!(Sassafras::slot_ticket_id(slot + 7).unwrap(), expected_ids[4]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[2]); -// assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[0]); -// assert!(Sassafras::slot_ticket_id(slot + 10).is_none()); -// }); -// } - -// #[test] -// fn block_allowed_to_skip_epochs() { -// let (pairs, mut ext) = new_test_ext_with_pairs(4); - -// ext.execute_with(|| { -// let start_slot = Slot::from(100); -// let start_block = 1; -// let epoch_duration: u64 = ::EpochDuration::get(); - -// initialize_block(start_block, start_slot, Default::default(), &pairs[0]); - -// let tickets = make_tickets(start_slot + 1, 3, &pairs[0]); -// Sassafras::submit_tickets( -// RuntimeOrigin::none(), -// BoundedVec::truncate_from(tickets.clone()), -// ) -// .unwrap(); - -// // Force sortition of next tickets (enactment) by explicitly querying next epoch tickets. -// assert_eq!(TicketsMeta::::get().segments_count, 1); -// Sassafras::slot_ticket(start_slot + epoch_duration).unwrap(); -// assert_eq!(TicketsMeta::::get().segments_count, 0); - -// let next_random = NextRandomness::::get(); - -// // We want to trigger a skip epoch in this test. -// let offset = 3 * epoch_duration; -// go_to_block(start_block + offset, start_slot + offset, &pairs[0]); - -// // Post-initialization status - -// assert!(Initialized::::get().is_some()); -// assert_eq!(Sassafras::genesis_slot(), start_slot); -// assert_eq!(Sassafras::current_slot(), start_slot + offset); -// assert_eq!(Sassafras::epoch_index(), 3); -// assert_eq!(Sassafras::current_epoch_start(), start_slot + offset); -// assert_eq!(Sassafras::current_slot_index(), 0); - -// // Tickets were discarded -// let meta = TicketsMeta::::get(); -// assert_eq!(meta, TicketsMetadata::default()); -// // We used the last known next epoch randomness as a fallback -// assert_eq!(next_random, Sassafras::randomness()); -// }); -// } +#[test] +fn segments_incremental_sortition_works() { + let (pairs, mut ext) = new_test_ext_with_pairs(1); + let pair = &pairs[0]; + let segments_count = 14; + + let ring_ctx = RingVrfContext::new_testing(); + + ext.execute_with(|| { + let start_slot = Slot::from(100); + let start_block = 1; + let max_tickets: u32 = ::MaxTickets::get(); + + RingContext::::set(Some(ring_ctx.clone())); + + initialize_block(start_block, start_slot, Default::default(), &pairs[0]); + + // Manually populate the segments to fool the threshold check + let tickets = make_tickets(start_slot + 1, segments_count * max_tickets, pair); + let segment_len = tickets.len() / segments_count as usize; + + for i in 0..segments_count as usize { + let segment: Vec = tickets[i * segment_len..(i + 1) * segment_len] + .iter() + .enumerate() + .map(|(j, ticket)| { + let ticket_id = (i * segment_len + j) as TicketId; + TicketsData::::set(ticket_id, ticket.body.clone()); + ticket_id + }) + .collect(); + let segment = BoundedVec::truncate_from(segment); + NextTicketsSegments::::insert(i as u32, segment); + } + let meta = TicketsMetadata { segments_count, tickets_count: [0, 0] }; + TicketsMeta::::set(meta); + + let epoch_duration: u64 = ::EpochDuration::get(); + + // Proceed to half of the epoch (sortition should not have been started yet) + let half_epoch_block = start_block + epoch_duration / 2; + progress_to_block(half_epoch_block, pair); + + // Check that next epoch tickets sortition is not started yet + let meta = TicketsMeta::::get(); + assert_eq!(meta.segments_count, segments_count); + assert_eq!(meta.tickets_count, [0, 0]); + + // Follow incremental sortition block by block + + progress_to_block(half_epoch_block + 1, pair); + let meta = TicketsMeta::::get(); + assert_eq!(meta.segments_count, 12); + assert_eq!(meta.tickets_count, [0, 0]); + + progress_to_block(half_epoch_block + 2, pair); + let meta = TicketsMeta::::get(); + assert_eq!(meta.segments_count, 9); + assert_eq!(meta.tickets_count, [0, 0]); + + progress_to_block(half_epoch_block + 3, pair); + let meta = TicketsMeta::::get(); + assert_eq!(meta.segments_count, 6); + assert_eq!(meta.tickets_count, [0, 0]); + + progress_to_block(half_epoch_block + 4, pair); + let meta = TicketsMeta::::get(); + assert_eq!(meta.segments_count, 3); + assert_eq!(meta.tickets_count, [0, 0]); + + let header = finalize_block(half_epoch_block + 4); + + // Sort should be finished. + // Check that next epoch tickets count have the correct value (6). + // Bigger values were discarded during sortition. + let meta = TicketsMeta::::get(); + assert_eq!(meta.segments_count, 0); + assert_eq!(meta.tickets_count, [0, 6]); + assert_eq!(header.digest.logs.len(), 1); + + // The next block will be the first produced on the new epoch, + // At this point the tickets are found already sorted and ready to be used. + let slot = Sassafras::current_slot() + 1; + let number = System::block_number() + 1; + initialize_block(number, slot, header.hash(), pair); + let header = finalize_block(number); + // Epoch changes digest is also produced + assert_eq!(header.digest.logs.len(), 2); + }); +} + +#[test] +fn submit_enact_claim_tickets() { + use sp_core::crypto::VrfSecret; + + let (pairs, mut ext) = new_test_ext_with_pairs(4); + + let ring_ctx = RingVrfContext::new_testing(); + + ext.execute_with(|| { + let start_slot = Slot::from(100); + let start_block = 1; + let max_tickets: u32 = ::MaxTickets::get(); + let pair = &pairs[0]; + + RingContext::::set(Some(ring_ctx.clone())); + + initialize_block(start_block, start_slot, Default::default(), pair); + + // We don't want to trigger an epoch change in this test. + let epoch_duration: u64 = ::EpochDuration::get(); + assert!(epoch_duration > 2); + progress_to_block(2, &pairs[0]).unwrap(); + + // // Check state before tickets submission + assert_eq!( + TicketsMeta::::get(), + TicketsMetadata { segments_count: 0, tickets_count: [0, 0] }, + ); + + // Submit authoring tickets in three different segments. + let tickets = make_tickets(start_slot + 1, 3 * max_tickets, pair); + let tickets0 = tickets[0..6].to_vec().try_into().unwrap(); + Sassafras::submit_tickets(RuntimeOrigin::none(), tickets0).unwrap(); + let tickets1 = tickets[6..12].to_vec().try_into().unwrap(); + Sassafras::submit_tickets(RuntimeOrigin::none(), tickets1).unwrap(); + let tickets2 = tickets[12..18].to_vec().try_into().unwrap(); + Sassafras::submit_tickets(RuntimeOrigin::none(), tickets2).unwrap(); + + // Check state after submit + assert_eq!( + TicketsMeta::::get(), + TicketsMetadata { segments_count: 3, tickets_count: [0, 0] }, + ); + + // Progress up to the last epoch slot (do not enact epoch change) + progress_to_block(epoch_duration, &pairs[0]).unwrap(); + + // At this point next tickets should have been sorted + // Check state after submit + assert_eq!( + TicketsMeta::::get(), + TicketsMetadata { segments_count: 0, tickets_count: [0, 6] }, + ); + + // Compute and sort the tickets ids (aka tickets scores) + let mut expected_ids: Vec<_> = tickets + .iter() + .map(|ticket| { + let epoch_idx = Sassafras::epoch_index() + 1; + let randomness = Sassafras::next_randomness(); + let vrf_input = sp_consensus_sassafras::ticket_id_vrf_input( + &randomness, + ticket.body.attempt_idx, + epoch_idx, + ); + let vrf_output = pair.as_ref().vrf_output(&vrf_input); + sp_consensus_sassafras::ticket_id(&vrf_input, &vrf_output) + }) + .collect(); + expected_ids.sort(); + expected_ids.truncate(max_tickets as usize); + + // Check if we can claim next epoch tickets in outside-in fashion. + let slot = Sassafras::current_slot(); + assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[1]); + assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[3]); + assert_eq!(Sassafras::slot_ticket_id(slot + 3).unwrap(), expected_ids[5]); + assert!(Sassafras::slot_ticket_id(slot + 4).is_none()); + assert!(Sassafras::slot_ticket_id(slot + 7).is_none()); + assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[4]); + assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[2]); + assert_eq!(Sassafras::slot_ticket_id(slot + 10).unwrap(), expected_ids[0]); + assert!(Sassafras::slot_ticket_id(slot + 11).is_none()); + + // Enact epoch change by progressing one more block + + progress_to_block(epoch_duration + 1, &pairs[0]).unwrap(); + + let meta = TicketsMeta::::get(); + assert_eq!(meta.segments_count, 0); + assert_eq!(meta.tickets_count, [0, 6]); + + let slot = Sassafras::current_slot(); + assert_eq!(Sassafras::slot_ticket_id(slot).unwrap(), expected_ids[1]); + assert_eq!(Sassafras::slot_ticket_id(slot + 1).unwrap(), expected_ids[3]); + assert_eq!(Sassafras::slot_ticket_id(slot + 2).unwrap(), expected_ids[5]); + assert!(Sassafras::slot_ticket_id(slot + 3).is_none()); + assert!(Sassafras::slot_ticket_id(slot + 6).is_none()); + assert_eq!(Sassafras::slot_ticket_id(slot + 7).unwrap(), expected_ids[4]); + assert_eq!(Sassafras::slot_ticket_id(slot + 8).unwrap(), expected_ids[2]); + assert_eq!(Sassafras::slot_ticket_id(slot + 9).unwrap(), expected_ids[0]); + assert!(Sassafras::slot_ticket_id(slot + 10).is_none()); + }); +} + +#[test] +fn block_allowed_to_skip_epochs() { + let (pairs, mut ext) = new_test_ext_with_pairs(4); + + let ring_ctx = RingVrfContext::new_testing(); + + ext.execute_with(|| { + let start_slot = Slot::from(100); + let start_block = 1; + let epoch_duration: u64 = ::EpochDuration::get(); + + RingContext::::set(Some(ring_ctx.clone())); + + initialize_block(start_block, start_slot, Default::default(), &pairs[0]); + + let tickets = make_tickets(start_slot + 1, 3, &pairs[0]); + Sassafras::submit_tickets( + RuntimeOrigin::none(), + BoundedVec::truncate_from(tickets.clone()), + ) + .unwrap(); + + // Force sortition of next tickets (enactment) by explicitly querying next epoch tickets. + assert_eq!(TicketsMeta::::get().segments_count, 1); + Sassafras::slot_ticket(start_slot + epoch_duration).unwrap(); + assert_eq!(TicketsMeta::::get().segments_count, 0); + + let next_random = NextRandomness::::get(); + + // We want to trigger a skip epoch in this test. + let offset = 3 * epoch_duration; + go_to_block(start_block + offset, start_slot + offset, &pairs[0]); + + // Post-initialization status + + assert!(Initialized::::get().is_some()); + assert_eq!(Sassafras::genesis_slot(), start_slot); + assert_eq!(Sassafras::current_slot(), start_slot + offset); + assert_eq!(Sassafras::epoch_index(), 3); + assert_eq!(Sassafras::current_epoch_start(), start_slot + offset); + assert_eq!(Sassafras::current_slot_index(), 0); + + // Tickets were discarded + let meta = TicketsMeta::::get(); + assert_eq!(meta, TicketsMetadata::default()); + // We used the last known next epoch randomness as a fallback + assert_eq!(next_random, Sassafras::randomness()); + }); +} diff --git a/primitives/consensus/sassafras/src/lib.rs b/primitives/consensus/sassafras/src/lib.rs index 5b9e52ab28531..7c23c5de22c26 100644 --- a/primitives/consensus/sassafras/src/lib.rs +++ b/primitives/consensus/sassafras/src/lib.rs @@ -18,7 +18,7 @@ //! Primitives for Sassafras //! TODO-SASS-P2 : write proper docs -// TODO DAVXY enable warnings +// TODO davxy enable warnings // #![deny(warnings)] // #![forbid(unsafe_code, missing_docs, unused_variables, unused_imports)] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index f588c9a601748..00421d26f2478 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -683,6 +683,10 @@ impl_runtime_apis! { } impl sp_consensus_sassafras::SassafrasApi for Runtime { + fn ring_context() -> Option { + Sassafras::ring_context() + } + fn submit_tickets_unsigned_extrinsic( tickets: Vec ) -> bool { @@ -703,7 +707,7 @@ impl_runtime_apis! { fn slot_ticket( slot: sp_consensus_sassafras::Slot - ) -> Option<(sp_consensus_sassafras::TicketId, sp_consensus_sassafras::TicketData)> { + ) -> Option<(sp_consensus_sassafras::TicketId, sp_consensus_sassafras::TicketBody)> { Sassafras::slot_ticket(slot) } From dd0d554727fa5b1a294f19be54ec3fd83a7ec3d0 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 16 Jun 2023 10:27:31 +0200 Subject: [PATCH 14/14] Adjust patch reference --- Cargo.lock | 7 +++++++ Cargo.toml | 18 +++++++++--------- frame/sassafras/src/lib.rs | 1 - 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a47e46321087b..117efac5ad7ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,7 @@ dependencies = [ [[package]] name = "ark-secret-scalar" version = "0.0.2" +source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" dependencies = [ "ark-ec", "ark-ff", @@ -528,6 +529,7 @@ dependencies = [ [[package]] name = "ark-transcript" version = "0.0.2" +source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" dependencies = [ "ark-ff", "ark-serialize", @@ -793,6 +795,7 @@ dependencies = [ [[package]] name = "bandersnatch_vrfs" version = "0.0.1" +source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1441,6 +1444,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" +source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" dependencies = [ "ark-ec", "ark-ff", @@ -2193,6 +2197,7 @@ checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" [[package]] name = "dleq_vrf" version = "0.0.2" +source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" dependencies = [ "ark-ec", "ark-ff", @@ -2565,6 +2570,7 @@ dependencies = [ [[package]] name = "fflonk" version = "0.1.0" +source = "git+https://github.com/davxy/fflonk?branch=working-fork#a2664567b88d96e1dc2f82f8799b2ca60171c81d" dependencies = [ "ark-ec", "ark-ff", @@ -8791,6 +8797,7 @@ dependencies = [ [[package]] name = "ring" version = "0.1.0" +source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" dependencies = [ "ark-ec", "ark-ff", diff --git a/Cargo.toml b/Cargo.toml index 5ace3d796bb81..eef655bf2b50c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,19 +344,19 @@ lto = "fat" codegen-units = 1 [patch."https://github.com/w3f/ring-vrf"] -# bandersnatch_vrfs = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } -bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } +bandersnatch_vrfs = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } +# bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } [patch."https://github.com/w3f/fflonk"] -# fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } -fflonk = { path = "/mnt/ssd/develop/w3f/fflonk" } +fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } +# fflonk = { path = "/mnt/ssd/develop/w3f/fflonk" } [patch."https://github.com/w3f/ring-proof"] -# common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } -# ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } -common = { path = "/mnt/ssd/develop/w3f/ring-proof/common" } -ring = { path = "/mnt/ssd/develop/w3f/ring-proof/ring" } +common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +# common = { path = "/mnt/ssd/develop/w3f/ring-proof/common" } +# ring = { path = "/mnt/ssd/develop/w3f/ring-proof/ring" } [patch.crates-io] #parity-scale-codec = { git = "https://github.com/koute/parity-scale-codec", branch = "master_fix_stack_overflow" } -codec = { package = "parity-scale-codec", git = "https://github.com/koute/parity-scale-codec", branch = "master_fix_stack_overflow" } \ No newline at end of file +codec = { package = "parity-scale-codec", git = "https://github.com/koute/parity-scale-codec", branch = "master_fix_stack_overflow" } diff --git a/frame/sassafras/src/lib.rs b/frame/sassafras/src/lib.rs index 20a4782120835..e04fb7a271238 100644 --- a/frame/sassafras/src/lib.rs +++ b/frame/sassafras/src/lib.rs @@ -366,7 +366,6 @@ pub mod pallet { }; log::debug!(target: LOG_TARGET, "... Loaded"); - // TODO Probably is better to cache the epoch prover in the storage log::debug!(target: LOG_TARGET, "Building prover"); let pks: Vec<_> = Self::authorities().iter().map(|auth| *auth.as_ref()).collect(); let verifier = ring_ctx.verifier(pks.as_slice()).unwrap();