attestion_spec.md edited
1122 lines 37 kB view raw view rendered
1# ATProtocol Record Structures for Inline and Remote Signature Structures (Attestations) 2 3## Technical Specification v1.0 4 5## Executive Summary 6 7This specification defines **attestation record structures** for ATProtocol that enable cryptographic signing of content using CID-based content addressing. **Attestations provide verifiable proof** that a specific identity has endorsed, verified, or made claims about content within the ATProtocol ecosystem. 8 9The specification supports two minimal patterns within a unified `signatures` array using union types: 10- **Inline attestations**: Signature objects with `$type`, `signature` (bytes), and `key` (DID verification method reference) as required fields 11- **Remote attestations**: Strong references (`com.atproto.repo.strongRef`) pointing to separate proof records containing a `cid` as a required field 12 13Both patterns use a `$sig` metadata object during CID generation that must always include at least a `$type` field and a `repository` field. The `repository` field contains the DID of the repository housing the record, **preventing replay attacks** where an attacker might attempt to clone records from one repository into their own. For inline attestations, the resulting CID bytes are signed with the specified key. For remote attestations, the resulting CID is stored directly in the proof record in the attestor's repository. 14 15The `signatures` array uses a union type system, where the `$type` field is always required to distinguish between inline signatures and strong references to remote attestations. 16 17--- 18 19## 1. Requirements 20 21**Content Addressability**: All attestations must reference content via CID (Content Identifier) to create immutable, verifiable links to specific versions of data. This ensures attestations cannot be replayed, reused, or applied to different content, preventing circumvention of ATProtocol security controls or repository integrity mechanisms. 22 23**Replay Attack Prevention**: The CID generation process must include the repository DID in the `$sig` metadata to prevent attackers from cloning records from one repository to another. This binding ensures that attestations are only valid within their original repository context. 24 25**Key Management Flexibility**: The attestation framework must support both inline and remote patterns to accommodate different operational constraints: 26- **Remote attestations** enable proof creation using only existing DID verification methods, with no attestation-specific key management required 27- **Inline attestations** embed signatures directly in records, eliminating the need to create and maintain separate proof records 28 29**Radical Simplicity**: The attestation structure must minimize complexity while maximizing security: 30- Inline attestations require only three fields: `$type`, `signature` (bytes), and `key` (verification method reference) 31- Remote attestations require only one field in the proof record: `cid` 32- Fewer fields reduce attack surface and implementation burden 33 34**Union Type System**: The `signatures` array must use ATProtocol's union type pattern with required `$type` field for clear discrimination between inline signatures and strongRef references to remote attestations. 35 36**Unified CID Generation**: Both attestation types must use the same CID generation process with a `$sig` metadata object that always includes at least a `$type` field and a `repository` field. The CID incorporates this metadata, providing scope control and preventing replay attacks across different contexts and repositories. 37 38**Application Extensibility**: Applications must be able to define custom attestation types and inject application-specific metadata into the attestation creation process: 39- The `$type` field in signatures enables application-specific attestation semantics 40- The `$sig` metadata object allows applications to include contextual information in CID generation, binding attestations to specific use cases and preventing cross-context attacks 41 42**DID Integration**: The framework must leverage existing ATProtocol DID infrastructure with verification methods for cryptographic operations, eliminating dependency on external key management systems. 43 44**Backward Compatibility**: Attestation-unaware clients must be able to safely ignore attestation fields. Records must validate against Lexicon schemas with or without signatures, ensuring the framework degrades gracefully in mixed-version environments. 45 46--- 47 48## 2. Record Structure Definitions 49 50The `signatures` array in ATProtocol records uses a **union type system** that supports both inline attestations and references to remote attestations. The `$type` field is always required to distinguish between types in the union. 51 52### 2.1 Inline Attestation Structure 53 54Inline attestations embed signatures directly in records using a **`signatures` array**. The signature is generated by signing the CID bytes of the record content (including a `$sig` metadata object with `repository` field). 55 56#### 2.1.1 Example Record 57 58```json 59{ 60 "$type": "app.example.record", 61 "createdAt": "2025-10-14T12:00:00Z", 62 "text": "Example content that is being attested", 63 "signatures": [ 64 { 65 "$type": "com.example.inlineSignature", 66 "signature": {"$bytes": "MzQ2Y2U4ZDNhYmM5NjU0Mzk5NWJmNjJkOGE4..."}, 67 "key": "did:web:example.com#signing1" 68 } 69 ] 70} 71``` 72 73#### 2.1.2 Inline Signature Fields 74 75When using inline signatures in the `signatures` array: 76 77**Required Fields:** 78 79- **`$type`** (string, NSID format): Type identifier for the signature object (required for union types) 80 - Example: `"com.example.inlineSignature"` 81 - Must NOT be `"com.atproto.repo.strongRef"` (that's for remote attestations) 82 - Allows applications to define custom signature types 83 84- **`signature`** (object with `$bytes`): Base64-encoded signature bytes 85 - Generated from signing the CID bytes of the record 86 - ECDSA signature using P-256 or K-256 curve 87 - Must use "low-S" variant (per BIP-0062) 88 - Format: `{"$bytes": "base64-encoded-signature"}` 89 90- **`key`** (string): Full verification method reference within a DID document 91 - Format: `did:{method}:{identifier}#{fragment}` 92 - Example: `did:web:example.com#signing1` 93 - The DID component identifies who signed the record 94 - The fragment identifies which verification method in the DID document was used 95 96#### 2.1.3 Signatures Array Lexicon Schema 97 98```json 99{ 100 "signatures": { 101 "type": "array", 102 "description": "Array of cryptographic signatures attesting to record", 103 "items": { 104 "type": "union", 105 "refs": [ 106 "com.atproto.repo.strongRef", 107 "com.example.inlineSignature" 108 ] 109 } 110 } 111} 112``` 113 114**Inline Signature Type Definition:** 115```json 116{ 117 "lexicon": 1, 118 "id": "com.example.inlineSignature", 119 "defs": { 120 "main": { 121 "type": "object", 122 "required": ["signature", "key"], 123 "properties": { 124 "signature": { 125 "type": "bytes", 126 "description": "Signature bytes" 127 }, 128 "key": { 129 "type": "string", 130 "description": "Full verification method reference (DID#fragment)" 131 } 132 } 133 } 134 } 135} 136``` 137 138### 2.2 Remote Attestation Structure 139 140Remote attestations are **separate records** that contain a CID of the content being attested. 141 142#### 2.2.1 Remote Attestation Record 143 144```json 145{ 146 "$type": "com.example.proof", 147 "cid": "bafyreifsqhrnlciktfxkz4yiqw5wtx6xvods67aicqt5tc7cly24dmhv3e" 148} 149``` 150 151The CID is created from a DAG-CBOR encoding of the record content that includes a `$sig` metadata object (with at least a `$type` field and `repository` field) and excludes the "signatures" field. 152 153#### 2.2.2 Attested Record with Remote Reference 154 155Records can reference remote attestations through strong-refs in their signatures array: 156 157```json 158{ 159 "$type": "app.bsky.feed.post", 160 "createdAt": "2025-10-14T20:09:00.733Z", 161 "text": "Content that is being attested", 162 "signatures": [ 163 { 164 "$type": "com.atproto.repo.strongRef", 165 "cid": "bafyreig5ug2vj63ag5b6okth3roujv2lngxnyssxeylfcmmqiznfje4enu", 166 "uri": "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/com.example.proof/3m3i7e3uhrocj" 167 } 168 ] 169} 170``` 171 172The proof record's CID was generated from the content with a `$sig` object containing attestation metadata including the repository DID. 173 174#### 2.2.3 Remote Attestation Lexicon Schema 175 176**Proof Record Schema:** 177```json 178{ 179 "lexicon": 1, 180 "id": "com.example.proof", 181 "defs": { 182 "main": { 183 "type": "record", 184 "description": "Remote attestation proof containing CID of attested content", 185 "key": "tid", 186 "record": { 187 "type": "object", 188 "required": ["cid"], 189 "properties": { 190 "cid": { 191 "type": "string", 192 "format": "cid", 193 "description": "CID of the attested content (record without signatures field)" 194 } 195 } 196 } 197 } 198 } 199} 200``` 201 202**Note:** The reference to a proof record from the `signatures` array uses the standard `com.atproto.repo.strongRef` type, which is already defined in the ATProtocol specification. 203 204#### 2.2.4 Pattern Comparison 205 206| Aspect | Inline Attestation | Remote Attestation | 207|--------|-------------------|-------------------| 208| **Location** | Signature in same record as content | Separate proof record with CID | 209| **Storage** | Recipient's repository | Attester's repository | 210| **Fields** | `signature` + `key` only | `cid` only | 211| **CID Usage** | CID bytes are signed | CID is stored directly | 212| **$sig Required** | Yes (with `$type` + `repository`) | Yes (with `$type` + `repository`) | 213| **Use Case** | Direct attestations, self-signing | Revocation, external proofs | 214| **Discoverability** | Direct (in record) | Requires query/index or strong-ref | 215| **Reference** | N/A | Strong-ref from attested record | 216 217--- 218 219## 3. CID Generation Algorithms and Requirements 220 221### 3.1 CID Format Specification 222 223ATProtocol attestations use **CIDv1** with the following "blessed" format: 224 225``` 226CIDv1: 227 Multibase: base32 (for strings), raw binary (for DAG-CBOR) 228 Multicodec: dag-cbor (0x71) 229 Multihash: sha2-256 (0x12), 32 bytes 230``` 231 232**Binary Structure:** 233``` 2340x01 # CID version 1 2350x71 # dag-cbor codec 2360x12 # SHA-256 hash function 2370x20 # Hash length (32 bytes) 238<32 bytes of hash> # SHA-256 digest 239``` 240 241### 3.2 CID Generation Algorithm 242 243#### 3.2.1 Standard Process 244 245``` 246Step 1: Prepare Record Data 247 - Remove "signatures" field from record 248 - Include "$sig" metadata object (required for attestations) 249 * Must always include "$type" field 250 * Must always include "repository" field (DID of housing repository) 251 * Additional fields based on attestation type 252 - Ensure all fields in canonical form 253 254Step 2: Encode to DAG-CBOR 255 - Apply canonical serialization rules: 256 * Sort map keys by length, then lexically 257 * Use shortest integer encodings 258 * Definite-length items only 259 * Tag 42 for CID links 260 - Output: Binary CBOR bytes 261 262Step 3: Hash with SHA-256 263 - Input: DAG-CBOR bytes from Step 2 264 - Output: 32-byte binary hash digest 265 266Step 4: Construct Multihash 267 - Prepend hash function code: 0x12 (SHA-256) 268 - Prepend hash length: 0x20 (32 bytes) 269 - Result: 34-byte multihash 270 271Step 5: Construct CID 272 - Prepend CID version: 0x01 273 - Prepend multicodec: 0x71 (dag-cbor) 274 - Append multihash from Step 4 275 - Result: 36-byte CID 276 277Step 6: Usage by Attestation Type 278 - For inline attestations: Sign the CID bytes with private key 279 - For remote attestations: Store the CID in proof record 280 - For string representation: Base32-encode with 'b' prefix 281``` 282 283#### 3.2.2 Pseudocode Implementation 284 285```pseudocode 286FUNCTION generateRecordCID(recordData, sigMetadata, repositoryDID): 287 # Step 1: Prepare data with $sig 288 encodingData = copy(recordData) 289 290 # Remove signatures field if present 291 DELETE encodingData["signatures"] 292 293 # Add $sig metadata (must have $type and repository) 294 IF NOT sigMetadata["$type"]: 295 THROW Error("$sig must include $type field") 296 297 # CRITICAL: Always set repository field to prevent replay attacks 298 sigMetadata["repository"] = repositoryDID 299 encodingData["$sig"] = sigMetadata 300 301 # Step 2: Encode to DAG-CBOR 302 cborBytes = encodeDAGCBORCanonical(encodingData) 303 304 # Step 3: Hash 305 hashDigest = SHA256(cborBytes) # 32 bytes 306 307 # Step 4: Construct multihash 308 multihash = CONCAT( 309 [0x12], # SHA-256 code 310 [0x20], # 32 bytes length 311 hashDigest # 32-byte hash 312 ) 313 314 # Step 5: Construct CID 315 cidBytes = CONCAT( 316 [0x01], # CIDv1 317 [0x71], # dag-cbor 318 multihash # 34 bytes 319 ) 320 321 RETURN cidBytes # 36 bytes total 322END FUNCTION 323 324# Wrapper function used in attestation generation 325FUNCTION generateCIDFromCBOR(cborBytes): 326 hashDigest = SHA256(cborBytes) 327 multihash = CONCAT([0x12], [0x20], hashDigest) 328 cidBytes = CONCAT([0x01], [0x71], multihash) 329 RETURN cidBytes 330END FUNCTION 331 332FUNCTION encodeDAGCBORCanonical(data): 333 # Recursive encoding with strict rules 334 335 IF data is NULL: 336 RETURN encodeCBORNull() 337 338 IF data is BOOLEAN: 339 RETURN encodeCBORBool(data) 340 341 IF data is INTEGER: 342 RETURN encodeCBORInt(data) # Shortest encoding 343 344 IF data is STRING: 345 utf8Bytes = encodeUTF8(data) 346 RETURN CONCAT( 347 majorType3WithLength(LENGTH(utf8Bytes)), 348 utf8Bytes 349 ) 350 351 IF data is BYTES: 352 RETURN CONCAT( 353 majorType2WithLength(LENGTH(data)), 354 data 355 ) 356 357 IF data is CID: 358 # Tag 42 encoding 359 cidWithPrefix = CONCAT([0x00], data.toBytes()) 360 RETURN CONCAT( 361 [0xd8, 0x2a], # Tag 42 362 majorType2WithLength(LENGTH(cidWithPrefix)), 363 cidWithPrefix 364 ) 365 366 IF data is ARRAY: 367 encoded = [] 368 FOR item IN data: 369 encoded.APPEND(encodeDAGCBORCanonical(item)) 370 371 RETURN CONCAT( 372 majorType4WithLength(LENGTH(data)), 373 CONCATENATE_ALL(encoded) 374 ) 375 376 IF data is MAP: 377 # CRITICAL: Sort keys before encoding 378 sortedKeys = sortKeysCanonical(data.KEYS()) 379 380 encoded = [] 381 FOR key IN sortedKeys: 382 encodedKey = encodeDAGCBORCanonical(key) 383 encodedValue = encodeDAGCBORCanonical(data[key]) 384 encoded.APPEND(CONCAT(encodedKey, encodedValue)) 385 386 RETURN CONCAT( 387 majorType5WithLength(LENGTH(sortedKeys)), 388 CONCATENATE_ALL(encoded) 389 ) 390 391 THROW Error("Unsupported type for DAG-CBOR") 392END FUNCTION 393 394FUNCTION sortKeysCanonical(keys): 395 # Convert keys to CBOR bytes first 396 keyPairs = [] 397 FOR key IN keys: 398 cborBytes = encodeDAGCBORCanonical(key) 399 keyPairs.APPEND((key, cborBytes)) 400 401 # Sort by length first, then lexically 402 sortedPairs = SORT(keyPairs, COMPARATOR=lambda a, b: 403 IF LENGTH(a.cborBytes) != LENGTH(b.cborBytes): 404 RETURN COMPARE(LENGTH(a.cborBytes), LENGTH(b.cborBytes)) 405 ELSE: 406 RETURN byteWiseCompare(a.cborBytes, b.cborBytes) 407 ) 408 409 RETURN [pair.key FOR pair IN sortedPairs] 410END FUNCTION 411``` 412 413### 3.3 DAG-CBOR Strictness Requirements 414 415**Map Key Sorting (RFC 7049 Section 3.9):** 4161. Keys sorted by complete CBOR byte representation 4172. Shorter byte representations sort before longer 4183. Same-length representations sorted lexically (byte-wise) 419 420**Example Sorting:** 421``` 422Original: {"text": ..., "$type": ..., "repository": ..., "createdAt": ...} 423 424CBOR bytes: 425 "text" → 0x64 74 65 78 74 (5 bytes) 426 "$type" → 0x65 24 74 79 70 65 (6 bytes) 427 "createdAt" → 0x69 63 72 65 61 74 65 64 41 74 (11 bytes) 428 "repository" → 0x6a 72 65 70 6f 73 69 74 6f 72 79 (11 bytes) 429 430Sorted: ["text", "$type", "createdAt", "repository"] 431``` 432 433**Integer Encoding:** 434- 0-23: Encoded in initial byte 435- 24-255: 1-byte extension (0x18 prefix) 436- 256-65535: 2-byte extension (0x19 prefix) 437- Must use shortest possible encoding 438 439**Tag 42 (CID Links):** 440- Only valid form: `0xd8 0x2a` 441- Must not use alternative encodings (0xf8 0x2a is invalid) 442 443**Floating Point:** 444- ATProtocol records should avoid floats 445- If required, use 64-bit double precision only 446- NaN, Infinity, -Infinity not supported 447 448**Forbidden:** 449- Tags other than 42 450- Indefinite-length items 451- Non-string map keys 452- Simple values except true (21), false (20), null (22) 453 454--- 455 456## 4. Signature Format and Key Resolution 457 458### 4.1 The $sig Metadata Object 459 460The **`$sig` object** is a temporary metadata structure included during CID generation for **both inline and remote attestations**. It provides scope control and prevents signature replay attacks. 461 462#### 4.1.1 Structure 463 464```json 465{ 466 "$sig": { 467 "$type": "com.example.attestation.type", 468 "repository": "did:plc:abc123", // Always required for attestations 469 "key": "did:plc:signer123#method" // For inline attestations 470 } 471} 472``` 473 474#### 4.1.2 Required and Optional Fields 475 476**Required Fields:** 477- **`$type`** (string, NSID): Type identifier for the attestation (always required) 478- **`repository`** (string): DID of the repository housing the record (always required for attestations) 479 - Must be a valid DID format string 480 - Prevents replay attacks where an attacker clones records to a different repository 481 - Value must be overwritten if present to ensure correct repository binding 482 483**Common Fields:** 484- **`key`** (string): The full verification method reference (for inline attestations) 485 486**Important**: 487- The `$sig` object is **only present during CID generation** for both attestation types 488- It is **NOT stored** in the final record 489- The `$sig` must always contain both `$type` and `repository` fields for attestations 490- The `repository` field ensures the CID is bound to a specific repository, preventing cross-repository replay attacks 491- For inline attestations: The CID (including $sig) is signed to create the signature 492- For remote attestations: The CID (including $sig) is stored directly in the proof record 493 494### 4.2 Signature Generation Process (Inline Attestations) 495 496```pseudocode 497FUNCTION generateInlineSignature(record, repositoryDID, signerDID, signingKey, fragmentID, signatureType): 498 # Step 1: Add $sig metadata for CID generation 499 sigMetadata = { 500 "$type": "com.example.inlineSignature", # Always required 501 "repository": repositoryDID, # Always required - prevents replay attacks 502 "key": signerDID + "#" + fragmentID, 503 } 504 505 # Step 2: Prepare record for CID generation 506 recordForCID = copy(record) 507 DELETE recordForCID["signatures"] 508 509 # Step 3: Generate CID (which includes $sig with repository) 510 recordForCID["$sig"] = sigMetadata 511 cborBytes = encodeDAGCBORCanonical(recordForCID) 512 cidBytes = generateCIDFromCBOR(cborBytes) 513 514 # Step 4: Sign the CID bytes (not the CBOR directly) 515 signature = ECDSA_Sign(signingKey, cidBytes) 516 517 # Step 5: Ensure low-S format 518 IF NOT isLowS(signature): 519 signature = convertToLowS(signature) 520 521 # Step 6: Create signature block with required $type 522 signatureBlock = { 523 "$type": "com.example.inlineSignature", 524 "signature": {"$bytes": base64Encode(signature)}, 525 "key": signerDID + "#" + fragmentID 526 } 527 528 # Step 7: Add to record (without $sig) 529 finalRecord = copy(record) 530 IF NOT finalRecord.signatures: 531 finalRecord["signatures"] = [] 532 finalRecord["signatures"].APPEND(signatureBlock) 533 534 RETURN finalRecord 535END FUNCTION 536``` 537 538### 4.3 Remote Attestation Generation Process 539 540```pseudocode 541FUNCTION generateRemoteProof(record, repositoryDID, proverDID): 542 # Step 1: Add $sig metadata for CID generation 543 sigMetadata = { 544 "$type": "com.example.proof", # Always required 545 "repository": repositoryDID # Always required - prevents replay attacks 546 } 547 548 # Step 2: Prepare record for CID generation 549 recordForCID = copy(record) 550 DELETE recordForCID["signatures"] 551 552 # Step 3: Generate CID (which includes $sig with repository) 553 recordForCID["$sig"] = sigMetadata 554 cborBytes = encodeDAGCBORCanonical(recordForCID) 555 cid = generateCIDFromCBOR(cborBytes) 556 557 # Step 4: Create minimal proof record 558 proofRecord = { 559 "$type": "com.example.proof", 560 "cid": cid 561 } 562 563 # Step 5: Store proof record and return reference 564 proofURI = storeRecord(proofRecord) 565 566 RETURN { 567 proofRecord: proofRecord, 568 proofURI: proofURI, 569 contentCID: cid 570 } 571END FUNCTION 572``` 573 574### 4.4 Key Resolution Process 575 576#### 4.4.1 DID Document Structure 577 578```json 579{ 580 "@context": ["https://www.w3.org/ns/did/v1"], 581 "id": "did:plc:abc123", 582 "alsoKnownAs": ["at://alice.bsky.social"], 583 "verificationMethod": [ 584 { 585 "id": "did:plc:abc123#atproto", 586 "type": "Multikey", 587 "controller": "did:plc:abc123", 588 "publicKeyMultibase": "zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc" 589 } 590 ], 591 "assertionMethod": [ 592 { 593 "id": "did:plc:abc123#attesting", 594 "type": "Multikey", 595 "controller": "did:plc:abc123", 596 "publicKeyMultibase": "zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo" 597 } 598 ], 599 "service": [ 600 { 601 "id": "#atproto_pds", 602 "type": "AtprotoPersonalDataServer", 603 "serviceEndpoint": "https://pds.example.com" 604 } 605 ] 606} 607``` 608 609#### 4.4.2 Resolution Algorithm 610 611```pseudocode 612FUNCTION resolveVerificationMethod(keyReference): 613 # Parse DID and fragment 614 [did, fragment] = splitDIDFragment(keyReference) 615 616 # Resolve DID document 617 didDocument = resolveDID(did) 618 IF didDocument is NULL: 619 THROW Error("Cannot resolve DID from key reference") 620 621 # Find verification method 622 FOR method IN didDocument.verificationMethod: 623 IF method.id == keyReference: 624 RETURN method 625 626 # Also check short form (without DID prefix) 627 IF method.id == "#" + fragment: 628 RETURN method 629 FOR method IN didDocument.assertionMethod: 630 IF method.id == keyReference: 631 RETURN method 632 633 # Also check short form (without DID prefix) 634 IF method.id == "#" + fragment: 635 RETURN method 636 637 THROW Error("Verification method not found: " + keyReference) 638END FUNCTION 639 640FUNCTION parsePublicKey(verificationMethod): 641 # Extract public key from multibase encoding 642 multibaseKey = verificationMethod.publicKeyMultibase 643 644 # Decode multibase (base58btc with 'z' prefix) 645 IF NOT multibaseKey.startsWith("z"): 646 THROW Error("Invalid multibase encoding") 647 648 keyBytes = base58Decode(multibaseKey.substring(1)) 649 650 # Extract multicodec 651 [codecVarint, offset] = decodeVarint(keyBytes) 652 653 # Determine key type 654 IF codecVarint == 0xE701: # secp256k1-pub 655 keyType = "K-256" 656 compressedKey = keyBytes[offset:] 657 ELSE IF codecVarint == 0x1200: # p256-pub 658 keyType = "P-256" 659 compressedKey = keyBytes[offset:] 660 ELSE: 661 THROW Error("Unsupported key type") 662 663 # Decompress public key (curve-specific) 664 publicKey = decompressPublicKey(compressedKey, keyType) 665 666 RETURN (publicKey, keyType) 667END FUNCTION 668``` 669 670### 4.5 Supported Cryptographic Algorithms 671 672**Elliptic Curves:** 673- **P-256** (secp256r1, NIST P-256): Widely supported, WebCrypto compatible 674- **K-256** (secp256k1): Bitcoin/Ethereum curve, default in ATProtocol 675 676**Signature Algorithm:** 677- ECDSA with CID bytes as input (not raw hash) 678- Low-S signature variant mandatory (BIP-0062) 679- Signs the complete 36-byte CID, not the underlying SHA-256 hash 680 681**Hash Function:** 682- SHA-256 (256-bit output) for CID generation 683 684**Encoding:** 685- Public keys: Multibase (base58btc) with multicodec prefix, compressed format 686- Signatures: Raw ECDSA bytes (r, s) encoded as base64 in JSON 687- CIDs: Base32-encoded for strings, raw bytes for signing 688 689--- 690 691## 5. Verification Workflows 692 693### 5.1 Inline Attestation Verification 694 695```pseudocode 696FUNCTION verifyInlineAttestation(record, repositoryDID): 697 IF NOT record.signatures OR LENGTH(record.signatures) == 0: 698 RETURN {valid: false, reason: "No signatures present"} 699 700 results = [] 701 702 FOR signatureBlock IN record.signatures: 703 # Skip strongRefs (they're remote attestations) 704 IF signatureBlock["$type"] == "com.atproto.repo.strongRef": 705 CONTINUE 706 707 # Verify inline signature 708 IF signatureBlock.signature AND signatureBlock.key: 709 result = verifySingleSignature(record, signatureBlock, repositoryDID) 710 results.APPEND(result) 711 712 RETURN results 713END FUNCTION 714 715FUNCTION verifySingleSignature(record, signatureBlock, repositoryDID): 716 # Step 1: Verify required fields 717 IF NOT signatureBlock["$type"]: 718 RETURN {valid: false, reason: "Missing $type field"} 719 720 # Step 2: Resolve verification method from key field 721 verificationMethod = resolveVerificationMethod(signatureBlock.key) 722 IF verificationMethod is NULL: 723 RETURN {valid: false, reason: "Cannot resolve verification method"} 724 725 (publicKey, keyType) = parsePublicKey(verificationMethod) 726 727 # Step 3: Reconstruct signed content with $sig 728 recordForSigning = copy(record) 729 DELETE recordForSigning["signatures"] 730 731 # Add $sig metadata (must include $type and repository) 732 sigMetadata = copy(signatureBlock) 733 DELETE sigMetadata["signature"] 734 735 # CRITICAL: Always set repository field for replay attack prevention 736 sigMetadata["repository"] = repositoryDID 737 recordForSigning["$sig"] = sigMetadata 738 739 # Step 4: Generate CID (same process as signing) 740 cborBytes = encodeDAGCBORCanonical(recordForSigning) 741 cidBytes = generateCIDFromCBOR(cborBytes) 742 743 # Step 5: Decode signature 744 signatureBytes = base64Decode(signatureBlock.signature["$bytes"]) 745 746 # Step 6: Verify signature is low-S 747 IF NOT isLowS(signatureBytes): 748 RETURN {valid: false, reason: "Signature not in low-S format"} 749 750 # Step 7: Cryptographic verification against CID bytes 751 isValid = ECDSA_Verify(publicKey, cidBytes, signatureBytes, keyType) 752 753 IF isValid: 754 RETURN { 755 valid: true, 756 type: signatureBlock["$type"], 757 key: signatureBlock.key, 758 signer: extractDIDFromKey(signatureBlock.key), 759 repository: repositoryDID 760 } 761 ELSE: 762 RETURN {valid: false, reason: "Signature verification failed"} 763END FUNCTION 764``` 765 766### 5.2 Remote Attestation Verification 767 768```pseudocode 769FUNCTION verifyRemoteAttestation(proofRecord, subjectRecord, repositoryDID): 770 # Step 1: Verify the proof record exists and has CID 771 IF NOT proofRecord.cid: 772 RETURN {valid: false, reason: "Proof record missing CID"} 773 774 # Step 2: Reconstruct subject record with $sig for CID generation 775 subjectRecordForCID = copy(subjectRecord) 776 DELETE subjectRecordForCID["signatures"] 777 778 # Add $sig metadata (must include $type and repository) 779 # Note: The exact $sig content must match what was used during proof creation 780 sigMetadata = copy(proofRecord) 781 DELETE sigMetadata["cid"] 782 783 # CRITICAL: Always set repository field for replay attack prevention 784 sigMetadata["repository"] = repositoryDID 785 subjectRecordForCID["$sig"] = sigMetadata 786 787 # Step 3: Generate CID from subject record 788 cborBytes = encodeDAGCBORCanonical(subjectRecordForCID) 789 subjectCID = generateCIDFromCBOR(cborBytes) 790 791 # Step 4: Verify CID match 792 IF proofRecord.cid != subjectCID: 793 RETURN { 794 valid: false, 795 reason: "CID mismatch - possible replay attack or incorrect repository", 796 expected: proofRecord.cid, 797 actual: subjectCID 798 } 799 800 # Step 5: Extract proof metadata 801 proofURI = constructATURI(proofRecord) 802 proofDID = extractDIDFromURI(proofURI) 803 804 RETURN { 805 valid: true, 806 proofCID: proofRecord.cid, 807 proofURI: proofURI, 808 prover: proofDID, 809 subjectCID: subjectCID, 810 repository: repositoryDID 811 } 812END FUNCTION 813``` 814 815### 5.3 CID Chain Verification 816 817For verifying content integrity through repository Merkle tree: 818 819```pseudocode 820FUNCTION verifyRecordInRepository(commit, recordPath): 821 # Step 1: Verify commit signature 822 IF NOT verifyCommitSignature(commit): 823 RETURN {valid: false, reason: "Invalid commit signature"} 824 825 # Step 2: Extract repository DID from commit 826 repositoryDID = commit.did 827 828 # Step 3: Fetch MST root 829 mstRoot = fetchBlock(commit.data) 830 831 # Step 4: Navigate MST to find record 832 (record, proof) = findRecordInMST(mstRoot, recordPath) 833 IF record is NULL: 834 RETURN {valid: false, reason: "Record not found in MST"} 835 836 # Step 5: Verify CID chain (with repository binding) 837 recordCID = generateRecordCID(record, getRecordSigMetadata(record), repositoryDID) 838 IF NOT verifyMSTPath(mstRoot, recordPath, recordCID): 839 RETURN {valid: false, reason: "MST path verification failed"} 840 841 # Step 6: Verify root CID matches commit 842 rootCID = generateRecordCID(mstRoot) 843 IF rootCID != commit.data: 844 RETURN {valid: false, reason: "Root CID mismatch"} 845 846 RETURN { 847 valid: true, 848 record: record, 849 recordCID: recordCID, 850 proof: proof, 851 repository: repositoryDID 852 } 853END FUNCTION 854``` 855 856--- 857 858## 6. Integration Patterns with ATProtocol Infrastructure 859 860### 6.1 PDS OAuth Integration 861 862**Signature Request Flow:** 863 864``` 8651. Client requests OAuth scope: "identity:attestation" 8662. PDS prompts user for consent 8673. User approves specific verification method usage 8684. Client receives scoped token 8695. Client calls com.atproto.attestation.sign with repository DID 8706. PDS signs record using authorized verification method 8717. Returns signed record to client 872``` 873 874**XRPC Method for Inline Signing:** 875```json 876{ 877 "lexicon": 1, 878 "id": "com.atproto.attestation.sign", 879 "defs": { 880 "main": { 881 "type": "procedure", 882 "description": "Sign a CID with repository binding", 883 "input": { 884 "encoding": "application/json", 885 "schema": { 886 "type": "object", 887 "required": ["cid"], 888 "properties": { 889 "cid": { 890 "type": "string", 891 "format": "cid", 892 "description": "The CID to sign." 893 }, 894 "key": { 895 "type": "string", 896 "description": "Fragment ID of verification method" 897 } 898 } 899 } 900 }, 901 "output": { 902 "encoding": "application/json", 903 "schema": { 904 "type": "object", 905 "required": ["signature"], 906 "properties": { 907 "signature": { 908 "type": "bytes", 909 "description": "The signature" 910 } 911 } 912 } 913 } 914 } 915 } 916} 917``` 918 919## 7. Examples and Use Cases 920 921### 7.1 Verification (Remote Attestation) 922 923**Scenario:** An organization provides profile verification records. 924 925**Proof Record:** 926```json 927{ 928 "$type": "network.bsky.verification.proof", 929 "cid": "bafyreig7w5q432clkzxn5azlybqi37lnuvxvl3uucbqojgew4cujyoamzq", 930 "type": "individual" 931} 932``` 933 934Note: The CID was generated with `$sig` containing: 935```json 936{ 937 "$type": "network.bsky.verification.proof", 938 "repository": "did:plc:cbkjy5n7bk3ax2wplmtjofq2", 939 "type": "individual" 940} 941``` 942 943**Identity's Profile Record with Reference:** 944```json 945{ 946 "$type": "app.bsky.actor.profile", 947 "avatar": { 948 "$type": "blob", 949 "mimeType": "image/jpeg", 950 "ref": { 951 "$link": "bafkreie4cpq6eisks4gojzfypjqjqyetaqc53rq224vu2ic7wht3wgdtem" 952 }, 953 "size": 622579 954 }, 955 "banner": { 956 "$type": "blob", 957 "mimeType": "image/jpeg", 958 "ref": { 959 "$link": "bafkreigecokmzaufpshelpgtlbguq3fgcq5qxnemgjd4l3sfdkuggumsum" 960 }, 961 "size": 758665 962 }, 963 "description": "he/him; Protocols, platforms, and machine learning; worker at GitHub; Check out @smokesignal.events @atwork.place\n\nProfile: myself with a thick mustache, septum ring, and hat", 964 "displayName": "Nick Gerakines", 965 "signatures": [ 966 { 967 "$type": "com.atproto.repo.strongRef", 968 "cid": "bafyreigk73rnjpjfjjeeii25w2cczdq7tpzwrv4xeyo7gs47m75pqshbau", 969 "uri": "at://did:plc:verify.bsky.network/network.bsky.verification.proof/3m3i7it5ya7cq" 970 } 971 ] 972} 973``` 974 975### 7.2 Badge Award (Inline Attestation) 976 977**Scenario:** Issue achievement badge to user 978 979```json 980{ 981 "$type": "community.lexicon.badge.award", 982 "badge": { 983 "$type": "com.atproto.repo.strongRef", 984 "cid": "bafyreibnfpriilyjmssycvlkcp46cmoscwon7okbfvhjmobggisinerj5e", 985 "uri": "at://did:plc:tgudj2fjm77pzkuawquqhsxm/community.lexicon.badge.definition/3ltwfsgx3vu2a" 986 }, 987 "did": "did:plc:cbkjy5n7bk3ax2wplmtjofq2", 988 "issued": "2025-07-14T12:00:00.000Z", 989 "signatures": [ 990 { 991 "$type": "community.lexicon.badge.proof", 992 "key": "did:plc:tgudj2fjm77pzkuawquqhsxm#badge", 993 "signature": { 994 "$bytes": "JjLKuf35PstZQhef36SHtGrPrlvWy6+Qt6xI2zINOBNAxh4pAAaq5X3tZdJc/3Y9wDZp1X7Sk97fMERexYMvBQ==" 995 } 996 } 997 ] 998} 999``` 1000 1001Note: The signature was created from a CID generated with `$sig` containing: 1002```json 1003{ 1004 "$type": "community.lexicon.badge.proof", 1005 "repository": "did:plc:cbkjy5n7bk3ax2wplmtjofq2", 1006 "key": "did:plc:tgudj2fjm77pzkuawquqhsxm#badge" 1007} 1008``` 1009 1010### 7.3 Event RSVP with Capacity Control 1011 1012**Scenario:** Ticket information is back-referenced to RSVPs 1013 1014**Proof Record:** 1015```json 1016{ 1017 "$type": "org.atmosphereconf.ticketProof", 1018 "cid": "bafyreid57no4elksfm3m5axi6tacs65rtjholez4qgdd3p5k5lfzeinwhm", 1019 "role": "organizer", 1020 "ticket": "full" 1021} 1022``` 1023 1024Note: The CID was generated with `$sig` containing: 1025```json 1026{ 1027 "$type": "org.atmosphereconf.ticketProof", 1028 "repository": "did:plc:lehcqqkwzcwvjvw66uthu5oq", 1029 "role": "organizer", 1030 "ticket": "full" 1031} 1032``` 1033 1034**RSVP Record:** 1035```json 1036{ 1037 "$type": "events.smokesignal.calendar.rsvp", 1038 "status": "events.smokesignal.calendar.rsvp#going", 1039 "subject": { 1040 "cid": "bafyreifwvuqm4hhkmrsaj3zxvpfqj7byjefucrb36burwbuu5uv5sk5jqa", 1041 "uri": "at://did:plc:lehcqqkwzcwvjvw66uthu5oq/events.smokesignal.calendar.event/3lcyaxnwvau2f" 1042 }, 1043 "signatures": [ 1044 { 1045 "$type": "com.atproto.repo.strongRef", 1046 "cid": "bafyreieo2yfcqvrkatitxhqyz54pmxvrafisnoocrpx5py5y3cawzh5slm", 1047 "uri": "at://did:plc:verify.bsky.network/org.atmosphereconf.ticketProof/3m3ia2cz7vqvo" 1048 } 1049 ] 1050} 1051``` 1052 1053### 7.4 Multi-Signer Document 1054 1055**Scenario:** Multiple authors and editors sign-off on their collaboration roles. 1056 1057**Article Record:** 1058```json 1059{ 1060 "$type": "publishing.awesome.article", 1061 "createdAt": "2025-10-14T09:00:00Z", 1062 "parties": [ 1063 "did:plc:party1", 1064 "did:plc:party2", 1065 "did:plc:party3" 1066 ], 1067 "signatures": [ 1068 { 1069 "$type": "publishing.awesome.collaborationProof", 1070 "key": "did:plc:party1#authoring", 1071 "role": "editor", 1072 "signature": { 1073 "$bytes": "jQR7uDmHE5fNbY7Z/W0Y20LK4fFDLFmJtDHjFSMhoe9EbR8Bbo6jMACMm5mERGbWyqVFFkc8rGRqpdyj51ymHA==" 1074 } 1075 }, 1076 { 1077 "$type": "com.atproto.repo.strongRef", 1078 "cid": "bafyreifryor4vmbibmtauvb2dre2uobsi7nguf75cm4fpnrvdlelwodyby", 1079 "uri": "at://did:plc:party2/publishing.awesome.collaborationProof/3m3ia64sghb4g" 1080 }, 1081 { 1082 "$type": "com.atproto.repo.strongRef", 1083 "cid": "bafyreifryor4vmbibmtauvb2dre2uobsi7nguf75cm4fpnrvdlelwodyby", 1084 "uri": "at://did:plc:party3/publishing.awesome.collaborationProof/3m3ia7ke7fz27" 1085 } 1086 ], 1087 "text": "This is a multi-author article" 1088} 1089``` 1090 1091Note: All signatures and proof records were created with `$sig` containing the `repository` field set to the DID of the repository housing the article record (`did:plc:articlerepo123`). 1092 1093**Party2 Proof Record:** 1094```json 1095{ 1096 "$type": "publishing.awesome.collaborationProof", 1097 "cid": "bafyreigppx6wwhoeuf25crbkwbjyqkqj5lnk2zuyjf7okmyai6ym7obnda", 1098 "role": "co-author" 1099} 1100``` 1101 1102**Party3 Proof Record:** 1103```json 1104{ 1105 "$type": "publishing.awesome.collaborationProof", 1106 "cid": "bafyreigppx6wwhoeuf25crbkwbjyqkqj5lnk2zuyjf7okmyai6ym7obnda", 1107 "role": "co-author" 1108} 1109``` 1110 1111## 8. Conclusion 1112 1113This specification defines a minimal yet comprehensive attestation framework for ATProtocol that: 1114 1115- **Prevents Replay Attacks**: The mandatory `repository` field in `$sig` binds attestations to specific repositories 1116- **Leverages Content Addressing**: CIDs provide immutable, verifiable references 1117- **Embraces Simplicity**: Inline attestations require only $type + signature + key; remote attestations only CID 1118- **Uses Union Types**: The `signatures` array supports multiple types with required `$type` discriminator 1119- **Unifies CID Generation**: Both patterns use the same CID generation process with mandatory `$sig.$type` and `$sig.repository` 1120- **Differentiates Usage**: Inline attestations sign CID bytes; remote attestations store CID directly 1121- **Integrates with DIDs**: Uses existing cryptographic infrastructure 1122- **Provides Security**: Repository binding, scope control via `$sig`, robust verification, and replay protection