Personal ATProto tools.
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 6.8 kB View raw
1//! # Crypto 2//! Sign messages using the secp256k1 elliptic curve algorithm. 3 4use secp256k1::{Secp256k1, Message, SecretKey, PublicKey}; 5use secp256k1::hashes::{sha256, Hash}; 6use std::str::FromStr; 7 8use crate::types::{AssignedLabelResponse, RetrievedLabelResponse, SignatureBytes, SignatureEnum}; 9 10 11/// Cryptographic signing and verification. 12pub struct Crypto { 13 private_key: SecretKey, 14 _public_key: PublicKey, 15} 16impl Default for Crypto { 17 fn default() -> Self { 18 Self::new() 19 } 20} 21impl Crypto { 22 /// Create a new `Crypto` instance. 23 pub fn new() -> Self { 24 let secp = Secp256k1::new(); 25 drop(dotenvy::dotenv().expect("Failed to load .env file")); 26 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"); 27 let private_key_vec = hex::decode(private_key_hex).expect("Expected to be able to decode a hex string, but failed"); 28 let private_key_array: [u8; 32] = private_key_vec.as_slice().try_into().expect("Expected 32 bytes, within curve order, but failed"); 29 let private_key = SecretKey::from_slice(&private_key_array).expect("Expected 32 bytes, within curve order, but failed"); 30 let public_key = PublicKey::from_secret_key(&secp, &private_key); 31 Self { 32 private_key, 33 _public_key: public_key, 34 } 35 } 36 /// Create a new `Crypto` instance from a slice. 37 pub fn from_slice(slice: &[u8]) -> Self { 38 let secp = Secp256k1::new(); 39 let private_key = SecretKey::from_slice(slice).expect("Expected 32 bytes, within curve order, but failed"); 40 let public_key = PublicKey::from_secret_key(&secp, &private_key); 41 Self { 42 private_key, 43 _public_key: public_key, 44 } 45 } 46 /// Sign a message. 47 #[expect(clippy::cognitive_complexity)] 48 pub fn sign(&self, label: &mut AssignedLabelResponse) { 49 let secp = Secp256k1::new(); 50 let label_for_serialization = RetrievedLabelResponse { 51 cts: label.cts.clone(), 52 neg: label.neg, 53 src: label.src.clone(), 54 uri: label.uri.clone(), 55 val: label.val.clone(), 56 ver: label.ver, 57 }; 58 tracing::debug!("Label for serialization: {:?}", label_for_serialization); 59 label.sig = None; 60 // let label_json = serde_json::to_string(&label_for_serialization).unwrap(); 61 // let digest = sha256::Hash::hash(msg.as_bytes()); 62 // let message = Message::from_digest(digest.to_byte_array()); 63 let label_cbor = serde_cbor::to_vec(&label_for_serialization).expect("Expected to be able to serialize a label, but failed"); 64 tracing::debug!("Label CBOR: {:?}", label_cbor); 65 // decode the cbor we just made 66 let label_decoded: RetrievedLabelResponse = serde_cbor::from_slice(&label_cbor).expect("Expected to be able to deserialize a label, but failed"); 67 tracing::debug!("Label decoded: {:?}", label_decoded); 68 let digest = sha256::Hash::hash(&label_cbor); 69 let message = Message::from_digest(digest.to_byte_array()); 70 let sig = secp.sign_ecdsa(&message, &self.private_key); 71 // verify the sig we just made: 72 let verified = secp.verify_ecdsa(&message, &sig, &self._public_key); 73 assert!(verified.is_ok()); 74 tracing::debug!("Verified: {:?}", verified); 75 tracing::debug!("Message: {:?}", message); 76 tracing::debug!("Signature: {:?}", sig); 77 tracing::debug!("Public key: {:?}", self._public_key); 78 // let serialized_sig = sig.serialize_der(); 79 // return raw 64 byte sig not DER-encoded 80 let serialized_sig: [u8; 64] = sig.serialize_compact(); 81 // serialized_sig.to_vec() 82 label.sig = Some(SignatureEnum::Bytes(SignatureBytes::from_bytes(serialized_sig))); 83 } 84 /// Consume a label response and validate the signature. 85 #[expect(clippy::cognitive_complexity)] 86 pub fn validate(&self, 87 label: RetrievedLabelResponse, 88 sig: &str, 89 public_key_string: &str, // multibase-encoded string 90 ) -> bool { 91 tracing::debug!("Retrieved label: {:?}", label); 92 // let public_key_vec = hex::decode(public_key_string).unwrap(); 93 // 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. 94 // The process for encoding a public key in this format is: 95 // Encode the public key curve "point" as bytes. Be sure to use the smaller "compact" or "compressed" representation. 96 // Prepend the appropriate curve multicodec value, as varint-encoded bytes, in front of the key bytes 97 // p256 (compressed, 33 byte key length): p256-pub, code 0x1200, varint-encoded bytes: [0x80, 0x24] 98 // k256 (compressed, 33 byte key length): secp256k1-pub, code 0xE7, varint bytes: [0xE7, 0x01] 99 // Encode the combined bytes with with base58btc, and prefix with a z character, yielding a multibase-encoded string 100 // The decoding process is the same in reverse, using the identified curve type as context. 101 let public_key_string = public_key_string.strip_prefix("z").expect("Expected to be able to strip a prefix, but failed"); 102 let public_key_vec = bs58::decode(public_key_string).into_vec().expect("Expected to be able to decode a base58 string, but failed"); 103 // // Remove the multicodec prefix 104 // let public_key_vec = public_key_vec[2..].to_vec(); 105 // Determine which curve the key is for 106 match public_key_vec[0] { 107 0x80 => { 108 tracing::debug!("p256"); 109 // p256 110 }, 111 0xE7 => { 112 tracing::debug!("k256"); 113 // k256 114 }, 115 _ => { 116 panic!("Unknown curve"); 117 }, 118 }; 119 let public_key_vec = public_key_vec[2..].to_vec(); 120 121 let public_key_array: [u8; 33] = public_key_vec.as_slice().try_into().expect("Expected 33 bytes, within curve order, but failed"); 122 let public_key = PublicKey::from_slice(&public_key_array).expect("Expected 33 bytes, within curve order, but failed"); 123 // use of the "low-S" signature variant is required 124 // let secp = Secp256k1::new(); 125 let secp = Secp256k1::verification_only(); 126 // let label_json = serde_json::to_string(&label).unwrap(); 127 // tracing::debug!("Label JSON: {:?}", label_json); 128 let label_cbor = serde_cbor::to_vec(&label).expect("Expected to be able to serialize a label, but failed"); 129 let digest = sha256::Hash::hash(&label_cbor); 130 tracing::debug!("Digest: {:?}", digest); 131 let message = Message::from_digest(digest.to_byte_array()); 132 tracing::debug!("Signature: {:?}", sig); 133 let sig = SignatureBytes::from_str(sig).expect("Expected to be able to parse a signature from a string, but failed"); 134 tracing::debug!("Signature bytes: {:?}", sig); 135 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"); 136 tracing::debug!("Message: {:?}", message); 137 tracing::debug!("Signature: {:?}", signature); 138 tracing::debug!("Public key: {:?}", public_key); 139 secp.verify_ecdsa(&message, &signature, &public_key).is_ok() 140 } 141 142}