minisketch_rs/
lib.rs

1#![deny(missing_debug_implementations)]
2#![deny(missing_docs)]
3#![deny(unused_results)]
4#![deny(dead_code)]
5#![doc(html_root_url = "https://docs.rs/minisketch_rs/0.1.9")]
6
7//! # minisketch-rs
8//!
9//! `minisketch-rs` is a wrapper around [minisketch],
10//! a C++ library by [Pieter Wuille] for efficient set reconciliation.
11//!
12//! Minisketch is proposed as part of an [Erlay] technique for bandwidth-efficient TX propagation in Bitcoin.
13//!
14//! This library exposes type-safe Rust bindings for all minisketch functions by providing [`Minisketch`] structure.
15//!
16//! # Examples
17//!
18//! See the [examples] module.
19//!
20//! [examples]: examples/index.html
21//! [minisketch]: https://github.com/sipa/minisketch
22//! [`Minisketch`]: struct.Minisketch.html
23//! [Pieter Wuille]: https://github.com/sipa
24//! [Erlay]: https://arxiv.org/abs/1905.10518
25
26pub mod examples;
27
28use std::error::Error;
29use std::fmt::{Debug, Display, Formatter};
30use std::ops::BitXorAssign;
31
32/// Error that originates from `libminisketch`, with a message.
33#[derive(Debug)]
34pub struct MinisketchError(String);
35
36impl MinisketchError {
37    /// Creates an error with a message.
38    pub fn new(msg: &str) -> Self {
39        MinisketchError(msg.to_owned())
40    }
41}
42
43impl Error for MinisketchError {}
44impl Display for MinisketchError {
45    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
46        write!(f, "MinisketchError({})", self.0)
47    }
48}
49
50#[doc(hidden)]
51mod ffi {
52    #![allow(non_upper_case_globals)]
53    #![allow(non_camel_case_types)]
54    #![allow(non_snake_case)]
55
56    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
57
58    unsafe impl Send for minisketch {}
59}
60
61/// Describes decoded sketches and holding underlying opaque type inside.
62pub struct Minisketch {
63    inner: *mut ffi::minisketch,
64    bits: u32,
65    implementation: u32,
66    capacity: usize,
67}
68
69impl Minisketch {
70    /// Tries to create a new empty sketch.
71    ///
72    /// # Errors
73    ///
74    /// If the combination of `bits` and `implementation` is unavailable, or if
75    /// `capacity` is 0, an `Err(MinisketchError)` is returned.
76    ///
77    /// # Examples
78    ///
79    /// ```rust
80    /// use minisketch_rs::Minisketch;
81    /// let sketch = Minisketch::try_new(12, 0, 4)?;
82    /// # Ok::<(), minisketch_rs::MinisketchError>(())
83    /// ```
84    pub fn try_new(
85        bits: u32,
86        implementation: u32,
87        capacity: usize,
88    ) -> Result<Self, MinisketchError> {
89        let inner = unsafe { ffi::minisketch_create(bits, implementation, capacity) };
90
91        if !inner.is_null() {
92            Ok(Minisketch {
93                inner,
94                bits,
95                implementation,
96                capacity,
97            })
98        } else {
99            Err(MinisketchError::new("Unsupported minisketch parameters"))
100        }
101    }
102
103    /// Determine whether support for elements of size of `bits` bits was compiled in.
104    pub fn bits_supported(bits: u32) -> bool {
105        let res = unsafe { ffi::minisketch_bits_supported(bits) };
106        res != 0
107    }
108
109    /// Determine the maximum number of implementations available.
110    ///
111    /// Multiple implementations may be available for a given element size, with
112    /// different performance characteristics on different hardware.
113    ///
114    /// Each implementation is identified by a number from 0 to the output of this
115    /// function call, inclusive. Note that not every combination of implementation
116    /// and element size may exist.
117    pub fn implementation_max() -> u32 {
118        unsafe { ffi::minisketch_implementation_max() }
119    }
120
121    /// Returns element size in a sketch in bits.
122    pub fn bits(&self) -> u32 {
123        unsafe { ffi::minisketch_bits(self.inner) }
124    }
125
126    /// Returns capacity of a sketch in number of elements.
127    pub fn capacity(&self) -> usize {
128        unsafe { ffi::minisketch_capacity(self.inner) }
129    }
130
131    /// Returns implementation version number.
132    pub fn implementation(&self) -> u32 {
133        unsafe { ffi::minisketch_implementation(self.inner) }
134    }
135
136    /// Returns the size in bytes for serializing a given sketch.
137    pub fn serialized_size(&self) -> usize {
138        unsafe { ffi::minisketch_serialized_size(self.inner) }
139    }
140
141    /// Adds a `u64` element to a sketch.
142    ///
143    /// If the element to be added is too large for the sketch, the most significant
144    /// bits of the element are dropped. More precisely, if the element size of
145    /// `sketch` is b bits, then this function adds the unsigned integer represented
146    /// by the b least significant bits of `element` to `sketch`.
147    ///
148    /// If the element to be added is 0 (after potentially dropping the most significant
149    /// bits), then this function is a no-op. Sketches cannot contain an element with
150    /// the value 0.
151    ///
152    /// Note that adding the same element a second time removes it again, as sketches have
153    /// set semantics, not multiset semantics.
154    ///
155    /// # Examples
156    ///
157    /// ```rust
158    /// use minisketch_rs::Minisketch;
159    /// let mut sketch = Minisketch::try_new(12, 0, 4)?;
160    /// sketch.add(42);
161    /// # Ok::<(), minisketch_rs::MinisketchError>(())
162    /// ```
163    pub fn add(&mut self, element: u64) {
164        unsafe { ffi::minisketch_add_uint64(self.inner, element) }
165    }
166
167    /// Set the seed for randomizing algorithm choices to a fixed value.
168    ///
169    /// By default, sketches are initialized with a random seed. This is important
170    /// to avoid scenarios where an attacker could force worst-case behavior.
171    ///
172    /// This function initializes the seed to a user-provided value (any 64-bit
173    /// integer is acceptable, regardless of field size).
174    ///
175    /// When seed is `std::u64::MAX`, a fixed internal value with predictable behavior is used.
176    /// It is only intended for testing.
177    ///
178    /// # Examples
179    ///
180    /// ```rust
181    /// use minisketch_rs::Minisketch;
182    /// let mut sketch = Minisketch::try_new(12, 0, 4)?;
183    /// sketch.set_seed(42);
184    /// # Ok::<(), minisketch_rs::MinisketchError>(())
185    /// ```
186    pub fn set_seed(&mut self, seed: u64) {
187        unsafe { ffi::minisketch_set_seed(self.inner, seed) }
188    }
189
190    /// Merge the elements of another sketch into this sketch.
191    ///
192    /// After merging, `sketch` will contain every element that existed in one but not
193    /// both of the input sketches. It can be seen as an exclusive or operation on
194    /// the set elements.  If the capacity of `other_sketch` is lower than `sketch`'s,
195    /// merging reduces the capacity of `sketch` to that of `other_sketch`.
196    ///
197    /// Returns the `Ok(capacity)` of `sketch` after merging has been performed (where this capacity
198    /// is at least 1)
199    ///
200    /// It is also possible to perform this operation directly on the serializations
201    /// of two sketches with the same element size and capacity by performing a bitwise XOR
202    /// of the serializations.
203    ///
204    /// You can also merge two sketches by doing xor-assignment (`^=`).
205    ///
206    /// # Errors
207    ///
208    /// Returns `Err(MinisketchError)` to indicate that merging has failed
209    /// because the two input sketches differ in their element size or implementation. If `Err` is
210    /// returned, `sketch` (and its capacity) have not been modified.
211    ///
212    /// # Examples
213    ///
214    /// ```rust
215    /// use minisketch_rs::Minisketch;
216    /// let mut sketch_a = Minisketch::try_new(12, 0, 4)?;
217    /// sketch_a.add(10);
218    /// sketch_a.add(43);
219    ///
220    /// let mut sketch_b = Minisketch::try_new(12, 0, 4)?;
221    /// sketch_b.add(42);
222    /// sketch_b.add(43);
223    ///
224    /// // Merge two sketches and extract a difference
225    /// let num_diffs = sketch_a.merge(&sketch_b)?;
226    ///
227    /// // Extract difference
228    /// let mut differences = vec![0u64; num_diffs];
229    /// sketch_a.decode(&mut differences)?;
230    ///
231    /// assert!((differences[0] == 42 || differences[0] == 10) && (differences[1] == 10 || differences[1] == 42));
232    ///
233    /// # Ok::<(), minisketch_rs::MinisketchError>(())
234    /// ```
235    pub fn merge(&mut self, other: &Self) -> Result<usize, MinisketchError> {
236        let capacity = unsafe { ffi::minisketch_merge(self.inner, other.inner) };
237
238        if capacity == 0 {
239            Err(MinisketchError::new("Merge is failed"))
240        } else {
241            Ok(capacity)
242        }
243    }
244
245    /// Decode a sketch.
246    ///
247    /// `elements` is a mutable reference to a buffer of `u64`, which will be filled with the
248    /// elements in this sketch.
249    ///
250    /// Returns `Ok(num. of decoded elements)`
251    ///
252    /// # Errors
253    ///
254    /// Returns `Err(MinisketchError)` if decoding failed for any reason.
255    ///
256    /// # Examples
257    ///
258    /// ```rust
259    /// use minisketch_rs::Minisketch;
260    /// let mut sketch = Minisketch::try_new(12, 0, 2)?;
261    /// sketch.add(42);
262    /// sketch.add(10);
263    /// let mut elements = [0u64; 2];
264    /// sketch.decode(&mut elements)?;
265    ///
266    /// // Elements may come in arbitrary order, so check all possible variants
267    /// assert!((elements[0] == 42 || elements[0] == 10) && (elements[1] == 10 || elements[1] == 42));
268    /// # Ok::<(), minisketch_rs::MinisketchError>(())
269    /// ```
270    pub fn decode(&self, elements: &mut [u64]) -> Result<usize, MinisketchError> {
271        let result =
272            unsafe { ffi::minisketch_decode(self.inner, elements.len(), elements.as_mut_ptr()) };
273
274        if result == -1 {
275            Err(MinisketchError::new("Sketch decoding failed"))
276        } else {
277            Ok(result as usize)
278        }
279    }
280
281    /// Deserialize a sketch from bytes.
282    ///
283    /// # Examples
284    ///
285    /// ```rust
286    /// use minisketch_rs::Minisketch;
287    ///
288    /// // Create Alice's sketch
289    /// let mut sketch_alice = Minisketch::try_new(12, 0, 2)?;
290    /// sketch_alice.add(42);
291    /// sketch_alice.add(10);
292    ///
293    /// // Serialize sketch on Alice's side
294    /// let mut message = vec![0u8; sketch_alice.serialized_size()];
295    /// sketch_alice.serialize(&mut message);
296    ///
297    /// // ... message is sent from Alice to Bob ...
298    ///
299    /// // Deserialize sketch from Alice on Bob's side
300    /// let mut sketch_bob = Minisketch::try_new(12, 0, 2)?;
301    /// sketch_bob.deserialize(&message);
302    ///
303    /// // Decode received sketch
304    /// let mut elements = [0u64; 2];
305    /// sketch_bob.decode(&mut elements)?;
306    /// // Elements may come in arbitrary order, so check all possible variants
307    /// assert!((elements[0] == 42 || elements[0] == 10) && (elements[1] == 10 || elements[1] == 42));
308    /// # Ok::<(), minisketch_rs::MinisketchError>(())
309    /// ```
310    pub fn deserialize(&mut self, buf: &[u8]) {
311        unsafe { ffi::minisketch_deserialize(self.inner, buf.as_ptr()) }
312    }
313
314    /// Serialize a sketch to bytes.
315    ///
316    /// # Errors
317    ///
318    /// Returns `Err(MinisketchError)` if `.len()` of the provided buffer `buf` is less than a size in bytes of
319    /// the serialized representation of the sketch.
320    ///
321    /// # Examples
322    ///
323    /// ```rust
324    /// use minisketch_rs::Minisketch;
325    /// let mut sketch = Minisketch::try_new(12, 0, 2)?;
326    /// sketch.add(42);
327    /// sketch.add(10);
328    ///
329    /// let mut buf = vec![0u8; sketch.serialized_size()];
330    /// sketch.serialize(&mut buf);
331    /// # Ok::<(), minisketch_rs::MinisketchError>(())
332    /// ```
333    pub fn serialize(&self, buf: &mut [u8]) -> Result<(), MinisketchError> {
334        let size = self.serialized_size();
335
336        if size < buf.len() {
337            return Err(MinisketchError::new("Invalid size of the output buffer"));
338        }
339
340        unsafe { ffi::minisketch_serialize(self.inner, buf.as_mut_ptr()) }
341        Ok(())
342    }
343}
344
345/// Custom `Debug` implementation that shows basic information about opaque `minisketch`.
346impl Debug for Minisketch {
347    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
348        write!(
349            f,
350            "Minisketch {{ bits = {}, implementation = {}, capacity = {} }}",
351            self.bits(),
352            self.implementation(),
353            self.capacity(),
354        )
355    }
356}
357
358/// Custom `Drop` implementation that frees an underlying opaque sketch.
359#[doc(hidden)]
360impl Drop for Minisketch {
361    fn drop(&mut self) {
362        unsafe {
363            ffi::minisketch_destroy(self.inner);
364        }
365    }
366}
367
368/// Custom `Clone` implementation that clones an underlying opaque sketch.
369#[doc(hidden)]
370impl Clone for Minisketch {
371    fn clone(&self) -> Self {
372        let inner = unsafe { ffi::minisketch_clone(self.inner) };
373
374        Minisketch {
375            inner,
376            bits: self.bits,
377            implementation: self.implementation,
378            capacity: self.capacity,
379        }
380    }
381}
382
383/// Custom `^=` operator implementation on two sketches that performs merging.
384///
385/// # Example
386///
387/// ```rust
388/// use minisketch_rs::Minisketch;
389/// let mut sketch_a = Minisketch::try_new(12, 0, 4)?;
390/// sketch_a.add(10);
391/// sketch_a.add(43);
392///
393/// let mut sketch_b = Minisketch::try_new(12, 0, 4)?;
394/// sketch_b.add(42);
395/// sketch_b.add(43);
396///
397/// // Merge two sketches with ^= operator
398/// sketch_a ^= sketch_b;
399///
400/// // Extract difference
401/// let mut differences = vec![0u64; 2];
402/// sketch_a.decode(&mut differences)?;
403///
404/// assert!((differences[0] == 42 || differences[0] == 10) && (differences[1] == 10 || differences[1] == 42));
405///
406/// # Ok::<(), minisketch_rs::MinisketchError>(())
407/// ```
408impl BitXorAssign for Minisketch {
409    fn bitxor_assign(&mut self, rhs: Minisketch) {
410        let _ = self.merge(&rhs);
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use crate::*;
417
418    fn validate_elements(elements: &[u64]) {
419        // Sort differences since they're come in arbitrary order from minisketch_decode()
420        let mut differences = Vec::from(elements);
421        differences.sort();
422
423        assert_eq!(differences[0], 3_000);
424        assert_eq!(differences[1], 3_001);
425        assert_eq!(differences[2], 3_010);
426        assert_eq!(differences[3], 3_011);
427    }
428
429    #[test]
430    // Implement an example from minisketch's README
431    pub fn test_sanity_check() {
432        use ffi::*;
433        unsafe {
434            // Alice's side
435            let sketch_a = minisketch_create(12, 0, 4);
436            for i in 3_000..3_010 {
437                minisketch_add_uint64(sketch_a, i as u64);
438            }
439
440            let sersize = minisketch_serialized_size(sketch_a);
441            assert_eq!(sersize, 12 * 4 / 8);
442
443            let mut buf_a = vec![0u8; sersize];
444            minisketch_serialize(sketch_a, buf_a.as_mut_slice().as_mut_ptr());
445            minisketch_destroy(sketch_a);
446
447            let sketch_b = minisketch_create(12, 0, 4);
448            for i in 3_002..3_012 {
449                minisketch_add_uint64(sketch_b, i as u64);
450            }
451
452            // Bob's side
453            {
454                let sketch_a = minisketch_create(12, 0, 4); // Alice's sketch
455                minisketch_deserialize(sketch_a, buf_a.as_ptr()); // Load Alice's sketch
456
457                // Merge the elements from sketch_a into sketch_b. The result is a sketch_b
458                // which contains all elements that occurred in Alice's or Bob's sets, but not
459                // in both.
460                let _ = minisketch_merge(sketch_b, sketch_a);
461
462                let mut differences = [0u64; 4];
463                let num_differences = minisketch_decode(sketch_b, 4, differences.as_mut_ptr());
464                minisketch_destroy(sketch_a);
465                minisketch_destroy(sketch_b);
466
467                assert!(num_differences > 0);
468                validate_elements(&differences[..]);
469            }
470        };
471    }
472
473    #[test]
474    // Implement a README's example with Rust API as a sanity check
475    pub fn sanity_check_rust_types() {
476        let mut sketch_a = Minisketch::try_new(12, 0, 4).unwrap();
477
478        assert_eq!(sketch_a.bits(), 12);
479        assert_eq!(sketch_a.implementation(), 0);
480        assert_eq!(sketch_a.capacity(), 4);
481
482        for i in 3_000..3_010 {
483            sketch_a.add(i);
484        }
485
486        let sersize = sketch_a.serialized_size();
487        assert_eq!(sersize, 12 * 4 / 8);
488
489        let mut buf_a = vec![0u8; sersize];
490        sketch_a.serialize(buf_a.as_mut_slice()).unwrap();
491
492        let mut sketch_b = Minisketch::try_new(12, 0, 4).unwrap();
493        for i in 3_002..3_012 {
494            sketch_b.add(i);
495        }
496
497        // Bob's side (with .merge() method)
498        {
499            let mut sketch_b = sketch_b.clone();
500            // Alice's sketch
501            let mut sketch_a = Minisketch::try_new(12, 0, 4).unwrap();
502            sketch_a.deserialize(&buf_a); // Load Alice's sketch
503
504            // Merge the elements from sketch_a into sketch_b. The result is a sketch_b
505            // which contains all elements that occurred in Alice's or Bob's sets, but not
506            // in both.
507            let _ = sketch_b.merge(&sketch_a).unwrap();
508
509            let mut differences = [0u64; 4];
510            let num_differences = sketch_b.decode(&mut differences[..]).unwrap();
511
512            assert!(num_differences > 0);
513            validate_elements(&differences[..]);
514        }
515
516        // Bob's side (with ^= instead of .merge())
517        {
518            let mut sketch_b = sketch_b.clone();
519
520            // Alice's sketch
521            let mut sketch_a = Minisketch::try_new(12, 0, 4).unwrap();
522            sketch_a.deserialize(&buf_a); // Load Alice's sketch
523
524            // Merge the elements from sketch_a into sketch_b. The result is a sketch_b
525            // which contains all elements that occurred in Alice's or Bob's sets, but not
526            // in both.
527            sketch_b ^= sketch_a;
528
529            let mut differences = [0u64; 4];
530            let num_differences = sketch_b.decode(&mut differences[..]).unwrap();
531
532            assert!(num_differences > 0);
533            validate_elements(&differences[..]);
534        }
535    }
536}