Skip to content

Commit

Permalink
feat!: Update fct/ucv layout for 0.10.0 spec (#108)
Browse files Browse the repository at this point in the history
* 'fct' changed from array to map of JSON.
* 'ucv' moved from header to payload.
  • Loading branch information
jsantell authored Jun 6, 2023
1 parent 4511bd3 commit ae19741
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 35 deletions.
19 changes: 11 additions & 8 deletions ucan/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::BTreeMap;

use crate::{
capability::{
proof::ProofDelegationSemantics, Action, Capability, CapabilityIpld, CapabilitySemantics,
Expand All @@ -6,15 +8,14 @@ use crate::{
crypto::KeyMaterial,
serde::Base64Encode,
time::now,
ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION},
ucan::{FactsMap, Ucan, UcanHeader, UcanPayload, UCAN_VERSION},
};
use anyhow::{anyhow, Result};
use base64::Engine;
use cid::multihash::Code;
use log::warn;
use rand::Rng;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;

/// A signable is a UCAN that has all the state it needs in order to be signed,
/// but has not yet been signed.
Expand All @@ -33,7 +34,7 @@ where
pub expiration: u64,
pub not_before: Option<u64>,

pub facts: Vec<Value>,
pub facts: FactsMap,
pub proofs: Vec<String>,
pub add_nonce: bool,
}
Expand All @@ -47,7 +48,6 @@ where
UcanHeader {
alg: self.issuer.get_jwt_algorithm_name(),
typ: "JWT".into(),
ucv: UCAN_VERSION.into(),
}
}

Expand All @@ -74,6 +74,7 @@ where
};

Ok(UcanPayload {
ucv: UCAN_VERSION.into(),
aud: self.audience.clone(),
iss: self.issuer.get_did().await?,
exp: self.expiration,
Expand Down Expand Up @@ -121,7 +122,7 @@ where
expiration: Option<u64>,
not_before: Option<u64>,

facts: Vec<Value>,
facts: FactsMap,
proofs: Vec<String>,
add_nonce: bool,
}
Expand Down Expand Up @@ -149,7 +150,7 @@ where
expiration: None,
not_before: None,

facts: Vec::new(),
facts: BTreeMap::new(),
proofs: Vec::new(),
add_nonce: false,
}
Expand Down Expand Up @@ -198,9 +199,11 @@ where
}

