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