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