Skip to content
Merged
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
36 changes: 34 additions & 2 deletions crates/chia-protocol/src/fullblock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use chia_traits::Streamable;
use chia_traits::chia_error::{Error, Result};
use std::io::Cursor;

// Similar to ProofOfSpace, we use unused bits in the Optional<> prefix byte
// Similar to ProofOfSpace, we use unused bits in the Option<> prefix byte
// for transactions_generator to encode a version flag. Bit 1 (0b10) indicates
// the "raw bytes" format where the generator is serialized as length-prefixed
// bytes (like Bytes) instead of a self-describing CLVM Program, and
Expand Down Expand Up @@ -121,7 +121,7 @@ impl Streamable for FullBlock {
let transactions_info = <Option<TransactionsInfo> as Streamable>::parse::<TRUSTED>(input)?;

let prefix = <u8 as Streamable>::parse::<TRUSTED>(input)?;
let version = u8::from((prefix & 0b10) != 0);
let version = prefix >> 1;
let has_generator = (prefix & 1) != 0;

if version == 0 {
Expand Down Expand Up @@ -547,4 +547,36 @@ mod tests {
let block2 = FullBlock::parse::<false>(&mut Cursor::new(&buf)).unwrap();
assert_eq!(block2.transactions_generator_buffer.unwrap(), garbage);
}

// The version flag is packed into the transactions_generator Option prefix
// byte. Only bit 0 (the Option flag) and bit 1 (the version) carry
// meaning. The high bits (2..=7) must be rejected, matching the strictness
// of a plain Option<> prefix in earlier protocol versions where this byte
// could only ever be 0 or 1.
#[test]
fn high_prefix_bits_rejected() {
let v0_none = make_v0_block(None, vec![]).to_bytes().unwrap();
let v0_some = make_v0_block(Some(Program::from(vec![0x80])), vec![])
.to_bytes()
.unwrap();
let offset = v0_none
.iter()
.zip(v0_some.iter())
.position(|(a, b)| a != b)
.unwrap();
assert_eq!(v0_none[offset], 0b00);

let v1_none = make_v1_block(None).to_bytes().unwrap();
assert_eq!(v1_none[offset], 0b10);

for valid in [&v0_none, &v1_none] {
for bit in 2..8u8 {
let mut buf = valid.clone();
buf[offset] |= 1 << bit;
let err = FullBlock::parse::<false>(&mut Cursor::new(&buf))
.expect_err("high prefix bit must be rejected");
assert_eq!(err, Error::InvalidFullBlock);
}
}
}
}
2 changes: 1 addition & 1 deletion crates/chia-protocol/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ impl Program {
// and it being treated as the CLVM structure it represents. In python's
// SerializedProgram, we have a hack where we interpret the first
// "layer" of SerializedProgram, or lists of SerializedProgram this way.
// But if we encounter an Optional or tuple, we defer to the clvm
// But if we encounter an Option or tuple, we defer to the clvm
// wheel's conversion function to SExp. This level does not have any
// special treatment for SerializedProgram (as that would cause a
// circular dependency).
Expand Down
12 changes: 6 additions & 6 deletions crates/chia-protocol/src/proof_of_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl ProofOfSpace {
// ProofOfSpace was updated in Chia 3.0 to support v2 proofs. In order to stay
// backwards compatible with the network protocol and the block hashes of
// previous versions, for v1 proofs, some care has to be taken.
// Optional fields are serialized with a 1-byte prefix indicating whether the
// Option fields are serialized with a 1-byte prefix indicating whether the
// field is set or not. This byte is either 0 or 1. This leaves 7 unused bits.
// We use bit 2 in the byte prefix for the pool_contract_puzzle_hash field to
// indicate whether this is a v2 proof or not. v1 proofs leave this bit as 0,
Expand Down Expand Up @@ -368,7 +368,7 @@ mod tests {
)
}

// Locate the pool_contract_puzzle_hash Optional prefix byte (which also
// Locate the pool_contract_puzzle_hash Option prefix byte (which also
// encodes the version) by finding where a contract-present and a
// contract-absent serialization first differ.
fn prefix_offset() -> usize {
Expand Down Expand Up @@ -495,7 +495,7 @@ mod tests {
}

// Round-trip every accepted prefix-byte combination. For v0 (legacy v1
// proofs) the pool_contract Optional is lenient and accepts any of
// proofs) the pool_contract Option is lenient and accepts any of
// {neither, pool_pk, contract, both}, exactly like the original plain
// Option<> derive. For v1 (v2 proofs) the size field is not stored and
// collapses to 0 on parse.
Expand Down Expand Up @@ -547,10 +547,10 @@ mod tests {
}
}

// The version flag is packed into the pool_contract_puzzle_hash Optional
// prefix byte. Only bit 0 (the Optional flag) and bit 1 (the version) carry
// The version flag is packed into the pool_contract_puzzle_hash Option
// prefix byte. Only bit 0 (the Option flag) and bit 1 (the version) carry
// meaning. The high bits (2..=7) must be rejected for both versions,
// matching the strictness of a plain Optional<> prefix in earlier protocol
// matching the strictness of a plain Option<> prefix in earlier protocol
// versions where this byte could only ever be 0 or 1.
#[rstest]
fn high_prefix_bits_rejected(#[values(0, 1)] version: u8, #[values(2, 3, 4, 5, 6, 7)] bit: u8) {
Expand Down
Loading
Loading