Skip to content

Commit 6c24f98

Browse files
committed
primitives: introduce the Berlekamp-Massey algorithm for computing linear shift registers
This provides a general-purpose implementation of the Berlekamp-Massey algorithm for finding a linear shift register that generates a given sequence prefix. If compiled without an allocator, it will run less efficiently (and be limited to a maximum size) but it will work. Also introduces a fuzz test to check that it works properly and does not crash.
1 parent fc903d6 commit 6c24f98

File tree

6 files changed

+404
-1
lines changed

6 files changed

+404
-1
lines changed

.github/workflows/fuzz.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
fuzz_target: [decode_rnd, encode_decode, parse_hrp]
13+
fuzz_target: [berlekamp_massey, decode_rnd, encode_decode, parse_hrp]
1414
steps:
1515
- name: Install test dependencies
1616
run: sudo apt-get update -y && sudo apt-get install -y binutils-dev libunwind8-dev libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc libiberty-dev

fuzz/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ bech32 = { path = ".." }
1717
[workspace]
1818
members = ["."]
1919

20+
[[bin]]
21+
name = "berlekamp_massey"
22+
path = "fuzz_targets/berlekamp_massey.rs"
23+
2024
[[bin]]
2125
name = "decode_rnd"
2226
path = "fuzz_targets/decode_rnd.rs"

fuzz/fuzz_targets/berlekamp_massey.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use bech32::primitives::LfsrIter;
2+
use bech32::Fe32;
3+
use honggfuzz::fuzz;
4+
5+
fn do_test(data: &[u8]) {
6+
for ch in data {
7+
if *ch >= 32 {
8+
return;
9+
}
10+
}
11+
if data.is_empty() || data.len() > 1_000 {
12+
return;
13+
}
14+
15+
let mut iv = Vec::with_capacity(data.len());
16+
for ch in data {
17+
iv.push(Fe32::try_from(*ch).unwrap());
18+
}
19+
20+
for (i, d) in LfsrIter::berlekamp_massey(&iv).take(data.len()).enumerate() {
21+
assert_eq!(data[i], d.to_u8());
22+
}
23+
}
24+
25+
fn main() {
26+
loop {
27+
fuzz!(|data| {
28+
do_test(data);
29+
});
30+
}
31+
}
32+
33+
#[cfg(test)]
34+
mod tests {
35+
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
36+
let mut b = 0;
37+
for (idx, c) in hex.as_bytes().iter().filter(|&&c| c != b'\n').enumerate() {
38+
b <<= 4;
39+
match *c {
40+
b'A'..=b'F' => b |= c - b'A' + 10,
41+
b'a'..=b'f' => b |= c - b'a' + 10,
42+
b'0'..=b'9' => b |= c - b'0',
43+
_ => panic!("Bad hex"),
44+
}
45+
if (idx & 1) == 1 {
46+
out.push(b);
47+
b = 0;
48+
}
49+
}
50+
}
51+
52+
#[test]
53+
fn duplicate_crash() {
54+
let mut a = Vec::new();
55+
extend_vec_from_hex("00", &mut a);
56+
super::do_test(&a);
57+
}
58+
}

src/primitives/fieldvec.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,23 @@ impl<F> FieldVec<F> {
107107
#[inline]
108108
pub fn is_empty(&self) -> bool { self.len == 0 }
109109

110+
/// Reverses the contents of the vector in-place.
111+
pub fn reverse(&mut self) {
112+
self.assert_has_data();
113+
114+
#[cfg(not(feature = "alloc"))]
115+
{
116+
self.inner_a[..self.len].reverse();
117+
}
118+
119+
#[cfg(feature = "alloc")]
120+
if self.len > NO_ALLOC_MAX_LENGTH {
121+
self.inner_v.reverse();
122+
} else {
123+
self.inner_a[..self.len].reverse();
124+
}
125+
}
126+
110127
/// Returns an immutable iterator over the elements in the vector.
111128
///
112129
/// # Panics
@@ -186,7 +203,48 @@ impl<F: Field> FieldVec<F> {
186203
}
187204
}
188205

206+
impl<F: Default> Default for FieldVec<F> {
207+
fn default() -> Self { Self::new() }
208+
}
209+
189210
impl<F: Default> FieldVec<F> {
211+
/// Constructs a new empty field vector.
212+
pub fn new() -> Self {
213+
FieldVec {
214+
inner_a: Default::default(),
215+
len: 0,
216+
#[cfg(feature = "alloc")]
217+
inner_v: Vec::new(),
218+
}
219+
}
220+
221+
/// Constructs a new field vector with the given capacity.
222+
pub fn with_capacity(cap: usize) -> Self {
223+
#[cfg(not(feature = "alloc"))]
224+
{
225+
let mut ret = Self::new();
226+
ret.len = cap;
227+
ret.assert_has_data();
228+
ret.len = 0;
229+
ret
230+
}
231+
232+
#[cfg(feature = "alloc")]
233+
if cap > NO_ALLOC_MAX_LENGTH {
234+
let mut ret = Self::new();
235+
ret.inner_v = Vec::with_capacity(cap);
236+
ret
237+
} else {
238+
Self::new()
239+
}
240+
}
241+
242+
/// Pushes an item onto the end of the vector.
243+
///
244+
/// Synonym for [`Self::push`] used to simplify code where a
245+
/// [`FieldVec`] is used in place of a `VecDeque`.
246+
pub fn push_back(&mut self, item: F) { self.push(item) }
247+
190248
/// Pushes an item onto the end of the vector.
191249
///
192250
/// # Panics
@@ -213,6 +271,38 @@ impl<F: Default> FieldVec<F> {
213271
}
214272
}
215273

274+
/// Pops an item off the front of the vector.
275+
///
276+
/// This operation is always O(n).
277+
pub fn pop_front(&mut self) -> Option<F> {
278+
self.assert_has_data();
279+
if self.len == 0 {
280+
return None;
281+
}
282+
283+
#[cfg(not(feature = "alloc"))]
284+
{
285+
// Not the most efficient algorithm, but it is safe code,
286+
// easily seen to be correct, and is only used with very
287+
// small vectors.
288+
self.reverse();
289+
let ret = self.pop();
290+
self.reverse();
291+
ret
292+
}
293+
294+
#[cfg(feature = "alloc")]
295+
if self.len > NO_ALLOC_MAX_LENGTH + 1 {
296+
self.len -= 1;
297+
Some(self.inner_v.remove(0))
298+
} else {
299+
self.reverse();
300+
let ret = self.pop();
301+
self.reverse();
302+
ret
303+
}
304+
}
305+
216306
/// Pops an item off the end of the vector.
217307
///
218308
/// # Panics

0 commit comments

Comments
 (0)