Skip to content

Commit b9a6895

Browse files
committed
replace expects/unwraps with fallible vrf routines
1 parent 8c9d728 commit b9a6895

File tree

8 files changed

+80
-22
lines changed

8 files changed

+80
-22
lines changed

stacks-common/src/util/vrf.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ impl VRFPublicKey {
181181
pub enum Error {
182182
InvalidPublicKey,
183183
InvalidDataError,
184+
InvalidHashPoints,
184185
OSRNGError(rand::Error),
185186
}
186187

@@ -189,6 +190,7 @@ impl fmt::Display for Error {
189190
match *self {
190191
Error::InvalidPublicKey => write!(f, "Invalid public key"),
191192
Error::InvalidDataError => write!(f, "No data could be found"),
193+
Error::InvalidHashPoints => write!(f, "VRF hash points did not yield a valid scalar"),
192194
Error::OSRNGError(ref e) => fmt::Display::fmt(e, f),
193195
}
194196
}
@@ -199,6 +201,7 @@ impl error::Error for Error {
199201
match *self {
200202
Error::InvalidPublicKey => None,
201203
Error::InvalidDataError => None,
204+
Error::InvalidHashPoints => None,
202205
Error::OSRNGError(ref e) => Some(e),
203206
}
204207
}
@@ -474,17 +477,17 @@ impl VRF {
474477

475478
/// Convert a 16-byte string into a scalar.
476479
/// The upper 16 bytes in the resulting scalar MUST BE 0's
477-
fn ed25519_scalar_from_hash128(hash128: &[u8; 16]) -> ed25519_Scalar {
480+
fn ed25519_scalar_from_hash128(hash128: &[u8; 16]) -> Option<ed25519_Scalar> {
478481
let mut scalar_buf = [0u8; 32];
479482
scalar_buf[0..16].copy_from_slice(hash128);
480483

481-
ed25519_Scalar::from_canonical_bytes(scalar_buf).expect("Invalid scalar")
484+
ed25519_Scalar::from_canonical_bytes(scalar_buf).into()
482485
}
483486

484487
/// ECVRF proof routine
485488
/// https://tools.ietf.org/id/draft-irtf-cfrg-vrf-02.html#rfc.section.5.1
486489
#[allow(clippy::op_ref)]
487-
pub fn prove(secret: &VRFPrivateKey, alpha: &[u8]) -> VRFProof {
490+
pub fn prove(secret: &VRFPrivateKey, alpha: &[u8]) -> Option<VRFProof> {
488491
let (Y_point, x_scalar, trunc_hash) = VRF::expand_privkey(secret);
489492
let H_point = VRF::hash_to_curve(&Y_point, alpha);
490493

@@ -495,14 +498,15 @@ impl VRF {
495498
let kH_point = &k_scalar * &H_point;
496499

497500
let c_hashbuf = VRF::hash_points(&H_point, &Gamma_point, &kB_point, &kH_point);
498-
let c_scalar = VRF::ed25519_scalar_from_hash128(&c_hashbuf);
501+
let c_scalar = VRF::ed25519_scalar_from_hash128(&c_hashbuf)?;
499502

500503
let s_scalar = &k_scalar + &c_scalar * &x_scalar;
501504

502505
// NOTE: expect() won't panic because c_scalar is guaranteed to have
503506
// its upper 16 bytes as 0
504507
VRFProof::new(Gamma_point, c_scalar, s_scalar)
505-
.expect("FATAL ERROR: upper-16 bytes of proof's C scalar are NOT 0")
508+
.inspect_err(|e| error!("FATAL: upper-16 bytes of proof's C scalar are NOT 0: {e}"))
509+
.ok()
506510
}
507511

508512
/// Given a public key, verify that the private key owner that generate the ECVRF proof did so on the given message.
@@ -525,7 +529,9 @@ impl VRF {
525529
let V_point = s_reduced * &H_point - proof.c() * proof.Gamma();
526530

527531
let c_prime_hashbuf = VRF::hash_points(&H_point, proof.Gamma(), &U_point, &V_point);
528-
let c_prime = VRF::ed25519_scalar_from_hash128(&c_prime_hashbuf);
532+
let Some(c_prime) = VRF::ed25519_scalar_from_hash128(&c_prime_hashbuf) else {
533+
return Err(Error::InvalidHashPoints);
534+
};
529535

530536
// NOTE: this leverages constant-time comparison inherited from the Scalar impl
531537
Ok(c_prime == *(proof.c()))
@@ -587,7 +593,7 @@ mod tests {
587593
let privk = VRFPrivateKey::from_bytes(&proof_fixture.privkey[..]).unwrap();
588594
let expected_proof_bytes = &proof_fixture.proof[..];
589595

590-
let proof = VRF::prove(&privk, &alpha.to_vec());
596+
let proof = VRF::prove(&privk, &alpha.to_vec()).unwrap();
591597
let proof_bytes = proof.to_bytes();
592598

593599
assert_eq!(proof_bytes.to_vec(), expected_proof_bytes.to_vec());
@@ -609,7 +615,7 @@ mod tests {
609615
let mut msg = [0u8; 1024];
610616
rng.fill_bytes(&mut msg);
611617

612-
let proof = VRF::prove(&secret_key, &msg);
618+
let proof = VRF::prove(&secret_key, &msg).unwrap();
613619
let res = VRF::verify(&public_key, &proof, &msg).unwrap();
614620

615621
assert!(res);

stackslib/src/burnchains/tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ impl TestMiner {
241241
);
242242
match self.vrf_key_map.get(vrf_pubkey) {
243243
Some(prover_key) => {
244-
let proof = VRF::prove(prover_key, last_sortition_hash.as_bytes());
244+
let proof = VRF::prove(prover_key, last_sortition_hash.as_bytes())?;
245245
let valid = match VRF::verify(vrf_pubkey, &proof, last_sortition_hash.as_bytes()) {
246246
Ok(v) => v,
247247
Err(e) => false,

stackslib/src/chainstate/coordinator/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ fn make_genesis_block_with_recipients(
646646

647647
let parent_stacks_header = StacksHeaderInfo::regtest_genesis();
648648

649-
let proof = VRF::prove(vrf_key, sortition_tip.sortition_hash.as_bytes());
649+
let proof = VRF::prove(vrf_key, sortition_tip.sortition_hash.as_bytes()).unwrap();
650650

651651
let mut builder = StacksBlockBuilder::make_regtest_block_builder(
652652
burnchain,
@@ -909,7 +909,7 @@ fn make_stacks_block_with_input(
909909

910910
eprintln!("Build off of {:?}", &parent_stacks_header);
911911

912-
let proof = VRF::prove(vrf_key, sortition_tip.sortition_hash.as_bytes());
912+
let proof = VRF::prove(vrf_key, sortition_tip.sortition_hash.as_bytes()).unwrap();
913913

914914
let total_burn = parents_sortition.total_burn;
915915

stackslib/src/chainstate/nakamoto/tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1628,7 +1628,7 @@ fn test_nakamoto_block_static_verification() {
16281628
let vrf_privkey = VRFPrivateKey::new();
16291629
let vrf_pubkey = VRFPublicKey::from_private(&vrf_privkey);
16301630
let sortition_hash = SortitionHash([0x01; 32]);
1631-
let vrf_proof = VRF::prove(&vrf_privkey, sortition_hash.as_bytes());
1631+
let vrf_proof = VRF::prove(&vrf_privkey, sortition_hash.as_bytes()).unwrap();
16321632

16331633
let burn_recipient = StacksAddress::burn_address(false).to_account_principal();
16341634
let alt_recipient = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(&private_key_2))

testnet/stacks-node/src/keychain.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,38 @@ impl Keychain {
118118

119119
/// Generate a VRF proof over a given byte message.
120120
/// `block_height` must be the _same_ block height called to make_vrf_keypair()
121-
pub fn generate_proof(&self, block_height: u64, bytes: &[u8; 32]) -> VRFProof {
121+
pub fn generate_proof(&self, block_height: u64, bytes: &[u8; 32]) -> Option<VRFProof> {
122122
let (pk, sk) = self.make_vrf_keypair(block_height);
123-
let proof = VRF::prove(&sk, bytes.as_ref());
123+
let Some(proof) = VRF::prove(&sk, bytes.as_ref()) else {
124+
error!(
125+
"Failed to generate proof with keypair, will be unable to mine.";
126+
"block_height" => block_height,
127+
"pk" => ?pk
128+
);
129+
return None;
130+
};
124131

125132
// Ensure that the proof is valid by verifying
126-
let is_valid = VRF::verify(&pk, &proof, bytes.as_ref()).unwrap_or(false);
127-
assert!(is_valid);
128-
proof
133+
let is_valid = VRF::verify(&pk, &proof, bytes.as_ref())
134+
.inspect_err(|e| {
135+
error!(
136+
"Failed to validate generated proof, will be unable to mine.";
137+
"block_height" => block_height,
138+
"pk" => ?pk,
139+
"err" => %e,
140+
);
141+
})
142+
.ok()?;
143+
if !is_valid {
144+
error!(
145+
"Generated invalidat proof, will be unable to mine.";
146+
"block_height" => block_height,
147+
"pk" => ?pk,
148+
);
149+
None
150+
} else {
151+
Some(proof)
152+
}
129153
}
130154

131155
/// Generate a microblock signing key for this burnchain block height.
@@ -367,7 +391,7 @@ mod tests {
367391
};
368392

369393
// Generate the proof
370-
let proof = VRF::prove(vrf_sk, bytes.as_ref());
394+
let proof = VRF::prove(vrf_sk, bytes.as_ref())?;
371395
// Ensure that the proof is valid by verifying
372396
let is_valid = VRF::verify(vrf_pk, &proof, bytes.as_ref()).unwrap_or(false);
373397
assert!(is_valid);

testnet/stacks-node/src/nakamoto_node/miner.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,17 @@ impl BlockMinerThread {
11271127
)
11281128
};
11291129

1130+
let Some(vrf_proof) = vrf_proof else {
1131+
error!(
1132+
"Unable to generate VRF proof, will be unable to mine";
1133+
"burn_block_sortition_hash" => %self.burn_election_block.sortition_hash,
1134+
"burn_block_block_height" => %self.burn_block.block_height,
1135+
"burn_block_hash" => %self.burn_block.burn_header_hash,
1136+
"vrf_pubkey" => &self.registered_key.vrf_public_key.to_hex()
1137+
);
1138+
return None;
1139+
};
1140+
11301141
debug!(
11311142
"Generated VRF Proof: {} over {} ({},{}) with key {}",
11321143
vrf_proof.to_hex(),

testnet/stacks-node/src/neon_node.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,6 +1730,17 @@ impl BlockMinerThread {
17301730
)
17311731
};
17321732

1733+
let Some(vrf_proof) = vrf_proof else {
1734+
error!(
1735+
"Unable to generate VRF proof, will be unable to mine";
1736+
"burn_block_sortition_hash" => %self.burn_block.sortition_hash,
1737+
"burn_block_block_height" => %self.burn_block.block_height,
1738+
"burn_block_hash" => %self.burn_block.burn_header_hash,
1739+
"vrf_pubkey" => &self.registered_key.vrf_public_key.to_hex()
1740+
);
1741+
return None;
1742+
};
1743+
17331744
debug!(
17341745
"Generated VRF Proof: {} over {} ({},{}) with key {}",
17351746
vrf_proof.to_hex(),

testnet/stacks-node/src/node.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -659,10 +659,13 @@ impl Node {
659659
.expect("FATAL: failed to query canonical burn chain tip");
660660

661661
// Generates a proof out of the sortition hash provided in the params.
662-
let vrf_proof = self.keychain.generate_proof(
662+
let Some(vrf_proof) = self.keychain.generate_proof(
663663
registered_key.target_block_height,
664664
tip.sortition_hash.as_bytes(),
665-
);
665+
) else {
666+
warn!("Failed to generate VRF proof, will be unable to initiate new tenure");
667+
return None;
668+
};
666669

667670
// Generates a new secret key for signing the trail of microblocks
668671
// of the upcoming tenure.
@@ -731,10 +734,13 @@ impl Node {
731734
if self.active_registered_key.is_some() {
732735
let registered_key = self.active_registered_key.clone().unwrap();
733736

734-
let vrf_proof = self.keychain.generate_proof(
737+
let Some(vrf_proof) = self.keychain.generate_proof(
735738
registered_key.target_block_height,
736739
burnchain_tip.block_snapshot.sortition_hash.as_bytes(),
737-
);
740+
) else {
741+
warn!("Failed to generate VRF proof, will be unable to mine commits");
742+
return;
743+
};
738744

739745
let op = self.generate_block_commit_op(
740746
anchored_block_from_ongoing_tenure.header.block_hash(),

0 commit comments

Comments
 (0)