A library for ATProtocol identities.
22
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: Removed issuedAt field as required signature field.

Signed-off-by: Nick Gerakines <nick.gerakines@gmail.com>

+358 -128
+81 -24
crates/atproto-record/README.md
··· 1 1 # atproto-record 2 2 3 - Cryptographic signature operations for AT Protocol records. 3 + Cryptographic signature operations and utilities for AT Protocol records. 4 4 5 5 ## Overview 6 6 7 - Sign and verify AT Protocol records using IPLD DAG-CBOR serialization with support for P-256, P-384, and K-256 cryptographic signatures. Includes AT-URI parsing for record identification. 7 + A comprehensive Rust library for working with AT Protocol records, providing cryptographic signature creation and verification, AT-URI parsing, and datetime utilities. Built on IPLD DAG-CBOR serialization with support for P-256, P-384, and K-256 elliptic curve cryptography. 8 8 9 9 ## Features 10 10 11 - - **Record signing**: Create cryptographic signatures on AT Protocol records with proper $sig object handling 12 - - **Signature verification**: Verify record signatures against public keys and issuer DIDs 13 - - **AT-URI parsing**: Parse and validate AT Protocol URIs (at://authority/collection/record_key) 14 - - **IPLD serialization**: DAG-CBOR serialization for signature consistency and integrity 15 - - **Multi-curve support**: Support for P-256, P-384, and K-256 elliptic curve signatures 16 - - **Structured errors**: Comprehensive error handling with detailed error types 11 + - **Record signing**: Create cryptographic signatures on AT Protocol records following community.lexicon.attestation.signature specification 12 + - **Signature verification**: Verify record signatures against public keys with issuer validation 13 + - **AT-URI parsing**: Parse and validate AT Protocol URIs (at://authority/collection/record_key) with robust error handling 14 + - **IPLD serialization**: DAG-CBOR serialization ensuring deterministic and verifiable record encoding 15 + - **Multi-curve support**: Full support for P-256, P-384, and K-256 elliptic curve signatures 16 + - **DateTime utilities**: RFC 3339 datetime serialization with millisecond precision for consistent timestamp handling 17 + - **Structured errors**: Type-safe error handling following project conventions with detailed error messages 17 18 18 19 ## CLI Tools 19 20 20 21 The following command-line tools are available when built with the `clap` feature: 21 22 22 - - **`atproto-record-sign`**: Sign AT Protocol records with private keys and create signature objects 23 - - **`atproto-record-verify`**: Verify AT Protocol record signatures against public keys and issuers 23 + - **`atproto-record-sign`**: Sign AT Protocol records with private keys, supporting flexible argument ordering 24 + - **`atproto-record-verify`**: Verify AT Protocol record signatures by validating cryptographic signatures against issuer DIDs and public keys 24 25 25 26 ## Library Usage 26 27 ··· 31 32 use atproto_identity::key::identify_key; 32 33 use serde_json::json; 33 34 35 + // Parse the signing key from a did:key 34 36 let key_data = identify_key("did:key:zQ3sh...")?; 35 - let record = json!({"$type": "app.bsky.feed.post", "text": "Hello!"}); 36 - let signature_object = json!({"issuer": "did:plc:issuer", "issuedAt": "2024-01-01T00:00:00Z"}); 37 + 38 + // The record to sign 39 + let record = json!({"$type": "app.bsky.feed.post", "text": "Hello world!"}); 40 + 41 + // Signature metadata (issuer is required, other fields are optional) 42 + let signature_object = json!({ 43 + "issuer": "did:plc:issuer" 44 + // Optional: "issuedAt", "purpose", "expiry", etc. 45 + }); 37 46 47 + // Create the signed record with embedded signatures array 38 48 let signed_record = signature::create( 39 49 &key_data, 40 50 &record, 41 - "did:plc:repo", 51 + "did:plc:repository", 42 52 "app.bsky.feed.post", 43 53 signature_object 44 54 ).await?; ··· 47 57 ### Verifying Signatures 48 58 49 59 ```rust 60 + use atproto_record::signature; 61 + use atproto_identity::key::identify_key; 62 + 63 + // Parse the public key for verification 50 64 let issuer_key = identify_key("did:key:zQ3sh...")?; 51 65 66 + // Verify the signature (throws error if invalid) 52 67 signature::verify( 53 - "did:plc:issuer", 54 - &issuer_key, 55 - signed_record, 56 - "did:plc:repo", 57 - "app.bsky.feed.post" 68 + "did:plc:issuer", // Expected issuer DID 69 + &issuer_key, // Public key for verification 70 + signed_record, // The signed record 71 + "did:plc:repository", // Repository context 72 + "app.bsky.feed.post" // Collection context 58 73 ).await?; 59 74 ``` 60 75 ··· 64 79 use atproto_record::aturi::ATURI; 65 80 use std::str::FromStr; 66 81 82 + // Parse an AT-URI into its components 67 83 let aturi = ATURI::from_str("at://did:plc:abc123/app.bsky.feed.post/3k2k4j5h6g")?; 68 - println!("Authority: {}", aturi.authority); 69 - println!("Collection: {}", aturi.collection); 70 - println!("Record Key: {}", aturi.record_key); 84 + 85 + // Access the parsed components 86 + println!("Authority: {}", aturi.authority); // "did:plc:abc123" 87 + println!("Collection: {}", aturi.collection); // "app.bsky.feed.post" 88 + println!("Record Key: {}", aturi.record_key); // "3k2k4j5h6g" 89 + 90 + // The Display trait formats back to a valid AT-URI 91 + println!("Full URI: {}", aturi); // "at://did:plc:abc123/app.bsky.feed.post/3k2k4j5h6g" 92 + ``` 93 + 94 + ### DateTime Utilities 95 + 96 + ```rust 97 + use chrono::{DateTime, Utc}; 98 + use serde::{Deserialize, Serialize}; 99 + 100 + // Use the datetime module for consistent RFC 3339 formatting 101 + #[derive(Serialize, Deserialize)] 102 + struct Record { 103 + #[serde(with = "atproto_record::datetime::format")] 104 + created_at: DateTime<Utc>, 105 + 106 + #[serde(with = "atproto_record::datetime::optional_format")] 107 + updated_at: Option<DateTime<Utc>>, 108 + } 71 109 ``` 72 110 73 111 ## Command Line Usage ··· 79 117 cargo build --features clap --bins 80 118 81 119 # Sign a record 82 - cargo run --features clap --bin atproto-record-sign -- did:key:zQ3sh... did:plc:issuer record.json \ 83 - repository=did:plc:repo collection=app.bsky.feed.post 120 + cargo run --features clap --bin atproto-record-sign -- \ 121 + did:key:zQ3sh... # Signing key (did:key format) 122 + did:plc:issuer # Issuer DID 123 + record.json # Record file (or use -- for stdin) 124 + repository=did:plc:repo # Repository context 125 + collection=app.bsky.feed.post # Collection type 126 + 127 + # Sign with custom fields (e.g., issuedAt, purpose, expiry) 128 + cargo run --features clap --bin atproto-record-sign -- \ 129 + did:key:zQ3sh... did:plc:issuer record.json \ 130 + repository=did:plc:repo collection=app.bsky.feed.post \ 131 + issuedAt="2024-01-01T00:00:00.000Z" purpose="attestation" 84 132 85 133 # Verify a signature 86 - cargo run --features clap --bin atproto-record-verify -- did:plc:issuer did:key:zQ3sh... signed.json \ 134 + cargo run --features clap --bin atproto-record-verify -- \ 135 + did:plc:issuer # Expected issuer DID 136 + did:key:zQ3sh... # Verification key 137 + signed.json # Signed record file 138 + repository=did:plc:repo # Repository context (must match signing) 139 + collection=app.bsky.feed.post # Collection type (must match signing) 140 + 141 + # Read from stdin 142 + echo '{"text":"Hello"}' | cargo run --features clap --bin atproto-record-sign -- \ 143 + did:key:zQ3sh... did:plc:issuer -- \ 87 144 repository=did:plc:repo collection=app.bsky.feed.post 88 145 ``` 89 146
+37 -7
crates/atproto-record/src/aturi.rs
··· 1 - //! AT-URI parsing and validation. 1 + //! AT-URI parsing and validation for AT Protocol record identification. 2 + //! 3 + //! This module provides functionality to parse and validate AT Protocol URIs (AT-URIs), 4 + //! which are used to uniquely identify records within the AT Protocol ecosystem. 5 + //! 6 + //! ## AT-URI Format 2 7 //! 3 - //! Parse AT Protocol URIs (at://authority/collection/record_key) for 4 - //! identifying records within AT Protocol repositories. 5 - //! - `collection` is an NSID identifying the record type 6 - //! - `record_key` is the unique identifier for the specific record 8 + //! AT-URIs follow the format: `at://authority/collection/record_key` 9 + //! 10 + //! - **authority**: A DID (Decentralized Identifier) - either did:plc or did:web 11 + //! - **collection**: An NSID (Namespaced Identifier) specifying the record type 12 + //! - **record_key**: A unique identifier for the specific record within the collection 13 + //! 14 + //! ## Example 15 + //! 16 + //! ```ignore 17 + //! use atproto_record::aturi::ATURI; 18 + //! use std::str::FromStr; 19 + //! 20 + //! let uri = "at://did:plc:abc123/app.bsky.feed.post/3k2k4j5h6g"; 21 + //! let aturi = ATURI::from_str(uri)?; 22 + //! 23 + //! assert_eq!(aturi.authority, "did:plc:abc123"); 24 + //! assert_eq!(aturi.collection, "app.bsky.feed.post"); 25 + //! assert_eq!(aturi.record_key, "3k2k4j5h6g"); 26 + //! ``` 27 + //! 28 + //! ## Validation Rules 29 + //! 30 + //! - Must start with the `at://` scheme 31 + //! - Authority must be a valid DID (handles are not supported) 32 + //! - Collection and record_key must be non-empty 33 + //! - Trailing slashes are not permitted 7 34 8 35 use std::fmt; 9 36 use std::str::FromStr; ··· 14 41 15 42 /// Represents a parsed AT Protocol URI (AT-URI). 16 43 /// 17 - /// AT-URIs are used to identify specific records within AT Protocol repositories. 18 - /// They consist of three components: authority (DID), collection (NSID), and record key. 44 + /// AT-URIs uniquely identify records within AT Protocol repositories by combining 45 + /// three essential components: authority (DID), collection (NSID), and record key. 46 + /// 47 + /// This struct provides validated access to these components after successful parsing 48 + /// and implements `Display` for reconstructing the original URI format. 19 49 #[cfg_attr(debug_assertions, derive(Debug))] 20 50 #[derive(Clone)] 21 51 pub struct ATURI {
+19 -12
crates/atproto-record/src/bin/atproto-record-sign.rs
··· 1 - //! CLI tool for signing AT Protocol records. 1 + //! Command-line tool for signing AT Protocol records with cryptographic signatures. 2 + //! 3 + //! This tool creates cryptographic signatures on AT Protocol records using ECDSA 4 + //! signatures with IPLD DAG-CBOR serialization. It supports flexible argument 5 + //! ordering and customizable signature metadata. 2 6 3 7 use anyhow::Result; 4 8 use atproto_identity::{ ··· 7 11 }; 8 12 use atproto_record::errors::CliError; 9 13 use atproto_record::signature::create; 10 - use chrono::{SecondsFormat, Utc}; 11 14 use clap::Parser; 12 15 use serde_json::json; 13 16 use std::{ ··· 28 31 signed record with embedded signature metadata. 29 32 30 33 The tool accepts flexible argument ordering with DID keys, issuer DIDs, record 31 - inputs, and key=value parameters for repository and collection context. 34 + inputs, and key=value parameters for repository, collection, and custom metadata. 32 35 33 36 REQUIRED PARAMETERS: 34 37 repository=<DID> Repository context for the signature 35 38 collection=<name> Collection type context for the signature 39 + 40 + OPTIONAL PARAMETERS: 41 + Any additional key=value pairs are included in the signature metadata 42 + (e.g., issuedAt=<timestamp>, purpose=<string>, expiry=<timestamp>) 36 43 37 44 EXAMPLES: 38 45 # Basic usage: ··· 43 50 repository=did:plc:4zutorghlchjxzgceklue4la \\ 44 51 collection=app.bsky.feed.post 45 52 53 + # With custom metadata: 54 + atproto-record-sign \\ 55 + did:key:z42tv1pb3... ./post.json did:plc:issuer... \\ 56 + repository=did:plc:repo... collection=app.bsky.feed.post \\ 57 + issuedAt=\"2024-01-01T00:00:00.000Z\" purpose=\"attestation\" 58 + 46 59 # Reading from stdin: 47 60 echo '{\"text\":\"Hello!\"}' | atproto-record-sign \\ 48 61 did:key:z42tv1pb3... -- did:plc:issuer... \\ 49 62 repository=did:plc:repo... collection=app.bsky.feed.post 50 63 51 64 SIGNATURE PROCESS: 52 - - Creates $sig object with repository and collection context 65 + - Creates $sig object with repository, collection, and custom metadata 53 66 - Serializes record using IPLD DAG-CBOR format 54 - - Generates ECDSA signatures using P-256 or K-256 curves 55 - - Embeds signatures with issuer metadata 67 + - Generates ECDSA signatures using P-256, P-384, or K-256 curves 68 + - Embeds signatures with issuer and any provided metadata 56 69 " 57 70 )] 58 71 struct Args { ··· 162 175 163 176 // Write "issuer" key to signature_extras 164 177 signature_extras.insert("issuer".to_string(), issuer); 165 - 166 - // If signature_extras does not contain "issued_at" key, create a RFC 3339 formatted timestamp 167 - if !signature_extras.contains_key("issuedAt") { 168 - let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true); 169 - signature_extras.insert("issuedAt".to_string(), timestamp); 170 - } 171 178 172 179 let signature_object = json!(signature_extras); 173 180 let signed_record = create(
+5 -1
crates/atproto-record/src/bin/atproto-record-verify.rs
··· 1 - //! CLI tool for verifying AT Protocol record signatures. 1 + //! Command-line tool for verifying cryptographic signatures on AT Protocol records. 2 + //! 3 + //! This tool validates signatures on AT Protocol records by reconstructing the 4 + //! signed content and verifying ECDSA signatures against public keys. It ensures 5 + //! that records have valid signatures from specified issuers. 2 6 3 7 use anyhow::Result; 4 8 use atproto_identity::{
+35 -15
crates/atproto-record/src/datetime.rs
··· 1 1 //! DateTime serialization utilities for AT Protocol records. 2 2 //! 3 - //! This module provides Serde serialization and deserialization functions for DateTime<Utc> 4 - //! values with RFC 3339 formatting, including support for optional datetime fields. 5 - //! Used for consistent datetime handling across AT Protocol record structures. 3 + //! This module provides specialized Serde serialization and deserialization functions 4 + //! for `DateTime<Utc>` values, ensuring consistent RFC 3339 formatting with millisecond 5 + //! precision across all AT Protocol record structures. 6 + //! 7 + //! ## Usage 8 + //! 9 + //! These modules are designed to be used with Serde's `#[serde(with = "...")]` attribute: 10 + //! 11 + //! ```ignore 12 + //! use chrono::{DateTime, Utc}; 13 + //! use serde::{Deserialize, Serialize}; 14 + //! 15 + //! #[derive(Serialize, Deserialize)] 16 + //! struct Signature { 17 + //! #[serde(with = "atproto_record::datetime::format")] 18 + //! issued_at: DateTime<Utc>, 19 + //! 20 + //! #[serde(with = "atproto_record::datetime::optional_format")] 21 + //! expires_at: Option<DateTime<Utc>>, 22 + //! } 23 + //! ``` 6 24 //! 7 25 //! ## Modules 8 26 //! 9 - //! - [`format`] - Required datetime field serialization in RFC 3339 format with milliseconds 10 - //! - [`optional_format`] - Optional datetime field serialization handling None values 27 + //! - [`format`] - Required datetime field serialization with RFC 3339 millisecond precision 28 + //! - [`optional_format`] - Optional datetime field serialization with proper None handling 11 29 12 - /// Required datetime field serialization in RFC 3339 format with milliseconds. 30 + /// Required datetime field serialization with RFC 3339 formatting. 13 31 /// 14 - /// Provides serde serialization functions for DateTime<Utc> values 15 - /// using RFC 3339 formatting with millisecond precision. 32 + /// This module provides serde serialization/deserialization for `DateTime<Utc>` values, 33 + /// ensuring consistent RFC 3339 format with millisecond precision (e.g., "2024-01-01T12:00:00.000Z"). 34 + /// Use with `#[serde(with = "atproto_record::datetime::format")]` on required datetime fields. 16 35 pub mod format { 17 36 use chrono::{DateTime, SecondsFormat, Utc}; 18 37 use serde::{self, Deserialize, Deserializer, Serializer}; 19 38 20 - /// Serializes a DateTime<Utc> to RFC 3339 format with milliseconds. 39 + /// Serializes a `DateTime<Utc>` to RFC 3339 string with millisecond precision. 21 40 pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error> 22 41 where 23 42 S: Serializer, ··· 26 45 serializer.serialize_str(&s) 27 46 } 28 47 29 - /// Deserializes an RFC 3339 string to DateTime<Utc>. 48 + /// Deserializes an RFC 3339 formatted string to `DateTime<Utc>`. 30 49 pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error> 31 50 where 32 51 D: Deserializer<'de>, ··· 38 57 } 39 58 } 40 59 41 - /// Optional datetime field serialization handling None values. 60 + /// Optional datetime field serialization with None value support. 42 61 /// 43 - /// Provides serde serialization functions for Option<DateTime<Utc>> values 44 - /// using RFC 3339 formatting with proper None handling. 62 + /// This module provides serde serialization/deserialization for `Option<DateTime<Utc>>` values, 63 + /// handling both Some values (formatted as RFC 3339 with milliseconds) and None values gracefully. 64 + /// Use with `#[serde(with = "atproto_record::datetime::optional_format")]` on optional datetime fields. 45 65 pub mod optional_format { 46 66 use chrono::{DateTime, SecondsFormat, Utc}; 47 67 use serde::{self, Deserialize, Deserializer, Serializer}; 48 68 49 - /// Serializes an Option<DateTime<Utc>> to RFC 3339 format with milliseconds. 69 + /// Serializes an `Option<DateTime<Utc>>` to RFC 3339 string or null. 50 70 pub fn serialize<S>(date: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error> 51 71 where 52 72 S: Serializer, ··· 58 78 serializer.serialize_str(&s) 59 79 } 60 80 61 - /// Deserializes an optional RFC 3339 string to Option<DateTime<Utc>>. 81 + /// Deserializes an optional RFC 3339 string to `Option<DateTime<Utc>>`. 62 82 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error> 63 83 where 64 84 D: Deserializer<'de>,
+40 -18
crates/atproto-record/src/errors.rs
··· 1 1 //! Structured error types for AT Protocol record operations. 2 2 //! 3 - //! Comprehensive error handling for AT Protocol record signature verification and AT-URI parsing 4 - //! operations using structured error types with the `thiserror` library. All errors follow the 5 - //! project convention of prefixed error codes with descriptive messages. 3 + //! This module provides comprehensive error handling for all AT Protocol record operations 4 + //! using type-safe error enums powered by the `thiserror` library. All errors follow the 5 + //! project's standardized naming convention for consistent error reporting and debugging. 6 6 //! 7 7 //! ## Error Categories 8 8 //! 9 - //! - **`VerificationError`** (verification-1 to verification-11): Record signature verification and creation errors 10 - //! - **`AturiError`** (aturi-1 to aturi-9): AT-URI parsing and validation errors 11 - //! - **`CliError`** (cli-1 to cli-8): Command-line interface specific errors including file I/O, DID validation, and argument parsing 9 + //! ### `VerificationError` (Domain: verification) 10 + //! Errors related to cryptographic signature creation and verification operations. 11 + //! Error codes: verification-1 through verification-11 12 + //! 13 + //! ### `AturiError` (Domain: aturi) 14 + //! Errors occurring during AT-URI parsing and validation. 15 + //! Error codes: aturi-1 through aturi-9 16 + //! 17 + //! ### `CliError` (Domain: cli) 18 + //! Command-line interface specific errors for file I/O, argument parsing, and DID validation. 19 + //! Error codes: cli-1 through cli-8 12 20 //! 13 21 //! ## Error Format 14 22 //! 15 - //! All errors use the standardized format: `error-atproto-record-{domain}-{number} {message}: {details}` 23 + //! All errors follow the standardized format: 24 + //! ```text 25 + //! error-atproto-record-{domain}-{number} {message}: {details} 26 + //! ``` 27 + //! 28 + //! ## Example Usage 29 + //! 30 + //! ```ignore 31 + //! use atproto_record::errors::VerificationError; 32 + //! 33 + //! fn verify_signature() -> Result<(), VerificationError> { 34 + //! // Verification logic 35 + //! Err(VerificationError::NoSignaturesField) 36 + //! } 37 + //! ``` 16 38 17 39 use thiserror::Error; 18 40 19 - /// Represents errors that can occur during record signature verification operations. 41 + /// Errors that can occur during record signature creation and verification. 20 42 /// 21 - /// These errors relate to various stages of the signature verification process 22 - /// for AT Protocol records, from parsing to cryptographic validation. 43 + /// This enum covers all failure modes in the signature lifecycle, from creating 44 + /// signatures with missing metadata fields to verifying signatures with invalid 45 + /// cryptographic proofs or serialization failures. 23 46 #[derive(Debug, Error)] 24 47 pub enum VerificationError { 25 48 /// Error when no signatures field is found in the record. ··· 86 109 /// Error when signature object is missing required fields. 87 110 /// 88 111 /// This error occurs when the signature object is missing fields that are 89 - /// necessary during signature creation, such as 'issuer' or 90 - /// 'issuedAt'. 112 + /// necessary during signature creation, such as 'issuer'. 91 113 #[error("error-atproto-record-verification-8 Signature object missing field: {field}")] 92 114 SignatureObjectMissingField { 93 115 /// The name of the missing field ··· 126 148 }, 127 149 } 128 150 129 - /// Represents errors that can occur during AT-URI parsing operations. 151 + /// Errors that can occur during AT-URI parsing and validation. 130 152 /// 131 - /// These errors relate to various stages of parsing AT-URIs into their 132 - /// component parts (authority, collection, record key). 153 + /// This enum covers all validation failures when parsing AT-URIs, including 154 + /// format violations, missing components, and invalid authority types. 133 155 #[derive(Debug, Error)] 134 156 pub enum AturiError { 135 157 /// Error when AT-URI does not start with the required "at://" prefix. ··· 200 222 EmptyRecordKey, 201 223 } 202 224 203 - /// Error types that can occur in CLI tools. 225 + /// Errors specific to command-line interface operations. 204 226 /// 205 - /// These errors represent failures specific to command-line interface operations 206 - /// such as file I/O, JSON parsing, DID validation, and missing required arguments. 227 + /// This enum covers failures in CLI argument parsing, file I/O operations, 228 + /// JSON processing, and DID validation that occur in the binary tools. 207 229 #[derive(Debug, Error)] 208 230 pub enum CliError { 209 231 /// Occurs when DID method is not supported
+52 -11
crates/atproto-record/src/lib.rs
··· 1 - //! Cryptographic signature operations for AT Protocol records. 1 + //! Cryptographic signature operations and utilities for AT Protocol records. 2 2 //! 3 - //! Sign and verify AT Protocol records using IPLD DAG-CBOR serialization 4 - //! with support for P-256, P-384, and K-256 cryptographic signatures. 3 + //! This library provides comprehensive functionality for working with AT Protocol records, 4 + //! including cryptographic signature creation and verification, AT-URI parsing, and 5 + //! datetime serialization utilities. Built on IPLD DAG-CBOR for deterministic encoding 6 + //! with support for P-256, P-384, and K-256 elliptic curve cryptography. 7 + //! 8 + //! ## Main Features 9 + //! 10 + //! - **Signature Operations**: Create and verify cryptographic signatures following the 11 + //! community.lexicon.attestation.signature specification 12 + //! - **AT-URI Support**: Parse and validate AT Protocol URIs for record identification 13 + //! - **DateTime Utilities**: RFC 3339 datetime serialization with millisecond precision 14 + //! - **Type-Safe Errors**: Structured error types following project conventions 15 + //! 16 + //! ## Example Usage 17 + //! 18 + //! ```ignore 19 + //! use atproto_record::signature; 20 + //! use atproto_identity::key::identify_key; 21 + //! use serde_json::json; 22 + //! 23 + //! // Sign a record 24 + //! let key_data = identify_key("did:key:...")?; 25 + //! let record = json!({"$type": "app.bsky.feed.post", "text": "Hello!"}); 26 + //! let sig_obj = json!({"issuer": "did:plc:..."}); 27 + //! 28 + //! let signed = signature::create(&key_data, &record, "did:plc:repo", 29 + //! "app.bsky.feed.post", sig_obj).await?; 30 + //! 31 + //! // Verify a signature 32 + //! signature::verify("did:plc:issuer", &key_data, signed, 33 + //! "did:plc:repo", "app.bsky.feed.post").await?; 34 + //! ``` 5 35 6 36 #![forbid(unsafe_code)] 7 37 #![warn(missing_docs)] 8 38 9 - /// Structured error types for signature verification operations. 39 + /// Structured error types for record operations. 10 40 /// 11 - /// This module defines comprehensive error types that can occur during 12 - /// AT Protocol record signature verification, following the project's 13 - /// error naming conventions. 41 + /// Comprehensive error handling for signature verification, AT-URI parsing, 42 + /// and CLI operations. All errors follow the project's standardized format: 43 + /// `error-atproto-record-{domain}-{number} {message}: {details}` 14 44 pub mod errors; 15 45 16 - /// Core signature creation and verification functions. 46 + /// Core signature creation and verification. 17 47 /// 18 - /// This module provides the primary functionality for creating and verifying 19 - /// cryptographic signatures on AT Protocol records, with proper handling of 20 - /// signature objects and IPLD serialization. 48 + /// Provides functions for creating and verifying cryptographic signatures on 49 + /// AT Protocol records using IPLD DAG-CBOR serialization. Supports the 50 + /// community.lexicon.attestation.signature specification with proper $sig 51 + /// object handling and multiple signature support. 21 52 pub mod signature; 22 53 54 + /// AT-URI parsing and validation. 55 + /// 56 + /// Parse and validate AT Protocol URIs (at://authority/collection/record_key) 57 + /// for identifying records within repositories. Supports both did:plc and 58 + /// did:web authority types with comprehensive validation. 23 59 pub mod aturi; 24 60 61 + /// DateTime serialization utilities. 62 + /// 63 + /// RFC 3339 datetime serialization modules for consistent timestamp handling 64 + /// across AT Protocol records. Includes support for both required and optional 65 + /// datetime fields with millisecond precision. 25 66 pub mod datetime;
+89 -40
crates/atproto-record/src/signature.rs
··· 1 - //! AT Protocol record signature operations. 1 + //! AT Protocol record signature creation and verification. 2 + //! 3 + //! This module provides comprehensive functionality for creating and verifying 4 + //! cryptographic signatures on AT Protocol records following the 5 + //! community.lexicon.attestation.signature specification. 6 + //! 7 + //! ## Signature Process 8 + //! 9 + //! 1. **Signing**: Records are augmented with a `$sig` object containing issuer, 10 + //! timestamp, and context information, then serialized using IPLD DAG-CBOR 11 + //! for deterministic encoding before signing with ECDSA. 12 + //! 13 + //! 2. **Storage**: Signatures are stored in a `signatures` array within the record, 14 + //! allowing multiple signatures from different issuers. 15 + //! 16 + //! 3. **Verification**: The original signed content is reconstructed by replacing 17 + //! the signatures array with the appropriate `$sig` object, then verified 18 + //! using the issuer's public key. 19 + //! 20 + //! ## Supported Curves 21 + //! 22 + //! - P-256 (NIST P-256 / secp256r1) 23 + //! - P-384 (NIST P-384 / secp384r1) 24 + //! - K-256 (secp256k1) 25 + //! 26 + //! ## Example 27 + //! 28 + //! ```ignore 29 + //! use atproto_record::signature::{create, verify}; 30 + //! use atproto_identity::key::identify_key; 31 + //! use serde_json::json; 32 + //! 33 + //! // Create a signature 34 + //! let key = identify_key("did:key:...")?; 35 + //! let record = json!({"text": "Hello!"}); 36 + //! let sig_obj = json!({ 37 + //! "issuer": "did:plc:issuer" 38 + //! // Optional: any additional fields like "issuedAt", "purpose", etc. 39 + //! }); 2 40 //! 3 - //! Sign and verify AT Protocol records using elliptic curve signatures 4 - //! with IPLD DAG-CBOR serialization and proper $sig object handling. 41 + //! let signed = create(&key, &record, "did:plc:repo", 42 + //! "app.bsky.feed.post", sig_obj).await?; 43 + //! 44 + //! // Verify the signature 45 + //! verify("did:plc:issuer", &key, signed, 46 + //! "did:plc:repo", "app.bsky.feed.post").await?; 47 + //! ``` 5 48 6 49 use atproto_identity::key::{KeyData, sign, validate}; 7 50 use serde_json::json; ··· 9 52 10 53 use crate::errors::VerificationError; 11 54 12 - /// Signs an AT Protocol record. 55 + /// Creates a cryptographic signature for an AT Protocol record. 13 56 /// 14 - /// This function generates a signature for the provided record using the specified 15 - /// key data and context information. The signature is embedded into the record 16 - /// following AT Protocol signature conventions. 57 + /// This function generates a signature following the community.lexicon.attestation.signature 58 + /// specification. The record is augmented with a `$sig` object containing context information, 59 + /// serialized using IPLD DAG-CBOR, signed with the provided key, and the signature is added 60 + /// to a `signatures` array in the returned record. 17 61 /// 18 62 /// # Parameters 19 63 /// 20 - /// * `key_data` - The cryptographic key information wrapped in KeyData 21 - /// * `record` - The JSON record to be signed 22 - /// * `repository` - The repository DID context for the signature 23 - /// * `collection` - The collection name context for the signature 24 - /// * `signature_object` - Optional additional fields for the signature object 64 + /// * `key_data` - The signing key (private key) wrapped in KeyData 65 + /// * `record` - The JSON record to be signed (will not be modified) 66 + /// * `repository` - The repository DID where this record will be stored 67 + /// * `collection` - The collection type (NSID) for this record 68 + /// * `signature_object` - Metadata for the signature, must include: 69 + /// - `issuer`: The DID of the entity creating the signature (required) 70 + /// - Additional custom fields are preserved in the signature (optional) 25 71 /// 26 72 /// # Returns 27 73 /// 28 - /// Returns the original record with a `signatures` field containing the new signature. 74 + /// Returns a new record containing: 75 + /// - All original record fields 76 + /// - A `signatures` array with the new signature appended 77 + /// - No `$sig` field (only used during signing) 29 78 /// 30 79 /// # Errors 31 80 /// 32 - /// Returns an error if: 33 - /// - IPLD serialization fails 34 - /// - Cryptographic signing fails 35 - /// - JSON manipulation fails 81 + /// Returns [`VerificationError`] if: 82 + /// - Required field `issuer` is missing from signature_object 83 + /// - IPLD DAG-CBOR serialization fails 84 + /// - Cryptographic signing operation fails 85 + /// - JSON structure manipulation fails 36 86 pub async fn create( 37 87 key_data: &KeyData, 38 88 record: &serde_json::Value, ··· 44 94 if !record_map.contains_key("issuer") { 45 95 return Err(VerificationError::SignatureObjectMissingField { 46 96 field: "issuer".to_string(), 47 - }); 48 - } 49 - if !record_map.contains_key("issuedAt") { 50 - return Err(VerificationError::SignatureObjectMissingField { 51 - field: "issuedAt".to_string(), 52 97 }); 53 98 } 54 99 } else { ··· 71 116 record_map.insert("$sig".to_string(), sig); 72 117 } 73 118 74 - { 75 - let thing = serde_json::to_string_pretty(&signing_record).expect("yeah"); 76 - println!("{}", &thing); 77 - } 78 119 79 120 // Create a signature. 80 121 let serialized_signing_record = serde_ipld_dagcbor::to_vec(&signing_record)?; ··· 114 155 115 156 /// Verifies a cryptographic signature on an AT Protocol record. 116 157 /// 117 - /// This function validates that the provided record contains a valid signature 118 - /// from the specified issuer using the provided public key. It reconstructs 119 - /// the signed content and verifies the cryptographic signature. 158 + /// This function validates signatures by reconstructing the original signed content 159 + /// (record with `$sig` object) and verifying the ECDSA signature against it. 160 + /// It searches through all signatures in the record to find one matching the 161 + /// specified issuer, then verifies it with the provided public key. 120 162 /// 121 163 /// # Parameters 122 164 /// 123 - /// * `issuer` - The DID of the expected signature issuer 124 - /// * `key_data` - The public key information for verification 125 - /// * `record` - The signed JSON record to verify 126 - /// * `repository` - The repository DID context for verification 127 - /// * `collection` - The collection name context for verification 165 + /// * `issuer` - The DID of the expected signature issuer to verify 166 + /// * `key_data` - The public key for signature verification 167 + /// * `record` - The signed record containing a `signatures` or `sigs` array 168 + /// * `repository` - The repository DID used during signing (must match) 169 + /// * `collection` - The collection type used during signing (must match) 128 170 /// 129 171 /// # Returns 130 172 /// 131 - /// Returns `Ok(())` if a valid signature from the issuer is found and verified. 173 + /// Returns `Ok(())` if a valid signature from the specified issuer is found 174 + /// and successfully verified against the reconstructed signed content. 132 175 /// 133 176 /// # Errors 134 177 /// 135 - /// Returns a [`VerificationError`] if: 136 - /// - No signatures field is found in the record 178 + /// Returns [`VerificationError`] if: 179 + /// - No `signatures` or `sigs` field exists in the record 137 180 /// - No signature from the specified issuer is found 138 - /// - Signature decoding or parsing fails 139 - /// - Cryptographic verification fails 140 - /// - Record serialization fails 181 + /// - The issuer's signature is malformed or missing required fields 182 + /// - Base64 decoding of the signature fails 183 + /// - IPLD DAG-CBOR serialization of reconstructed content fails 184 + /// - Cryptographic verification fails (invalid signature) 185 + /// 186 + /// # Note 187 + /// 188 + /// This function supports both `signatures` and `sigs` field names for 189 + /// backward compatibility with different AT Protocol implementations. 141 190 pub async fn verify( 142 191 issuer: &str, 143 192 key_data: &KeyData,