1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
//! Defines traits and types used by the rollup to verify claims about the
//! DA layer.
use core::fmt::Debug;
use std::cmp::min;
use borsh::{BorshDeserialize, BorshSerialize};
use bytes::Buf;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::zk::ValidityCondition;
use crate::BasicAddress;
/// A specification for the types used by a DA layer.
pub trait DaSpec: 'static + Debug + PartialEq + Eq {
/// The hash of a DA layer block
type SlotHash: BlockHashTrait;
/// The block header type used by the DA layer
type BlockHeader: BlockHeaderTrait<Hash = Self::SlotHash>;
/// The transaction type used by the DA layer.
type BlobTransaction: BlobReaderTrait<Address = Self::Address>;
/// The type used to represent addresses on the DA layer.
type Address: BasicAddress;
/// Any conditions imposed by the DA layer which need to be checked outside of the SNARK
type ValidityCondition: ValidityCondition;
/// A proof that each tx in a set of blob transactions is included in a given block.
type InclusionMultiProof: Serialize + DeserializeOwned;
/// A proof that a claimed set of transactions is complete.
/// For example, this could be a range proof demonstrating that
/// the provided BlobTransactions represent the entire contents
/// of Celestia namespace in a given block
type CompletenessProof: Serialize + DeserializeOwned;
/// The parameters of the rollup which are baked into the state-transition function.
/// For example, this could include the namespace of the rollup on Celestia.
type ChainParams;
}
/// A `DaVerifier` implements the logic required to create a zk proof that some data
/// has been processed.
///
/// This trait implements the required functionality to *verify* claims of the form
/// "If X is the most recent block in the DA layer, then Y is the ordered set of transactions that must
/// be processed by the rollup."
pub trait DaVerifier {
/// The set of types required by the DA layer.
type Spec: DaSpec;
/// The error type returned by the DA layer's verification function
/// TODO: Should we add `std::Error` bound so it can be `()?` ?
type Error: Debug;
/// Create a new da verifier with the given chain parameters
fn new(params: <Self::Spec as DaSpec>::ChainParams) -> Self;
/// Verify a claimed set of transactions against a block header.
fn verify_relevant_tx_list(
&self,
block_header: &<Self::Spec as DaSpec>::BlockHeader,
txs: &[<Self::Spec as DaSpec>::BlobTransaction],
inclusion_proof: <Self::Spec as DaSpec>::InclusionMultiProof,
completeness_proof: <Self::Spec as DaSpec>::CompletenessProof,
) -> Result<<Self::Spec as DaSpec>::ValidityCondition, Self::Error>;
}
#[derive(Debug, Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize, PartialEq)]
/// Simple structure that implements the Read trait for a buffer and counts the number of bytes read from the beginning.
/// Useful for the partial blob reading optimization: we know for each blob how many bytes have been read from the beginning.
///
/// Because of soundness issues we cannot implement the Buf trait because the prover could get unproved blob data using the chunk method.
pub struct CountedBufReader<B: Buf> {
/// The original blob data.
inner: B,
/// An accumulator that stores the data read from the blob buffer into a vector.
/// Allows easy access to the data that has already been read
accumulator: Vec<u8>,
}
impl<B: Buf> CountedBufReader<B> {
/// Creates a new buffer reader with counter from an objet that implements the buffer trait
pub fn new(inner: B) -> Self {
let buf_size = inner.remaining();
CountedBufReader {
inner,
accumulator: Vec::with_capacity(buf_size),
}
}
/// Advance the accumulator by `num_bytes` bytes. If `num_bytes` is greater than the length
/// of remaining unverified data, then all remaining unverified data is added to the accumulator.
pub fn advance(&mut self, num_bytes: usize) {
let requested = num_bytes;
let remaining = self.inner.remaining();
if remaining == 0 {
return;
}
// `Buf::advance` would panic if `num_bytes` was greater than the length of the remaining unverified data,
// but we just advance to the end of the buffer.
let num_to_read = min(remaining, requested);
// Extend the inner vector with zeros (copy_to_slice requires the vector to have
// the correct *length* not just capacity)
self.accumulator
.resize(self.accumulator.len() + num_to_read, 0);
// Use copy_to_slice to overwrite the zeros we just added
let accumulator_len = self.accumulator.len();
self.inner
.copy_to_slice(self.accumulator[accumulator_len - num_to_read..].as_mut());
}
/// Getter: returns a reference to an accumulator of the blob data read by the rollup
pub fn accumulator(&self) -> &[u8] {
&self.accumulator
}
/// Contains the total length of the data (length already read + length remaining)
pub fn total_len(&self) -> usize {
self.inner.remaining() + self.accumulator.len()
}
}
/// This trait wraps "blob transaction" from a data availability layer allowing partial consumption of the
/// blob data by the rollup.
///
/// The motivation for this trait is limit the amount of validation work that a rollup has to perform when
/// verifying a state transition. In general, it will often be the case that a rollup only cares about some
/// portion of the data from a blob. For example, if a blob contains a malformed transaction then the rollup
/// will slash the sequencer and exit early - so it only cares about the content of the blob up to that point.
///
/// This trait allows the DaVerifier to track which data was read by the rollup, and only verify the relevant data.
pub trait BlobReaderTrait: Serialize + DeserializeOwned + Send + Sync + 'static {
/// The type used to represent addresses on the DA layer.
type Address: BasicAddress;
/// Returns the address (on the DA layer) of the entity which submitted the blob transaction
fn sender(&self) -> Self::Address;
/// Returns the hash of the blob as it appears on the DA layer
fn hash(&self) -> [u8; 32];
/// Returns a slice containing all the data accessible to the rollup at this point in time.
/// When running in native mode, the rollup can extend this slice by calling `advance`. In zk-mode,
/// the rollup is limited to only the verified data.
///
/// Rollups should use this method in conjunction with `advance` to read only the minimum amount
/// of data required for execution
fn verified_data(&self) -> &[u8];
/// Returns the total number of bytes in the blob. Note that this may be unequal to `verified_data.len()`.
fn total_len(&self) -> usize;
/// Extends the `partial_data` accumulator with the next `num_bytes` of data from the blob
/// and returns a reference to the entire contents of the blob up to this point.
///
/// If `num_bytes` is greater than the length of the remaining unverified data,
/// then all remaining unverified data is added to the accumulator.
///
/// ### Note:
/// This method is only available when the `native` feature is enabled because it is unsafe to access
/// unverified data during execution
#[cfg(feature = "native")]
fn advance(&mut self, num_bytes: usize) -> &[u8];
/// Verifies all remaining unverified data in the blob and returns a reference to the entire contents of the blob.
/// For efficiency, rollups should prefer use of `verified_data` and `advance` unless they know that all of the
/// blob data will be required for execution.
#[cfg(feature = "native")]
fn full_data(&mut self) -> &[u8] {
self.advance(self.total_len())
}
}
/// Trait with collection of trait bounds for a block hash.
pub trait BlockHashTrait:
Serialize + DeserializeOwned + PartialEq + Debug + Send + Sync + Clone + Eq + Into<[u8; 32]>
{
}
/// A block header, typically used in the context of an underlying DA blockchain.
pub trait BlockHeaderTrait: PartialEq + Debug + Clone + Serialize + DeserializeOwned {
/// Each block header must have a unique canonical hash.
type Hash: Clone;
/// Each block header must contain the hash of the previous block.
fn prev_hash(&self) -> Self::Hash;
/// Hash the type to get the digest.
fn hash(&self) -> Self::Hash;
}