Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename CannotCidOr, API for creating powerlines & more #18

Merged
merged 10 commits into from
Mar 28, 2024
100 changes: 48 additions & 52 deletions src/crypto/signature/envelope.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::ability::arguments::Named;
use crate::crypto::varsig::Header;
use crate::{capsule::Capsule, crypto::varsig, did::Did};
use libipld_core::{
cid::Cid,
Expand All @@ -10,6 +11,7 @@ use libipld_core::{
use signature::SignatureEncoding;
use signature::Verifier;
use std::collections::BTreeMap;
use std::io::Write;
use thiserror::Error;

pub trait Envelope: Sized {
Expand All @@ -30,60 +32,61 @@ pub trait Envelope: Sized {
) -> Self;

fn to_ipld_envelope(&self) -> Ipld {
let inner_args: Named<Ipld> = self.payload().clone().into();
let inner_ipld: Ipld = inner_args.into();

let wrapped_payload: Ipld =
BTreeMap::from_iter([(Self::Payload::TAG.into(), inner_ipld)]).into();

let header_bytes: Vec<u8> = (*self.varsig_header()).clone().into();
let wrapped_payload = Self::wrap_payload(self.payload().clone());
let header_bytes: Vec<u8> = self.varsig_header().clone().into();
let header: Ipld = vec![header_bytes.into(), wrapped_payload].into();
let sig_bytes: Ipld = self.signature().to_vec().into();

vec![sig_bytes.into(), header].into()
}

fn wrap_payload(payload: Self::Payload) -> Ipld {
let inner_args: Named<Ipld> = payload.into();
let inner_ipld: Ipld = inner_args.into();
BTreeMap::from_iter([(Self::Payload::TAG.into(), inner_ipld)]).into()
}

fn try_from_ipld_envelope(
ipld: Ipld,
) -> Result<Self, FromIpldError<<Self::Payload as TryFrom<Named<Ipld>>>::Error>> {
if let Ipld::List(list) = ipld {
if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() {
if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() {
if let (1, Some(Ipld::Map(inner))) = (
btree.len(),
btree.get(<Self::Payload as Capsule>::TAG.into()),
) {
let payload = Self::Payload::try_from(Named(inner.clone()))
.map_err(FromIpldError::CannotParsePayload)?;

let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice())
.map_err(|_| FromIpldError::CannotParseVarsigHeader)?;

let signature = <Self::DID as Did>::Signature::try_from(sig.as_slice())
.map_err(|_| FromIpldError::CannotParseSignature)?;

Ok(Self::construct(varsig_header, signature, payload))
} else {
Err(FromIpldError::InvalidPayloadCapsule)
}
} else {
Err(FromIpldError::InvalidVarsigContainer)
}
} else {
Err(FromIpldError::InvalidSignatureContainer)
}
} else {
Err(FromIpldError::InvalidSignatureContainer)
}
let Ipld::List(list) = ipld else {
return Err(FromIpldError::InvalidSignatureContainer);
};

let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() else {
return Err(FromIpldError::InvalidSignatureContainer);
};

let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() else {
return Err(FromIpldError::InvalidVarsigContainer);
};

let (1, Some(Ipld::Map(inner))) = (
btree.len(),
btree.get(<Self::Payload as Capsule>::TAG.into()),
) else {
return Err(FromIpldError::InvalidPayloadCapsule);
};

let payload = Self::Payload::try_from(Named(inner.clone()))
.map_err(FromIpldError::CannotParsePayload)?;

let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice())
.map_err(|_| FromIpldError::CannotParseVarsigHeader)?;

let signature = <Self::DID as Did>::Signature::try_from(sig.as_slice())
.map_err(|_| FromIpldError::CannotParseSignature)?;

Ok(Self::construct(varsig_header, signature, payload))
}

fn varsig_encode(self, w: &mut Vec<u8>) -> Result<(), libipld_core::error::Error>
fn varsig_encode<W: Write>(&self, mut w: W) -> Result<W, libipld_core::error::Error>
where
Ipld: Encode<Self::Encoder> + From<Self>,
Ipld: Encode<Self::Encoder>,
{
let codec = varsig::header::Header::codec(self.varsig_header()).clone();
let ipld = Ipld::from(self);
ipld.encode(codec, w)
let codec = self.varsig_header().codec().clone();
self.to_ipld_envelope().encode(codec, &mut w)?;
Ok(w)
}

/// Attempt to sign some payload with a given signer.
Expand Down Expand Up @@ -132,14 +135,9 @@ pub trait Envelope: Sized {
Ipld: Encode<Self::Encoder>,
Named<Ipld>: From<Self::Payload>,
{
let ipld: Ipld = BTreeMap::from_iter([(
Self::Payload::TAG.into(),
Named::<Ipld>::from(payload.clone()).into(),
)])
.into();

let ipld = Self::wrap_payload(payload.clone());
let mut buffer = vec![];
ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer)
ipld.encode(*varsig_header.codec(), &mut buffer)
.map_err(SignError::PayloadEncodingError)?;

let signature =
Expand Down Expand Up @@ -188,11 +186,9 @@ pub trait Envelope: Sized {
where
Ipld: Encode<Self::Encoder>,
{
let codec = varsig::header::Header::codec(self.varsig_header()).clone();
let mut ipld_buffer = vec![];
self.to_ipld_envelope().encode(codec, &mut ipld_buffer)?;
let encoded = self.varsig_encode(Vec::new())?;
let multihash = Code::Sha2_256.digest(&encoded);

let multihash = Code::Sha2_256.digest(&ipld_buffer);
Ok(Cid::new_v1(
varsig::header::Header::codec(self.varsig_header())
.clone()
Expand Down
23 changes: 16 additions & 7 deletions src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@ mod payload;
pub use agent::Agent;
pub use payload::*;

use crate::ability::arguments::Named;
use crate::{
ability::arguments::Named,
capsule::Capsule,
crypto::{signature::Envelope, varsig, Nonce},
did::{self, Did},
time::{TimeBoundError, Timestamp},
};
use libipld_core::link::Link;
use libipld_core::{codec::Codec, ipld::Ipld};
use libipld_core::{codec::Codec, ipld::Ipld, link::Link};
use policy::Predicate;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -83,15 +82,25 @@ impl<DID: Did, V: varsig::Header<C>, C: Codec> Delegation<DID, V, C> {
}

/// Retrive the `subject` of a [`Delegation`]
pub fn subject(&self) -> &Option<DID> {
&self.payload.subject
pub fn subject(&self) -> Option<&DID> {
self.payload.subject.as_ref()
}

/// Retrive the `audience` of a [`Delegation`]
pub fn audience(&self) -> &DID {
&self.payload.audience
}

/// Retrieve the `via` of a [`Delegation`]
pub fn via(&self) -> Option<&DID> {
self.payload.via.as_ref()
}

/// Retrieve the `command` of a [`Delegation`]
pub fn command(&self) -> &String {
&self.payload.command
}

/// Retrive the `policy` of a [`Delegation`]
pub fn policy(&self) -> &Vec<Predicate> {
&self.payload.policy
Expand All @@ -113,8 +122,8 @@ impl<DID: Did, V: varsig::Header<C>, C: Codec> Delegation<DID, V, C> {
}

/// Retrive the `expiration` of a [`Delegation`]
pub fn expiration(&self) -> &Timestamp {
&self.payload.expiration
pub fn expiration(&self) -> Option<&Timestamp> {
self.payload.expiration.as_ref()
}

pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> {
Expand Down
58 changes: 24 additions & 34 deletions src/delegation/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,57 +58,47 @@ where
pub fn delegate(
&self,
audience: DID,
subject: &DID,
subject: Option<&DID>,
via: Option<DID>,
command: String,
new_policy: Vec<Predicate>,
metadata: BTreeMap<String, Ipld>,
expiration: Timestamp,
expiration: Option<Timestamp>,
not_before: Option<Timestamp>,
now: SystemTime,
varsig_header: V,
) -> Result<Delegation<DID, V, C>, DelegateError<S::DelegationStoreError>> {
) -> Result<Delegation<DID, V, C>, DelegateError<S::Error>> {
let mut salt = self.did.clone().to_string().into_bytes();
let nonce = Nonce::generate_16();

if *subject == self.did {
let payload: Payload<DID> = Payload {
issuer: self.did.clone(),
audience,
subject: Some(subject.clone()),
via,
command,
metadata,
nonce,
expiration: expiration.into(),
not_before: not_before.map(Into::into),
policy: new_policy,
};

return Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME"));
}

let proofs = &self
.store
.get_chain(&self.did, &subject, &command, vec![], now)
.map_err(DelegateError::StoreError)?
.ok_or(DelegateError::ProofsNotFound)?;
let to_delegate = proofs.first().1.payload();

let mut policy = to_delegate.policy.clone();
policy.append(&mut new_policy.clone());
let (subject, policy) = match subject {
Some(subject) if *subject == self.did => (Some(subject.clone()), new_policy),
None => (None, new_policy),
Some(subject) => {
let proofs = &self
.store
.get_chain(&self.did, &subject, &command, vec![], now)
.map_err(DelegateError::StoreError)?
.ok_or(DelegateError::ProofsNotFound)?;
let to_delegate = proofs.first().1.payload();

let mut policy = to_delegate.policy.clone();
policy.extend(new_policy);
(Some(subject.clone()), policy)
}
};

let payload: Payload<DID> = Payload {
issuer: self.did.clone(),
audience,
subject: Some(subject.clone()),
subject,
via,
command,
policy,
metadata,
nonce,
expiration: expiration.into(),
not_before: not_before.map(Into::into),
expiration,
not_before,
policy,
};

Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME"))
Expand All @@ -118,7 +108,7 @@ where
&self,
cid: Cid, // FIXME remove and generate from the capsule header?
delegation: Delegation<DID, V, C>,
) -> Result<(), ReceiveError<S::DelegationStoreError, DID>> {
) -> Result<(), ReceiveError<S::Error, DID>> {
if self.store.get(&cid).is_ok() {
return Ok(());
}
Expand Down
23 changes: 16 additions & 7 deletions src/delegation/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ pub struct Payload<DID: Did> {
/// given as a [Unix timestamp].
///
/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
pub expiration: Timestamp,
#[builder(default)]
pub expiration: Option<Timestamp>,

/// An optional earliest wall-clock time that the UCAN is valid from,
/// given as a [Unix timestamp].
Expand All @@ -91,8 +92,10 @@ impl<DID: Did> Payload<DID> {
pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> {
let ts_now = &Timestamp::postel(now);

if &self.expiration < ts_now {
return Err(TimeBoundError::Expired);
if let Some(ref exp) = self.expiration {
if exp < ts_now {
return Err(TimeBoundError::Expired);
}
}

if let Some(ref nbf) = self.not_before {
Expand Down Expand Up @@ -190,8 +193,11 @@ where
},
"exp" => match ipld {
Ipld::Integer(i) => {
expiration = Some(Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?)
expiration = Some(Some(
Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?,
))
}
Ipld::Null => expiration = Some(None),
bad => return Err(ParseError::WrongTypeForField("exp".to_string(), bad)),
},
"nbf" => match ipld {
Expand Down Expand Up @@ -308,7 +314,10 @@ impl<DID: Did> From<Payload<DID>> for Named<Ipld> {
Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect())
}),
("nonce".to_string(), payload.nonce.into()),
("exp".to_string(), payload.expiration.into()),
(
"exp".to_string(),
payload.expiration.map_or(Ipld::Null, |e| e.into()),
),
]);

if let Some(subject) = payload.subject {
Expand Down Expand Up @@ -348,7 +357,7 @@ where
DID::arbitrary_with(did_args),
String::arbitrary(),
Nonce::arbitrary(),
Timestamp::arbitrary(),
Option::<Timestamp>::arbitrary(),
Option::<Timestamp>::arbitrary(),
prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..5).prop_map(|m| {
m.into_iter()
Expand Down Expand Up @@ -442,7 +451,7 @@ mod tests {
prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone()));
prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect()));
prop_assert_eq!(nonce.unwrap(), &payload.nonce.into());
prop_assert_eq!(exp.unwrap(), &payload.expiration.into());
prop_assert_eq!(exp.unwrap(), &payload.expiration.map_or(Ipld::Null, |e| e.into()));

// Optional Fields
match (payload.subject, named.get("sub")) {
Expand Down
2 changes: 1 addition & 1 deletion src/delegation/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ mod memory;
mod traits;

pub use memory::MemoryStore;
pub use traits::Store;
pub use traits::*;
Loading
Loading