chadtmiller.com/atproto
atproto's primitives in Rust, small and free of I/O
atproto 0.1.1 Latest 1 package
0
0
//! PLC operations, against what @did-plc/lib makes of the same keys
//! (tests/reference/identity.json): the same bytes, signatures and DIDs.
use atproto::keys::{Curve, Keypair};
use atproto::plc::{Op, Service, Signed, tombstone};
use serde_json::{Value, json};
fn reference() -> Value {
let path = format!(
"{}/tests/reference/identity.json",
env!("CARGO_MANIFEST_DIR")
);
serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap()
}
fn key(curve: Curve, hex: &Value) -> Keypair {
let h = hex.as_str().unwrap();
let bytes: Vec<u8> = (0..h.len())
.step_by(2)
.map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap())
.collect();
Keypair::from_bytes(curve, &bytes).unwrap()
}
#[test]
fn operations_as_the_reference_makes_them() {
let r = reference();
let rotation = key(Curve::K256, &r["keys"]["rotation"]);
let signing = key(Curve::K256, &r["keys"]["signing"]);
let p256 = key(Curve::P256, &r["keys"]["p256Rotation"]);
for (v, signer, keys) in [
(&r["plc"][0], &rotation, vec![rotation.public().did_key()]),
(
&r["plc"][1],
&p256,
vec![p256.public().did_key(), rotation.public().did_key()],
),
] {
let op = Op::atproto(
&signing.public().did_key(),
&keys,
"riders.groups.example.com",
"https://groups.example.com",
);
let genesis = op.clone().sign(None, signer);
assert_eq!(genesis.to_json(), v["genesis"], "{}", v["name"]);
assert_eq!(genesis.did(), v["did"].as_str().unwrap());
assert_eq!(genesis.cid().to_string(), v["genesisCid"].as_str().unwrap());
assert_eq!(genesis.prev(), None);
assert_eq!(genesis.signed_by(&keys), Some(0));
let mut handle = op.clone();
handle.also_known_as = vec!["at://riders.example.org".into()];
assert_eq!(handle.handle(), Some("riders.example.org"));
let handle = handle.sign(Some(genesis.cid()), signer);
assert_eq!(handle.to_json(), v["updateHandle"]);
let mut pds = op.clone();
pds.also_known_as = vec!["at://riders.example.org".into()];
pds.services.insert(
"atproto_pds".into(),
Service {
kind: "AtprotoPersonalDataServer".into(),
endpoint: "https://other.example.com".into(),
},
);
let pds = pds.sign(Some(handle.cid()), signer);
assert_eq!(pds.to_json(), v["updatePds"]);
let end = tombstone(pds.cid(), signer);
assert_eq!(end.to_json(), v["tombstone"]);
assert_eq!(end.prev(), Some(pds.cid()));
assert_eq!(end.signed_by(&keys), Some(0));
for (json, ours) in [
(&v["genesis"], &genesis),
(&v["updatePds"], &pds),
(&v["tombstone"], &end),
] {
assert_eq!(&Signed::from_json(json).unwrap(), ours);
}
}
}
#[test]
fn says_which_key_signed() {
let r = reference();
let rotation = key(Curve::K256, &r["keys"]["rotation"]);
let other = Keypair::generate(Curve::P256);
let genesis = Signed::from_json(&r["plc"][0]["genesis"]).unwrap();
let keys = [
other.public().did_key(),
"did:key:z1".into(),
rotation.public().did_key(),
];
assert_eq!(genesis.signed_by(&keys), Some(2));
assert_eq!(genesis.signed_by(&keys[..2]), None);
assert_eq!(genesis.signed_by(&[]), None);
}
#[test]
fn refuses_what_is_no_operation() {
let r = reference();
let g = &r["plc"][0]["genesis"];
let with = |k: &str, v: Value| {
let mut j = g.clone();
j[k] = v;
Signed::from_json(&j).unwrap_err().to_string()
};
assert_eq!(with("sig", json!("!")), "an operation's sig is base64url");
assert_eq!(
with("prev", json!("x")),
"prev: a CID is base32, starting with b"
);
assert_eq!(
with("rotationKeys", json!([1])),
"rotationKeys is a list of text"
);
assert_eq!(
with("alsoKnownAs", json!("x")),
"alsoKnownAs is a list of text"
);
assert_eq!(
with("verificationMethods", json!({"atproto": 1})),
"an operation's keys and services are maps of text"
);
assert_eq!(
with("services", json!({"atproto_pds": {"type": 1}})),
"an operation's keys and services are maps of text"
);
assert_eq!(
with("services", json!([])),
"an operation's keys and services are maps of text"
);
assert_eq!(
with("type", json!("create")),
"not a plc_operation or plc_tombstone"
);
let mut t = r["plc"][0]["tombstone"].clone();
t["prev"] = Value::Null;
assert_eq!(
Signed::from_json(&t).unwrap_err().to_string(),
"a tombstone names the operation it ends"
);
}