/// Add a fact or proof of knowledge to this UCAN.
pub fn with_fact<T: Serialize + DeserializeOwned>(mut self, fact: T) -> Self {
pub fn with_fact<T: Serialize + DeserializeOwned>(mut self, key: &str, fact: T) -> Self {
match serde_json::to_value(fact) {
Ok(value) => self.facts.push(value),
Ok(value) => {
self.facts.insert(key.to_owned(), value);
}
Err(error) => warn!("Could not add fact to UCAN: {}", error),
}
self
Expand Down
40 changes: 24 additions & 16 deletions ucan/src/ipld/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ use crate::{
crypto::JwtSignatureAlgorithm,
ipld::{Principle, Signature},
serde::Base64Encode,
ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION},
ucan::{FactsMap, Ucan, UcanHeader, UcanPayload, UCAN_VERSION},
};
use cid::Cid;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{convert::TryFrom, str::FromStr};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
Expand All @@ -21,7 +20,7 @@ pub struct UcanIpld {
pub att: Vec<CapabilityIpld>,
pub prf: Option<Vec<Cid>>,
pub exp: u64,
pub fct: Option<Vec<Value>>,
pub fct: Option<FactsMap>,

pub nnc: Option<String>,
pub nbf: Option<u64>,
Expand Down Expand Up @@ -72,10 +71,10 @@ impl TryFrom<&UcanIpld> for Ucan {
let header = UcanHeader {
alg: algorithm.to_string(),
typ: "JWT".into(),
ucv: UCAN_VERSION.into(),
};

let payload = UcanPayload {
ucv: UCAN_VERSION.into(),
iss: value.iss.to_string(),
aud: value.aud.to_string(),
exp: value.exp,
Expand Down Expand Up @@ -131,10 +130,13 @@ mod tests {
let other_builder = scaffold_ucan_builder(&identities).await.unwrap();

let canon_jwt = canon_builder
.with_fact(json!({
"baz": true,
"foo": "bar"
}))
.with_fact(
"abc/challenge",
json!({
"baz": true,
"foo": "bar"
}),
)
.build()
.unwrap()
.sign()
Expand All @@ -144,10 +146,13 @@ mod tests {
.unwrap();

let other_jwt = other_builder
.with_fact(json!({
"foo": "bar",
"baz": true
}))
.with_fact(
"abc/challenge",
json!({
"foo": "bar",
"baz": true
}),
)
.build()
.unwrap()
.sign()
Expand All @@ -166,10 +171,13 @@ mod tests {
let builder = scaffold_ucan_builder(&identities).await.unwrap();

let jwt = builder
.with_fact(json!({
"baz": true,
"foo": "bar"
}))
.with_fact(
"abc/challenge",
json!({
"baz": true,
"foo": "bar"
}),
)
.with_nonce()
.build()
.unwrap()
Expand Down
14 changes: 11 additions & 3 deletions ucan/src/tests/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::BTreeMap;

use crate::{
builder::UcanBuilder,
capability::{CapabilityIpld, CapabilitySemantics},
Expand Down Expand Up @@ -52,8 +54,8 @@ async fn it_builds_with_a_simple_example() {
.for_audience(identities.bob_did.as_str())
.with_expiration(expiration)
.not_before(not_before)
.with_fact(fact_1.clone())
.with_fact(fact_2.clone())
.with_fact("abc/challenge", fact_1.clone())
.with_fact("def/challenge", fact_2.clone())
.claiming_capability(&cap_1)
.claiming_capability(&cap_2)
.with_nonce()
Expand All @@ -67,7 +69,13 @@ async fn it_builds_with_a_simple_example() {
assert_eq!(ucan.expires_at(), &expiration);
assert!(ucan.not_before().is_some());
assert_eq!(ucan.not_before().unwrap(), not_before);
assert_eq!(ucan.facts(), &Some(vec![fact_1, fact_2]));
assert_eq!(
ucan.facts(),
&Some(BTreeMap::from([
(String::from("abc/challenge"), fact_1),
(String::from("def/challenge"), fact_2),
]))
);

let expected_attenuations =
Vec::from([CapabilityIpld::from(&cap_1), CapabilityIpld::from(&cap_2)]);
Expand Down
9 changes: 7 additions & 2 deletions ucan/src/tests/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod validate {
ucan::Ucan,
};

use serde_json::json;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};

Expand Down Expand Up @@ -81,6 +82,7 @@ mod validate {
.for_audience(identities.bob_did.as_str())
.not_before(now() / 1000)
.with_lifetime(30)
.with_fact("abc/challenge", json!({ "foo": "bar" }))
.build()
.unwrap()
.sign()
Expand All @@ -94,15 +96,18 @@ mod validate {
serde_json::json!({
"header": {
"alg": "EdDSA",
"typ": "JWT",
"ucv": crate::ucan::UCAN_VERSION
"typ": "JWT"
},
"payload": {
"ucv": crate::ucan::UCAN_VERSION,
"iss": ucan.issuer(),
"aud": ucan.audience(),
"exp": ucan.expires_at(),
"nbf": ucan.not_before(),
"att": [],
"fct": {
"abc/challenge": { "foo": "bar" }
}
},
"signed_data": ucan.signed_data(),
"signature": ucan.signature()
Expand Down
14 changes: 8 additions & 6 deletions ucan/src/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ use cid::{
use libipld_core::{codec::Codec, raw::RawCodec};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{convert::TryFrom, str::FromStr};
use std::{collections::BTreeMap, convert::TryFrom, str::FromStr};

pub const UCAN_VERSION: &str = "0.9.0-canary";
pub const UCAN_VERSION: &str = "0.10.0-canary";

pub type FactsMap = BTreeMap<String, Value>;

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct UcanHeader {
pub alg: String,
pub typ: String,
pub ucv: String,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct UcanPayload {
pub ucv: String,
pub iss: String,
pub aud: String,
pub exp: u64,
Expand All @@ -35,7 +37,7 @@ pub struct UcanPayload {
pub nnc: Option<String>,
pub att: Vec<CapabilityIpld>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fct: Option<Vec<Value>>,
pub fct: Option<FactsMap>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prf: Option<Vec<String>>,
}
Expand Down Expand Up @@ -175,12 +177,12 @@ impl Ucan {
&self.payload.att
}

pub fn facts(&self) -> &Option<Vec<Value>> {
pub fn facts(&self) -> &Option<FactsMap> {
&self.payload.fct
}

pub fn version(&self) -> &str {
&self.header.ucv
&self.payload.ucv
}

pub fn to_cid(&self, hasher: Code) -> Result<Cid> {
Expand Down

0 comments on commit ae19741

Please sign in to comment.