use super::check_profile;
use crate::authority::indexed::KvTuple;
use crate::cose::CoseSigned;
use crate::error::RejectReason;
use serde::{Deserialize, Serialize};
mod optional_bytes {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(value: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(bytes) => serde_bytes::Bytes::new(bytes).serialize(serializer),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<serde_bytes::ByteBuf> = Option::deserialize(deserializer)?;
Ok(opt.map(|b| b.into_vec()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Predecessor {
#[serde(rename = "type")]
pub predecessor_type: String,
#[serde(with = "serde_bytes")]
pub hash: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransitionChallenge {
#[serde(with = "serde_bytes")]
pub previous_challenge: Vec<u8>,
#[serde(with = "serde_bytes")]
pub next_challenge: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofOfRelationship {
#[serde(rename = "type")]
pub por_type: String,
#[serde(with = "serde_bytes")]
pub evidence: Vec<u8>,
}
impl ProofOfRelationship {
pub fn sd_jwt(presentation: &str) -> Self {
Self {
por_type: crate::POR_TYPE_SD_JWT.to_string(),
evidence: presentation.as_bytes().to_vec(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BitmapAttenuation {
#[serde(with = "serde_bytes")]
pub remove_bitmap: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContractAdditions {
pub additions: Vec<KvTuple>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct AttenuationsWire {
#[serde(skip_serializing_if = "Option::is_none")]
pub identity_context: Option<BitmapAttenuation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub invariants: Option<BitmapAttenuation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution_contract: Option<ContractAdditions>,
}
impl AttenuationsWire {
pub fn is_empty(&self) -> bool {
self.identity_context.is_none()
&& self.invariants.is_none()
&& self.execution_contract.is_none()
}
pub fn parse(&self) -> Result<crate::authority::attenuation::Attenuations, RejectReason> {
use crate::authority::attenuation::Attenuations;
use crate::authority::bitmap::RemoveBitmap;
Ok(Attenuations {
identity_context: self
.identity_context
.as_ref()
.map(|b| RemoveBitmap::from_bytes(&b.remove_bitmap))
.transpose()?,
invariants: self
.invariants
.as_ref()
.map(|b| RemoveBitmap::from_bytes(&b.remove_bitmap))
.transpose()?,
execution_contract_additions: self
.execution_contract
.as_ref()
.map(|a| a.additions.clone())
.unwrap_or_default(),
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PicTransitionPayload {
pub profile: String,
pub position: u64,
pub predecessor: Predecessor,
pub challenge: TransitionChallenge,
#[serde(skip_serializing_if = "Option::is_none")]
pub attenuations: Option<AttenuationsWire>,
pub proof_of_relationship: ProofOfRelationship,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "optional_bytes"
)]
pub request_digest: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executor_evidence: Option<ciborium::Value>,
}
impl PicTransitionPayload {
pub fn check_profile(&self) -> Result<(), RejectReason> {
check_profile("pic-continuity-transition+cose", &self.profile)
}
}
pub type PicTransitionCose = CoseSigned<PicTransitionPayload>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transition_cbor_roundtrip() {
let t = PicTransitionPayload {
profile: crate::PROFILE_0_2.into(),
position: 1,
predecessor: Predecessor {
predecessor_type: crate::PREDECESSOR_TYPE_PCA.into(),
hash: vec![0xAA; 32],
},
challenge: TransitionChallenge {
previous_challenge: b"c0".to_vec(),
next_challenge: b"c1".to_vec(),
},
attenuations: Some(AttenuationsWire {
invariants: Some(BitmapAttenuation {
remove_bitmap: vec![0x01],
}),
..Default::default()
}),
proof_of_relationship: ProofOfRelationship {
por_type: crate::POR_TYPE_SD_JWT.into(),
evidence: b"<sd-jwt presentation bytes>".to_vec(),
},
request_digest: None,
executor_evidence: None,
};
let mut buf = Vec::new();
ciborium::into_writer(&t, &mut buf).unwrap();
let decoded: PicTransitionPayload = ciborium::from_reader(buf.as_slice()).unwrap();
assert_eq!(t, decoded);
let parsed = decoded.attenuations.unwrap().parse().unwrap();
assert_eq!(parsed.invariants.unwrap().indices(), vec![0]);
}
#[test]
fn sd_jwt_constructor_keeps_exact_bytes() {
let presentation = "<issuer-signed jwt>~<disclosure>~";
let por = ProofOfRelationship::sd_jwt(presentation);
assert_eq!(por.por_type, crate::POR_TYPE_SD_JWT);
assert_eq!(por.evidence, presentation.as_bytes());
}
}