1use crate::error::AttestationError;
4use crate::types::{CanonicalDid, IdentityDID};
5use chrono::{DateTime, Utc};
6use hex;
7use json_canon;
8use log::debug;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::fmt;
12use std::ops::Deref;
13use std::str::FromStr;
14
15pub const MAX_ATTESTATION_JSON_SIZE: usize = 64 * 1024;
17
18pub const MAX_JSON_BATCH_SIZE: usize = 1024 * 1024;
20
21pub const MAX_PUBLIC_KEY_HEX_LEN: usize = 64;
23pub const MAX_SIGNATURE_HEX_LEN: usize = 128;
25pub const MAX_FILE_HASH_HEX_LEN: usize = 64;
27
28pub use auths_keri::capability::{
29 Capability, CapabilityError, MANAGE_MEMBERS, ROTATE_KEYS, SIGN_COMMIT, SIGN_RELEASE,
30};
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
42#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
43#[serde(transparent)]
44pub struct ResourceId(String);
45
46impl ResourceId {
47 pub fn new(s: impl Into<String>) -> Self {
49 Self(s.into())
50 }
51
52 pub fn as_str(&self) -> &str {
54 &self.0
55 }
56}
57
58impl Deref for ResourceId {
59 type Target = str;
60 fn deref(&self) -> &str {
61 &self.0
62 }
63}
64
65impl fmt::Display for ResourceId {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 f.write_str(&self.0)
68 }
69}
70
71impl From<String> for ResourceId {
72 fn from(s: String) -> Self {
73 Self(s)
74 }
75}
76
77impl From<&str> for ResourceId {
78 fn from(s: &str) -> Self {
79 Self(s.to_string())
80 }
81}
82
83impl PartialEq<str> for ResourceId {
84 fn eq(&self, other: &str) -> bool {
85 self.0 == other
86 }
87}
88
89impl PartialEq<&str> for ResourceId {
90 fn eq(&self, other: &&str) -> bool {
91 self.0 == *other
92 }
93}
94
95impl PartialEq<String> for ResourceId {
96 fn eq(&self, other: &String) -> bool {
97 self.0 == *other
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
110#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
111#[serde(rename_all = "lowercase")]
112pub enum Role {
113 Admin,
115 Member,
117 Readonly,
119}
120
121impl Role {
122 pub fn as_str(&self) -> &str {
124 match self {
125 Role::Admin => "admin",
126 Role::Member => "member",
127 Role::Readonly => "readonly",
128 }
129 }
130
131 pub fn default_capabilities(&self) -> Vec<Capability> {
133 match self {
134 Role::Admin => vec![
135 Capability::sign_commit(),
136 Capability::sign_release(),
137 Capability::manage_members(),
138 Capability::rotate_keys(),
139 ],
140 Role::Member => vec![Capability::sign_commit(), Capability::sign_release()],
141 Role::Readonly => vec![],
142 }
143 }
144}
145
146impl fmt::Display for Role {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 f.write_str(self.as_str())
149 }
150}
151
152impl FromStr for Role {
153 type Err = RoleParseError;
154
155 fn from_str(s: &str) -> Result<Self, Self::Err> {
156 match s.trim().to_lowercase().as_str() {
157 "admin" => Ok(Role::Admin),
158 "member" => Ok(Role::Member),
159 "readonly" => Ok(Role::Readonly),
160 other => Err(RoleParseError(other.to_string())),
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
167#[error("unknown role: '{0}' (expected admin, member, or readonly)")]
168pub struct RoleParseError(String);
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub struct Ed25519PublicKey([u8; 32]);
180
181impl Ed25519PublicKey {
182 pub fn from_bytes(bytes: [u8; 32]) -> Self {
184 Self(bytes)
185 }
186
187 pub fn try_from_slice(slice: &[u8]) -> Result<Self, Ed25519KeyError> {
197 let arr: [u8; 32] = slice
198 .try_into()
199 .map_err(|_| Ed25519KeyError::InvalidLength(slice.len()))?;
200 Ok(Self(arr))
201 }
202
203 pub fn as_bytes(&self) -> &[u8; 32] {
205 &self.0
206 }
207
208 pub fn is_zero(&self) -> bool {
210 self.0 == [0u8; 32]
211 }
212}
213
214impl Serialize for Ed25519PublicKey {
215 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
216 s.serialize_str(&hex::encode(self.0))
217 }
218}
219
220impl<'de> Deserialize<'de> for Ed25519PublicKey {
221 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
222 let s = String::deserialize(d)?;
223 let bytes =
224 hex::decode(&s).map_err(|e| serde::de::Error::custom(format!("invalid hex: {e}")))?;
225 let arr: [u8; 32] = bytes.try_into().map_err(|v: Vec<u8>| {
226 serde::de::Error::custom(format!("expected 32 bytes, got {}", v.len()))
227 })?;
228 Ok(Self(arr))
229 }
230}
231
232impl fmt::Display for Ed25519PublicKey {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 f.write_str(&hex::encode(self.0))
235 }
236}
237
238impl AsRef<[u8]> for Ed25519PublicKey {
239 fn as_ref(&self) -> &[u8] {
240 &self.0
241 }
242}
243
244#[cfg(feature = "schema")]
245impl schemars::JsonSchema for Ed25519PublicKey {
246 fn schema_name() -> String {
247 "Ed25519PublicKey".to_owned()
248 }
249
250 fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
251 schemars::schema::SchemaObject {
252 instance_type: Some(schemars::schema::InstanceType::String.into()),
253 format: Some("hex".to_owned()),
254 metadata: Some(Box::new(schemars::schema::Metadata {
255 description: Some("Ed25519 public key (32 bytes, hex-encoded)".to_owned()),
256 ..Default::default()
257 })),
258 ..Default::default()
259 }
260 .into()
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
266pub enum Ed25519KeyError {
267 #[error("expected 32 bytes, got {0}")]
269 InvalidLength(usize),
270 #[error("invalid hex: {0}")]
272 InvalidHex(String),
273}
274
275pub const ATTESTATION_VERSION: u32 = 2;
285
286#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct TypedSignature([u8; 64]);
296
297pub type Ed25519Signature = TypedSignature;
299
300impl TypedSignature {
301 pub fn from_bytes(bytes: [u8; 64]) -> Self {
303 Self(bytes)
304 }
305
306 pub fn try_from_slice(slice: &[u8]) -> Result<Self, SignatureLengthError> {
308 let arr: [u8; 64] = slice
309 .try_into()
310 .map_err(|_| SignatureLengthError(slice.len()))?;
311 Ok(Self(arr))
312 }
313
314 pub fn empty() -> Self {
316 Self([0u8; 64])
317 }
318
319 pub fn is_empty(&self) -> bool {
321 self.0 == [0u8; 64]
322 }
323
324 pub fn as_bytes(&self) -> &[u8; 64] {
326 &self.0
327 }
328}
329
330impl Default for TypedSignature {
331 fn default() -> Self {
332 Self::empty()
333 }
334}
335
336impl std::fmt::Display for TypedSignature {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 write!(f, "{}", hex::encode(self.0))
339 }
340}
341
342impl AsRef<[u8]> for TypedSignature {
343 fn as_ref(&self) -> &[u8] {
344 &self.0
345 }
346}
347
348#[cfg(feature = "schema")]
349impl schemars::JsonSchema for TypedSignature {
350 fn schema_name() -> String {
351 "TypedSignature".to_owned()
352 }
353
354 fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
355 schemars::schema::SchemaObject {
356 instance_type: Some(schemars::schema::InstanceType::String.into()),
357 format: Some("hex".to_owned()),
358 metadata: Some(Box::new(schemars::schema::Metadata {
359 description: Some(
360 "Curve-agnostic 64-byte signature (Ed25519 or P-256 r||s, hex-encoded). \
361 Curve is determined by the companion DevicePublicKey."
362 .to_owned(),
363 ),
364 ..Default::default()
365 })),
366 ..Default::default()
367 }
368 .into()
369 }
370}
371
372impl serde::Serialize for TypedSignature {
373 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
374 serializer.serialize_str(&hex::encode(self.0))
375 }
376}
377
378impl<'de> serde::Deserialize<'de> for TypedSignature {
379 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
380 let s = String::deserialize(deserializer)?;
381 if s.is_empty() {
382 return Ok(Self::empty());
383 }
384 let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
385 Self::try_from_slice(&bytes).map_err(serde::de::Error::custom)
386 }
387}
388
389#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
391#[error("expected 64 bytes, got {0}")]
392pub struct SignatureLengthError(pub usize);
393
394#[derive(Debug, Clone)]
404#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
405pub struct DevicePublicKey {
406 #[cfg_attr(feature = "schema", schemars(skip))]
407 curve: auths_crypto::CurveType,
408 #[cfg_attr(feature = "schema", schemars(with = "String"))]
409 bytes: Vec<u8>,
410}
411
412impl DevicePublicKey {
413 fn canonical_sec1(&self) -> std::borrow::Cow<'_, [u8]> {
419 if self.curve == auths_crypto::CurveType::P256
420 && self.bytes.len() == 65
421 && self.bytes[0] == 0x04
422 {
423 let mut compressed = vec![0u8; 33];
424 compressed[0] = if self.bytes[64] & 1 == 0 { 0x02 } else { 0x03 };
425 compressed[1..].copy_from_slice(&self.bytes[1..33]);
426 std::borrow::Cow::Owned(compressed)
427 } else {
428 std::borrow::Cow::Borrowed(&self.bytes)
429 }
430 }
431}
432
433impl PartialEq for DevicePublicKey {
434 fn eq(&self, other: &Self) -> bool {
435 self.curve == other.curve && self.canonical_sec1() == other.canonical_sec1()
436 }
437}
438
439impl Eq for DevicePublicKey {}
440
441impl std::hash::Hash for DevicePublicKey {
442 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
443 self.curve.hash(state);
444 self.canonical_sec1().hash(state);
445 }
446}
447
448#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
450pub enum InvalidKeyError {
451 #[error("invalid key length for {curve}: expected {expected}, got {actual}")]
453 InvalidLength {
454 curve: auths_crypto::CurveType,
456 expected: &'static str,
458 actual: usize,
460 },
461}
462
463impl DevicePublicKey {
464 pub fn try_new(curve: auths_crypto::CurveType, bytes: &[u8]) -> Result<Self, InvalidKeyError> {
475 let valid = match curve {
476 auths_crypto::CurveType::Ed25519 => bytes.len() == 32,
477 auths_crypto::CurveType::P256 => bytes.len() == 33 || bytes.len() == 65,
478 };
479 if !valid {
480 return Err(InvalidKeyError::InvalidLength {
481 curve,
482 expected: match curve {
483 auths_crypto::CurveType::Ed25519 => "32",
484 auths_crypto::CurveType::P256 => "33 or 65",
485 },
486 actual: bytes.len(),
487 });
488 }
489 Ok(Self {
490 curve,
491 bytes: bytes.to_vec(),
492 })
493 }
494
495 pub fn ed25519(bytes: &[u8; 32]) -> Self {
497 Self {
498 curve: auths_crypto::CurveType::Ed25519,
499 bytes: bytes.to_vec(),
500 }
501 }
502
503 pub fn p256(bytes: &[u8]) -> Result<Self, InvalidKeyError> {
507 Self::try_new(auths_crypto::CurveType::P256, bytes)
508 }
509
510 pub fn curve(&self) -> auths_crypto::CurveType {
512 self.curve
513 }
514
515 pub fn as_bytes(&self) -> &[u8] {
517 &self.bytes
518 }
519
520 pub fn is_zero(&self) -> bool {
522 self.bytes.iter().all(|&b| b == 0)
523 }
524
525 pub fn len(&self) -> usize {
527 self.bytes.len()
528 }
529
530 pub fn is_empty(&self) -> bool {
532 self.bytes.is_empty()
533 }
534
535 pub async fn verify(
554 &self,
555 message: &[u8],
556 signature: &[u8],
557 provider: &dyn auths_crypto::CryptoProvider,
558 ) -> Result<(), SignatureVerifyError> {
559 let result = match self.curve {
560 auths_crypto::CurveType::Ed25519 => {
561 provider
562 .verify_ed25519(&self.bytes, message, signature)
563 .await
564 }
565 auths_crypto::CurveType::P256 => {
566 provider.verify_p256(&self.bytes, message, signature).await
567 }
568 };
569 result.map_err(|e| SignatureVerifyError::VerificationFailed(e.to_string()))
570 }
571}
572
573#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
576pub enum PublicKeyDecodeError {
577 #[error("invalid hex: {0}")]
579 InvalidHex(String),
580
581 #[error("invalid public key length {len} — expected 32 (Ed25519) or 33/65 (P-256)")]
584 InvalidLength {
585 len: usize,
587 },
588
589 #[error("DevicePublicKey validation failed: {0}")]
592 Validation(String),
593}
594
595pub fn decode_public_key_hex(
612 hex_str: &str,
613 curve: auths_crypto::CurveType,
614) -> Result<DevicePublicKey, PublicKeyDecodeError> {
615 let bytes =
616 hex::decode(hex_str.trim()).map_err(|e| PublicKeyDecodeError::InvalidHex(e.to_string()))?;
617 decode_public_key_bytes(&bytes, curve)
618}
619
620pub fn decode_public_key_bytes(
632 bytes: &[u8],
633 curve: auths_crypto::CurveType,
634) -> Result<DevicePublicKey, PublicKeyDecodeError> {
635 DevicePublicKey::try_new(curve, bytes)
636 .map_err(|e| PublicKeyDecodeError::Validation(e.to_string()))
637}
638
639#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
641pub enum SignatureVerifyError {
642 #[error("signature verification failed: {0}")]
644 VerificationFailed(String),
645
646 #[error("{0}")]
649 UnsupportedOnTarget(String),
650}
651
652impl From<Ed25519PublicKey> for DevicePublicKey {
653 fn from(pk: Ed25519PublicKey) -> Self {
654 Self {
655 curve: auths_crypto::CurveType::Ed25519,
656 bytes: pk.as_bytes().to_vec(),
657 }
658 }
659}
660
661impl Serialize for DevicePublicKey {
662 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
663 use serde::ser::SerializeStruct;
664 let mut st = s.serialize_struct("DevicePublicKey", 2)?;
665 st.serialize_field("curve", &self.curve.to_string())?;
666 st.serialize_field("key", &hex::encode(&self.bytes))?;
667 st.end()
668 }
669}
670
671impl<'de> Deserialize<'de> for DevicePublicKey {
672 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
673 let value = serde_json::Value::deserialize(d)?;
677
678 if let Some(s) = value.as_str() {
679 if s.is_empty() {
681 return Ok(Self::default());
682 }
683 let bytes = hex::decode(s)
684 .map_err(|e| serde::de::Error::custom(format!("invalid hex: {e}")))?;
685 let curve = match bytes.len() {
686 32 => auths_crypto::CurveType::Ed25519,
687 33 | 65 => auths_crypto::CurveType::P256,
688 n => {
689 return Err(serde::de::Error::custom(format!(
690 "invalid device public key length: {n}"
691 )));
692 }
693 };
694 return Self::try_new(curve, &bytes)
695 .map_err(|e| serde::de::Error::custom(e.to_string()));
696 }
697
698 let curve_str = value["curve"]
700 .as_str()
701 .ok_or_else(|| serde::de::Error::custom("missing 'curve' field"))?;
702 let key_hex = value["key"]
703 .as_str()
704 .ok_or_else(|| serde::de::Error::custom("missing 'key' field"))?;
705
706 let curve = match curve_str {
707 "ed25519" => auths_crypto::CurveType::Ed25519,
708 "p256" => auths_crypto::CurveType::P256,
709 other => {
710 return Err(serde::de::Error::custom(format!("unknown curve: {other}")));
711 }
712 };
713 if key_hex.is_empty() {
714 return Err(serde::de::Error::custom("empty key"));
715 }
716 let bytes = hex::decode(key_hex)
717 .map_err(|e| serde::de::Error::custom(format!("invalid hex: {e}")))?;
718 Self::try_new(curve, &bytes).map_err(|e| serde::de::Error::custom(e.to_string()))
719 }
720}
721
722impl Default for DevicePublicKey {
723 fn default() -> Self {
724 Self {
725 curve: auths_crypto::CurveType::Ed25519,
726 bytes: vec![0u8; 32],
727 }
728 }
729}
730
731impl fmt::Display for DevicePublicKey {
732 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733 write!(f, "{}:{}", self.curve, hex::encode(&self.bytes))
734 }
735}
736
737#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
754#[serde(rename_all = "snake_case")]
755pub enum SignatureAlgorithm {
756 #[default]
758 Ed25519,
759 EcdsaP256,
761}
762
763impl fmt::Display for SignatureAlgorithm {
764 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
765 match self {
766 Self::Ed25519 => f.write_str("ed25519"),
767 Self::EcdsaP256 => f.write_str("ecdsa_p256"),
768 }
769 }
770}
771
772#[derive(Debug, Clone, PartialEq, Eq)]
786pub struct EcdsaP256PublicKey(Vec<u8>);
787
788impl EcdsaP256PublicKey {
789 pub fn from_der(der: &[u8]) -> Result<Self, EcdsaP256Error> {
794 const P256_OID: &[u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07];
797 if der.len() < 26 {
798 return Err(EcdsaP256Error::InvalidKey(
799 "DER too short for P-256 PKIX key".into(),
800 ));
801 }
802 if !der.windows(P256_OID.len()).any(|w| w == P256_OID) {
803 return Err(EcdsaP256Error::InvalidKey(
804 "missing P-256 OID in PKIX key".into(),
805 ));
806 }
807 Ok(Self(der.to_vec()))
808 }
809
810 pub fn as_der(&self) -> &[u8] {
812 &self.0
813 }
814
815 pub fn as_sec1_uncompressed(&self) -> Option<&[u8]> {
830 let start = self.0.len().checked_sub(65)?;
831 let point = &self.0[start..];
832 (point[0] == 0x04).then_some(point)
833 }
834}
835
836impl Serialize for EcdsaP256PublicKey {
837 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
838 use base64::Engine;
839 s.serialize_str(&base64::engine::general_purpose::STANDARD.encode(&self.0))
840 }
841}
842
843impl<'de> Deserialize<'de> for EcdsaP256PublicKey {
844 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
845 use base64::Engine;
846 let s = String::deserialize(d)?;
847 let bytes = base64::engine::general_purpose::STANDARD
848 .decode(&s)
849 .map_err(|e| serde::de::Error::custom(format!("invalid base64: {e}")))?;
850 Self::from_der(&bytes).map_err(serde::de::Error::custom)
851 }
852}
853
854#[derive(Debug, Clone, PartialEq, Eq)]
863pub struct EcdsaP256Signature(Vec<u8>);
864
865impl EcdsaP256Signature {
866 pub fn from_der(der: &[u8]) -> Result<Self, EcdsaP256Error> {
868 if der.is_empty() || der[0] != 0x30 {
870 return Err(EcdsaP256Error::InvalidSignature(
871 "not an ASN.1 SEQUENCE".into(),
872 ));
873 }
874 Ok(Self(der.to_vec()))
875 }
876
877 pub fn as_der(&self) -> &[u8] {
879 &self.0
880 }
881}
882
883impl Serialize for EcdsaP256Signature {
884 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
885 use base64::Engine;
886 s.serialize_str(&base64::engine::general_purpose::STANDARD.encode(&self.0))
887 }
888}
889
890impl<'de> Deserialize<'de> for EcdsaP256Signature {
891 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
892 use base64::Engine;
893 let s = String::deserialize(d)?;
894 let bytes = base64::engine::general_purpose::STANDARD
895 .decode(&s)
896 .map_err(|e| serde::de::Error::custom(format!("invalid base64: {e}")))?;
897 Self::from_der(&bytes).map_err(serde::de::Error::custom)
898 }
899}
900
901#[derive(Debug, Clone, thiserror::Error)]
903pub enum EcdsaP256Error {
904 #[error("invalid ECDSA P-256 key: {0}")]
906 InvalidKey(String),
907 #[error("invalid ECDSA P-256 signature: {0}")]
909 InvalidSignature(String),
910}
911
912#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
917#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
918pub struct IdentityBundle {
919 pub identity_did: IdentityDID,
921 pub public_key_hex: PublicKeyHex,
923 #[serde(default)]
927 #[cfg_attr(feature = "schema", schemars(with = "String"))]
928 pub curve: auths_crypto::CurveType,
929 pub attestation_chain: Vec<Attestation>,
931 #[serde(default, skip_serializing_if = "Vec::is_empty")]
937 pub kel: Vec<auths_keri::Event>,
938 #[serde(default, skip_serializing_if = "Vec::is_empty")]
945 pub kel_attachments: Vec<String>,
946 pub bundle_timestamp: DateTime<Utc>,
948 pub max_valid_for_secs: u64,
950}
951
952impl IdentityBundle {
953 pub fn check_freshness(&self, now: DateTime<Utc>) -> Result<(), AttestationError> {
963 let age = (now - self.bundle_timestamp).num_seconds().max(0) as u64;
964 if age > self.max_valid_for_secs {
965 return Err(AttestationError::BundleExpired {
966 age_secs: age,
967 max_secs: self.max_valid_for_secs,
968 });
969 }
970 Ok(())
971 }
972}
973
974#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
976#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
977pub struct Attestation {
978 pub version: u32,
980 pub rid: ResourceId,
982 pub issuer: CanonicalDid,
984 pub subject: CanonicalDid,
986 pub device_public_key: DevicePublicKey,
988 #[serde(default, skip_serializing_if = "Ed25519Signature::is_empty")]
990 pub identity_signature: Ed25519Signature,
991 pub device_signature: Ed25519Signature,
993 #[serde(default, skip_serializing_if = "Option::is_none")]
995 pub revoked_at: Option<DateTime<Utc>>,
996 #[serde(skip_serializing_if = "Option::is_none")]
998 pub expires_at: Option<DateTime<Utc>>,
999 pub timestamp: Option<DateTime<Utc>>,
1001 #[serde(skip_serializing_if = "Option::is_none")]
1003 pub note: Option<String>,
1004 #[serde(skip_serializing_if = "Option::is_none")]
1006 pub payload: Option<Value>,
1007
1008 #[serde(skip_serializing_if = "Option::is_none")]
1010 pub commit_sha: Option<String>,
1011
1012 #[serde(skip_serializing_if = "Option::is_none")]
1014 pub commit_message: Option<String>,
1015
1016 #[serde(skip_serializing_if = "Option::is_none")]
1018 pub author: Option<String>,
1019
1020 #[serde(skip_serializing_if = "Option::is_none")]
1022 pub oidc_binding: Option<OidcBinding>,
1023
1024 #[serde(default, skip_serializing_if = "Option::is_none")]
1026 pub delegated_by: Option<CanonicalDid>,
1027
1028 #[serde(default, skip_serializing_if = "Option::is_none")]
1031 pub signer_type: Option<SignerType>,
1032
1033 #[serde(default, skip_serializing_if = "Option::is_none")]
1036 pub environment_claim: Option<Value>,
1037}
1038
1039#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1045#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1046pub struct OidcBinding {
1047 pub issuer: String,
1049 pub subject: String,
1051 pub audience: String,
1053 pub token_exp: i64,
1055 #[serde(default, skip_serializing_if = "Option::is_none")]
1057 pub platform: Option<String>,
1058 #[serde(default, skip_serializing_if = "Option::is_none")]
1060 pub jti: Option<String>,
1061 #[serde(default, skip_serializing_if = "Option::is_none")]
1063 pub normalized_claims: Option<serde_json::Map<String, serde_json::Value>>,
1064}
1065
1066#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1071#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1072#[non_exhaustive]
1073pub enum SignerType {
1074 Human,
1076 Agent,
1078 Workload,
1080}
1081
1082#[derive(Debug, Clone, Serialize)]
1092pub struct VerifiedAttestation(Attestation);
1093
1094impl VerifiedAttestation {
1095 pub fn inner(&self) -> &Attestation {
1097 &self.0
1098 }
1099
1100 pub fn into_inner(self) -> Attestation {
1102 self.0
1103 }
1104
1105 #[doc(hidden)]
1111 pub fn dangerous_from_unchecked(attestation: Attestation) -> Self {
1112 Self(attestation)
1113 }
1114
1115 pub(crate) fn from_verified(attestation: Attestation) -> Self {
1116 Self(attestation)
1117 }
1118}
1119
1120impl std::ops::Deref for VerifiedAttestation {
1121 type Target = Attestation;
1122
1123 fn deref(&self) -> &Attestation {
1124 &self.0
1125 }
1126}
1127
1128#[derive(Serialize, Debug)]
1130pub struct CanonicalAttestationData<'a> {
1131 pub version: u32,
1133 pub rid: &'a str,
1135 pub issuer: &'a CanonicalDid,
1137 pub subject: &'a CanonicalDid,
1139 #[serde(with = "hex::serde")]
1141 pub device_public_key: &'a [u8],
1142 pub payload: &'a Option<Value>,
1144 pub timestamp: &'a Option<DateTime<Utc>>,
1146 pub expires_at: &'a Option<DateTime<Utc>>,
1148 pub revoked_at: &'a Option<DateTime<Utc>>,
1150 pub note: &'a Option<String>,
1152
1153 #[serde(skip_serializing_if = "Option::is_none")]
1155 pub delegated_by: Option<&'a CanonicalDid>,
1156 #[serde(skip_serializing_if = "Option::is_none")]
1158 pub signer_type: Option<&'a SignerType>,
1159 #[serde(skip_serializing_if = "Option::is_none")]
1161 pub commit_sha: Option<&'a str>,
1162 #[serde(skip_serializing_if = "Option::is_none")]
1167 pub oidc_binding: Option<&'a OidcBinding>,
1168}
1169
1170pub fn canonicalize_attestation_data(
1175 data: &CanonicalAttestationData,
1176) -> Result<Vec<u8>, AttestationError> {
1177 let canonical_json_string = json_canon::to_string(data).map_err(|e| {
1178 AttestationError::SerializationError(format!("Failed to create canonical JSON: {}", e))
1179 })?;
1180 debug!(
1181 "Generated canonical data (standard): {}",
1182 canonical_json_string
1183 );
1184 Ok(canonical_json_string.into_bytes())
1185}
1186
1187impl Attestation {
1188 pub fn is_revoked(&self) -> bool {
1190 self.revoked_at.is_some()
1191 }
1192
1193 pub fn from_json(json_bytes: &[u8]) -> Result<Self, AttestationError> {
1197 if json_bytes.len() > MAX_ATTESTATION_JSON_SIZE {
1198 return Err(AttestationError::InputTooLarge(format!(
1199 "attestation JSON is {} bytes, max {}",
1200 json_bytes.len(),
1201 MAX_ATTESTATION_JSON_SIZE
1202 )));
1203 }
1204 serde_json::from_slice(json_bytes)
1205 .map_err(|e| AttestationError::SerializationError(e.to_string()))
1206 }
1207
1208 pub fn canonical_data(&self) -> CanonicalAttestationData<'_> {
1219 CanonicalAttestationData {
1220 version: self.version,
1221 rid: &self.rid,
1222 issuer: &self.issuer,
1223 subject: &self.subject,
1224 device_public_key: self.device_public_key.as_bytes(),
1225 payload: &self.payload,
1226 timestamp: &self.timestamp,
1227 expires_at: &self.expires_at,
1228 revoked_at: &self.revoked_at,
1229 note: &self.note,
1230 delegated_by: self.delegated_by.as_ref(),
1231 signer_type: self.signer_type.as_ref(),
1232 commit_sha: self.commit_sha.as_deref(),
1233 oidc_binding: self.oidc_binding.as_ref(),
1234 }
1235 }
1236
1237 pub fn to_debug_string(&self) -> String {
1239 format!(
1240 "RID: {}\nIssuer DID: {}\nSubject DID: {}\nDevice PK: {}\nIdentity Sig: {}\nDevice Sig: {}\nRevoked At: {:?}\nExpires: {:?}\nNote: {:?}",
1241 self.rid,
1242 self.issuer,
1243 self.subject, hex::encode(self.device_public_key.as_bytes()),
1245 hex::encode(self.identity_signature.as_bytes()),
1246 hex::encode(self.device_signature.as_bytes()),
1247 self.revoked_at,
1248 self.expires_at,
1249 self.note
1250 )
1251 }
1252}
1253
1254#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1321#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1322pub struct ThresholdPolicy {
1323 pub threshold: u8,
1325
1326 pub signers: Vec<String>,
1328
1329 pub policy_id: PolicyId,
1331
1332 #[serde(default, skip_serializing_if = "Option::is_none")]
1334 pub scope: Option<Capability>,
1335
1336 #[serde(default, skip_serializing_if = "Option::is_none")]
1338 pub ceremony_endpoint: Option<String>,
1339}
1340
1341impl ThresholdPolicy {
1342 pub fn new(threshold: u8, signers: Vec<String>, policy_id: impl Into<PolicyId>) -> Self {
1344 Self {
1345 threshold,
1346 signers,
1347 policy_id: policy_id.into(),
1348 scope: None,
1349 ceremony_endpoint: None,
1350 }
1351 }
1352
1353 pub fn is_valid(&self) -> bool {
1355 if self.threshold < 1 {
1357 return false;
1358 }
1359 if self.threshold as usize > self.signers.len() {
1361 return false;
1362 }
1363 if self.signers.is_empty() {
1365 return false;
1366 }
1367 if self.policy_id.is_empty() {
1369 return false;
1370 }
1371 true
1372 }
1373
1374 pub fn m_of_n(&self) -> (u8, usize) {
1376 (self.threshold, self.signers.len())
1377 }
1378}
1379
1380#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1386pub enum CommitOidError {
1387 #[error("commit OID is empty")]
1389 Empty,
1390 #[error("expected 40 or 64 hex chars, got {0}")]
1392 InvalidLength(usize),
1393 #[error("invalid hex character in commit OID")]
1395 InvalidHex,
1396}
1397
1398#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
1402#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1403#[repr(transparent)]
1404#[serde(try_from = "String")]
1405pub struct CommitOid(String);
1406
1407impl CommitOid {
1408 pub fn parse(raw: &str) -> Result<Self, CommitOidError> {
1418 let s = raw.trim().to_lowercase();
1419 if s.is_empty() {
1420 return Err(CommitOidError::Empty);
1421 }
1422 if s.len() != 40 && s.len() != 64 {
1423 return Err(CommitOidError::InvalidLength(s.len()));
1424 }
1425 if !s.chars().all(|c| c.is_ascii_hexdigit()) {
1426 return Err(CommitOidError::InvalidHex);
1427 }
1428 Ok(Self(s))
1429 }
1430
1431 pub fn new_unchecked(s: impl Into<String>) -> Self {
1435 Self(s.into())
1436 }
1437
1438 pub fn as_str(&self) -> &str {
1440 &self.0
1441 }
1442
1443 pub fn into_inner(self) -> String {
1445 self.0
1446 }
1447}
1448
1449impl fmt::Display for CommitOid {
1450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1451 f.write_str(&self.0)
1452 }
1453}
1454
1455impl AsRef<str> for CommitOid {
1456 fn as_ref(&self) -> &str {
1457 &self.0
1458 }
1459}
1460
1461impl TryFrom<String> for CommitOid {
1462 type Error = CommitOidError;
1463 fn try_from(s: String) -> Result<Self, Self::Error> {
1464 Self::parse(&s)
1465 }
1466}
1467
1468impl TryFrom<&str> for CommitOid {
1469 type Error = CommitOidError;
1470 fn try_from(s: &str) -> Result<Self, Self::Error> {
1471 Self::parse(s)
1472 }
1473}
1474
1475impl FromStr for CommitOid {
1476 type Err = CommitOidError;
1477 fn from_str(s: &str) -> Result<Self, Self::Err> {
1478 Self::parse(s)
1479 }
1480}
1481
1482impl From<CommitOid> for String {
1483 fn from(oid: CommitOid) -> Self {
1484 oid.0
1485 }
1486}
1487
1488impl<'de> Deserialize<'de> for CommitOid {
1489 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1490 let s = String::deserialize(d)?;
1491 Self::parse(&s).map_err(serde::de::Error::custom)
1492 }
1493}
1494
1495#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1501pub enum PublicKeyHexError {
1502 #[error("expected 64 (Ed25519) or 66 (P-256) hex chars, got {0} chars")]
1504 InvalidLength(usize),
1505 #[error("invalid hex: {0}")]
1507 InvalidHex(String),
1508}
1509
1510#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
1512#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1513#[repr(transparent)]
1514#[serde(try_from = "String")]
1515pub struct PublicKeyHex(String);
1516
1517impl PublicKeyHex {
1518 pub fn parse(raw: &str) -> Result<Self, PublicKeyHexError> {
1528 let s = raw.trim().to_lowercase();
1529 let bytes = hex::decode(&s).map_err(|e| PublicKeyHexError::InvalidHex(e.to_string()))?;
1530 if bytes.len() != 32 && bytes.len() != 33 {
1531 return Err(PublicKeyHexError::InvalidLength(s.len()));
1532 }
1533 Ok(Self(s))
1534 }
1535
1536 pub fn new_unchecked(s: impl Into<String>) -> Self {
1540 Self(s.into())
1541 }
1542
1543 pub fn as_str(&self) -> &str {
1545 &self.0
1546 }
1547
1548 pub fn into_inner(self) -> String {
1550 self.0
1551 }
1552}
1553
1554impl fmt::Display for PublicKeyHex {
1555 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1556 f.write_str(&self.0)
1557 }
1558}
1559
1560impl AsRef<str> for PublicKeyHex {
1561 fn as_ref(&self) -> &str {
1562 &self.0
1563 }
1564}
1565
1566impl TryFrom<String> for PublicKeyHex {
1567 type Error = PublicKeyHexError;
1568 fn try_from(s: String) -> Result<Self, Self::Error> {
1569 Self::parse(&s)
1570 }
1571}
1572
1573impl TryFrom<&str> for PublicKeyHex {
1574 type Error = PublicKeyHexError;
1575 fn try_from(s: &str) -> Result<Self, Self::Error> {
1576 Self::parse(s)
1577 }
1578}
1579
1580impl FromStr for PublicKeyHex {
1581 type Err = PublicKeyHexError;
1582 fn from_str(s: &str) -> Result<Self, Self::Err> {
1583 Self::parse(s)
1584 }
1585}
1586
1587impl From<PublicKeyHex> for String {
1588 fn from(pk: PublicKeyHex) -> Self {
1589 pk.0
1590 }
1591}
1592
1593impl<'de> Deserialize<'de> for PublicKeyHex {
1594 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1595 let s = String::deserialize(d)?;
1596 Self::parse(&s).map_err(serde::de::Error::custom)
1597 }
1598}
1599
1600#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1609#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1610#[serde(transparent)]
1611pub struct PolicyId(String);
1612
1613impl PolicyId {
1614 pub fn new(s: impl Into<String>) -> Self {
1616 Self(s.into())
1617 }
1618
1619 pub fn as_str(&self) -> &str {
1621 &self.0
1622 }
1623}
1624
1625impl Deref for PolicyId {
1626 type Target = str;
1627 fn deref(&self) -> &str {
1628 &self.0
1629 }
1630}
1631
1632impl fmt::Display for PolicyId {
1633 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1634 f.write_str(&self.0)
1635 }
1636}
1637
1638impl From<String> for PolicyId {
1639 fn from(s: String) -> Self {
1640 Self(s)
1641 }
1642}
1643
1644impl From<&str> for PolicyId {
1645 fn from(s: &str) -> Self {
1646 Self(s.to_string())
1647 }
1648}
1649
1650impl PartialEq<str> for PolicyId {
1651 fn eq(&self, other: &str) -> bool {
1652 self.0 == other
1653 }
1654}
1655
1656impl PartialEq<&str> for PolicyId {
1657 fn eq(&self, other: &&str) -> bool {
1658 self.0 == *other
1659 }
1660}
1661
1662#[cfg(test)]
1663#[allow(clippy::disallowed_methods)]
1664mod tests {
1665 use super::*;
1666 use crate::AttestationBuilder;
1667
1668 #[test]
1671 fn attestation_old_json_without_delegated_by_deserializes() {
1672 let old_json = r#"{
1674 "version": 1,
1675 "rid": "test-rid",
1676 "issuer": "did:keri:Eissuer",
1677 "subject": "did:key:zSubject",
1678 "device_public_key": "0102030405060708091011121314151617181920212223242526272829303132",
1679 "identity_signature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1680 "device_signature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1681 "revoked_at": null,
1682 "timestamp": null
1683 }"#;
1684
1685 let att: Attestation = serde_json::from_str(old_json).unwrap();
1686
1687 assert_eq!(att.delegated_by, None);
1688 }
1689
1690 #[test]
1691 fn attestation_delegated_by_serializes_correctly() {
1692 let att = AttestationBuilder::default()
1693 .rid("test-rid")
1694 .issuer("did:keri:Eissuer")
1695 .subject("did:key:zSubject")
1696 .delegated_by(Some(CanonicalDid::new_unchecked("did:keri:Edelegator")))
1697 .build();
1698
1699 let json = serde_json::to_string(&att).unwrap();
1700 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1701
1702 assert_eq!(parsed["delegated_by"], "did:keri:Edelegator");
1703 }
1704
1705 #[test]
1706 fn attestation_omits_delegated_by_when_absent() {
1707 let att = AttestationBuilder::default()
1708 .rid("test-rid")
1709 .issuer("did:keri:Eissuer")
1710 .subject("did:key:zSubject")
1711 .build();
1712
1713 let json = serde_json::to_string(&att).unwrap();
1714 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1715
1716 assert!(parsed.get("delegated_by").is_none());
1717 }
1718
1719 #[test]
1720 fn attestation_delegated_by_roundtrips() {
1721 let original = AttestationBuilder::default()
1722 .rid("test-rid")
1723 .issuer("did:keri:Eissuer")
1724 .subject("did:key:zSubject")
1725 .delegated_by(Some(CanonicalDid::new_unchecked("did:keri:Eadmin")))
1726 .build();
1727
1728 let json = serde_json::to_string(&original).unwrap();
1729 let deserialized: Attestation = serde_json::from_str(&json).unwrap();
1730
1731 assert_eq!(original.delegated_by, deserialized.delegated_by);
1732 }
1733
1734 #[test]
1737 fn threshold_policy_new_creates_valid_policy() {
1738 let policy = ThresholdPolicy::new(
1739 2,
1740 vec![
1741 "did:key:alice".to_string(),
1742 "did:key:bob".to_string(),
1743 "did:key:carol".to_string(),
1744 ],
1745 "test-policy".to_string(),
1746 );
1747
1748 assert_eq!(policy.threshold, 2);
1749 assert_eq!(policy.signers.len(), 3);
1750 assert_eq!(policy.policy_id, "test-policy");
1751 assert!(policy.scope.is_none());
1752 assert!(policy.ceremony_endpoint.is_none());
1753 }
1754
1755 #[test]
1756 fn threshold_policy_is_valid_checks_constraints() {
1757 let valid = ThresholdPolicy::new(
1759 2,
1760 vec!["a".to_string(), "b".to_string(), "c".to_string()],
1761 "policy".to_string(),
1762 );
1763 assert!(valid.is_valid());
1764
1765 let zero_threshold = ThresholdPolicy::new(0, vec!["a".to_string()], "policy".to_string());
1767 assert!(!zero_threshold.is_valid());
1768
1769 let too_high = ThresholdPolicy::new(
1771 3,
1772 vec!["a".to_string(), "b".to_string()],
1773 "policy".to_string(),
1774 );
1775 assert!(!too_high.is_valid());
1776
1777 let no_signers = ThresholdPolicy::new(1, vec![], "policy".to_string());
1779 assert!(!no_signers.is_valid());
1780
1781 let no_id = ThresholdPolicy::new(1, vec!["a".to_string()], "".to_string());
1783 assert!(!no_id.is_valid());
1784 }
1785
1786 #[test]
1787 fn threshold_policy_m_of_n_returns_correct_values() {
1788 let policy = ThresholdPolicy::new(
1789 2,
1790 vec!["a".to_string(), "b".to_string(), "c".to_string()],
1791 "policy".to_string(),
1792 );
1793 let (m, n) = policy.m_of_n();
1794 assert_eq!(m, 2);
1795 assert_eq!(n, 3);
1796 }
1797
1798 #[test]
1799 fn threshold_policy_serializes_correctly() {
1800 let mut policy = ThresholdPolicy::new(
1801 2,
1802 vec!["did:key:alice".to_string(), "did:key:bob".to_string()],
1803 "release-policy".to_string(),
1804 );
1805 policy.scope = Some(Capability::sign_release());
1806 policy.ceremony_endpoint = Some("wss://example.com/ceremony".to_string());
1807
1808 let json = serde_json::to_string(&policy).unwrap();
1809 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1810
1811 assert_eq!(parsed["threshold"], 2);
1812 assert_eq!(parsed["signers"][0], "did:key:alice");
1813 assert_eq!(parsed["policy_id"], "release-policy");
1814 assert_eq!(parsed["scope"], "sign_release");
1815 assert_eq!(parsed["ceremony_endpoint"], "wss://example.com/ceremony");
1816 }
1817
1818 #[test]
1819 fn threshold_policy_without_optional_fields_omits_them() {
1820 let policy =
1821 ThresholdPolicy::new(1, vec!["did:key:alice".to_string()], "policy".to_string());
1822
1823 let json = serde_json::to_string(&policy).unwrap();
1824 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1825
1826 assert!(parsed.get("scope").is_none());
1827 assert!(parsed.get("ceremony_endpoint").is_none());
1828 }
1829
1830 #[test]
1831 fn threshold_policy_roundtrips() {
1832 let mut original = ThresholdPolicy::new(
1833 3,
1834 vec![
1835 "a".to_string(),
1836 "b".to_string(),
1837 "c".to_string(),
1838 "d".to_string(),
1839 ],
1840 "important-policy".to_string(),
1841 );
1842 original.scope = Some(Capability::rotate_keys());
1843
1844 let json = serde_json::to_string(&original).unwrap();
1845 let deserialized: ThresholdPolicy = serde_json::from_str(&json).unwrap();
1846
1847 assert_eq!(original, deserialized);
1848 }
1849
1850 #[test]
1853 fn identity_bundle_serializes_correctly() {
1854 let bundle = IdentityBundle {
1855 identity_did: IdentityDID::new_unchecked("did:keri:test123"),
1856 public_key_hex: PublicKeyHex::new_unchecked("aabbccdd"),
1857 curve: Default::default(),
1858 attestation_chain: vec![],
1859 kel: vec![],
1860 kel_attachments: vec![],
1861 bundle_timestamp: DateTime::parse_from_rfc3339("2099-01-01T00:00:00Z")
1862 .unwrap()
1863 .with_timezone(&Utc),
1864 max_valid_for_secs: 86400,
1865 };
1866
1867 let json = serde_json::to_string(&bundle).unwrap();
1868 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1869
1870 assert_eq!(parsed["identity_did"], "did:keri:test123");
1871 assert_eq!(parsed["public_key_hex"], "aabbccdd");
1872 assert!(parsed["attestation_chain"].as_array().unwrap().is_empty());
1873 }
1874
1875 #[test]
1876 fn identity_bundle_deserializes_correctly() {
1877 let json = r#"{
1878 "identity_did": "did:keri:abc123",
1879 "public_key_hex": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
1880 "attestation_chain": [],
1881 "bundle_timestamp": "2099-01-01T00:00:00Z",
1882 "max_valid_for_secs": 86400
1883 }"#;
1884
1885 let bundle: IdentityBundle = serde_json::from_str(json).unwrap();
1886
1887 assert_eq!(bundle.identity_did.as_str(), "did:keri:abc123");
1888 assert_eq!(
1889 bundle.public_key_hex.as_str(),
1890 "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
1891 );
1892 assert!(bundle.attestation_chain.is_empty());
1893 }
1894
1895 #[test]
1896 fn identity_bundle_roundtrips() {
1897 let attestation = AttestationBuilder::default()
1898 .rid("test-rid")
1899 .issuer("did:keri:Eissuer")
1900 .subject("did:key:zSubject")
1901 .build();
1902
1903 let original = IdentityBundle {
1904 identity_did: IdentityDID::new_unchecked("did:keri:Eexample"),
1905 public_key_hex: PublicKeyHex::new_unchecked(
1906 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
1907 ),
1908 curve: Default::default(),
1909 attestation_chain: vec![attestation],
1910 kel: vec![],
1911 kel_attachments: vec![],
1912 bundle_timestamp: DateTime::parse_from_rfc3339("2099-01-01T00:00:00Z")
1913 .unwrap()
1914 .with_timezone(&Utc),
1915 max_valid_for_secs: 86400,
1916 };
1917
1918 let json = serde_json::to_string(&original).unwrap();
1919 let deserialized: IdentityBundle = serde_json::from_str(&json).unwrap();
1920
1921 assert_eq!(original.identity_did, deserialized.identity_did);
1922 assert_eq!(original.public_key_hex, deserialized.public_key_hex);
1923 assert_eq!(
1924 original.attestation_chain.len(),
1925 deserialized.attestation_chain.len()
1926 );
1927 }
1928}
1929
1930#[cfg(test)]
1931mod decode_public_key_tests {
1932 use super::*;
1933
1934 #[test]
1935 fn hex_ed25519_32_bytes() {
1936 let hex = "00".repeat(32);
1937 let pk = decode_public_key_hex(&hex, auths_crypto::CurveType::Ed25519).unwrap();
1938 assert_eq!(pk.curve(), auths_crypto::CurveType::Ed25519);
1939 assert_eq!(pk.len(), 32);
1940 }
1941
1942 #[test]
1943 fn hex_p256_33_bytes_compressed() {
1944 let mut bytes = [0u8; 33];
1946 bytes[0] = 0x02;
1947 let hex = hex::encode(bytes);
1948 let pk = decode_public_key_hex(&hex, auths_crypto::CurveType::P256).unwrap();
1949 assert_eq!(pk.curve(), auths_crypto::CurveType::P256);
1950 assert_eq!(pk.len(), 33);
1951 }
1952
1953 #[test]
1954 fn bytes_p256_65_uncompressed() {
1955 let mut bytes = [0u8; 65];
1956 bytes[0] = 0x04;
1957 let pk = decode_public_key_bytes(&bytes, auths_crypto::CurveType::P256).unwrap();
1958 assert_eq!(pk.curve(), auths_crypto::CurveType::P256);
1959 assert_eq!(pk.len(), 65);
1960 }
1961
1962 #[test]
1963 fn rejects_validation_error() {
1964 let err = decode_public_key_bytes(&[0u8; 50], auths_crypto::CurveType::P256).unwrap_err();
1965 assert!(matches!(err, PublicKeyDecodeError::Validation(_)));
1966 }
1967
1968 #[test]
1969 fn rejects_malformed_hex() {
1970 let err = decode_public_key_hex("zz", auths_crypto::CurveType::P256).unwrap_err();
1971 assert!(matches!(err, PublicKeyDecodeError::InvalidHex(_)));
1972 }
1973}