use std::future::Future;
use api::{
heddle::api::common::{HostedWitnessHistoryProofV1, SignedHostedWitnessStatementV1},
hybrid_codec::{Reject, canonical},
witness_trust::{self, VerifiedWitnessSet},
};
use prost::Message;
use crate::contract::{GetHostedWitnessHistoryProofRequest, GetHostedWitnessHistoryProofResponse};
#[derive(Debug, thiserror::Error)]
pub enum Error<E: std::error::Error> {
#[error("HYBRID history proof rejected: {0}")]
Rejected(#[from] Reject),
#[error("history proof lookup failed: {0}")]
Lookup(E),
#[error("history proof is unavailable")]
NotFound,
}
pub trait HistoryProofLookup: Send + Sync {
type Error: std::error::Error + Send + Sync + 'static;
fn lookup(
&self,
request: &GetHostedWitnessHistoryProofRequest,
) -> impl Future<Output = Result<Option<GetHostedWitnessHistoryProofResponse>, Self::Error>> + Send;
}
pub fn request(
signed: &SignedHostedWitnessStatementV1,
) -> Result<GetHostedWitnessHistoryProofRequest, Reject> {
let statement = signed.body.as_ref().ok_or(Reject::Canonical)?;
let request = GetHostedWitnessHistoryProofRequest {
executor_id: statement.executor_id.clone(),
statement_leaf_digest: witness_trust::leaf_digest(
statement.purpose,
&canonical(statement)?,
&signed.signature,
)?,
};
witness_trust::validate_lookup(&request)?;
Ok(request)
}
pub fn replace_receiver_metadata(
original: &mut crate::contract::ImportPublicProofBundleV1,
refreshed: crate::contract::ImportPublicProofBundleV1,
) -> Result<(), Reject> {
api::import_authority::validate_public_bundle(&refreshed)?;
let mut unchanged = refreshed.clone();
unchanged.witness_set = original.witness_set.clone();
unchanged.history_proofs = original.history_proofs.clone();
if &unchanged != original {
return Err(Reject::Scope);
}
*original = refreshed;
Ok(())
}
pub async fn retrieve<L: HistoryProofLookup>(
lookup: &L,
set: &VerifiedWitnessSet,
signed: &SignedHostedWitnessStatementV1,
now_ms: i64,
) -> Result<HostedWitnessHistoryProofV1, Error<L::Error>> {
let request = request(signed)?;
let response = lookup
.lookup(&request)
.await
.map_err(Error::Lookup)?
.ok_or(Error::NotFound)?;
if response.encoded_len() > witness_trust::MAX_PROOF_BYTES {
return Err(Reject::Bounds.into());
}
let proof = response.proof.ok_or(Reject::Proof)?;
if proof.siblings.len() > witness_trust::MAX_SIBLINGS {
return Err(Reject::Bounds.into());
}
witness_trust::resolve_statement(set, signed, Some(&proof), false, now_ms)?;
Ok(proof)
}
pub async fn complete_bundle<L: HistoryProofLookup>(
lookup: &L,
set: &VerifiedWitnessSet,
bundle: &mut crate::contract::ImportPublicProofBundleV1,
now_ms: i64,
) -> Result<(), Error<L::Error>> {
api::import_authority::validate_public_bundle(bundle)?;
let proofs = complete_proofs(
lookup,
set,
&bundle.statements,
&bundle.history_proofs,
now_ms,
)
.await?;
let mut completed = bundle.clone();
completed.history_proofs = proofs;
api::import_authority::validate_public_bundle(&completed)?;
*bundle = completed;
Ok(())
}
pub fn replace_native_receiver_metadata(
original: &mut crate::contract::NativePublicProofBundleV1,
refreshed: crate::contract::NativePublicProofBundleV1,
) -> Result<(), Reject> {
api::native_witness::validate_public_bundle(&refreshed)?;
let mut unchanged = refreshed.clone();
unchanged.witness_set = original.witness_set.clone();
unchanged.history_proofs = original.history_proofs.clone();
if &unchanged != original {
return Err(Reject::Scope);
}
*original = refreshed;
Ok(())
}
pub async fn complete_native_bundle<L: HistoryProofLookup>(
lookup: &L,
set: &VerifiedWitnessSet,
bundle: &mut crate::contract::NativePublicProofBundleV1,
now_ms: i64,
) -> Result<(), Error<L::Error>> {
api::native_witness::validate_public_bundle(bundle)?;
let proofs = complete_proofs(
lookup,
set,
&bundle.statements,
&bundle.history_proofs,
now_ms,
)
.await?;
let mut completed = bundle.clone();
completed.history_proofs = proofs;
api::native_witness::validate_public_bundle(&completed)?;
*bundle = completed;
Ok(())
}
async fn complete_proofs<L: HistoryProofLookup>(
lookup: &L,
set: &VerifiedWitnessSet,
statements: &[SignedHostedWitnessStatementV1],
original_proofs: &[HostedWitnessHistoryProofV1],
now_ms: i64,
) -> Result<Vec<HostedWitnessHistoryProofV1>, Error<L::Error>> {
let mut proofs = original_proofs.to_vec();
let mut used = std::collections::BTreeSet::new();
for signed in statements {
let body = signed.body.as_ref().ok_or(Reject::Canonical)?;
match witness_trust::resolve_statement(set, signed, None, false, now_ms) {
Ok(_) => continue,
Err(Reject::Proof) => {}
Err(error) => return Err(error.into()),
}
let mut matched = None;
for (index, proof) in proofs.iter().enumerate().filter(|(_, proof)| {
proof.executor_id == body.executor_id && proof.purpose == body.purpose
}) {
if witness_trust::resolve_statement(set, signed, Some(proof), false, now_ms).is_ok()
&& matched.replace(index).is_some()
{
return Err(Reject::Proof.into());
}
}
let index = if let Some(index) = matched {
index
} else {
let proof = retrieve(lookup, set, signed, now_ms).await?;
proofs.push(proof);
if proofs.len() > 1024 {
return Err(Reject::Bounds.into());
}
proofs.len() - 1
};
used.insert(index);
}
if used.len() != proofs.len() {
return Err(Reject::Proof.into());
}
Ok(proofs)
}