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 codec: UcanCodec,
45}
46
47#[derive(Clone, PartialEq, Debug)]
48enum UcanCodec {
49 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 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 (Some("did:"), Some("pkh:"), Some(jwk), Content::Resource(Resource::Document(d))) => {
78 match_key_with_did_pkh(jwk, &d)?;
79 jwk.clone()
80 }
81 (
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 (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 (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 #[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
325trait DIDVerificationMethodExt {
327 fn public_key_jwk(&self) -> Result<Option<JWK>, InvalidVerificationMethod>;
332
333 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#[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 (Some("did:"), Some("pkh:"), Some(jwk), Content::Resource(Resource::Document(d))) => {
504 match_key_with_did_pkh(jwk, &d)?;
505 jwk.clone()
506 }
507 (
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 (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 (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}