Skip to content

Commit

Permalink
Builder + required pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
expede committed Apr 2, 2024
1 parent a8d1b1d commit 90f7fd7
Show file tree
Hide file tree
Showing 9 changed files with 555 additions and 187 deletions.
25 changes: 1 addition & 24 deletions src/crypto/signature/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,29 +89,6 @@ pub trait Envelope: Sized {
Ok(w)
}

/// Attempt to sign some payload with a given signer.
///
/// # Arguments
///
/// * `signer` - The signer to use to sign the payload.
/// * `payload` - The payload to sign.
///
/// # Errors
///
/// * [`SignError`] - the payload can't be encoded or the signature fails.
// FIXME ported
fn try_sign(
signer: &<Self::DID as Did>::Signer,
varsig_header: Self::VarsigHeader,
payload: Self::Payload,
) -> Result<Self, SignError>
where
Ipld: Encode<Self::Encoder>,
Named<Ipld>: From<Self::Payload>,
{
Self::try_sign_generic(signer, varsig_header, payload)
}

/// Attempt to sign some payload with a given signer and specific codec.
///
/// # Arguments
Expand All @@ -126,7 +103,7 @@ pub trait Envelope: Sized {
///
/// # Example
///
fn try_sign_generic(
fn try_sign(
signer: &<Self::DID as Did>::Signer,
varsig_header: Self::VarsigHeader,
payload: Self::Payload,
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/varsig/header/preset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl Header<encoding::Preset> for Preset {
Preset::Es256(es256) => es256.codec(),
Preset::Es256k(es256k) => es256k.codec(),
Preset::Es512(es512) => es512.codec(),
// Preset::Bls
// Preset::Bls FIXME
}
}
}
8 changes: 4 additions & 4 deletions src/crypto/varsig/header/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ use libipld_core::codec::{Codec, Encode};
use signature::Verifier;
use thiserror::Error;

pub trait Header<Enc: Codec>: for<'a> TryFrom<&'a [u8]> + Into<Vec<u8>> {
pub trait Header<C: Codec>: for<'a> TryFrom<&'a [u8]> + Into<Vec<u8>> {
type Signature: signature::SignatureEncoding;
type Verifier: signature::Verifier<Self::Signature>;

fn codec(&self) -> &Enc;
fn codec(&self) -> &C;

fn encode_payload<T: Encode<Enc>, Buf: std::io::Write>(
fn encode_payload<T: Encode<C>, Buf: std::io::Write>(
&self,
payload: T,
buffer: &mut Buf,
) -> Result<(), libipld_core::error::Error> {
payload.encode(Self::codec(self).clone(), buffer)
}

fn try_verify<'a, T: Encode<Enc>>(
fn try_verify<'a, T: Encode<C>>(
&self,
verifier: &'a Self::Verifier,
signature: &'a Self::Signature,
Expand Down
255 changes: 252 additions & 3 deletions src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ use crate::{
did::{self, Did},
time::{TimeBoundError, Timestamp},
};
use libipld_core::{codec::Codec, ipld::Ipld, link::Link};
use core::str::FromStr;
use libipld_core::{
codec::{Codec, Encode},
ipld::Ipld,
link::Link,
};
use policy::Predicate;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
Expand All @@ -49,6 +54,167 @@ pub struct Delegation<
_marker: std::marker::PhantomData<C>,
}

pub struct DelegationRequired<
DID: Did = did::preset::Verifier,
V: varsig::Header<C> = varsig::header::Preset,
C: Codec = varsig::encoding::Preset,
> {
pub subject: Subject<DID>,
pub issuer: DID,
pub audience: DID,
pub command: String,

pub codec: C,
pub varsig_header: V,
pub signer: DID::Signer,
}

pub struct DelegationBuilder<
DID: Did = did::preset::Verifier,
V: varsig::Header<C> = varsig::header::Preset,
C: Codec = varsig::encoding::Preset,
> {
pub subject: Subject<DID>,
pub issuer: DID,
pub audience: DID,
pub command: String,

pub codec: C,
pub varsig_header: V,
pub signer: DID::Signer,

pub via: Option<DID>,
pub policy: Vec<Predicate>,
pub metadata: BTreeMap<String, Ipld>,
pub nonce: Option<Nonce>,
pub expiration: Option<Timestamp>,
pub not_before: Option<Timestamp>,
}

impl<DID: Did + Clone, V: varsig::Header<C> + Clone, C: Codec> DelegationRequired<DID, V, C>
where
Ipld: Encode<C>,
{
pub fn to_builder(self) -> DelegationBuilder<DID, V, C> {
DelegationBuilder {
subject: self.subject,
issuer: self.issuer,
audience: self.audience,
command: self.command,

codec: self.codec,
varsig_header: self.varsig_header,
signer: self.signer,

via: None,
policy: vec![],
metadata: Default::default(),
nonce: None,
expiration: None,
not_before: None,
}
}

pub fn try_finalize(self) -> Result<Delegation<DID, V, C>, crate::crypto::signature::SignError>
where
<DID as FromStr>::Err: std::fmt::Debug,
{
self.to_builder().try_finalize()
}

/// Set the `via` field of the [`Delegation`]
pub fn with_via(self, via: DID) -> DelegationBuilder<DID, V, C> {
let builder = self.to_builder();
builder.with_via(via)
}

pub fn with_policy(self, policy: Vec<Predicate>) -> DelegationBuilder<DID, V, C> {
let builder = self.to_builder();
builder.with_policy(policy)
}

pub fn with_nonce(self, nonce: Nonce) -> DelegationBuilder<DID, V, C> {
let builder = self.to_builder();
builder.with_nonce(nonce)
}

pub fn with_metadata(self, metadata: BTreeMap<String, Ipld>) -> DelegationBuilder<DID, V, C> {
let builder = self.to_builder();
builder.with_metadata(metadata)
}

pub fn with_expiration(self, expiration: Timestamp) -> DelegationBuilder<DID, V, C> {
let builder = self.to_builder();
builder.with_expiration(expiration)
}

pub fn with_not_before(self, not_before: Timestamp) -> DelegationBuilder<DID, V, C> {
let builder = self.to_builder();
builder.with_not_before(not_before)
}
}

impl<DID: Did + Clone, V: varsig::Header<C> + Clone, C: Codec> DelegationBuilder<DID, V, C>
where
Ipld: Encode<C>,
{
pub fn try_finalize(self) -> Result<Delegation<DID, V, C>, crate::crypto::signature::SignError>
where
<DID as FromStr>::Err: std::fmt::Debug,
{
let payload = Payload {
subject: self.subject,
issuer: self.issuer,
audience: self.audience,
via: None,

command: self.command,
policy: vec![],

nonce: Nonce::generate_16(),
metadata: Default::default(),

expiration: None,
not_before: None,
};

Delegation::try_sign(&self.signer, self.varsig_header, payload)
}

pub fn with_via(mut self, via: DID) -> DelegationBuilder<DID, V, C> {
self.via = Some(via);
self
}

pub fn with_policy(mut self, policy: Vec<Predicate>) -> DelegationBuilder<DID, V, C> {
self.policy = policy;
self
}

pub fn with_nonce(mut self, nonce: Nonce) -> DelegationBuilder<DID, V, C> {
self.nonce = Some(nonce);
self
}

pub fn with_metadata(
mut self,
metadata: BTreeMap<String, Ipld>,
) -> DelegationBuilder<DID, V, C> {
self.metadata = metadata;
self
}

pub fn with_expiration(mut self, expiration: Timestamp) -> DelegationBuilder<DID, V, C> {
self.expiration = Some(expiration);
self
}

pub fn with_not_before(mut self, not_before: Timestamp) -> DelegationBuilder<DID, V, C> {
self.not_before = Some(not_before);
self
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct Proof<
DID: Did = did::preset::Verifier,
Expand Down Expand Up @@ -82,8 +248,8 @@ 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.as_ref()
pub fn subject(&self) -> &Subject<DID> {
&self.payload.subject
}

/// Retrive the `audience` of a [`Delegation`]
Expand Down Expand Up @@ -197,3 +363,86 @@ where
Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::varsig::{encoding, header};
use assert_matches::assert_matches;
use rand::thread_rng;
use std::collections::BTreeMap;
use testresult::TestResult;

fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) {
let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng());
let verifier =
crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa(sk.verifying_key()));
let signer = crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(sk));

(verifier, signer)
}

mod required {
use super::*;

fn fixture() -> DelegationRequired {
let (alice, alice_signer) = gen_did();
let (bob, bob_signer) = gen_did();
let (carol, carol_signer) = gen_did();

DelegationRequired {
subject: Subject::Any,
issuer: alice,
audience: bob,
command: "/".to_string(),

signer: alice_signer,
codec: encoding::Preset::DagCbor,
varsig_header: header::Preset::EdDsa(header::EdDsaHeader {
codec: encoding::Preset::DagCbor,
}),
}
}

#[test_log::test]
fn test_finalize_always_works() -> TestResult {
let delegation = fixture().try_finalize();
assert_matches!(delegation, Ok(_));
Ok(())
}
}

mod builder {
use super::*;

fn fixture() -> Result<Delegation, crate::crypto::signature::SignError> {
let (alice, alice_signer) = gen_did();
let (bob, bob_signer) = gen_did();
let (carol, carol_signer) = gen_did();

DelegationRequired {
subject: Subject::Any,
issuer: alice,
audience: bob,
command: "/".to_string(),

signer: alice_signer,
codec: encoding::Preset::DagCbor,
varsig_header: header::Preset::EdDsa(header::EdDsaHeader {
codec: encoding::Preset::DagCbor,
}),
}
.with_via(carol)
.with_policy(vec![])
.with_metadata(BTreeMap::from_iter([("foo".into(), 123.into())]))
.try_finalize()
}

#[test_log::test]
fn test_full_builder() -> TestResult {
let delegation = fixture();
assert_matches!(delegation, Ok(_));
Ok(())
}
}
}
Loading

0 comments on commit 90f7fd7

Please sign in to comment.