Skip to main content

cbor_core/ext/
crypto_bigint.rs

1use crypto_bigint::{Choice, Encoding, Int, NonZero, Uint};
2
3use crate::{
4    Error, Result, Value, ValueKey,
5    integer::IntegerBytes,
6    tag,
7    util::{trim_leading_zeros, u64_from_slice},
8};
9
10/// Pad `src` with leading zeros to fill `dst`, placing bytes right-aligned.
11/// Returns `Err(Overflow)` if `src` is longer than `dst`.
12fn pad_be_bytes(src: &[u8], dst: &mut [u8]) -> Result<()> {
13    if src.len() > dst.len() {
14        return Err(Error::Overflow);
15    }
16    dst.fill(0);
17    let offset = dst.len() - src.len();
18    dst[offset..].copy_from_slice(src);
19    Ok(())
20}
21
22// ---------------------------------------------------------------------------
23// Uint<LIMBS> → Value
24// ---------------------------------------------------------------------------
25
26impl<'a, const LIMBS: usize> From<Uint<LIMBS>> for Value<'a>
27where
28    Uint<LIMBS>: Encoding,
29{
30    /// Encodes a `crypto_bigint::Uint` as a CBOR integer.
31    ///
32    /// Values that fit in a `u64` are encoded as a plain unsigned integer.
33    /// Larger values are encoded as a tag-2 big integer.
34    fn from(value: Uint<LIMBS>) -> Self {
35        from_crypto_uint(&value)
36    }
37}
38
39impl<'a, const LIMBS: usize> From<&Uint<LIMBS>> for Value<'a>
40where
41    Uint<LIMBS>: Encoding,
42{
43    /// Encodes a `crypto_bigint::Uint` as a CBOR integer.
44    ///
45    /// This conversion does not borrow the argument: a big integer is
46    /// always re-encoded into fresh CBOR bytes, so the resulting `Value`
47    /// owns its data and keeps no reference to the source. Taking `&`
48    /// rather than an owned value only saves the caller a clone.
49    fn from(value: &Uint<LIMBS>) -> Self {
50        from_crypto_uint(value)
51    }
52}
53
54fn from_crypto_uint<'a, const LIMBS: usize>(value: &Uint<LIMBS>) -> Value<'a>
55where
56    Uint<LIMBS>: Encoding,
57{
58    let be = value.to_be_bytes();
59    let trimmed = trim_leading_zeros(be.as_ref());
60
61    if let Ok(number) = u64_from_slice(trimmed) {
62        Value::Unsigned(number)
63    } else {
64        Value::tag(tag::POS_BIG_INT, trimmed.to_vec())
65    }
66}
67
68// ---------------------------------------------------------------------------
69// Int<LIMBS> → Value
70// ---------------------------------------------------------------------------
71
72impl<'a, const LIMBS: usize> From<Int<LIMBS>> for Value<'a>
73where
74    Uint<LIMBS>: Encoding,
75{
76    /// Encodes a `crypto_bigint::Int` as a CBOR integer.
77    fn from(value: Int<LIMBS>) -> Self {
78        from_crypto_int(&value)
79    }
80}
81
82impl<'a, const LIMBS: usize> From<&Int<LIMBS>> for Value<'a>
83where
84    Uint<LIMBS>: Encoding,
85{
86    /// Encodes a `crypto_bigint::Int` as a CBOR integer.
87    ///
88    /// This conversion does not borrow the argument: a big integer is
89    /// always re-encoded into fresh CBOR bytes, so the resulting `Value`
90    /// owns its data and keeps no reference to the source. Taking `&`
91    /// rather than an owned value only saves the caller a clone.
92    fn from(value: &Int<LIMBS>) -> Self {
93        from_crypto_int(value)
94    }
95}
96
97fn from_crypto_int<'a, const LIMBS: usize>(value: &Int<LIMBS>) -> Value<'a>
98where
99    Uint<LIMBS>: Encoding,
100{
101    let (magnitude, is_negative) = value.abs_sign();
102
103    if !bool::from(is_negative) {
104        from_crypto_uint(&magnitude)
105    } else {
106        // CBOR negative: -1 - payload, so payload = magnitude - 1
107        let payload = magnitude.wrapping_sub(&Uint::ONE);
108        let be = payload.to_be_bytes();
109        let trimmed = trim_leading_zeros(be.as_ref());
110
111        if let Ok(number) = u64_from_slice(trimmed) {
112            Value::Negative(number)
113        } else {
114            Value::tag(tag::NEG_BIG_INT, trimmed.to_vec())
115        }
116    }
117}
118
119// ---------------------------------------------------------------------------
120// Value → Uint<LIMBS>
121// ---------------------------------------------------------------------------
122
123impl<'a, const LIMBS: usize> TryFrom<Value<'a>> for Uint<LIMBS>
124where
125    Uint<LIMBS>: Encoding,
126{
127    type Error = Error;
128
129    /// Extracts a `crypto_bigint::Uint` from a CBOR integer value.
130    ///
131    /// Returns `Err(NegativeUnsigned)` for negative integers,
132    /// `Err(Overflow)` if the value does not fit,
133    /// `Err(IncompatibleType)` for non-integer values.
134    fn try_from(value: Value) -> Result<Self> {
135        to_crypto_uint(&value)
136    }
137}
138
139impl<'a, const LIMBS: usize> TryFrom<&Value<'a>> for Uint<LIMBS>
140where
141    Uint<LIMBS>: Encoding,
142{
143    type Error = Error;
144
145    fn try_from(value: &Value) -> Result<Self> {
146        to_crypto_uint(value)
147    }
148}
149
150fn to_crypto_uint<const LIMBS: usize>(value: &Value) -> Result<Uint<LIMBS>>
151where
152    Uint<LIMBS>: Encoding,
153{
154    match value.as_integer_bytes()? {
155        IntegerBytes::UnsignedOwned(bytes) => {
156            let mut buf = vec![0_u8; Uint::<LIMBS>::BYTES];
157            pad_be_bytes(&bytes, &mut buf)?;
158            Ok(Uint::from_be_slice(&buf))
159        }
160
161        IntegerBytes::NegativeOwned(_) => Err(Error::NegativeUnsigned),
162
163        IntegerBytes::UnsignedBorrowed(bytes) => {
164            let mut buf = vec![0_u8; Uint::<LIMBS>::BYTES];
165            pad_be_bytes(bytes, &mut buf)?;
166            Ok(Uint::from_be_slice(&buf))
167        }
168
169        IntegerBytes::NegativeBorrowed(_) => Err(Error::NegativeUnsigned),
170    }
171}
172
173// ---------------------------------------------------------------------------
174// Value → Int<LIMBS>
175// ---------------------------------------------------------------------------
176
177impl<'a, const LIMBS: usize> TryFrom<Value<'a>> for Int<LIMBS>
178where
179    Uint<LIMBS>: Encoding,
180{
181    type Error = Error;
182
183    /// Extracts a `crypto_bigint::Int` from a CBOR integer value.
184    ///
185    /// Returns `Err(Overflow)` if the value does not fit,
186    /// `Err(IncompatibleType)` for non-integer values.
187    fn try_from(value: Value) -> Result<Self> {
188        to_crypto_int(&value)
189    }
190}
191
192impl<'a, const LIMBS: usize> TryFrom<&Value<'a>> for Int<LIMBS>
193where
194    Uint<LIMBS>: Encoding,
195{
196    type Error = Error;
197
198    fn try_from(value: &Value) -> Result<Self> {
199        to_crypto_int(value)
200    }
201}
202
203fn to_crypto_int<const LIMBS: usize>(value: &Value) -> Result<Int<LIMBS>>
204where
205    Uint<LIMBS>: Encoding,
206{
207    match value.as_integer_bytes()? {
208        IntegerBytes::UnsignedOwned(bytes) => {
209            let mut buf = vec![0_u8; Uint::<LIMBS>::BYTES];
210            pad_be_bytes(&bytes, &mut buf)?;
211            let magnitude = Uint::from_be_slice(&buf);
212
213            Int::new_from_abs_sign(magnitude, Choice::from(0))
214                .into_option()
215                .ok_or(Error::Overflow)
216        }
217
218        IntegerBytes::NegativeOwned(bytes) => {
219            // CBOR negative: payload = magnitude - 1, so magnitude = payload + 1
220            let mut buf = vec![0_u8; Uint::<LIMBS>::BYTES];
221            pad_be_bytes(&bytes, &mut buf)?;
222            let payload = Uint::from_be_slice(&buf);
223            let magnitude = payload.wrapping_add(&Uint::ONE);
224
225            // If magnitude wrapped to zero, actual magnitude is 2^(LIMBS*64)
226            // which cannot fit in this Uint, so overflow.
227            if magnitude.is_zero().into() {
228                return Err(Error::Overflow);
229            }
230
231            Int::new_from_abs_sign(magnitude, Choice::from(1))
232                .into_option()
233                .ok_or(Error::Overflow)
234        }
235
236        IntegerBytes::UnsignedBorrowed(bytes) => {
237            let mut buf = vec![0_u8; Uint::<LIMBS>::BYTES];
238            pad_be_bytes(bytes, &mut buf)?;
239            let magnitude = Uint::from_be_slice(&buf);
240
241            Int::new_from_abs_sign(magnitude, Choice::from(0))
242                .into_option()
243                .ok_or(Error::Overflow)
244        }
245
246        IntegerBytes::NegativeBorrowed(bytes) => {
247            // payload = magnitude - 1, big-endian bytes
248            let mut buf = vec![0_u8; Uint::<LIMBS>::BYTES];
249            pad_be_bytes(bytes, &mut buf)?;
250            let payload = Uint::from_be_slice(&buf);
251            let magnitude = payload.wrapping_add(&Uint::ONE);
252
253            // If magnitude wrapped to zero, the actual magnitude is 2^(LIMBS*64)
254            // which cannot fit, so overflow.
255            if magnitude.is_zero().into() {
256                return Err(Error::Overflow);
257            }
258
259            Int::new_from_abs_sign(magnitude, Choice::from(1))
260                .into_option()
261                .ok_or(Error::Overflow)
262        }
263    }
264}
265
266// ---------------------------------------------------------------------------
267// NonZero wrappers
268// ---------------------------------------------------------------------------
269
270impl<'a, const LIMBS: usize> From<NonZero<Uint<LIMBS>>> for Value<'a>
271where
272    Uint<LIMBS>: Encoding,
273{
274    fn from(value: NonZero<Uint<LIMBS>>) -> Self {
275        from_crypto_uint(&value)
276    }
277}
278
279impl<'a, const LIMBS: usize> From<&NonZero<Uint<LIMBS>>> for Value<'a>
280where
281    Uint<LIMBS>: Encoding,
282{
283    /// Encodes a non-zero `crypto_bigint::Uint` as a CBOR integer.
284    ///
285    /// This conversion does not borrow the argument: a big integer is
286    /// always re-encoded into fresh CBOR bytes, so the resulting `Value`
287    /// owns its data and keeps no reference to the source. Taking `&`
288    /// rather than an owned value only saves the caller a clone.
289    fn from(value: &NonZero<Uint<LIMBS>>) -> Self {
290        from_crypto_uint(value)
291    }
292}
293
294impl<'a, const LIMBS: usize> From<NonZero<Int<LIMBS>>> for Value<'a>
295where
296    Uint<LIMBS>: Encoding,
297{
298    fn from(value: NonZero<Int<LIMBS>>) -> Self {
299        from_crypto_int(&value)
300    }
301}
302
303impl<'a, const LIMBS: usize> From<&NonZero<Int<LIMBS>>> for Value<'a>
304where
305    Uint<LIMBS>: Encoding,
306{
307    /// Encodes a non-zero `crypto_bigint::Int` as a CBOR integer.
308    ///
309    /// This conversion does not borrow the argument: a big integer is
310    /// always re-encoded into fresh CBOR bytes, so the resulting `Value`
311    /// owns its data and keeps no reference to the source. Taking `&`
312    /// rather than an owned value only saves the caller a clone.
313    fn from(value: &NonZero<Int<LIMBS>>) -> Self {
314        from_crypto_int(value)
315    }
316}
317
318// ---------------------------------------------------------------------------
319// crypto_bigint → ValueKey
320// ---------------------------------------------------------------------------
321
322macro_rules! impl_value_key {
323    ($($type:ty),* $(,)?) => { $(
324        impl<'a, const LIMBS: usize> From<$type> for ValueKey<'a>
325        where
326            Uint<LIMBS>: Encoding,
327        {
328            fn from(value: $type) -> Self {
329                Value::from(value).into()
330            }
331        }
332    )* }
333}
334
335impl_value_key!(Uint<LIMBS>, Int<LIMBS>, NonZero<Uint<LIMBS>>, NonZero<Int<LIMBS>>);
336
337// ---------------------------------------------------------------------------
338// Tests
339// ---------------------------------------------------------------------------
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344    use crate::DataType;
345    use crypto_bigint::{U64, U128, U256};
346
347    type I128 = Int<{ U128::LIMBS }>;
348    type I256 = Int<{ U256::LIMBS }>;
349
350    fn roundtrip_uint<const L: usize>(n: Uint<L>) -> Uint<L>
351    where
352        Uint<L>: Encoding,
353    {
354        let encoded = Value::from(n).encode();
355        let decoded = Value::decode(&encoded).unwrap();
356        Uint::try_from(decoded).unwrap()
357    }
358
359    fn roundtrip_int<const L: usize>(n: Int<L>) -> Int<L>
360    where
361        Uint<L>: Encoding,
362    {
363        let encoded = Value::from(n).encode();
364        let decoded = Value::decode(&encoded).unwrap();
365        Int::try_from(decoded).unwrap()
366    }
367
368    // ---- Uint tests ----
369
370    #[test]
371    fn uint_zero() {
372        assert_eq!(roundtrip_uint(U256::ZERO), U256::ZERO);
373    }
374
375    #[test]
376    fn uint_small() {
377        let n = U256::from(42_u64);
378        assert_eq!(roundtrip_uint(n), n);
379    }
380
381    #[test]
382    fn uint_u64_max() {
383        let n = U256::from(u64::MAX);
384        let v = Value::from(n);
385        assert!(
386            matches!(v, Value::Unsigned(_)),
387            "u64::MAX should encode as plain Unsigned"
388        );
389        assert_eq!(Uint::<{ U256::LIMBS }>::try_from(v).unwrap(), n);
390    }
391
392    #[test]
393    fn uint_large() {
394        // 2^64 + 1 — must use tag-2
395        let n = U256::from(u64::MAX).wrapping_add(&U256::from(2_u64));
396        let v = Value::from(n);
397        assert!(
398            matches!(&v, Value::Tag(2, _)),
399            "value > u64::MAX should encode as tag-2"
400        );
401        assert_eq!(Uint::<{ U256::LIMBS }>::try_from(v).unwrap(), n);
402    }
403
404    #[test]
405    fn uint_max_u128() {
406        let n = U128::MAX;
407        let v = Value::from(n);
408        assert_eq!(U128::try_from(v).unwrap(), n);
409    }
410
411    #[test]
412    fn uint_max_u256() {
413        let n = U256::MAX;
414        let v = Value::from(n);
415        assert_eq!(Uint::<{ U256::LIMBS }>::try_from(v).unwrap(), n);
416    }
417
418    #[test]
419    fn uint_overflow() {
420        // Encode a U256 that doesn't fit in U64
421        let n = U256::from(u64::MAX).wrapping_add(&U256::ONE);
422        let v = Value::from(n);
423        assert_eq!(U64::try_from(v), Err(Error::Overflow));
424    }
425
426    #[test]
427    fn uint_negative_errors() {
428        let v = Value::from(-1);
429        assert_eq!(U256::try_from(v), Err(Error::NegativeUnsigned));
430    }
431
432    #[test]
433    fn uint_non_integer_errors() {
434        assert_eq!(
435            U256::try_from(Value::from("hello")),
436            Err(Error::IncompatibleType(DataType::Text))
437        );
438        assert_eq!(
439            U256::try_from(Value::null()),
440            Err(Error::IncompatibleType(DataType::Null))
441        );
442    }
443
444    #[test]
445    fn uint_from_u128_roundtrip() {
446        for x in [0_u128, 1, 42, u64::MAX.into(), u64::MAX as u128 + 1, u128::MAX] {
447            let expected = U256::from_u128(x);
448            let via_value = Value::from(x);
449            assert_eq!(
450                Uint::<{ U256::LIMBS }>::try_from(via_value).unwrap(),
451                expected,
452                "u128={x}"
453            );
454        }
455    }
456
457    // ---- Int tests ----
458
459    #[test]
460    fn int_zero() {
461        assert_eq!(roundtrip_int(I256::ZERO), I256::ZERO);
462    }
463
464    #[test]
465    fn int_positive_small() {
466        let n = I256::from_i64(42);
467        assert_eq!(roundtrip_int(n), n);
468    }
469
470    #[test]
471    fn int_negative_one() {
472        let n = I256::MINUS_ONE;
473        assert_eq!(roundtrip_int(n), n);
474    }
475
476    #[test]
477    fn int_i64_min() {
478        let n = I256::from_i64(i64::MIN);
479        assert_eq!(roundtrip_int(n), n);
480    }
481
482    #[test]
483    fn int_i128_min() {
484        let n = I128::from_i128(i128::MIN);
485        let v = Value::from(n);
486        assert_eq!(I128::try_from(v).unwrap(), n);
487    }
488
489    #[test]
490    fn int_i128_max() {
491        let n = I128::from_i128(i128::MAX);
492        let v = Value::from(n);
493        assert_eq!(I128::try_from(v).unwrap(), n);
494    }
495
496    #[test]
497    fn int_large_positive() {
498        // Positive value > u64::MAX
499        let big_uint = U256::from(u64::MAX).wrapping_add(&U256::from(2_u64));
500        let v = Value::from(big_uint);
501        let result = Int::<{ U256::LIMBS }>::try_from(v).unwrap();
502        let (mag, sign) = result.abs_sign();
503        assert!(!bool::from(sign));
504        assert_eq!(mag, big_uint);
505        assert_eq!(roundtrip_int(result), result);
506    }
507
508    #[test]
509    fn int_large_negative() {
510        // -(u64::MAX + 2) — requires tag-3 big integer
511        let magnitude = U256::from(u64::MAX).wrapping_add(&U256::from(2_u64));
512        let n = Int::new_from_abs_sign(magnitude, Choice::from(1)).unwrap();
513        let v = Value::from(n);
514        assert!(matches!(&v, Value::Tag(3, _)));
515        assert_eq!(Int::<{ U256::LIMBS }>::try_from(v).unwrap(), n);
516    }
517
518    #[test]
519    fn int_non_integer_errors() {
520        assert_eq!(
521            I256::try_from(Value::from(0.5)),
522            Err(Error::IncompatibleType(DataType::Float16))
523        );
524        assert_eq!(
525            I256::try_from(Value::null()),
526            Err(Error::IncompatibleType(DataType::Null))
527        );
528    }
529
530    // ---- NonZero tests ----
531
532    #[test]
533    fn nonzero_uint_roundtrip() {
534        let nz = NonZero::new(U256::from(42_u64)).unwrap();
535        let v = Value::from(nz);
536        assert_eq!(U256::try_from(v).unwrap(), U256::from(42_u64));
537    }
538
539    #[test]
540    fn nonzero_int_roundtrip() {
541        let nz = NonZero::new(I256::MINUS_ONE).unwrap();
542        let v = Value::from(nz);
543        assert_eq!(I256::try_from(v).unwrap(), I256::MINUS_ONE);
544    }
545
546    // ---- Cross-type consistency ----
547
548    #[test]
549    fn int_and_uint_agree_on_positives() {
550        for x in [0_u64, 1, 42, u64::MAX] {
551            let vu = Value::from(U256::from(x));
552            let vi = Value::from(I256::from_i64(x as i64));
553            if x <= i64::MAX as u64 {
554                assert_eq!(vu, vi, "Uint/Int encoding differs for {x}");
555            }
556        }
557    }
558}