-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Introduce ark-vrf #7669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce ark-vrf #7669
Changes from 19 commits
64d59d9
1d2eed8
be67253
3cbdf75
fa5377a
7103b40
db08b53
7e060e4
adb012e
3c4036a
6156d40
a07c51a
df014b8
9826d28
479758a
1720113
4d24263
af82993
5c1cc41
5c8cdab
42503c6
555ae8d
4704379
df12918
7b9a15a
f0fd53e
1ef9407
ac11b52
a3a5da0
704c9b1
8cb5d3c
bbd4b1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -90,9 +90,6 @@ pub use pallet::*; | |
|
|
||
| const LOG_TARGET: &str = "sassafras::runtime"; | ||
|
|
||
| // Contextual string used by the VRF to generate per-block randomness. | ||
| const RANDOMNESS_VRF_CONTEXT: &[u8] = b"SassafrasOnChainRandomness"; | ||
|
|
||
| // Max length for segments holding unsorted tickets. | ||
| const SEGMENT_MAX_SIZE: u32 = 128; | ||
|
|
||
|
|
@@ -202,6 +199,12 @@ pub mod pallet { | |
| #[pallet::getter(fn randomness_accumulator)] | ||
| pub(crate) type RandomnessAccumulator<T> = StorageValue<_, Randomness, ValueQuery>; | ||
|
|
||
| /// Per slot randomness used to feed the randomness accumulator. | ||
| /// | ||
| /// The value is ephemeral and is cleared on block finalization. | ||
| #[pallet::storage] | ||
| pub(crate) type SlotRandomness<T> = StorageValue<_, Randomness>; | ||
|
|
||
| /// The configuration for the current epoch. | ||
| #[pallet::storage] | ||
| #[pallet::getter(fn config)] | ||
|
|
@@ -272,13 +275,7 @@ pub mod pallet { | |
|
|
||
| /// Ring verifier data for the current epoch. | ||
| #[pallet::storage] | ||
| pub type RingVerifierData<T: Config> = StorageValue<_, vrf::RingVerifierData>; | ||
|
|
||
| /// Slot claim VRF pre-output used to generate per-slot randomness. | ||
| /// | ||
| /// The value is ephemeral and is cleared on block finalization. | ||
| #[pallet::storage] | ||
| pub(crate) type ClaimTemporaryData<T> = StorageValue<_, vrf::VrfPreOutput>; | ||
| pub type RingVerifierData<T: Config> = StorageValue<_, vrf::RingVerifierKey>; | ||
|
|
||
| /// Genesis configuration for Sassafras protocol. | ||
| #[pallet::genesis_config] | ||
|
|
@@ -326,12 +323,8 @@ pub mod pallet { | |
| Self::post_genesis_initialize(claim.slot); | ||
| } | ||
|
|
||
| let randomness_pre_output = claim | ||
| .vrf_signature | ||
| .pre_outputs | ||
| .get(0) | ||
| .expect("Valid claim must have VRF signature; qed"); | ||
| ClaimTemporaryData::<T>::put(randomness_pre_output); | ||
| let randomness = claim.vrf_signature.pre_output.make_bytes(); | ||
| SlotRandomness::<T>::put(randomness); | ||
|
|
||
| let trigger_weight = T::EpochChangeTrigger::trigger::<T>(block_num); | ||
|
|
||
|
|
@@ -343,15 +336,8 @@ pub mod pallet { | |
| // to the accumulator. If we've determined that this block was the first in | ||
| // a new epoch, the changeover logic has already occurred at this point | ||
| // (i.e. `enact_epoch_change` has already been called). | ||
| let randomness_input = vrf::slot_claim_input( | ||
| &Self::randomness(), | ||
| CurrentSlot::<T>::get(), | ||
| EpochIndex::<T>::get(), | ||
| ); | ||
| let randomness_pre_output = ClaimTemporaryData::<T>::take() | ||
| let randomness = SlotRandomness::<T>::take() | ||
| .expect("Unconditionally populated in `on_initialize`; `on_finalize` is always called after; qed"); | ||
| let randomness = randomness_pre_output | ||
| .make_bytes::<RANDOMNESS_LENGTH>(RANDOMNESS_VRF_CONTEXT, &randomness_input); | ||
| Self::deposit_slot_randomness(&randomness); | ||
|
|
||
| // Check if we are in the epoch's second half. | ||
|
|
@@ -399,7 +385,9 @@ pub mod pallet { | |
| return Err("Tickets shall be submitted in the first epoch half".into()) | ||
| } | ||
|
|
||
| let Some(verifier) = RingVerifierData::<T>::get().map(|v| v.into()) else { | ||
| let Some(verifier) = | ||
| RingVerifierData::<T>::get().map(|vk| vrf::RingContext::verifier_no_context(vk)) | ||
| else { | ||
| warn!(target: LOG_TARGET, "Ring verifier key not initialized"); | ||
| return Err("Ring verifier key not initialized".into()) | ||
| }; | ||
|
|
@@ -424,15 +412,8 @@ pub mod pallet { | |
| for ticket in tickets { | ||
| debug!(target: LOG_TARGET, "Checking ring proof"); | ||
|
|
||
| let Some(ticket_id_pre_output) = ticket.signature.pre_outputs.get(0) else { | ||
| debug!(target: LOG_TARGET, "Missing ticket VRF pre-output from ring signature"); | ||
| continue | ||
| }; | ||
| let ticket_id_input = | ||
| vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx); | ||
|
|
||
| // Check threshold constraint | ||
| let ticket_id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_pre_output); | ||
| let ticket_id = vrf::make_ticket_id(&ticket.signature.pre_output); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So ticket id used to explicitly depends on the ticket_id_input but not anymore? why?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. preout = input * secret THis is exactly what we're doing in JAM and prescribed by the GP |
||
| if ticket_id >= ticket_threshold { | ||
| debug!(target: LOG_TARGET, "Ignoring ticket over threshold ({:032x} >= {:032x})", ticket_id, ticket_threshold); | ||
| continue | ||
|
|
@@ -445,6 +426,8 @@ pub mod pallet { | |
| } | ||
|
|
||
| // Check ring signature | ||
| let ticket_id_input = | ||
| vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx); | ||
| let sign_data = vrf::ticket_body_sign_data(&ticket.body, ticket_id_input); | ||
| if !ticket.signature.ring_vrf_verify(&sign_data, &verifier) { | ||
| debug!(target: LOG_TARGET, "Proof verification failure for ticket ({:032x})", ticket_id); | ||
|
|
@@ -585,9 +568,7 @@ impl<T: Config> Pallet<T> { | |
| let pks: Vec<_> = authorities.iter().map(|auth| *auth.as_ref()).collect(); | ||
|
|
||
| debug!(target: LOG_TARGET, "Building ring verifier (ring size: {})", pks.len()); | ||
| let verifier_data = ring_ctx | ||
| .verifier_data(&pks) | ||
| .expect("Failed to build ring verifier. This is a bug"); | ||
| let verifier_data = ring_ctx.verifier_key(&pks); | ||
|
|
||
| RingVerifierData::<T>::put(verifier_data); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -261,7 +261,7 @@ fn on_first_block_after_genesis() { | |
|
|
||
| // Post-initialization status | ||
|
|
||
| assert!(ClaimTemporaryData::<Test>::exists()); | ||
| assert!(SlotRandomness::<Test>::exists()); | ||
| common_assertions(); | ||
| println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); | ||
| assert_eq!( | ||
|
|
@@ -273,12 +273,11 @@ fn on_first_block_after_genesis() { | |
|
|
||
| // Post-finalization status | ||
|
|
||
| assert!(!ClaimTemporaryData::<Test>::exists()); | ||
| assert!(!SlotRandomness::<Test>::exists()); | ||
| common_assertions(); | ||
| println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); | ||
| assert_eq!( | ||
| Sassafras::randomness_accumulator(), | ||
| h2b("9f2b9fd19a772c34d437dcd8b84a927e73a5cb43d3d1cd00093223d60d2b4843"), | ||
| h2b("95a508cf10f877cf0457af3503a6cb3192763d5c15a7b9a58e40dc543efae889"), | ||
| ); | ||
|
|
||
| // Header data check | ||
|
|
@@ -332,23 +331,24 @@ fn on_normal_block() { | |
|
|
||
| // Post-initialization status | ||
|
|
||
| assert!(ClaimTemporaryData::<Test>::exists()); | ||
| assert!(SlotRandomness::<Test>::exists()); | ||
| common_assertions(); | ||
| println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); | ||
| assert_eq!( | ||
| Sassafras::randomness_accumulator(), | ||
| h2b("9f2b9fd19a772c34d437dcd8b84a927e73a5cb43d3d1cd00093223d60d2b4843"), | ||
| h2b("95a508cf10f877cf0457af3503a6cb3192763d5c15a7b9a58e40dc543efae889"), | ||
| ); | ||
|
|
||
| let header = finalize_block(end_block); | ||
|
|
||
| // Post-finalization status | ||
|
|
||
| assert!(!ClaimTemporaryData::<Test>::exists()); | ||
| assert!(!SlotRandomness::<Test>::exists()); | ||
| common_assertions(); | ||
| println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to keep this?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can remove it |
||
| assert_eq!( | ||
| Sassafras::randomness_accumulator(), | ||
| h2b("be9261adb9686dfd3f23f8a276b7acc7f4beb3137070beb64c282ac22d84cbf0"), | ||
| h2b("5465cb257ad20cd4b9400a9fc85af7b1e2e72b59debd8ca06580dfb76bfca394"), | ||
| ); | ||
|
|
||
| // Header data check | ||
|
|
@@ -389,34 +389,34 @@ fn produce_epoch_change_digest_no_config() { | |
|
|
||
| // Post-initialization status | ||
|
|
||
| assert!(ClaimTemporaryData::<Test>::exists()); | ||
| assert!(SlotRandomness::<Test>::exists()); | ||
| common_assertions(); | ||
| println!("[DEBUG] {}", b2h(Sassafras::next_randomness())); | ||
| assert_eq!( | ||
| Sassafras::next_randomness(), | ||
| h2b("d3a18b857af6ecc7b52f047107e684fff0058b5722d540a296d727e37eaa55b3"), | ||
| h2b("c4d374ed47b71e1c29e57143db23861916ff2d0c59ead4c51070d42ff4af2830"), | ||
| ); | ||
| println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); | ||
| assert_eq!( | ||
| Sassafras::randomness_accumulator(), | ||
| h2b("bf0f1228f4ff953c8c1bda2cceb668bf86ea05d7ae93e26d021c9690995d5279"), | ||
| h2b("c6d84d1f389853959c39271a38010f2f27abe6ff56cc419cf9e89eafcae1ab5e"), | ||
| ); | ||
|
|
||
| let header = finalize_block(end_block); | ||
|
|
||
| // Post-finalization status | ||
|
|
||
| assert!(!ClaimTemporaryData::<Test>::exists()); | ||
| assert!(!SlotRandomness::<Test>::exists()); | ||
| common_assertions(); | ||
| println!("[DEBUG] {}", b2h(Sassafras::next_randomness())); | ||
| assert_eq!( | ||
| Sassafras::next_randomness(), | ||
| h2b("d3a18b857af6ecc7b52f047107e684fff0058b5722d540a296d727e37eaa55b3"), | ||
| h2b("c4d374ed47b71e1c29e57143db23861916ff2d0c59ead4c51070d42ff4af2830"), | ||
| ); | ||
| println!("[DEBUG] {}", b2h(Sassafras::randomness_accumulator())); | ||
| assert_eq!( | ||
| Sassafras::randomness_accumulator(), | ||
| h2b("8a1ceb346036c386d021264b10912c8b656799668004c4a487222462b394cd89"), | ||
| h2b("6ca02b90e14ef11b3855069794da7e9d4007526b0588c426c3e3533b0b6ade7a"), | ||
| ); | ||
|
|
||
| // Header data check | ||
|
|
@@ -670,7 +670,7 @@ fn block_allowed_to_skip_epochs() { | |
|
|
||
| // Post-initialization status | ||
|
|
||
| assert!(ClaimTemporaryData::<Test>::exists()); | ||
| assert!(SlotRandomness::<Test>::exists()); | ||
| assert_eq!(Sassafras::genesis_slot(), start_slot); | ||
| assert_eq!(Sassafras::current_slot(), start_slot + offset); | ||
| assert_eq!(Sassafras::epoch_index(), 4); | ||
|
|
@@ -829,9 +829,9 @@ fn submit_tickets_with_ring_proof_check_works() { | |
| // Check state after submission | ||
| assert_eq!( | ||
| TicketsMeta::<Test>::get(), | ||
| TicketsMetadata { unsorted_tickets_count: 16, tickets_count: [0, 0] }, | ||
| TicketsMetadata { unsorted_tickets_count: 13, tickets_count: [0, 0] }, | ||
| ); | ||
| assert_eq!(UnsortedSegments::<Test>::get(0).len(), 16); | ||
| assert_eq!(UnsortedSegments::<Test>::get(0).len(), 13); | ||
| assert_eq!(UnsortedSegments::<Test>::get(1).len(), 0); | ||
|
|
||
| finalize_block(start_block); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,88 +24,48 @@ use codec::Encode; | |
| use sp_consensus_slots::Slot; | ||
|
|
||
| pub use sp_core::bandersnatch::{ | ||
| ring_vrf::{RingProver, RingVerifier, RingVerifierData, RingVrfSignature}, | ||
| ring_vrf::{RingProver, RingVerifier, RingVerifierKey, RingVrfSignature}, | ||
| vrf::{VrfInput, VrfPreOutput, VrfSignData, VrfSignature}, | ||
| }; | ||
|
|
||
| /// Ring VRF domain size for Sassafras consensus. | ||
| pub const RING_VRF_DOMAIN_SIZE: u32 = 2048; | ||
| /// Ring size (aka authorities count) for Sassafras consensus. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how come ring size was 2048 before?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The size is arbitrary at this point. But I can make it a generic maybe |
||
| pub const RING_SIZE: usize = 1024; | ||
|
|
||
| /// Bandersnatch VRF [`RingContext`] specialization for Sassafras using [`RING_VRF_DOMAIN_SIZE`]. | ||
| pub type RingContext = sp_core::bandersnatch::ring_vrf::RingContext<RING_VRF_DOMAIN_SIZE>; | ||
| /// Bandersnatch VRF [`RingContext`] specialization for Sassafras using [`RING_SIZE`]. | ||
| pub type RingContext = sp_core::bandersnatch::ring_vrf::RingContext<RING_SIZE>; | ||
|
|
||
| fn vrf_input_from_data( | ||
| domain: &[u8], | ||
| data: impl IntoIterator<Item = impl AsRef<[u8]>>, | ||
| ) -> VrfInput { | ||
| let buf = data.into_iter().fold(Vec::new(), |mut buf, item| { | ||
| let bytes = item.as_ref(); | ||
| buf.extend_from_slice(bytes); | ||
| let len = u8::try_from(bytes.len()).expect("private function with well known inputs; qed"); | ||
| buf.push(len); | ||
| buf | ||
| }); | ||
| VrfInput::new(domain, buf) | ||
| } | ||
|
|
||
| /// VRF input to claim slot ownership during block production. | ||
| /// TODO | ||
davxy marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| pub fn slot_claim_input(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfInput { | ||
| vrf_input_from_data( | ||
| b"sassafras-claim-v1.0", | ||
| [randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()], | ||
| ) | ||
| let v = [b"sassafras-ticket", randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. b"sassafras-ticket" should really be defined as a constant rather than hidden here in the code. On dropping the length, it should be OK if the it the input is constant length but maybe specify that somewhere in the comment with specifying the pre-set length of each components. |
||
| .concat(); | ||
| VrfInput::new(&v[..]) | ||
| } | ||
|
|
||
| /// Signing-data to claim slot ownership during block production. | ||
| pub fn slot_claim_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfSignData { | ||
| let input = slot_claim_input(randomness, slot, epoch); | ||
| VrfSignData::new_unchecked( | ||
| b"sassafras-slot-claim-transcript-v1.0", | ||
| Option::<&[u8]>::None, | ||
| Some(input), | ||
| ) | ||
| let v = [b"sassafras-ticket", randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. b"sassafras-ticket" should be pre defined constant. |
||
| .concat(); | ||
| VrfSignData::new(&v[..], &[]) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are getting rid of including the length in the transcript because we are only accepting a constant length, maybe vrfSignData should police the length?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alistair's comment on this: "The question then is how does the VRF input appear in Hashes. If we are just heading the VRF inputs alone to a curve, then that is fine. If we are ever appending anything after the VRF input in a hash, whether the hash to curve or the Fiat-Shamir hash, then we have a collision issue. If the appended things are always fixed length it might be fine. Or the nonce generation hash. A collision there for some massage /input that can be generated without knowing the secret key would be fatal." (the last one is refering to the nonce in Schnorr signature I believe.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understood the question. This sequence of bytes is used to construct |
||
| } | ||
|
|
||
| /// VRF input to generate the ticket id. | ||
| pub fn ticket_id_input(randomness: &Randomness, attempt: u32, epoch: u64) -> VrfInput { | ||
| vrf_input_from_data( | ||
| b"sassafras-ticket-v1.0", | ||
| [randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()], | ||
| ) | ||
| } | ||
|
|
||
| /// VRF input to generate the revealed key. | ||
| pub fn revealed_key_input(randomness: &Randomness, attempt: u32, epoch: u64) -> VrfInput { | ||
| vrf_input_from_data( | ||
| b"sassafras-revealed-v1.0", | ||
| [randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()], | ||
| ) | ||
| let v = | ||
| [b"sassafras-ticket", randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()] | ||
| .concat(); | ||
| VrfInput::new(&v[..]) | ||
| } | ||
|
|
||
| /// Data to be signed via ring-vrf. | ||
| pub fn ticket_body_sign_data(ticket_body: &TicketBody, ticket_id_input: VrfInput) -> VrfSignData { | ||
| VrfSignData::new_unchecked( | ||
| b"sassafras-ticket-body-transcript-v1.0", | ||
| Some(ticket_body.encode().as_slice()), | ||
| Some(ticket_id_input), | ||
| ) | ||
| VrfSignData { vrf_input: ticket_id_input, aux_data: ticket_body.encode() } | ||
| } | ||
|
|
||
| /// Make ticket-id from the given VRF input and pre-output. | ||
| /// Make ticket-id from the given VRF pre-output. | ||
| /// | ||
| /// Input should have been obtained via [`ticket_id_input`]. | ||
| /// Pre-output should have been obtained from the input directly using the vrf | ||
| /// secret key or from the vrf signature pre-outputs. | ||
| pub fn make_ticket_id(input: &VrfInput, pre_output: &VrfPreOutput) -> TicketId { | ||
| let bytes = pre_output.make_bytes::<16>(b"ticket-id", input); | ||
| /// secret key or from the vrf signature pre-output. | ||
| pub fn make_ticket_id(preout: &VrfPreOutput) -> TicketId { | ||
| let bytes: [u8; 16] = preout.make_bytes()[..16].try_into().unwrap(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why aren't we depending on input anymore?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because IETF vrf defines preout = input*secret. |
||
| u128::from_le_bytes(bytes) | ||
| } | ||
|
|
||
| /// Make revealed key seed from a given VRF input and pre-output. | ||
| /// | ||
| /// Input should have been obtained via [`revealed_key_input`]. | ||
| /// Pre-output should have been obtained from the input directly using the vrf | ||
| /// secret key or from the vrf signature pre-outputs. | ||
| pub fn make_revealed_key_seed(input: &VrfInput, pre_output: &VrfPreOutput) -> [u8; 32] { | ||
| pre_output.make_bytes::<32>(b"revealed-seed", input) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the motivation behind removing the context for vrf randomness?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Context strings are supplied by the dedicated utility functions to construct sassafras VrfInputs (e.g. the one to construct ticket)