Something went wrong. Try again.
atproto made easy crates.io/crates/jacquard
atproto rust
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328//! Commit structures and signature verification for AT Protocol repositories.//!//! This module provides repository commit object handling with signature support.
pub mod firehose;pub mod proof;pub(crate) mod serde_bytes_helper;use crate::error::{CommitError, Result};use bytes::Bytes;use cid::Cid as IpldCid;use jacquard_common::types::crypto::PublicKey;use jacquard_common::types::string::Did;use jacquard_common::types::tid::Tid;use jacquard_common::{BosStr, IntoStatic};
/// Repository commit object////// This structure represents a signed commit in an AT Protocol repository./// Stored as a block in CAR files, identified by its CID.////// **Version compatibility**: v2 and v3 commits differ only in how `prev` is/// serialized (v2 uses it, v3 must include it even if null). This struct/// handles both by always including `prev` in serialization.#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]pub struct Commit<S: BosStr> { /// Repository DID pub did: Did<S>,
/// Commit version (2 or 3) pub version: i64,
/// MST root CID pub data: IpldCid,
/// Revision TID pub rev: Tid,
/// Previous commit CID (None for initial commit) pub prev: Option<IpldCid>,
/// Signature bytes #[serde(with = "serde_bytes_helper")] pub sig: Bytes,}
/// Unsigned commit object./// Explicitly a separate struct minus the sig field to match deserialization/signatures/// from other implementations.#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]pub struct UnsignedCommit<S: BosStr> { /// Repository DID pub did: Did<S>,
/// Commit version (2 or 3) pub version: i64,
/// MST root CID pub data: IpldCid,
/// Revision TID pub rev: Tid,
/// Previous commit CID (None for initial commit) pub prev: Option<IpldCid>,}
impl<S: BosStr + serde::Serialize> UnsignedCommit<S> { /// Create new unsigned commit (version = 3, sig empty) pub fn new_unsigned( did: Did<S>, data: IpldCid, rev: Tid, prev: Option<IpldCid>, ) -> UnsignedCommit<S> { Self { did, version: 3, data, rev, prev, } } /// Get unsigned commit bytes (for signing/verification) pub(super) fn unsigned_bytes(&self) -> Result<Vec<u8>> { // Serialize without signature field serde_ipld_dagcbor::to_vec(self) .map_err(|e| crate::error::CommitError::Serialization(Box::new(e)).into()) }
/// Sign this commit with a key pub fn sign(self, key: &impl SigningKey) -> Result<Commit<S>> { let unsigned = self.unsigned_bytes()?; let sig = key.sign_bytes(&unsigned)?;
Ok(Commit { did: self.did, version: self.version, data: self.data, rev: self.rev, prev: self.prev, sig, }) } /// Get the repository DID pub fn did(&self) -> &Did<S> { &self.did }
/// Get the MST root CID pub fn data(&self) -> &IpldCid { &self.data }
/// Get the revision TID pub fn rev(&self) -> &Tid { &self.rev }
/// Get the previous commit CID pub fn prev(&self) -> Option<&IpldCid> { self.prev.as_ref() }}
impl<S: BosStr + Clone + serde::Serialize> Commit<S> { /// Create new unsigned commit (version = 3, sig empty) pub fn new_unsigned( did: Did<S>, data: IpldCid, rev: Tid, prev: Option<IpldCid>, ) -> UnsignedCommit<S> { UnsignedCommit { did, version: 3, data, rev, prev, } }
/// Get unsigned commit bytes (for signing/verification) pub(super) fn unsigned_bytes(&self) -> Result<Vec<u8>> { // Serialize without signature field let unsigned = UnsignedCommit { did: self.did.clone(), version: self.version, data: self.data.clone(), rev: self.rev.clone(), prev: self.prev.clone(), }; serde_ipld_dagcbor::to_vec(&unsigned) .map_err(|e| crate::error::CommitError::Serialization(Box::new(e)).into()) }
/// Get the repository DID pub fn did(&self) -> &Did<S> { &self.did }
/// Get the MST root CID pub fn data(&self) -> &IpldCid { &self.data }
/// Get the revision TID pub fn rev(&self) -> &Tid { &self.rev }
/// Get the previous commit CID pub fn prev(&self) -> Option<&IpldCid> { self.prev.as_ref() }
/// Get the signature bytes pub fn sig(&self) -> &Bytes { &self.sig }
/// Serialize to DAG-CBOR pub fn to_cbor(&self) -> Result<Vec<u8>> { serde_ipld_dagcbor::to_vec(self).map_err(|e| CommitError::Serialization(Box::new(e)).into()) }
/// Deserialize from DAG-CBOR pub fn from_cbor<'a>(data: &'a [u8]) -> Result<Self> where S: serde::Deserialize<'a>, { serde_ipld_dagcbor::from_slice(data) .map_err(|e| CommitError::Serialization(Box::new(e)).into()) }
/// Compute CID of this commit pub fn to_cid(&self) -> Result<IpldCid> { let cbor = self.to_cbor()?; crate::mst::util::compute_cid(&cbor) }
/// Verify signature against a public key from a DID document. /// /// The key type is inferred from the PublicKey codec. pub fn verify(&self, pubkey: &PublicKey) -> std::result::Result<(), CommitError> { let unsigned = self .unsigned_bytes() .map_err(|e| CommitError::Serialization(e.into()))?; let signature = self.sig();
use jacquard_common::types::crypto::KeyCodec; match pubkey.codec { KeyCodec::Ed25519 => { let vk = pubkey .to_ed25519() .map_err(|e| CommitError::InvalidKey(e.to_string()))?; let sig = ed25519_dalek::Signature::from_slice(signature.as_ref()) .map_err(|e| CommitError::InvalidSignature(e.to_string()))?; vk.verify_strict(&unsigned, &sig) .map_err(|_| CommitError::SignatureVerificationFailed)?; } KeyCodec::Secp256k1 => { use k256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; let vk = pubkey .to_k256() .map_err(|e| CommitError::InvalidKey(e.to_string()))?; let verifying_key = VerifyingKey::from(&vk); let sig = Signature::from_slice(signature.as_ref()) .map_err(|e| CommitError::InvalidSignature(e.to_string()))?; verifying_key .verify(&unsigned, &sig) .map_err(|_| CommitError::SignatureVerificationFailed)?; } KeyCodec::P256 => { use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; let vk = pubkey .to_p256() .map_err(|e| CommitError::InvalidKey(e.to_string()))?; let verifying_key = VerifyingKey::from(&vk); let sig = Signature::from_slice(signature.as_ref()) .map_err(|e| CommitError::InvalidSignature(e.to_string()))?; verifying_key .verify(&unsigned, &sig) .map_err(|_| CommitError::SignatureVerificationFailed)?; } KeyCodec::Unknown(code) => { return Err(CommitError::UnsupportedKeyType(code)); } }
Ok(()) }}
impl<S> IntoStatic for Commit<S>where S: BosStr + IntoStatic, S::Output: BosStr,{ type Output = Commit<S::Output>;
fn into_static(self) -> Self::Output { Commit { did: self.did.into_static(), version: self.version, data: self.data, rev: self.rev, prev: self.prev, sig: self.sig, } }}
/// Trait for signing keys.////// Implemented for ed25519_dalek::SigningKey, k256::ecdsa::SigningKey, and p256::ecdsa::SigningKey.pub trait SigningKey { /// Sign the given data and return signature as Bytes fn sign_bytes(&self, data: &[u8]) -> Result<Bytes>;
/// Get the public key bytes fn public_key(&self) -> Vec<u8>;}
// Ed25519 implementationimpl SigningKey for ed25519_dalek::SigningKey { fn sign_bytes(&self, data: &[u8]) -> Result<Bytes> { use ed25519_dalek::Signer; let sig = Signer::sign(self, data); Ok(Bytes::copy_from_slice(&sig.to_bytes())) }
fn public_key(&self) -> Vec<u8> { self.verifying_key().to_bytes().to_vec() }}
// K-256 (secp256k1) implementationimpl SigningKey for k256::ecdsa::SigningKey { fn sign_bytes(&self, data: &[u8]) -> Result<Bytes> { use k256::ecdsa::signature::Signer; let sig: k256::ecdsa::Signature = Signer::sign(self, data); Ok(Bytes::copy_from_slice(&sig.to_bytes())) }
fn public_key(&self) -> Vec<u8> { self.verifying_key() .to_encoded_point(true) .as_bytes() .to_vec() }}
// P-256 implementationimpl SigningKey for p256::ecdsa::SigningKey { fn sign_bytes(&self, data: &[u8]) -> Result<Bytes> { use p256::ecdsa::signature::Signer; let sig: p256::ecdsa::Signature = Signer::sign(self, data); Ok(Bytes::copy_from_slice(&sig.to_bytes())) }
fn public_key(&self) -> Vec<u8> { self.verifying_key() .to_encoded_point(true) .as_bytes() .to_vec() }}