Skip to content

Commit 786e6e4

Browse files
committed
move base32 to codebase
1 parent 7cac991 commit 786e6e4

File tree

3 files changed

+280
-2
lines changed

3 files changed

+280
-2
lines changed

lightning/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ regex = { version = "1.5.6", optional = true }
4747
backtrace = { version = "0.3", optional = true }
4848

4949
core2 = { version = "0.3.0", optional = true, default-features = false }
50-
base32 = "0.4.0"
5150

5251
[dev-dependencies]
5352
hex = "0.4"
5453
regex = "1.5.6"
54+
quickcheck = "1.0.3"
5555

5656
[dev-dependencies.bitcoin]
5757
version = "0.29.0"

lightning/src/util/base32.rs

+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/// Copied from https://crates.io/crates/base32 v0.4.0
2+
/// License: MIT or Apache-2.0
3+
use std::cmp::min;
4+
5+
#[derive(Copy, Clone)]
6+
pub enum Alphabet {
7+
RFC4648 { padding: bool },
8+
Crockford,
9+
}
10+
11+
const RFC4648_ALPHABET: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
12+
const CROCKFORD_ALPHABET: &'static [u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
13+
14+
pub fn encode(alphabet: Alphabet, data: &[u8]) -> String {
15+
let (alphabet, padding) = match alphabet {
16+
Alphabet::RFC4648 { padding } => (RFC4648_ALPHABET, padding),
17+
Alphabet::Crockford => (CROCKFORD_ALPHABET, false),
18+
};
19+
let mut ret = Vec::with_capacity((data.len() + 3) / 4 * 5);
20+
21+
for chunk in data.chunks(5) {
22+
let buf = {
23+
let mut buf = [0u8; 5];
24+
for (i, &b) in chunk.iter().enumerate() {
25+
buf[i] = b;
26+
}
27+
buf
28+
};
29+
ret.push(alphabet[((buf[0] & 0xF8) >> 3) as usize]);
30+
ret.push(alphabet[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize]);
31+
ret.push(alphabet[((buf[1] & 0x3E) >> 1) as usize]);
32+
ret.push(alphabet[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize]);
33+
ret.push(alphabet[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize]);
34+
ret.push(alphabet[((buf[3] & 0x7C) >> 2) as usize]);
35+
ret.push(alphabet[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize]);
36+
ret.push(alphabet[(buf[4] & 0x1F) as usize]);
37+
}
38+
39+
if data.len() % 5 != 0 {
40+
let len = ret.len();
41+
let num_extra = 8 - (data.len() % 5 * 8 + 4) / 5;
42+
if padding {
43+
for i in 1..num_extra + 1 {
44+
ret[len - i] = b'=';
45+
}
46+
} else {
47+
ret.truncate(len - num_extra);
48+
}
49+
}
50+
51+
String::from_utf8(ret).unwrap()
52+
}
53+
54+
const RFC4648_INV_ALPHABET: [i8; 43] = [
55+
-1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,
56+
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
57+
];
58+
const CROCKFORD_INV_ALPHABET: [i8; 43] = [
59+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 1,
60+
18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31,
61+
];
62+
63+
pub fn decode(alphabet: Alphabet, data: &str) -> Option<Vec<u8>> {
64+
if !data.is_ascii() {
65+
return None;
66+
}
67+
let data = data.as_bytes();
68+
let alphabet = match alphabet {
69+
Alphabet::RFC4648 { .. } => RFC4648_INV_ALPHABET,
70+
Alphabet::Crockford => CROCKFORD_INV_ALPHABET,
71+
};
72+
let mut unpadded_data_length = data.len();
73+
for i in 1..min(6, data.len()) + 1 {
74+
if data[data.len() - i] != b'=' {
75+
break;
76+
}
77+
unpadded_data_length -= 1;
78+
}
79+
let output_length = unpadded_data_length * 5 / 8;
80+
let mut ret = Vec::with_capacity((output_length + 4) / 5 * 5);
81+
for chunk in data.chunks(8) {
82+
let buf = {
83+
let mut buf = [0u8; 8];
84+
for (i, &c) in chunk.iter().enumerate() {
85+
match alphabet.get(c.to_ascii_uppercase().wrapping_sub(b'0') as usize) {
86+
Some(&-1) | None => return None,
87+
Some(&value) => buf[i] = value as u8,
88+
};
89+
}
90+
buf
91+
};
92+
ret.push((buf[0] << 3) | (buf[1] >> 2));
93+
ret.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4));
94+
ret.push((buf[3] << 4) | (buf[4] >> 1));
95+
ret.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3));
96+
ret.push((buf[6] << 5) | buf[7]);
97+
}
98+
ret.truncate(output_length);
99+
Some(ret)
100+
}
101+
102+
#[cfg(test)]
103+
#[allow(dead_code, unused_attributes)]
104+
mod test {
105+
use super::Alphabet::{Crockford, RFC4648};
106+
use super::{decode, encode};
107+
use quickcheck::{self, Arbitrary, Gen};
108+
use std::fmt::{Debug, Error, Formatter};
109+
110+
#[derive(Clone)]
111+
struct B32 {
112+
c: u8,
113+
}
114+
115+
impl Arbitrary for B32 {
116+
fn arbitrary(g: &mut Gen) -> B32 {
117+
B32 {
118+
c: *g.choose(b"0123456789ABCDEFGHJKMNPQRSTVWXYZ").unwrap(),
119+
}
120+
}
121+
}
122+
123+
impl Debug for B32 {
124+
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
125+
(self.c as char).fmt(f)
126+
}
127+
}
128+
129+
#[test]
130+
fn masks_crockford() {
131+
assert_eq!(
132+
encode(Crockford, &[0xF8, 0x3E, 0x0F, 0x83, 0xE0]),
133+
"Z0Z0Z0Z0"
134+
);
135+
assert_eq!(
136+
encode(Crockford, &[0x07, 0xC1, 0xF0, 0x7C, 0x1F]),
137+
"0Z0Z0Z0Z"
138+
);
139+
assert_eq!(
140+
decode(Crockford, "Z0Z0Z0Z0").unwrap(),
141+
[0xF8, 0x3E, 0x0F, 0x83, 0xE0]
142+
);
143+
assert_eq!(
144+
decode(Crockford, "0Z0Z0Z0Z").unwrap(),
145+
[0x07, 0xC1, 0xF0, 0x7C, 0x1F]
146+
);
147+
}
148+
149+
#[test]
150+
fn masks_rfc4648() {
151+
assert_eq!(
152+
encode(RFC4648 { padding: true }, &[0xF8, 0x3E, 0x7F, 0x83, 0xE7]),
153+
"7A7H7A7H"
154+
);
155+
assert_eq!(
156+
encode(RFC4648 { padding: true }, &[0x77, 0xC1, 0xF7, 0x7C, 0x1F]),
157+
"O7A7O7A7"
158+
);
159+
assert_eq!(
160+
decode(RFC4648 { padding: true }, "7A7H7A7H").unwrap(),
161+
[0xF8, 0x3E, 0x7F, 0x83, 0xE7]
162+
);
163+
assert_eq!(
164+
decode(RFC4648 { padding: true }, "O7A7O7A7").unwrap(),
165+
[0x77, 0xC1, 0xF7, 0x7C, 0x1F]
166+
);
167+
assert_eq!(
168+
encode(RFC4648 { padding: true }, &[0xF8, 0x3E, 0x7F, 0x83]),
169+
"7A7H7AY="
170+
);
171+
}
172+
173+
#[test]
174+
fn masks_unpadded_rfc4648() {
175+
assert_eq!(
176+
encode(RFC4648 { padding: false }, &[0xF8, 0x3E, 0x7F, 0x83, 0xE7]),
177+
"7A7H7A7H"
178+
);
179+
assert_eq!(
180+
encode(RFC4648 { padding: false }, &[0x77, 0xC1, 0xF7, 0x7C, 0x1F]),
181+
"O7A7O7A7"
182+
);
183+
assert_eq!(
184+
decode(RFC4648 { padding: false }, "7A7H7A7H").unwrap(),
185+
[0xF8, 0x3E, 0x7F, 0x83, 0xE7]
186+
);
187+
assert_eq!(
188+
decode(RFC4648 { padding: false }, "O7A7O7A7").unwrap(),
189+
[0x77, 0xC1, 0xF7, 0x7C, 0x1F]
190+
);
191+
assert_eq!(
192+
encode(RFC4648 { padding: false }, &[0xF8, 0x3E, 0x7F, 0x83]),
193+
"7A7H7AY"
194+
);
195+
}
196+
197+
#[test]
198+
fn padding() {
199+
let num_padding = [0, 6, 4, 3, 1];
200+
for i in 1..6 {
201+
let encoded = encode(
202+
RFC4648 { padding: true },
203+
(0..(i as u8)).collect::<Vec<u8>>().as_ref(),
204+
);
205+
assert_eq!(encoded.len(), 8);
206+
for j in 0..(num_padding[i % 5]) {
207+
assert_eq!(encoded.as_bytes()[encoded.len() - j - 1], b'=');
208+
}
209+
for j in 0..(8 - num_padding[i % 5]) {
210+
assert!(encoded.as_bytes()[j] != b'=');
211+
}
212+
}
213+
}
214+
215+
#[test]
216+
fn invertible_crockford() {
217+
fn test(data: Vec<u8>) -> bool {
218+
decode(Crockford, encode(Crockford, data.as_ref()).as_ref()).unwrap() == data
219+
}
220+
quickcheck::quickcheck(test as fn(Vec<u8>) -> bool)
221+
}
222+
223+
#[test]
224+
fn invertible_rfc4648() {
225+
fn test(data: Vec<u8>) -> bool {
226+
decode(
227+
RFC4648 { padding: true },
228+
encode(RFC4648 { padding: true }, data.as_ref()).as_ref(),
229+
)
230+
.unwrap()
231+
== data
232+
}
233+
quickcheck::quickcheck(test as fn(Vec<u8>) -> bool)
234+
}
235+
#[test]
236+
fn invertible_unpadded_rfc4648() {
237+
fn test(data: Vec<u8>) -> bool {
238+
decode(
239+
RFC4648 { padding: false },
240+
encode(RFC4648 { padding: false }, data.as_ref()).as_ref(),
241+
)
242+
.unwrap()
243+
== data
244+
}
245+
quickcheck::quickcheck(test as fn(Vec<u8>) -> bool)
246+
}
247+
248+
#[test]
249+
fn lower_case() {
250+
fn test(data: Vec<B32>) -> bool {
251+
let data: String = data.iter().map(|e| e.c as char).collect();
252+
decode(Crockford, data.as_ref())
253+
== decode(Crockford, data.to_ascii_lowercase().as_ref())
254+
}
255+
quickcheck::quickcheck(test as fn(Vec<B32>) -> bool)
256+
}
257+
258+
#[test]
259+
#[allow(non_snake_case)]
260+
fn iIlL1_oO0() {
261+
assert_eq!(decode(Crockford, "IiLlOo"), decode(Crockford, "111100"));
262+
}
263+
264+
#[test]
265+
fn invalid_chars_crockford() {
266+
assert_eq!(decode(Crockford, ","), None)
267+
}
268+
269+
#[test]
270+
fn invalid_chars_rfc4648() {
271+
assert_eq!(decode(RFC4648 { padding: true }, ","), None)
272+
}
273+
274+
#[test]
275+
fn invalid_chars_unpadded_rfc4648() {
276+
assert_eq!(decode(RFC4648 { padding: false }, ","), None)
277+
}
278+
}

lightning/src/util/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod invoice;
2323
pub mod persist;
2424
pub mod string;
2525
pub mod wakers;
26+
pub mod base32;
2627

2728
pub(crate) mod atomic_counter;
2829
pub(crate) mod byte_utils;
@@ -58,4 +59,3 @@ pub mod test_utils;
5859
/// machine errors and used in fuzz targets and tests.
5960
#[cfg(any(test, fuzzing, feature = "_test_utils"))]
6061
pub mod enforcing_trait_impls;
61-

0 commit comments

Comments
 (0)