atproto user agency toolkit for individuals and groups
8
fork

Configure Feed

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

at main 176 lines 4.1 kB view raw
1/** 2 * Challenge verifier: validate a response against the original challenge. 3 * 4 * Pure verification: 5 * - For MST proofs: calls verifyMstProof for each proof. 6 * - For block samples: checks availability and verifies prefix against local copy. 7 */ 8 9import type { BlockStore } from "../../ipfs.js"; 10import type { 11 StorageChallenge, 12 StorageChallengeResponse, 13 StorageChallengeResult, 14 MstProofResult, 15 BlockCheckResult, 16} from "./types.js"; 17import { verifyMstProof } from "../mst-proof.js"; 18 19/** 20 * Verify a challenge response. 21 * 22 * Checks: 23 * 1. Challenge not expired 24 * 2. Response challenge ID matches 25 * 3. MST proofs are valid (if applicable) 26 * 4. Block prefixes match local copies (if applicable) 27 */ 28export async function verifyResponse( 29 challenge: StorageChallenge, 30 response: StorageChallengeResponse, 31 blockStore: BlockStore, 32 now?: Date, 33): Promise<StorageChallengeResult> { 34 const start = Date.now(); 35 const currentTime = now ?? new Date(); 36 37 // Check expiration 38 if (currentTime > new Date(challenge.expiresAt)) { 39 return { 40 challengeId: challenge.id, 41 passed: false, 42 verifiedAt: currentTime.toISOString(), 43 durationMs: Date.now() - start, 44 }; 45 } 46 47 // Check challenge ID match 48 if (response.challengeId !== challenge.id) { 49 return { 50 challengeId: challenge.id, 51 passed: false, 52 verifiedAt: currentTime.toISOString(), 53 durationMs: Date.now() - start, 54 }; 55 } 56 57 let mstResults: MstProofResult[] | undefined; 58 let blockResults: BlockCheckResult[] | undefined; 59 let allPassed = true; 60 61 // Verify MST proofs 62 if ( 63 challenge.challengeType === "mst-proof" || 64 challenge.challengeType === "combined" 65 ) { 66 mstResults = []; 67 68 if ( 69 !response.mstProofs || 70 response.mstProofs.length !== challenge.recordPaths.length 71 ) { 72 allPassed = false; 73 } else { 74 for (let i = 0; i < challenge.recordPaths.length; i++) { 75 const recordPath = challenge.recordPaths[i]!; 76 const proof = response.mstProofs[i]!; 77 78 const verification = await verifyMstProof( 79 proof, 80 challenge.commitCid, 81 recordPath, 82 ); 83 84 mstResults.push({ 85 recordPath, 86 valid: verification.valid, 87 found: verification.found, 88 error: verification.error, 89 }); 90 91 if (!verification.valid) { 92 allPassed = false; 93 } 94 } 95 } 96 } 97 98 // Verify block samples 99 if ( 100 challenge.challengeType === "block-sample" || 101 challenge.challengeType === "combined" 102 ) { 103 blockResults = []; 104 105 if (challenge.blockCids) { 106 if ( 107 !response.blockResults || 108 response.blockResults.length !== challenge.blockCids.length 109 ) { 110 allPassed = false; 111 } else { 112 for (let i = 0; i < challenge.blockCids.length; i++) { 113 const expectedCid = challenge.blockCids[i]!; 114 const result = response.blockResults[i]!; 115 116 if (result.cid !== expectedCid) { 117 blockResults.push({ 118 cid: expectedCid, 119 available: false, 120 prefixValid: false, 121 }); 122 allPassed = false; 123 continue; 124 } 125 126 if (!result.available || !result.prefix) { 127 blockResults.push({ 128 cid: expectedCid, 129 available: false, 130 prefixValid: false, 131 }); 132 allPassed = false; 133 continue; 134 } 135 136 // Verify prefix against our local copy 137 const localBytes = await blockStore.getBlock(expectedCid); 138 let prefixValid = false; 139 140 if (localBytes) { 141 const localPrefix = localBytes.slice( 142 0, 143 result.prefix.length, 144 ); 145 prefixValid = Buffer.from(result.prefix).equals( 146 Buffer.from(localPrefix), 147 ); 148 } else { 149 // We don't have the block locally — accept any non-empty prefix 150 // (we're checking that *they* have it) 151 prefixValid = result.prefix.length > 0; 152 } 153 154 blockResults.push({ 155 cid: expectedCid, 156 available: result.available, 157 prefixValid, 158 }); 159 160 if (!prefixValid) { 161 allPassed = false; 162 } 163 } 164 } 165 } 166 } 167 168 return { 169 challengeId: challenge.id, 170 passed: allPassed, 171 mstResults, 172 blockResults, 173 verifiedAt: currentTime.toISOString(), 174 durationMs: Date.now() - start, 175 }; 176}