Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/sortition-sample-scopes.changed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
From Epoch 4.1, sortition draws the winning block-commit and the null miner's challenge from separate, domain-separated digests of the sortition hash and VRF seed, instead of reusing one digest for both.
7 changes: 7 additions & 0 deletions stacks-common/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,13 @@ impl StacksEpochId {
self >= &StacksEpochId::Epoch41
}

/// Whether sortition samples the winning block-commit and the null miner's challenge from
/// separate, domain-separated digests of the sortition hash and VRF seed, rather than both
/// from the same digest.
pub fn uses_sortition_scopes(&self) -> bool {
self >= &StacksEpochId::Epoch41
}

pub fn supports_call_with_constant(&self) -> bool {
self >= &StacksEpochId::Epoch34
}
Expand Down
9 changes: 5 additions & 4 deletions stackslib/src/burnchains/tests/burnchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::chainstate::burn::operations::{
BlockstackOperationType, LeaderBlockCommitOp, LeaderKeyRegisterOp,
};
use crate::chainstate::burn::{
BlockSnapshot, ConsensusHash, ConsensusHashExtensions, OpsHash, SortitionHash,
BlockSnapshot, ConsensusHash, ConsensusHashExtensions, OpsHash, SampleScope, SortitionHash,
};
use crate::chainstate::stacks::StacksPublicKey;

Expand Down Expand Up @@ -627,9 +627,10 @@ fn test_process_block_ops() {
};

if next_sortition {
block_124_snapshot.sortition_hash = block_124_snapshot
.sortition_hash
.mix_VRF_seed(&block_124_winners[scenario_idx].new_seed);
block_124_snapshot.sortition_hash = block_124_snapshot.sortition_hash.mix_VRF_seed(
SampleScope::Unscoped,
&block_124_winners[scenario_idx].new_seed,
);
}

let block124 = BurnchainBlock::Bitcoin(BitcoinBlock::new(
Expand Down
31 changes: 29 additions & 2 deletions stackslib/src/chainstate/burn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ pub struct BlockSnapshot {
pub miner_pk_hash: Option<Hash160>,
}

/// Domain separator for a sample taken from a sortition hash and VRF seed, so that samples
/// taken for different purposes in the same sortition are independent of each other.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SampleScope {
/// No domain separation. Used before Epoch 4.1, where the winning block-commit and the
/// null miner's challenge were drawn from the same digest.
Unscoped,
/// Sampling the winning block-commit (Epoch 4.1 and later).
Winner,
/// Sampling the null miner's challenge (Epoch 4.1 and later).
NullMiner,
}

impl SampleScope {
/// The bytes hashed ahead of the sortition hash and VRF seed for this scope.
pub const fn tag(self) -> &'static [u8] {
match self {
Self::Unscoped => b"",
Self::Winner => b"SORTITION",
Self::NullMiner => b"NULL_MINER",
}
}
}

impl SortitionHash {
/// Calculate a new sortition hash from the given burn header hash
pub fn initial() -> SortitionHash {
Expand All @@ -128,9 +152,12 @@ impl SortitionHash {
SortitionHash(ret)
}

/// Mix in a new VRF seed to make a new sortition hash.
pub fn mix_VRF_seed(&self, VRF_seed: &VRFSeed) -> SortitionHash {
/// Mix in a new VRF seed in the given scope to make a new sortition hash. The scope's tag
/// is hashed first, so that each scope yields an independent digest.
/// `SampleScope::Unscoped` has an empty tag, so it yields HASH(self ++ VRF_seed).
pub fn mix_VRF_seed(&self, scope: SampleScope, VRF_seed: &VRFSeed) -> SortitionHash {
let mut sha2 = Sha256::new();
sha2.update(scope.tag());
sha2.update(self.as_bytes());
sha2.update(VRF_seed.as_bytes());
let mut ret = [0u8; 32];
Expand Down
Loading
Loading