├── .editorconfig ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── basic.rs └── src └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 4 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 4 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 4 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 4 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | 36 | [*.{yml,yaml}] 37 | indent_size = 2 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: iceyec/ci-rust 2 | 3 | before_script: 4 | - export TRAVIS_JOB_ID=$CI_BUILD_ID 5 | 6 | cache: 7 | untracked: true 8 | key: $CI_BUILD_STAGE/$CI_BUILD_REF_NAME 9 | paths: 10 | - $HOME/.cargo 11 | - $CI_PROJECT_DIR/target 12 | 13 | variables: 14 | TRAVIS_CARGO_NIGHTLY_FEATURE: "" 15 | 16 | stages: 17 | - test 18 | - publish 19 | 20 | test-stable: 21 | script: 22 | - curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- -y --disable-sudo 23 | - travis-cargo build && travis-cargo test && travis-cargo coveralls --no-sudo 24 | 25 | test-beta: 26 | allow_failure: true 27 | script: 28 | - curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- -y --disable-sudo --channel=beta 29 | - travis-cargo build && travis-cargo test 30 | 31 | test-nightly: 32 | allow_failure: true 33 | script: 34 | - curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- -y --disable-sudo --channel=nightly 35 | - travis-cargo build && travis-cargo test 36 | 37 | publish: 38 | stage: publish 39 | only: 40 | - tags 41 | script: 42 | - curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- -y --disable-sudo 43 | - cargo build --release --verbose 44 | - cargo test --verbose 45 | - cargo package --verbose 46 | - cargo doc --verbose 47 | - cargo publish --token "$CRATES_IO_TOKEN" 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | addons: 2 | apt: 3 | packages: 4 | - libcurl4-openssl-dev 5 | - libelf-dev 6 | - libdw-dev 7 | language: rust 8 | rust: 9 | - nightly 10 | - beta 11 | - stable 12 | matrix: 13 | allow_failures: 14 | - rust: 15 | - nightly 16 | - beta 17 | env: 18 | - TRAVIS_CARGO_NIGHTLY_FEATURE="" 19 | cache: 20 | apt: true 21 | rust-download: true 22 | directories: 23 | - ~/.cargo 24 | notifications: 25 | email: 26 | on_success: never 27 | 28 | before_script: 29 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 30 | 31 | script: 32 | - | 33 | travis-cargo build && 34 | travis-cargo test 35 | after_success: 36 | - travis-cargo coveralls --no-sudo 37 | sudo: false 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shamir" 3 | version = "2.0.0" 4 | authors = ["Chris MacNaughton "] 5 | description = "Shamir is a pure Rust implementation of Shamir's secret sharing" 6 | homepage = "https://github.com/Nebulosus/shamir" 7 | repository = "https://github.com/Nebulosus/shamir.git" 8 | license = "MIT" 9 | 10 | [dependencies] 11 | rand = "0.7.3" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Chris MacNaughton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shamir 2 | 3 | [![Coverage Status](https://img.shields.io/coveralls/Nebulosus/shamir.svg?style=flat-square)](https://coveralls.io/github/Nebulosus/shamir) 4 | # [![Build Status](https://git.cmacinfo.com/Nebulosus/shamir/badges/master/build.svg)](https://git.cmacinfo.com/Nebulosus/shamir/builds) 5 | 6 | Shamir is a pure Rust implementation of [Shamir's secret sharing][shamirs]. 7 | 8 | [shamirs]: https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing 9 | 10 | ## Install 11 | 12 | To install [shamir][this_app] into your application, you need to add it to your `cargo.toml`: 13 | 14 | ```yaml 15 | [dependencies] 16 | shamir = "~1.0" 17 | ``` 18 | 19 | and you need to include it at the top of oyur `main.rs`: 20 | 21 | ```rust 22 | extern crate shamir; 23 | 24 | use shamir::SecretData; 25 | ``` 26 | 27 | [this_app]: https://github.com/Nebulosus/shamir 28 | 29 | ## Usage 30 | 31 | ```rust 32 | extern crate shamir; 33 | 34 | use shamir::SecretData; 35 | 36 | fn main() { 37 | let secret_data = SecretData::with_secret("Hello World!", 3); 38 | 39 | let share1 = secret_data.get_share(1); 40 | let share2 = secret_data.get_share(2); 41 | let share3 = secret_data.get_share(3); 42 | 43 | let recovered = SecretData::recover_secret(3, vec![share1, share2, share3]).unwrap(); 44 | 45 | println!("Recovered {}", recovered); 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | extern crate shamir; 2 | 3 | use shamir::SecretData; 4 | 5 | fn main() { 6 | let secret_data = SecretData::with_secret(&"Hello World!"[..], 3); 7 | 8 | let share1 = secret_data.get_share(1).unwrap(); 9 | let share2 = secret_data.get_share(2).unwrap(); 10 | let share3 = secret_data.get_share(3).unwrap(); 11 | println!("Used keys:\n1:{:?}\n2:{:?}\n3:{:?}", share1, share2, share3); 12 | let recovered = SecretData::recover_secret(3, vec![share1, share2, share3]).unwrap(); 13 | 14 | println!("Recovered {}", recovered); 15 | } 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use rand::{thread_rng, RngCore}; 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::SecretData; 8 | #[test] 9 | fn it_works() {} 10 | 11 | #[test] 12 | fn it_generates_coefficients() { 13 | let secret_data = SecretData::with_secret("Hello, world!", 3); 14 | assert_eq!(secret_data.coefficients.len(), 13); 15 | } 16 | 17 | #[test] 18 | fn it_rejects_share_id_under_1() { 19 | let secret_data = SecretData::with_secret("Hello, world!", 3); 20 | let d = secret_data.get_share(0); 21 | assert!(d.is_err()); 22 | } 23 | 24 | #[test] 25 | fn it_issues_shares() { 26 | let secret_data = SecretData::with_secret("Hello, world!", 3); 27 | 28 | let s1 = secret_data.get_share(1).unwrap(); 29 | println!("Share: {:?}", s1); 30 | assert!(secret_data.is_valid_share(&s1)); 31 | } 32 | 33 | #[test] 34 | fn it_repeatedly_issues_shares() { 35 | let secret_data = SecretData::with_secret("Hello, world!", 3); 36 | 37 | let s1 = secret_data.get_share(1).unwrap(); 38 | println!("Share: {:?}", s1); 39 | assert!(secret_data.is_valid_share(&s1)); 40 | 41 | let s2 = secret_data.get_share(1).unwrap(); 42 | assert_eq!(s1, s2); 43 | } 44 | 45 | #[test] 46 | fn it_can_recover_secret() { 47 | let s1 = vec![1, 184, 190, 251, 87, 232, 39, 47, 17, 4, 36, 190, 245]; 48 | let s2 = vec![2, 231, 107, 52, 138, 34, 221, 9, 221, 67, 79, 33, 16]; 49 | let s3 = vec![3, 23, 176, 163, 177, 165, 218, 113, 163, 53, 7, 251, 196]; 50 | 51 | let new_secret = SecretData::recover_secret(3, vec![s1, s2, s3]).unwrap(); 52 | 53 | assert_eq!(&new_secret[..], "Hello World!"); 54 | } 55 | 56 | #[test] 57 | fn it_can_recover_a_generated_secret() { 58 | let secret_data = SecretData::with_secret("Hello, world!", 3); 59 | 60 | let s1 = secret_data.get_share(1).unwrap(); 61 | println!("s1: {:?}", s1); 62 | let s2 = secret_data.get_share(2).unwrap(); 63 | println!("s2: {:?}", s2); 64 | let s3 = secret_data.get_share(3).unwrap(); 65 | println!("s3: {:?}", s3); 66 | 67 | let new_secret = SecretData::recover_secret(3, vec![s1, s2, s3]).unwrap(); 68 | 69 | assert_eq!(&new_secret[..], "Hello, world!"); 70 | } 71 | 72 | #[test] 73 | fn it_requires_enough_shares() { 74 | fn try_recover(n: u8, shares: &Vec>) -> Option { 75 | let shares = shares.iter().take(n as usize).cloned().collect::>(); 76 | SecretData::recover_secret(n, shares) 77 | } 78 | let secret_data = SecretData::with_secret("Hello World!", 5); 79 | 80 | let shares = vec![ 81 | secret_data.get_share(1).unwrap(), 82 | secret_data.get_share(2).unwrap(), 83 | secret_data.get_share(3).unwrap(), 84 | secret_data.get_share(4).unwrap(), 85 | secret_data.get_share(5).unwrap(), 86 | ]; 87 | 88 | let recovered = try_recover(5, &shares); 89 | assert!(recovered.is_some()); 90 | 91 | let recovered = try_recover(3, &shares); 92 | assert!(recovered.is_none()); 93 | } 94 | } 95 | 96 | pub struct SecretData { 97 | pub secret_data: Option, 98 | pub coefficients: Vec>, 99 | } 100 | 101 | #[derive(Debug)] 102 | pub enum ShamirError { 103 | /// The number of shares must be between 1 and 255 104 | InvalidShareCount, 105 | } 106 | 107 | impl SecretData { 108 | pub fn with_secret(secret: &str, threshold: u8) -> SecretData { 109 | let mut coefficients: Vec> = vec![]; 110 | let mut rng = thread_rng(); 111 | let mut rand_container = vec![0u8; (threshold - 1) as usize]; 112 | for c in secret.as_bytes() { 113 | rng.fill_bytes(&mut rand_container); 114 | let mut coef: Vec = vec![*c]; 115 | for r in rand_container.iter() { 116 | coef.push(*r); 117 | } 118 | coefficients.push(coef); 119 | } 120 | 121 | SecretData { 122 | secret_data: Some(secret.to_string()), 123 | coefficients, 124 | } 125 | } 126 | 127 | pub fn get_share(&self, id: u8) -> Result, ShamirError> { 128 | if id == 0 { 129 | return Err(ShamirError::InvalidShareCount); 130 | } 131 | let mut share_bytes: Vec = vec![]; 132 | let coefficients = self.coefficients.clone(); 133 | for coefficient in coefficients { 134 | let b = try!(SecretData::accumulate_share_bytes(id, coefficient)); 135 | share_bytes.push(b); 136 | } 137 | 138 | share_bytes.insert(0, id); 139 | Ok(share_bytes) 140 | } 141 | 142 | pub fn is_valid_share(&self, share: &[u8]) -> bool { 143 | let id = share[0]; 144 | match self.get_share(id) { 145 | Ok(s) => s == share, 146 | _ => false, 147 | } 148 | } 149 | 150 | pub fn recover_secret(threshold: u8, shares: Vec>) -> Option { 151 | if threshold as usize > shares.len() { 152 | println!("Number of shares is below the threshold"); 153 | return None; 154 | } 155 | let mut xs: Vec = vec![]; 156 | 157 | for share in shares.iter() { 158 | if xs.contains(&share[0]) { 159 | println!("Multiple shares with the same first byte"); 160 | return None; 161 | } 162 | 163 | if share.len() != shares[0].len() { 164 | println!("Shares have different lengths"); 165 | return None; 166 | } 167 | 168 | xs.push(share[0].to_owned()); 169 | } 170 | let mut mycoefficients: Vec = vec![]; 171 | let mut mysecretdata: Vec = vec![]; 172 | let rounds = shares[0].len() - 1; 173 | 174 | for byte_to_use in 0..rounds { 175 | let mut fxs: Vec = vec![]; 176 | for share in shares.clone() { 177 | fxs.push(share[1..][byte_to_use]); 178 | } 179 | 180 | match SecretData::full_lagrange(&xs, &fxs) { 181 | None => return None, 182 | Some(resulting_poly) => { 183 | mycoefficients.push(String::from_utf8_lossy(&resulting_poly[..]).to_string()); 184 | mysecretdata.push(resulting_poly[0]); 185 | } 186 | } 187 | } 188 | 189 | match String::from_utf8(mysecretdata) { 190 | Ok(s) => Some(s), 191 | Err(e) => { 192 | println!("{:?}", e); 193 | None 194 | } 195 | } 196 | } 197 | 198 | fn accumulate_share_bytes(id: u8, coefficient_bytes: Vec) -> Result { 199 | if id == 0 { 200 | return Err(ShamirError::InvalidShareCount); 201 | } 202 | let mut accumulator: u8 = 0; 203 | 204 | let mut x_i: u8 = 1; 205 | 206 | for c in coefficient_bytes { 207 | accumulator = SecretData::gf256_add(accumulator, SecretData::gf256_mul(c, x_i)); 208 | x_i = SecretData::gf256_mul(x_i, id); 209 | } 210 | 211 | Ok(accumulator) 212 | } 213 | 214 | fn full_lagrange(xs: &[u8], fxs: &[u8]) -> Option> { 215 | let mut returned_coefficients: Vec = vec![]; 216 | let len = fxs.len(); 217 | for i in 0..len { 218 | let mut this_polynomial: Vec = vec![1]; 219 | 220 | for j in 0..len { 221 | if i == j { 222 | continue; 223 | } 224 | 225 | let denominator = SecretData::gf256_sub(xs[i], xs[j]); 226 | let first_term = SecretData::gf256_checked_div(xs[j], denominator); 227 | let second_term = SecretData::gf256_checked_div(1, denominator); 228 | match (first_term, second_term) { 229 | (Some(a), Some(b)) => { 230 | let this_term = vec![a, b]; 231 | this_polynomial = 232 | SecretData::multiply_polynomials(&this_polynomial, &this_term); 233 | } 234 | (_, _) => return None, 235 | }; 236 | } 237 | if fxs.len() + 1 >= i { 238 | this_polynomial = SecretData::multiply_polynomials(&this_polynomial, &[fxs[i]]) 239 | } 240 | returned_coefficients = 241 | SecretData::add_polynomials(&returned_coefficients, &this_polynomial); 242 | } 243 | Some(returned_coefficients) 244 | } 245 | 246 | #[inline] 247 | fn gf256_add(a: u8, b: u8) -> u8 { 248 | a ^ b 249 | } 250 | 251 | #[inline] 252 | fn gf256_sub(a: u8, b: u8) -> u8 { 253 | SecretData::gf256_add(a, b) 254 | } 255 | 256 | #[inline] 257 | fn gf256_mul(a: u8, b: u8) -> u8 { 258 | if a == 0 || b == 0 { 259 | 0 260 | } else { 261 | GF256_EXP[((u16::from(GF256_LOG[a as usize]) + u16::from(GF256_LOG[b as usize])) % 255) 262 | as usize] 263 | } 264 | } 265 | 266 | #[inline] 267 | fn gf256_checked_div(a: u8, b: u8) -> Option { 268 | if a == 0 { 269 | Some(0) 270 | } else if b == 0 { 271 | None 272 | } else { 273 | let a_log = i16::from(GF256_LOG[a as usize]); 274 | let b_log = i16::from(GF256_LOG[b as usize]); 275 | 276 | let mut diff = a_log - b_log; 277 | 278 | if diff < 0 { 279 | diff += 255; 280 | } 281 | Some(GF256_EXP[(diff % 255) as usize]) 282 | } 283 | } 284 | 285 | #[inline] 286 | fn multiply_polynomials(a: &[u8], b: &[u8]) -> Vec { 287 | let mut resultterms: Vec = vec![]; 288 | 289 | let mut termpadding: Vec = vec![]; 290 | 291 | for bterm in b { 292 | let mut thisvalue = termpadding.clone(); 293 | for aterm in a { 294 | thisvalue.push(SecretData::gf256_mul(*aterm, *bterm)); 295 | } 296 | resultterms = SecretData::add_polynomials(&resultterms, &thisvalue); 297 | termpadding.push(0); 298 | } 299 | resultterms 300 | } 301 | 302 | #[inline] 303 | fn add_polynomials(a: &[u8], b: &[u8]) -> Vec { 304 | let mut a = a.to_owned(); 305 | let mut b = b.to_owned(); 306 | if a.len() < b.len() { 307 | let mut t = vec![0; b.len() - a.len()]; 308 | a.append(&mut t); 309 | } else if a.len() > b.len() { 310 | let mut t = vec![0; a.len() - b.len()]; 311 | b.append(&mut t); 312 | } 313 | let mut results: Vec = vec![]; 314 | 315 | for i in 0..a.len() { 316 | results.push(SecretData::gf256_add(a[i], b[i])); 317 | } 318 | results 319 | } 320 | } 321 | 322 | static GF256_EXP: [u8; 256] = [ 323 | 0x01, 0x03, 0x05, 0x0f, 0x11, 0x33, 0x55, 0xff, 0x1a, 0x2e, 0x72, 0x96, 0xa1, 0xf8, 0x13, 0x35, 324 | 0x5f, 0xe1, 0x38, 0x48, 0xd8, 0x73, 0x95, 0xa4, 0xf7, 0x02, 0x06, 0x0a, 0x1e, 0x22, 0x66, 0xaa, 325 | 0xe5, 0x34, 0x5c, 0xe4, 0x37, 0x59, 0xeb, 0x26, 0x6a, 0xbe, 0xd9, 0x70, 0x90, 0xab, 0xe6, 0x31, 326 | 0x53, 0xf5, 0x04, 0x0c, 0x14, 0x3c, 0x44, 0xcc, 0x4f, 0xd1, 0x68, 0xb8, 0xd3, 0x6e, 0xb2, 0xcd, 327 | 0x4c, 0xd4, 0x67, 0xa9, 0xe0, 0x3b, 0x4d, 0xd7, 0x62, 0xa6, 0xf1, 0x08, 0x18, 0x28, 0x78, 0x88, 328 | 0x83, 0x9e, 0xb9, 0xd0, 0x6b, 0xbd, 0xdc, 0x7f, 0x81, 0x98, 0xb3, 0xce, 0x49, 0xdb, 0x76, 0x9a, 329 | 0xb5, 0xc4, 0x57, 0xf9, 0x10, 0x30, 0x50, 0xf0, 0x0b, 0x1d, 0x27, 0x69, 0xbb, 0xd6, 0x61, 0xa3, 330 | 0xfe, 0x19, 0x2b, 0x7d, 0x87, 0x92, 0xad, 0xec, 0x2f, 0x71, 0x93, 0xae, 0xe9, 0x20, 0x60, 0xa0, 331 | 0xfb, 0x16, 0x3a, 0x4e, 0xd2, 0x6d, 0xb7, 0xc2, 0x5d, 0xe7, 0x32, 0x56, 0xfa, 0x15, 0x3f, 0x41, 332 | 0xc3, 0x5e, 0xe2, 0x3d, 0x47, 0xc9, 0x40, 0xc0, 0x5b, 0xed, 0x2c, 0x74, 0x9c, 0xbf, 0xda, 0x75, 333 | 0x9f, 0xba, 0xd5, 0x64, 0xac, 0xef, 0x2a, 0x7e, 0x82, 0x9d, 0xbc, 0xdf, 0x7a, 0x8e, 0x89, 0x80, 334 | 0x9b, 0xb6, 0xc1, 0x58, 0xe8, 0x23, 0x65, 0xaf, 0xea, 0x25, 0x6f, 0xb1, 0xc8, 0x43, 0xc5, 0x54, 335 | 0xfc, 0x1f, 0x21, 0x63, 0xa5, 0xf4, 0x07, 0x09, 0x1b, 0x2d, 0x77, 0x99, 0xb0, 0xcb, 0x46, 0xca, 336 | 0x45, 0xcf, 0x4a, 0xde, 0x79, 0x8b, 0x86, 0x91, 0xa8, 0xe3, 0x3e, 0x42, 0xc6, 0x51, 0xf3, 0x0e, 337 | 0x12, 0x36, 0x5a, 0xee, 0x29, 0x7b, 0x8d, 0x8c, 0x8f, 0x8a, 0x85, 0x94, 0xa7, 0xf2, 0x0d, 0x17, 338 | 0x39, 0x4b, 0xdd, 0x7c, 0x84, 0x97, 0xa2, 0xfd, 0x1c, 0x24, 0x6c, 0xb4, 0xc7, 0x52, 0xf6, 0x01, 339 | ]; 340 | 341 | static GF256_LOG: [u8; 256] = [ 342 | 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1a, 0xc6, 0x4b, 0xc7, 0x1b, 0x68, 0x33, 0xee, 0xdf, 0x03, 343 | 0x64, 0x04, 0xe0, 0x0e, 0x34, 0x8d, 0x81, 0xef, 0x4c, 0x71, 0x08, 0xc8, 0xf8, 0x69, 0x1c, 0xc1, 344 | 0x7d, 0xc2, 0x1d, 0xb5, 0xf9, 0xb9, 0x27, 0x6a, 0x4d, 0xe4, 0xa6, 0x72, 0x9a, 0xc9, 0x09, 0x78, 345 | 0x65, 0x2f, 0x8a, 0x05, 0x21, 0x0f, 0xe1, 0x24, 0x12, 0xf0, 0x82, 0x45, 0x35, 0x93, 0xda, 0x8e, 346 | 0x96, 0x8f, 0xdb, 0xbd, 0x36, 0xd0, 0xce, 0x94, 0x13, 0x5c, 0xd2, 0xf1, 0x40, 0x46, 0x83, 0x38, 347 | 0x66, 0xdd, 0xfd, 0x30, 0xbf, 0x06, 0x8b, 0x62, 0xb3, 0x25, 0xe2, 0x98, 0x22, 0x88, 0x91, 0x10, 348 | 0x7e, 0x6e, 0x48, 0xc3, 0xa3, 0xb6, 0x1e, 0x42, 0x3a, 0x6b, 0x28, 0x54, 0xfa, 0x85, 0x3d, 0xba, 349 | 0x2b, 0x79, 0x0a, 0x15, 0x9b, 0x9f, 0x5e, 0xca, 0x4e, 0xd4, 0xac, 0xe5, 0xf3, 0x73, 0xa7, 0x57, 350 | 0xaf, 0x58, 0xa8, 0x50, 0xf4, 0xea, 0xd6, 0x74, 0x4f, 0xae, 0xe9, 0xd5, 0xe7, 0xe6, 0xad, 0xe8, 351 | 0x2c, 0xd7, 0x75, 0x7a, 0xeb, 0x16, 0x0b, 0xf5, 0x59, 0xcb, 0x5f, 0xb0, 0x9c, 0xa9, 0x51, 0xa0, 352 | 0x7f, 0x0c, 0xf6, 0x6f, 0x17, 0xc4, 0x49, 0xec, 0xd8, 0x43, 0x1f, 0x2d, 0xa4, 0x76, 0x7b, 0xb7, 353 | 0xcc, 0xbb, 0x3e, 0x5a, 0xfb, 0x60, 0xb1, 0x86, 0x3b, 0x52, 0xa1, 0x6c, 0xaa, 0x55, 0x29, 0x9d, 354 | 0x97, 0xb2, 0x87, 0x90, 0x61, 0xbe, 0xdc, 0xfc, 0xbc, 0x95, 0xcf, 0xcd, 0x37, 0x3f, 0x5b, 0xd1, 355 | 0x53, 0x39, 0x84, 0x3c, 0x41, 0xa2, 0x6d, 0x47, 0x14, 0x2a, 0x9e, 0x5d, 0x56, 0xf2, 0xd3, 0xab, 356 | 0x44, 0x11, 0x92, 0xd9, 0x23, 0x20, 0x2e, 0x89, 0xb4, 0x7c, 0xb8, 0x26, 0x77, 0x99, 0xe3, 0xa5, 357 | 0x67, 0x4a, 0xed, 0xde, 0xc5, 0x31, 0xfe, 0x18, 0x0d, 0x63, 0x8c, 0x80, 0xc0, 0xf7, 0x70, 0x07, 358 | ]; 359 | --------------------------------------------------------------------------------