use crate::hash::Hash;
use crate::identity::VerifyingKey;
use crate::operation::OperationError;
use crate::traits::{Chain, Digest, Offchain, Provenance};
pub fn validate_operation<T>(operation: &T) -> Result<(), OperationError>
where
T: Provenance<VerifyingKey> + Chain<Hash> + Offchain<Hash>,
{
validate_header::<T>(operation)?;
let claimed_payload_size = operation.payload_size();
let claimed_payload_hash: Option<Hash> = match claimed_payload_size {
0 => None,
_ => {
let hash = operation
.payload_hash()
.ok_or(OperationError::MissingPayloadHash)?;
Some(hash)
}
};
if let Some(body) = &operation.payload()
&& (claimed_payload_hash != Some(body.hash()) || claimed_payload_size != body.size())
{
return Err(OperationError::PayloadMismatch);
}
Ok(())
}
pub fn validate_header<T>(header: &T) -> Result<(), OperationError>
where
T: Provenance<VerifyingKey> + Chain<Hash> + Offchain<Hash>,
{
if !header.verify() {
return Err(OperationError::SignatureMismatch);
}
if (header.payload_hash().is_some() && header.payload_size() == 0)
|| (header.payload_hash().is_none() && header.payload_size() > 0)
{
return Err(OperationError::InconsistentPayloadInfo);
}
if header.backlink().is_some() && header.seq_num() == 0 {
return Err(OperationError::SeqNumMismatch);
}
if header.backlink().is_none() && header.seq_num() > 0 {
return Err(OperationError::BacklinkMissing);
}
Ok(())
}
pub fn validate_backlink<T>(past_header: &T, header: &T) -> Result<(), OperationError>
where
T: Provenance<VerifyingKey> + Digest<Hash> + Chain<Hash>,
{
if past_header.author() != header.author() {
return Err(OperationError::TooManyAuthors);
}
if past_header.seq_num() + 1 != header.seq_num() {
return Err(OperationError::SeqNumNonIncremental(
past_header.seq_num() + 1,
header.seq_num(),
));
}
match header.backlink() {
Some(backlink) => {
if past_header.hash() != backlink {
return Err(OperationError::BacklinkMismatch);
}
}
None => {
return Err(OperationError::BacklinkMissing);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::hash::Hash;
use crate::identity::SigningKey;
use crate::operation::{Body, Header, Operation, OperationError};
use super::{validate_backlink, validate_header, validate_operation};
#[test]
fn sign_and_verify() {
let signing_key = SigningKey::generate();
let body = Body::from_bytes("Hello, Sloth!".as_bytes());
type CustomExtensions = (u32, String);
let header = Header::<CustomExtensions>::builder()
.body(&body)
.build(&signing_key, (42, "penguin".to_string()));
assert!(header.verify());
let operation = Operation {
hash: header.hash(),
header,
body: Some(body),
};
assert!(validate_operation::<Operation<_>>(&operation).is_ok());
}
#[test]
fn valid_backlink_header() {
let signing_key = SigningKey::generate();
let header_0 = Header::builder().build(&signing_key, ());
assert!(validate_header(&header_0).is_ok());
let header_1 = Header::builder()
.chain(1, header_0.hash())
.build(&signing_key, ());
assert!(validate_header(&header_1).is_ok());
assert!(validate_backlink(&header_0, &header_1).is_ok());
}
#[test]
fn invalid_operations() {
let signing_key = SigningKey::generate();
let body = Body::from_bytes("Hello, Sloth!".as_bytes());
let header_base = Header::<()> {
verifying_key: signing_key.verifying_key(),
payload_size: body.size(),
payload_hash: Some(body.hash()),
..Default::default()
};
let mut header = header_base.clone();
header.verifying_key = SigningKey::generate().verifying_key();
header.sign(&signing_key);
std::assert_matches!(
validate_header(&header),
Err(OperationError::SignatureMismatch)
);
let mut header = header_base.clone();
header.seq_num = 1;
header.sign(&signing_key);
std::assert_matches!(
validate_header(&header),
Err(OperationError::BacklinkMissing)
);
let mut header = header_base.clone();
header.backlink = Some(Hash::digest(vec![4, 5, 6]));
header.sign(&signing_key);
std::assert_matches!(
validate_header(&header),
Err(OperationError::SeqNumMismatch)
);
let mut header = header_base.clone();
header.payload_size = 11;
header.sign(&signing_key);
std::assert_matches!(
validate_operation(&Operation {
hash: header.hash(),
header,
body: Some(body.clone()),
}),
Err(OperationError::PayloadMismatch)
);
let mut header = header_base.clone();
header.payload_hash = Some(Hash::digest(vec![4, 5, 6]));
header.sign(&signing_key);
std::assert_matches!(
validate_operation(&Operation {
hash: header.hash(),
header,
body: Some(body.clone()),
}),
Err(OperationError::PayloadMismatch)
);
}
}