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