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}