From ee15145662434a1d0aa97a911cf4f331c0c55abe Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 24 Aug 2023 20:25:42 -0500 Subject: [PATCH] keyfork-frame: initial commit --- Cargo.lock | 193 +++++++++++ Cargo.toml | 3 +- keyfork-frame/Cargo.toml | 14 + keyfork-frame/src/lib.rs | 157 +++++++++ ...eyfork_frame__tests__stable_interface.snap | 299 ++++++++++++++++++ 5 files changed, 665 insertions(+), 1 deletion(-) create mode 100644 keyfork-frame/Cargo.toml create mode 100644 keyfork-frame/src/lib.rs create mode 100644 keyfork-frame/src/snapshots/keyfork_frame__tests__stable_interface.snap diff --git a/Cargo.lock b/Cargo.lock index 2dc3d23..a4c416b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys", +] + [[package]] name = "cpufeatures" version = "0.2.9" @@ -63,6 +75,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "generic-array" version = "0.14.7" @@ -79,12 +97,35 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "insta" +version = "1.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", + "yaml-rust", +] + [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "keyfork-frame" +version = "0.1.0" +dependencies = [ + "hex", + "insta", + "sha2", + "thiserror", +] + [[package]] name = "keyfork-mnemonic-generate" version = "0.1.0" @@ -102,12 +143,46 @@ dependencies = [ "sha2", ] +[[package]] +name = "keyforkd" +version = "0.1.0" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + [[package]] name = "ryu" version = "1.0.15" @@ -142,6 +217,43 @@ dependencies = [ "digest", ] +[[package]] +name = "similar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -163,6 +275,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -177,3 +295,78 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 190ed50..428eef6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ members = [ "keyfork-mnemonic-generate", - "keyfork-mnemonic-util" + "keyfork-mnemonic-util", + "keyfork-frame" ] diff --git a/keyfork-frame/Cargo.toml b/keyfork-frame/Cargo.toml new file mode 100644 index 0000000..d09136f --- /dev/null +++ b/keyfork-frame/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "keyfork-frame" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hex = "0.4.3" +sha2 = "0.10.7" +thiserror = "1.0.47" + +[dev-dependencies] +insta = "1.31.0" diff --git a/keyfork-frame/src/lib.rs b/keyfork-frame/src/lib.rs new file mode 100644 index 0000000..22b403b --- /dev/null +++ b/keyfork-frame/src/lib.rs @@ -0,0 +1,157 @@ +//! Utility functions to quickly encode and decode `&[u8]` to and from framed messages. +//! +//! Framed messages consist of the following items: +//! +//! ```txt +//! | len: u32 of data.len() | data: binary data | +//! ``` +//! +//! The data stored after the length consists of the following items: +//! +//! ```txt +//! | checksum: [u8; 32] sha256 hash of `raw_data` | raw_data: &[u8] | +//! ``` + + +use sha2::{Digest, Sha256}; + +#[derive(Debug, Clone, thiserror::Error)] +pub enum DecodeError { + /// There were not enough bytes to determine the length of the data slice. + #[error("Invalid length: {0}")] + InvalidLength(std::array::TryFromSliceError), + + /// There were not enough bytes to read a checksum of the data slice. + #[error("Invalid checksum: {0} bytes")] + InvalidChecksum(std::array::TryFromSliceError), + + /// There were not enough bytes to read the rest of the data. + #[error("Incorrect length of internal data: {0}, expected at least: {1}")] + IncorrectLength(usize, u32), + + /// The provided checksum of the data did not match the locally-generated checksum. + #[error("Checksum did not match: Their {0} != Our {1}")] + BadChecksum(String, String), +} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum EncodeError { + /// The given input was larger than could be encoded by this protocol. + #[error("Input too large to encode: {0}")] + InputTooLarge(usize), +} + +const LEN_SIZE: usize = std::mem::size_of::(); + +fn hash(data: &[u8]) -> Vec { + let mut hashobj = Sha256::new(); + hashobj.update(data); + hashobj.finalize().to_vec() +} + +/// Encode a given `&[u8]` to a framed message. +/// +/// # Errors +/// An error may be returned if the given `data` is more than [`u32::MAX`] bytes. This is a +/// constraint on a protocol level. +pub fn try_encode(data: &[u8]) -> Result, EncodeError> { + let hash = hash(data); + let content = hash.iter().chain(data.iter()).copied().collect::>(); + let mut result = (u32::try_from(content.len()) + .map_err(|_| EncodeError::InputTooLarge(content.len()))?) + .to_be_bytes() + .to_vec(); + result.extend(content); + Ok(result) +} + +/// Decode a framed message into a `Vec`. +/// +/// # Errors +/// An error may be returned if: +/// * The given `data` does not contain enough data to parse a length, +/// * The given `data` does not contain the given length's worth of data, +/// * The given `data` has a checksum that does not match what we build locally. +pub fn try_decode(data: &[u8]) -> Result, DecodeError> { + // check length and advance data pointer beyond length check + let len_bytes: [u8; LEN_SIZE] = data[..LEN_SIZE] + .try_into() + .map_err(DecodeError::InvalidLength)?; + let len = u32::from_be_bytes(len_bytes); + if len as usize + LEN_SIZE > data.len() { + return Err(DecodeError::IncorrectLength(data.len() - LEN_SIZE, len)); + } + let data = &data[LEN_SIZE..]; + + let checksum: &[u8; 32] = &data[..32] + .try_into() + .map_err(DecodeError::InvalidChecksum)?; + + let content = &data[32..]; + let our_checksum = hash(content); + if our_checksum != checksum { + return Err(DecodeError::BadChecksum( + hex::encode(checksum), + hex::encode(our_checksum), + )); + } + + Ok(content.to_vec()) +} + +#[cfg(test)] +mod tests { + use super::{try_encode, try_decode, DecodeError}; + + #[test] + fn stable_interface() { + let data = (0..255).collect::>(); + insta::assert_debug_snapshot!(try_encode(&data[..])); + } + + #[test] + fn equivalency() -> Result<(), DecodeError> { + let data = (0..255).collect::>(); + assert_eq!(try_decode(&try_encode(&data[..]).unwrap())?, data); + Ok(()) + } + + #[test] + fn allows_extra_data() -> Result<(), DecodeError> { + let data = (0..255).collect::>(); + let mut encoded = try_encode(&data[..]).unwrap(); + // Throw on some extra data + encoded.extend(0..255); + assert_eq!(try_decode(&try_encode(&data[..]).unwrap())?, data); + Ok(()) + } + + #[test] + fn error_on_smaller_data() { + // Data sliced by 1 byte + let data = (0..255).collect::>(); + let encoded = try_encode(&data[..]).unwrap(); + let error = try_decode(&encoded[..data.len() - 1]); + assert!(error.is_err()); + + // Data includes length and checksum + let error = try_decode(&encoded[..super::LEN_SIZE + 256 / 8]); + assert!(error.is_err()); + + // Data only includes length + let data = 12u32.to_be_bytes(); + let error = try_decode(&data[..]); + assert!(error.is_err()); + } + + #[test] + fn error_on_invalid_checksum() { + let data = (0..255).collect::>(); + let mut encoded = try_encode(&data[..]).unwrap(); + assert_ne!(encoded[super::LEN_SIZE + 1], 0); + encoded[super::LEN_SIZE + 1] = 0; + + let error = try_decode(&data[..]); + assert!(error.is_err()); + } +} diff --git a/keyfork-frame/src/snapshots/keyfork_frame__tests__stable_interface.snap b/keyfork-frame/src/snapshots/keyfork_frame__tests__stable_interface.snap new file mode 100644 index 0000000..de5b160 --- /dev/null +++ b/keyfork-frame/src/snapshots/keyfork_frame__tests__stable_interface.snap @@ -0,0 +1,299 @@ +--- +source: keyfork-frame/src/lib.rs +expression: "try_encode(&data[..])" +--- +Ok( + [ + 0, + 0, + 1, + 31, + 63, + 133, + 145, + 17, + 44, + 107, + 190, + 92, + 150, + 57, + 101, + 149, + 78, + 41, + 49, + 8, + 183, + 32, + 142, + 210, + 175, + 137, + 62, + 80, + 13, + 133, + 147, + 104, + 198, + 84, + 234, + 190, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + ], +)