Skip to main content

vest_lib/primitives/
base256.rs

1//! Minimal big-endian base-256 integer conversion and formats.
2#[cfg(feature = "alloc")]
3use alloc::{vec, vec::Vec};
4use vstd::arithmetic::{div_mod::*, power::*, power2::*};
5use vstd::bits::*;
6use vstd::{calc, prelude::*};
7
8verus! {
9
10const USIZE_MODULUS_32: u64 = 0x100000000;
11
12const USIZE_MODULUS_64: u128 = 0x10000000000000000u128;
13
14/// Unsigned big-endian base-256 decoding.
15pub open spec fn nat_from_be_bytes(bytes: Seq<u8>) -> nat
16    decreases bytes.len(),
17{
18    if bytes.len() == 0 {
19        0
20    } else {
21        nat_from_be_bytes(bytes.drop_last()) * 256 + bytes.last() as nat
22    }
23}
24
25/// Unsigned big-endian base-256 encoding.
26pub open spec fn nat_to_be_bytes(n: nat) -> Seq<u8>
27    decreases n,
28{
29    if n < 256 {
30        seq![n as u8]
31    } else {
32        nat_to_be_bytes((n / 256) as nat).push((n % 256) as u8)
33    }
34}
35
36/// Number of bytes in `usize`.
37pub open spec fn size_of_usize() -> nat {
38    if usize::BITS == 32 {
39        4
40    } else {
41        8
42    }
43}
44
45proof fn lemma_usize_shr8_is_div256(v: usize)
46    ensures
47        (v >> 8usize) as nat == v as nat / 256,
48{
49    lemma_usize_shr_is_div(v, 8usize);
50    assert(pow2(8) == 256) by (compute_only);
51}
52
53proof fn lemma_usize_low8_is_mod256(v: usize)
54    ensures
55        (v & 0xffusize) as nat == v as nat % 256,
56{
57    lemma_usize_low_bits_mask_is_mod(v, 8);
58    assert(pow2(8) == 256) by (compute_only);
59}
60
61proof fn lemma_nat_from_be_bytes_fits_shr8(bytes: Seq<u8>)
62    requires
63        bytes.len() <= size_of_usize(),
64    ensures
65        usize::BITS == 32 ==> nat_from_be_bytes(bytes) < USIZE_MODULUS_32 as nat,
66        usize::BITS == 64 ==> nat_from_be_bytes(bytes) < USIZE_MODULUS_64 as nat,
67{
68    lemma_from_be_bytes_upper_bound(bytes);
69    assert(usize::BITS == 32 || usize::BITS == 64);
70    if usize::BITS == 32 {
71        assert(size_of_usize() == 4);
72        reveal_with_fuel(pow, 5);
73    } else {
74        assert(usize::BITS == 64);
75        assert(size_of_usize() == 8);
76        reveal_with_fuel(pow, 9);
77    }
78}
79
80proof fn lemma_usize32_shl8_or_is_base256(v: usize, b: u8)
81    by (bit_vector)
82    requires
83        usize::BITS == 32,
84    ensures
85        (((v << 8usize) | b as usize) as nat) == (v as nat * 256 + b as nat) % (
86        USIZE_MODULUS_32 as nat),
87{
88}
89
90proof fn lemma_usize64_shl8_or_is_base256(v: usize, b: u8)
91    by (bit_vector)
92    requires
93        usize::BITS == 64,
94    ensures
95        (((v << 8usize) | b as usize) as nat) == (v as nat * 256 + b as nat) % (
96        USIZE_MODULUS_64 as nat),
97{
98}
99
100pub proof fn lemma_nat_from_be_bytes_fits_usize(bytes: Seq<u8>)
101    requires
102        bytes.len() <= size_of_usize(),
103    ensures
104        nat_from_be_bytes(bytes) <= usize::MAX,
105{
106    // nat_from_be_bytes(bytes) < pow(256, bytes.len()) <= pow(256, size_of_usize())
107    // For 32-bit: pow(256, 4) = 2^32 = usize::MAX + 1, so < pow(256,4) means <= usize::MAX.
108    // For 64-bit: pow(256, 8) = 2^64 = usize::MAX + 1, same argument.
109    lemma_from_be_bytes_upper_bound(bytes);
110    if usize::BITS == 32 {
111        reveal_with_fuel(pow, 5);  // unfolds pow(256, 0..4)
112    } else {
113        reveal_with_fuel(pow, 9);  // unfolds pow(256, 0..8)
114    }
115}
116
117pub proof fn lemma_from_be_bytes_push(bytes: Seq<u8>, b: u8)
118    ensures
119        nat_from_be_bytes(bytes.push(b)) == nat_from_be_bytes(bytes) * 256 + b as nat,
120{
121    assert(bytes.push(b).drop_last() == bytes);
122}
123
124pub proof fn lemma_from_be_bytes_singleton(b: u8)
125    ensures
126        nat_from_be_bytes(seq![b]) == b as nat,
127{
128    lemma_from_be_bytes_push(seq![], b);
129}
130
131pub proof fn lemma_pow256_succ(exp: nat)
132    ensures
133        pow(256, exp + 1) == pow(256, exp) * 256,
134{
135    lemma_pow_adds(256, exp, 1);
136    lemma_pow1(256);
137}
138
139pub broadcast proof fn lemma_from_be_bytes_upper_bound(bytes: Seq<u8>)
140    ensures
141        #[trigger] nat_from_be_bytes(bytes) < pow(256, bytes.len()),
142    decreases bytes.len(),
143{
144    if bytes.len() == 0 {
145        lemma_pow0(256);
146    } else {
147        let prefix = bytes.drop_last();
148        lemma_from_be_bytes_upper_bound(prefix);
149        lemma_pow256_succ(prefix.len());
150    }
151}
152
153pub proof fn lemma_from_be_bytes_lower_bound(bytes: Seq<u8>)
154    requires
155        bytes.len() > 0,
156        bytes[0] != 0,
157    ensures
158        pow(256, (bytes.len() - 1) as nat) <= nat_from_be_bytes(bytes),
159    decreases bytes.len(),
160{
161    if bytes.len() == 1 {
162        lemma_pow0(256);
163    } else {
164        let prefix = bytes.drop_last();
165        lemma_from_be_bytes_lower_bound(prefix);
166        lemma_pow256_succ((prefix.len() - 1) as nat);
167    }
168}
169
170pub proof fn lemma_to_be_bytes_props(n: nat)
171    ensures
172        nat_to_be_bytes(n).len() > 0,
173        n > 0 ==> nat_to_be_bytes(n)[0] != 0,
174        n > 0 ==> pow(256, (nat_to_be_bytes(n).len() - 1) as nat) <= n,
175    decreases n,
176{
177    if n < 256 {
178        lemma_pow0(256);
179    } else {
180        let q = (n / 256) as nat;
181        lemma_to_be_bytes_props(q);
182        lemma_pow256_succ((nat_to_be_bytes(q).len() - 1) as nat);
183    }
184}
185
186pub proof fn lemma_to_be_bytes_len_bound(n: nat, max_len: nat)
187    requires
188        0 < max_len,
189        n < pow(256, max_len),
190    ensures
191        nat_to_be_bytes(n).len() <= max_len,
192{
193    if n == 0 {
194    } else {
195        lemma_to_be_bytes_props(n);
196        lemma_pow_strictly_increases_converse(256, (nat_to_be_bytes(n).len() - 1) as nat, max_len);
197    }
198}
199
200pub proof fn lemma_usize_to_be_bytes_len_bound(n: usize)
201    ensures
202        usize::BITS == 32 ==> nat_to_be_bytes(n as nat).len() <= 4,
203        usize::BITS == 64 ==> nat_to_be_bytes(n as nat).len() <= 8,
204{
205    if usize::BITS == 32 {
206        reveal_with_fuel(pow, 5);
207        lemma_to_be_bytes_len_bound(n as nat, 4);
208    } else {
209        reveal_with_fuel(pow, 9);
210        lemma_to_be_bytes_len_bound(n as nat, 8);
211    }
212}
213
214pub proof fn lemma_to_from_be_bytes_roundtrip(n: nat)
215    ensures
216        nat_from_be_bytes(nat_to_be_bytes(n)) == n,
217    decreases n,
218{
219    if n < 256 {
220        lemma_from_be_bytes_singleton(n as u8);
221    } else {
222        let q = (n / 256) as nat;
223        let r = (n % 256) as nat;
224        lemma_to_from_be_bytes_roundtrip(q);
225        lemma_from_be_bytes_push(nat_to_be_bytes(q), r as u8);
226    }
227}
228
229pub proof fn lemma_from_to_be_bytes_roundtrip(bytes: Seq<u8>)
230    requires
231        bytes.len() > 0,
232        bytes.len() > 1 ==> bytes[0] != 0,
233    ensures
234        nat_to_be_bytes(nat_from_be_bytes(bytes)) == bytes,
235    decreases bytes.len(),
236{
237    if bytes.len() == 1 {
238        lemma_from_be_bytes_singleton(bytes[0]);
239        assert(bytes == seq![bytes[0]]);
240    } else {
241        let prefix = bytes.drop_last();
242        lemma_from_to_be_bytes_roundtrip(prefix);
243    }
244}
245
246pub proof fn lemma_from_be_bytes_prepend(bytes: Seq<u8>, b: u8)
247    ensures
248        nat_from_be_bytes(seq![b] + bytes) == b as nat * pow(256, bytes.len()) + nat_from_be_bytes(
249            bytes,
250        ),
251    decreases bytes.len(),
252{
253    if bytes.len() == 0 {
254        lemma_from_be_bytes_singleton(b);
255        lemma_pow0(256);
256    } else {
257        let prefix = bytes.drop_last();
258        let last = bytes.last();
259        lemma_from_be_bytes_prepend(prefix, b);
260        lemma_from_be_bytes_push(prefix, last);
261        lemma_from_be_bytes_push(seq![b] + prefix, last);
262        lemma_pow256_succ(prefix.len());
263        assert(seq![b] + bytes == (seq![b] + prefix).push(last));
264        assert((b as nat * pow(256, prefix.len()) + nat_from_be_bytes(prefix)) * 256 + last as nat
265            == b as nat * (pow(256, prefix.len()) * 256) + (nat_from_be_bytes(prefix) * 256
266            + last as nat)) by (nonlinear_arith);
267    }
268}
269
270/// Executable loop-based big-endian base-256 decoding into `usize`.
271/// Verified against the `nat_from_be_bytes` specification.
272pub fn usize_from_be_bytes_exec(bytes: &[u8]) -> (result: usize)
273    requires
274        bytes.len() <= size_of_usize(),
275    ensures
276        result as nat == nat_from_be_bytes(bytes.deep_view()),
277{
278    let n = bytes.len();
279    let mut acc: usize = 0;
280    for i in 0..n
281        invariant
282            n == bytes.len(),
283            n <= size_of_usize(),
284            acc == nat_from_be_bytes(bytes@.take(i as int)),
285    {
286        let b = bytes[i];
287        proof {
288            let prefix = bytes@.take(i as int);
289            let current = prefix.push(b);
290            assert(bytes@.take(i as int + 1) == current);
291            assert(current.drop_last() == prefix);
292            lemma_nat_from_be_bytes_fits_shr8(current);
293            if usize::BITS == 32 {
294                lemma_usize32_shl8_or_is_base256(acc, b);
295            } else {
296                lemma_usize64_shl8_or_is_base256(acc, b);
297            }
298        }
299        acc = (acc << 8usize) | (b as usize);
300    }
301    assert(bytes@.take(n as int) == bytes.deep_view());
302    acc
303}
304
305pub fn u64_from_be_bytes(bytes: &[u8]) -> (r: u64)
306    requires
307        usize::BITS == 64,
308        bytes.len() <= 8,
309    ensures
310        r as nat == nat_from_be_bytes(bytes.deep_view()),
311{
312    usize_from_be_bytes_exec(bytes) as u64
313}
314
315/// Executable big-endian base-256 encoding from `usize`.
316/// Verified against the `nat_to_be_bytes` specification.
317///
318/// This allocation-backed compatibility helper builds the result recursively.
319/// Serialization hot paths should prefer [`usize_to_be_bytes_in_place`].
320#[cfg(feature = "alloc")]
321pub fn usize_to_be_bytes_exec(v: usize) -> (buf: Vec<u8>)
322    ensures
323        buf@ == nat_to_be_bytes(v as nat),
324    decreases v,
325{
326    if v < 256 {
327        vec![v as u8]
328    } else {
329        proof {
330            lemma_usize_shr8_is_div256(v);
331            lemma_usize_low8_is_mod256(v);
332        }
333        let mut buf = usize_to_be_bytes_exec(v >> 8);
334        buf.push((v & 0xff) as u8);
335        buf
336    }
337}
338
339/// Writes the minimal big-endian base-256 encoding of `v` into an exactly-sized slice.
340#[verifier::loop_isolation(false)]
341pub fn usize_to_be_bytes_in_place(v: usize, obuf: &mut [u8])
342    requires
343        old(obuf)@.len() == nat_to_be_bytes(v as nat).len(),
344    ensures
345        final(obuf)@ == nat_to_be_bytes(v as nat),
346{
347    let len = obuf.len();
348
349    let ghost target = nat_to_be_bytes(v as nat);
350    proof {
351        lemma_to_from_be_bytes_roundtrip(v as nat);  // nat_from_be_bytes(nat_to_be_bytes(v)) == v
352        assert(target.take(len as int) == target);
353    }
354    let mut pos = len;
355    let mut current = v;
356
357    // We write the bytes in Big-Endian order, starting from the last byte and moving backwards.
358    // The loop invariant maintains that `current` is the remaining value to encode (the higher bytes),
359    // and the suffix `obuf[pos..]` is the part of the output buffer that has already been filled with the correct bytes.
360    while pos > 0
361        invariant
362            len == obuf.len(),
363            pos <= len,
364            current as nat == nat_from_be_bytes(target.take(pos as int)),
365            obuf@.skip(pos as int) == target.skip(pos as int),
366        decreases pos,
367    {
368        let ghost old_buf = obuf@;
369        let ghost old_current = current;
370
371        pos -= 1;
372        let byte = (current & 0xff) as u8;
373        obuf[pos] = byte;
374        current = current >> 8;
375        proof {
376            lemma_usize_shr8_is_div256(old_current);
377            lemma_usize_low8_is_mod256(old_current);
378            assert(target.take(pos as int + 1).drop_last() == target.take(pos as int));
379            assert(obuf@.skip(pos as int) == seq![byte] + old_buf.skip(pos as int + 1));
380        }
381    }
382}
383
384#[cfg(feature = "alloc")]
385pub fn u64_to_be_bytes(v: u64) -> (buf: Vec<u8>)
386    requires
387        usize::BITS == 64,
388    ensures
389        buf@ == nat_to_be_bytes(v as nat),
390{
391    usize_to_be_bytes_exec(v as usize)
392}
393
394/// Executable loop-based byte-length computation.
395/// verified against the `nat_to_be_bytes` specification.
396pub fn usize_to_be_bytes_len(v: usize) -> (len: usize)
397    ensures
398        len == nat_to_be_bytes(v as nat).len(),
399{
400    let mut cur = v;
401    let mut len: usize = 1;
402    while cur >= 256
403        invariant
404            len + nat_to_be_bytes(cur as nat).len() == nat_to_be_bytes(v as nat).len() + 1,
405        decreases cur,
406    {
407        proof {
408            lemma_usize_shr8_is_div256(cur);
409            lemma_usize_to_be_bytes_len_bound(v);
410        }
411        cur >>= 8;
412        len += 1;
413    }
414    len
415}
416
417pub fn u64_to_be_bytes_len(v: u64) -> (len: usize)
418    requires
419        usize::BITS == 64,
420    ensures
421        len == nat_to_be_bytes(v as nat).len(),
422{
423    usize_to_be_bytes_len(v as usize)
424}
425
426pub fn u64_to_be_bytes_first(v: u64) -> (first: u8)
427    requires
428        usize::BITS == 64,
429    ensures
430        first == nat_to_be_bytes(v as nat)[0],
431    decreases v,
432{
433    if v < 256 {
434        v as u8
435    } else {
436        proof {
437            lemma_usize_shr8_is_div256(v as usize);
438        }
439        u64_to_be_bytes_first(v >> 8)
440    }
441}
442
443#[verifier::external_body]
444fn bytes_needed(n: usize) -> (need: usize)
445    ensures
446        need == nat_to_be_bytes(n as nat).len(),
447{
448    let active_bits = match usize::BITS {
449        total @ 32 => total - (n as u32).leading_zeros(),
450        total @ 64 => total - (n as u64).leading_zeros(),
451        _ => 0,  // unreachable
452    };
453
454    if active_bits == 0 {
455        1
456    } else {
457        ((active_bits + 7) / 8) as usize
458    }
459}
460
461// Executable loop-based big-endian base-256 encoding from `usize`.
462// Verified against [`usize_to_be_bytes`].
463// pub fn usize_to_be_bytes_exec(mut v: usize, obuf: &mut Vec<u8>)
464//     ensures
465//         final(obuf)@ == old(obuf)@ + usize_to_be_bytes(v),
466//  {
467// }
468} // verus!
469#[cfg(all(test, feature = "alloc"))]
470mod tests {
471    use super::{usize_to_be_bytes_exec, usize_to_be_bytes_in_place, usize_to_be_bytes_len};
472
473    #[test]
474    fn usize_to_be_bytes_in_place_matches_vec_encoding() {
475        for value in [0, 1, 0xff, 0x100, 0xffff, 0x10000, usize::MAX] {
476            let expected = usize_to_be_bytes_exec(value);
477            let len = usize_to_be_bytes_len(value);
478            let mut actual = [0u8; size_of::<usize>()];
479
480            usize_to_be_bytes_in_place(value, &mut actual[..len]);
481
482            assert_eq!(&actual[..len], expected.as_slice());
483        }
484    }
485}