├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── src ├── fs.rs ├── lib.rs ├── main.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | - beta 6 | before_script: 7 | - rustup component add clippy rustfmt 8 | script: 9 | - cargo clippy --all-targets --all-features -- -D warnings 10 | - cargo fmt --all -- --check 11 | - cargo build 12 | - cargo test 13 | matrix: 14 | allow_failures: 15 | - rust: nightly 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bulk-gcd" 3 | description = "Fast parallel bulk GCD computation for finding weak RSA keys in a set" 4 | version = "2.2.0" 5 | authors = ["Fedor Indutny "] 6 | repository = "https://github.com/indutny/bulk-gcd" 7 | license = "MIT" 8 | keywords = ["bulk","gcd","fastgcd"] 9 | categories = ["cryptography"] 10 | readme = "README.md" 11 | exclude = [".travis.yml"] 12 | 13 | [dependencies] 14 | byteorder = "1.5.0" 15 | clap = "^2.32.0" 16 | env_logger = "^0.6.0" 17 | log = "^0.4.6" 18 | rayon = "^1.10.0" 19 | 20 | [dependencies.rug] 21 | version = "^1.27.0" 22 | default-features = false 23 | features = ["integer"] 24 | 25 | [badges] 26 | travis-ci = { repository = "indutny/bulk-gcd" } 27 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright Fedor Indutny, 2018. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to permit 10 | persons to whom the Software is furnished to do so, subject to the 11 | following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 21 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 22 | USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bulk-gcd 2 | [![Build Status](https://secure.travis-ci.org/indutny/bulk-gcd.svg)](http://travis-ci.org/indutny/bulk-gcd) 3 | [![Latest version](https://img.shields.io/crates/v/bulk-gcd.svg)](https://crates.io/crates/bulk-gcd) 4 | [![Documentation](https://docs.rs/bulk-gcd/badge.svg)](https://docs.rs/bulk-gcd) 5 | ![License](https://img.shields.io/crates/l/bulk-gcd.svg) 6 | 7 | This package provides and implementation of bulk GCD (Greatest Common Divisor) 8 | algorithm by [D. Bernstein][bernstein]. 9 | 10 | ## Why? 11 | 12 | GCD is useful for identifying weak keys in a large set of RSA keys. Such 13 | sets were collected by researches (e.g. [this paper][that paper]). In order to 14 | find weak keys a pairwise GCD has to be executed on all RSA moduli (i.e. 15 | products of two primes `P` and `Q`, pertaining to each RSA private key). 16 | However, each separate GCD computation takes considerable amount of time and the 17 | naive pairwise process doesn't scale well (`O(n^2)`). 18 | 19 | Instead of doing this search in a brute-force way, this module employs clever 20 | algorithm by [D. Bernstein][bernstein], that finds GCD of each moduli with a 21 | product of all other moduli. Through introduction of product and remainder 22 | trees, the computational cost becomes logarithmic instead of quadratic, which 23 | results in dramatic drop of the execution time. 24 | 25 | ## Quick example 26 | 27 | ```rust 28 | extern crate bulk_gcd; 29 | extern crate rug; 30 | 31 | use rug::Integer; 32 | 33 | let moduli = [ 34 | Integer::from(15), 35 | Integer::from(35), 36 | Integer::from(23), 37 | ]; 38 | 39 | let result = bulk_gcd::compute(&moduli).unwrap(); 40 | 41 | assert_eq!(result, vec![ 42 | Some(Integer::from(5)), 43 | Some(Integer::from(5)), 44 | None, 45 | ]); 46 | ``` 47 | 48 | ## Using bulk-gcd 49 | 50 | `bulk-gcd` is available on [crates.io][crates]. To use `bulk-gcd` in your crate, 51 | add it as a dependency inside [Cargo.toml][cargo doc]: 52 | 53 | ``` 54 | [dependencies] 55 | bulk-gcd = "^1.0.0" 56 | ``` 57 | 58 | You also need to declare it by adding this to your crate root (usually 59 | `lib.rs` or `main.rs`): 60 | 61 | ```rust 62 | extern crate bulk_gcd; 63 | ``` 64 | 65 | ## Credits 66 | 67 | Huge thanks to [Michael Grunder][1] for helping me make threads work in Rust. 68 | 69 | [bernstein]: https://cr.yp.to/factorization/smoothparts-20040510.pdf 70 | [that paper]: https://factorable.net/weakkeys12.conference.pdf 71 | [crates]: https://crates.io/crates/bulk-gcd 72 | [cargo doc]: https://doc.rust-lang.org/cargo/guide/dependencies.html 73 | [1]: https://github.com/michael-grunder 74 | -------------------------------------------------------------------------------- /src/fs.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 2 | use rug::integer::Order; 3 | use rug::Integer; 4 | use std::fs::File; 5 | use std::io; 6 | use std::io::prelude::*; 7 | use std::io::{BufReader, BufWriter}; 8 | use std::path::Path; 9 | 10 | pub fn read_from(path: &Path) -> io::Result> { 11 | let file = File::open(path)?; 12 | let mut reader = BufReader::new(file); 13 | 14 | let mut result = Vec::::new(); 15 | 16 | // 32768 bits 17 | let mut digits = Vec::::with_capacity(4096); 18 | loop { 19 | let size = match reader.read_u32::() { 20 | Err(err) => { 21 | if err.kind() == io::ErrorKind::UnexpectedEof { 22 | return Ok(result); 23 | } else { 24 | return Err(err); 25 | } 26 | } 27 | Ok(size) => size, 28 | }; 29 | 30 | digits.resize(size as usize, 0); 31 | reader.read_exact(&mut digits)?; 32 | result.push(Integer::from_digits(&digits, Order::MsfBe)); 33 | } 34 | } 35 | 36 | pub fn write_to(path: &Path, values: &[Integer]) -> io::Result<()> { 37 | let file = File::create(path)?; 38 | let mut reader = BufWriter::new(file); 39 | 40 | // 2.56e9 bits 41 | let mut digits = Vec::::with_capacity(320_000_000); 42 | 43 | for value in values { 44 | digits.resize(value.significant_digits::(), 0); 45 | value.write_digits(&mut digits, Order::MsfBe); 46 | 47 | reader.write_u32::(digits.len() as u32)?; 48 | 49 | reader.write_all(&digits)?; 50 | } 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # bulk-gcd 2 | //! 3 | //! This crate computes GCD of each integer in moduli list with the product of 4 | //! all integers in the same list using [fast algorithm][bernstein] by 5 | //! D. Bernstein. 6 | //! 7 | //! See: [this paper][that paper] for more details and motivation. 8 | //! 9 | //! Usage example: 10 | //! ```rust 11 | //! extern crate bulk_gcd; 12 | //! extern crate rug; 13 | //! 14 | //! use rug::Integer; 15 | //! 16 | //! let moduli = [ 17 | //! Integer::from(15), 18 | //! Integer::from(35), 19 | //! Integer::from(23), 20 | //! ]; 21 | //! 22 | //! let result = bulk_gcd::compute(&moduli, None).unwrap(); 23 | //! 24 | //! assert_eq!( 25 | //! result, 26 | //! vec![ 27 | //! Some(Integer::from(5)), 28 | //! Some(Integer::from(5)), 29 | //! None, 30 | //! ] 31 | //! ); 32 | //! ``` 33 | //! 34 | //! [bernstein]: https://cr.yp.to/factorization/smoothparts-20040510.pdf 35 | //! [that paper]: https://factorable.net/weakkeys12.conference.pdf 36 | extern crate byteorder; 37 | extern crate rayon; 38 | extern crate rug; 39 | 40 | #[macro_use] 41 | extern crate log; 42 | extern crate env_logger; 43 | 44 | pub mod fs; 45 | mod utils; 46 | 47 | use rayon::prelude::*; 48 | use rug::integer::IntegerExt64; 49 | use rug::ops::MulFrom; 50 | use rug::Integer; 51 | use std::error::Error; 52 | use std::fmt; 53 | use std::fmt::Display; 54 | use std::path::Path; 55 | use utils::*; 56 | 57 | /// Possible computation errors 58 | #[derive(Debug, PartialEq)] 59 | pub enum ComputeError { 60 | /// Returned when `compute()` is called with 0 or 1 moduli 61 | /// Minimum 2 moduli are required for meaningful operation of the function. 62 | NotEnoughModuli, 63 | } 64 | 65 | impl Display for ComputeError { 66 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 67 | write!(f, "ComputeError") 68 | } 69 | } 70 | 71 | impl Error for ComputeError { 72 | fn description(&self) -> &str { 73 | match self { 74 | ComputeError::NotEnoughModuli => "Not enough moduli", 75 | } 76 | } 77 | } 78 | 79 | pub type ComputeResult = Result>, ComputeError>; 80 | 81 | fn get_bounds(list: &[Integer]) -> (u64, u64) { 82 | let min = list 83 | .iter() 84 | .fold(u64::MAX, |acc, value| u64::min(acc, value.signed_bits_64())); 85 | let max = list 86 | .iter() 87 | .fold(u64::MIN, |acc, value| u64::max(acc, value.signed_bits_64())); 88 | (min, max) 89 | } 90 | 91 | fn compute_products( 92 | mut moduli: Vec, 93 | target_len: usize, 94 | cache_dir: Option<&Path>, 95 | ) -> Vec { 96 | if let Some(cache_dir) = cache_dir { 97 | let path = cache_dir.join(format!("{}.bin", target_len)); 98 | trace!("trying reading products from {}", path.display()); 99 | 100 | if let Ok(result) = fs::read_from(path.as_path()) { 101 | return result; 102 | } 103 | } 104 | 105 | while moduli.len() > target_len { 106 | trace!("computing products {} -> {}", moduli.len(), target_len); 107 | 108 | // See pad_ints() in utils.rs 109 | // The list is sorted and the second half is reversed so that the 110 | // smallest moduli is at [0] and the largest is at [half] 111 | let half = moduli.len() / 2; 112 | let (left, right) = moduli.split_at_mut(half); 113 | 114 | left.par_iter_mut() 115 | .zip(right.par_iter_mut()) 116 | .for_each(|(small, big)| small.mul_from(std::mem::take(big))); 117 | moduli.truncate(half); 118 | 119 | if let Some(cache_dir) = cache_dir { 120 | if moduli.len() <= target_len { 121 | continue; 122 | } 123 | 124 | let (min, max) = get_bounds(&moduli); 125 | 126 | let path = cache_dir.join(format!("{}.bin", moduli.len())); 127 | trace!( 128 | "writing products (min={}, max={}) to {}", 129 | min, 130 | max, 131 | path.display() 132 | ); 133 | fs::write_to(path.as_path(), &moduli).unwrap(); 134 | } 135 | } 136 | moduli 137 | } 138 | 139 | fn compute_remainders(moduli: Vec, cache_dir: Option<&Path>) -> Vec { 140 | let mut pre_last = Some(compute_products(moduli.clone(), 2, cache_dir)); 141 | let mut remainders = compute_products(pre_last.clone().unwrap(), 1, cache_dir); 142 | 143 | let mut depth = 2; 144 | while depth <= moduli.len() { 145 | let mut current = pre_last 146 | .take() 147 | .unwrap_or_else(|| compute_products(moduli.clone(), depth, cache_dir)); 148 | 149 | let (min, max) = get_bounds(¤t); 150 | trace!( 151 | "computing remainder level {} (min={}, max={})", 152 | depth, 153 | min, 154 | max 155 | ); 156 | 157 | let compute = |(i, value): (usize, &mut Integer)| { 158 | // value = remainders[i % remainders.len()] % (value ** 2) 159 | let parent = &remainders[i % remainders.len()]; 160 | 161 | // Don't compute square if the divisor is bigger than the divisor 162 | if value.signed_bits_64() * 2 > parent.signed_bits_64() { 163 | value.clone_from(parent); 164 | } else { 165 | value.square_mut(); 166 | value.modulo_from(parent); 167 | } 168 | }; 169 | 170 | // First levels use most memory so don't par_iter yet 171 | if current.len() <= 32 { 172 | current.iter_mut().enumerate().for_each(compute); 173 | } else { 174 | current.par_iter_mut().enumerate().for_each(compute); 175 | } 176 | 177 | remainders = current; 178 | depth *= 2; 179 | } 180 | 181 | remainders 182 | } 183 | 184 | fn compute_gcds(remainders: &[Integer], moduli: &[Integer]) -> Vec { 185 | trace!("computing quotients and gcd"); 186 | remainders 187 | .par_iter() 188 | .zip(moduli.par_iter()) 189 | .map(|(remainder, modulo)| { 190 | let quotient = Integer::from(remainder / modulo); 191 | quotient.gcd(modulo) 192 | }) 193 | .collect() 194 | } 195 | 196 | /// Compute GCD of each integer in the `moduli` with all other integers in it. 197 | /// 198 | /// Usage example: 199 | /// ```rust 200 | /// extern crate bulk_gcd; 201 | /// extern crate rug; 202 | /// 203 | /// use rug::Integer; 204 | /// 205 | /// let moduli = [ 206 | /// Integer::from(15), 207 | /// Integer::from(35), 208 | /// Integer::from(23), 209 | /// Integer::from(49), 210 | /// ]; 211 | /// 212 | /// let result = bulk_gcd::compute(&moduli, None).unwrap(); 213 | /// 214 | /// assert_eq!( 215 | /// result, 216 | /// vec![ 217 | /// Some(Integer::from(5)), 218 | /// Some(Integer::from(35)), 219 | /// None, 220 | /// Some(Integer::from(7)), 221 | /// ] 222 | /// ); 223 | /// ``` 224 | /// 225 | /// NOTE: Minimum 2 `moduli` are required for running the algorithm, otherwise 226 | /// `NotEnoughModuli` error is returned: 227 | /// 228 | /// ```rust 229 | /// extern crate bulk_gcd; 230 | /// extern crate rug; 231 | /// 232 | /// use rug::Integer; 233 | /// 234 | /// assert_eq!( 235 | /// bulk_gcd::compute(&[], None).unwrap_err(), 236 | /// bulk_gcd::ComputeError::NotEnoughModuli 237 | /// ); 238 | /// ``` 239 | /// 240 | pub fn compute(moduli: &[Integer], cache_dir: Option<&Path>) -> ComputeResult { 241 | if moduli.len() < 2 { 242 | return Err(ComputeError::NotEnoughModuli); 243 | } 244 | 245 | trace!("starting with {} moduli", moduli.len()); 246 | 247 | // Pad to the power-of-two len 248 | let (padded_moduli, original_indices) = pad_ints(moduli.to_vec()); 249 | trace!("padded to {}", padded_moduli.len()); 250 | 251 | let remainders = compute_remainders(padded_moduli.clone(), cache_dir); 252 | 253 | let gcds = compute_gcds(&remainders, &padded_moduli); 254 | 255 | let unpadded_gcds = unpad_ints(gcds, original_indices, moduli.len()); 256 | 257 | Ok(unpadded_gcds 258 | .into_iter() 259 | .map(|gcd| if gcd == 1 { None } else { Some(gcd) }) 260 | .collect()) 261 | } 262 | 263 | #[cfg(test)] 264 | mod tests { 265 | use super::*; 266 | 267 | #[test] 268 | fn it_should_fail_on_zero_moduli() { 269 | assert!(compute(&[], None).is_err()); 270 | } 271 | 272 | #[test] 273 | fn it_should_fail_on_single_moduli() { 274 | assert!(compute(&[Integer::new()], None).is_err()); 275 | } 276 | 277 | #[test] 278 | fn it_should_return_gcd_of_two_moduli() { 279 | let moduli = [Integer::from(6), Integer::from(15)]; 280 | 281 | let result = compute(&moduli, None).unwrap(); 282 | assert_eq!( 283 | result, 284 | vec![Some(Integer::from(3)), Some(Integer::from(3)),] 285 | ); 286 | } 287 | 288 | #[test] 289 | fn it_should_find_gcd_for_many_moduli() { 290 | let moduli = vec![ 291 | Integer::from(31 * 41), 292 | Integer::from(41), 293 | Integer::from(61), 294 | Integer::from(71 * 31), 295 | Integer::from(101 * 131), 296 | Integer::from(131 * 151), 297 | ]; 298 | 299 | let result = compute(&moduli, None).unwrap(); 300 | 301 | assert_eq!( 302 | result, 303 | vec![ 304 | Some(Integer::from(31 * 41)), 305 | Some(Integer::from(41)), 306 | None, 307 | Some(Integer::from(31)), 308 | Some(Integer::from(131)), 309 | Some(Integer::from(131)), 310 | ] 311 | ); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate bulk_gcd; 2 | extern crate clap; 3 | extern crate rug; 4 | 5 | #[macro_use] 6 | extern crate log; 7 | extern crate env_logger; 8 | 9 | use clap::{App, Arg}; 10 | use rug::Integer; 11 | use std::path::{Path, PathBuf}; 12 | use std::process::exit; 13 | 14 | fn main() -> std::io::Result<()> { 15 | env_logger::init(); 16 | 17 | let matches = App::new("bulk-gcd") 18 | .version(clap::crate_version!()) 19 | .author(clap::crate_authors!("\n")) 20 | .about("Compute bulk GCD of a list of hex RSA moduli") 21 | .arg( 22 | Arg::with_name("INPUT") 23 | .help(concat!( 24 | "Input file to use. Must contain hex values of moduli ", 25 | "separated by newline ('\\n')" 26 | )) 27 | .required(true) 28 | .index(1), 29 | ) 30 | .get_matches(); 31 | 32 | let input = matches.value_of("INPUT").unwrap(); 33 | trace!("opening file \"{}\"", &input); 34 | 35 | let moduli = bulk_gcd::fs::read_from(Path::new(input))?; 36 | 37 | let cache_dir: Option = std::env::var_os("CACHE_DIR").map(PathBuf::from); 38 | 39 | if let Some(ref dir) = cache_dir { 40 | std::fs::create_dir_all(dir)?; 41 | } 42 | 43 | let result: Vec<(usize, Integer)> = bulk_gcd::compute(&moduli, cache_dir.as_deref()) 44 | .unwrap() 45 | .into_iter() 46 | .enumerate() 47 | .filter_map(|(i, opt)| opt.map(|gcd| (i, gcd))) 48 | .collect(); 49 | 50 | if result.is_empty() { 51 | eprintln!("no results"); 52 | exit(0); 53 | } 54 | 55 | for (i, gcd) in result { 56 | println!("i={} divisor={:x} moduli={:x}", i, gcd, moduli[i]); 57 | } 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | extern crate rayon; 2 | extern crate rug; 3 | 4 | use rayon::prelude::*; 5 | use rug::Integer; 6 | 7 | pub fn pad_ints(mut list: Vec) -> (Vec, Vec) { 8 | // Pad to the power-of-two len 9 | let mut desired_size: usize = 1; 10 | loop { 11 | if desired_size >= list.len() { 12 | break; 13 | } 14 | desired_size <<= 1; 15 | } 16 | 17 | while list.len() < desired_size { 18 | list.push(Integer::from(1)) 19 | } 20 | 21 | let mut enumerated: Vec<(usize, Integer)> = list.into_iter().enumerate().collect(); 22 | 23 | // Sort by increasing modulo size so that the smallest moduli (and padding) 24 | // are on the left, and the large are on the right 25 | enumerated.par_sort_by(|(_, a), (_, b)| a.cmp(b)); 26 | 27 | // Reverse second half of the list so that the biggest moduli are close to 28 | // the center. 29 | let half = enumerated.len() / 2; 30 | enumerated[half..].reverse(); 31 | 32 | // At this point the vector should look like this order-wise: 33 | // [1, ..., N, N, ..., 1 ] 34 | // 35 | // When computing the product tree we'll multiply elements from left half 36 | // with the elements from the right half, balancing the size of resulting 37 | // products. 38 | 39 | // Keep the original indices so that we could restore the order at the end 40 | let original_indices: Vec = enumerated.iter().map(|(index, _value)| *index).collect(); 41 | let sorted: Vec = enumerated 42 | .into_iter() 43 | .map(|(_index, value)| value) 44 | .collect(); 45 | (sorted, original_indices) 46 | } 47 | 48 | pub fn unpad_ints( 49 | list: Vec, 50 | original_indices: Vec, 51 | original_size: usize, 52 | ) -> Vec { 53 | let mut pairs = original_indices 54 | .into_iter() 55 | .zip(list) 56 | .collect::>(); 57 | 58 | pairs.par_sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap()); 59 | 60 | pairs.truncate(original_size); 61 | 62 | pairs.into_iter().map(|(_, value)| value).collect() 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[test] 70 | fn it_should_pad_properly() { 71 | let input = (0..5).map(|i| Integer::from(10 + i)).rev().collect(); 72 | 73 | let expected = vec![ 74 | Integer::from(1), 75 | Integer::from(1), 76 | Integer::from(1), 77 | Integer::from(10), 78 | Integer::from(14), 79 | Integer::from(13), 80 | Integer::from(12), 81 | Integer::from(11), 82 | ]; 83 | assert_eq!(pad_ints(input), (expected, vec![5, 6, 7, 4, 0, 1, 2, 3])); 84 | } 85 | 86 | #[test] 87 | fn it_should_pad_properly_with_clean_division() { 88 | let input = (0..6).map(|i| Integer::from(10 + i)).collect(); 89 | 90 | let expected = vec![ 91 | Integer::from(1), 92 | Integer::from(1), 93 | Integer::from(10), 94 | Integer::from(11), 95 | Integer::from(15), 96 | Integer::from(14), 97 | Integer::from(13), 98 | Integer::from(12), 99 | ]; 100 | assert_eq!(pad_ints(input), (expected, vec![6, 7, 0, 1, 5, 4, 3, 2])); 101 | } 102 | 103 | #[test] 104 | fn it_should_skip_padding() { 105 | let input = (0..4).map(|i| Integer::from(10 + i)).collect(); 106 | 107 | let expected = vec![ 108 | Integer::from(10), 109 | Integer::from(11), 110 | Integer::from(13), 111 | Integer::from(12), 112 | ]; 113 | assert_eq!(pad_ints(input), (expected, vec![0, 1, 3, 2])); 114 | } 115 | 116 | #[test] 117 | fn it_should_unpad_properly() { 118 | let input = vec![ 119 | Integer::from(1), 120 | Integer::from(10), 121 | Integer::from(1), 122 | Integer::from(11), 123 | Integer::from(1), 124 | Integer::from(12), 125 | Integer::from(13), 126 | Integer::from(14), 127 | ]; 128 | assert_eq!( 129 | unpad_ints(input, vec![6, 0, 7, 1, 8, 2, 3, 4], 5), 130 | vec![ 131 | Integer::from(10), 132 | Integer::from(11), 133 | Integer::from(12), 134 | Integer::from(13), 135 | Integer::from(14), 136 | ] 137 | ); 138 | } 139 | } 140 | --------------------------------------------------------------------------------