chadtmiller.com/atproto
atproto's primitives in Rust, small and free of I/O
atproto 0.1.1 Latest 1 package
0
0
//! Keys and signatures, against atproto's interop test vectors
//! (tests/interop/crypto).
use atproto::keys::{Curve, KeyError, Keypair, PublicKey};
use base64::Engine;
use base64::engine::general_purpose::STANDARD_NO_PAD;
use serde_json::Value;
fn vectors(name: &str) -> Vec<Value> {
let path = format!("{}/tests/interop/crypto/{name}", env!("CARGO_MANIFEST_DIR"));
serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap()
}
fn hex(text: &str) -> Vec<u8> {
(0..text.len())
.step_by(2)
.map(|i| u8::from_str_radix(&text[i..i + 2], 16).unwrap())
.collect()
}
fn b64(v: &Value) -> Vec<u8> {
STANDARD_NO_PAD.decode(v.as_str().unwrap()).unwrap()
}
#[test]
fn signatures_as_the_vectors_say() {
for v in vectors("signature-fixtures.json") {
let key = PublicKey::from_did_key(v["publicKeyDid"].as_str().unwrap()).unwrap();
let legacy = PublicKey::from_method(
v["didDocSuite"].as_str().unwrap(),
v["publicKeyMultibase"].as_str().unwrap(),
)
.unwrap();
assert_eq!(key, legacy);
assert_eq!(key.curve().alg(), v["algorithm"].as_str().unwrap());
assert_eq!(key.did_key(), v["publicKeyDid"].as_str().unwrap());
let valid = key.verify(&b64(&v["messageBase64"]), &b64(&v["signatureBase64"]));
// A high-S signature is refused here, and taken where either S is.
let tags = v["tags"].as_array().unwrap();
if tags.iter().any(|t| t == "high-s") {
assert!(key.verify_either_s(&b64(&v["messageBase64"]), &b64(&v["signatureBase64"])));
}
assert_eq!(
valid,
v["validSignature"].as_bool().unwrap(),
"{}",
v["comment"]
);
}
}
#[test]
fn did_keys_as_the_vectors_say() {
for (file, curve) in [
("w3c_didkey_K256.json", Curve::K256),
("w3c_didkey_P256.json", Curve::P256),
] {
for v in vectors(file) {
// The K-256 vectors give the secret in hex, the P-256 in base58.
let secret = match v.get("privateKeyBytesHex") {
Some(h) => hex(h.as_str().unwrap()),
None => bs58::decode(v["privateKeyBytesBase58"].as_str().unwrap())
.into_vec()
.unwrap(),
};
let key = Keypair::from_bytes(curve, &secret).unwrap();
assert_eq!(key.public().did_key(), v["publicDidKey"].as_str().unwrap());
assert_eq!(key.to_bytes().to_vec(), secret);
assert_eq!(key.curve(), curve);
}
}
}
#[test]
fn signs_low_s_and_verifies() {
for curve in [Curve::K256, Curve::P256] {
let key = Keypair::generate(curve);
let public = key.public();
assert_eq!(format!("{key:?}"), format!("Keypair({})", public.did_key()));
// Enough signatures that one would come out high-S unless normalized.
for i in 0..32u8 {
let sig = key.sign(&[i]);
assert!(public.verify(&[i], &sig));
assert!(!public.verify(&[i, i], &sig));
}
assert!(!public.verify(b"m", &[0; 10]));
let again = PublicKey::from_multibase(&public.to_multibase()).unwrap();
assert_eq!(again, public);
assert_eq!(
PublicKey::from_sec1(curve, &public.to_sec1()).unwrap(),
public
);
let method = PublicKey::from_method("Multikey", &public.to_multibase()).unwrap();
assert_eq!(method, public);
}
assert_eq!(Curve::P256.alg(), "ES256");
}
#[test]
fn refuses_what_is_no_key() {
let err = |r: Result<PublicKey, KeyError>| r.unwrap_err().to_string();
assert_eq!(
err(PublicKey::from_multibase("Q3sh")),
"a multikey is base58btc, starting with z"
);
assert_eq!(
err(PublicKey::from_multibase("z0OIl")),
"bad base58 in a key"
);
assert_eq!(
err(PublicKey::from_multibase("z1")),
"not a secp256k1 or P-256 multikey"
);
assert_eq!(err(PublicKey::from_did_key("did:web:x")), "not a did:key");
assert_eq!(
err(PublicKey::from_sec1(Curve::K256, &[2; 3])),
"not a secp256k1 key"
);
assert_eq!(
err(PublicKey::from_sec1(Curve::P256, &[2; 3])),
"not a P-256 key"
);
assert_eq!(
Keypair::from_bytes(Curve::P256, &[0; 32])
.unwrap_err()
.to_string(),
"not a secret key on its curve"
);
assert!(Keypair::from_bytes(Curve::K256, &[0; 3]).is_err());
}