Skip to content

Commit

Permalink
VRF: add nonce to message
Browse files Browse the repository at this point in the history
The nonce is used to disambiguate VRF values across non-competing branches in the blockchain.
  • Loading branch information
styppo authored and jsdanielh committed Oct 8, 2024
1 parent f245d01 commit 58d3464
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 34 deletions.
11 changes: 6 additions & 5 deletions blockchain/src/block_production/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ impl BlockProducer {
// leader.
prev_seed
} else {
prev_seed.sign_next_with_rng(&self.signing_key, rng)
prev_seed.sign_next_with_rng(&self.signing_key, block_number, rng)
};

// Create the inherents from the equivocation proofs or skip block info.
Expand Down Expand Up @@ -303,10 +303,11 @@ impl BlockProducer {

// Calculate the seed for this block by signing the previous block seed with the validator
// key.
let seed = blockchain
.head()
.seed()
.sign_next_with_rng(&self.signing_key, rng);
let seed =
blockchain
.head()
.seed()
.sign_next_with_rng(&self.signing_key, block_number, rng);

// If this is an election block, calculate the validator set for the next epoch.
let validators = match Policy::is_election_block_at(block_number) {
Expand Down
6 changes: 5 additions & 1 deletion pow-migration/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ pub async fn get_pos_genesis(
let mut parent_hash_bytes = [0u8; 32];
parent_hash_bytes.copy_from_slice(parent_hash.as_slice());
let mut rng = StdRng::from_seed(parent_hash_bytes);
let vrf_seed = VrfSeed::default().sign_next_with_rng(&KeyPair::generate(&mut rng), &mut rng);
let vrf_seed = VrfSeed::default().sign_next_with_rng(
&KeyPair::generate(&mut rng),
final_block.number,
&mut rng,
);

log::info!("Getting PoW account state");

Expand Down
2 changes: 1 addition & 1 deletion primitives/block/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ impl Block {
// Verify VRF seed.
if !self.is_skip() {
self.seed()
.verify(prev_seed, signing_key)
.verify(prev_seed, signing_key, self.block_number())
.map_err(|_| BlockError::InvalidSeed)?;
} else {
// XXX This is also checked in `verify_immediate_successor`.
Expand Down
12 changes: 8 additions & 4 deletions primitives/block/src/equivocation_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ mod test {
block_number: Policy::genesis_block_number(),
timestamp: 1,
parent_hash: "".hash(),
seed: VrfSeed::default().sign_next(&key),
seed: VrfSeed::default().sign_next(&key, Policy::genesis_block_number()),
extra_data: vec![1],
state_root: "".hash(),
body_root: "".hash(),
Expand All @@ -650,7 +650,9 @@ mod test {
block_number: Policy::genesis_block_number(),
timestamp: 2,
parent_hash: "1".hash(),
seed: VrfSeed::default().sign_next(&key).sign_next(&key),
seed: VrfSeed::default()
.sign_next(&key, Policy::genesis_block_number())
.sign_next(&key, Policy::genesis_block_number()),
extra_data: vec![2],
state_root: "1".hash(),
body_root: "1".hash(),
Expand Down Expand Up @@ -748,7 +750,7 @@ mod test {
parent_hash: "".hash(),
parent_election_hash: "".hash(),
interlink: Some(vec![]),
seed: VrfSeed::default().sign_next(&key),
seed: VrfSeed::default().sign_next(&key, Policy::genesis_block_number()),
extra_data: vec![1],
state_root: "".hash(),
body_root: "".hash(),
Expand All @@ -767,7 +769,9 @@ mod test {
parent_hash: "1".hash(),
parent_election_hash: "1".hash(),
interlink: Some(vec![Blake2bHash::default()]),
seed: VrfSeed::default().sign_next(&key).sign_next(&key),
seed: VrfSeed::default()
.sign_next(&key, Policy::genesis_block_number())
.sign_next(&key, Policy::genesis_block_number()),
extra_data: vec![2],
state_root: "1".hash(),
body_root: "1".hash(),
Expand Down
12 changes: 7 additions & 5 deletions test-utils/src/test_custom_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub fn next_micro_block(
let seed = config
.seed
.clone()
.unwrap_or_else(|| prev_seed.sign_next(signing_key));
.unwrap_or_else(|| prev_seed.sign_next(signing_key, block_number));

let mut transactions = config.transactions.clone();
transactions.sort_unstable();
Expand Down Expand Up @@ -313,10 +313,12 @@ pub fn next_macro_block_proposal(
}
});

let seed = config
.seed
.clone()
.unwrap_or_else(|| blockchain.head().seed().sign_next(signing_key));
let seed = config.seed.clone().unwrap_or_else(|| {
blockchain
.head()
.seed()
.sign_next(signing_key, block_number)
});

let validators = if Policy::is_election_block_at(blockchain.block_number() + 1) {
Some(blockchain.next_validators(&seed))
Expand Down
54 changes: 36 additions & 18 deletions vrf/src/vrf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,13 @@ pub struct VrfSeed {
impl VrfSeed {
const SIZE: usize = 96;

/// Verifies the current VRF Seed given the previous VRF Seed (which is part of the message)
/// and the signer's public key.
/// Verifies the current VRF Seed given the previous VRF Seed (which is part of the message),
/// the signer's public key and the nonce.
pub fn verify(
&self,
prev_seed: &VrfSeed,
public_key: &Ed25519PublicKey,
nonce: u32,
) -> Result<(), VrfError> {
// Deserialize signature.
let V = CompressedEdwardsY::from_slice(&self.signature[..32])
Expand All @@ -119,9 +120,10 @@ impl VrfSeed {
.decompress()
.ok_or(VrfError::InvalidSignature)?;

// Concatenate use case prefix and previous entropy to form message. Note that we use the
// entropy here and not the signature, that's because we need the message to be unique.
// Concatenate use case prefix, nonce, and previous entropy to form message. Note that we use
// the entropy here and not the signature, that's because we need the message to be unique.
let mut message = vec![VrfUseCase::Seed as u8];
message.extend(nonce.to_be_bytes());
message.extend_from_slice(prev_seed.entropy().as_slice());

// Follow the verification algorithm for VXEdDSA.
Expand Down Expand Up @@ -159,16 +161,17 @@ impl VrfSeed {
/// Produces the next VRF Seed given the current VRF Seed (which is part of the message) and a
/// key pair.
#[must_use]
pub fn sign_next(&self, keypair: &KeyPair) -> Self {
self.sign_next_with_rng(keypair, &mut rand::thread_rng())
pub fn sign_next(&self, keypair: &KeyPair, nonce: u32) -> Self {
self.sign_next_with_rng(keypair, nonce, &mut rand::thread_rng())
}

/// Produces the next VRF Seed given the current VRF Seed (which is part of the message) and a
/// key pair.
/// Produces the next VRF Seed given the current VRF Seed (which is part of the message), a
/// key pair and a nonce.
#[must_use]
pub fn sign_next_with_rng<R: RngCore + CryptoRng>(
&self,
keypair: &KeyPair,
nonce: u32,
rng: &mut R,
) -> Self {
// Get random bytes.
Expand All @@ -179,9 +182,10 @@ impl VrfSeed {
let a = keypair.private.to_scalar();
let A_bytes = keypair.public.as_bytes();

// Concatenate use case prefix and entropy to form message. Note that we use the entropy
// here and not the signature, that's because we need the message to be unique.
// Concatenate use case prefix, nonce, and entropy to form message. Note that we use the
// entropy here and not the signature, that's because we need the message to be unique.
let mut message = vec![VrfUseCase::Seed as u8];
message.extend(nonce.to_be_bytes());
message.extend_from_slice(self.entropy().as_slice());

// Follow the signing algorithm for VXEdDSA.
Expand Down Expand Up @@ -326,9 +330,9 @@ mod tests {
for _ in 0..1000 {
let key_pair = KeyPair::generate(&mut rng);

let next_seed = prev_seed.sign_next(&key_pair);
let next_seed = prev_seed.sign_next(&key_pair, 0);

assert!(next_seed.verify(&prev_seed, &key_pair.public).is_ok());
assert!(next_seed.verify(&prev_seed, &key_pair.public, 0).is_ok());

next_seed.entropy();

Expand All @@ -342,13 +346,13 @@ mod tests {
let key_pair = KeyPair::generate(&mut rng);
let prev_seed = VrfSeed::default();

let next_seed = prev_seed.sign_next(&key_pair);
let next_seed = prev_seed.sign_next(&key_pair, 0);

for _ in 0..1000 {
let fake_pk = KeyPair::generate(&mut rng).public;

assert_eq!(
next_seed.verify(&prev_seed, &fake_pk),
next_seed.verify(&prev_seed, &fake_pk, 0),
Err(VrfError::Forged)
);
}
Expand All @@ -360,14 +364,14 @@ mod tests {
let key_pair = KeyPair::generate(&mut rng);
let prev_seed = VrfSeed::default();

let next_seed = prev_seed.sign_next(&key_pair);
let next_seed = prev_seed.sign_next(&key_pair, 0);

for _ in 0..1000 {
let fake_key_pair = KeyPair::generate(&mut rng);
let fake_seed = VrfSeed::default().sign_next(&fake_key_pair);
let fake_seed = VrfSeed::default().sign_next(&fake_key_pair, 0);

assert_eq!(
next_seed.verify(&fake_seed, &key_pair.public),
next_seed.verify(&fake_seed, &key_pair.public, 0),
Err(VrfError::Forged)
);
}
Expand All @@ -384,7 +388,21 @@ mod tests {
rng.fill_bytes(&mut bytes);
let fake_seed = VrfSeed { signature: bytes };

assert!(fake_seed.verify(&prev_seed, &key_pair.public).is_err());
assert!(fake_seed.verify(&prev_seed, &key_pair.public, 0).is_err());
}
}

#[test]
fn wrong_nonce() {
let mut rng = test_rng(false);
let key_pair = KeyPair::generate(&mut rng);
let prev_seed = VrfSeed::default();

let next_seed = prev_seed.sign_next(&key_pair, 0);

assert_eq!(
next_seed.verify(&prev_seed, &key_pair.public, 1),
Err(VrfError::Forged)
);
}
}

0 comments on commit 58d3464

Please sign in to comment.