chie_crypto/
linkable_ring.rs

1//! Linkable ring signatures for double-spend prevention.
2//!
3//! Linkable ring signatures are an extension of ring signatures that allow
4//! detection of double-signing (when the same key signs twice with the same ring).
5//! This is crucial for preventing double-spending in anonymous payment systems
6//! while preserving signer anonymity for single uses.
7//!
8//! # Features
9//!
10//! - Anonymous signing within a group (like regular ring signatures)
11//! - Double-signing detection via key images
12//! - No linkability between different rings
13//! - Perfect for preventing double-spending of anonymous tokens
14//!
15//! # Example
16//!
17//! ```
18//! use chie_crypto::linkable_ring::{LinkableRingSignature, sign_linkable, verify_linkable, check_double_sign};
19//! use chie_crypto::KeyPair;
20//!
21//! let keypair1 = KeyPair::generate();
22//! let keypair2 = KeyPair::generate();
23//! let keypair3 = KeyPair::generate();
24//!
25//! let ring = vec![
26//!     keypair1.public_key(),
27//!     keypair2.public_key(),
28//!     keypair3.public_key(),
29//! ];
30//!
31//! // Sign two different messages with the same key
32//! let msg1 = b"Transaction 1";
33//! let msg2 = b"Transaction 2";
34//!
35//! let sig1 = sign_linkable(&keypair2, &ring, msg1).unwrap();
36//! let sig2 = sign_linkable(&keypair2, &ring, msg2).unwrap();
37//!
38//! // Both signatures verify
39//! assert!(verify_linkable(&ring, msg1, &sig1).unwrap());
40//! assert!(verify_linkable(&ring, msg2, &sig2).unwrap());
41//!
42//! // But we can detect they were signed by the same key!
43//! assert!(check_double_sign(&sig1, &sig2));
44//! ```
45
46use crate::signing::{KeyPair, PublicKey, verify as verify_signature};
47use blake3;
48use serde::{Deserialize, Serialize};
49use thiserror::Error;
50
51/// Error types for linkable ring signature operations.
52#[derive(Debug, Error)]
53pub enum LinkableRingError {
54    #[error("Ring must contain at least 2 public keys")]
55    RingTooSmall,
56
57    #[error("Signer not found in ring")]
58    SignerNotInRing,
59
60    #[error("Invalid signature")]
61    InvalidSignature,
62
63    #[error("Serialization error: {0}")]
64    SerializationError(String),
65
66    #[error("Ring size mismatch")]
67    RingSizeMismatch,
68
69    #[error("Invalid key image")]
70    InvalidKeyImage,
71}
72
73pub type LinkableRingResult<T> = Result<T, LinkableRingError>;
74
75/// A linkable ring signature with key image for double-sign detection.
76///
77/// The key image is a unique value derived from the signer's secret key
78/// and the ring. The same key signing twice will produce the same key image,
79/// allowing detection of double-signing.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct LinkableRingSignature {
82    /// Commitments for each ring member
83    commitments: Vec<[u8; 32]>,
84    /// The signature
85    #[serde(with = "serde_signature")]
86    signature: [u8; 64],
87    /// Key image for linkability detection
88    key_image: [u8; 32],
89    /// Ring identifier hash
90    ring_hash: [u8; 32],
91}
92
93// Serde helper for [u8; 64] signature
94mod serde_signature {
95    use serde::{Deserialize, Deserializer, Serializer};
96
97    pub fn serialize<S>(bytes: &[u8; 64], serializer: S) -> Result<S::Ok, S::Error>
98    where
99        S: Serializer,
100    {
101        serializer.serialize_bytes(bytes)
102    }
103
104    pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 64], D::Error>
105    where
106        D: Deserializer<'de>,
107    {
108        let bytes: Vec<u8> = Deserialize::deserialize(deserializer)?;
109        if bytes.len() != 64 {
110            return Err(serde::de::Error::custom("invalid signature length"));
111        }
112        let mut array = [0u8; 64];
113        array.copy_from_slice(&bytes);
114        Ok(array)
115    }
116}
117
118impl LinkableRingSignature {
119    /// Serialize to bytes.
120    pub fn to_bytes(&self) -> LinkableRingResult<Vec<u8>> {
121        crate::codec::encode(self).map_err(|e| LinkableRingError::SerializationError(e.to_string()))
122    }
123
124    /// Deserialize from bytes.
125    pub fn from_bytes(bytes: &[u8]) -> LinkableRingResult<Self> {
126        crate::codec::decode(bytes)
127            .map_err(|e| LinkableRingError::SerializationError(e.to_string()))
128    }
129
130    /// Get the key image for double-sign detection.
131    pub fn key_image(&self) -> &[u8; 32] {
132        &self.key_image
133    }
134
135    /// Get the ring identifier hash.
136    pub fn ring_hash(&self) -> &[u8; 32] {
137        &self.ring_hash
138    }
139
140    /// Get the number of ring members.
141    pub fn ring_size(&self) -> usize {
142        self.commitments.len()
143    }
144}
145
146/// Compute a deterministic key image from a secret key and ring.
147///
148/// The key image is computed as: H(secret_key || ring_hash)
149/// This ensures the same key produces the same image for the same ring,
150/// but different images for different rings.
151fn compute_key_image(secret_key: &[u8; 32], ring_hash: &[u8; 32]) -> [u8; 32] {
152    let mut hasher = blake3::Hasher::new();
153    hasher.update(b"CHIE-KEY-IMAGE-V1");
154    hasher.update(secret_key);
155    hasher.update(ring_hash);
156    *hasher.finalize().as_bytes()
157}
158
159/// Compute ring identifier hash.
160fn compute_ring_hash(ring: &[PublicKey]) -> [u8; 32] {
161    let mut hasher = blake3::Hasher::new();
162    hasher.update(b"CHIE-RING-HASH-V1");
163    for pk in ring {
164        hasher.update(pk);
165    }
166    *hasher.finalize().as_bytes()
167}
168
169/// Sign a message with a linkable ring signature.
170///
171/// Creates a signature that can be verified as coming from one of the ring
172/// members, and includes a key image that allows detecting if the same key
173/// signs multiple messages with the same ring.
174pub fn sign_linkable(
175    signer: &KeyPair,
176    ring: &[PublicKey],
177    message: &[u8],
178) -> LinkableRingResult<LinkableRingSignature> {
179    if ring.len() < 2 {
180        return Err(LinkableRingError::RingTooSmall);
181    }
182
183    // Verify signer is in ring
184    let signer_pubkey = signer.public_key();
185    let _signer_index = ring
186        .iter()
187        .position(|pk| pk == &signer_pubkey)
188        .ok_or(LinkableRingError::SignerNotInRing)?;
189
190    // Compute ring hash
191    let ring_hash = compute_ring_hash(ring);
192
193    // Compute key image
194    let secret_key = signer.secret_key();
195    let key_image = compute_key_image(&secret_key, &ring_hash);
196
197    // Create commitments for each ring member
198    let mut commitments = Vec::with_capacity(ring.len());
199
200    for (i, pk) in ring.iter().enumerate() {
201        let mut hasher = blake3::Hasher::new();
202        hasher.update(b"CHIE-LINKABLE-RING-V1");
203        hasher.update(message);
204        hasher.update(pk);
205        hasher.update(&i.to_le_bytes());
206        hasher.update(&ring_hash);
207        hasher.update(&key_image);
208
209        for ring_pk in ring {
210            hasher.update(ring_pk);
211        }
212
213        commitments.push(*hasher.finalize().as_bytes());
214    }
215
216    // Create signature
217    let mut sig_message = Vec::new();
218    sig_message.extend_from_slice(message);
219    sig_message.extend_from_slice(&key_image);
220    sig_message.extend_from_slice(&ring_hash);
221    for commitment in &commitments {
222        sig_message.extend_from_slice(commitment);
223    }
224
225    let signature = signer.sign(&sig_message);
226
227    Ok(LinkableRingSignature {
228        commitments,
229        signature,
230        key_image,
231        ring_hash,
232    })
233}
234
235/// Verify a linkable ring signature.
236///
237/// Returns Ok(true) if the signature is valid and was created by one of
238/// the ring members.
239pub fn verify_linkable(
240    ring: &[PublicKey],
241    message: &[u8],
242    signature: &LinkableRingSignature,
243) -> LinkableRingResult<bool> {
244    if ring.len() < 2 {
245        return Err(LinkableRingError::RingTooSmall);
246    }
247
248    if ring.len() != signature.commitments.len() {
249        return Err(LinkableRingError::RingSizeMismatch);
250    }
251
252    // Verify ring hash matches
253    let expected_ring_hash = compute_ring_hash(ring);
254    if expected_ring_hash != signature.ring_hash {
255        return Ok(false);
256    }
257
258    // Verify commitments
259    for (i, pk) in ring.iter().enumerate() {
260        let mut hasher = blake3::Hasher::new();
261        hasher.update(b"CHIE-LINKABLE-RING-V1");
262        hasher.update(message);
263        hasher.update(pk);
264        hasher.update(&i.to_le_bytes());
265        hasher.update(&signature.ring_hash);
266        hasher.update(&signature.key_image);
267
268        for ring_pk in ring {
269            hasher.update(ring_pk);
270        }
271
272        let expected_commitment = hasher.finalize();
273        if expected_commitment.as_bytes() != &signature.commitments[i] {
274            return Ok(false);
275        }
276    }
277
278    // Verify signature
279    let mut sig_message = Vec::new();
280    sig_message.extend_from_slice(message);
281    sig_message.extend_from_slice(&signature.key_image);
282    sig_message.extend_from_slice(&signature.ring_hash);
283    for commitment in &signature.commitments {
284        sig_message.extend_from_slice(commitment);
285    }
286
287    for pk in ring {
288        if verify_signature(pk, &sig_message, &signature.signature).is_ok() {
289            return Ok(true);
290        }
291    }
292
293    Ok(false)
294}
295
296/// Check if two signatures were created by the same key (double-signing detection).
297///
298/// Returns true if both signatures have the same key image and ring hash,
299/// indicating they were signed by the same key in the same ring.
300pub fn check_double_sign(sig1: &LinkableRingSignature, sig2: &LinkableRingSignature) -> bool {
301    sig1.key_image == sig2.key_image && sig1.ring_hash == sig2.ring_hash
302}
303
304/// Database for tracking used key images to prevent double-spending.
305pub struct KeyImageDb {
306    /// Set of used key images (ring_hash || key_image)
307    used_images: std::collections::HashSet<Vec<u8>>,
308}
309
310impl KeyImageDb {
311    /// Create a new key image database.
312    pub fn new() -> Self {
313        Self {
314            used_images: std::collections::HashSet::new(),
315        }
316    }
317
318    /// Check if a signature has already been used.
319    pub fn is_used(&self, signature: &LinkableRingSignature) -> bool {
320        let mut key = Vec::new();
321        key.extend_from_slice(&signature.ring_hash);
322        key.extend_from_slice(&signature.key_image);
323        self.used_images.contains(&key)
324    }
325
326    /// Mark a signature as used.
327    ///
328    /// Returns false if the signature was already used (double-spend attempt).
329    pub fn mark_used(&mut self, signature: &LinkableRingSignature) -> bool {
330        let mut key = Vec::new();
331        key.extend_from_slice(&signature.ring_hash);
332        key.extend_from_slice(&signature.key_image);
333        self.used_images.insert(key)
334    }
335
336    /// Get the number of used signatures.
337    pub fn size(&self) -> usize {
338        self.used_images.len()
339    }
340
341    /// Clear all used signatures.
342    pub fn clear(&mut self) {
343        self.used_images.clear();
344    }
345}
346
347impl Default for KeyImageDb {
348    fn default() -> Self {
349        Self::new()
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356    use crate::signing::KeyPair;
357
358    #[test]
359    fn test_linkable_ring_basic() {
360        let keypair1 = KeyPair::generate();
361        let keypair2 = KeyPair::generate();
362        let keypair3 = KeyPair::generate();
363
364        let ring = vec![
365            keypair1.public_key(),
366            keypair2.public_key(),
367            keypair3.public_key(),
368        ];
369
370        let message = b"Test message";
371        let signature = sign_linkable(&keypair2, &ring, message).unwrap();
372
373        assert!(verify_linkable(&ring, message, &signature).unwrap());
374    }
375
376    #[test]
377    fn test_double_sign_detection() {
378        let keypair1 = KeyPair::generate();
379        let keypair2 = KeyPair::generate();
380        let keypair3 = KeyPair::generate();
381
382        let ring = vec![
383            keypair1.public_key(),
384            keypair2.public_key(),
385            keypair3.public_key(),
386        ];
387
388        // Same key signs two different messages
389        let msg1 = b"Transaction 1";
390        let msg2 = b"Transaction 2";
391
392        let sig1 = sign_linkable(&keypair2, &ring, msg1).unwrap();
393        let sig2 = sign_linkable(&keypair2, &ring, msg2).unwrap();
394
395        // Key images should match (double-sign detected)
396        assert!(check_double_sign(&sig1, &sig2));
397        assert_eq!(sig1.key_image(), sig2.key_image());
398    }
399
400    #[test]
401    fn test_different_signers_different_images() {
402        let keypair1 = KeyPair::generate();
403        let keypair2 = KeyPair::generate();
404        let keypair3 = KeyPair::generate();
405
406        let ring = vec![
407            keypair1.public_key(),
408            keypair2.public_key(),
409            keypair3.public_key(),
410        ];
411
412        let message = b"Same message";
413
414        let sig1 = sign_linkable(&keypair1, &ring, message).unwrap();
415        let sig2 = sign_linkable(&keypair2, &ring, message).unwrap();
416
417        // Different keys produce different images
418        assert!(!check_double_sign(&sig1, &sig2));
419        assert_ne!(sig1.key_image(), sig2.key_image());
420    }
421
422    #[test]
423    fn test_different_rings_different_images() {
424        let keypair1 = KeyPair::generate();
425        let keypair2 = KeyPair::generate();
426        let keypair3 = KeyPair::generate();
427        let keypair4 = KeyPair::generate();
428
429        let ring1 = vec![
430            keypair1.public_key(),
431            keypair2.public_key(),
432            keypair3.public_key(),
433        ];
434
435        let ring2 = vec![
436            keypair1.public_key(),
437            keypair2.public_key(),
438            keypair4.public_key(),
439        ];
440
441        let message = b"Test";
442
443        // Same key, different rings
444        let sig1 = sign_linkable(&keypair1, &ring1, message).unwrap();
445        let sig2 = sign_linkable(&keypair1, &ring2, message).unwrap();
446
447        // Different rings produce different images
448        assert!(!check_double_sign(&sig1, &sig2));
449        assert_ne!(sig1.key_image(), sig2.key_image());
450        assert_ne!(sig1.ring_hash(), sig2.ring_hash());
451    }
452
453    #[test]
454    fn test_serialization() {
455        let keypair1 = KeyPair::generate();
456        let keypair2 = KeyPair::generate();
457
458        let ring = vec![keypair1.public_key(), keypair2.public_key()];
459        let message = b"Serialization test";
460
461        let signature = sign_linkable(&keypair1, &ring, message).unwrap();
462
463        let bytes = signature.to_bytes().unwrap();
464        let deserialized = LinkableRingSignature::from_bytes(&bytes).unwrap();
465
466        assert!(verify_linkable(&ring, message, &deserialized).unwrap());
467        assert_eq!(signature.key_image(), deserialized.key_image());
468    }
469
470    #[test]
471    fn test_key_image_db() {
472        let keypair1 = KeyPair::generate();
473        let keypair2 = KeyPair::generate();
474
475        let ring = vec![keypair1.public_key(), keypair2.public_key()];
476
477        let msg1 = b"Transaction 1";
478        let msg2 = b"Transaction 2";
479
480        let sig1 = sign_linkable(&keypair1, &ring, msg1).unwrap();
481        let sig2 = sign_linkable(&keypair1, &ring, msg2).unwrap();
482
483        let mut db = KeyImageDb::new();
484
485        // First use should succeed
486        assert!(db.mark_used(&sig1));
487        assert_eq!(db.size(), 1);
488
489        // Check if used
490        assert!(db.is_used(&sig1));
491
492        // Second use with same key should fail (double-spend)
493        assert!(!db.mark_used(&sig2));
494
495        // Different key should succeed
496        let sig3 = sign_linkable(&keypair2, &ring, msg1).unwrap();
497        assert!(db.mark_used(&sig3));
498        assert_eq!(db.size(), 2);
499    }
500
501    #[test]
502    fn test_wrong_message() {
503        let keypair1 = KeyPair::generate();
504        let keypair2 = KeyPair::generate();
505
506        let ring = vec![keypair1.public_key(), keypair2.public_key()];
507
508        let message = b"Original";
509        let wrong_message = b"Wrong";
510
511        let signature = sign_linkable(&keypair1, &ring, message).unwrap();
512
513        assert!(!verify_linkable(&ring, wrong_message, &signature).unwrap());
514    }
515
516    #[test]
517    fn test_ring_too_small() {
518        let keypair = KeyPair::generate();
519        let ring = vec![keypair.public_key()];
520        let message = b"Test";
521
522        let result = sign_linkable(&keypair, &ring, message);
523        assert!(matches!(result, Err(LinkableRingError::RingTooSmall)));
524    }
525
526    #[test]
527    fn test_signer_not_in_ring() {
528        let keypair1 = KeyPair::generate();
529        let keypair2 = KeyPair::generate();
530        let outsider = KeyPair::generate();
531
532        let ring = vec![keypair1.public_key(), keypair2.public_key()];
533        let message = b"Test";
534
535        let result = sign_linkable(&outsider, &ring, message);
536        assert!(matches!(result, Err(LinkableRingError::SignerNotInRing)));
537    }
538}