Skip to main content

cbor_core/ext/
crypto_bigint.rs

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