1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
9use rand::rngs::OsRng;
10use suture_common::Hash;
11use thiserror::Error;
12
13#[derive(Error, Debug)]
14pub enum SigningError {
15 #[error("signature verification failed: {0}")]
16 VerificationFailed(String),
17
18 #[error("invalid signature: {0}")]
19 InvalidSignature(#[from] ed25519_dalek::SignatureError),
20
21 #[error("key error: {0}")]
22 KeyError(String),
23
24 #[error("{0}")]
25 Custom(String),
26}
27
28#[derive(Clone)]
29pub struct SigningKeypair {
30 signing_key: SigningKey,
31 verifying_key: VerifyingKey,
32}
33
34impl SigningKeypair {
35 pub fn generate() -> Self {
36 let signing_key = SigningKey::generate(&mut OsRng);
37 let verifying_key = signing_key.verifying_key();
38 Self {
39 signing_key,
40 verifying_key,
41 }
42 }
43
44 pub fn public_key_bytes(&self) -> [u8; 32] {
45 self.verifying_key.to_bytes()
46 }
47
48 pub fn private_key_bytes(&self) -> Vec<u8> {
49 self.signing_key.to_bytes().to_vec()
50 }
51
52 pub fn sign(&self, canonical_bytes: &[u8]) -> Signature {
53 self.signing_key.sign(canonical_bytes)
54 }
55
56 pub fn verifying_key(&self) -> &VerifyingKey {
57 &self.verifying_key
58 }
59}
60
61#[allow(clippy::too_many_arguments)]
62pub fn canonical_patch_bytes(
63 operation_type: &str,
64 touch_set: &[String],
65 target_path: &Option<String>,
66 payload: &[u8],
67 parent_ids: &[Hash],
68 author: &str,
69 message: &str,
70 timestamp: u64,
71) -> Vec<u8> {
72 let mut buf = Vec::new();
73
74 buf.extend_from_slice(operation_type.as_bytes());
75 buf.push(0);
76
77 let mut sorted_touches: Vec<&String> = touch_set.iter().collect();
78 sorted_touches.sort();
79 for touch in &sorted_touches {
80 buf.extend_from_slice(touch.as_bytes());
81 buf.push(0);
82 }
83
84 match target_path {
85 Some(path) => {
86 buf.extend_from_slice(path.as_bytes());
87 }
88 None => {
89 buf.push(0xFF);
90 }
91 }
92 buf.push(0);
93
94 buf.extend_from_slice(&(payload.len() as u64).to_le_bytes());
95 buf.extend_from_slice(payload);
96
97 let mut sorted_parents: Vec<&Hash> = parent_ids.iter().collect();
98 sorted_parents.sort_by_key(|h| h.to_hex());
99 for parent in &sorted_parents {
100 buf.extend_from_slice(parent.to_hex().as_bytes());
101 buf.push(0);
102 }
103
104 buf.extend_from_slice(&(timestamp.to_le_bytes()));
105 buf.push(0);
106
107 buf.extend_from_slice(author.as_bytes());
108 buf.push(0);
109
110 buf.extend_from_slice(message.as_bytes());
111
112 buf
113}
114
115pub fn verify_signature(
116 verifying_key_bytes: &[u8; 32],
117 canonical_bytes: &[u8],
118 signature_bytes: &[u8; 64],
119) -> Result<(), SigningError> {
120 let verifying_key = VerifyingKey::from_bytes(verifying_key_bytes)
121 .map_err(|e| SigningError::VerificationFailed(e.to_string()))?;
122 let signature = Signature::from_bytes(signature_bytes);
123 verifying_key
124 .verify(canonical_bytes, &signature)
125 .map_err(|e| SigningError::VerificationFailed(e.to_string()))
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::patch::types::{OperationType, Patch, TouchSet};
132
133 #[test]
134 fn test_generate_keypair() {
135 let kp = SigningKeypair::generate();
136 assert_eq!(kp.public_key_bytes().len(), 32);
137 assert_eq!(kp.private_key_bytes().len(), 32);
138 }
139
140 #[test]
141 fn test_sign_and_verify() {
142 let kp = SigningKeypair::generate();
143 let data = b"hello, suture!";
144
145 let signature = kp.sign(data);
146 let result = verify_signature(&kp.public_key_bytes(), data, &signature.to_bytes());
147 assert!(result.is_ok());
148 }
149
150 #[test]
151 fn test_verify_wrong_data_fails() {
152 let kp = SigningKeypair::generate();
153 let data = b"hello, suture!";
154 let wrong_data = b"hello, world!";
155
156 let signature = kp.sign(data);
157 let result = verify_signature(&kp.public_key_bytes(), wrong_data, &signature.to_bytes());
158 assert!(result.is_err());
159 }
160
161 #[test]
162 fn test_verify_wrong_key_fails() {
163 let kp1 = SigningKeypair::generate();
164 let kp2 = SigningKeypair::generate();
165 let data = b"hello, suture!";
166
167 let signature = kp1.sign(data);
168 let result = verify_signature(&kp2.public_key_bytes(), data, &signature.to_bytes());
169 assert!(result.is_err());
170 }
171
172 #[test]
173 fn test_canonical_patch_bytes_deterministic() {
174 let bytes1 = canonical_patch_bytes(
175 "Modify",
176 &["a.txt".to_string(), "b.txt".to_string()],
177 &Some("a.txt".to_string()),
178 b"payload",
179 &[],
180 "alice",
181 "test message",
182 1000,
183 );
184 let bytes2 = canonical_patch_bytes(
185 "Modify",
186 &["b.txt".to_string(), "a.txt".to_string()],
187 &Some("a.txt".to_string()),
188 b"payload",
189 &[],
190 "alice",
191 "test message",
192 1000,
193 );
194 assert_eq!(bytes1, bytes2);
195 }
196
197 #[test]
198 fn test_roundtrip_patch_signing() {
199 let kp = SigningKeypair::generate();
200
201 let patch = Patch::new(
202 OperationType::Modify,
203 TouchSet::single("test.txt"),
204 Some("test.txt".to_string()),
205 b"hello".to_vec(),
206 vec![],
207 "alice".to_string(),
208 "test commit".to_string(),
209 );
210
211 let canonical = canonical_patch_bytes(
212 &patch.operation_type.to_string(),
213 &patch.touch_set.addresses(),
214 &patch.target_path,
215 &patch.payload,
216 &patch.parent_ids,
217 &patch.author,
218 &patch.message,
219 patch.timestamp,
220 );
221
222 let signature = kp.sign(&canonical);
223 let result = verify_signature(&kp.public_key_bytes(), &canonical, &signature.to_bytes());
224 assert!(result.is_ok());
225 }
226}