Skip to content

Commit d8b85c7

Browse files
committed
Reject BitName registrations without a matching reservation
1 parent 67a981f commit d8b85c7

3 files changed

Lines changed: 38 additions & 10 deletions

File tree

lib/state/bitnames.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -505,21 +505,24 @@ impl Dbs {
505505
bitname_data: &crate::types::MutableBitNameData,
506506
height: u32,
507507
) -> Result<(), Error> {
508-
// Find the reservation to burn
509-
let implied_commitment =
510-
filled_tx.implied_reservation_commitment().expect(
511-
"A BitName registration tx should have an implied commitment",
512-
);
513-
let burned_reservation_txid =
514-
filled_tx.spent_reservations().find_map(|(_, filled_output)| {
515-
let (txid, commitment) = filled_output.reservation_data()
516-
.expect("A spent reservation should correspond to a commitment");
508+
// Find the reservation to burn: the spent reservation whose commitment
509+
// equals keyed_hash(revealed_nonce, name_hash). This is enforced by
510+
// `validate_bitnames`; returning an error here rather than panicking
511+
// guards against any unvalidated connection path.
512+
let implied_commitment = filled_tx
513+
.implied_reservation_commitment()
514+
.ok_or(Error::NoReservationForRegistration { bitname })?;
515+
let burned_reservation_txid = filled_tx
516+
.spent_reservations()
517+
.find_map(|(_, filled_output)| {
518+
let (txid, commitment) = filled_output.reservation_data()?;
517519
if *commitment == implied_commitment {
518520
Some(txid)
519521
} else {
520522
None
521523
}
522-
}).expect("A BitName registration tx should correspond to a burned reservation");
524+
})
525+
.ok_or(Error::NoReservationForRegistration { bitname })?;
523526
if !self.reservations.delete(rwtxn, burned_reservation_txid)? {
524527
return Err(Error::MissingReservation {
525528
txid: *burned_reservation_txid,

lib/state/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ pub enum BitName {
3737
MissingReservation { txid: Txid },
3838
#[error("no BitNames to update")]
3939
NoBitNamesToUpdate,
40+
#[error(
41+
"no spent BitName reservation matches the commitment for the \
42+
registration of {bitname}"
43+
)]
44+
NoReservationForRegistration { bitname: BitNameId },
4045
}
4146

4247
impl From<db::Error> for BitName {

lib/state/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,26 @@ impl State {
407407
if self.bitnames.try_get_bitname(rotxn, &name_hash)?.is_some() {
408408
return Err(Error::BitNameAlreadyRegistered { name_hash });
409409
}
410+
// A registration must burn the reservation that commits to it,
411+
// i.e. a spent reservation whose commitment equals
412+
// keyed_hash(revealed_nonce, name_hash). Without this check,
413+
// `apply_registration` would later fail to find the reservation
414+
// to burn.
415+
if let Some(implied_commitment) =
416+
tx.implied_reservation_commitment()
417+
{
418+
let burns_matching_reservation =
419+
tx.spent_reservations().any(|(_, filled_output)| {
420+
filled_output.reservation_commitment()
421+
== Some(&implied_commitment)
422+
});
423+
if !burns_matching_reservation {
424+
return Err(error::BitName::NoReservationForRegistration {
425+
bitname: name_hash,
426+
}
427+
.into());
428+
}
429+
}
410430
if n_bitname_outputs == n_bitname_inputs + 1 {
411431
return Ok(());
412432
};

0 commit comments

Comments
 (0)