Something went wrong. Try again.
Personal ATProto tools.
Something went wrong. Try again.
6.8 kB · 142 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142//! # Crypto//! Sign messages using the secp256k1 elliptic curve algorithm.
use secp256k1::{Secp256k1, Message, SecretKey, PublicKey};use secp256k1::hashes::{sha256, Hash};use std::str::FromStr;
use crate::types::{AssignedLabelResponse, RetrievedLabelResponse, SignatureBytes, SignatureEnum};
/// Cryptographic signing and verification.pub struct Crypto { private_key: SecretKey, _public_key: PublicKey,}impl Default for Crypto { fn default() -> Self { Self::new() }}impl Crypto { /// Create a new `Crypto` instance. pub fn new() -> Self { let secp = Secp256k1::new(); drop(dotenvy::dotenv().expect("Failed to load .env file")); let private_key_hex = std::env::var("PRIVATE_KEY_HEX").expect("Expected to be able to get a private key from the environment, but failed"); let private_key_vec = hex::decode(private_key_hex).expect("Expected to be able to decode a hex string, but failed"); let private_key_array: [u8; 32] = private_key_vec.as_slice().try_into().expect("Expected 32 bytes, within curve order, but failed"); let private_key = SecretKey::from_slice(&private_key_array).expect("Expected 32 bytes, within curve order, but failed"); let public_key = PublicKey::from_secret_key(&secp, &private_key); Self { private_key, _public_key: public_key, } } /// Create a new `Crypto` instance from a slice. pub fn from_slice(slice: &[u8]) -> Self { let secp = Secp256k1::new(); let private_key = SecretKey::from_slice(slice).expect("Expected 32 bytes, within curve order, but failed"); let public_key = PublicKey::from_secret_key(&secp, &private_key); Self { private_key, _public_key: public_key, } } /// Sign a message. #[expect(clippy::cognitive_complexity)] pub fn sign(&self, label: &mut AssignedLabelResponse) { let secp = Secp256k1::new(); let label_for_serialization = RetrievedLabelResponse { cts: label.cts.clone(), neg: label.neg, src: label.src.clone(), uri: label.uri.clone(), val: label.val.clone(), ver: label.ver, }; tracing::debug!("Label for serialization: {:?}", label_for_serialization); label.sig = None; // let label_json = serde_json::to_string(&label_for_serialization).unwrap(); // let digest = sha256::Hash::hash(msg.as_bytes()); // let message = Message::from_digest(digest.to_byte_array()); let label_cbor = serde_cbor::to_vec(&label_for_serialization).expect("Expected to be able to serialize a label, but failed"); tracing::debug!("Label CBOR: {:?}", label_cbor); // decode the cbor we just made let label_decoded: RetrievedLabelResponse = serde_cbor::from_slice(&label_cbor).expect("Expected to be able to deserialize a label, but failed"); tracing::debug!("Label decoded: {:?}", label_decoded); let digest = sha256::Hash::hash(&label_cbor); let message = Message::from_digest(digest.to_byte_array()); let sig = secp.sign_ecdsa(&message, &self.private_key); // verify the sig we just made: let verified = secp.verify_ecdsa(&message, &sig, &self._public_key); assert!(verified.is_ok()); tracing::debug!("Verified: {:?}", verified); tracing::debug!("Message: {:?}", message); tracing::debug!("Signature: {:?}", sig); tracing::debug!("Public key: {:?}", self._public_key); // let serialized_sig = sig.serialize_der(); // return raw 64 byte sig not DER-encoded let serialized_sig: [u8; 64] = sig.serialize_compact(); // serialized_sig.to_vec() label.sig = Some(SignatureEnum::Bytes(SignatureBytes::from_bytes(serialized_sig))); } /// Consume a label response and validate the signature. #[expect(clippy::cognitive_complexity)] pub fn validate(&self, label: RetrievedLabelResponse, sig: &str, public_key_string: &str, // multibase-encoded string ) -> bool { tracing::debug!("Retrieved label: {:?}", label); // let public_key_vec = hex::decode(public_key_string).unwrap(); // When encoding public keys as strings, the preferred representation uses multibase (with base58btc specifically) and a multicode prefix to indicate the specific key type. By embedding metadata about the type of key in the encoding itself, they can be parsed unambiguously. // The process for encoding a public key in this format is: // Encode the public key curve "point" as bytes. Be sure to use the smaller "compact" or "compressed" representation. // Prepend the appropriate curve multicodec value, as varint-encoded bytes, in front of the key bytes // p256 (compressed, 33 byte key length): p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24] // k256 (compressed, 33 byte key length): secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01] // Encode the combined bytes with with base58btc, and prefix with a z character, yielding a multibase-encoded string // The decoding process is the same in reverse, using the identified curve type as context. let public_key_string = public_key_string.strip_prefix("z").expect("Expected to be able to strip a prefix, but failed"); let public_key_vec = bs58::decode(public_key_string).into_vec().expect("Expected to be able to decode a base58 string, but failed"); // // Remove the multicodec prefix // let public_key_vec = public_key_vec[2..].to_vec(); // Determine which curve the key is for match public_key_vec[0] { 0x80 => { tracing::debug!("p256"); // p256 }, 0xE7 => { tracing::debug!("k256"); // k256 }, _ => { panic!("Unknown curve"); }, }; let public_key_vec = public_key_vec[2..].to_vec(); let public_key_array: [u8; 33] = public_key_vec.as_slice().try_into().expect("Expected 33 bytes, within curve order, but failed"); let public_key = PublicKey::from_slice(&public_key_array).expect("Expected 33 bytes, within curve order, but failed"); // use of the "low-S" signature variant is required // let secp = Secp256k1::new(); let secp = Secp256k1::verification_only(); // let label_json = serde_json::to_string(&label).unwrap(); // tracing::debug!("Label JSON: {:?}", label_json); let label_cbor = serde_cbor::to_vec(&label).expect("Expected to be able to serialize a label, but failed"); let digest = sha256::Hash::hash(&label_cbor); tracing::debug!("Digest: {:?}", digest); let message = Message::from_digest(digest.to_byte_array()); tracing::debug!("Signature: {:?}", sig); let sig = SignatureBytes::from_str(sig).expect("Expected to be able to parse a signature from a string, but failed"); tracing::debug!("Signature bytes: {:?}", sig); let signature = secp256k1::ecdsa::Signature::from_compact(&sig.as_vec()).expect("Expected to be able to parse a signature from a byte array, but failed"); tracing::debug!("Message: {:?}", message); tracing::debug!("Signature: {:?}", signature); tracing::debug!("Public key: {:?}", public_key); secp.verify_ecdsa(&message, &signature, &public_key).is_ok() }
}