Skip to content

Commit

Permalink
adds shred::layout::get_signed_data
Browse files Browse the repository at this point in the history
Working towards removing merkle root from shreds payload, the commit
implements api to obtain signed data from shreds binary.
  • Loading branch information
behzadnouri committed Dec 30, 2022
1 parent 1d456a8 commit ed107b1
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 138 deletions.
117 changes: 104 additions & 13 deletions ledger/src/shred.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ use dispatch;
impl Shred {
dispatch!(fn common_header(&self) -> &ShredCommonHeader);
dispatch!(fn set_signature(&mut self, signature: Signature));
dispatch!(fn signed_message(&self) -> &[u8]);
dispatch!(fn signed_data(&self) -> &[u8]);

// Returns the portion of the shred's payload which is erasure coded.
dispatch!(pub(crate) fn erasure_shard(self) -> Result<Vec<u8>, Error>);
Expand Down Expand Up @@ -460,7 +460,7 @@ impl Shred {
}

pub fn sign(&mut self, keypair: &Keypair) {
let signature = keypair.sign_message(self.signed_message());
let signature = keypair.sign_message(self.signed_data());
self.set_signature(signature);
}

Expand Down Expand Up @@ -508,8 +508,8 @@ impl Shred {

#[must_use]
pub fn verify(&self, pubkey: &Pubkey) -> bool {
let message = self.signed_message();
self.signature().verify(pubkey.as_ref(), message)
let data = self.signed_data();
self.signature().verify(pubkey.as_ref(), data)
}

// Returns true if the erasure coding of the two shreds mismatch.
Expand Down Expand Up @@ -538,9 +538,26 @@ impl Shred {
// Helper methods to extract pieces of the shred from the payload
// without deserializing the entire payload.
pub mod layout {
use {super::*, crate::shred::merkle::MerkleRoot, std::ops::Range};
#[cfg(test)]
use crate::shred::merkle::MerkleRoot;
use {super::*, std::ops::Range};
use {
rand::{seq::SliceRandom, Rng},
std::collections::HashMap,
};

pub(crate) enum SignedData<'a> {
Chunk(&'a [u8]),
MerkleRoot(MerkleRoot),
}

impl<'a> AsRef<[u8]> for SignedData<'a> {
fn as_ref(&self) -> &[u8] {
match self {
Self::Chunk(chunk) => chunk,
Self::MerkleRoot(root) => root,
}
}
}

fn get_shred_size(packet: &Packet) -> Option<usize> {
let size = packet.data(..)?.len();
Expand Down Expand Up @@ -615,18 +632,36 @@ pub mod layout {
))
}

// Returns slice range of the shred payload which is signed.
pub(crate) fn get_signed_message_range(shred: &[u8]) -> Option<Range<usize>> {
let range = match get_shred_variant(shred).ok()? {
ShredVariant::LegacyCode | ShredVariant::LegacyData => legacy::SIGNED_MESSAGE_RANGE,
pub(crate) fn get_signed_data(shred: &[u8]) -> Option<SignedData> {
let data = match get_shred_variant(shred).ok()? {
ShredVariant::LegacyCode | ShredVariant::LegacyData => {
let chunk = shred.get(self::legacy::SIGNED_MESSAGE_OFFSETS)?;
SignedData::Chunk(chunk)
}
ShredVariant::MerkleCode(proof_size) => {
let merkle_root = self::merkle::ShredCode::get_merkle_root(shred, proof_size)?;
SignedData::MerkleRoot(merkle_root)
}
ShredVariant::MerkleData(proof_size) => {
let merkle_root = self::merkle::ShredData::get_merkle_root(shred, proof_size)?;
SignedData::MerkleRoot(merkle_root)
}
};
Some(data)
}

// Returns offsets within the shred payload which is signed.
pub(crate) fn get_signed_data_offsets(shred: &[u8]) -> Option<Range<usize>> {
let offsets = match get_shred_variant(shred).ok()? {
ShredVariant::LegacyCode | ShredVariant::LegacyData => legacy::SIGNED_MESSAGE_OFFSETS,
ShredVariant::MerkleCode(proof_size) => {
merkle::ShredCode::get_signed_message_range(proof_size)?
merkle::ShredCode::get_signed_data_offsets(proof_size)?
}
ShredVariant::MerkleData(proof_size) => {
merkle::ShredData::get_signed_message_range(proof_size)?
merkle::ShredData::get_signed_data_offsets(proof_size)?
}
};
(range.end <= shred.len()).then_some(range)
(offsets.end <= shred.len()).then_some(offsets)
}

pub(crate) fn get_reference_tick(shred: &[u8]) -> Result<u8, Error> {
Expand All @@ -652,6 +687,62 @@ pub mod layout {
}
}
}

// Minimally corrupts the packet so that the signature no longer verifies.
#[cfg(test)]
pub(crate) fn corrupt_packet<R: Rng>(
rng: &mut R,
packet: &mut Packet,
keypairs: &HashMap<Slot, Keypair>,
) {
fn modify_packet<R: Rng>(rng: &mut R, packet: &mut Packet, offsets: Range<usize>) {
let buffer = packet.buffer_mut();
let byte = buffer[offsets].choose_mut(rng).unwrap();
*byte = rng.gen::<u8>().max(1u8).wrapping_add(*byte);
}
let shred = get_shred(packet).unwrap();
let merkle_proof_size = match get_shred_variant(shred).unwrap() {
ShredVariant::LegacyCode | ShredVariant::LegacyData => None,
ShredVariant::MerkleCode(proof_size) | ShredVariant::MerkleData(proof_size) => {
Some(proof_size)
}
};
let coin_flip: bool = rng.gen();
if coin_flip {
// Corrupt one byte within the signature offsets.
modify_packet(rng, packet, 0..SIGNATURE_BYTES);
} else {
// Corrupt one byte within the signed data offsets.
let size = shred.len();
let offsets = get_signed_data_offsets(shred).unwrap();
modify_packet(rng, packet, offsets);
if let Some(proof_size) = merkle_proof_size {
// Also need to corrupt the merkle proof.
// Proof entries are each 20 bytes at the end of shreds.
let offset = usize::from(proof_size) * 20;
modify_packet(rng, packet, size - offset..size);
}
}
// Assert that the signature no longer verifies.
let shred = get_shred(packet).unwrap();
let slot = get_slot(shred).unwrap();
let signature = get_signature(shred).unwrap();
if coin_flip {
let pubkey = keypairs[&slot].pubkey();
let data = get_signed_data(shred).unwrap();
assert!(!signature.verify(pubkey.as_ref(), data.as_ref()));
let offsets = get_signed_data_offsets(shred).unwrap();
assert!(!signature.verify(pubkey.as_ref(), &shred[offsets]));
} else {
// Slot may have been corrupted and no longer mapping to a keypair.
let pubkey = keypairs.get(&slot).map(Keypair::pubkey).unwrap_or_default();
if let Some(data) = get_signed_data(shred) {
assert!(!signature.verify(pubkey.as_ref(), data.as_ref()));
}
let offsets = get_signed_data_offsets(shred).unwrap_or_default();
assert!(!signature.verify(pubkey.as_ref(), &shred[offsets]));
}
}
}

impl From<ShredCode> for Shred {
Expand Down
7 changes: 4 additions & 3 deletions ledger/src/shred/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use {

// All payload including any zero paddings are signed.
// Code and data shreds have the same payload size.
pub(super) const SIGNED_MESSAGE_RANGE: Range<usize> = SIZE_OF_SIGNATURE..ShredData::SIZE_OF_PAYLOAD;
pub(super) const SIGNED_MESSAGE_OFFSETS: Range<usize> =
SIZE_OF_SIGNATURE..ShredData::SIZE_OF_PAYLOAD;
const_assert_eq!(ShredData::SIZE_OF_PAYLOAD, ShredCode::SIZE_OF_PAYLOAD);
const_assert_eq!(ShredData::SIZE_OF_PAYLOAD, 1228);
const_assert_eq!(ShredData::CAPACITY, 1051);
Expand Down Expand Up @@ -108,7 +109,7 @@ impl Shred for ShredData {
shred_data::sanitize(self)
}

fn signed_message(&self) -> &[u8] {
fn signed_data(&self) -> &[u8] {
debug_assert_eq!(self.payload.len(), Self::SIZE_OF_PAYLOAD);
&self.payload[SIZE_OF_SIGNATURE..]
}
Expand Down Expand Up @@ -170,7 +171,7 @@ impl Shred for ShredCode {
shred_code::sanitize(self)
}

fn signed_message(&self) -> &[u8] {
fn signed_data(&self) -> &[u8] {
debug_assert_eq!(self.payload.len(), Self::SIZE_OF_PAYLOAD);
&self.payload[SIZE_OF_SIGNATURE..]
}
Expand Down
30 changes: 15 additions & 15 deletions ledger/src/shred/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ impl Shred {
dispatch!(fn sanitize(&self, verify_merkle_proof: bool) -> Result<(), Error>);
dispatch!(fn set_merkle_branch(&mut self, merkle_branch: &MerkleBranch) -> Result<(), Error>);
dispatch!(fn set_signature(&mut self, signature: Signature));
dispatch!(fn signed_message(&self) -> &[u8]);
dispatch!(fn signed_data(&self) -> &[u8]);

#[must_use]
fn verify(&self, pubkey: &Pubkey) -> bool {
let message = self.signed_message();
self.signature().verify(pubkey.as_ref(), message)
let data = self.signed_data();
self.signature().verify(pubkey.as_ref(), data)
}

fn signature(&self) -> Signature {
Expand Down Expand Up @@ -147,7 +147,7 @@ impl ShredData {
.ok_or(Error::InvalidProofSize(proof_size))
}

pub(super) fn get_signed_message_range(proof_size: u8) -> Option<Range<usize>> {
pub(super) fn get_signed_data_offsets(proof_size: u8) -> Option<Range<usize>> {
let data_buffer_size = Self::capacity(proof_size).ok()?;
let offset = Self::SIZE_OF_HEADERS + data_buffer_size;
Some(offset..offset + SIZE_OF_MERKLE_ROOT)
Expand Down Expand Up @@ -247,7 +247,6 @@ impl ShredData {
shred_data::sanitize(self)
}

#[cfg(test)]
pub(super) fn get_merkle_root(shred: &[u8], proof_size: u8) -> Option<MerkleRoot> {
debug_assert_eq!(
shred::layout::get_shred_variant(shred).unwrap(),
Expand Down Expand Up @@ -332,7 +331,7 @@ impl ShredCode {
Ok(verify_merkle_proof(index, node, &self.merkle_branch()?))
}

pub(super) fn get_signed_message_range(proof_size: u8) -> Option<Range<usize>> {
pub(super) fn get_signed_data_offsets(proof_size: u8) -> Option<Range<usize>> {
let offset = Self::SIZE_OF_HEADERS + Self::capacity(proof_size).ok()?;
Some(offset..offset + SIZE_OF_MERKLE_ROOT)
}
Expand Down Expand Up @@ -403,7 +402,6 @@ impl ShredCode {
shred_code::sanitize(self)
}

#[cfg(test)]
pub(super) fn get_merkle_root(shred: &[u8], proof_size: u8) -> Option<MerkleRoot> {
debug_assert_eq!(
shred::layout::get_shred_variant(shred).unwrap(),
Expand Down Expand Up @@ -494,7 +492,7 @@ impl ShredTrait for ShredData {
self.sanitize(/*verify_merkle_proof:*/ true)
}

fn signed_message(&self) -> &[u8] {
fn signed_data(&self) -> &[u8] {
self.merkle_root().map(AsRef::as_ref).unwrap_or_default()
}
}
Expand Down Expand Up @@ -559,7 +557,7 @@ impl ShredTrait for ShredCode {
self.sanitize(/*verify_merkle_proof:*/ true)
}

fn signed_message(&self) -> &[u8] {
fn signed_data(&self) -> &[u8] {
self.merkle_root().map(AsRef::as_ref).unwrap_or_default()
}
}
Expand Down Expand Up @@ -646,7 +644,6 @@ where
})?
}

#[cfg(test)]
fn get_merkle_root(
shred: &[u8],
proof_size: u8,
Expand Down Expand Up @@ -1353,7 +1350,7 @@ mod test {
let merkle_branch = make_merkle_branch(index, num_shreds, &tree).unwrap();
assert_eq!(merkle_branch.proof.len(), usize::from(proof_size));
shred.set_merkle_branch(&merkle_branch).unwrap();
let signature = keypair.sign_message(shred.signed_message());
let signature = keypair.sign_message(shred.signed_data());
shred.set_signature(signature);
assert!(shred.verify(&keypair.pubkey()));
assert_matches!(shred.sanitize(/*verify_merkle_proof:*/ true), Ok(()));
Expand Down Expand Up @@ -1485,8 +1482,9 @@ mod test {
.collect::<Vec<_>>()
);
// Assert that shreds sanitize and verify.
let pubkey = keypair.pubkey();
for shred in &shreds {
assert!(shred.verify(&keypair.pubkey()));
assert!(shred.verify(&pubkey));
assert_matches!(shred.sanitize(/*verify_merkle_proof:*/ true), Ok(()));
let ShredCommonHeader {
signature,
Expand All @@ -1511,9 +1509,11 @@ mod test {
assert_eq!(shred::layout::get_version(shred), Some(version));
assert_eq!(shred::layout::get_shred_id(shred), Some(key));
assert_eq!(shred::layout::get_merkle_root(shred), merkle_root);
let slice = shred::layout::get_signed_message_range(shred).unwrap();
let message = shred.get(slice).unwrap();
assert!(signature.verify(keypair.pubkey().as_ref(), message));
let offsets = shred::layout::get_signed_data_offsets(shred).unwrap();
let data = shred.get(offsets).unwrap();
assert!(signature.verify(pubkey.as_ref(), data));
let data = shred::layout::get_signed_data(shred).unwrap();
assert!(signature.verify(pubkey.as_ref(), data.as_ref()));
}
// Verify common, data and coding headers.
let mut num_data_shreds = 0;
Expand Down
2 changes: 1 addition & 1 deletion ledger/src/shred/shred_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl ShredCode {
dispatch!(pub(super) fn payload(&self) -> &Vec<u8>);
dispatch!(pub(super) fn sanitize(&self) -> Result<(), Error>);
dispatch!(pub(super) fn set_signature(&mut self, signature: Signature));
dispatch!(pub(super) fn signed_message(&self) -> &[u8]);
dispatch!(pub(super) fn signed_data(&self) -> &[u8]);

// Only for tests.
dispatch!(pub(super) fn set_index(&mut self, index: u32));
Expand Down
2 changes: 1 addition & 1 deletion ledger/src/shred/shred_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl ShredData {
dispatch!(pub(super) fn payload(&self) -> &Vec<u8>);
dispatch!(pub(super) fn sanitize(&self) -> Result<(), Error>);
dispatch!(pub(super) fn set_signature(&mut self, signature: Signature));
dispatch!(pub(super) fn signed_message(&self) -> &[u8]);
dispatch!(pub(super) fn signed_data(&self) -> &[u8]);

// Only for tests.
dispatch!(pub(super) fn set_index(&mut self, index: u32));
Expand Down
2 changes: 1 addition & 1 deletion ledger/src/shred/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub(super) trait Shred: Sized {
fn erasure_shard_as_slice(&self) -> Result<&[u8], Error>;

// Portion of the payload which is signed.
fn signed_message(&self) -> &[u8];
fn signed_data(&self) -> &[u8];

// Only for tests.
fn set_index(&mut self, index: u32);
Expand Down
Loading

0 comments on commit ed107b1

Please sign in to comment.