From 276943611723fd3789eb22a8a21186bf681566ad Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Fri, 22 May 2020 11:48:11 +0100 Subject: [PATCH] Slatepack - Pt 2 - Encryption (#411) * recreate PR from #400 * first tests with slate encryption * simplify slatepack model to contain encryption header in payload, and add de/ser tests * update tests and confirm slatepack encryption working * remove recipient list, add version check warning --- Cargo.lock | 542 +++++++++++++++--- api/src/owner.rs | 11 + controller/Cargo.toml | 3 + controller/tests/no_change.rs | 1 - controller/tests/slatepack.rs | 526 +++++++++++++++++ impls/Cargo.toml | 1 + impls/src/adapters/file.rs | 2 +- impls/src/adapters/mod.rs | 2 + impls/src/adapters/slatepack.rs | 169 ++++++ impls/src/client_utils/mod.rs | 1 - impls/src/lib.rs | 5 +- libwallet/Cargo.toml | 10 + libwallet/src/api_impl/owner.rs | 32 +- libwallet/src/error.rs | 28 + libwallet/src/lib.rs | 2 + libwallet/src/slate_versions/ser.rs | 119 +++- libwallet/src/slatepack/armor.rs | 187 ++++++ libwallet/src/slatepack/mod.rs | 19 + libwallet/src/slatepack/types.rs | 491 ++++++++++++++++ .../src/client_utils => util/src}/byte_ser.rs | 7 +- util/src/lib.rs | 3 + 21 files changed, 2064 insertions(+), 97 deletions(-) create mode 100644 controller/tests/slatepack.rs create mode 100644 impls/src/adapters/slatepack.rs create mode 100644 libwallet/src/slatepack/armor.rs create mode 100644 libwallet/src/slatepack/mod.rs create mode 100644 libwallet/src/slatepack/types.rs rename {impls/src/client_utils => util/src}/byte_ser.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index fd861551..78520dc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "addr2line" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456d75cbb82da1ad150c8a9d97285ffcd21c9931dcb11e995903e7d75141b38b" +checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" dependencies = [ "gimli", ] @@ -15,6 +15,102 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +[[package]] +name = "aead" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf01b9b56e767bb57b94ebf91a58b338002963785cdd7013e21c0d4679471e4" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "aes" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" +dependencies = [ + "aes-soft", + "aesni", + "block-cipher-trait", +] + +[[package]] +name = "aes-ctr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee" +dependencies = [ + "aes-soft", + "aesni", + "ctr", + "stream-cipher", +] + +[[package]] +name = "aes-soft" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" +dependencies = [ + "block-cipher-trait", + "byteorder", + "opaque-debug", +] + +[[package]] +name = "aesni" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" +dependencies = [ + "block-cipher-trait", + "opaque-debug", + "stream-cipher", +] + +[[package]] +name = "age" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64701e2aa240aa36ad430f8e1b82cd3f226ea0b93185f6fbb9ada0ab12a9a92e" +dependencies = [ + "aes", + "aes-ctr", + "age-core", + "base64 0.11.0", + "bcrypt-pbkdf", + "bech32", + "block-cipher-trait", + "block-modes", + "c2-chacha", + "chacha20poly1305", + "cookie-factory", + "curve25519-dalek", + "hkdf", + "hmac 0.7.1", + "nom 5.1.1", + "radix64", + "rand 0.7.3", + "scrypt", + "secrecy", + "sha2 0.8.1", + "subtle 2.2.2", + "x25519-dalek", + "zeroize 1.1.0", +] + +[[package]] +name = "age-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edc5c56a290116d446475265057ff5bc44490f681ee15cb27111ed47d4afe78" +dependencies = [ + "base64 0.11.0", + "cookie-factory", + "nom 5.1.1", +] + [[package]] name = "aho-corasick" version = "0.7.10" @@ -99,9 +195,9 @@ version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] @@ -165,6 +261,26 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "bcrypt-pbkdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "108e67d628901912875b038f27df25091799ba3b2fb1254a00fde6efb4318313" +dependencies = [ + "blowfish", + "byteorder", + "crypto-mac 0.7.0", + "pbkdf2 0.3.0", + "sha2 0.8.1", + "zeroize 1.1.0", +] + +[[package]] +name = "bech32" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" + [[package]] name = "bindgen" version = "0.52.0" @@ -181,8 +297,8 @@ dependencies = [ "lazycell", "log", "peeking_take_while", - "proc-macro2 1.0.12", - "quote 1.0.5", + "proc-macro2 1.0.13", + "quote 1.0.6", "regex", "rustc-hash", "shlex", @@ -256,6 +372,25 @@ dependencies = [ "generic-array 0.12.3", ] +[[package]] +name = "block-cipher-trait" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "block-modes" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa8410095e39fdb732909fb5730a48d5bd7c2e3cd76bd1b07b3dbea130c529" +dependencies = [ + "block-cipher-trait", + "block-padding", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -265,6 +400,23 @@ dependencies = [ "byte-tools 0.3.1", ] +[[package]] +name = "blowfish" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" +dependencies = [ + "block-cipher-trait", + "byteorder", + "opaque-debug", +] + +[[package]] +name = "bs58" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" + [[package]] name = "built" version = "0.3.2" @@ -318,10 +470,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" [[package]] -name = "cc" -version = "1.0.52" +name = "c2-chacha" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" +checksum = "217192c943108d8b13bac38a1d51df9ce8a407a3f5a71ab633980665e68fbd9a" +dependencies = [ + "byteorder", + "ppv-lite86", + "stream-cipher", +] + +[[package]] +name = "cc" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" dependencies = [ "jobserver", ] @@ -332,7 +495,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" dependencies = [ - "nom", + "nom 4.2.3", ] [[package]] @@ -341,6 +504,18 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chacha20poly1305" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48901293601228db2131606f741db33561f7576b5d19c99cd66222380a7dc863" +dependencies = [ + "aead", + "poly1305", + "stream-cipher", + "zeroize 1.1.0", +] + [[package]] name = "chrono" version = "0.4.11" @@ -404,6 +579,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "cookie-factory" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f21b581d2f0cb891554812435667bb9610d74feb1a4c6415bf09c28ff0381d" + [[package]] name = "core-foundation" version = "0.6.4" @@ -524,6 +705,16 @@ dependencies = [ "generic-array 0.9.0", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.3", + "subtle 1.0.0", +] + [[package]] name = "csv" version = "0.15.0" @@ -544,6 +735,16 @@ dependencies = [ "sct", ] +[[package]] +name = "ctr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022cd691704491df67d25d006fe8eca083098253c4d43516c2206479c58c6736" +dependencies = [ + "block-cipher-trait", + "stream-cipher", +] + [[package]] name = "curve25519-dalek" version = "2.0.0" @@ -553,7 +754,7 @@ dependencies = [ "byteorder", "digest 0.8.1", "rand_core 0.5.1", - "subtle", + "subtle 2.2.2", "zeroize 1.1.0", ] @@ -744,9 +945,9 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", "synstructure 0.12.3", ] @@ -770,9 +971,9 @@ dependencies = [ [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" @@ -872,9 +1073,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] @@ -975,7 +1176,7 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "grin_api" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "bytes 0.5.4", "easy-jsonrpc-mw", @@ -1001,14 +1202,14 @@ dependencies = [ "serde_derive", "serde_json", "tokio", - "tokio-rustls 0.13.0", + "tokio-rustls 0.13.1", "url 1.7.2", ] [[package]] name = "grin_chain" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "bit-vec", "bitflags 1.2.1", @@ -1031,7 +1232,7 @@ dependencies = [ [[package]] name = "grin_core" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "blake2-rfc", "byteorder", @@ -1057,16 +1258,16 @@ dependencies = [ [[package]] name = "grin_keychain" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "blake2-rfc", "byteorder", "digest 0.7.6", "grin_util", - "hmac", + "hmac 0.6.3", "lazy_static", "log", - "pbkdf2", + "pbkdf2 0.2.3", "rand 0.6.5", "ripemd160", "serde", @@ -1079,7 +1280,7 @@ dependencies = [ [[package]] name = "grin_p2p" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "bitflags 1.2.1", "chrono", @@ -1100,7 +1301,7 @@ dependencies = [ [[package]] name = "grin_pool" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "blake2-rfc", "chrono", @@ -1134,7 +1335,7 @@ dependencies = [ [[package]] name = "grin_store" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "byteorder", "croaring-mw", @@ -1154,7 +1355,7 @@ dependencies = [ [[package]] name = "grin_util" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#2c621115612013a68de7bd973a42ec88ae5f44fc" +source = "git+https://github.com/mimblewimble/grin#93f5de3d2957f6f30dde8ae8f588efa0754c5ca3" dependencies = [ "backtrace", "base64 0.9.3", @@ -1264,6 +1465,7 @@ dependencies = [ "tokio", "url 1.7.2", "uuid", + "x25519-dalek", ] [[package]] @@ -1298,16 +1500,20 @@ dependencies = [ "timer", "tokio", "uuid", + "x25519-dalek", ] [[package]] name = "grin_wallet_libwallet" version = "4.0.0-alpha.1" dependencies = [ + "age", "base64 0.9.3", "blake2-rfc", + "bs58", "byteorder", "chrono", + "curve25519-dalek", "ed25519-dalek", "failure", "failure_derive", @@ -1316,12 +1522,16 @@ dependencies = [ "lazy_static", "log", "rand 0.5.6", + "regex", + "secrecy", "serde", "serde_derive", "serde_json", + "sha2 0.8.1", "strum", "strum_macros", "uuid", + "x25519-dalek", ] [[package]] @@ -1373,23 +1583,43 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" dependencies = [ "libc", ] +[[package]] +name = "hkdf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" +dependencies = [ + "digest 0.8.1", + "hmac 0.7.1", +] + [[package]] name = "hmac" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a" dependencies = [ - "crypto-mac", + "crypto-mac 0.6.2", "digest 0.7.6", ] +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac 0.7.0", + "digest 0.8.1", +] + [[package]] name = "http" version = "0.1.21" @@ -1474,7 +1704,7 @@ dependencies = [ "rustls 0.16.0", "rustls-native-certs 0.1.0", "tokio", - "tokio-rustls 0.12.2", + "tokio-rustls 0.12.3", "webpki", ] @@ -1492,7 +1722,7 @@ dependencies = [ "rustls 0.17.0", "rustls-native-certs 0.3.0", "tokio", - "tokio-rustls 0.13.0", + "tokio-rustls 0.13.1", "webpki", ] @@ -1641,6 +1871,19 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +[[package]] +name = "lexical-core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" +dependencies = [ + "arrayvec 0.4.12", + "cfg-if", + "rustc_version 0.2.3", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.70" @@ -1984,7 +2227,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" dependencies = [ "memchr 2.3.3", - "version_check", + "version_check 0.1.5", +] + +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +dependencies = [ + "lexical-core", + "memchr 2.3.3", + "version_check 0.9.1", ] [[package]] @@ -2238,13 +2492,23 @@ dependencies = [ "base64 0.9.3", "byteorder", "constant_time_eq", - "crypto-mac", + "crypto-mac 0.6.2", "generic-array 0.9.0", - "hmac", + "hmac 0.6.3", "rand 0.5.6", "sha2 0.7.1", ] +[[package]] +name = "pbkdf2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" +dependencies = [ + "byteorder", + "crypto-mac 0.7.0", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -2303,22 +2567,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d480cb4e89522ccda96d0eed9af94180b7a5f93fb28f66e1fd7d68431663d1" +checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82996f11efccb19b685b14b5df818de31c1edcee3daa256ab5775dd98e72feb" +checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] @@ -2346,10 +2610,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" [[package]] -name = "ppv-lite86" -version = "0.2.6" +name = "poly1305" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +checksum = "b5829f50f48e9ddb79f3f7c3097029d0caee30f8286accb241416df603b080b8" +dependencies = [ + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "pretty_assertions" @@ -2398,9 +2671,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319" +checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" dependencies = [ "unicode-xid 0.2.0", ] @@ -2422,11 +2695,21 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42934bc9c8ab0d3b273a16d8551c8f0fcff46be73276ca083ec2414c15c4ba5e" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ - "proc-macro2 1.0.12", + "proc-macro2 1.0.13", +] + +[[package]] +name = "radix64" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999718fa65c3be3a74f3f6dae5a98526ff436ea58a82a574f0de89eecd342bee" +dependencies = [ + "arrayref", + "cfg-if", ] [[package]] @@ -2876,6 +3159,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scrypt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656c79d0e90d0ab28ac86bf3c3d10bfbbac91450d3f190113b4e76d9fec3cfdd" +dependencies = [ + "byte-tools 0.3.1", + "byteorder", + "hmac 0.7.1", + "pbkdf2 0.3.0", + "sha2 0.8.1", +] + [[package]] name = "sct" version = "0.6.0" @@ -2886,6 +3182,15 @@ dependencies = [ "untrusted", ] +[[package]] +name = "secrecy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182278ed645df3477a9c27bfee0621c621aa16f6972635f7f795dae3d81070f" +dependencies = [ + "zeroize 1.1.0", +] + [[package]] name = "security-framework" version = "0.3.4" @@ -2976,9 +3281,9 @@ version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] @@ -3117,6 +3422,21 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "static_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" + +[[package]] +name = "stream-cipher" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" +dependencies = [ + "generic-array 0.12.3", +] + [[package]] name = "strsim" version = "0.8.0" @@ -3141,6 +3461,12 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.2.2" @@ -3166,12 +3492,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4696caa4048ac7ce2bcd2e484b3cef88c1004e41b8e945a277e2c25dc0b72060" +checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", + "proc-macro2 1.0.13", + "quote 1.0.6", "unicode-xid 0.2.0", ] @@ -3193,9 +3519,9 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", "unicode-xid 0.2.0", ] @@ -3253,7 +3579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e51065bafd2abe106b6036483b69d1741f4a1ec56ce8a2378de341637de689e" dependencies = [ "fnv", - "nom", + "nom 4.2.3", "phf", "phf_codegen", ] @@ -3269,22 +3595,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467e5ff447618a916519a4e0d62772ab14f434897f3d63f05d8700ef1e9b22c1" +checksum = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63c1091225b9834089b429bc4a2e01223470e3183e891582909e9d1c4cb55d9" +checksum = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] @@ -3366,16 +3692,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "tokio-rustls" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141afec0978abae6573065a48882c6bae44c5cc61db9b511ac4abf6a09bfd9cc" +checksum = "3068d891551949b37681724d6b73666787cc63fa8e255c812a41d2513aff9775" dependencies = [ "futures-core", "rustls 0.16.0", @@ -3385,9 +3711,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4adb8b3e5f86b707f1b54e7c15b6de52617a823608ccda98a15d3a24222f265a" +checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" dependencies = [ "futures-core", "rustls 0.17.0", @@ -3512,6 +3838,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "universal-hash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01" +dependencies = [ + "generic-array 0.12.3", + "subtle 2.2.2", +] + [[package]] name = "unsafe-any" version = "0.4.2" @@ -3583,6 +3919,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + [[package]] name = "void" version = "1.0.2" @@ -3635,9 +3977,9 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", "wasm-bindgen-shared", ] @@ -3647,7 +3989,7 @@ version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" dependencies = [ - "quote 1.0.5", + "quote 1.0.6", "wasm-bindgen-macro-support", ] @@ -3657,9 +3999,9 @@ version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" dependencies = [ - "proc-macro2 1.0.12", - "quote 1.0.5", - "syn 1.0.21", + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3752,6 +4094,17 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "x25519-dalek" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" +dependencies = [ + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize 1.1.0", +] + [[package]] name = "yaml-rust" version = "0.3.5" @@ -3773,7 +4126,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" dependencies = [ - "zeroize_derive", + "zeroize_derive 0.9.3", ] [[package]] @@ -3781,6 +4134,9 @@ name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +dependencies = [ + "zeroize_derive 1.0.0", +] [[package]] name = "zeroize_derive" @@ -3794,6 +4150,18 @@ dependencies = [ "synstructure 0.10.2", ] +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +dependencies = [ + "proc-macro2 1.0.13", + "quote 1.0.6", + "syn 1.0.22", + "synstructure 0.12.3", +] + [[package]] name = "zip" version = "0.5.5" diff --git a/api/src/owner.rs b/api/src/owner.rs index 1cb7a532..2e83a83d 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -16,6 +16,7 @@ use chrono::prelude::*; use ed25519_dalek::PublicKey as DalekPublicKey; +use ed25519_dalek::SecretKey as DalekSecretKey; use uuid::Uuid; use crate::config::{TorConfig, WalletConfig}; @@ -1913,6 +1914,16 @@ where owner::get_public_proof_address(self.wallet_inst.clone(), keychain_mask, derivation_index) } + // TODO: Doc + /// get public proof a + pub fn get_secret_key( + &self, + keychain_mask: Option<&SecretKey>, + derivation_index: u32, + ) -> Result { + owner::get_secret_key(self.wallet_inst.clone(), keychain_mask, derivation_index) + } + /// Helper function to convert an Onion v3 address to a payment proof address (essentially /// exctacting and verifying the public key) /// diff --git a/controller/Cargo.toml b/controller/Cargo.toml index b0fd2d87..cb5624ca 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -36,3 +36,6 @@ grin_wallet_api = { path = "../api", version = "4.0.0-alpha.1" } grin_wallet_impls = { path = "../impls", version = "4.0.0-alpha.1" } grin_wallet_libwallet = { path = "../libwallet", version = "4.0.0-alpha.1" } grin_wallet_config = { path = "../config", version = "4.0.0-alpha.1" } + +[dev-dependencies] +x25519-dalek = "0.6" diff --git a/controller/tests/no_change.rs b/controller/tests/no_change.rs index bd141289..3283791e 100644 --- a/controller/tests/no_change.rs +++ b/controller/tests/no_change.rs @@ -93,7 +93,6 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { api.tx_lock_outputs(m, &slate)?; slate = api.finalize_tx(m, &slate)?; println!("Posted Slate: {:?}", slate); - println!("Posted TX: {}", slate); stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); api.post_tx(m, &slate, false)?; Ok(()) diff --git a/controller/tests/slatepack.rs b/controller/tests/slatepack.rs new file mode 100644 index 00000000..5447d439 --- /dev/null +++ b/controller/tests/slatepack.rs @@ -0,0 +1,526 @@ +// Copyright 2019 The Grin Developers +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test a wallet file send/recieve +#[macro_use] +extern crate log; +extern crate grin_wallet_controller as wallet; +extern crate grin_wallet_impls as impls; + +use grin_wallet_libwallet as libwallet; +use grin_wallet_util::grin_core as core; +use grin_wallet_util::OnionV3Address; + +use impls::test_framework::{self, LocalWalletClient}; +use impls::{ + PathToSlatepack, PathToSlatepackArmored, SlateGetter as _, SlatePutter as _, SlatepackArgs, +}; +use std::sync::atomic::Ordering; +use std::thread; +use std::time::Duration; + +use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, Slatepack}; + +use x25519_dalek::PublicKey as xDalekPublicKey; +use x25519_dalek::StaticSecret; + +#[macro_use] +mod common; +use common::{clean_output_dir, create_wallet_proxy, setup}; + +fn output_slatepack( + slate: &Slate, + file: &str, + armored: bool, + use_bin: bool, + sender: Option, + recipients: Vec, +) -> Result<(), libwallet::Error> { + if armored { + let file = format!("{}.armored", file); + let args = SlatepackArgs { + pathbuf: file.into(), + sender, + recipients, + dec_key: None, + }; + PathToSlatepackArmored::new(args).put_tx(&slate, use_bin) + } else { + let args = SlatepackArgs { + pathbuf: file.into(), + sender, + recipients, + dec_key: None, + }; + PathToSlatepack::new(args).put_tx(&slate, use_bin) + } +} + +fn slate_from_packed( + file: &str, + armored: bool, + dec_key: Option<&StaticSecret>, +) -> Result<(Slatepack, Slate), libwallet::Error> { + if armored { + let file = format!("{}.armored", file); + let args = SlatepackArgs { + pathbuf: file.into(), + sender: None, + recipients: vec![], + dec_key, + }; + let pts = PathToSlatepackArmored::new(args); + Ok((pts.get_slatepack()?, pts.get_tx()?.0)) + } else { + let args = SlatepackArgs { + pathbuf: file.into(), + sender: None, + recipients: vec![], + dec_key, + }; + let pts = PathToSlatepack::new(args); + Ok((pts.get_slatepack()?, pts.get_tx()?.0)) + } +} + +/// self send impl +fn slatepack_exchange_test_impl( + test_dir: &'static str, + use_bin: bool, + use_armored: bool, + use_encryption: bool, +) -> Result<(), libwallet::Error> { + // Create a new proxy to simulate server and wallet responses + let mut wallet_proxy = create_wallet_proxy(test_dir); + let chain = wallet_proxy.chain.clone(); + let stopper = wallet_proxy.running.clone(); + + // Create a new wallet test client, and set its queues to communicate with the + // proxy + create_wallet_and_add!( + client1, + wallet1, + mask1_i, + test_dir, + "wallet1", + None, + &mut wallet_proxy, + false + ); + let mask1 = (&mask1_i).as_ref(); + create_wallet_and_add!( + client2, + wallet2, + mask2_i, + test_dir, + "wallet2", + None, + &mut wallet_proxy, + false + ); + let mask2 = (&mask2_i).as_ref(); + + // Set the wallet proxy listener running + thread::spawn(move || { + if let Err(e) = wallet_proxy.run() { + error!("Wallet Proxy error: {}", e); + } + }); + + // few values to keep things shorter + let reward = core::consensus::REWARD; + + // add some accounts + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + api.create_account_path(m, "mining")?; + api.create_account_path(m, "listener")?; + Ok(()) + })?; + + // add some accounts + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + api.create_account_path(m, "account1")?; + api.create_account_path(m, "account2")?; + Ok(()) + })?; + + // Get some mining done + { + wallet_inst!(wallet1, w); + w.set_parent_key_id_by_name("mining")?; + } + let mut bh = 10u64; + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + + let (recipients_1, dec_key_1, sender_1) = match use_encryption { + true => { + let mut rec_address = xDalekPublicKey::from([0u8; 32]); + let mut sec_key = StaticSecret::from([0u8; 32]); + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let ed25519_sec_key = api.get_secret_key(m, 0)?; + let mut b = [0u8; 32]; + b.copy_from_slice(&ed25519_sec_key.as_ref()[0..32]); + sec_key = StaticSecret::from(b); + rec_address = xDalekPublicKey::from(&sec_key); + Ok(()) + })?; + (vec![rec_address], Some(sec_key), Some(rec_address.clone())) + } + false => (vec![], None, None), + }; + + let (recipients_2, dec_key_2, sender_2) = match use_encryption { + true => { + let mut rec_address = xDalekPublicKey::from([0u8; 32]); + let mut sec_key = StaticSecret::from([0u8; 32]); + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + let ed25519_sec_key = api.get_secret_key(m, 0)?; + let mut b = [0u8; 32]; + b.copy_from_slice(&ed25519_sec_key.as_ref()[0..32]); + sec_key = StaticSecret::from(b); + rec_address = xDalekPublicKey::from(&sec_key); + Ok(()) + })?; + (vec![rec_address], Some(sec_key), Some(rec_address.clone())) + } + false => (vec![], None, None), + }; + + let (send_file, receive_file, final_file) = match use_bin { + false => ( + format!("{}/standard_S1.slatepack", test_dir), + format!("{}/standard_S2.slatepack", test_dir), + format!("{}/standard_S3.slatepack", test_dir), + ), + true => ( + format!("{}/standard_S1.slatepackbin", test_dir), + format!("{}/standard_S2.slatepackbin", test_dir), + format!("{}/standard_S3.slatepackbin", test_dir), + ), + }; + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + // output tx file + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_1, + recipients_2.clone(), + )?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + + // Get some mining done + { + wallet_inst!(wallet2, w); + w.set_parent_key_id_by_name("account1")?; + } + + let (mut slatepack, mut slate) = + slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?; + + // wallet 2 receives file, completes, sends file back + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.receive_tx(&slate, None)?; + output_slatepack( + &slate, + &receive_file, + use_armored, + use_bin, + // re-encrypt for sender! + sender_2, + match slatepack.sender { + Some(s) => vec![s.clone()], + None => vec![], + }, + )?; + Ok(()) + })?; + + // wallet 1 finalises and posts + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (_, mut slate) = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; + slate = api.finalize_tx(m, &slate)?; + // Output final file for reference + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + api.post_tx(m, &slate, false)?; + bh += 1; + Ok(()) + })?; + + let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); + bh += 3; + + // Check total in mining account + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); + Ok(()) + })?; + + // Check total in 'wallet 2' account + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * reward); + Ok(()) + })?; + + // Now other types of exchange, for reference + // Invoice transaction + let (send_file, receive_file, final_file) = match use_bin { + false => ( + format!("{}/invoice_I1.slatepack", test_dir), + format!("{}/invoice_I2.slatepack", test_dir), + format!("{}/invoice_I3.slatepack", test_dir), + ), + true => ( + format!("{}/invoice_I1.slatepackbin", test_dir), + format!("{}/invoice_I2.slatepackbin", test_dir), + format!("{}/invoice_I3.slatepackbin", test_dir), + ), + }; + + let mut slate = Slate::blank(2, true); + + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + let args = IssueInvoiceTxArgs { + amount: 1000000000, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_2, + recipients_1.clone(), + )?; + Ok(()) + })?; + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let res = slate_from_packed(&send_file, use_armored, (&dec_key_1).as_ref())?; + slatepack = res.0; + slate = res.1; + slate = api.process_invoice_tx(m, &slate, args)?; + api.tx_lock_outputs(m, &slate)?; + output_slatepack( + &slate, + &receive_file, + use_armored, + use_bin, + sender_1, + match slatepack.sender { + Some(s) => vec![s.clone()], + None => vec![], + }, + )?; + Ok(()) + })?; + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + // Wallet 2 receives the invoice transaction + let res = slate_from_packed(&receive_file, use_armored, (&dec_key_2).as_ref())?; + slate = res.1; + slate = api.finalize_invoice_tx(&slate)?; + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + Ok(()) + })?; + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + api.post_tx(m, &slate, false)?; + Ok(()) + })?; + + // Standard, with payment proof + let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); + let (send_file, receive_file, final_file) = match use_bin { + false => ( + format!("{}/standard_pp_S1.slatepack", test_dir), + format!("{}/standard_pp_S2.slatepack", test_dir), + format!("{}/standard_pp_S3.slatepack", test_dir), + ), + true => ( + format!("{}/standard_pp_S1.slatepackbin", test_dir), + format!("{}/standard_pp_S2.slatepackbin", test_dir), + format!("{}/standard_pp_S3.slatepackbin", test_dir), + ), + }; + + let mut slate = Slate::blank(2, true); + let mut address = None; + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + address = Some(api.get_public_proof_address(m, 0)?); + Ok(()) + })?; + + let address = OnionV3Address::from_bytes(address.as_ref().unwrap().to_bytes()); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: Some(address.clone()), + ..Default::default() + }; + let slate = api.init_send_tx(m, args)?; + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_1, + recipients_2.clone(), + )?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + let res = slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?; + slatepack = res.0; + slate = res.1; + slate = api.receive_tx(&slate, None)?; + output_slatepack( + &slate, + &receive_file, + use_armored, + use_bin, + sender_2, + match slatepack.sender { + Some(s) => vec![s.clone()], + None => vec![], + }, + )?; + Ok(()) + })?; + + // wallet 1 finalises and posts + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let res = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; + slate = res.1; + slate = api.finalize_tx(m, &slate)?; + // Output final file for reference + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + api.post_tx(m, &slate, false)?; + bh += 1; + Ok(()) + })?; + + // let logging finish + stopper.store(false, Ordering::Relaxed); + thread::sleep(Duration::from_millis(200)); + Ok(()) +} + +#[test] +fn slatepack_exchange_json() { + let test_dir = "test_output/slatepack_exchange_json"; + setup(test_dir); + // Json output + if let Err(e) = slatepack_exchange_test_impl(test_dir, false, false, false) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } + clean_output_dir(test_dir); +} + +#[test] +fn slatepack_exchange_bin() { + let test_dir = "test_output/slatepack_exchange_bin"; + setup(test_dir); + // Bin output + if let Err(e) = slatepack_exchange_test_impl(test_dir, true, false, false) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } + clean_output_dir(test_dir); +} + +#[test] +fn slatepack_exchange_armored() { + let test_dir = "test_output/slatepack_exchange_armored"; + setup(test_dir); + // Bin output + if let Err(e) = slatepack_exchange_test_impl(test_dir, true, true, false) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } + clean_output_dir(test_dir); +} + +#[test] +fn slatepack_exchange_json_enc() { + let test_dir = "test_output/slatepack_exchange_json_enc"; + setup(test_dir); + // Json output + if let Err(e) = slatepack_exchange_test_impl(test_dir, false, false, true) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } + clean_output_dir(test_dir); +} + +#[test] +fn slatepack_exchange_bin_enc() { + let test_dir = "test_output/slatepack_exchange_bin_enc"; + setup(test_dir); + // Bin output + if let Err(e) = slatepack_exchange_test_impl(test_dir, true, false, true) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } + clean_output_dir(test_dir); +} + +#[test] +fn slatepack_exchange_armored_enc() { + let test_dir = "test_output/slatepack_exchange_armored_enc"; + setup(test_dir); + // Bin output + if let Err(e) = slatepack_exchange_test_impl(test_dir, true, true, true) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } + clean_output_dir(test_dir); +} diff --git a/impls/Cargo.toml b/impls/Cargo.toml index 4f675099..c33720ab 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -35,6 +35,7 @@ byteorder = "1" hyper = "0.13" hyper-socks2 = "0.4" ed25519-dalek = "1.0.0-pre.1" +x25519-dalek = "0.6" data-encoding = "2" regex = "1.3" timer = "0.2" diff --git a/impls/src/adapters/file.rs b/impls/src/adapters/file.rs index 2fd91411..055e61c0 100644 --- a/impls/src/adapters/file.rs +++ b/impls/src/adapters/file.rs @@ -16,13 +16,13 @@ use std::fs::File; use std::io::{Read, Write}; -use crate::client_utils::byte_ser; use crate::libwallet::slate_versions::v3::SlateV3; use crate::libwallet::slate_versions::v4::SlateV4; use crate::libwallet::{ Error, ErrorKind, Slate, SlateState, SlateVersion, VersionedBinSlate, VersionedSlate, }; use crate::{SlateGetter, SlatePutter}; +use grin_wallet_util::byte_ser; use std::convert::TryFrom; use std::path::PathBuf; diff --git a/impls/src/adapters/mod.rs b/impls/src/adapters/mod.rs index 8ff0b361..4ca603dd 100644 --- a/impls/src/adapters/mod.rs +++ b/impls/src/adapters/mod.rs @@ -15,10 +15,12 @@ mod file; pub mod http; mod keybase; +mod slatepack; pub use self::file::PathToSlate; pub use self::http::{HttpSlateSender, SchemeNotHttp}; pub use self::keybase::{KeybaseAllChannels, KeybaseChannel}; +pub use self::slatepack::{PathToSlatepack, PathToSlatepackArmored, SlatepackArgs}; use crate::config::{TorConfig, WalletConfig}; use crate::libwallet::{Error, ErrorKind, Slate}; diff --git a/impls/src/adapters/slatepack.rs b/impls/src/adapters/slatepack.rs new file mode 100644 index 00000000..dcff4370 --- /dev/null +++ b/impls/src/adapters/slatepack.rs @@ -0,0 +1,169 @@ +// Copyright 2020 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::convert::TryFrom; +/// Slatepack Output 'plugin' implementation +use std::fs::File; +use std::io::{Read, Write}; +use std::path::PathBuf; + +use x25519_dalek::PublicKey as xDalekPublicKey; +use x25519_dalek::StaticSecret; + +use crate::libwallet::{ + Error, ErrorKind, Slate, SlateVersion, Slatepack, SlatepackArmor, SlatepackBin, + VersionedBinSlate, VersionedSlate, +}; +use crate::{SlateGetter, SlatePutter}; +use grin_wallet_util::byte_ser; + +#[derive(Clone)] +pub struct SlatepackArgs<'a> { + pub pathbuf: PathBuf, + pub sender: Option, + pub recipients: Vec, + pub dec_key: Option<&'a StaticSecret>, +} + +pub struct PathToSlatepack<'a>(SlatepackArgs<'a>); + +impl<'a> PathToSlatepack<'a> { + /// Create with pathbuf and recipients + pub fn new(args: SlatepackArgs<'a>) -> Self { + Self(args) + } + + pub fn get_slatepack_file_contents(&self) -> Result, Error> { + let mut pub_tx_f = File::open(&self.0.pathbuf)?; + let mut data = Vec::new(); + pub_tx_f.read_to_end(&mut data)?; + Ok(data) + } + + // return slatepack itself + pub fn deser_slatepack(&self, data: Vec) -> Result { + // try as bin first, then as json + let bin_res = byte_ser::from_bytes::(&data); + match bin_res { + Err(e) => debug!("Not a valid binary slatepack: {} - Will try JSON", e), + Ok(s) => return Ok(s.0), + } + // Otherwise try json + let content = String::from_utf8(data).map_err(|_| ErrorKind::SlatepackDeser)?; + let slatepack: Slatepack = serde_json::from_str(&content).map_err(|e| { + error!("Error reading JSON Slatepack: {}", e); + ErrorKind::SlatepackDeser + })?; + slatepack.ver_check_warn(); + Ok(slatepack) + } + + pub fn get_slatepack(&self) -> Result { + let data = self.get_slatepack_file_contents()?; + self.deser_slatepack(data) + } + + // Create slatepack from slate and args + pub fn create_slatepack(&self, slate: &Slate) -> Result { + let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?; + let bin_slate = + VersionedBinSlate::try_from(out_slate).map_err(|_| ErrorKind::SlatepackSer)?; + let mut slatepack = Slatepack::default(); + slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| ErrorKind::SlatepackSer)?; + slatepack.sender = self.0.sender; + slatepack.try_encrypt_payload(self.0.recipients.clone())?; + Ok(slatepack) + } +} + +impl<'a> SlatePutter for PathToSlatepack<'a> { + fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error> { + let slatepack = self.create_slatepack(slate)?; + let mut pub_tx = File::create(&self.0.pathbuf)?; + if as_bin { + pub_tx.write_all( + &byte_ser::to_bytes(&SlatepackBin(slatepack)) + .map_err(|_| ErrorKind::SlatepackSer)?, + )?; + } else { + pub_tx.write_all( + serde_json::to_string_pretty(&slatepack) + .map_err(|_| ErrorKind::SlateSer)? + .as_bytes(), + )?; + } + pub_tx.sync_all()?; + Ok(()) + } +} + +impl<'a> SlateGetter for PathToSlatepack<'a> { + fn get_tx(&self) -> Result<(Slate, bool), Error> { + let data = self.get_slatepack_file_contents()?; + let mut slatepack = self.deser_slatepack(data)?; + slatepack.try_decrypt_payload(self.0.dec_key)?; + let slate = byte_ser::from_bytes::(&slatepack.payload) + .map_err(|_| ErrorKind::SlatepackSer)?; + Ok((Slate::upgrade(slate.into())?, true)) + } +} + +pub struct PathToSlatepackArmored<'a>(SlatepackArgs<'a>); + +impl<'a> PathToSlatepackArmored<'a> { + /// Create with pathbuf and recipients + pub fn new(args: SlatepackArgs<'a>) -> Self { + Self(args) + } + + /// decode armor + pub fn decode_armored_file(&self) -> Result, Error> { + let mut pub_sp_armored = File::open(&self.0.pathbuf)?; + let mut data = Vec::new(); + pub_sp_armored.read_to_end(&mut data)?; + SlatepackArmor::decode(&String::from_utf8(data).unwrap()) + } + + // return slatepack + pub fn get_slatepack(&self) -> Result { + let data = self.decode_armored_file()?; + let pts = PathToSlatepack::new(self.0.clone()); + pts.deser_slatepack(data) + } +} + +impl<'a> SlatePutter for PathToSlatepackArmored<'a> { + fn put_tx(&self, slate: &Slate, _as_bin: bool) -> Result<(), Error> { + let pts = PathToSlatepack::new(self.0.clone()); + let slatepack = pts.create_slatepack(slate)?; + let armored = SlatepackArmor::encode(&slatepack, 3)?; + let mut pub_tx = File::create(&self.0.pathbuf)?; + pub_tx.write_all(armored.as_bytes())?; + pub_tx.sync_all()?; + Ok(()) + } +} + +impl<'a> SlateGetter for PathToSlatepackArmored<'a> { + fn get_tx(&self) -> Result<(Slate, bool), Error> { + let mut slatepack = self.get_slatepack()?; + slatepack.try_decrypt_payload(self.0.dec_key)?; + let slate_bin = + byte_ser::from_bytes::(&slatepack.payload).map_err(|e| { + error!("Error reading slate from armored slatepack: {}", e); + ErrorKind::SlatepackDeser + })?; + Ok((Slate::upgrade(slate_bin.into())?, true)) + } +} diff --git a/impls/src/client_utils/mod.rs b/impls/src/client_utils/mod.rs index 0e4ca119..e4a65b88 100644 --- a/impls/src/client_utils/mod.rs +++ b/impls/src/client_utils/mod.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod byte_ser; mod client; pub mod json_rpc; diff --git a/impls/src/lib.rs b/impls/src/lib.rs index bee04c5a..7b629350 100644 --- a/impls/src/lib.rs +++ b/impls/src/lib.rs @@ -44,8 +44,9 @@ pub mod test_framework; pub mod tor; pub use crate::adapters::{ - create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, SlateGetter, - SlatePutter, SlateReceiver, SlateSender, + create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, + PathToSlatepack, PathToSlatepackArmored, SlateGetter, SlatePutter, SlateReceiver, SlateSender, + SlatepackArgs, }; pub use crate::backends::{wallet_db_exists, LMDBBackend}; pub use crate::error::{Error, ErrorKind}; diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index 9df402b8..6aa746c6 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -25,8 +25,18 @@ lazy_static = "1" strum = "0.15" strum_macros = "0.15" ed25519-dalek = "1.0.0-pre.1" +x25519-dalek = "0.6" byteorder = "1" base64 = "0.9" +regex = "1.3" +sha2 = "0.8" +bs58 = "0.3" +age = "0.4" +curve25519-dalek = "2.0.0" +secrecy = "0.6" grin_wallet_util = { path = "../util", version = "4.0.0-alpha.1" } grin_wallet_config = { path = "../config", version = "4.0.0-alpha.1" } + +[dev-dependencies] +byteorder = "1.3" diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 99e299e6..0a194218 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -20,7 +20,7 @@ use crate::grin_core::core::hash::Hashed; use crate::grin_core::core::Transaction; use crate::grin_util::secp::key::SecretKey; use crate::grin_util::Mutex; -use crate::util::OnionV3Address; +use crate::util::{OnionV3Address, OnionV3AddressError}; use crate::api_impl::owner_updater::StatusMessage; use crate::grin_keychain::{Identifier, Keychain}; @@ -93,6 +93,36 @@ where Ok(addr.to_ed25519()?) } +/// Retrieve the decryption key for the current parent key +/// the given index +/// set active account +pub fn get_secret_key<'a, L, C, K>( + wallet_inst: Arc>>>, + keychain_mask: Option<&SecretKey>, + index: u32, +) -> Result +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + wallet_lock!(wallet_inst, w); + let parent_key_id = w.parent_key_id(); + let k = w.keychain(keychain_mask)?; + let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, index)?; + let d_skey = match DalekSecretKey::from_bytes(&sec_addr_key.0) { + Ok(k) => k, + Err(e) => { + return Err(OnionV3AddressError::InvalidPrivateKey(format!( + "Unable to create secret key: {}", + e + )) + .into()); + } + }; + Ok(d_skey) +} + /// retrieve outputs pub fn retrieve_outputs<'a, L, C, K>( wallet_inst: Arc>>>, diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index 244ab727..6fdce3a0 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -202,6 +202,14 @@ pub enum ErrorKind { #[fail(display = "Can't Deserialize slate")] SlateDeser, + /// Can't serialize slate pack + #[fail(display = "Can't Serialize slatepack")] + SlatepackSer, + + /// Can't deserialize slate + #[fail(display = "Can't Deserialize slatepack")] + SlatepackDeser, + /// Unknown slate version #[fail(display = "Unknown Slate Version: {}", _0)] SlateVersion(u16), @@ -270,6 +278,18 @@ pub enum ErrorKind { #[fail(display = "Unknown Kernel Feature: {}", _0)] UnknownKernelFeatures(u8), + /// Invalid Slatepack Data + #[fail(display = "Invalid Slatepack Data: {}", _0)] + InvalidSlatepackData(String), + + /// Slatepack Encryption + #[fail(display = "Couldn't encrypt Slatepack: {}", _0)] + SlatepackEncryption(String), + + /// age error + #[fail(display = "Age error: {}", _0)] + Age(String), + /// Other #[fail(display = "Generic error: {}", _0)] GenericError(String), @@ -398,3 +418,11 @@ impl From for Error { Error::from(ErrorKind::OnionV3Address(error)) } } + +impl From for Error { + fn from(error: age::Error) -> Error { + Error { + inner: Context::new(ErrorKind::Age(format!("{}", error))), + } + } +} diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 6679961e..5e2bc649 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -52,6 +52,7 @@ mod error; mod internal; mod slate; pub mod slate_versions; +mod slatepack; mod types; pub use crate::error::{Error, ErrorKind}; @@ -60,6 +61,7 @@ pub use crate::slate_versions::{ SlateVersion, VersionedBinSlate, VersionedCoinbase, VersionedSlate, CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION, }; +pub use crate::slatepack::{Slatepack, SlatepackArmor, SlatepackBin}; pub use api_impl::owner_updater::StatusMessage; pub use api_impl::types::{ BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult, diff --git a/libwallet/src/slate_versions/ser.rs b/libwallet/src/slate_versions/ser.rs index 9d1a3da8..163d6f3c 100644 --- a/libwallet/src/slate_versions/ser.rs +++ b/libwallet/src/slate_versions/ser.rs @@ -28,13 +28,22 @@ where serializer.serialize_str(&base64::encode(&bytes)) } +/// Creates a Vec from a base string +pub fn bytes_from_base64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + use serde::de::Error; + String::deserialize(deserializer) + .and_then(|string| base64::decode(&string).map_err(|err| Error::custom(err.to_string()))) +} + /// Creates a BlindingFactor from a base64 string pub fn blindingfactor_from_base64<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { use serde::de::Error; - let val = String::deserialize(deserializer) .and_then(|string| base64::decode(&string).map_err(|err| Error::custom(err.to_string())))?; Ok(BlindingFactor::from_slice(&val)) @@ -280,6 +289,36 @@ pub mod dalek_pubkey_serde { } } +/// Serializes an x25519 PublicKey to and from hex +pub mod dalek_xpubkey_serde { + use crate::grin_util::{from_hex, ToHex}; + use serde::{Deserialize, Deserializer, Serializer}; + use x25519_dalek::PublicKey as xDalekPublicKey; + + /// + pub fn serialize(key: &xDalekPublicKey, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&key.as_bytes().to_hex()) + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de::Error; + String::deserialize(deserializer) + .and_then(|string| from_hex(&string).map_err(|err| Error::custom(err.to_string()))) + .and_then(|bytes: Vec| { + let mut b = [0u8; 32]; + b.copy_from_slice(&bytes[0..32]); + Ok(xDalekPublicKey::from(b)) + }) + } +} + /// Serializes an ed25519 PublicKey to and from base64 pub mod dalek_pubkey_base64 { use base64; @@ -310,6 +349,47 @@ pub mod dalek_pubkey_base64 { } } +/// Serializes an Option to and from hex +pub mod option_dalek_pubkey_base64 { + use base64; + use ed25519_dalek::PublicKey as DalekPublicKey; + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; + + /// + pub fn serialize(key: &Option, serializer: S) -> Result + where + S: Serializer, + { + match key { + Some(key) => serializer.serialize_str(&base64::encode(&key.to_bytes())), + None => serializer.serialize_none(), + } + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer).and_then(|res| match res { + Some(string) => base64::decode(&string) + .map_err(|err| Error::custom(err.to_string())) + .and_then(|bytes: Vec| { + let mut b = [0u8; 32]; + b.copy_from_slice(&bytes[0..32]); + DalekPublicKey::from_bytes(&b) + .map(Some) + .map_err(|err| Error::custom(err.to_string())) + }), + None => { + println!("None fine"); + Ok(None) + } + }) + } +} + /// Serializes an Option to and from hex pub mod option_dalek_pubkey_serde { use ed25519_dalek::PublicKey as DalekPublicKey; @@ -349,6 +429,43 @@ pub mod option_dalek_pubkey_serde { } } +/// Serializes an Option to and from hex +pub mod option_xdalek_pubkey_serde { + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; + use x25519_dalek::PublicKey as xDalekPublicKey; + + use crate::grin_util::{from_hex, ToHex}; + + /// + pub fn serialize(key: &Option, serializer: S) -> Result + where + S: Serializer, + { + match key { + Some(key) => serializer.serialize_str(&key.as_bytes().to_hex()), + None => serializer.serialize_none(), + } + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer).and_then(|res| match res { + Some(string) => from_hex(&string) + .map_err(|err| Error::custom(err.to_string())) + .and_then(|bytes: Vec| { + let mut b = [0u8; 32]; + b.copy_from_slice(&bytes[0..32]); + Ok(Some(xDalekPublicKey::from(b))) + }), + None => Ok(None), + }) + } +} + /// Serializes an ed25519_dalek::Signature to and from hex pub mod dalek_sig_serde { use ed25519_dalek::Signature as DalekSignature; diff --git a/libwallet/src/slatepack/armor.rs b/libwallet/src/slatepack/armor.rs new file mode 100644 index 00000000..15c65244 --- /dev/null +++ b/libwallet/src/slatepack/armor.rs @@ -0,0 +1,187 @@ +// Copyright 2020 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +// See the License for the specific language governing permissions and +// limitations under the License. + +// A note on encoding efficiency: 0.75 for Base64, 0.744 for Base62, 0.732 for Base58 +// slatepack uses a modified Base58Check encoding to create armored slate payloads: +// 1. Take first four bytes of SHA256(SHA256(slate.as_bytes())) +// 2. Concatenate result of step 1 and slate.as_bytes() +// 3. Base58 encode bytes from step 2 +// Finally add armor framing and space/newline formatting as desired + +use crate::{Error, ErrorKind}; +use grin_wallet_util::byte_ser; +use regex::Regex; +use sha2::{Digest, Sha256}; +use std::str; + +use super::types::{Slatepack, SlatepackBin}; + +// Framing and formatting for slate armor +static HEADER: &str = "BEGINSLATEPACK. "; +static FOOTER: &str = ". ENDSLATEPACK."; +const WORD_LENGTH: usize = 15; + +lazy_static! { + static ref HEADER_REGEX: Regex = + Regex::new(concat!(r"^[>\n\r\t ]*BEGINSLATEPACK[>\n\r\t ]*$")).unwrap(); + static ref FOOTER_REGEX: Regex = + Regex::new(concat!(r"^[>\n\r\t ]*ENDSLATEPACK[>\n\r\t ]*$")).unwrap(); + static ref WHITESPACE_LIST: [u8; 5] = [b'>', b'\n', b'\r', b'\t', b' ']; +} + +/// Wrapper for associated functions +pub struct SlatepackArmor; + +impl SlatepackArmor { + /// Decode an armored Slatepack + pub fn decode(data: &str) -> Result, Error> { + // Convert the armored slate to bytes for parsing + let armor_bytes: Vec = data.as_bytes().to_vec(); + // Collect the bytes up to the first period, this is the header + let header_bytes = &armor_bytes + .iter() + .take_while(|byte| **byte != b'.') + .cloned() + .collect::>(); + // Verify the header... + check_header(&header_bytes)?; + // Get the length of the header + let header_len = *&header_bytes.len() + 1; + // Skip the length of the header to read for the payload until the next period + let payload_bytes = &armor_bytes[header_len as usize..] + .iter() + .take_while(|byte| **byte != b'.') + .cloned() + .collect::>(); + // Get length of the payload to check the footer framing + let payload_len = *&payload_bytes.len(); + // Get footer bytes and verify them + let consumed_bytes = header_len + payload_len + 1; + let footer_bytes = &armor_bytes[consumed_bytes as usize..] + .iter() + .take_while(|byte| **byte != b'.') + .cloned() + .collect::>(); + check_footer(&footer_bytes)?; + // Clean up the payload bytes to be deserialized + let clean_payload = &payload_bytes + .iter() + .filter(|byte| !WHITESPACE_LIST.contains(byte)) + .cloned() + .collect::>(); + // Decode payload from base58 + let base_decode = bs58::decode(&clean_payload).into_vec().unwrap(); + let error_code = &base_decode[0..4]; + let slatepack_bytes = &base_decode[4..]; + // Make sure the error check code is valid for the slate data + error_check(&error_code.to_vec(), &slatepack_bytes.to_vec())?; + // Return slate as binary or string + /*let slatepack_bin = byte_ser::from_bytes::(&slate_bytes).map_err(|e| { + error!("Error reading JSON Slatepack: {}", e); + ErrorKind::SlatepackDeser + })?;*/ + Ok(slatepack_bytes.to_vec()) + } + + /// Encode an armored slatepack + pub fn encode(slatepack: &Slatepack, num_cols: usize) -> Result { + let slatepack_bytes = byte_ser::to_bytes(&SlatepackBin(slatepack.clone())) + .map_err(|_| ErrorKind::SlatepackSer)?; + let encoded_slatepack = base58check(&slatepack_bytes)?; + let formatted_slatepack = format_slatepack(&encoded_slatepack, num_cols)?; + Ok(format!("{}{}{}\n", HEADER, formatted_slatepack, FOOTER)) + } +} + +// Takes an error check code and a slate binary and verifies that the code was generated from slate +fn error_check(error_code: &Vec, slate_bytes: &Vec) -> Result<(), Error> { + let new_check = generate_check(&slate_bytes)?; + if error_code.iter().eq(new_check.iter()) { + Ok(()) + } else { + Err(ErrorKind::InvalidSlatepackData( + "Bad slate error code- some data was corrupted".to_string(), + ) + .into()) + } +} + +// Checks header framing bytes and returns an error if they are invalid +fn check_header(header: &Vec) -> Result<(), Error> { + let framing = str::from_utf8(&header).unwrap(); + if HEADER_REGEX.is_match(framing) { + Ok(()) + } else { + Err(ErrorKind::InvalidSlatepackData("Bad armor header".to_string()).into()) + } +} + +// Checks footer framing bytes and returns an error if they are invalid +fn check_footer(footer: &Vec) -> Result<(), Error> { + let framing = str::from_utf8(&footer).unwrap(); + if FOOTER_REGEX.is_match(framing) { + Ok(()) + } else { + Err(ErrorKind::InvalidSlatepackData("Bad armor footer".to_string()).into()) + } +} + +// MODIFIED Base58Check encoding for slate bytes +fn base58check(slate: &[u8]) -> Result { + // Serialize the slate json string to a vector of bytes + let mut slate_bytes: Vec = slate.to_vec(); + // Get the four byte checksum for the slate binary + let mut check_bytes: Vec = generate_check(&slate_bytes)?; + // Make a new buffer and concatenate checksum bytes and slate bytes + let mut slate_buf = Vec::new(); + slate_buf.append(&mut check_bytes); + slate_buf.append(&mut slate_bytes); + // Encode the slate buffer containing the slate binary and checksum bytes as Base58 + let b58_slate = bs58::encode(slate_buf).into_string(); + Ok(b58_slate) +} + +// Adds human readable formatting to the slate payload for armoring +fn format_slatepack(slatepack: &str, num_cols: usize) -> Result { + let formatter = slatepack + .chars() + .enumerate() + .flat_map(|(i, c)| { + if i != 0 && i % WORD_LENGTH == 0 { + if num_cols != 0 && i % (WORD_LENGTH * num_cols) == WORD_LENGTH * 2 { + Some('\n') + } else { + Some(' ') + } + } else { + None + } + .into_iter() + .chain(std::iter::once(c)) + }) + .collect::(); + Ok(formatter) +} + +// Returns the first four bytes of a double sha256 hash of some bytes +fn generate_check(payload: &Vec) -> Result, Error> { + let mut first_hash = Sha256::new(); + first_hash.input(payload); + let mut second_hash = Sha256::new(); + second_hash.input(first_hash.result()); + let checksum = second_hash.result(); + let check_bytes: Vec = checksum[0..4].to_vec(); + Ok(check_bytes) +} diff --git a/libwallet/src/slatepack/mod.rs b/libwallet/src/slatepack/mod.rs new file mode 100644 index 00000000..640af4b0 --- /dev/null +++ b/libwallet/src/slatepack/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2020 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod armor; +mod types; + +pub use self::armor::SlatepackArmor; +pub use self::types::{Slatepack, SlatepackBin}; diff --git a/libwallet/src/slatepack/types.rs b/libwallet/src/slatepack/types.rs new file mode 100644 index 00000000..e6536bd1 --- /dev/null +++ b/libwallet/src/slatepack/types.rs @@ -0,0 +1,491 @@ +// Copyright 2020 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Slatepack Types + Serialization implementation +use x25519_dalek::PublicKey as xDalekPublicKey; +use x25519_dalek::StaticSecret; + +use crate::dalek_ser; +use crate::grin_core::ser::{self, Readable, Reader, Writeable, Writer}; +use crate::Error; + +use std::io::{Read, Write}; + +pub const SLATEPACK_MAJOR_VERSION: u8 = 1; +pub const SLATEPACK_MINOR_VERSION: u8 = 0; + +/// Basic Slatepack definition +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Slatepack { + // Required Fields + /// Versioning info + #[serde(with = "slatepack_version")] + pub slatepack: SlatepackVersion, + /// Delivery Mode, 0 = plain_text, 1 = age encrypted + pub mode: u8, + + // Optional Fields + /// Optional Sender address + #[serde(default = "default_sender_none")] + #[serde(with = "dalek_ser::option_xdalek_pubkey_serde")] + #[serde(skip_serializing_if = "Option::is_none")] + pub sender: Option, + + // Payload + /// Binary payload, can be encrypted or plaintext + #[serde( + serialize_with = "dalek_ser::as_base64", + deserialize_with = "dalek_ser::bytes_from_base64" + )] + pub payload: Vec, +} + +fn default_sender_none() -> Option { + None +} + +impl Default for Slatepack { + fn default() -> Self { + Self { + slatepack: SlatepackVersion { + major: SLATEPACK_MAJOR_VERSION, + minor: SLATEPACK_MINOR_VERSION, + }, + mode: 0, + sender: None, + payload: vec![], + } + } +} + +impl Slatepack { + /// return length of optional fields + pub fn opt_fields_len(&self) -> Result { + let mut retval = 0; + if self.sender.is_some() { + retval += 32; + } + Ok(retval) + } + + /// age encrypt the payload with the given public key + pub fn try_encrypt_payload(&mut self, recipients: Vec) -> Result<(), Error> { + if recipients.is_empty() { + return Ok(()); + } + let rec_keys: Result, _> = recipients + .into_iter() + .map(|pk| { + let key = age::keys::RecipientKey::X25519(pk); + Ok(key) + }) + .collect(); + + let keys = match rec_keys { + Ok(k) => k, + Err(e) => return Err(e), + }; + + let encryptor = age::Encryptor::with_recipients(keys); + let mut encrypted = vec![]; + let mut writer = encryptor.wrap_output(&mut encrypted, age::Format::Binary)?; + writer.write_all(&self.payload)?; + writer.finish()?; + self.payload = encrypted.to_vec(); + self.mode = 1; + Ok(()) + } + + /// As above, decrypt if needed + pub fn try_decrypt_payload(&mut self, dec_key: Option<&StaticSecret>) -> Result<(), Error> { + if self.mode == 0 { + return Ok(()); + } + let dec_key = match dec_key { + Some(k) => k, + None => return Ok(()), + }; + let key = age::keys::SecretKey::X25519(dec_key.clone()); + let decryptor = match age::Decryptor::new(&self.payload[..])? { + age::Decryptor::Recipients(d) => d, + _ => unreachable!(), + }; + let mut decrypted = vec![]; + let mut reader = decryptor.decrypt(&[key.into()])?; + reader.read_to_end(&mut decrypted)?; + self.payload = decrypted.to_vec(); + Ok(()) + } + + /// version check warning + // TODO: API? + pub fn ver_check_warn(&self) { + if self.slatepack.major > SLATEPACK_MAJOR_VERSION + || (self.slatepack.major == SLATEPACK_MAJOR_VERSION + && self.slatepack.minor < SLATEPACK_MINOR_VERSION) + { + warn!("Incoming Slatepack's version is greater than what this wallet recognizes"); + warn!("You may need to upgrade if it contains unsupported features"); + warn!( + "Incoming: {}.{}, This wallet: {}.{}", + self.slatepack.major, + self.slatepack.minor, + SLATEPACK_MAJOR_VERSION, + SLATEPACK_MINOR_VERSION + ); + } + } +} + +/// Wrapper for outputting slate as binary +#[derive(Debug, Clone)] +pub struct SlatepackBin(pub Slatepack); + +impl serde::Serialize for SlatepackBin { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut vec = vec![]; + ser::serialize(&mut vec, ser::ProtocolVersion(4), self) + .map_err(|err| serde::ser::Error::custom(err.to_string()))?; + serializer.serialize_bytes(&vec) + } +} + +impl<'de> serde::Deserialize<'de> for SlatepackBin { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct SlatepackBinVisitor; + + impl<'de> serde::de::Visitor<'de> for SlatepackBinVisitor { + type Value = SlatepackBin; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a serialised binary Slatepack") + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: serde::de::Error, + { + let mut reader = std::io::Cursor::new(value.to_vec()); + let s = ser::deserialize(&mut reader, ser::ProtocolVersion(4)) + .map_err(|err| serde::de::Error::custom(err.to_string()))?; + Ok(s) + } + } + + deserializer.deserialize_bytes(SlatepackBinVisitor) + } +} + +impl Writeable for SlatepackBin { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { + let sp = self.0.clone(); + // Version (2) + sp.slatepack.write(writer)?; + // Mode (1) + writer.write_u8(sp.mode)?; + + // 16 bits of optional content flags (2), most reserved for future use + let mut opt_flags: u16 = 0; + if sp.sender.is_some() { + opt_flags |= 0x01; + } + writer.write_u16(opt_flags)?; + + // Bytes to skip from here (Start of optional fields) to get to payload + writer.write_u32(sp.opt_fields_len()? as u32)?; + + // write optional fields + if let Some(s) = sp.sender { + writer.write_fixed_bytes(s.as_bytes())?; + }; + + // Now write payload (length prefixed) + writer.write_bytes(sp.payload.clone()) + } +} + +impl Readable for SlatepackBin { + fn read(reader: &mut R) -> Result { + // Version (2) + let slatepack = SlatepackVersion::read(reader)?; + // Mode (1) + let mode = reader.read_u8()?; + if mode > 1 { + return Err(ser::Error::UnexpectedData { + expected: vec![0, 1], + received: vec![mode], + }); + } + // optional content flags (2) + let opt_flags = reader.read_u16()?; + + // start of header + let mut bytes_to_payload = reader.read_u32()?; + + let sender = if opt_flags & 0x01 > 0 { + bytes_to_payload -= 32; + let bytes = reader.read_fixed_bytes(32)?; + let mut b = [0u8; 32]; + b.copy_from_slice(&bytes[0..32]); + Some(xDalekPublicKey::from(b)) + } else { + None + }; + + // skip over any unknown future fields until header + while bytes_to_payload > 0 { + let _ = reader.read_u8()?; + bytes_to_payload -= 1; + } + + let payload = reader.read_bytes_len_prefix()?; + + Ok(SlatepackBin(Slatepack { + slatepack, + mode, + sender, + payload, + })) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct SlatepackVersion { + /// Major + pub major: u8, + /// Minor + pub minor: u8, +} + +impl Writeable for SlatepackVersion { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { + writer.write_u8(self.major)?; + writer.write_u8(self.minor) + } +} + +impl Readable for SlatepackVersion { + fn read(reader: &mut R) -> Result { + let major = reader.read_u8()?; + let minor = reader.read_u8()?; + Ok(SlatepackVersion { major, minor }) + } +} + +/// Serializes version field JSON +pub mod slatepack_version { + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; + + use super::SlatepackVersion; + + /// + pub fn serialize(v: &SlatepackVersion, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{}.{}", v.major, v.minor)) + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer).and_then(|s| { + let mut retval = SlatepackVersion { major: 0, minor: 0 }; + let v: Vec<&str> = s.split('.').collect(); + if v.len() != 2 { + return Err(Error::custom("Cannot parse version")); + } + match u8::from_str_radix(v[0], 10) { + Ok(u) => retval.major = u, + Err(e) => return Err(Error::custom(format!("Cannot parse version: {}", e))), + } + match u8::from_str_radix(v[1], 10) { + Ok(u) => retval.minor = u, + Err(e) => return Err(Error::custom(format!("Cannot parse version: {}", e))), + } + Ok(retval) + }) + } +} + +/// Header struct definition +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RecipientListEntry { + #[serde(with = "dalek_ser::dalek_xpubkey_serde")] + /// Public Address + pub pub_address: xDalekPublicKey, +} + +impl Writeable for RecipientListEntry { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { + writer.write_fixed_bytes(self.pub_address.as_bytes()) + } +} + +impl Readable for RecipientListEntry { + fn read(reader: &mut R) -> Result { + let bytes = reader.read_fixed_bytes(32)?; + let mut b = [0u8; 32]; + b.copy_from_slice(&bytes[0..32]); + let pub_address = xDalekPublicKey::from(b); + Ok(RecipientListEntry { pub_address }) + } +} + +#[test] +fn slatepack_bin_basic_ser() -> Result<(), grin_wallet_util::byte_ser::Error> { + use grin_wallet_util::byte_ser; + let slatepack = SlatepackVersion { major: 1, minor: 0 }; + let mut payload: Vec = Vec::with_capacity(243); + for _ in 0..payload.capacity() { + payload.push(rand::random()); + } + let sp = Slatepack { + slatepack, + mode: 1, + sender: None, + payload, + }; + let ser = byte_ser::to_bytes(&SlatepackBin(sp.clone()))?; + let deser = byte_ser::from_bytes::(&ser)?.0; + assert_eq!(sp.slatepack, deser.slatepack); + assert_eq!(sp.mode, deser.mode); + assert!(sp.sender.is_none()); + Ok(()) +} + +#[test] +fn slatepack_bin_opt_fields_ser() -> Result<(), grin_wallet_util::byte_ser::Error> { + use grin_wallet_util::byte_ser; + use rand::{thread_rng, Rng}; + let slatepack = SlatepackVersion { major: 1, minor: 0 }; + let mut payload: Vec = Vec::with_capacity(243); + for _ in 0..payload.capacity() { + payload.push(rand::random()); + } + + // includes optional fields + let bytes: [u8; 32] = thread_rng().gen(); + let sender_secret = StaticSecret::from(bytes); + let sender = Some(xDalekPublicKey::from(&sender_secret)); + let sp = Slatepack { + slatepack, + mode: 1, + sender, + payload, + }; + let ser = byte_ser::to_bytes(&SlatepackBin(sp.clone()))?; + let deser = byte_ser::from_bytes::(&ser)?.0; + assert_eq!(sp.slatepack, deser.slatepack); + assert_eq!(sp.mode, deser.mode); + assert_eq!( + sp.sender.unwrap().as_bytes(), + deser.sender.unwrap().as_bytes() + ); + + Ok(()) +} + +// ensure that a slatepack with unknown data in the optional fields can be read +#[test] +fn slatepack_bin_future() -> Result<(), grin_wallet_util::byte_ser::Error> { + use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + use grin_wallet_util::byte_ser; + use rand::{thread_rng, Rng}; + use std::io::Cursor; + let slatepack = SlatepackVersion { major: 1, minor: 0 }; + let payload_size = 1234; + let mut payload: Vec = Vec::with_capacity(payload_size); + for _ in 0..payload.capacity() { + payload.push(rand::random()); + } + + let bytes: [u8; 32] = thread_rng().gen(); + let sender_secret = StaticSecret::from(bytes); + let sender = Some(xDalekPublicKey::from(&sender_secret)); + println!( + "sender key len: {}", + sender.as_ref().unwrap().as_bytes().len() + ); + + let sp = Slatepack { + slatepack, + mode: 1, + sender, + payload: payload.clone(), + }; + let ser = byte_ser::to_bytes(&SlatepackBin(sp.clone()))?; + + // Add an amount of meaningless (to us) data + let num_extra_bytes = 248; + let mut new_bytes = vec![]; + // Version 2 + // mode 1 + // opt flags 2 + // opt fields len (bytes to payload) 4 + // bytes 5-8 are opt fields len + + // sender 32 + + let mut opt_fields_len_bytes = [0u8; 4]; + opt_fields_len_bytes.copy_from_slice(&ser[5..9]); + let mut rdr = Cursor::new(opt_fields_len_bytes.to_vec()); + let opt_fields_len = rdr.read_u32::().unwrap(); + // check this matches what we expect below + assert_eq!(opt_fields_len, 32); + + let end_head_pos = opt_fields_len as usize + 8 + 1; + + for i in 0..end_head_pos { + new_bytes.push(ser[i]); + } + for _ in 0..num_extra_bytes { + new_bytes.push(thread_rng().gen()); + } + for i in 0..8 { + //push payload length prefix + new_bytes.push(ser[end_head_pos + i]); + } + for i in 0..payload_size { + new_bytes.push(ser[end_head_pos + 8 + i]); + } + + assert_eq!(new_bytes.len(), ser.len() + num_extra_bytes as usize); + + // and set new opt fields length + let mut wtr = vec![]; + wtr.write_u32::(opt_fields_len + num_extra_bytes as u32) + .unwrap(); + for i in 0..wtr.len() { + new_bytes[5 + i] = wtr[i]; + } + + let deser = byte_ser::from_bytes::(&new_bytes)?.0; + assert_eq!(sp.slatepack, deser.slatepack); + assert_eq!(sp.mode, deser.mode); + assert_eq!( + sp.sender.unwrap().as_bytes(), + deser.sender.unwrap().as_bytes() + ); + Ok(()) +} diff --git a/impls/src/client_utils/byte_ser.rs b/util/src/byte_ser.rs similarity index 97% rename from impls/src/client_utils/byte_ser.rs rename to util/src/byte_ser.rs index d99e139f..c944b6e9 100644 --- a/impls/src/client_utils/byte_ser.rs +++ b/util/src/byte_ser.rs @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Simple serde byte array serializer, assumes target already -// knows how to serialize itself into binary (because that all -// this serializer can do) +//! Simple serde byte array serializer, assumes target already +//! knows how to serialize itself into binary (because that all +//! this serializer can do) + use serde::de::Visitor; use serde::{de, ser, Deserialize, Serialize}; use std; diff --git a/util/src/lib.rs b/util/src/lib.rs index 5995e738..74f14d3a 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -27,6 +27,9 @@ mod ov3; pub use ov3::OnionV3Address; pub use ov3::OnionV3Error as OnionV3AddressError; +#[allow(missing_docs)] +pub mod byte_ser; + pub use grin_api; pub use grin_chain; pub use grin_core;