Skip to content

Commit

Permalink
Rename CannotCidOr, API for creating powerlines & more (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
matheus23 committed Mar 28, 2024
1 parent 53c49d2 commit a00db2d
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 204 deletions.
100 changes: 48 additions & 52 deletions src/crypto/signature/envelope.rs
@@ -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
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
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
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
Expand Up @@ -4,4 +4,4 @@ mod memory;
mod traits;

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

0 comments on commit a00db2d

Please sign in to comment.