PDS software with bells & whistles you didn’t even know you needed. will move this to its own account when ready.
at main 5.1 kB view raw
1use bytes::Bytes; 2use cid::Cid; 3use std::collections::HashMap; 4mod common; 5 6#[tokio::test] 7#[ignore = "depends on external live server state; run manually with --ignored"] 8async fn test_verify_live_commit() { 9 let client = reqwest::Client::new(); 10 let did = "did:plc:zp3oggo2mikqntmhrc4scby4"; 11 let resp = client 12 .get(format!( 13 "https://testpds.wizardry.systems/xrpc/com.atproto.sync.getRepo?did={}", 14 did 15 )) 16 .send() 17 .await 18 .expect("Failed to fetch repo"); 19 assert!( 20 resp.status().is_success(), 21 "getRepo failed: {}", 22 resp.status() 23 ); 24 let car_bytes = resp.bytes().await.expect("Failed to read body"); 25 println!("CAR bytes: {} bytes", car_bytes.len()); 26 let mut cursor = std::io::Cursor::new(&car_bytes[..]); 27 let (roots, blocks) = parse_car(&mut cursor).expect("Failed to parse CAR"); 28 println!("CAR roots: {:?}", roots); 29 println!("CAR blocks: {}", blocks.len()); 30 assert!(!roots.is_empty(), "No roots in CAR"); 31 let root_cid = roots[0]; 32 let root_block = blocks.get(&root_cid).expect("Root block not found"); 33 let commit = 34 jacquard_repo::commit::Commit::from_cbor(root_block).expect("Failed to parse commit"); 35 println!("Commit DID: {}", commit.did().as_str()); 36 println!("Commit rev: {}", commit.rev()); 37 println!("Commit prev: {:?}", commit.prev()); 38 println!("Commit sig length: {} bytes", commit.sig().len()); 39 let resp = client 40 .get(format!("https://plc.directory/{}", did)) 41 .send() 42 .await 43 .expect("Failed to fetch DID doc"); 44 let did_doc_text = resp.text().await.expect("Failed to read body"); 45 println!("DID doc: {}", did_doc_text); 46 let did_doc: jacquard::common::types::did_doc::DidDocument<'_> = 47 serde_json::from_str(&did_doc_text).expect("Failed to parse DID doc"); 48 let pubkey = did_doc 49 .atproto_public_key() 50 .expect("Failed to get public key") 51 .expect("No public key"); 52 println!("Public key codec: {:?}", pubkey.codec); 53 println!("Public key bytes: {} bytes", pubkey.bytes.len()); 54 match commit.verify(&pubkey) { 55 Ok(()) => println!("SIGNATURE VALID!"), 56 Err(e) => { 57 println!("SIGNATURE VERIFICATION FAILED: {:?}", e); 58 let unsigned = commit_unsigned_bytes(&commit); 59 println!("Unsigned bytes length: {} bytes", unsigned.len()); 60 panic!("Signature verification failed"); 61 } 62 } 63} 64 65fn commit_unsigned_bytes(commit: &jacquard_repo::commit::Commit<'_>) -> Vec<u8> { 66 #[derive(serde::Serialize)] 67 struct UnsignedCommit<'a> { 68 did: &'a str, 69 version: i64, 70 data: &'a cid::Cid, 71 rev: &'a jacquard::types::string::Tid, 72 prev: Option<&'a cid::Cid>, 73 #[serde(with = "serde_bytes")] 74 sig: &'a [u8], 75 } 76 let unsigned = UnsignedCommit { 77 did: commit.did().as_str(), 78 version: 3, 79 data: commit.data(), 80 rev: commit.rev(), 81 prev: commit.prev(), 82 sig: &[], 83 }; 84 serde_ipld_dagcbor::to_vec(&unsigned).unwrap() 85} 86 87fn parse_car( 88 cursor: &mut std::io::Cursor<&[u8]>, 89) -> Result<(Vec<Cid>, HashMap<Cid, Bytes>), Box<dyn std::error::Error>> { 90 use std::io::Read; 91 fn read_varint<R: Read>(r: &mut R) -> std::io::Result<u64> { 92 let mut result = 0u64; 93 let mut shift = 0; 94 loop { 95 let mut byte = [0u8; 1]; 96 r.read_exact(&mut byte)?; 97 result |= ((byte[0] & 0x7f) as u64) << shift; 98 if byte[0] & 0x80 == 0 { 99 break; 100 } 101 shift += 7; 102 } 103 Ok(result) 104 } 105 let header_len = read_varint(cursor)? as usize; 106 let mut header_bytes = vec![0u8; header_len]; 107 cursor.read_exact(&mut header_bytes)?; 108 #[derive(serde::Deserialize)] 109 struct CarHeader { 110 #[allow(dead_code)] 111 version: u64, 112 roots: Vec<cid::Cid>, 113 } 114 let header: CarHeader = serde_ipld_dagcbor::from_slice(&header_bytes)?; 115 let mut blocks = HashMap::new(); 116 loop { 117 let block_len = match read_varint(cursor) { 118 Ok(len) => len as usize, 119 Err(_) => break, 120 }; 121 if block_len == 0 { 122 break; 123 } 124 let mut block_data = vec![0u8; block_len]; 125 if cursor.read_exact(&mut block_data).is_err() { 126 break; 127 } 128 let cid_bytes = &block_data[..]; 129 let (cid, cid_len) = parse_cid(cid_bytes)?; 130 let content = Bytes::copy_from_slice(&block_data[cid_len..]); 131 blocks.insert(cid, content); 132 } 133 Ok((header.roots, blocks)) 134} 135fn parse_cid(bytes: &[u8]) -> Result<(Cid, usize), Box<dyn std::error::Error>> { 136 if bytes[0] == 0x01 { 137 let codec = bytes[1]; 138 let _hash_type = bytes[2]; 139 let hash_len = bytes[3] as usize; 140 let cid_len = 4 + hash_len; 141 let cid = Cid::new_v1( 142 codec as u64, 143 cid::multihash::Multihash::from_bytes(&bytes[2..cid_len])?, 144 ); 145 Ok((cid, cid_len)) 146 } else { 147 Err("Unsupported CID version".into()) 148 } 149}