ssi_ucan/
lib.rs

1pub mod error;
2use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
3pub use error::Error;
4use iref::UriBuf;
5use libipld::{
6    codec::{Codec, Decode, Encode},
7    error::Error as IpldError,
8    json::DagJsonCodec,
9    serde::{from_ipld, to_ipld},
10    Block, Cid, Ipld,
11};
12use serde::{de::DeserializeOwned, Deserialize, Serialize};
13use serde_json::Value as JsonValue;
14use serde_with::{
15    base64::{Base64, UrlSafe},
16    serde_as, DisplayFromStr,
17};
18use ssi_caips::caip10::{BlockchainAccountId, BlockchainAccountIdParseError};
19use ssi_dids_core::{
20    document::{DIDVerificationMethod, Resource},
21    resolution::{Content, DerefOutput},
22    DIDBuf, DIDResolver, DIDURLBuf, Document,
23};
24use ssi_jwk::{Algorithm, JWK};
25use ssi_jws::{decode_jws_parts, sign_bytes, split_jws, verify_bytes, Header, JwsSignature};
26use ssi_jwt::NumericDate;
27use ssi_verification_methods::{GenericVerificationMethod, InvalidVerificationMethod};
28use std::{
29    borrow::Cow,
30    fmt::Display,
31    io::{Read, Seek, Write},
32    str::Utf8Error,
33};
34
35#[derive(Clone, PartialEq, Debug)]
36pub struct Ucan<F = JsonValue, A = JsonValue> {
37    pub header: Header,
38    pub payload: Payload<F, A>,
39    pub signature: JwsSignature,
40    // unfortunately this matters for sig verification
41    // we have to keep track of how this ucan was created
42    // alternatively we could have 2 different types?
43    // e.g. DagJsonUcan and RawJwtUcan
44    codec: UcanCodec,
45}
46
47#[derive(Clone, PartialEq, Debug)]
48enum UcanCodec {
49    // maintain serialization
50    Raw(String),
51    DagJson,
52}
53
54impl Default for UcanCodec {
55    fn default() -> Self {
56        Self::DagJson
57    }
58}
59
60impl<F, A> Ucan<F, A> {
61    pub async fn verify_signature(&self, resolver: &impl DIDResolver) -> Result<(), Error>
62    where
63        F: Serialize,
64        A: Serialize,
65    {
66        // extract or deduce signing key
67        let key: JWK = match (
68            self.payload.issuer.get(..4),
69            self.payload.issuer.get(4..8),
70            &self.header.jwk,
71            resolver
72                .dereference(&self.payload.issuer)
73                .await
74                .map(DerefOutput::into_content)?,
75        ) {
76            // did:pkh without fragment
77            (Some("did:"), Some("pkh:"), Some(jwk), Content::Resource(Resource::Document(d))) => {
78                match_key_with_did_pkh(jwk, &d)?;
79                jwk.clone()
80            }
81            // did:pkh with fragment
82            (
83                Some("did:"),
84                Some("pkh:"),
85                Some(jwk),
86                Content::Resource(Resource::VerificationMethod(vm)),
87            ) => {
88                match_key_with_vm(jwk, &vm)?;
89                jwk.clone()
90            }
91            // did:key without fragment
92            (Some("did:"), Some("key:"), _, Content::Resource(Resource::Document(d))) => d
93                .verification_method
94                .first()
95                .ok_or(Error::VerificationMethodMismatch)?
96                .public_key_jwk()?
97                .ok_or(Error::MissingPublicKey)?,
98            // general case, did with fragment
99            (Some("did:"), Some(_), _, Content::Resource(Resource::VerificationMethod(vm))) => {
100                vm.public_key_jwk()?.ok_or(Error::MissingPublicKey)?
101            }
102            _ => return Err(Error::VerificationMethodMismatch),
103        };
104
105        Ok(verify_bytes(
106            self.header.algorithm,
107            self.encode()?
108                .rsplit_once('.')
109                .ok_or(ssi_jws::Error::InvalidJws)?
110                .0
111                .as_bytes(),
112            &key,
113            &self.signature,
114        )?)
115    }
116
117    pub fn decode(jwt: &str) -> Result<Self, Error>
118    where
119        F: DeserializeOwned,
120        A: DeserializeOwned,
121    {
122        let parts = split_jws(jwt).and_then(|(h, p, s)| decode_jws_parts(h, p.as_bytes(), s))?;
123        let (payload, codec): (Payload<F, A>, UcanCodec) =
124            match serde_json::from_slice(&parts.signing_bytes.payload) {
125                Ok(p) => Ok((p, UcanCodec::Raw(jwt.to_string()))),
126                Err(e) => match DagJsonCodec.decode(&parts.signing_bytes.payload) {
127                    Ok(p) => Ok((p, UcanCodec::DagJson)),
128                    Err(_) => Err(e),
129                },
130            }?;
131
132        if parts.signing_bytes.header.type_.as_deref() != Some("JWT") {
133            return Err(Error::MissingUCANHeaderField("type: JWT"));
134        }
135
136        match parts.signing_bytes.header.additional_parameters.get("ucv") {
137            Some(JsonValue::String(v)) if v == "0.9.0" => (),
138            _ => return Err(Error::MissingUCANHeaderField("ucv: 0.9.0")),
139        }
140
141        if !payload.audience.starts_with("did:") {
142            return Err(Error::DIDURL);
143        }
144
145        Ok(Self {
146            header: parts.signing_bytes.header,
147            payload,
148            signature: parts.signature,
149            codec,
150        })
151    }
152
153    pub fn encode(&self) -> Result<String, Error>
154    where
155        F: Serialize,
156        A: Serialize,
157    {
158        Ok(match &self.codec {
159            UcanCodec::Raw(r) => r.clone(),
160            UcanCodec::DagJson => [
161                BASE64_URL_SAFE_NO_PAD
162                    .encode(DagJsonCodec.encode(&to_ipld(&self.header).map_err(IpldError::new)?)?),
163                BASE64_URL_SAFE_NO_PAD.encode(DagJsonCodec.encode(&self.payload)?),
164                BASE64_URL_SAFE_NO_PAD.encode(&self.signature),
165            ]
166            .join("."),
167        })
168    }
169
170    pub fn to_block<S, H>(&self, hash: H) -> Result<Block<S>, IpldError>
171    where
172        F: Serialize,
173        A: Serialize,
174        S: libipld::store::StoreParams,
175        H: Into<S::Hashes>,
176        S::Codecs: From<DagJsonCodec> + From<libipld::raw::RawCodec>,
177    {
178        match &self.codec {
179            UcanCodec::Raw(r) => Block::encode(libipld::raw::RawCodec, hash.into(), r.as_bytes()),
180            UcanCodec::DagJson => Block::encode(
181                DagJsonCodec,
182                hash.into(),
183                &to_ipld(ipld_encoding::DagJsonUcanRef::from(self))?,
184            ),
185        }
186    }
187
188    pub fn from_block<S>(block: &Block<S>) -> Result<Self, FromIpldBlockError>
189    where
190        F: DeserializeOwned,
191        A: DeserializeOwned,
192        S: libipld::store::StoreParams,
193        S::Codecs: From<DagJsonCodec> + From<libipld::raw::RawCodec>,
194        Ipld: Decode<S::Codecs>,
195    {
196        if block.cid().codec() == S::Codecs::from(DagJsonCodec).into() {
197            let ipld: Ipld = S::Codecs::from(DagJsonCodec).decode(block.data())?;
198            let du: ipld_encoding::DagJsonUcan<F, A> = from_ipld(ipld)?;
199            Ok(du.into())
200        } else if block.cid().codec() == S::Codecs::from(libipld::raw::RawCodec).into() {
201            Ok(Self::decode(std::str::from_utf8(block.data())?)?)
202        } else {
203            Err(FromIpldBlockError::InvalidCodec)
204        }
205    }
206}
207
208#[derive(Debug, thiserror::Error)]
209pub enum FromIpldBlockError {
210    #[error(transparent)]
211    Ipld(#[from] libipld::error::Error),
212
213    #[error(transparent)]
214    Decode(#[from] libipld::error::SerdeError),
215
216    #[error(transparent)]
217    Utf8(#[from] Utf8Error),
218
219    #[error(transparent)]
220    Ucan(#[from] Error),
221
222    #[error("Invalid codec: expected `raw` or `dagJson`")]
223    InvalidCodec,
224}
225
226fn match_key_with_did_pkh(key: &JWK, doc: &Document) -> Result<(), Error> {
227    for vm in &doc.verification_method {
228        if let Some(id) = vm.blockchain_account_id()? {
229            if id.verify(key).is_ok() {
230                return Ok(());
231            }
232        }
233    }
234
235    Err(Error::VerificationMethodMismatch)
236}
237
238fn match_key_with_vm(key: &JWK, vm: &DIDVerificationMethod) -> Result<(), Error> {
239    Ok(vm
240        .blockchain_account_id()?
241        .ok_or(Error::VerificationMethodMismatch)?
242        .verify(key)?)
243}
244
245#[serde_as]
246#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
247pub struct Payload<F = JsonValue, A = JsonValue> {
248    #[serde(rename = "iss")]
249    pub issuer: DIDURLBuf,
250    #[serde(rename = "aud")]
251    pub audience: DIDBuf,
252    #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")]
253    pub not_before: Option<NumericDate>,
254    #[serde(rename = "exp")]
255    pub expiration: NumericDate,
256    #[serde(rename = "nnc", skip_serializing_if = "Option::is_none")]
257    pub nonce: Option<String>,
258    #[serde(rename = "fct", skip_serializing_if = "Option::is_none")]
259    pub facts: Option<Vec<F>>,
260    #[serde_as(as = "Vec<DisplayFromStr>")]
261    #[serde(rename = "prf")]
262    pub proof: Vec<Cid>,
263    #[serde(rename = "att")]
264    pub attenuation: Vec<Capability<A>>,
265}
266
267#[derive(thiserror::Error, Debug)]
268pub enum TimeInvalid {
269    #[error("UCAN not yet valid")]
270    TooEarly,
271    #[error("UCAN has expired")]
272    TooLate,
273}
274
275impl<F, A> Payload<F, A> {
276    pub fn validate_time(&self, time: Option<f64>) -> Result<(), TimeInvalid> {
277        let t = time.unwrap_or_else(now);
278        match (self.not_before, t > self.expiration.as_seconds()) {
279            (_, true) => Err(TimeInvalid::TooLate),
280            (Some(nbf), _) if t < nbf.as_seconds() => Err(TimeInvalid::TooEarly),
281            _ => Ok(()),
282        }
283    }
284
285    // NOTE IntoIter::new is deprecated, but into_iter() returns references until we move to 2021 edition
286    #[allow(deprecated)]
287    pub fn sign(self, algorithm: Algorithm, key: &JWK) -> Result<Ucan<F, A>, Error>
288    where
289        F: Serialize,
290        A: Serialize,
291    {
292        let header = Header {
293            algorithm,
294            type_: Some("JWT".to_string()),
295            additional_parameters: std::array::IntoIter::new([(
296                "ucv".to_string(),
297                serde_json::Value::String("0.9.0".to_string()),
298            )])
299            .collect(),
300            ..Default::default()
301        };
302
303        let signature = sign_bytes(
304            algorithm,
305            [
306                BASE64_URL_SAFE_NO_PAD
307                    .encode(DagJsonCodec.encode(&to_ipld(&header).map_err(IpldError::new)?)?),
308                BASE64_URL_SAFE_NO_PAD.encode(DagJsonCodec.encode(&self)?),
309            ]
310            .join(".")
311            .as_bytes(),
312            key,
313        )?
314        .into();
315
316        Ok(Ucan {
317            header,
318            payload: self,
319            signature,
320            codec: UcanCodec::DagJson,
321        })
322    }
323}
324
325/// Extension for the `DIDVerificationMethod` type.
326trait DIDVerificationMethodExt {
327    /// Returns the public key of a DID verification method as a JWK.
328    ///
329    /// The verification method must be known by `ssi` and well-formed, or this
330    /// function will return an `InvalidVerificationMethod` error.
331    fn public_key_jwk(&self) -> Result<Option<JWK>, InvalidVerificationMethod>;
332
333    /// Returns the blockchain account id of a DID verification method.
334    fn blockchain_account_id(
335        &self,
336    ) -> Result<Option<BlockchainAccountId>, BlockchainAccountIdError>;
337}
338
339impl DIDVerificationMethodExt for DIDVerificationMethod {
340    fn public_key_jwk(&self) -> Result<Option<JWK>, InvalidVerificationMethod> {
341        let vm: GenericVerificationMethod = self.clone().into();
342        Ok(ssi_verification_methods::AnyMethod::try_from(vm)?
343            .public_key_jwk()
344            .map(Cow::into_owned))
345    }
346
347    fn blockchain_account_id(
348        &self,
349    ) -> Result<Option<BlockchainAccountId>, BlockchainAccountIdError> {
350        match self.properties.get("blockchainAccountId") {
351            Some(serde_json::Value::String(value)) => Ok(Some(value.parse()?)),
352            Some(_) => Err(BlockchainAccountIdError::InvalidValue),
353            None => Ok(None),
354        }
355    }
356}
357
358#[derive(Debug, thiserror::Error)]
359pub enum BlockchainAccountIdError {
360    #[error("Invalid JSON value")]
361    InvalidValue,
362
363    #[error(transparent)]
364    Parse(#[from] BlockchainAccountIdParseError),
365}
366
367#[serde_as]
368#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
369#[serde(untagged)]
370pub enum UcanResource {
371    Proof(#[serde_as(as = "DisplayFromStr")] UcanProofRef),
372    URI(UriBuf),
373}
374
375impl Display for UcanResource {
376    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
377        match &self {
378            Self::Proof(p) => write!(f, "{p}"),
379            Self::URI(u) => write!(f, "{u}"),
380        }
381    }
382}
383
384#[derive(Clone, PartialEq, Eq, Debug)]
385pub struct UcanProofRef(pub Cid);
386
387impl Display for UcanProofRef {
388    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
389        write!(f, "ucan:{}", self.0)
390    }
391}
392
393#[derive(thiserror::Error, Debug)]
394pub enum ProofRefParseErr {
395    #[error("Missing ucan prefix")]
396    Format,
397    #[error("Invalid Cid reference")]
398    ParseCid(#[from] libipld::cid::Error),
399}
400
401impl std::str::FromStr for UcanProofRef {
402    type Err = ProofRefParseErr;
403
404    fn from_str(s: &str) -> Result<Self, Self::Err> {
405        Ok(UcanProofRef(
406            s.strip_prefix("ucan:")
407                .map(Cid::from_str)
408                .ok_or(ProofRefParseErr::Format)??,
409        ))
410    }
411}
412
413#[derive(Clone, PartialEq, Eq, Debug)]
414pub struct UcanScope {
415    pub namespace: String,
416    pub capability: String,
417}
418
419impl std::fmt::Display for UcanScope {
420    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
421        write!(f, "{}/{}", self.namespace, self.capability)
422    }
423}
424
425#[derive(thiserror::Error, Debug)]
426pub enum UcanScopeParseErr {
427    #[error("Missing namespace")]
428    Namespace,
429}
430
431impl std::str::FromStr for UcanScope {
432    type Err = UcanScopeParseErr;
433
434    fn from_str(s: &str) -> Result<Self, Self::Err> {
435        let (ns, cap) = s.split_once('/').ok_or(UcanScopeParseErr::Namespace)?;
436        Ok(UcanScope {
437            namespace: ns.to_string(),
438            capability: cap.to_string(),
439        })
440    }
441}
442
443/// 3.2.5 A JSON capability MUST include the with and can fields and
444/// MAY have additional fields needed to describe the capability
445#[serde_as]
446#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
447pub struct Capability<A = JsonValue> {
448    pub with: UcanResource,
449    #[serde_as(as = "DisplayFromStr")]
450    pub can: UcanScope,
451    #[serde(rename = "nb", skip_serializing_if = "Option::is_none")]
452    pub additional_fields: Option<A>,
453}
454
455fn now() -> f64 {
456    let now = chrono::prelude::Utc::now();
457    match now.timestamp_nanos_opt() {
458        Some(nano) => nano as f64 / 1e+9_f64,
459        None => now.timestamp_micros() as f64 / 1e+6_f64,
460    }
461}
462
463#[serde_as]
464#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
465pub struct UcanRevocation {
466    #[serde(rename = "iss")]
467    pub issuer: DIDURLBuf,
468    #[serde_as(as = "DisplayFromStr")]
469    pub revoke: Cid,
470    #[serde_as(as = "Base64<UrlSafe>")]
471    pub challenge: Vec<u8>,
472}
473
474impl UcanRevocation {
475    pub fn sign(
476        issuer: DIDURLBuf,
477        revoke: Cid,
478        jwk: &JWK,
479        algorithm: Algorithm,
480    ) -> Result<Self, Error> {
481        Ok(Self {
482            issuer,
483            revoke,
484            challenge: sign_bytes(algorithm, format!("REVOKE:{revoke}").as_bytes(), jwk)?,
485        })
486    }
487    pub async fn verify_signature(
488        &self,
489        resolver: &impl DIDResolver,
490        algorithm: Algorithm,
491        jwk: Option<&JWK>,
492    ) -> Result<(), Error> {
493        let key: JWK = match (
494            self.issuer.get(..4),
495            self.issuer.get(4..8),
496            jwk,
497            resolver
498                .dereference(&self.issuer)
499                .await
500                .map(DerefOutput::into_content)?,
501        ) {
502            // did:pkh without fragment
503            (Some("did:"), Some("pkh:"), Some(jwk), Content::Resource(Resource::Document(d))) => {
504                match_key_with_did_pkh(jwk, &d)?;
505                jwk.clone()
506            }
507            // did:pkh with fragment
508            (
509                Some("did:"),
510                Some("pkh:"),
511                Some(jwk),
512                Content::Resource(Resource::VerificationMethod(vm)),
513            ) => {
514                match_key_with_vm(jwk, &vm)?;
515                jwk.clone()
516            }
517            // did:key without fragment
518            (Some("did:"), Some("key:"), _, Content::Resource(Resource::Document(d))) => d
519                .verification_method
520                .first()
521                .ok_or(Error::VerificationMethodMismatch)?
522                .public_key_jwk()?
523                .ok_or(Error::MissingPublicKey)?,
524            // general case, did with fragment
525            (Some("did:"), Some(_), _, Content::Resource(Resource::VerificationMethod(vm))) => {
526                vm.public_key_jwk()?.ok_or(Error::MissingPublicKey)?
527            }
528            _ => return Err(Error::VerificationMethodMismatch),
529        };
530
531        Ok(verify_bytes(
532            algorithm,
533            format!("REVOKE:{}", self.revoke).as_bytes(),
534            &key,
535            &self.challenge,
536        )?)
537    }
538}
539
540mod ipld_encoding {
541    use super::*;
542
543    #[derive(Serialize, Clone, PartialEq, Debug)]
544    pub struct DagJsonUcanRef<'a, F = JsonValue, A = JsonValue> {
545        header: &'a Header,
546        payload: DagJsonPayloadRef<'a, F, A>,
547        signature: &'a [u8],
548    }
549
550    #[derive(Deserialize, Clone, PartialEq, Debug)]
551    pub struct DagJsonUcan<F = JsonValue, A = JsonValue> {
552        header: Header,
553        payload: DagJsonPayload<F, A>,
554        signature: Vec<u8>,
555    }
556
557    #[derive(Serialize, Clone, PartialEq, Debug)]
558    pub struct DagJsonPayloadRef<'a, F = JsonValue, A = JsonValue> {
559        pub iss: &'a str,
560        pub aud: &'a str,
561        #[serde(skip_serializing_if = "Option::is_none")]
562        pub nbf: &'a Option<NumericDate>,
563        pub exp: &'a NumericDate,
564        #[serde(skip_serializing_if = "Option::is_none")]
565        pub nnc: &'a Option<String>,
566        #[serde(skip_serializing_if = "Option::is_none")]
567        pub fct: &'a Option<Vec<F>>,
568        pub prf: &'a Vec<Cid>,
569        pub att: &'a Vec<Capability<A>>,
570    }
571
572    #[derive(Deserialize, Clone, PartialEq, Debug)]
573    pub struct DagJsonPayload<F = JsonValue, A = JsonValue> {
574        pub iss: DIDURLBuf,
575        pub aud: DIDBuf,
576        #[serde(skip_serializing_if = "Option::is_none")]
577        pub nbf: Option<NumericDate>,
578        pub exp: NumericDate,
579        #[serde(skip_serializing_if = "Option::is_none")]
580        pub nnc: Option<String>,
581        #[serde(skip_serializing_if = "Option::is_none")]
582        pub fct: Option<Vec<F>>,
583        pub prf: Vec<Cid>,
584        pub att: Vec<Capability<A>>,
585    }
586
587    impl<F, A> Encode<DagJsonCodec> for Ucan<F, A>
588    where
589        F: Serialize,
590        A: Serialize,
591    {
592        fn encode<W: Write>(&self, c: DagJsonCodec, w: &mut W) -> Result<(), IpldError> {
593            to_ipld(ipld_encoding::DagJsonUcanRef::from(self))?.encode(c, w)
594        }
595    }
596
597    impl<F, A> Decode<DagJsonCodec> for Ucan<F, A>
598    where
599        F: DeserializeOwned,
600        A: DeserializeOwned,
601    {
602        fn decode<R: Read + Seek>(c: DagJsonCodec, r: &mut R) -> Result<Self, IpldError> {
603            let u: ipld_encoding::DagJsonUcan<F, A> = from_ipld(Ipld::decode(c, r)?)?;
604            Ok(u.into())
605        }
606    }
607
608    impl<F, A> Encode<DagJsonCodec> for Payload<F, A>
609    where
610        F: Serialize,
611        A: Serialize,
612    {
613        fn encode<W: Write>(&self, c: DagJsonCodec, w: &mut W) -> Result<(), IpldError> {
614            to_ipld(ipld_encoding::DagJsonPayloadRef::from(self))?.encode(c, w)
615        }
616    }
617
618    impl<F, A> Decode<DagJsonCodec> for Payload<F, A>
619    where
620        F: DeserializeOwned,
621        A: DeserializeOwned,
622    {
623        fn decode<R: Read + Seek>(c: DagJsonCodec, r: &mut R) -> Result<Self, IpldError> {
624            let p: ipld_encoding::DagJsonPayload<F, A> = from_ipld(Ipld::decode(c, r)?)?;
625            Ok(p.into())
626        }
627    }
628
629    impl<'a, F, A> From<&'a Ucan<F, A>> for DagJsonUcanRef<'a, F, A> {
630        fn from(u: &'a Ucan<F, A>) -> Self {
631            Self {
632                header: &u.header,
633                payload: DagJsonPayloadRef::from(&u.payload),
634                signature: &u.signature,
635            }
636        }
637    }
638
639    impl<F, A> From<DagJsonUcan<F, A>> for Ucan<F, A> {
640        fn from(u: DagJsonUcan<F, A>) -> Self {
641            Self {
642                header: u.header,
643                payload: u.payload.into(),
644                signature: u.signature.into(),
645                codec: UcanCodec::DagJson,
646            }
647        }
648    }
649
650    impl<'a, F, A> From<&'a Payload<F, A>> for DagJsonPayloadRef<'a, F, A> {
651        fn from(p: &'a Payload<F, A>) -> Self {
652            Self {
653                iss: &p.issuer,
654                aud: &p.audience,
655                nbf: &p.not_before,
656                exp: &p.expiration,
657                nnc: &p.nonce,
658                fct: &p.facts,
659                prf: &p.proof,
660                att: &p.attenuation,
661            }
662        }
663    }
664
665    impl<F, A> From<DagJsonPayload<F, A>> for Payload<F, A> {
666        fn from(p: DagJsonPayload<F, A>) -> Self {
667            Self {
668                issuer: p.iss,
669                audience: p.aud,
670                not_before: p.nbf,
671                expiration: p.exp,
672                nonce: p.nnc,
673                facts: p.fct,
674                proof: p.prf,
675                attenuation: p.att,
676            }
677        }
678    }
679}
680
681#[cfg(test)]
682mod tests {
683    use super::*;
684    use did_method_key::DIDKey;
685
686    #[async_std::test]
687    async fn valid() {
688        let cases: Vec<ValidTestVector> =
689            serde_json::from_str(include_str!("../../../tests/ucan-v0.9.0-valid.json")).unwrap();
690
691        for case in cases {
692            let ucan = match Ucan::decode(&case.token) {
693                Ok(u) => u,
694                Err(e) => panic!("{:?}", e),
695            };
696
697            if let Err(e) = ucan.verify_signature(&DIDKey).await {
698                panic!("{:?}", e)
699            };
700
701            assert_eq!(ucan.payload, case.assertions.payload);
702            assert_eq!(ucan.header, case.assertions.header);
703        }
704    }
705
706    #[async_std::test]
707    async fn invalid() {
708        let cases: Vec<InvalidTestVector> =
709            serde_json::from_str(include_str!("../../../tests/ucan-v0.9.0-invalid.json")).unwrap();
710        for case in cases {
711            match Ucan::<JsonValue>::decode(&case.token) {
712                Ok(u) => {
713                    if u.payload.validate_time(None).is_ok()
714                        && u.verify_signature(&DIDKey).await.is_ok()
715                    {
716                        panic!("{}", case.comment);
717                    }
718                }
719                Err(_e) => {}
720            };
721        }
722    }
723
724    #[async_std::test]
725    async fn basic() {
726        let case = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsInVjdiI6IjAuOS4wIn0.eyJhdHQiOltdLCJhdWQiOiJkaWQ6ZXhhbXBsZToxMjMiLCJleHAiOjkwMDAwMDAwMDEuMCwiaXNzIjoiZGlkOmtleTp6Nk1ram16ZXBUcGc0NFJvejhKbk45QXhUS0QyMjk1Z2p6M3h0NDhQb2k3MjYxR1MiLCJwcmYiOltdfQ.V38liNHsdVO0Zk_davTBsewq-2XCxs_3qIRLuwUNj87aqdlMfa9X5O5IRR5u7apzWm7sUiR0FS3J3Nnu7IWtBQ";
727        let u = Ucan::<JsonValue>::decode(case).unwrap();
728        u.verify_signature(&DIDKey).await.unwrap();
729    }
730
731    #[derive(Deserialize)]
732    struct ValidAssertions {
733        pub header: Header,
734        pub payload: Payload,
735    }
736
737    #[derive(Deserialize)]
738    struct ValidTestVector {
739        pub token: String,
740        pub assertions: ValidAssertions,
741    }
742
743    #[derive(Deserialize)]
744    #[serde(rename_all = "camelCase")]
745    #[allow(dead_code)]
746    struct InvalidAssertions {
747        pub header: Option<JsonValue>,
748        pub payload: Option<JsonValue>,
749        pub type_errors: Option<Vec<String>>,
750        pub validation_errors: Option<Vec<String>>,
751    }
752
753    #[derive(Deserialize)]
754    struct InvalidTestVector {
755        pub comment: String,
756        pub token: String,
757        #[allow(dead_code)]
758        pub assertions: InvalidAssertions,
759    }
760}