Open pool: the share's claimed key, its header's key and its coinbase reveal are one key or the share is refused before its PoW (sidechain::check_key, called in accept; known-pass make_share, known-fail the claim, the header and a foreign reveal); suite 30 of 30 on build-2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
c999eb2f0a
commit
a4c9958cc8
2 changed files with 52 additions and 1 deletions
|
|
@ -10,7 +10,7 @@
|
|||
//! fee-neutral and the choice is about variance alone (reinvent 3.5).
|
||||
|
||||
use crate::pool::{Member, Pool};
|
||||
use crate::sidechain::{check_pow, ChainParams, SeedsWire, Share, ShareChain, GENESIS_PARENT};
|
||||
use crate::sidechain::{check_key, check_pow, ChainParams, SeedsWire, Share, ShareChain, GENESIS_PARENT};
|
||||
use crate::state::{unix_ms, BlockRec, State};
|
||||
use crate::verify::JobKey;
|
||||
use kaspa_consensus_core::block::Block;
|
||||
|
|
@ -318,6 +318,8 @@ impl Open {
|
|||
if wire != seeds {
|
||||
return Err(format!("the share's day {} is not the day of its timestamp ({})", wire.day, seeds.day));
|
||||
}
|
||||
// Igneum 2.0: the claimed key, the header's key and the reveal are one key, before the PoW
|
||||
crate::sidechain::check_key(&share)?;
|
||||
check_pow(&self.engine, &share)?;
|
||||
let share = Arc::new(share);
|
||||
let tip = {
|
||||
|
|
|
|||
|
|
@ -436,6 +436,25 @@ impl ShareChain {
|
|||
}
|
||||
|
||||
/// The PoW of a share: the lane hash under the share's seeds equals the claim and meets the share's target.
|
||||
/// Igneum 2.0, the vote-key commitment on the open pool (`docs/design/pool-vote-key-commitment.md` 2.2 item 3 and 5.3
|
||||
/// item 2): the key a share claims (`key_hash`), the key its header names (`vote_key_hash`, under proof of work) and,
|
||||
/// when its coinbase carries a reveal (`IGNK`), the reveal's key are one key, or the share is refused before its PoW is
|
||||
/// evaluated. A daemon that stamps another identity into a member's template fails here on every honest daemon.
|
||||
pub fn check_key(s: &Share) -> Result<(), String> {
|
||||
let claimed: Hash = s.key_hash.parse().map_err(|_| format!("key_hash {} is not a hash", s.key_hash))?;
|
||||
let header_key = s.raw.header.vote_key_hash;
|
||||
if claimed != header_key {
|
||||
return Err(format!("the share claims key {} but its header names {header_key}", s.key_hash));
|
||||
}
|
||||
if let Some(cb) = s.raw.transactions.first()
|
||||
&& let Some(reveal) = KeyReveal::find_in(&cb.payload)
|
||||
&& reveal.key_hash() != header_key
|
||||
{
|
||||
return Err(format!("the coinbase reveals key {} but the header names {header_key}", reveal.key_hash()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_pow(engine: &IgneumEngine, s: &Share) -> Result<(), String> {
|
||||
let seeds = s.seeds.to_seeds()?;
|
||||
let block: Block = s.raw.clone().try_into().map_err(|e| format!("block convert: {e}"))?;
|
||||
|
|
@ -583,6 +602,36 @@ pub mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Igneum 2.0: the key a share claims, the key its header names and the key its coinbase reveals are one key.
|
||||
/// Known-pass: make_share. Known-fail: the claim swapped, the header's key swapped (the PoW still over the old
|
||||
/// header, so check_key fires before check_pow would), and a foreign reveal in the coinbase.
|
||||
#[test]
|
||||
fn a_share_whose_keys_disagree_is_refused_before_its_pow() {
|
||||
let engine = IgneumEngine::new();
|
||||
let mut chain = ShareChain::new(params());
|
||||
chain.genesis_target = Some(u64::MAX >> 6);
|
||||
let good = make_share(&chain, &engine, GENESIS_PARENT, "ma", [0xaa; 20], 1_000_000, 0);
|
||||
assert!(check_key(&good).is_ok(), "known-pass");
|
||||
let mut claim = good.clone();
|
||||
claim.key_hash = Hash::from_bytes([9u8; 32]).to_string();
|
||||
assert!(check_key(&claim).unwrap_err().contains("claims key"), "known-fail: the claim");
|
||||
let mut header = good.clone();
|
||||
header.raw.header.vote_key_hash = Hash::from_bytes([9u8; 32]);
|
||||
assert!(check_key(&header).unwrap_err().contains("header names"), "known-fail: the header");
|
||||
let mut reveal = good.clone();
|
||||
let other = kaspa_consensus_core::finality::VoteSecretKey::from_label("mb").key_reveal();
|
||||
let mut payload = reveal.raw.transactions[0].payload.clone();
|
||||
// the reveal in the payload is IGNK || pubkey || pop (scheme 0): overwrite it in place with the other key's
|
||||
let tag = kaspa_consensus_core::finality::KEY_REVEAL_TAG;
|
||||
let at = payload.windows(4).position(|w| w == tag).expect("the test share carries a reveal");
|
||||
let mine = good.raw.transactions[0].payload[at..].to_vec();
|
||||
let theirs = other.to_extra_data();
|
||||
assert_eq!(mine.len() >= theirs.len(), true);
|
||||
payload[at..at + theirs.len()].copy_from_slice(&theirs);
|
||||
reveal.raw.transactions[0].payload = payload;
|
||||
assert!(check_key(&reveal).unwrap_err().contains("reveals key"), "known-fail: a foreign reveal");
|
||||
}
|
||||
|
||||
/// A chain of shares by three members: every share passes the structure check and the PoW check, the window's
|
||||
/// split is the members' work plus the dev entry and sums to one, the retarget moves toward the share interval,
|
||||
/// a withheld share is in nobody's window, and a share off the heavier branch is stale.
|
||||
|
|
|
|||
Loading…
Reference in a new issue