A better Rust ATProto crate

fixing jacquard-repo signatures being incorrect compared to signatures from other implementations

Changed files
+96 -12
crates
jacquard-repo
src
commit
+96 -12
crates/jacquard-repo/src/commit/mod.rs
··· 12 12 use jacquard_common::types::crypto::PublicKey; 13 13 use jacquard_common::types::string::Did; 14 14 use jacquard_common::types::tid::Tid; 15 + 15 16 /// Repository commit object 16 17 /// 17 18 /// This structure represents a signed commit in an AT Protocol repository. ··· 43 44 pub sig: Bytes, 44 45 } 45 46 46 - impl<'a> Commit<'a> { 47 + /// Unsigned commit object. 48 + /// Explicitly a separate struct minus the sig field to match deserialization/signatures 49 + /// from other implementations. 50 + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] 51 + pub struct UnsignedCommit<'a> { 52 + /// Repository DID 53 + #[serde(borrow)] 54 + pub did: Did<'a>, 55 + 56 + /// Commit version (2 or 3) 57 + pub version: i64, 58 + 59 + /// MST root CID 60 + pub data: IpldCid, 61 + 62 + /// Revision TID 63 + pub rev: Tid, 64 + 65 + /// Previous commit CID (None for initial commit) 66 + pub prev: Option<IpldCid>, 67 + } 68 + 69 + impl<'a> UnsignedCommit<'a> { 47 70 /// Create new unsigned commit (version = 3, sig empty) 48 - pub fn new_unsigned(did: Did<'a>, data: IpldCid, rev: Tid, prev: Option<IpldCid>) -> Self { 71 + pub fn new_unsigned( 72 + did: Did<'a>, 73 + data: IpldCid, 74 + rev: Tid, 75 + prev: Option<IpldCid>, 76 + ) -> UnsignedCommit<'a> { 49 77 Self { 50 78 did, 51 79 version: 3, 52 80 data, 53 81 rev, 54 82 prev, 55 - sig: Bytes::new(), 56 83 } 57 84 } 85 + /// Get unsigned commit bytes (for signing/verification) 86 + pub(super) fn unsigned_bytes(&self) -> Result<Vec<u8>> { 87 + // Serialize without signature field 88 + let unsigned = self.clone(); 89 + serde_ipld_dagcbor::to_vec(&unsigned) 90 + .map_err(|e| crate::error::CommitError::Serialization(Box::new(e)).into()) 91 + } 58 92 59 93 /// Sign this commit with a key 60 - pub fn sign(mut self, key: &impl SigningKey) -> Result<Self> { 94 + pub fn sign(self, key: &impl SigningKey) -> Result<Commit<'a>> { 61 95 let unsigned = self.unsigned_bytes()?; 62 - self.sig = key.sign_bytes(&unsigned)?; 63 - Ok(self) 64 - } 96 + let sig = key.sign_bytes(&unsigned)?; 65 97 98 + Ok(Commit { 99 + did: self.did, 100 + version: self.version, 101 + data: self.data, 102 + rev: self.rev, 103 + prev: self.prev, 104 + sig, 105 + }) 106 + } 66 107 /// Get the repository DID 67 108 pub fn did(&self) -> &Did<'a> { 68 109 &self.did ··· 82 123 pub fn prev(&self) -> Option<&IpldCid> { 83 124 self.prev.as_ref() 84 125 } 126 + } 85 127 86 - /// Get the signature bytes 87 - pub fn sig(&self) -> &Bytes { 88 - &self.sig 128 + impl<'a> Commit<'a> { 129 + /// Create new unsigned commit (version = 3, sig empty) 130 + pub fn new_unsigned( 131 + did: Did<'a>, 132 + data: IpldCid, 133 + rev: Tid, 134 + prev: Option<IpldCid>, 135 + ) -> UnsignedCommit<'a> { 136 + UnsignedCommit { 137 + did, 138 + version: 3, 139 + data, 140 + rev, 141 + prev, 142 + } 89 143 } 90 144 91 145 /// Get unsigned commit bytes (for signing/verification) 92 146 pub(super) fn unsigned_bytes(&self) -> Result<Vec<u8>> { 93 147 // Serialize without signature field 94 - let mut unsigned = self.clone(); 95 - unsigned.sig = Bytes::new(); 148 + let unsigned = UnsignedCommit { 149 + did: self.did.clone(), 150 + version: self.version, 151 + data: self.data.clone(), 152 + rev: self.rev.clone(), 153 + prev: self.prev.clone(), 154 + }; 96 155 serde_ipld_dagcbor::to_vec(&unsigned) 97 156 .map_err(|e| crate::error::CommitError::Serialization(Box::new(e)).into()) 157 + } 158 + 159 + /// Get the repository DID 160 + pub fn did(&self) -> &Did<'a> { 161 + &self.did 162 + } 163 + 164 + /// Get the MST root CID 165 + pub fn data(&self) -> &IpldCid { 166 + &self.data 167 + } 168 + 169 + /// Get the revision TID 170 + pub fn rev(&self) -> &Tid { 171 + &self.rev 172 + } 173 + 174 + /// Get the previous commit CID 175 + pub fn prev(&self) -> Option<&IpldCid> { 176 + self.prev.as_ref() 177 + } 178 + 179 + /// Get the signature bytes 180 + pub fn sig(&self) -> &Bytes { 181 + &self.sig 98 182 } 99 183 100 184 /// Serialize to DAG-CBOR