mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-02-09 05:40:09 +00:00
148 lines
5.0 KiB
Rust
148 lines
5.0 KiB
Rust
use bytes::Bytes;
|
|
use cid::Cid;
|
|
use std::collections::HashMap;
|
|
mod common;
|
|
|
|
#[tokio::test]
|
|
#[ignore = "depends on external live server state; run manually with --ignored"]
|
|
async fn test_verify_live_commit() {
|
|
let client = reqwest::Client::new();
|
|
let did = "did:plc:zp3oggo2mikqntmhrc4scby4";
|
|
let resp = client
|
|
.get(format!(
|
|
"https://testpds.wizardry.systems/xrpc/com.atproto.sync.getRepo?did={}",
|
|
did
|
|
))
|
|
.send()
|
|
.await
|
|
.expect("Failed to fetch repo");
|
|
assert!(
|
|
resp.status().is_success(),
|
|
"getRepo failed: {}",
|
|
resp.status()
|
|
);
|
|
let car_bytes = resp.bytes().await.expect("Failed to read body");
|
|
println!("CAR bytes: {} bytes", car_bytes.len());
|
|
let mut cursor = std::io::Cursor::new(&car_bytes[..]);
|
|
let (roots, blocks) = parse_car(&mut cursor).expect("Failed to parse CAR");
|
|
println!("CAR roots: {:?}", roots);
|
|
println!("CAR blocks: {}", blocks.len());
|
|
assert!(!roots.is_empty(), "No roots in CAR");
|
|
let root_cid = roots[0];
|
|
let root_block = blocks.get(&root_cid).expect("Root block not found");
|
|
let commit =
|
|
jacquard_repo::commit::Commit::from_cbor(root_block).expect("Failed to parse commit");
|
|
println!("Commit DID: {}", commit.did().as_str());
|
|
println!("Commit rev: {}", commit.rev());
|
|
println!("Commit prev: {:?}", commit.prev());
|
|
println!("Commit sig length: {} bytes", commit.sig().len());
|
|
let resp = client
|
|
.get(format!("https://plc.directory/{}", did))
|
|
.send()
|
|
.await
|
|
.expect("Failed to fetch DID doc");
|
|
let did_doc_text = resp.text().await.expect("Failed to read body");
|
|
println!("DID doc: {}", did_doc_text);
|
|
let did_doc: jacquard::common::types::did_doc::DidDocument<'_> =
|
|
serde_json::from_str(&did_doc_text).expect("Failed to parse DID doc");
|
|
let pubkey = did_doc
|
|
.atproto_public_key()
|
|
.expect("Failed to get public key")
|
|
.expect("No public key");
|
|
println!("Public key codec: {:?}", pubkey.codec);
|
|
println!("Public key bytes: {} bytes", pubkey.bytes.len());
|
|
match commit.verify(&pubkey) {
|
|
Ok(()) => println!("SIGNATURE VALID!"),
|
|
Err(e) => {
|
|
println!("SIGNATURE VERIFICATION FAILED: {:?}", e);
|
|
let unsigned = commit_unsigned_bytes(&commit);
|
|
println!("Unsigned bytes length: {} bytes", unsigned.len());
|
|
panic!("Signature verification failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn commit_unsigned_bytes(commit: &jacquard_repo::commit::Commit<'_>) -> Vec<u8> {
|
|
#[derive(serde::Serialize)]
|
|
struct UnsignedCommit<'a> {
|
|
did: &'a str,
|
|
version: i64,
|
|
data: &'a cid::Cid,
|
|
rev: &'a jacquard::types::string::Tid,
|
|
prev: Option<&'a cid::Cid>,
|
|
#[serde(with = "serde_bytes")]
|
|
sig: &'a [u8],
|
|
}
|
|
let unsigned = UnsignedCommit {
|
|
did: commit.did().as_str(),
|
|
version: 3,
|
|
data: commit.data(),
|
|
rev: commit.rev(),
|
|
prev: commit.prev(),
|
|
sig: &[],
|
|
};
|
|
serde_ipld_dagcbor::to_vec(&unsigned).unwrap()
|
|
}
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
fn parse_car(
|
|
cursor: &mut std::io::Cursor<&[u8]>,
|
|
) -> Result<(Vec<Cid>, HashMap<Cid, Bytes>), Box<dyn std::error::Error>> {
|
|
use std::io::Read;
|
|
fn read_varint<R: Read>(r: &mut R) -> std::io::Result<u64> {
|
|
let mut result = 0u64;
|
|
let mut shift = 0;
|
|
loop {
|
|
let mut byte = [0u8; 1];
|
|
r.read_exact(&mut byte)?;
|
|
result |= ((byte[0] & 0x7f) as u64) << shift;
|
|
if byte[0] & 0x80 == 0 {
|
|
break;
|
|
}
|
|
shift += 7;
|
|
}
|
|
Ok(result)
|
|
}
|
|
let header_len = read_varint(cursor)? as usize;
|
|
let mut header_bytes = vec![0u8; header_len];
|
|
cursor.read_exact(&mut header_bytes)?;
|
|
#[derive(serde::Deserialize)]
|
|
struct CarHeader {
|
|
#[allow(dead_code)]
|
|
version: u64,
|
|
roots: Vec<cid::Cid>,
|
|
}
|
|
let header: CarHeader = serde_ipld_dagcbor::from_slice(&header_bytes)?;
|
|
let mut blocks = HashMap::new();
|
|
while let Ok(len) = read_varint(cursor) {
|
|
let block_len = len as usize;
|
|
if block_len == 0 {
|
|
break;
|
|
}
|
|
let mut block_data = vec![0u8; block_len];
|
|
if cursor.read_exact(&mut block_data).is_err() {
|
|
break;
|
|
}
|
|
let cid_bytes = &block_data[..];
|
|
let (cid, cid_len) = parse_cid(cid_bytes)?;
|
|
let content = Bytes::copy_from_slice(&block_data[cid_len..]);
|
|
blocks.insert(cid, content);
|
|
}
|
|
Ok((header.roots, blocks))
|
|
}
|
|
fn parse_cid(bytes: &[u8]) -> Result<(Cid, usize), Box<dyn std::error::Error>> {
|
|
if bytes[0] == 0x01 {
|
|
let codec = bytes[1];
|
|
let _hash_type = bytes[2];
|
|
let hash_len = bytes[3] as usize;
|
|
let cid_len = 4 + hash_len;
|
|
let cid = Cid::new_v1(
|
|
codec as u64,
|
|
cid::multihash::Multihash::from_bytes(&bytes[2..cid_len])?,
|
|
);
|
|
Ok((cid, cid_len))
|
|
} else {
|
|
Err("Unsupported CID version".into())
|
|
}
|
|
}
|