diff --git a/__crypto/secretshare/CHANGELOG.md b/__crypto/secretshare/CHANGELOG.md new file mode 100644 index 0000000..ad2deb5 --- /dev/null +++ b/__crypto/secretshare/CHANGELOG.md @@ -0,0 +1,8 @@ +2015-02-03: + +* I changed the CRC-24 checksum computation to include the coding parameter + K and the share number N so that these numbers are also protected. + If you have older shares generated with a previous version, you can still + decode the secret by simply removing the checksum part of the shares. +* The README now includes more information about the inner workings of + secretshare and also a note on "perfect secrecy". diff --git a/__crypto/secretshare/Cargo.lock b/__crypto/secretshare/Cargo.lock new file mode 100644 index 0000000..09d71b1 --- /dev/null +++ b/__crypto/secretshare/Cargo.lock @@ -0,0 +1,121 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "crc24" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "secretshare" +version = "0.1.6" +dependencies = [ + "crc24", + "getopts", + "rand 0.3.23", + "rustc-serialize", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/__crypto/secretshare/Cargo.toml b/__crypto/secretshare/Cargo.toml new file mode 100644 index 0000000..df84125 --- /dev/null +++ b/__crypto/secretshare/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "secretshare" +version = "0.1.6" +authors = ["Sebastian Gesemann "] +description = "This is an implementation of Shamir's secret sharing scheme." +license = "GPLv3" +readme = "README.md" + +[dependencies] +getopts = "0.2" +rustc-serialize = "0.3" +crc24 = "0.1" +rand = "0.3" diff --git a/__crypto/secretshare/README.md b/__crypto/secretshare/README.md new file mode 100644 index 0000000..5ba3576 --- /dev/null +++ b/__crypto/secretshare/README.md @@ -0,0 +1,114 @@ +> From: https://github.com/sellibitze/secretshare + +# secretshare + +This program is an implementation of +[Shamir's secret sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing). +A secret can be split into N shares in a way so that +a selectable number of shares K (with K ≤ N) is required +to reconstruct the secret again. + +**Warning**: I don't yet recommend the serious use of this tool. The +encoding of the shares might change in a newer version in which case +you would have trouble decoding secrets that have been shared using +an older version of the program. For now, this is experimental. + +# Example + +Passing a secret to secretshare for encoding: + +``` +$ echo My secret | ./secretshare -e2,5 +2-1-1YAYwmOHqZ69jA-v+mz +2-2-YJZQDGm22Y77Gw-IhSh +2-3-+G9ovW9SAnUynQ-Elwi +2-4-F7rAjX3UOa53KA-b2vm +2-5-j0P4PHsw4lW+rg-XyNl +``` + +The parameters following the `-e` option tell `secretshare` to create 5 shares of which 2 will be necessary for decoding. + +Decoding a subset of shares (one share per line) can be done like this: + +``` +$ echo -e "2-2-YJZQDGm22Y77Gw-IhSh \n 2-4-F7rAjX3UOa53KA-b2vm" | ./secretshare -d +My secret +``` + +# Building + +This project is Cargo-enabled. So, you should be able to build it with + +``` +$ cargo build --release +``` + +once you have made sure that `rustc` (the compiler) and `cargo` +(the build and dependency management tool) are installed. +Visit the [Rust homepage](http://www.rust-lang.org/) if you are +don't know where to get these tools. + +# I/O + +The secret data does not have to be text. `secretshare` treats it as +binary data. But, of course, you can feed it text as well. In the above +example the echo command terminated the string with a line feed which +is actually part of the secret and output as well after decoding. +Note that, while `secretshare` supports secrets of up to 64 KiB +it makes little sense to use such large secrets directly. In situations +where you want to share larger secrets, you would usually pick a random +password for encryption and use that password as secret for `secretshare`. + +The generated shares are lines of ASCII text. + +# Structure of the shares + +``` + 2-1-LiTyeXwEP71IUA-Qj6n + ^ ^ ^^^^^^^^^^^^^^ ^^^^ + K N D C +``` + +A share is built out of three or four parts separated with a minus: K-N-D-C. +The last part is optional. K is one of the encoding parameters that tell you +how many distinct +shares of a specific secret are necessary to be able to recover the +secret. The number N identifies the share (ranging from 1 to the number +of shares that have been created). The D part is a Base64 encoding of +a specific share's raw data. The optional part C is a Base64 encoding +of a CRC-24 checksum of the concatenation of K and N as bytes followed +by the share's raw data (before Base64 encoding). The same checksum +algorithm is used in the OpenPGP format for “ASCII amoring”. + +# A word on the secrecy + +Shamir's secret sharing is known to have the perfect secrecy property. +In the context of (K,N)-threshold schemes this means that if you have +less than K shares available, you have absolutely no information about +what the secret is except for its length. The checksums that are included +in the shares +also don't reveal anything about the secret. +They are just a simple integrity protection of the shares themselves. +In other words, given a share without checksum, we can derive a share +with a checksum. This obviously does not add any new information. + +# Galois field + +Shamir's secret sharing algorithm requires the use of polynomials over +a finite field. One easy way of constructing a finite field is to pick +a prime number p, use the integers 0, 1, 2, ..., p-1 as field elements +and simply use modular arithmetic (mod p) for the field operations. + +So, you *could* pick a prime like 257 to apply Shamir's algorithm +byte-wise. The downside of this is that the shares would consist of +sequences of values each between 0 and 256 *inclusive*. So, you would +need more than 8 bits to encode each of them. + +But there is another way. We are not restricted to so-called +prime fields. There are also non-prime fields where the number of +elements is a *power* of a prime, for example 2^8=256. It's just +a bit harder to explain how they are constructed. The finite +field I used is the same as the one you can find in the RAID 6 +implementation of the Linux kernel or the Anubis block cipher: +Gf(2^8) reduction polynomial is x^8 + x^4 + x^3 + x^2 + 1 or +alternatively 11D in hex. diff --git a/__crypto/secretshare/src/gf256.rs b/__crypto/secretshare/src/gf256.rs new file mode 100644 index 0000000..deab336 --- /dev/null +++ b/__crypto/secretshare/src/gf256.rs @@ -0,0 +1,143 @@ +//! This module provides the Gf256 type which is used to represent +//! elements of a finite field wich 256 elements. + +use std::num::Wrapping; +use std::ops::{Add, Div, Mul, Sub}; +use std::sync::Once; + +const POLY: u8 = 0x1D; // represents x^8 + x^4 + x^3 + x^2 + 1 + +/// replicates the least significant bit to every other bit +#[inline] +fn mask(bit: u8) -> u8 { + (Wrapping(0u8) - Wrapping(bit & 1)).0 +} + +/// multiplies a polynomial with x and returns the residual +/// of the polynomial division with POLY as divisor +#[inline] +fn xtimes(poly: u8) -> u8 { + (poly << 1) ^ (mask(poly >> 7) & POLY) +} + +/// Tables used for multiplication and division +struct Tables { + exp: [u8; 256], + log: [u8; 256], + inv: [u8; 256], +} + +static INIT: Once = Once::new(); +static mut TABLES: Tables = Tables { + exp: [0; 256], + log: [0; 256], + inv: [0; 256], +}; + +fn get_tables() -> &'static Tables { + INIT.call_once(|| { + // mutable access is fine because of synchronization via INIT + let tabs = unsafe { &mut TABLES }; + let mut tmp = 1; + for power in 0..255usize { + tabs.exp[power] = tmp; + tabs.log[tmp as usize] = power as u8; + tmp = xtimes(tmp); + } + tabs.exp[255] = 1; + for x in 1..256usize { + let l = tabs.log[x]; + let nl = if l == 0 { 0 } else { 255 - l }; + let i = tabs.exp[nl as usize]; + tabs.inv[x] = i; + } + }); + // We're guaranteed to have TABLES initialized by now + return unsafe { &TABLES }; +} + +/// Type for elements of a finite field with 256 elements +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Gf256 { + pub poly: u8, +} + +impl Gf256 { + /// returns the additive neutral element of the field + #[inline] + pub fn zero() -> Gf256 { + Gf256 { poly: 0 } + } + /// returns the multiplicative neutral element of the field + #[inline] + pub fn one() -> Gf256 { + Gf256 { poly: 1 } + } + #[inline] + pub fn from_byte(b: u8) -> Gf256 { + Gf256 { poly: b } + } + #[inline] + pub fn to_byte(&self) -> u8 { + self.poly + } + pub fn log(&self) -> Option { + if self.poly == 0 { + None + } else { + let tabs = get_tables(); + Some(tabs.log[self.poly as usize]) + } + } + pub fn exp(power: u8) -> Gf256 { + let tabs = get_tables(); + Gf256 { poly: tabs.exp[power as usize] } + } + /* + pub fn inv(&self) -> Option { + self.log().map(|l| Gf256::exp(255 - l)) + } + */ +} + +impl Add for Gf256 { + type Output = Gf256; + #[inline] + fn add(self, rhs: Gf256) -> Gf256 { + Gf256::from_byte(self.poly ^ rhs.poly) + } +} + +impl Sub for Gf256 { + type Output = Gf256; + #[inline] + fn sub(self, rhs: Gf256) -> Gf256 { + Gf256::from_byte(self.poly ^ rhs.poly) + } +} + +impl Mul for Gf256 { + type Output = Gf256; + fn mul(self, rhs: Gf256) -> Gf256 { + if let (Some(l1), Some(l2)) = (self.log(), rhs.log()) { + let tmp = ((l1 as u16) + (l2 as u16)) % 255; + Gf256::exp(tmp as u8) + } else { + Gf256 { poly: 0 } + } + } +} + +impl Div for Gf256 { + type Output = Gf256; + fn div(self, rhs: Gf256) -> Gf256 { + let l2 = rhs.log().expect("division by zero"); + if let Some(l1) = self.log() { + let tmp = ((l1 as u16) + 255 - (l2 as u16)) % 255; + Gf256::exp(tmp as u8) + } else { + Gf256 { poly: 0 } + } + } +} + diff --git a/__crypto/secretshare/src/main.rs b/__crypto/secretshare/src/main.rs new file mode 100644 index 0000000..dfe6451 --- /dev/null +++ b/__crypto/secretshare/src/main.rs @@ -0,0 +1,345 @@ +extern crate rustc_serialize as serialize; +extern crate getopts; +extern crate crc24; +extern crate rand; + +use std::convert; +use std::env; +use std::error; +use std::fmt; +use std::io; +use std::io::prelude::*; +use std::iter::repeat; +use std::num; + +use rand::{Rng, OsRng}; +use getopts::Options; +use serialize::base64::{self, FromBase64, ToBase64}; + +use gf256::Gf256; + +mod gf256; + +fn new_vec(n: usize, x: T) -> Vec { + repeat(x).take(n).collect() +} + +#[derive(Debug)] +pub struct Error { + descr: &'static str, + detail: Option, +} + +impl Error { + fn new(descr: &'static str, detail: Option) -> Error { + Error { descr: descr, detail: detail } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.detail { + None => write!(f, "{}", self.descr), + Some(ref detail) => write!(f, "{} ({})", self.descr, detail) + } + } +} + +impl error::Error for Error { + fn description(&self) -> &str { self.descr } + fn cause(&self) -> Option<&dyn error::Error> { None } +} + +impl convert::From for io::Error { + fn from(me: Error) -> io::Error { + io::Error::new(io::ErrorKind::Other, me) + } +} + +// a try!-like macro for Option expressions that takes +// a &'static str as error message as 2nd parameter +// and creates an Error out of it if necessary. +macro_rules! otry { + ($o:expr, $e:expr) => ( + match $o { + Some(thing_) => thing_, + None => return Err(convert::From::from(Error::new($e, None))) + } + ) +} + +/// maps a ParseIntError to an io::Error +fn pie2io(p: num::ParseIntError) -> io::Error { + convert::From::from( + Error::new("Integer parsing error", Some(p.to_string())) + ) +} + +fn other_io_err(descr: &'static str, detail: Option) -> io::Error { + convert::From::from( + Error::new(descr, detail) + ) +} + +/// evaluates a polynomial at x=1, 2, 3, ... n (inclusive) +fn encode(src: &[u8], n: u8, w: &mut W) -> io::Result<()> { + for raw_x in 1..((n as u16) + 1) { + let x = Gf256::from_byte(raw_x as u8); + let mut fac = Gf256::one(); + let mut acc = Gf256::zero(); + for &coeff in src.iter() { + acc = acc + fac * Gf256::from_byte(coeff); + fac = fac * x; + } + w.write(&[acc.to_byte()])?; + } + Ok(()) +} + +/// evaluates an interpolated polynomial at `raw_x` where +/// the polynomial is determined using Lagrangian interpolation +/// based on the given x/y coordinates `src`. +fn lagrange_interpolate(src: &[(u8, u8)], raw_x: u8) -> u8 { + let x = Gf256::from_byte(raw_x); + let mut sum = Gf256::zero(); + for (i, &(raw_xi, raw_yi)) in src.iter().enumerate() { + let xi = Gf256::from_byte(raw_xi); + let yi = Gf256::from_byte(raw_yi); + let mut lix = Gf256::one(); + for (j, &(raw_xj, _)) in src.iter().enumerate() { + if i != j { + let xj = Gf256::from_byte(raw_xj); + let delta = xi - xj; + assert!(delta.poly != 0, "Duplicate shares"); + lix = lix * (x - xj) / delta; + } + } + sum = sum + lix * yi; + } + sum.to_byte() +} + +fn secret_share(src: &[u8], k: u8, n: u8) -> io::Result>> { + let mut result = Vec::with_capacity(n as usize); + for _ in 0..(n as usize) { + result.push(new_vec(src.len(), 0u8)); + } + let mut col_in = new_vec(k as usize, 0u8); + let mut col_out = Vec::with_capacity(n as usize); + let mut osrng = OsRng::new()?; + for (c, &s) in src.iter().enumerate() { + col_in[0] = s; + osrng.fill_bytes(&mut col_in[1..]); + col_out.clear(); + encode(&*col_in, n, &mut col_out)?; + for (&y, share) in col_out.iter().zip(result.iter_mut()) { + share[c] = y; + } + } + Ok(result) +} + +enum Action { + Encode(u8, u8), + // k and n parameter + Decode, +} + +fn parse_k_n(s: &str) -> io::Result<(u8, u8)> { + let mut iter = s.split(','); + let msg = "K and N have to be separated with a comma"; + let s1 = otry!(iter.next(), msg).trim(); + let s2 = otry!(iter.next(), msg).trim(); + let k = s1.parse().map_err(pie2io)?; + let n = s2.parse().map_err(pie2io)?; + Ok((k, n)) +} + +/// computes a CRC-24 hash over the concatenated coding parameters k, n +/// and the raw share data +fn crc24_as_bytes(k: u8, n: u8, octets: &[u8]) -> [u8; 3] { + use std::hash::Hasher; + + let mut h = crc24::Crc24Hasher::new(); + h.write(&[k, n]); + h.write(octets); + let v = h.finish(); + + [((v >> 16) & 0xFF) as u8, + ((v >> 8) & 0xFF) as u8, + (v & 0xFF) as u8] +} + +fn perform_encode(k: u8, n: u8, with_checksums: bool) -> io::Result<()> { + let secret = { + let limit: usize = 0x10000; + let stdin = io::stdin(); + let mut locked = stdin.lock(); + let mut tmp: Vec = Vec::new(); + locked.by_ref().take(limit as u64).read_to_end(&mut tmp)?; + if tmp.len() == limit { + let mut dummy = [0u8]; + if locked.read(&mut dummy)? > 0 { + return Err(other_io_err("Secret too large", + Some(format!("My limit is at {} bytes.", limit)))); + } + } + tmp + }; + let shares = secret_share(&*secret, k, n)?; + let config = base64::Config { + pad: false, + ..base64::STANDARD + }; + for (index, share) in shares.iter().enumerate() { + let salad = share.to_base64(config); + if with_checksums { + let crc_bytes = crc24_as_bytes(k, (index + 1) as u8, &**share); + println!("{}-{}-{}-{}", k, index + 1, salad, crc_bytes.to_base64(config)); + } else { + println!("{}-{}-{}", k, index + 1, salad); + } + } + Ok(()) +} + +/// reads shares from stdin and returns Ok(k, shares) on success +/// where shares is a Vec<(u8, Vec)> representing x-coordinates +/// and share data. +fn read_shares() -> io::Result<(u8, Vec<(u8, Vec)>)> { + let stdin = io::stdin(); + let stdin = io::BufReader::new(stdin.lock()); + let mut opt_k_l: Option<(u8, usize)> = None; + let mut counter = 0u8; + let mut shares: Vec<(u8, Vec)> = Vec::new(); + for line in stdin.lines() { + let line = line?; + let parts: Vec<_> = line.trim().split('-').collect(); + if parts.len() < 3 || parts.len() > 4 { + return Err(other_io_err("Share parse error: Expected 3 or 4 \ + parts searated by a minus sign", None)); + } + let (k, n, p3, opt_p4) = { + let mut iter = parts.into_iter(); + let k = iter.next().unwrap().parse::().map_err(pie2io)?; + let n = iter.next().unwrap().parse::().map_err(pie2io)?; + let p3 = iter.next().unwrap(); + let opt_p4 = iter.next(); + (k, n, p3, opt_p4) + }; + if k < 1 || n < 1 { + return Err(other_io_err("Share parse error: Illegal K,N parameters", None)); + } + let data = p3.from_base64().map_err(|_| other_io_err( + "Share parse error: Base64 decoding of data block failed", None))?; + if let Some(check) = opt_p4 { + if check.len() != 4 { + return Err(other_io_err("Share parse error: Checksum part is \ + expected to be four characters", None)); + } + let crc_bytes = check.from_base64().map_err(|_| other_io_err( + "Share parse error: Base64 decoding of checksum failed", None))?; + let mychksum = crc24_as_bytes(k, n, &*data); + if crc_bytes != mychksum { + return Err(other_io_err("Share parse error: Checksum mismatch", None)); + } + } + if let Some((ck, cl)) = opt_k_l { + if ck != k || cl != data.len() { + return Err(other_io_err("Incompatible shares", None)); + } + } else { + opt_k_l = Some((k, data.len())); + } + if shares.iter().all(|s| s.0 != n) { + shares.push((n, data)); + counter += 1; + if counter == k { + return Ok((k, shares)); + } + } + } + Err(other_io_err("Not enough shares provided!", None)) +} + +fn perform_decode() -> io::Result<()> { + let (k, shares) = read_shares()?; + assert!(!shares.is_empty()); + let slen = shares[0].1.len(); + let mut col_in = Vec::with_capacity(k as usize); + let mut secret = Vec::with_capacity(slen); + for byteindex in 0..slen { + col_in.clear(); + for s in shares.iter().take(k as usize) { + col_in.push((s.0, s.1[byteindex])); + } + secret.push(lagrange_interpolate(&*col_in, 0u8)); + } + let mut out = io::stdout(); + out.write_all(&*secret)?; + out.flush() +} + +fn main() { + let mut stderr = io::stderr(); + let args: Vec = env::args().collect(); + + let mut opts = Options::new(); + opts.optflag("h", "help", "print this help text"); + opts.optflag("d", "decode", "for decoding"); + opts.optopt("e", "encode", "for encoding, K is the required number of \ + shares for decoding, N is the number of shares \ + to generate. 1 <= K <= N <= 255", "K,N"); + let opt_matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + drop(writeln!(&mut stderr, "Error: {}", f)); + // env::set_exit_status(1); // FIXME: unstable feature + return; + } + }; + + if args.len() < 2 || opt_matches.opt_present("h") { + println!( + "The program secretshare is an implementation of Shamir's secret sharing scheme.\n\ + It is applied byte-wise within a finite field for arbitrarily long secrets.\n"); + println!("{}", opts.usage("Usage: secretshare [options]")); + println!("Input is read from STDIN and output is written to STDOUT."); + return; + } + + let action: Result<_, _> = + match (opt_matches.opt_present("e"), opt_matches.opt_present("d")) { + (false, false) => Err("Nothing to do! Use -e or -d"), + (true, true) => Err("Use either -e or -d and not both"), + (false, true) => Ok(Action::Decode), + (true, false) => { + if let Some(param) = opt_matches.opt_str("e") { + if let Ok((k, n)) = parse_k_n(&*param) { + if 0 < k && k <= n { + Ok(Action::Encode(k, n)) + } else { + Err("Invalid encoding parameters K,N") + } + } else { + Err("Could not parse K,N parameters") + } + } else { + Err("No parameter for -e or -d provided") + } + } + }; + + let result = + match action { + Ok(Action::Encode(k, n)) => perform_encode(k, n, true), + Ok(Action::Decode) => perform_decode(), + Err(e) => Err(other_io_err(e, None)) + }; + + if let Err(e) = result { + drop(writeln!(&mut stderr, "{}", e)); + // env::set_exit_status(1); // FIXME: unstable feature + } +} +