bitcoin_varint/lib.rs
1pub mod test;
2
3use std::io::Error;
4
5/// CompactSize Unsigned Integers
6///
7/// The raw transaction format and several peer-to-peer network messages use a type of variable-length integer to indicate the number of bytes in a following piece of data.
8///
9/// Bitcoin Core code and this document refers to these variable length integers as compactSize.
10/// Many other documents refer to them as var_int or varInt, but this risks conflation with other variable-length integer encodings—such as the CVarInt class used in Bitcoin Core for serializing data to disk.
11/// Because it’s used in the transaction format, the format of compactSize unsigned integers is part of the consensus rules.
12///
13/// https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
14///
15/// https://learnmeabitcoin.com/technical/varint
16pub struct VarInt;
17
18impl VarInt {
19 /// For numbers from 0 to 252, compactSize unsigned integers look like regular unsigned integers.
20 /// For other numbers up to 0xffffffffffffffff, a byte is prefixed to the number to indicate its length—but otherwise the numbers look like regular unsigned integers in little-endian order.
21 pub fn encode(size: u64) -> Result<Vec<u8>, Error> {
22 let size_bytes = size.to_le_bytes();
23 let result = match size {
24 x if x <= 252 => vec![size_bytes[0]],
25 x if (253..0xffff).contains(&x) => {
26 vec![0xfd, size_bytes[0], size_bytes[1]]
27 }
28 x if (0x10000..0xffffffff).contains(&x) => vec![
29 0xfe,
30 size_bytes[0],
31 size_bytes[1],
32 size_bytes[2],
33 size_bytes[3],
34 ],
35 x if (0x100000000..u64::MAX).contains(&x) => {
36 let mut x = size_bytes.to_vec();
37 x.insert(0, 0xff);
38 x
39 }
40 _ => panic!("VarInt: unexpected input"),
41 };
42 Ok(result)
43 }
44
45 /// For numbers from 0 to 252, compactSize unsigned integers look like regular unsigned integers.
46 /// For other numbers up to 0xffffffffffffffff, a byte is prefixed to the number to indicate its length—but otherwise the numbers look like regular unsigned integers in little-endian order.
47 pub fn decode(bytes: &[u8]) -> Result<u64, Error> {
48 let result = match bytes[0] {
49 x if x < 0xfd => u64::from_le_bytes([bytes[0], 0, 0, 0, 0, 0, 0, 0]),
50 x if x == 0xfd => u64::from_le_bytes([bytes[1], bytes[2], 0, 0, 0, 0, 0, 0]),
51 x if x == 0xfe => {
52 u64::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4], 0, 0, 0, 0])
53 }
54 x if x == 0xff => u64::from_le_bytes([
55 bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8],
56 ]),
57 _ => panic!("VarInt: unexpected input"),
58 };
59 Ok(result)
60 }
61
62 /// Returns the bytes needed to encode this varint
63 pub fn get_size(varint: u64) -> Result<u8, Error> {
64 match varint {
65 x if x <= 252 => Ok(1),
66 x if (253..0xffff).contains(&x) => Ok(3),
67 x if (0x10000..0xffffffff).contains(&x) => Ok(5),
68 x if (0x10000000..u64::MAX).contains(&x) => Ok(9),
69 _ => panic!("VarInt: unexpected input"),
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_varint_encode() {
80 assert_eq!(VarInt::encode(515).unwrap(), vec![0xfd, 3, 2]);
81 }
82
83 #[test]
84 fn test_varint_decode() {
85 assert_eq!(VarInt::decode(&vec![0xfd, 3, 2]).unwrap(), 515);
86 }
87
88 #[test]
89 fn test_varint_get_size() {
90 assert_eq!(VarInt::get_size(515).unwrap(), 3);
91 }
92}