use ciborium::value::Value;
use super::{
ALG_EDDSA, CONSISTENCY_CONTENT_TYPE, CONTENT_TYPE, COSE_SIGN1_TAG, DIGEST_LEN,
HEADER_LABEL_ALG, HEADER_LABEL_CONTENT_TYPE, HEADER_LABEL_KID, HEADER_LABEL_VDP,
HEADER_LABEL_VDS, MAX_ARTIFACT_LEN, MAX_PATH_ELEMENTS, PROOF_TYPE_CONSISTENCY,
PROOF_TYPE_INCLUSION, VDS_RFC9162_SHA256,
};
use crate::error::{TrustError, TrustResult};
pub(crate) struct DecodedFields {
pub(crate) anchor_public_key: [u8; DIGEST_LEN],
pub(crate) tree_size: u64,
pub(crate) leaf_index: u64,
pub(crate) inclusion_path: Vec<[u8; DIGEST_LEN]>,
pub(crate) signature: [u8; 64],
}
fn reject() -> TrustError {
TrustError::ReceiptVerification
}
fn parse_value(bytes: &[u8]) -> TrustResult<Value> {
ciborium::de::from_reader(bytes).map_err(|_err| reject())
}
fn fixed_bytes<const N: usize>(value: &Value) -> TrustResult<[u8; N]> {
let Value::Bytes(bytes) = value else {
return Err(reject());
};
bytes.as_slice().try_into().map_err(|_err| reject())
}
fn require_integer(value: &Value, expected: i128) -> TrustResult<()> {
let Value::Integer(int) = value else {
return Err(reject());
};
if i128::from(*int) == expected {
Ok(())
} else {
Err(reject())
}
}
fn unsigned(value: &Value) -> TrustResult<u64> {
let Value::Integer(int) = value else {
return Err(reject());
};
u64::try_from(i128::from(*int)).map_err(|_err| reject())
}
fn decode_protected(
protected_raw: &[u8],
expected_content_type: &str,
) -> TrustResult<[u8; DIGEST_LEN]> {
let Value::Map(protected) = parse_value(protected_raw)? else {
return Err(reject());
};
let [(alg_key, alg), (ct_key, ct), (kid_key, kid), (vds_key, vds)] = protected.as_slice()
else {
return Err(reject());
};
require_integer(alg_key, i128::from(HEADER_LABEL_ALG))?;
require_integer(alg, i128::from(ALG_EDDSA))?;
require_integer(ct_key, i128::from(HEADER_LABEL_CONTENT_TYPE))?;
let Value::Text(content_type) = ct else {
return Err(reject());
};
if content_type != expected_content_type {
return Err(reject());
}
require_integer(kid_key, i128::from(HEADER_LABEL_KID))?;
let anchor_public_key: [u8; DIGEST_LEN] = fixed_bytes(kid)?;
require_integer(vds_key, i128::from(HEADER_LABEL_VDS))?;
require_integer(vds, i128::from(VDS_RFC9162_SHA256))?;
Ok(anchor_public_key)
}
struct DecodedProof {
tree_size: u64,
leaf_index: u64,
inclusion_path: Vec<[u8; DIGEST_LEN]>,
}
fn decode_unprotected(unprotected_item: &Value) -> TrustResult<DecodedProof> {
let Value::Map(unprotected) = unprotected_item else {
return Err(reject());
};
let [(vdp_key, vdp)] = unprotected.as_slice() else {
return Err(reject());
};
require_integer(vdp_key, i128::from(HEADER_LABEL_VDP))?;
let Value::Map(proofs_by_type) = vdp else {
return Err(reject());
};
let [(proof_type, proofs)] = proofs_by_type.as_slice() else {
return Err(reject());
};
require_integer(proof_type, i128::from(PROOF_TYPE_INCLUSION))?;
let Value::Array(proofs) = proofs else {
return Err(reject());
};
let [proof_item] = proofs.as_slice() else {
return Err(reject());
};
let Value::Bytes(proof_raw) = proof_item else {
return Err(reject());
};
let Value::Array(proof) = parse_value(proof_raw)? else {
return Err(reject());
};
let [size_item, index_item, path_item] = proof.as_slice() else {
return Err(reject());
};
let tree_size = unsigned(size_item)?;
let leaf_index = unsigned(index_item)?;
let Value::Array(path) = path_item else {
return Err(reject());
};
if path.len() > MAX_PATH_ELEMENTS {
return Err(reject());
}
let mut inclusion_path = Vec::with_capacity(path.len());
for node in path {
inclusion_path.push(fixed_bytes::<DIGEST_LEN>(node)?);
}
Ok(DecodedProof {
tree_size,
leaf_index,
inclusion_path,
})
}
pub(crate) struct DecodedConsistencyFields {
pub(crate) anchor_public_key: [u8; DIGEST_LEN],
pub(crate) tree_size_1: u64,
pub(crate) tree_size_2: u64,
pub(crate) consistency_path: Vec<[u8; DIGEST_LEN]>,
pub(crate) signature: [u8; 64],
}
struct DecodedConsistencyProof {
tree_size_1: u64,
tree_size_2: u64,
consistency_path: Vec<[u8; DIGEST_LEN]>,
}
fn decode_consistency_unprotected(
unprotected_item: &Value,
) -> TrustResult<DecodedConsistencyProof> {
let Value::Map(unprotected) = unprotected_item else {
return Err(reject());
};
let [(vdp_key, vdp)] = unprotected.as_slice() else {
return Err(reject());
};
require_integer(vdp_key, i128::from(HEADER_LABEL_VDP))?;
let Value::Map(proofs_by_type) = vdp else {
return Err(reject());
};
let [(proof_type, proofs)] = proofs_by_type.as_slice() else {
return Err(reject());
};
require_integer(proof_type, i128::from(PROOF_TYPE_CONSISTENCY))?;
let Value::Array(proofs) = proofs else {
return Err(reject());
};
let [proof_item] = proofs.as_slice() else {
return Err(reject());
};
let Value::Bytes(proof_raw) = proof_item else {
return Err(reject());
};
let Value::Array(proof) = parse_value(proof_raw)? else {
return Err(reject());
};
let [size_1_item, size_2_item, path_item] = proof.as_slice() else {
return Err(reject());
};
let tree_size_1 = unsigned(size_1_item)?;
let tree_size_2 = unsigned(size_2_item)?;
let Value::Array(path) = path_item else {
return Err(reject());
};
if path.len() > MAX_PATH_ELEMENTS {
return Err(reject());
}
let mut consistency_path = Vec::with_capacity(path.len());
for node in path {
consistency_path.push(fixed_bytes::<DIGEST_LEN>(node)?);
}
Ok(DecodedConsistencyProof {
tree_size_1,
tree_size_2,
consistency_path,
})
}
pub(crate) fn decode_consistency_fields(bytes: &[u8]) -> TrustResult<DecodedConsistencyFields> {
if bytes.len() > MAX_ARTIFACT_LEN {
return Err(reject());
}
let Value::Tag(COSE_SIGN1_TAG, boxed) = parse_value(bytes)? else {
return Err(reject());
};
let Value::Array(items) = *boxed else {
return Err(reject());
};
let [
protected_item,
unprotected_item,
payload_item,
signature_item,
] = items.as_slice()
else {
return Err(reject());
};
if !matches!(payload_item, Value::Null) {
return Err(reject());
}
let signature: [u8; 64] = fixed_bytes(signature_item)?;
let Value::Bytes(protected_raw) = protected_item else {
return Err(reject());
};
let anchor_public_key = decode_protected(protected_raw, CONSISTENCY_CONTENT_TYPE)?;
let proof = decode_consistency_unprotected(unprotected_item)?;
Ok(DecodedConsistencyFields {
anchor_public_key,
tree_size_1: proof.tree_size_1,
tree_size_2: proof.tree_size_2,
consistency_path: proof.consistency_path,
signature,
})
}
pub(crate) fn decode_fields(bytes: &[u8]) -> TrustResult<DecodedFields> {
if bytes.len() > MAX_ARTIFACT_LEN {
return Err(reject());
}
let Value::Tag(COSE_SIGN1_TAG, boxed) = parse_value(bytes)? else {
return Err(reject());
};
let Value::Array(items) = *boxed else {
return Err(reject());
};
let [
protected_item,
unprotected_item,
payload_item,
signature_item,
] = items.as_slice()
else {
return Err(reject());
};
if !matches!(payload_item, Value::Null) {
return Err(reject());
}
let signature: [u8; 64] = fixed_bytes(signature_item)?;
let Value::Bytes(protected_raw) = protected_item else {
return Err(reject());
};
let anchor_public_key = decode_protected(protected_raw, CONTENT_TYPE)?;
let proof = decode_unprotected(unprotected_item)?;
Ok(DecodedFields {
anchor_public_key,
tree_size: proof.tree_size,
leaf_index: proof.leaf_index,
inclusion_path: proof.inclusion_path,
signature,
})
}