From dc82f959d3f6dd10d967e3a2bdb9e24827bf0d1a Mon Sep 17 00:00:00 2001 From: CjS77 Date: Fri, 21 Jun 2019 13:34:20 +0200 Subject: [PATCH] tari_crypto v0.0.2 This point release updates the tari_crypto crate to v0.0.2. Major changes * RFC content updated to latest as of 21/6/2019 * New benchmarks, using the criterion library * Commitments have been refactored to be more general and ergonomic * The Blake256 type has been added, providing a 256bit Blake2b Hash * Lots of code reorganisations - putting things where they belong * A Bulletproof Range Proof service has been added * Challenge has been deprecated in favour of using Digest directly * Keys and Signatures now implement the Serialize and Deserialize traits * Schnorr signatures implement Ord * General cipher trait and ChaCha20 added * General MessageFormat trait added, helpful for serialising to hex, json and binary * ExtendBytes trait added, helpful for serialising to binary --- .gitignore | 5 + Cargo.lock | 585 +++++++++++++- RELEASE_CHECKLIST.md | 13 + RFC/src/AssetManagement.md | 3 + RFC/src/BaseLayerExtensions.md | 9 + RFC/src/Glossary.md | 223 ++++-- RFC/src/RFC-0001_overview.md | 49 +- RFC/src/RFC-0010_CodeStructure.md | 2 +- RFC/src/RFC-0110_BaseNodes.md | 6 +- RFC/src/RFC-0151_TransactionProtocol.md | 268 +++++++ .../RFC-0170_NetworkCommunicationProtocol.md | 32 +- .../RFC-0172_PeerToPeerMessagingProtocol.md | 718 ++++++++++++++++++ RFC/src/RFC-0190_Mempool.md | 237 ++++++ RFC/src/RFC-0230_HTLC.md | 126 ++- RFC/src/RFC-0300_DAN.md | 3 +- RFC/src/RFC-0302_ValidatorNodes.md | 18 +- RFC/src/RFC-0304_VNCommittees.md | 93 ++- RFC/src/RFC-0311_AssetTemplates.md | 6 +- RFC/src/RFC-0322_VNRegistration.md | 170 ++++- RFC/src/RFC-0341_AssetRegistration.md | 98 +++ RFC/src/RFC-0400_TariApplications.md | 0 RFC/src/RFC-1000_TariUseCases.md | 199 +++++ RFC/src/SUMMARY.md | 63 +- RFC/src/assets/Tari Network Overview.drawio | 1 + RFC/src/theme/css/chrome.css | 3 +- .../theme/images/tari_network_overview.png | Bin 0 -> 68369 bytes base_layer/core/Cargo.toml | 6 +- base_layer/keymanager/Cargo.toml | 4 +- infrastructure/crypto/Cargo.toml | 22 +- infrastructure/crypto/benches/signatures.rs | 95 ++- infrastructure/crypto/src/commitment.rs | 102 ++- infrastructure/crypto/src/common.rs | 37 +- infrastructure/crypto/src/keys.rs | 14 +- infrastructure/crypto/src/lib.rs | 4 +- infrastructure/crypto/src/musig.rs | 190 +---- infrastructure/crypto/src/range_proof.rs | 55 ++ .../crypto/src/ristretto/constants.rs | 50 +- .../crypto/src/ristretto/dalek_range_proof.rs | 165 ++++ infrastructure/crypto/src/ristretto/mod.rs | 2 + infrastructure/crypto/src/ristretto/musig.rs | 68 +- .../crypto/src/ristretto/pedersen.rs | 244 +++--- .../crypto/src/ristretto/ristretto_keys.rs | 162 ++-- .../crypto/src/ristretto/ristretto_sig.rs | 55 +- .../crypto/src/ristretto/serialize.rs | 103 +++ infrastructure/crypto/src/signatures.rs | 64 +- infrastructure/merklemountainrange/Cargo.toml | 2 +- infrastructure/tari_util/Cargo.toml | 12 +- infrastructure/tari_util/src/bit.rs | 12 +- infrastructure/tari_util/src/byte_array.rs | 62 +- .../tari_util/src/ciphers/chacha20.rs | 427 +++++++++++ .../tari_util/src/ciphers/cipher.rs | 53 ++ infrastructure/tari_util/src/ciphers/mod.rs | 24 + infrastructure/tari_util/src/extend_bytes.rs | 140 ++++ infrastructure/tari_util/src/fixed_set.rs | 199 +++++ infrastructure/tari_util/src/hex.rs | 24 +- infrastructure/tari_util/src/lib.rs | 7 + .../tari_util/src/message_format.rs | 219 ++++++ scripts/publish_crates.sh | 11 +- 58 files changed, 4809 insertions(+), 755 deletions(-) create mode 100644 RELEASE_CHECKLIST.md create mode 100644 RFC/src/AssetManagement.md create mode 100644 RFC/src/BaseLayerExtensions.md create mode 100644 RFC/src/RFC-0151_TransactionProtocol.md create mode 100644 RFC/src/RFC-0172_PeerToPeerMessagingProtocol.md create mode 100644 RFC/src/RFC-0190_Mempool.md create mode 100644 RFC/src/RFC-0400_TariApplications.md create mode 100644 RFC/src/RFC-1000_TariUseCases.md create mode 100644 RFC/src/assets/Tari Network Overview.drawio create mode 100644 RFC/src/theme/images/tari_network_overview.png create mode 100644 infrastructure/crypto/src/range_proof.rs create mode 100644 infrastructure/crypto/src/ristretto/dalek_range_proof.rs create mode 100644 infrastructure/crypto/src/ristretto/serialize.rs create mode 100644 infrastructure/tari_util/src/ciphers/chacha20.rs create mode 100644 infrastructure/tari_util/src/ciphers/cipher.rs create mode 100644 infrastructure/tari_util/src/ciphers/mod.rs create mode 100644 infrastructure/tari_util/src/extend_bytes.rs create mode 100644 infrastructure/tari_util/src/fixed_set.rs create mode 100644 infrastructure/tari_util/src/message_format.rs diff --git a/.gitignore b/.gitignore index b59ec2a7be..814895f79a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,8 @@ book # Ignore Code Coverage Report files report + +# On development branch only. This should be removed for point releases +Cargo.lock + +*.log diff --git a/Cargo.lock b/Cargo.lock index 8536e31cc9..a1917725cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,49 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "arrayvec" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "autocfg" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "backtrace" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base64" version = "0.10.1" @@ -15,7 +54,7 @@ dependencies = [ [[package]] name = "bincode" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -71,6 +110,24 @@ dependencies = [ "tari_core 0.0.1", ] +[[package]] +name = "bulletproofs" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "merlin 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "byte-tools" version = "0.3.1" @@ -86,6 +143,11 @@ name = "case" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cast" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cc" version = "1.0.31" @@ -106,6 +168,16 @@ dependencies = [ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clear_on_drop" version = "0.2.3" @@ -122,6 +194,81 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "criterion" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "csv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "criterion-plot" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-queue" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crypto-mac" version = "0.7.0" @@ -131,6 +278,25 @@ dependencies = [ "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "csv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv-core" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "curve25519-dalek" version = "1.1.3" @@ -141,6 +307,7 @@ dependencies = [ "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -166,11 +333,36 @@ dependencies = [ "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "error-chain" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -194,11 +386,24 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "keymanager" version = "0.0.1" @@ -210,8 +415,8 @@ dependencies = [ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tari_crypto 0.0.1", - "tari_utilities 0.0.1", + "tari_crypto 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tari_utilities 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -260,6 +465,19 @@ dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memchr" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memoffset" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "mempool" version = "0.0.1" @@ -271,7 +489,18 @@ dependencies = [ "blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tari_utilities 0.0.1", + "tari_utilities 0.0.2", +] + +[[package]] +name = "merlin" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -288,6 +517,11 @@ dependencies = [ name = "mining" version = "0.0.1" +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "num-integer" version = "0.1.39" @@ -309,6 +543,19 @@ name = "num-traits" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "opaque-debug" version = "0.2.2" @@ -364,6 +611,33 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -377,11 +651,114 @@ name = "rand_core" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xoshiro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rmp" version = "0.8.7" @@ -401,15 +778,36 @@ dependencies = [ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-demangle" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ryu" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "same-file" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "serde_derive" @@ -442,6 +840,18 @@ dependencies = [ "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "subtle" version = "1.0.0" @@ -485,12 +895,23 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "synstructure" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tari_comms" version = "0.0.1" dependencies = [ "derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tari_crypto 0.0.1", + "tari_crypto 0.0.2", "zmq 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -509,14 +930,15 @@ dependencies = [ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", - "tari_crypto 0.0.1", - "tari_infra_derive 0.0.1", - "tari_utilities 0.0.1", + "tari_crypto 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tari_infra_derive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tari_utilities 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tari_crypto" version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -528,12 +950,34 @@ dependencies = [ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tari_utilities 0.0.1", + "tari_utilities 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tari_crypto" +version = "0.0.2" +dependencies = [ + "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bulletproofs 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "merlin 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tari_utilities 0.0.2", ] [[package]] name = "tari_infra_derive" version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", @@ -543,7 +987,7 @@ dependencies = [ name = "tari_storage" version = "0.0.1" dependencies = [ - "bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "lmdb-zero 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -556,10 +1000,44 @@ dependencies = [ [[package]] name = "tari_utilities" version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tari_utilities" +version = "0.0.2" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rmp-serde 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.42" @@ -570,6 +1048,15 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tinytemplate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "toml" version = "0.2.1" @@ -580,6 +1067,11 @@ name = "typenum" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.0.4" @@ -590,6 +1082,16 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "walkdir" +version = "2.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.3.6" @@ -604,6 +1106,14 @@ name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -629,42 +1139,68 @@ dependencies = [ ] [metadata] +"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" +"checksum backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)" = "ada4c783bb7e7443c14e0480f429ae2cc99da95065aeab7ee1b81ada0419404f" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -"checksum bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3efe0b4c8eaeed8600549c29f538a6a11bf422858d0ed435b1d70ec4ab101190" +"checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91721a6330935673395a0607df4d49a9cb90ae12d259f1b3e0a3f6e1d486872e" "checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d" "checksum block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d75255892aeb580d3c566f213a2b6fdc1c66667839f45719ee1d30ebf2aea591" +"checksum bulletproofs 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3bc1edb4bc09df7d4afdcf9576bcdc70c80aeec096042bee80ea6b5d62e1621f" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum case 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e88b166b48e29667f5443df64df3c61dc07dc2b1a0b0d231800e07f09a33ecc1" +"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" "checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0363053954f3e679645fc443321ca128b7b950a6fe288cf5f9335cc22ee58394" +"checksum criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76f9212ddf2f4a9eb2d401635190600656a1f88a932ef53d06e7fa4c7e02fb8e" +"checksum crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "05e44b8cf3e1a625844d1750e1f7820da46044ff6d28f4d43e455ba3e5bb2c13" +"checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" +"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum csv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9044e25afb0924b5a5fc5511689b0918629e85d68ea591e5e87fbf1e85ea1b3b" +"checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65" "checksum curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1f8a6fc0376eb52dc18af94915cc04dfdf8353746c0e8c550ae683a0815e5c1" "checksum derive-error 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ec098440b29ea3b1ece3e641bac424c19cf996779b623c9e0f2171495425c2c8" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" "checksum liblmdb-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "feed38a3a580f60bf61aaa067b0ff4123395966839adeaf67258a9e50c4d2e49" "checksum lmdb-zero 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "13416eee745b087c22934f35f1f24da22da41ba2a5ce197143d168ce055cc58d" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum merlin 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8c39467de91b004f5b9c06fac5bbc8e7d28309a205ee66905166b70804a71fea" "checksum metadeps 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b122901b3a675fac8cecf68dcb2f0d3036193bc861d1ac0e1c337f7d5254c2" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" @@ -672,29 +1208,56 @@ dependencies = [ "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "03b418169fb9c46533f326efd6eed2576699c44ca92d3052a066214a8d828929" +"checksum rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4b0186e22767d5b9738a05eab7c6ac90b15db17e5b5f9bd87976dd7d89a10a4" +"checksum rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbe0df8435ac0c397d467b6cad6d25543d06e8a019ef3f6af3c384597515bd2" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum rmp 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a3d45d7afc9b132b34a2479648863aa95c5c88e98b32285326a6ebadc80ec5c9" "checksum rmp-serde 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)" = "011e1d58446e9fa3af7cdc1fb91295b10621d3ac4cb3a85cc86385ee9ca50cd3" +"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" "checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926" "checksum supercow 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "171758edb47aa306a78dfa4ab9aeb5167405bd4e3dc2b64e88f6a84bbe98bd63" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum tari_crypto 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cc90db87150c5d6575d7a5560ff6121d9e4067e183cca756462a1dd0d849c273" +"checksum tari_infra_derive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f347f846e83aa14207a979a0591d54e8a06168566f6e68f9900e6d671c352a82" +"checksum tari_utilities 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0d09a588c21d8dcc826d3c1fc0ada39a4242bf436452c3cae8f7e8b588821431" +"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4574b75faccaacddb9b284faecdf0b544b80b6b294f3d062d325c5726a209c20" "checksum toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7904a7e2bb3cdf0cf5e783f44204a85a37a93151738fa349f06680f59a98b45" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum zmq 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e6e33f05ebc9a1cb360e5db1f8ed6e5512ece86aed271654b0f171d04c24c23" "checksum zmq-sys 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3cc251d25f3c6ffc54dfa3e8d808598825f8ccfee3a008dfc7866ffe325dcb3" diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md new file mode 100644 index 0000000000..c496d06f80 --- /dev/null +++ b/RELEASE_CHECKLIST.md @@ -0,0 +1,13 @@ +# Point Release checklist + +THings to do before pushing a new commit to `master`: + +* Create new `rc` branch off development. +* Update crate version numbers +* Check that all tests pass in development (`cargo test`, `cargo test --release`) +* Publish new crates to crates.io (`./scripts/publish_crates.sh`) +* Rebase onto master +* Tag commit +* Write release notes on GitHub. +* Merge back into development (where appropriate) +* Delete branch diff --git a/RFC/src/AssetManagement.md b/RFC/src/AssetManagement.md new file mode 100644 index 0000000000..e3a4104ef6 --- /dev/null +++ b/RFC/src/AssetManagement.md @@ -0,0 +1,3 @@ +# Asset Management + +This section contains RFCs that describe various aspects of asset management in the Tari network. \ No newline at end of file diff --git a/RFC/src/BaseLayerExtensions.md b/RFC/src/BaseLayerExtensions.md new file mode 100644 index 0000000000..32d4c415af --- /dev/null +++ b/RFC/src/BaseLayerExtensions.md @@ -0,0 +1,9 @@ +# Tari-specific Base Layer Extensions + +This section covers RFCs that describe extensions to the Mimblewimble protocol that enable key functionality for the +Tari Base layer token and Digital Asset network. + +* [RFC-0220: Asset Checkpoints](RFC-0220_AssetCheckpoints.md) +* [RFC-0230: Time related transactions](RFC-0230_HTLC.md) +* [RFC-0322: Validator Node Registration](RFC-0322_VNRegistration.md) +* [RFC-0322: Asset Registration](RFC-0341_AssetRegistration.md) diff --git a/RFC/src/Glossary.md b/RFC/src/Glossary.md index aaf412de25..b030d94062 100644 --- a/RFC/src/Glossary.md +++ b/RFC/src/Glossary.md @@ -4,6 +4,17 @@ Below are a list of terms and their definitions that are used throughout the Tar glossary to disambiguate ideas, and work towards a [ubiquitous language](https://blog.carbonfive.com/2016/10/04/ubiquitous-language-the-joy-of-naming/) for this project. + +## Archive node +[archivenode]: #archive-node "a full history node" + +This is a full history [base node]. It will keep a complete history of every transaction ever received and it will not implement pruning. + +## AssetCollateral +[AssetCollateral]: #assetcollateral + +The amount of tari coin that a [Validator Node] must put up on the [base layer] in order to become part of an asset [committee]. + ## Asset Issuer [Asset Issuer]: #asset-issuer "An entity that creates digital assets on the Tari DAN" @@ -11,15 +22,16 @@ An entity that creates digital assets on the Tari DAN. The Asset Issuer will spe that defines the rules that govern the asset and the number and nature of its constituent tokens on issuance. The Asset Issuer will, generally, be the initial owner of the tokens. + ## Bad Actor [Bad Actor]: #bad-actor "A participant that acts maliciously or negligently to the detriment of the network or another participant" A participant that acts maliciously or negligently to the detriment of the network or another participant. + ## Base layer [Base Layer]: #base-layer "The Tari layer handling payments and secured by proof of work" - The Tari Base layer is a merge-mined [blockchain] secured by proof-of-work. The base layer is primarily responsible for the emission of new Tari, for securing and managing [Tari coin] transfers. @@ -52,38 +64,44 @@ A sequence of tari [block]s. Each block contains a hash of the previous valid bl with the property that changing anything in a block other than the head block requires rewriting the entire blockchain from that point on. -## Current head - -[currenthead]: #currenthead "The last valid block of the longest chain" -The last [block] of the base layer that represents the latest valid block. This [block] must be from the longest proof of work chain to be the current head. - -## Checkpoint +## Blockchain state +[blockchainstate]: #blockchain-state "This is a snapshot of how the blockchain looks" -[checkpoint]: #checkpoint "A summary of the state of a Digital Asset that is recorded on the base layer" +The complete state of the blockchain at a specific block height. This means a pruned [utxo] set, a complete set of kernels and headers up to that block height from the genesis block. -A hash of the state of a Digital Asset that is recorded on the base layer. ## BroadcastStrategy +[broadcast strategy]: #broadcaststrategy "Strategy used for broadcasting messages in a peer-to-peer network" A strategy for propagating messages amongst nodes in a peer-to-peer network. Example implementations of `BroadcastStrategy` include the Gossip protocol, Dandelion and flood fill. + +## Checkpoint +[checkpoint]: #checkpoint "A summary of the state of a Digital Asset that is recorded on the base layer" + +A hash of the state of a Digital Asset that is recorded on the base layer. + + ## Coinbase transaction [coinbase transaction]: #coinbase-transaction The first transaction in every Tari block yields a [Block Reward] according to the Tari [emission Schedule] and is awarded to the miner that performed the Proof of Work for the block. + ## Committee -[committee]: #committee "A group of validator nodes that are responsible for managing a specific Digital Asset" +[Committee]: #committee "A group of validator nodes that are responsible for managing a specific Digital Asset" A group of [Validator Node]s that are responsible for managing the state of a specific [Digital Asset]. A committee is selected during asset issuance and can be updated at [Checkpoint]s. + ## CommitteeSelectionStrategy -[CommitteeSelectionStrategy]: #committeeselectionstrategy "A strategy for the DAN to select candidates for the committee from the available registered Validator Nodes" -A strategy for the DAN to algorithmically select candidates for the committee from the available registered Validator Nodes. The VNs will need accept the nomination to become part of the committee. +[CommitteeSelectionStrategy]: #committeeselectionstrategy "A strategy for an Asset Issuer to select candidates for the committee from the available registered Validator Nodes who responded to the nomination call for that asset" +A strategy for an Asset Issuer to select candidates for the committee from the available registered Validator Nodes who responded to the nomination call for that asset. + ## ConsensusStrategy [ConsensusStrategy]: #consensusstrategy "The approach that will be taken for a committee to reach consensus on instructions" @@ -91,26 +109,37 @@ A strategy for the DAN to algorithmically select candidates for the committee fr The approach that will be taken for a committee to reach consensus on the validity of instructions that are performed on a given Digital Asset. + ## Commitment -[commitment]: #commitment +[Commitment]: #commitment A commitment is a cryptographic primitive that allows one to commit to a chosen value while keeping it hidden from others, with the ability to reveal the committed value later. Commitments are designed so that one cannot change the value or statement after they have committed to it. + ## Communication Node -[communication node]: #communication-node 'A communication node that is responsible for maintaining the Tari communication network' +[Communication Node]: #communication-node "A communication node that is responsible for maintaining the Tari communication network" A Communication Node is either a Validator Node or Base Node that is part of the Tari communication network. It maintains the network and is responsible for forwarding and propagating joining requests, discovery requests and data messages on the communication network. -## Communication Clients -[communication client]: #communication-client 'A communication client that makes use of the Tari communication network, but does not maintain it' + +## Communication Client +[Communication Client]: #communication-client "A communication client that makes use of the Tari communication network, but does not maintain it" A Communication Client is a Wallet or Asset Manager that makes use of the Tari communication network to send joining and discovery requests. A Communication Client does not maintain the communication network and is not responsible for forwarding or propagating any requests or data messages. -## Creator Assigned Mode -An asset runs in creator-assigned mode when _every_ validator node in a validator committee is a [Trusted Node]. +## Creator Nomination Mode +[creator nomination mode]: #creator-nomination-mode "An asset runs in creator nomination mode when _every_ validator node in a validator committee is a [Trusted Node] that was directly nominated by the AI." + +An asset runs in creator nomination mode when _every_ validator node in a validator committee is a [Trusted Node] that was directly nominated by the [Asset Issuer]. + + +## Current head +[currenthead]: #current-head "The last valid block of the longest chain" + +The last [block] of the base layer that represents the latest valid block. This [block] must be from the longest proof-of-work chain to be the current head. ## Digital asset @@ -128,12 +157,14 @@ created by [asset issuer]s on the Tari 2nd layer. For example, a promoter might The Tari second layer. All digital asset interactions are managed on the Tari Digital Assets Network (DAN). These interactions (defined in [instruction]s) are processed and validated by [Validator Node]s. + ## DigitalAssetTemplate [DigitalAssetTemplate]: #digitalassettemplate "A set of non-turing complete contract types supported by the DAN" A DigitalAssetTemplate is one of a set of contract types supported by the DAN. These contracts are non-turing complete and consist of rigid rule-sets with parameters that can be set by Asset Issuers. + ## Digital asset tokens [tokens]: #digital-asset-tokens 'or just, "tokens". The tokens associated with a given digital asset. Tokens are created by asset issuers' @@ -143,12 +174,10 @@ asset. Depending on the DA created, tokens can represent tickets, in-game items, are bound to the [digital asset] that created them. -## Instructions -[instruction]: #instructions "Second-layer network commands for managing digital asset state" +## Hashed Time Locked Contract +[htlc]: #hashed-time-locked-contract 'or just, "HTLC".' -Instructions are the [digital asset network] equivalent of [transaction]s. Instructions are issued by asset issuers and -client applications and are relayed by the DAN to the [validator node]s that are managing the associated -[digital asset]. +A time locked contract that only pays out after a certain criteria has been met or refunds the originator if a certain period has expired. ## Emission schedule @@ -158,11 +187,19 @@ An explicit formula as a function of the block height, _h_, that determines the _h_th block. +## Instructions +[instruction]: #instructions "Second-layer network commands for managing digital asset state" + +Instructions are the [digital asset network] equivalent of [transaction]s. Instructions are issued by asset issuers and +client applications and are relayed by the DAN to the [validator node]s that are managing the associated +[digital asset]. + + ## Mempool -[mempool]: #mempool +[mempool]: #mempool "A memory pool for unconfirmed transactions on the base layer" -The mempool is the set of transactions that have been validated by a base node, but have not yet been included in the -longest proof-of-work chain. Miners usually draw from the mempool to build up transaction [block]s. +The mempool consists of the transaction pool, pending pool, orphan pool and reorg pool, and is responsible for managing unconfirmed transactions that have not yet been included in the +longest proof-of-work chain. Miners usually draw verified transactions from the mempool to build up transaction [block]s. ## Mimblewimble @@ -184,10 +221,43 @@ A Mining Server is responsible for constructing new blocks by bundling transacti A Mining Worker is responsible for performing Proof-of-Work tasks received from its parent [Mining Server]. + +## Multisig +[multisig]: #multisig + +Multi-signatures (Multisigs) are also known as N-of-M signatures, this means that a minimum of N number of the M peers need to agree before a transaction can be spent. N and M can be equal; which is a special case and is often referred to as an N-of-N Multisig. + +[TLU musig]() + + ## Node ID [node ID]: #node-id -A node ID is a unique identifier that specifies the location of a [Consensus Node] or [Consensus Client] in the Tari communication network. The node ID can either be obtained from registration on the [Base Layer] or can be derived from the public identification key of a [Consensus Node] or [Consensus Client]. +A node ID is a unique identifier that specifies the location of a [communication node] or [communication client] in the +Tari communication network. The node ID can either be obtained from registration on the [Base Layer] or can be derived +from the public identification key of a [communication node] or [communication client]. + + +## Orphan Pool +[orphan pool]: #orphan-pool "A pool in the Mempool for unconfirmed transactions that attempt to spend non-existent UTXOs" + +The orphan pool is part of the [mempool] and manages all [transaction]s that have been verified but attempt to spend [UTXO]s that do not exist or haven't been created yet. + + +## Pending Pool +[pending pool]: #pending-pool "A pool in the Mempool for unconfirmed transactions with time-lock restrictions" + +The pending pool is part of the [mempool] and manages all [transaction]s that have a time-lock restriction on when it can be processed or attempts to spend [UTXO]s with time-locks. + + +## Pruning horizon +[pruninghorizon]: #pruning-horizon "Block height at which pruning will commence" + +This is a local setting for each node to help reduce syncing time and bandwidth. This is the number of blocks from the chain tip beyond which a chain will be pruned. + +## Public Nomination Mode +[public nomination mode]: #public-nomination-mode +An asset runs in public nomination mode when the [Asset Issuer] broadcasts a call for nominations to the network and VNs from the network nominate themselves as candidates to become members of the [committee] for the asset. The [Asset Issuer] will then employ the [CommitteeSelectionStrategy] to select the committee from the list of available candidates. ## Range proof [range proof]: #range-proof @@ -195,26 +265,41 @@ A node ID is a unique identifier that specifies the location of a [Consensus Nod A mathematical demonstration that a value inside a [commitment] (i.e. it is hidden) lies within a certain range. For [Mimblewimble], range proofs are used to prove that outputs are positive values. -## RegistrationCollateral -[RegistrationCollateral]: #registrationcollateral "An amount of tari coin that is locked up on the base layer when a [Validator Node] is registered" -An amount of tari coin that is locked up on the base layer when a [Validator Node] is registered. In order to make Sybil attacks expensive and to -provide an authorative base layer registry of [validator node]s they will need to lock up a amount of [Tari Coin] on the [Base Layer] using a -registration transaction to begin acting as a VN on the DAN. +## Registration Deposit +[Registration Deposit]: #registration-deposit "An amount of tari coin that is locked up on the base layer when a [Validator Node] is registered" -## RegistrationTerm -[RegistrationTerm]: #registrationterm "The minimum amount of time that a VN registration lasts" +An amount of tari coin that is locked up on the base layer when a [Validator Node] is registered. In order to make Sybil +attacks expensive and to provide an authorative base layer registry of [validator node]s they will need to lock up a +amount of [Tari Coin] on the [Base Layer] using a registration transaction to begin acting as a VN on the DAN. -The minimum amount of time that a VN registration lasts, the RegistrationCollateral can only be released after this minimum period has elapsed. -## SynchronisationStrategy +## Registration Term +[Registration Term]: #registration-term "The minimum amount of time that a VN registration lasts" -The generalised approach for a [Base Node] to obtain the current state of the blockchain from the peer-to-peer network. -Specific implementations may differ based on different trade-offs and constraints with respect to bandwidth, local -network conditions etc. +The minimum amount of time that a VN registration lasts, the [Registration Deposit] can only be released after this +minimum period has elapsed. + + +## Reorg Pool +[reorg pool]: #reorg-pool "A backup pool in the Mempool for unconfirmed transactions that have been included in blocks" + +The reorg pool is part of the [mempool] and stores all [transaction]s that have recently been included in blocks in case a blockchain reorganization occurs and the transactions need to be restored to the [transaction pool]. + + +## Spending Key +[spending key]: #spending-key + +A private spending key is a private key that permits spending of a [UTXO]. It is also sometimes referred to as a +Blinding Factor, since is Tari (and Mimblewimble) outputs, the value of a UTXO is _blinded_ by the spending key: + +$$ C = v.H + k.G $$ + +The public key, \\(P = k.G\\) is known as the _public_ spending key. ## SynchronisationState +[SynchronisationState]: #synchronisationstate The current synchronisation state of a [Base Node]. This can either be * `new` - The blockchain state is empty and is waiting for synchronisation to begin. @@ -222,20 +307,14 @@ The current synchronisation state of a [Base Node]. This can either be * `synchronised` - The blockchain state has synchronised with the rest of the network and is in a position to validate transactions. -## Transaction -[transaction]: #transaction "Base layer tari coin transfers." - -Transactions are activities recorded on the Tari [blockchain] running on the [base layer]. Transactions always involve a -transfer of [Tari coin]s. - -## ValidationState -Transactions or blocks are `unvalidated` when first received by a [Base Node]. After validation, they are either -`rejected` or `validated`. +## SynchronisationStrategy +[SynchronisationStrategy]: #synchronisationstrategy -`Validated` transactions can be added to the [mempool] and propagated to peers. +The generalised approach for a [Base Node] to obtain the current state of the blockchain from the peer-to-peer network. +Specific implementations may differ based on different trade-offs and constraints with respect to bandwidth, local +network conditions etc. -`Validated` blocks are added to the [blockchain] and propagated to peers. ## Tari Coin [tari coin]: #tari-coin "The base layer token" @@ -243,15 +322,31 @@ Transactions or blocks are `unvalidated` when first received by a [Base Node]. A The base layer token. Tari coins are released according to the [emission schedule] on the Tari [base layer] [blockchain] in [coinbase transaction]s. + +## Transaction +[transaction]: #transaction "Base layer tari coin transfers." + +Transactions are activities recorded on the Tari [blockchain] running on the [base layer]. Transactions always involve a +transfer of [Tari coin]s. + + +## Transaction Pool +[transaction pool]: #transaction-pool "A pool in the Mempool for valid and verified unconfirmed transactions" + +The transaction pool is part of the [mempool] and manages all [transaction]s that have been verified, that spend valid [UTXO]s and don't have any time-lock restrictions. + + ## Trusted Node [trusted node]: #trusted-node "A permissioned Validator Node nominated by an Asset Issuer" A permissioned Validator Node nominated by an Asset Issuer that will form part of the committee for that Digital Asset. + ## Token Wallet [token wallet]: #token-wallet "An Asset Manager Wallet for Tari Assets and Tokens" -A Tari Token Wallet is responsible for managing [Digital asset]s and [Tokens], and for constructing and negotiating [instructions]s for transferring and receiving Assets and Tokens on the [Digital Asset Network]. +A Tari Token Wallet is responsible for managing [Digital asset]s and [Tokens], and for constructing and negotiating [instruction]s for transferring and receiving Assets and Tokens on the [Digital Asset Network]. + ## Unspent transaction outputs [utxo]: #unspent-transaction-outputs @@ -265,33 +360,29 @@ UTXO values are hidden by their [commitment]s. Only the owner of the UTXO and (p ## Validator Node -[validator node]: #validator-node "A second-layer node that manages and validates digital asset state transitions" +[Validator Node]: #validator-node "A second-layer node that manages and validates digital asset state transitions" Validator nodes (VNs) make up the Tari second layer, or [Digital Asset Network]. VNs are responsible for creating and updating [digital asset]s living on the Tari network. -## Wallet -[wallet]: #wallet "A Wallet for Tari coins" -A Tari Wallet is responsible for managing key pairs, and for constructing and negotiating [transaction]s for transferring and receiving [tari coin]s on the [Base Layer]. - -## Pruning horizon - -[pruninghorizon]: #pruninghorizon "Block height at which pruning will commence" +## ValidationState +[ValidationState]: #validationstate +Transactions or blocks are `unvalidated` when first received by a [Base Node]. After validation, they are either +`rejected` or `validated`. -This is a local setting for each node to help reduce syncing time and bandwidth. This is the number of blocks from the chain tip beyond which a chain will be pruned. +`Validated` transactions can be added to the [mempool] and propagated to peers. -## Archive node +`Validated` blocks are added to the [blockchain] and propagated to peers. -[archivenode]: #archivenode "a full history node" -This is a full history [base node]. It will keep a complete history of every transaction ever received and it will not implement pruning. +## Wallet +[wallet]: #wallet "A Wallet for Tari coins" +[Registration Deposit]: #registration-deposit -## Blockchain state -[blockchainstate]: #blockchainstate "This is a snapshot of how the blockchain looks" +A Tari Wallet is responsible for managing key pairs, and for constructing and negotiating [transaction]s for transferring and receiving [tari coin]s on the [Base Layer]. -This is a complete snapshot of the blockchain at a spesific block height. This means pruned [utxo], complete set of kernals and headers up to that block height from genesis block is known. # Disclaimer diff --git a/RFC/src/RFC-0001_overview.md b/RFC/src/RFC-0001_overview.md index 93a1171798..8a659fc96a 100644 --- a/RFC/src/RFC-0001_overview.md +++ b/RFC/src/RFC-0001_overview.md @@ -60,19 +60,24 @@ The aim of this proposal is to provide a very high-level perspective for the mov ### Abstract -The Tari network is composed of two layers: +The Tari network is composed of three layers: 1. The base layer deals with [Tari coin] [transaction]s. It governed by a proof-of-work blockchain that is merged-mined with Monero. The base layer is highly secure, decentralised and relatively slow. -2. The digital assets network runs on a second layer. This layer manages all things to do with native digital assets. It - is built for liveness, speed and scalability at the expense of decentralisation. +2. A multiparty payments channel allows rapid, secure, low cost off-chain payments that are periodically settled on the + base layer. +3. The digital assets network. This layer manages all things to do with native digital assets. It is built for liveness, + speed and scalability at the expense of decentralisation. + + +![Tari Network Overview](theme/images/tari_network_overview.png) ### Currency tokens and digital assets There are two major digital entities on the Tari network: The coins that are the unit of transfer for the Tari cryptocurrency, and the digital assets that could represent anything from tickets to in-game items. -Tari coins are the fuel that drives the entire Tari ecosystem. But they share many of the properties of money, and so +Tari coins are the fuel that drives the entire Tari ecosystem. They share many of the properties of money, and so security is a non-negotiable requirement. In a cryptocurrency context, this is usually achieved by employing a decentralised network running a censorship-resistant protocol like Nakamoto consensus over a proof of work blockchain. As we know, proof of work blockchains are not scalable, or terribly fast. @@ -91,21 +96,24 @@ state updates because centralised solutions offer them that today. Therefore the Tari digital assets network must offer speed and scalability. -#### Two layers +#### Multiple layers The [distributed system trilemma](https://en.wikipedia.org/wiki/CAP_theorem) tells us that these requirements are mutually exclusive. We can't have fast, cheap digital assets and also highly secure and decentralised currency tokens on a single system. -Tari overcomes this constraint by building two layers: +Tari overcomes this constraint by building three layers: -* A base layer that provides a public ledger of Tari coin transactions, secured by proof of work to maximise security, and -* A second layer that manages digital asset state that is very fast and cheap, at the expense of decentralisation. +* A base layer that provides a public ledger of Tari coin transactions, secured by proof of work to maximise security, +* A multiparty payment channel, allowing funds to be sent to parties in the channel instantly, securely and with very + low fees. +* A digital asset layer that manages digital asset state that is very fast and cheap, at the expense of + decentralisation. If required, the digital asset layer can refer back to the base layer to temporarily give up speed in exchange for -increased security. This fallback is used to resolve consensus issues on the second layer that may crop up from time to -time as a result of the lower degree of decentralisation. +increased security. This fallback is used to resolve consensus issues on the digital asset layer that may crop up from +time to time as a result of the lower degree of decentralisation. ### The Base Layer @@ -148,6 +156,11 @@ Given Tari's relationship with Monero, a merge-mined strategy with Monero makes SHOULD be written in a way that makes it relatively easy to code, implement and switch to a different strategy in the future. +### Multiparty Payment Channels + +Further details about the Tari multiparty payment channel technology are given in +[RFC-500/PaymentChannels](RFC-0500_PaymentChannels.md). + ### The Digital Assets Network A more detailed proposal for the digital assets network is presented in [RFC-0300/DAN](RFC-0300_DAN.md). Digital assets @@ -209,14 +222,14 @@ well-functioning network even while acting in their own self-interests. Table 1 summarises the defining characteristics of the Tari network layers: -| | Base layer | DAN | -|:-------------------------------------|:-----------------|:-----------------------| -| Speed | Slow | Fast | -| Scalability | Moderate | Very high | -| Security | High | Mod (High w/ fallback) | -| Decentralisation | High | Low - Med | -| Processes Tari coin transactions | Yes | No | -| Processes digital asset instructions | Only checkpoints | Yes | +| | Base layer | Payment Channels | DAN | +|:-------------------------------------|:-----------------|:-----------------|:-----------------------| +| Speed | Slow | Fast | Fast | +| Scalability | Moderate | High | Very high | +| Security | High | High | Mod (High w/ fallback) | +| Decentralisation | High | Low - Med | Low - Med | +| Processes Tari coin transactions | Yes | Yes | No | +| Processes digital asset instructions | Only checkpoints | No | Yes | [Tari coin]: Glossary.md#tari-coin diff --git a/RFC/src/RFC-0010_CodeStructure.md b/RFC/src/RFC-0010_CodeStructure.md index 133b769db7..445596cb9b 100644 --- a/RFC/src/RFC-0010_CodeStructure.md +++ b/RFC/src/RFC-0010_CodeStructure.md @@ -102,7 +102,7 @@ domain layer directories, corresponding to the two network layers that make up t - `crypto`: All cryptographic services, including a Curve25519 implementation - `storage`: Data persistence services, including an LMDB persistence implementation - `merklemountainrange`: An independant implementation of a merkle mountain range - - `derive`: A crate to contain #[derive(...)] macros + - `derive`: A crate to contain `derive(...)` macros 1. `base_layer` is a domain-layer directory and contains: - `blockchain`: The Tari consensus code diff --git a/RFC/src/RFC-0110_BaseNodes.md b/RFC/src/RFC-0110_BaseNodes.md index 5ca68ef645..eac8dc6c0d 100644 --- a/RFC/src/RFC-0110_BaseNodes.md +++ b/RFC/src/RFC-0110_BaseNodes.md @@ -10,7 +10,7 @@ [ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). -Copyright 2018 The Tari Development Community +Copyright 2019 The Tari Development Community Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -109,7 +109,7 @@ The transaction validation service checks that: * all inputs are signed by their owners. * all outputs have valid [range proof]s. * no outputs currently exist in the [UTXO] set. -* the transaction does not have a timelock applied, limiting it from being added to the blockchain before a specified block height or timestamp has been reached. +* the transaction does not have a [timelock](timelocks) applied, limiting it from being mined and added to the blockchain before a specified block height or timestamp has been reached. * the transaction excess has a valid signature. * the transaction excess is a valid public key. This proves that: $$ \Sigma \left( \mathrm{inputs} - \mathrm{outputs} - \mathrm{fees} \right) = 0 $$ @@ -119,6 +119,7 @@ The transaction validation service checks that: `Timelocked` transactions are: * marked with a timelocked status and gets added to the [mempool]. * will be evaluated again at a later state to determine if the timelock has passed and if it can be upgraded to 'Validated' status. +* More detailed information in [RFC-0230](timelocks) `Validated` transactions are: * Added to the [mempool]. @@ -189,3 +190,4 @@ Syncing, pruning and cut-through is discussed in detail in [RFC-0140](RFC-0140_S [SynchronisationState]: Glossary.md#synchronisationstate [mining server]: Glossary.md#mining-server [cut-through]: RFC-0140_Syncing.md#Pruning-and-cut-through +[timelocks]: RFC-0230_HTLC.md#Time-Locked-contracts diff --git a/RFC/src/RFC-0151_TransactionProtocol.md b/RFC/src/RFC-0151_TransactionProtocol.md new file mode 100644 index 0000000000..c19c0592d0 --- /dev/null +++ b/RFC/src/RFC-0151_TransactionProtocol.md @@ -0,0 +1,268 @@ +# TransactionProtocol + +## Transaction protocol + +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: Cayle Sharrock + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2019. The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals + +This document describes the transaction protocol for peer-to-peer Tari payments using the Mimblewimble protocol. It also +considers some attacks that may be launched against the protocol and offers some discussion around those attacks and +potential alternatives to the protocol. + +The goal is to describe a transaction protocol that +* permits multiple recipients, +* preserves privacy regarding how many parties are involved in the transaction, +* is secure against all reasonable attacks. + +## Related RFCs + +* [RFC-0100: The Base Layer](RFC-0100_BaseLayer.md) + +## Description + +The Tari base layer is built using [Mimblewimble], which requires that all parties involved in a Tari transfer must interact +to construct a valid [Mimblewimble transaction]. + +A valid transaction involves: + +* A set of one or more inputs being spent by the Sender, +* A set of zero or more outputs being sent to the Sender, +* A set of recipients, each of whom MUST construct exactly one output, +* A set of partial schnorr signatures that when aggregated, validates the transaction construction and indicates every + party's satisfaction with the terms. + +### The issue with multiple recipients + +Each party involved in a Tari transaction must produce a partial signature signing the same challenge. This challenge is +defined as + +$$ e = H(\Sigma R_i \Vert \Sigma P_i \Vert m) $$ + +where \\(R_i\\) are public nonces generated by each party for this signature; \\(P_i\\) are the public [Spending Key]s; +and _m_ is the additional metadata for the transaction. \\(\Sigma P_i\\) is the value of the (pre-offset) excess that is +stored in the transaction kernel. + +Notice that every signing party needs to know the sum of all the nonces and public spending keys. This suggests that +every party knows how many parties are involved in the transaction, which is not an ideal privacy scenario. It would be +preferable if a secure scheme could be found where each recipient interacts only with the sender and does not need to +calculate these sums themselves. + +Unfortunately, as we discuss below, it seems that it's not possible using any known scheme that satisfies both this +privacy goal while achieving the desired security level. + + +### The issue with multiple senders + +To increase privacy, the public excess values are [offset] by a constant random value. The choice of this value, as well +as fee selection, can only be set once per transaction. The privilege of selecting these values is generally bestowed on +the sender, since she pays the fee. Allowing multiple sending parties (or equivalently, allowing recipients to provide +inputs) would require a negotiation round to set the fee and offset before the transaction could be constructed. This is +a complication we don't want to deal with, and so all schemes presented here allow exactly one sender. + +### Two-party transactions + +Two party transactions are fairly straightforward and are described in detail on TLU. (See [Mimblewimble transaction]). + +It is proposed that Tari implement this single-round 2-party transaction scheme as a special case to support both online +2-party transactions as well as "offline" transactions such as via e-mail, text-message, carrier pigeon etc. + + +### Multiple recipient transaction scheme + +
+sequenceDiagram + participant Sender + participant Receivers +# Sender Initializes the transaction + activate Sender + Sender-->>Sender: initialize + deactivate Sender +# Sender transmits initial data to all receivers + activate Sender + Sender-->>+Receivers: [tx_id, amt_i,H(Rs||Xs),m] + note left of Sender: CollectingCommitments + note right of Receivers: SendingCommitment + Receivers-->>-Sender: [tx_id, H(Ri||Pi)] + deactivate Sender +# Receiving Outputs + activate Sender + Sender-->>+Receivers: [tx_id, [Hashed commitments]] + note left of Sender: CollectingPubKeys + note right of Receivers: SendingOutput + Receivers-->>-Sender: [tx_id, Pi, Ri, C_i, RP_i] + deactivate Sender + alt inflation_error()? + Sender--XReceivers: [tx-id, failed] + end +# Signature collection + activate Sender + Sender-->>+Receivers: [tx_id, [Rs, Ri] [Xs, Pi]] + note left of Sender: CollectingSignatures + note right of Receivers: Signing + Receivers-->>Receivers: validate nonces, pubkeys and pubkey sum + alt invalid + Receivers--XSender: failed + end + Receivers-->>Receivers: Sign + Receivers-->>-Sender: [tx_id, s_i] + deactivate Sender +# Sender sends final notification of result + note left of Sender: Finalizing + alt is_valid() + Sender-->>Receivers: [tx_id, OK] + else invalid + Sender--XReceivers: [tx_id, Failed] + end +
+ +** Legend ** + +| Symbol | Meaning | +|:-------|:------------------------------| +| tx_id | Transaction identifier | +| amt_i | Amount sent to i-th recipient | +| Rs, Ri | Public nonce | +| Xs, Pi | Public excess / key | +| m | Message metadata | +| C_i | Commitment | +| RP_i | Range proof | +| \[..\] | Vector of data | + +## Transaction ID + +The scheme above makes use of a `tx_id` field in every peer-to-peer message. Since all messages are stateless and +asynchronous, peers need some way of figuring out which message refers to which transaction. The transaction id fulfils +this role. + +The ID does not appear on the blockchain in any manner; is purely used to disambiguate Tari transaction messages; and +can be discarded after the transaction is broadcast to the network. + +The `tx_id` is unique for every receiver so that any observers of the communication won't be able to group receivers +together (the communication should be over secure channels in general though). + + The format of the transaction ID is a 4-byte little endian integer (u64) and is +calculated as + +```text +H(Rs||i)[0..4] +``` + +where `i` is the i-th recipient in the transaction. The sender can use the `tx_id` as a hash map key to identify and +differentiate recipients. + + +## Replay attacks + +If any party can be convinced to sign a different message with the same nonce, their private keys will be lost. One way +of achieving this would be if a virtual machine could be "snapshotted" or otherwise cloned at any point between sharing +the public nonce and signing the message. Both copies of the victim's machine will now continue unaware that there's a +copy participating in a signature round. What then happens is: + +$$ + \begin{align} + &\text{Clone A} & &\text{Clone B} \\\\ + e_1 &= H(r_1 \Vert r_s \Vert \dots) & e_2 &= H(r_1 \Vert r_s^* \Vert \dots) \\\\ + s_1 &= r_1 + e_1 \cdot k_1 & s_2 &= r_1 + e_2 \cdot k_1 \\\\ + \end{align} +$$ + +The attacker receives both signatures and trivially calculates the secret key: + +$$ + \begin{align} + \Delta s &= s_1 - s_2 \\\\ + &= k_1(e_1 - e_2) = k_1\Delta e \\\\ + \Rightarrow k_1 &= \frac{\Delta s}{\Delta e} + \end{align} +$$ + +We've demonstrated this with the attacker changing his nonce, but literally any alteration to the challenge will provide +a new challenge \\(e_2\\), enabling the attack. + +What can we do about this? In fact, it's not possible to eliminate this attack at all! The reason sits with the proof +that the Schnorr scheme works as a zero-knowledge protocol; the demonstration of this proof is precisely the attack +we're trying to avoid [[GOL19]]. If we could eliminate this attack, we'd need to come up with a completely different way +of proving the zero-knowledge property. + +So we can't stop it, but we can make it as tricky as possible for the attacker to trick the receiver into replaying the +signature -- MuSig does this by requiring parties to share the hash of their nonces beforehand. At it's extreme; in the +two-party single-round scheme for example; the attacker would need to be able to control the victim's machine code +execution (like running a debugger), at which point one might think the attacker could read the private key directly +from memory anyway. + +## Rogue key attacks + +Another type of attack that can occur in multi-signature schemes are what's known as +[Rogue Key attacks](https://tlu.tarilabs.com/cryptography/digital_signatures/introduction_schnorr_signatures.html#key-cancellation-attack). + +In this case, the attacker has the freedom to choose a key or nonce _after_ the victim has already disclosed his. This +may allow the attacker to forge a valid signature on behalf of the victim. A recent paper, [[DRI19]]], suggests that +_any_ Schnorr-based 2-round multi-signature scheme is vulnerable to a rogue key attack. + +How this might apply in an insecure 2-round Tari multi-signature scheme is as follows: A receiver sends his public +nonce, output commitment and range proof, and public spending key to the sender, but then decides to cancel the +transaction by refusing to provide a signature and sending an "Abort" message to the sender instead. The sender could, +if he wanted, forge the 2-of-2 signature using this rogue key attack and broadcast the transaction anyway. + +_Note:_ This attack is not applicable in the one-round 2-party scheme since the receiver returns his information in an +all-or-nothing manner. However, the _receiver_ could attempt to forge a signature, since he has the Sender's public +nonce, but there's nothing he can really do with this signature; he certainly cannot broadcast a transaction with it +because he doesn't have any of the transaction data at this stage. + +We avoid rogue-key attacks in the Tari multi-recipient scheme by employing 3-rounds. In the first round, parties share a +hash of their public nonces, which each party can later use to verify that no nonces were changed after the actual +public nonces were shared. + + + +[Mimblewimble]: https://tlu.tarilabs.com/protocols/grin-protocol-overview/MainReport.html "Mimblewimble on TLU" +[Mimblewimble transaction]: https://tlu.tarilabs.com/protocols/mimblewimble-1/MainReport.html "Mimblewimble +transactions on TLU" +[Spending Key]: Glossary.md#spending-key +[offset]: https://github.com/mimblewimble/grin/blob/master/doc/intro.md#kernel-offsets +[DRI19]: https://eprint.iacr.org/2018/417.pdf "On the Security of Two-Round Multi-Signatures" +[GOL19]: https://medium.com/magicofc/interactive-proofs-and-zero-knowledge-b32f6c8d66c3 "On Interactive Proofs and +Zero-Knowledge: A Primer [Section 3]" diff --git a/RFC/src/RFC-0170_NetworkCommunicationProtocol.md b/RFC/src/RFC-0170_NetworkCommunicationProtocol.md index 055d34e309..0920794850 100644 --- a/RFC/src/RFC-0170_NetworkCommunicationProtocol.md +++ b/RFC/src/RFC-0170_NetworkCommunicationProtocol.md @@ -1,10 +1,10 @@ -# RFC-0170: Network Communication Protocol +# RFC-0170/NetworkCommunicationProtocol ## The Tari Communication Network and Network Communication Protocol ![status: draft](theme/images/status-draft.svg) -**Maintainer(s)**: [Yuko Roodt] (https://github.com/neonknight64) +**Maintainer(s)**: [Yuko Roodt](https://github.com/neonknight64) # License @@ -155,15 +155,15 @@ Each CC and CN on the Tari communication network will have identification crypto The online communication address SHOULD be either an IPv4, IPv6, URL, Tor (Base32) or I2P (Base32) address and can be stored using the network address type as follows: | Description | Data type | Comments | -|--- |--- |--- | +|:-------------|:-----------|:----------------------------------------------------| | address type | uint4 | Specify if IPv4/IPv6/Url/Tor/I2P | | address | char array | IPv4, IPv6, URL, Tor (Base32), I2P (Base32) address | | port | uint16 | port number | -The address type is used to determine how to interpret the address characters. An I2P address can be interpreted as "{52 address characters}.b32.i2p". -The Tor address should be interpreted as "http://{16 or 52 address chars}.onion/". -The IPv4 and IPv6 address can be stored in the address field without modification. -URL addresses can be used for nodes with dynamic IP addresses. +The address type is used to determine how to interpret the address characters. An I2P address can be interpreted as "{52 +address characters}.b32.i2p". The Tor address should be interpreted as "http://{16_or_52_address_chars}.onion/". The +IPv4 and IPv6 address can be stored in the address field without modification. URL addresses can be used for nodes with +dynamic IP addresses. A Tor or I2P address can be used when anonymity is important for a CC or CN. The IPv4, IPv6 and URL address types do not provide any privacy features but do provide increased bandwidth. @@ -177,15 +177,15 @@ The routing table consists of a list of peer addresses that link node IDs, publi The Peer Address stored in the routing table MAY be implemented as follows: -| Description | Data type | Comments | -|--- |--- |--- | -| network address | network_address | The online communication address of the CC or CN | -| node_ID | node_ID | Registration Assigned for VN, Self selected for BN, W and TW | -| public_key | public_key | The public key of the identification cryptographic key of the CC or CN | -| node_type | node_type | VN, BN, W or TW | -| linked asset IDs | list of asset IDs | Asset IDs can be used as an address on Tari network similar to a node ID | -| last_connection | timestamp | Time of last successful connection with peer | -| update_timestamp | timestamp | A timestamp for the last peer address update | +| Description | Data type | Comments | +|:-----------------|:------------------|:-------------------------------------------------------------------------| +| network address | network_address | The online communication address of the CC or CN | +| node_ID | node_ID | Registration Assigned for VN, Self selected for BN, W and TW | +| public_key | public_key | The public key of the identification cryptographic key of the CC or CN | +| node_type | node_type | VN, BN, W or TW | +| linked asset IDs | list of asset IDs | Asset IDs can be used as an address on Tari network similar to a node ID | +| last_connection | timestamp | Time of last successful connection with peer | +| update_timestamp | timestamp | A timestamp for the last peer address update | When a new CC or CN wants to join the Tari communication network they need to submit a joining request to the rest of the network. The joining request contains the peer address of the new CC or CN. diff --git a/RFC/src/RFC-0172_PeerToPeerMessagingProtocol.md b/RFC/src/RFC-0172_PeerToPeerMessagingProtocol.md new file mode 100644 index 0000000000..b56f25172a --- /dev/null +++ b/RFC/src/RFC-0172_PeerToPeerMessagingProtocol.md @@ -0,0 +1,718 @@ +# RFC-0172/PeerToPeerMessaging + +## Peer to Peer Messaging Protocol + +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: [Stanley Bondi](https://github.com/sdbondi), [Cayle Sharrock](https://github.com/CjS77), [Yuko Roodt](https://github.com/neonknight64) + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2019. The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals + +This document describes the peer-to-peer messaging protocol for [communication node]s and [communication client]s on the Tari network. + +## Related RFCs + +- [RFC-0170: NetworkCommunicationProtocol](RFC-0170_NetworkCommunicationProtocol.md) +- [RFC-0171: Message Serialisation] + +## Description + +### Assumptions + +- Either every [communication node] or [communication client] has access to a Tor/I2P proxy, or a native Tor/I2P implementation + exists which allows communication across the Tor network. +- All messages are de/serialized as per [RFC-0171: Message Serialisation] + +### Broad requirements + +Tari network peer communication must facilitate secure, private and efficient communication +between peers. Broadly, a [communication node] or [communication client] MUST be capable of + +- bi-directional communication between multiple connected peers, +- private and secure over the wire communication, +- understanding and constructing Tari messages, +- encrypting and decrypting message payloads, +- gracefully reestablishing dropped connections, +- and optionally, communicating to a SOCKS5 proxy (for connections over Tor and I2P). + +Additionally, [communication node]s MUST be capable of performing the following tasks: + +- Open a control port for establishing secure peer channels. +- Maintain a list of known peers in the form of a routing table. +- Forward directed messages to neighbouring peers. +- Broadcast messages to neighbouring peers. + +### Overall Architectural Design + +The Tari communication layer has a modular design to allow for the various communicating nodes and clients to +use the same infrastructure code. + +The design is influenced by open-source library called [ZeroMQ] and the [ZeroMQ] C bindings are a dependency of +the project. [ZeroMQ]'s over-the-wire protocol is relatively simple and replicating [ZeroMQ] framing in a custom +implementation should not be prohibitively difficult. However, there are many valuable features offered by +[ZeroMQ] which would be a significantly larger undertaking to reproduce. Fortunately, bindings or native ports are +available in numerous languages. + +To learn more about [ZeroMQ], read [the guide](http://zguide.zeromq.org/page:all). It's an enjoyable and worthwhile read. + +A quick overview of what [ZeroMQ] provides: + +- A simple socket API. +- Some well-defined patterns to connect sockets together. +- Sockets that are tiny asynchronous message queues which: + - abstract away complexity around the underlying socket, + - are transport agnostic, meaning you can choose between TCP, PGM, IPC and inproc transports with little or + no changes to code, + - and, they transparently reconnect when connections are dropped. +- The `inproc` transport for message passing between threads without mutex locks. +- Built-in protocol for asymmetric encryption over the wire using Curve25519. +- The ability to send and receive multipart messages using a simple framing scheme. [More info](http://zguide.zeromq.org/php:chapter2#toc11). +- Support for SOCKS proxies. + +This document will refer to several [ZeroMQ socket]s. These are referred to by prepending `ZMQ_` and the name +of the socket in capitals. For example, `ZMQ_ROUTER`. + +**_Note about [ZeroMQ] frames and multipart messages_** + +[ZeroMQ] frames are length-specified blocks of binary data and can be strung together to make multipart messages. + +```text +|5|H|E|L|L|O|*|0|*|3|F|O|O|+| +* = more flag ++ = no more flag +``` + +_A multipart message consisting of three frames "HELLO", a zero-length frame and "FOO"_ + +When this RFC mentions 'multipart messages', this is what it's referring to. + +#### Establishing a Connection + +Every participating [communication node] SHOULD open a control socket (see [ControlService]) to allow peers to negotiate and +establish a peer connection. The [NetAddress] of the control socket is what is stored in peer's routing tables and will +be used to establish new ephemeral [PeerConnection]s. Any peer that wants to connect MUST establish a connection +to the control socket of the destination peer to negotiate a new encrypted [PeerConnection]. + +Once a connection is established, messages can be sent and received directly to/from the [Peer]. + +Incoming messages are validated, deserialized and handled as appropriate. + +#### Encryption + +There are two forms of encryption which are used: + +- Over the wire encryption: encryption of traffic between nodes using zMQ's [CURVE](http://curvezmq.org/page:read-the-docs) + implementation. +- Payload encryption: the [MessageEnvelopeBody] is encrypted in such a way that only the destination recipient can decrypt it. + +### Components + +The following components are proposed: + +
+graph TD +CSRV[ControlService] +IMS[InboundMessageService] +PM[PeerManager] +CM[ConnectionManager] +BCS[BroadcastStrategy] +OMS[OutboundMessageService] +PC[PeerConnection] +NA[NetAddress] +STR[Storage] +PEER[Peer] +RT[RoutingTable] +IMS --> PM +IMS --> CM +CSRV --> CM +CSRV --> PM +OMS -->|execute| BCS +OMS --> PM +OMS --> CM +PM --> RT +PM --> STR +CM --> PC +PC -->|has one| PEER +PEER -->|has many| NA +
+ +#### NetAddress + +Represents one of the following: + +- an IP address, +- an Onion address, +- or, an I2P address. + +```rust,compile_fail +#[derive(Clone, PartialEq, Eq, Debug)] +/// Represents an address which can be used to reach a node on the network +pub enum NetAddress { + /// IPv4 and IPv6 + IP(SocketAddress), + Tor(OnionAddress), + I2P(I2PAddress), +} +``` + +#### Messaging structure + +This illustrates the structure of a Tari message. + +```text ++----------------------------------------+ +| MessageEnvelope | +| +----------------------------------+ | +| | MessageEnvelopeHeader | | +| +----------------------------------+ | +| +----------------------------------+ | +| | MessageEnvelopeBody | | +| | (optionally encrypted) | | +| | +------------------------------+ | | +| | | Message | | | +| | | +-----------------------+ | | | +| | | | MessageHeader | | | | +| | | +-----------------------+ | | | +| | | | | | +| | | +-----------------------+ | | | +| | | | MessageBody | | | | +| | | +-----------------------+ | | | +| | +------------------------------+ | | +| +----------------------------------+ | ++----------------------------------------+ +``` + +#### MessageEnvelope wire format + +Every Tari message MUST use the MessageEnvelope format. This format consists of four frames of a multipart message. + +A MessageEnvelope represents a message which has just come off, or is about to go on to the wire and consists of the following: + +| Name | Frame | Length (octets) | Type | Description | +| -------- | ----- | --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| identity | 0 | 8 | `[u8;8]` | The identifier that a `ZMQ_ROUTER` socket expects so that it knows the intended destination of the message. This can be thought of as a session token. | +| version | 1 | 1 | `u8` | The wire protocol version. | +| header | 2 | varies | `Vec` | Serialized bytes of data containing an unencrypted [MessageEnvelopeHeader]. | +| body | 3 | varies | `Vec` | Serialized bytes of data containing a unencrypted or encrypted [MessageEnvelopeBody]. | + +The header and decrypted body MUST be deserializable as per [RFC-0171: Message Serialisation](RFC-0171_MessageSerialisation.md) + +#### MessageEnvelopeHeader + +Every [MessageEnvelope] MUST have an unencrypted header containing the following fields: + +| Name | Type | Description | +| --------- | ------------------------- | -------------------------------------------------------------------------------------------------- | +| version | `u8` | The message protocol version. | +| source | `PublicKey` | The source public key. | +| dest | `Option` | The destination [node ID] or public key. A destination is optional. | +| signature | `[u8]` | Signature of the message header and body, signed with the private key of the source. | +| flags | `u8` |
  • bit 0: 1 indicates that the message body is encrypted
  • bits 1-7: reserved
| + +A [communication node] and [communication client]: + +- MUST validate the signature of the message using the source's public key. +- MUST reject the message if the signature verification fails. +- if the encryption bit flag is set: + - MUST attempt to decrypt the [MessageEnvelopeBody], or failing that + - MUST forward the message to a subset of peers using the `Closest` [BroadcastStrategy] + - MUST discard the message if the body is not encrypted + +#### MessageEnvelopeBody + +A MessageEnvelopeBody is the payload of the [MessageEnvelope]. A [MessageEnvelopeBody] may be encrypted as required. + +It consists of a [MessageHeader] and [Message] of a particular predefined [MessageType]. + +#### MessageType + +An enumeration of the messages which are part of the Tari network. [MessageType]s are represented +as an unsigned 8-bit integer and each value must be mapped to a corresponding [Message] struct. + +All [MessageType]s fall within a particular numerical range according to the message's concern: + +| Category | Range | # message types | Description | +| ------------ | ------- | --------------- | ---------------------------------------------------------------------- | +| reserved | 0 | 1 | Reserved for control messages such as `Ack` | +| `net` | 1-32 | 32 | Network-related messages such as `join` and `discover` | +| `peer` | 33-64 | 32 | Peer connection messages, such as `establish connection` | +| `blockchain` | 65-96 | 32 | Messages related to the blockchain, such as `add block` | +| `vn` | 97-224 | 128 | Messages related to the validator nodes, such as `execute instruction` | +| `extended` | 225-255 | 30 | Reserved for future use | + +In documentation, [MessageType]s can be referred to by the category and name. For example, `peer::EstablishConnection` and +`net::Discover`. + +#### MessageHeader + +Every Tari message MUST have a header containing the following fields: + +| Name | Type | Description | +| ------------ | ---- | ------------------------------------------------------------------ | +| version | `u8` | The message version. | +| message_type | `u8` | An enumeration of the message type of the body. See [MessageType]. | + +As this is part of the [MessageEnvelopeBody], it can be encrypted along with the rest of the message +which keeps the type of message private. + +#### MessageBody + +Messages are an intention to perform a task, and so MessageType names should be a verb like `net::Join` or `blockchain::AddBlock`. + +All messages can be categorized as follows, each categorization has rules for how they should be handled: + +- a propagation message + - SHOULD NOT have a destination in the MessageHeader + - MUST be forwarded + - SHOULD use the `Random` BroadcastStrategy + - SHOULD discard a message that they have seen within the [DuplicateMessageWindow] +- a direct message + - MUST have a destination in the MessageHeader + - SHOULD be discarded if it does not have a destination + - SHOULD discard a message that they have seen before + - if a destination peer is known, MUST use the `Direct` BroadcastStrategy + - otherwise, SHOULD use the `Closest` BroadcastStrategy +- an encrypted message + + - all recipients MUST attempt to decrypt the message + - recipients MUST forward a message which cannot be decrypted + - SHOULD discard a message that they have seen before + +The [MessageType] in the header MUST be used to determine the type of the message deserialize. +If the deserialization fails, the message SHOULD be discarded. + +#### DuplicateMessageWindow + +A configurable length of time for which message signatures should be tracked in order to eliminate duplicate messages. +This should be long enough to make it highly unlikely that a particular message will be processed again +and short enough to not be a burden on the node. + +#### InboundConnection + +A thin wrapper around a `ZMQ_ROUTER` socket which binds to a [NetAddress] and accepts incoming multipart messages. +This connection blocks until there is data to read, or a timeout is reached. In either case, the `receive` method +can be called again (i.e. in a loop) to continue listening for messages. Client code should run this loop in it's own thread. +`send` is only called (if at all) in response to an incoming message. + +Fields may include + +- a [NetAddress], +- a timeout, +- underlying [ZeroMQ socket]. + +Methods may include: + +- `receive()` +- `send(data)` +- `set_encryption(secret_key)` +- `set_socks_proxy(address)` +- `set_hwm(v)` + +An InboundConnection: + +- MUST perform the "server-side" [CurveZMQ](http://curvezmq.org/page:read-the-docs) encryption protocol if encryption is set. + - using [ZeroMQ] this means setting the socketopts `ZMQ_CURVE_SERVER` to 1 and `ZMQ_CURVE_SECRETKEY` to the secret key before binding. +- MUST listen for and accept TCP connections. + - For an IP [NetAddress], bind on the given host IP and port. + - For an Onion [NetAddress], bind on 127.0.0.1 and the given port. + - For an I2P [NetAddress], as yet undetermined. +- MUST read multipart messages and return them to the caller + - if the timeout is reached return an error to be handled by the calling code + +#### OutboundConnection + +A thin wrapper around a `ZMQ_DEALER` socket which connects to a [NetAddress] and sends outbound multipart messages. +This connection blocks until data can be written, or a timeout is reached. The timeout should never be reached as +[ZeroMQ] internally queues messages to be sent. + +Fields may include + +- a [NetAddress], +- underlying [ZeroMQ socket], + +Methods may include: + +- `send(msg)` +- `receive()` +- `disconnect()` +- `set_encryption(server_pk, client_pk, client_sk)` +- `set_socks_proxy(address)` +- `set_hwm(v)` + +An [OutboundConnection]: + +- MUST perform the "client-side" [CurveZMQ](http://curvezmq.org/page:read-the-docs) encryption protocol if encryption is set. + - using [ZeroMQ] this means setting the socketopts `ZMQ_CURVE_SERVERKEY`, `ZMQ_CURVE_SECRETKEY` and `ZMQ_CURVE_PUBLICKEY` +- MUST connect to a TCP endpoint + - For an IP [NetAddress], connect to the given host IP and port + - For an Onion [NetAddress], connect to the onion address using the tcp protocol (e.g. `tcp://xyz...123.onion:1234`) + - For an I2P [NetAddress], as yet undetermined +- MUST write the parts of the given [MessageEnvelope] to the socket as a multipart message consisting of, in order: + - identity + - version + - header + - body +- if specified, MUST set a high water mark (HWM) on the underlying [ZeroMQ] socket +- If the HWM is reached, a call to `send` MUST return an error and any messages received SHOULD be discarded + +#### Peer + +A single peer which can communicate on the Tari network. + +Fields may include: + +- `addresses` - a list of [NetAddress]es associated with the Peer, perhaps accompanied with some bookkeeping metadata (such as preferred address) +- `node_type` - The type of node or client (i.e [BaseNode], [ValidatorNode], [Wallet], or [TokenWallet]) +- `last_seen` - a timestamp of the last time a message has been sent/received from this Peer +- `flags` - 8-bit flag + - bit 0: is_banned + - bit 1-7: reserved + +A Peer may also contain reputation metrics (e.g. rejected_message_count, avg_latency) to be used to decide +if a peer should be banned. This mechanism is yet to be decided. + +#### PeerConnection + +Represents direct bi-directional connection to another node or client. As connections are bi-directional, +the [PeerConnection] need only hold a single [InboundConnection] or [OutboundConnection], depending on if the +node requested a peer connect to it or it is connecting to a peer. + +PeerConnection will send messages to the peer in a non-blocking, asynchronous manner as long as the connection +is maintained. + +It has a few important functions: + +- Manage the underlying network connections; with automatic reconnection if necessary. +- Forward incoming messages onto the given handler socket. +- Send outgoing messages. + +Unlike [InboundConnection] and [OutboundConnection] which are essentially stateless, +`PeerConnection` maintains a particular `ConnectionState`. + +- `Idle` - the connection has not been established. +- `Connecting` - the connection is in progress. +- `Connected` - the connection has been established. +- `Suspended` - the connection has been suspended. Incoming messages will be discarded, calls to `send()` will error. +- `Dead` - the connection is no longer active because the connection was dropped. +- `Shutdown` - the connection is no longer active because it was shutdown. + +Fields may include: + +- a connection state, +- a control socket, +- a peer connection `NetAddress` +- a direction (either `Inbound` or `Outbound`) +- a public key obtained from the connection negotiation +- (optional) SOCKS proxy. + +Methods may include: + +- `establish()` +- `shutdown()` +- `suspend()` +- `resume()` +- `send(msg)` + +A `PeerConnection`: + +- MUST listen for data on the given [NetAddress] using an [InboundConnection] +- MUST sequentially try to connect to one of the peer's [NetAddress]es until one succeeds or all fail using an [OutboundConnection] +- MUST immediately reject and dispose of a multipart message not consisting of four parts, as detailed in [MessageEnvelope]. +- MUST construct a [MessageEnvelope] from the multiple parts. +- MUST pass the constructed [MessageEnvelope] to the message handler. +- Should a connection drop, the connection state MUST transition to `Connecting` and the connection retried. +- When a shutdown signal is received, MUST send a `net::Disconnect` message and drop the connection. + +#### ConnectionManager + +The ConnectionManager manages a set of live PeerConnections and provides an abstraction for other components +to initiate and use PeerConnections without having to worry about attaching the new PeerConnection to message handlers. + +It consists of a list of active peer connections and an `inproc` message handler socket. This socket is 'written to' whenever +a message is received from any active [PeerConnection] for other components to act on. + +Methods may include: + +- `establish_connection(Peer)` - create and return a new PeerConnection +- `disconnect(peer)` - disconnect a particular peer +- `suspend()` - temporarily suspend connections +- `resume()` - temporarily suspend connections +- `shutdown` - cleanly shutdown all PeerConnections + +The `ConnectionManager`: + +- MUST call `suspend` on every [PeerConnection] if it's `suspend` method is called +- MUST call `resume` on every [PeerConnection] if it's `resume` method is called +- MUST call `shutdown` on every [PeerConnection] if it's `shutdown` method is called +- MUST create a new [PeerConnection] with the given Peer and NetAddress, when `establish_connection` is called +- MUST call `shutdown` on the [PeerConnection] and remove the connection for the given Peer, when `disconnect(peer)` is called +- MAY disconnect peers if the connection has not been used for an extended period +- SHOULD disconnect the least recently used peer if the connection pool is greater than `max connections` + +#### ControlService + +The purpose of this service is to negotiate a new secure PeerConnection. + +The control service accepts a single message: + +- `peer::EstablishConnection(pk, curve_pk, net_address)` + +A ControlService: + +- MUST listen for connections on a predefined CONTROL PORT +- SHOULD deny connections from banned peers + +To establish a peer connection, the following steps apply: + +Alice wants to connect to Bob + +1. Alice creates a `PeerConnection` to which Bob can connect. + - A new CURVE keypair is generated +2. Alice connects to Bob's control server and Bob accepts the connection. +3. Alice sends an `peer::establish_connection` message, with + - the CURVE public key for the socket connection, + - the node's public key corresponding to its [Node ID], + - the [NetAddress] of the new PeerConnection. +4. Bob accepts this request, and opens a new `PeerConnnection` socket using Alice's CURVE public key. +5. Bob connects to the given [NetAddress] and sends a `peer::establish_connection` message. +6. If Alice accepts the connection, they can begin sending messages. If not, both sides terminate the connection. + +#### PeerManager + +The PeerManager's responsibility is to manage the list of peers that the node has previously interacted with. +This list is called a routing table and is made up of [Peer]s. + +The PeerManager can + +- add a peer to the routing table, +- search for a peer given a node id, public key or [NetAddress], +- delete a peer from the list, +- persist the peer list using a storage backend, +- restore the peer list from the storage backend, +- maintain lightweight views of peers; using a filter criterion; e.g. a list of peers that have been banned (i.e. a blacklist), +- prune the routing table based on a filter criterion (e.g. last date seen) + +#### MessageDispatcher + +A MessageDispatcher associates MessageTypes to handlers. Each handler gets a MessageContext as a parameter. + +The MessageContext contains: + +- the requesting PeerConnection, +- the MessageHeader +- the deserialized message, +- the OutboundMessageService. + +Basically, all the tools the handler needs to interact with the network. + +A MessageDispatcher is responsible for: + +- constructing the MessageContext +- finding the message handler which is associated with the MessageType +- passing the MessageContext to the handler +- if the handler cannot be found, the message is ignored + +An example API may be: + +```rust,compile_fail +let dispatcher = MessageDispatcher::::new() + .middleware(logger) + .route(BlockchainMessageType::NewBlock, BlockHandlers::store_and_broadcast) + ... + .route(NetMessageType::Ping, send_pong); + +inbound_msg_service.set_handler(dispatcher.handler); +``` + +#### InboundMessageService + +InboundMessageService is a service that receives messages over a non-blocking asynchronous socket and +determines what to do with it. There are 3 options: handle, forward, discard. + +A pool of worker threads (with a configurable size) is started and each listen for messages on their $1:n$ `inproc` message +socket. A `ZMQ_DEALER` socket is suggested for fair-queueing work amongst workers, who listen for work with a `ZMQ_REP`. +The workers read off this socket and process the messages. + +An InboundMessageService: + +- MUST receive messages from all PeerConnections +- MUST write the message to the worker socket + +A worker: + +- MUST deserialize the MessageHeader + - if unable to deserialize, MUST discard the message +- MUST check the message signature + - MUST discard the message if the signature is invalid. + - MUST discard the message if the signature has been processed within the [DuplicateMessageWindow] +- If the encryption flag is set: + - MUST attempt to decrypt the message + - if successful, process and handle the message + - otherwise, MUST forward the message using the `Random` BroadcastStrategy + - if the message is not encrypted, MUST discard it +- If the destination [node ID] is set: + - if the destination match this node's ID: process and handle the message + - if the destination does not match: MUST forward the message using the `Closest` BroadcastStrategy +- If the destination is not set: + - if the MessageType is a kind of propagation message: + - MUST handle the message + - MUST forward the message using the `Random` BroadcastStrategy, + - if the MessageType is a kind of encrypted message: + - MUST attempt to decrypt and handle the message + - if successful, MUST handle the message + - if unsuccessful, MUST forward the message using the `Random` or `Flood` BroadcastStrategy, + +#### OutboundMessageService + +OutboundMessageService is responsible for using the connection and peer infrastructure to +send messages to the rest of the network. + +In particular, it is responsible for: + +- serializing the message body +- constructing the [MessageEnvelope] +- executing the required BroadcastStrategy +- sending messages using the [ConnectionManager] + +The actual sending of messages can be requested via the public `send_message` method which takes a +MessageHeader, MessageBody and BroadcastStrategy as parameters. + +`send_message` then selects an appropriate peer(s) from the ConnectionManager according to the +BroadcastStrategy and sends the message to each of the selected peers. + +BroadcastStrategy determines how a set of peer nodes will be selected and can be one of: + +- `Direct` - send to a particular peer matching the given [node ID] +- `Flood` - send to all known peers who are not [communication clients] +- `Closest` - send to $n$ closest peers who are not [communication clients] +- `Random` - send to a random set of peers of size $n$ who are not [communication clients] + +### Privacy Features + +The following privacy features are proposed: + +- A [communication node] or [communication client] MAY communicate solely over the Tor/I2P networks +- All traffic (with the exception of the control service) MUST be encrypted +- Messages MAY encrypt the body of a MessageEnvelope which only a particular recipient can decrypt. +- The `destination` header field can be omitted, when used in conjunction with body encryption the destination is + completely unknown to the rest of the network. + +### Store and Forward Strategy + +Sometimes it may be desirable for messages to be sent without a destination node/client being online. This +is especially important for a modern chat/messaging application. + +The mechanism for this is proposed as follows: + +Each [communication node] MUST allocate some disk space for storage of messages for offline recipients. +Only some whitelisted MessageTypes are permitted to be stored. A sender sends a message destined for a particular +[node ID] to its closest peers which forward the message to their closest peers and so on. + +Eventually, the message will reach nodes which either know the destination or are very close to the destination. +These nodes MUST store the message in some pending message bucket for the destination. The maximum number of +buckets and the size of each bucket SHOULD be a sufficiently large as to be unlikely to overflow, but not so +large as to approach disk space problems. Individual messages should be small and responsibilities for +storage are spread over the entire network. + +A [communication node] + +- MUST store messages for later retransmission, if all of the following conditions are true: + - the MessageType is permitted to be stored + - there are fewer than $n$ closer online peers to the destination +- MUST retransmit pending messages when a closer peer comes online or is added to the routing table +- MAY remove a bucket, in any of the following conditions: + - the bucket is empty, + - a configured maximum number of buckets has been reached. Discard the bucket with the earliest creation timestamp. + - the number of closer online peers to the destination is equal to or has exceeded $n$ +- MAY expire individual messages after a sufficiently long time to live (ttl) + +This approach has the following benefits: + +- When a destination comes online, they'll receive pending messages without having to query them. +- The "closer within a threshold" metric is simple. +- Messages are stored on multiple peers which makes it less likely for messages to disappear as nodes come and go + (depending on threshold $n$). + +### Queue Overflow Strategy + +Inbound/OutboundConnections (and therefore PeerConnection) has a high water mark (HWM) set. + +If the HWM is hit: + +- any call to `send()` should return an error. +- Incoming messages should be silently discarded. + +### Outstanding Items + +- A PeerConnection will probably need to implement a heartbeat to detect if a peer has gone offline. +- InboundConnection(Service) may want to send small replies (such as OK, ERR) when the message has been accepted/rejected. +- OutboundConnection(Service) may want to receive and handle small replies. +- Encrypted communication for the [ControlService] would be better privacy, but since zMQ requires a CURVE public key before + the connection is bound, a dedicated 'secure connection negotiation socket' would be needed. +- Details of distributed message storage. +- Which [NetAddress] to use if a peer has many. + +[basenode]: Glossary.md#base-node +[broadcaststrategy]: #outboundmessageservice +[communication client]: Glossary.md#communication-client +[communication node]: Glossary.md#communication-node +[connectioncontext]: #connectioncontext +[controlservice]: #controlservice +[MessageEnvelope]: #MessageEnvelope +[MessageEnvelopebody]: #MessageEnvelopebody +[MessageEnvelopeHeader]: #MessageEnvelopeHeader +[duplicatemessagewindow]: #duplicatemessagewindow +[inboundconnection]: #inboundconnection +[message]: #message +[messagetype]: #messagetype +[netaddress]: #netaddress +[node id]: Glossary.md#node-id +[outboundconnection]: #outboundconnection +[peer]: #peer +[peerconnection]: #peerconnection +[rfc-0171: message serialisation]: RFC-0171_MessageSerialisation.md +[tokenwallet]: Glossary.md#token-wallet +[validatornode]: Glossary.md#validator-node +[wallet]: Glossary.md#wallet +[zeromq socket]: http://api.zeromq.org/2-1:zmq-socket +[zeromq]: http://zeromq.org/ diff --git a/RFC/src/RFC-0190_Mempool.md b/RFC/src/RFC-0190_Mempool.md new file mode 100644 index 0000000000..2b2fd2a971 --- /dev/null +++ b/RFC/src/RFC-0190_Mempool.md @@ -0,0 +1,237 @@ +# Mempool + +## The Mempool for Unconfirmed Transactions on the Tari Base Layer + +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: [Yuko Roodt](https://github.com/neonknight64) + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2018 The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals + +This document will introduce the Tari [base layer] [Mempool] that consists of a [Transaction Pool], [Pending Pool], [Orphan Pool] and [Reorg Pool]. +The Mempool is used for storing and managing unconfirmed and time-lock restricted [transaction]s. + +## Related RFCs + +* [RFC-0100: The Tari Base Layer](RFC-0100_BaseLayer.md) + +## Description + +### Assumptions + +- Each [base node] is connected to a number of peers that maintain their own copies of the Mempool. + +### Abstract + +The Mempool is responsible for managing, verifying and maintaining all unconfirmed transactions that have not yet +been included in a [block] and added to the Tari [blockchain]. It consists of a Transaction Pool, Pending Pool, +Orphan Pool and Reorg Pool to achieve these tasks. It is also responsible for propagating valid transactions and +sharing the Mempool state with connected peers. An overview of the required functionality for the Mempool and each +of its component pools will be provided. + +### Overview + +Every base node maintains a Mempool that consists of four separate pools: the Transaction Pool, Pending Pool, +Orphan Pool and Reorg Pool. These four pools have different tasks and work together to form the Mempool used +for maintaining unconfirmed transactions. + +This is the role descriptions for each component pool: +- Transaction Pool: contains all unconfirmed transactions that have been verified, have passed all checks, that +only spend valid [UTXO]s and don't have any time-lock restrictions. +- Pending Pool: contains unconfirmed transactions that have time-lock restrictions. The transactions in this pool +either attempt to spend UTXOs with time-locks or the transactions themselves have time-locks limiting them from +being included in new blocks until a specified future time or block height has been reached. +- Orphan Pool: out of order unconfirmed transactions are managed by this pool, these transactions attempt to spend +non-existent UTXOs. +- Reorg Pool: stores a backup of all transactions that have recently been included into blocks, in case a blockchain +reorganization occurs and these transactions have to be restored to the Transaction Pool so that they can be included +in future blocks. + +### Prioritizing Unconfirmed Transactions + +The maximum storage capacity used for storing unconfirmed transactions by the Mempool and each of its component pools +can be configured. If a new transaction is received and the storage capacity limits have been reached, then +transactions are prioritized according to the transaction priority metric. The transaction priority metric consist +of the fees and the maturity of the UTXOs being spent by the transaction and should be applied to determine the priority +of each transaction in the Mempool. As the allocated storage space of the Mempool becomes limited, the transactions +with the highest priority are kept in the pool. The lowest priority transactions are discarded to make room for +higher priority incoming transactions. + +The transaction priority metric has the following behavior: + - Transactions spending UTXOs with higher block height maturity SHOULD be prioritized over transactions spending UTXOs + with lower block height maturity. + - Transactions with higher fees per transaction message size SHOULD be prioritized over lower fee transactions. + +### Syncing and Updating of the Memory Pool State + +On the initial startup of the Mempool, the complete state of the Mempool that consists of the Transaction Pool, Pending +Pool, Orphan Pool and Reorg Pool can be requested and downloaded from the connected peers. A memory pool typically +doesn't make use of persistent storage but could be configured to keep a backup of the last known state. If an existing +Mempool state is locally available then a more efficient update process can be performed by requesting only the +unconfirmed transactions missing from the current Mempool state. When no state is available then the entire Mempool +state must be downloaded from the connected peers. During downloading or updating of the Mempool state, the validity +of all transaction in the pool must be verified and the priority of each transaction must be calculated. + +Functional behavior required for sharing and updating of the Mempool state, and propagation of transactions between peers: +- All verified transaction MUST be propagated to neighboring peers. +- Duplicate transactions MUST NOT be propagated to peers. +- Unvalidated or invalid transactions MUST NOT be propagated to peers. +- Verified transactions that were discarded due to low priority levels MUST be propagated to peers. +- The Mempool MUST have an interface that can be used by neighboring peers to query the content and state of the memory +pool. +- It MUST have a mechanism that enables peers to download the memory pool state in full or in part. +- It MUST accept all transactions received from peers but MAY decide to discard low priority transactions. +- It MUST allow wallets to track payments by monitoring that a particular transaction has been added to the Mempool. +- A Mempool MAY choose: + - to discard the Mempool state on restarts and then download the full state from its peers or + - to store the state of the Mempool using persistent storage to reduce communication bandwidth required when + reinitializing the Mempool after a restart. + +### Transaction Pool + +The Transaction Pool consists of all unconfirmed transactions that have been received, verified and have passed +all checks. These unconfirmed transactions in the Transaction Pool are ready to be included and can be used to +construct new blocks for the Tari blockchain. + +Functional behavior required of the Transaction Pool: +- It MUST verify that incoming transactions only spend existing UTXOs. +- It MUST ensure that incoming transactions don't have a processing time-lock or has a time-lock that has +expired. +- It MUST ensure that all time-locks of the UTXOs that will be spent by the transaction have expired. +- Transactions that have been used to construct new blocks MUST be removed from the Transaction Pool and added to the Reorg Pool. + +### Pending Pool + +The Pending Pool contains all transactions that are restricted by time-locks. A transaction could have a time-lock +limiting it from being processed or it can attempt to spend UTXOs with time-locks. These transactions require +their time-locks or the time-locks of the input UTXOs to expire before they can be processed and included into new +blocks. All transactions in the Pending Pool have been verified and passed all checks except their own time-lock has +not yet expired or some of the UTXOs that will be spent have time-lock restrictions that are not yet valid. Once the +transactions time-lock or the time-locks on the UTXOs have expired then the Pending Pool transactions can be moved +to the Transaction Pool for inclusion in future blocks. + +Functional behavior required of the Pending Pool: +- Once the transaction time-lock or UTXO time-lock restricting the processing of a transaction has expired then the +pending transaction MUST be moved to the Transaction Pool. + +### Orphan Pool + +The Orphan Pool contains all the received transactions that have passed all the verification steps and checks, except +they attempt to spend UTXOs that don't exist. A possible reason these UTXOs do not yet exist is that they may not yet +have been created and might exist in the future. Typically, these orphaned transactions are from a series or bundle of +transactions that need to be processed in a specific order but +- have been either received out of order, +- or the order of processing the bundled transactions might not have been known or specified. + +As transactions are processed and the missing UTXOs have been created, then the orphaned transactions can be moved +to the Transaction Pool for possible inclusion in future blocks. Another possibility why the input UTXOs might not +be available is that the UTXOs were double spent by other transactions. In time, the double spent transactions will +be discarded from the Orphan Pool once they have reached the appropriate maturity threshold. + +Functional behavior required of the Orphan Pool: +- Each newly received transaction MUST be verified and pass all checks except the UTXO validity check before it is +placed in the Orphan Pool. +- Orphaned transactions must be upgraded and moved to the Transaction Pool once the previously unavailable UTXOs become +available. +- Orphaned transactions that have surpassed the expiration time threshold MUST be removed from the Orphan Pool. + +### Reorg Pool + +The Reorg Pool consists of all unconfirmed transaction that have recently been added to blocks, resulting in +their removal from the Transaction Pool. When a potential blockchain reorganization occurs that invalidates previously +assembled blocks, the transactions used to construct these discarded blocks can be recovered from the Reorg Pool and +can be added back into the Transaction Pool. This will ensure that high priority transactions are not lost during +blockchain reorganization but can be added into future blocks without retransmission of these transactions. + +Functional behavior required of the Reorg Pool: +- Copies of the verified transactions removed from the Transaction pool that were placed in blocks MUST be stored in +the Reorg Pool. +- Transactions in the Reorg pool MAY be removed after the threshold expiration time has been reached. +- When a blockchain reorganization is detected, all affected transactions from the Reorg Pool MUST be moved to the +Transaction Pool. + +### Mempool + +The Mempool manages the four component pools and interacts with peers to share and retrieve transactions and +the Mempool state. During the operation of the Mempool it will distribute incoming transactions to the appropriate +component pools. When a new incoming transaction is received a number of checks and verification steps need to be performed +to determine if the transaction can be added to the Mempool and determine which of the component pools should be responsible +for that particular transaction. Only when the new transaction has passed these checks can it be added to the Mempool and +should it be propagated to the connected peers. + +Functional behavior required of the Mempool: +- If a duplicate transaction is received, that already exist in the Mempool, then the duplicate copy MUST be discarded. +- When considering transactions that attempt to double spend UTXOs, the highest priority transaction MUST be kept and any +other transactions that spend the same UTXO MUST be discarded. +- When the storage limit of the Mempool has been reached, new incoming transactions SHOULD be prioritized according to the +Priority metric. +- Lower priority Mempool transactions MUST be discarded to make room for higher priority incoming transactions. +- Incoming transactions with lower priorities than the minimum transaction priority in the Mempool MUST be discarded. +- The Mempool MUST verify that incoming transactions do not have duplicate outputs. +- It MUST check that all coinbase outputs that will be spent have matured sufficiently. +- The distribution of storage space allocated to each component pool in the memory pool MAY be configured and adjusted. +- The memory pool SHOULD have a mechanism to estimate fee categories from the current Mempool state. As an example, +a priority fee can be estimated that will ensure that a new transactions will have the appropriate priority to be added +into a new block in a timely manner. + +Functional behavior required for distributing incoming transactions to the component pools: +- Verified transactions that have passed all checks such as spending of valid UTXOs and expired time-locks MUST be +placed in the Transaction Pool +- All transactions that attempt to spend UTXOs with valid time-locks MUST be added to the Pending Pool. +- Incoming transactions with time-locks prohibiting them from being included into new blocks should be added to the +Pending Pool. +- Newly received verified transaction attempting to spend a UTXO that does not yet exist MUST be added to the Orphan +pool. +- Transactions that have been added to blocks and were removed from the Transaction Pool should be added to the Reorg Pool. + +[base layer]: Glossary.md#base-layer +[mempool]: Glossary.md#mempool +[transaction pool]: Glossary.md#transaction-pool +[pending pool]: Glossary.md#pending-pool +[orphan pool]: Glossary.md#orphan-pool +[reorg pool]: Glossary.md#reorg-pool +[transaction]: Glossary.md#transaction +[base node]: Glossary.md#base-node +[block]: Glossary.md#block +[blockchain]: Glossary.md#blockchain +[utxo]: Glossary.md#unspent-transaction-outputs diff --git a/RFC/src/RFC-0230_HTLC.md b/RFC/src/RFC-0230_HTLC.md index dff58a320f..8a175bf21a 100644 --- a/RFC/src/RFC-0230_HTLC.md +++ b/RFC/src/RFC-0230_HTLC.md @@ -1,6 +1,124 @@ -# RFC-0230/HTLC +# RFC-0230/Time related transactions + +## Time related transactions + +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: [SW van heerden](https://github.com/SWvheerden) + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2019 The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals + +This document describes a few extensions to [MimbleWimble] to allow time related transactions. + +## Related RFCs + +* [RFC-0200: Base Layer Extensions](BaseLayerExtensions.md) + +## Description + +#### Time Locked contracts +In [Mimblewimble] time-locked contracts can be accomplished by modifying the kernel of each transaction to include a +block height. This limits how early in the blockchain lifetime the specific transaction can be included in a block. + +This requires users constructing a transaction to: +* MUST include a lock height in the kernel of their transaction, +* MUST include the lock height in the transaction signature to prevent lock height malleability. + +Tari Miners: +* MUST not add any transaction to the mined [block] that has not already exceeded its lock height. + +This also adds the following requirement to a [Base Node]: +* MUST reject any [block] that contains a kernel with a lock height greater than the [current head]. + +#### Time Locked UTXOs +Time locked UTXOs can be accomplished by adding feature flag to a UTXO and a lock height. This allows a limit as to when + in the blockchain lifetime the specific UTXO can be spent. + +This requires that users constructing a transaction: + +- MUST include a the feature flag of their UTXO, +- MUST include a lockheight in their UTXO. + +This adds the following requirement to a miner: +- MUST not allow a UTXO to be spent if the [current head] has not already exceeded the UTXO's lock height. + +This also adds the following requirement to a [base node]: +- MUST reject any [block] that contains a [UTXO] with a lock height not already past the [current head]. + +#### Hashed Time Locked Contract +Hashed time locked contracts are a way of reserving funds for a certain payment, but it only pays out to the receiver if +certain conditions are met. If these are not met withing a time limit, the funds are payed back to the sender. + +Unlike Bitcoin where this can be accomplished with a single transaction, in [MimbleWimble] HTLCs involve a multi-step +process to construct a timelocked contract. + +The steps are as follows: +* The sender MUST pay all the funds into a n-of-n [multisig] [UTXO]. +* All parties involved MUST construct a refund [transaction] paying back all funds to the sender, spending this n-of-n + [multisig] [UTXO]. However, this [transaction] has a [transaction lock height](#hashed-time-locked-contract) set in + the future and cannot be immediately mined. It therefore lives in the [mempool]. This means that if anything goes + wrong from here on out, the sender will get his money back after the time lock expires. +* The sender MUST publish both above [transaction]s at the same time to ensure the receiver cannot hold him hostage. +* The parties MUST construct a third [transaction] that pays the receiver the funds. This [transaction] typically makes + use of a preimage to allow spending of the [transaction] if the user reveals some knowledge, allowing the user to + unlock the [UTXO]. + +HTLC's in [Mimblewimble] makes use of double spending the n-of-n [multisig] [UTXO] and the +first valid published [transaction] can then be mined and claim the n-of-n [multisig] +[UTXO]. + +An example of a [HTLC] in practice can be viewed at Tari University: +[Bitcoin atomic swaps](https://tlu.tarilabs.com/protocols/atomic-swaps/AtomicSwaps.html) +[MimbleWimble atomic swaps](https://tlu.tarilabs.com/protocols/grin-protocol-overview/MainReport.html#atomic-swaps) + +[HTLC]: Glossary.md#hashed-time-locked-contract +[Mempool]: Glossary.md#mempool +[Mimblewimble]: Glossary.md#mimblewimble +[Base Node]: Glossary.md#base-node +[Block]: Glossary.md#block +[current head]: Glossary.md#current-head +[UTXO]: Glossary.md#unspent-transaction-outputs +[Multisig]: Glossary.md#multisig +[Transaction]: Glossary.md#transaction -RFC placeholder. -This document will describe extensions to Mimblewimble enabling time-locked transactions (as opposed to time-locked -outputs) which allow the development of time locked contracts (useful for e.g. Atomic Swap contracts) \ No newline at end of file diff --git a/RFC/src/RFC-0300_DAN.md b/RFC/src/RFC-0300_DAN.md index 05b41f1a23..9a4bc72711 100644 --- a/RFC/src/RFC-0300_DAN.md +++ b/RFC/src/RFC-0300_DAN.md @@ -84,8 +84,7 @@ that the rules of the asset contracts are enforced. An [Asset Issuer] (AI) will issue a Digital Assets by constructing a contract from one of the supported set of [DigitalAssetTemplate]s. The AI will choose how large the committee of Validator Nodes will be for this DA and have the option to nominate [Trusted Node]s to be part of the VN committee for the DA. Any remaining spots on the committee will be filled by permissionless VNs that are selected according to a [CommitteeSelectionStrategy]. This is a strategy -for the DAN to algorithmically select candidates for the committee from the available registered Validator Nodes. The VNs will need to accept the nomination -to become part of the committee by putting up the specified collateral. +that an AI will use to select from the set of potential candidate VNs that nominated themselves for a position on the committee when the AI broadcast a public call for VNs during the asset creation process. For the VNs to accept the appointment to the committee they will need to put up the specified collateral. [asset issuer]: Glossary.md#asset-issuer [base layer]: Glossary.md#base-layer diff --git a/RFC/src/RFC-0302_ValidatorNodes.md b/RFC/src/RFC-0302_ValidatorNodes.md index 01a34e1cec..9738b2dc57 100644 --- a/RFC/src/RFC-0302_ValidatorNodes.md +++ b/RFC/src/RFC-0302_ValidatorNodes.md @@ -51,7 +51,7 @@ technological merits of the potential system outlined herein. The goal of this RFC is to describe the responisibilities of Validator Nodes (VNs) on the DAN. ## Related RFCs -* [RFC-0303: Validator Node Registration](RFC-0303_VNRegistration.md) +* [RFC-0322: Validator Node Registration](RFC-0322_VNRegistration.md) * [RFC-0304: Validator Node committee selection](RFC-0304_VNCommittees.md) * [RFC-0340: VN Consensus Overview](RFC-0340_VNConsensusOverview.md) @@ -67,18 +67,9 @@ VNs will also perform archival functions for the assets they manage. The lifetim still being discussed. #### Registration -VNs register themselves on the [Base Layer] using a special [transaction] type. The registration [transaction] type -requires the spending of a certain minimum amount of [Tari coin], the ([RegistrationCollateral]), that has a time-lock on the -output for a minimum amount of time ([RegistrationTerm]) as well as some metadata, such as the VNs public key and a generated [Node ID]. The Node ID is generated -during registration to prevent mining of VN public keys that can be used to manipulate routing on the DAN. The blinding factor for the Registration transaction is the private key -that the VN node will use to sign every instruction that it executes for the duration of its [RegistrationTerm]. +VNs register themselves on the [Base Layer] using a special [transaction] type. -Once a VNs [RegistrationTerm] has expired so will this specific VN registration. The UTXO timelock will have elapsed so the [RegistrationCollateral] can be reclaimed and a new VN registration -need to be performed. This automatic registration expiry will ensure that the VN registry stays up to date with active VN registrations and inactive registrations will naturally be removed. - -Requiring nodes to register themselves serves two purposes: -* Makes VN Sybil attacks expensive, -* Provides an authoritative "central-but-not-centralised" registry of validator nodes from the base layer. +Validator node registration is described in [RFC-0322](RFC-0322_VNRegistration.md). #### Execution of instructions VNs are expected to manage the state of digital assets on behalf of digital asset issuers. They receive fees as reward @@ -89,7 +80,7 @@ for doing this. issuance process and membership of the committee can be updated at [Checkpoint]s. * It is the VNs responsibility to ensure that every state change in a digital asset conforms to the contract's rules. * VNs accept digital asset [Instructions] from clients and peers. [Instructions] allow for creating, updating, expiring and archiving digital assets on the DAN. -* VNs provide additional collateral when accepting an offer to manage an asset, which is stored in a multi-signature +* VNs provide additional collateral, called [AssetCollateral], when accepting an offer to manage an asset, which is stored in a multi-signature UTXO on the base layer. This collateral can be taken from the VN if it is proven that the VN engaged in malicious behaviour. * VNs participate in fraud proof validations in the event of consensus disputes (which could result in the malicious VN's @@ -156,6 +147,7 @@ The VNs will communicate using a peer-to-peer (P2P) network. To facilitate this * VNs MUST respond to requests for information about digital assets that they manage on the DAN. * VNs and clients can advertise public keys to facilitate P2P communication encryption +[assetcollateral]: Glossary.md#assetcollateral [asset issuer]: Glossary.md#asset-issuer [base layer]: Glossary.md#base-layer [bad actor]: Glossary.md#bad-actor diff --git a/RFC/src/RFC-0304_VNCommittees.md b/RFC/src/RFC-0304_VNCommittees.md index 8b423b6cfc..1ad3b43f00 100644 --- a/RFC/src/RFC-0304_VNCommittees.md +++ b/RFC/src/RFC-0304_VNCommittees.md @@ -1,6 +1,93 @@ # RFC-0304/VNCommittees -RFC placeholder. +## Validator Node committee selection -This document will describe the process of how validator committees are formed. In particular, the process of allowing -the network to elect a set of nodes via algorithm is described. \ No newline at end of file +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: Philip Robinson + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2019. The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals + +This document will describe the process an [Asset Issuer] (AI) will go through in order to select the committee of [Validator Node]s +(VNs) that will serve a given [Digital Asset] (DA). + +## Related RFCs +* [RFC-0311: Digital Asset templates](RFC-0311_AssetTemplates.md) +* [RFC-0302: Validator Nodes](RFC-0302_ValidatorNodes.md) +* [RFC-0341: Asset Registration](RFC-0341_AssetRegistration.md) + +## Description + +### Abstract +[Digital Asset]s (DAs) are managed by [committee]s of nodes called [Validator Node]s (VNs), as described in [RFC-0300](RFC-0300_DAN.md) and [RFC-0302](RFC-0302_ValidatorNodes.md). During the asset creation process, described in [RFC-0341](RFC-0341_AssetRegistration.md), the [Asset Issuer] (AI) MUST select a committee of VNs to manage their asset. This process consists of the following steps: + +1. Candidate VNs MUST be nominated to be considered for selection by the AI. +2. The AI MUST employ a [CommitteeSelectionStrategy] to select VNs from the set of nominated candidates. +3. The AI MUST make an offer of committee membership to the selected VNs. +4. Selected VNs MAY accept the offer to become part of the committee by posting the required [AssetCollateral]. + +### Nomination +The first step in assembling a committee is to nominate candidate VNs. As described in [RFC-0311](RFC-0311_AssetTemplates.md) an asset can be created with two possible `committee_modes`: `CREATOR_NOMINATION` or `PUBLIC_NOMINATION`. + +In `CREATOR_NOMINATION` mode the AI nominates candidate committee members directly. The AI will have a list of permissioned [Trusted Node]s that they want to act as the committee. The AI will contact the candidate VNs directly to inform them of their nomination. + +In `PUBLIC_NOMINATION` mode the AI does not have a list of [Trusted Node]s and wants to source unknown VNs from the network. In this case the AI broadcasts a public call for nomination to the Tari network using the peer-to-peer messaging protocol described in [RFC-0172](RFC-0172_PeerToPeerMessagingProtocol.md). This call for nomination contains all the details of the asset and VNs that want to participate will then nominate themselves by contacting the AI. + +### Selection +Once the AI has received a list of nominated VNs it must make a selection, assuming enough VNs were nominated to populate the committee. The AI will employ some [CommitteeSelectionStrategy] in order to select the committee from the candidate VNs that have been nominated. This strategy might aim for a perfectly random selection or perhaps it will consider some metrics about the candidate VNs such as the length of their VN registrations which might indicate that they are reliable and have not been blacklisted for poor or malicious performance. + +A consideration when selecting a committee in `PUBLIC_NOMINATION` mode will be the size of the pool of nominated VNs. The size of this pool relative to the size of the committee to be selected will be linked to a risk profile. If the pool has very few candidates in it then it will be much easier for an attacker to have nominated their own nodes in order to obtain a majority membership of the committee i.e. if the AI is selecting a committee of 10 members using a uniformly random selection strategy and only 12 public nominations are received an attacker only requires control of 6 VNs to achieve a majority position in the committee. In contrast, if 100 nominations are received and the AI performs a uniformly random selection an attacked would need to control more than 50 of the nominated nodes in order to achieve a majority position in the committee. + +### Offer acceptance +Once the selection has been made by the AI the selected VNs will be informed and an offer of membership will be made to them. If the VNs are still inclined to join the committee they will accept the offer by posting the [AssetCollateral] required by the asset to the [base layer] during the initial [Checkpoint] transaction built to commence the operation of the asset. + +[assetcollateral]: Glossary.md#assetcollateral +[asset issuer]: Glossary.md#asset-issuer +[base layer]: Glossary.md#base-layer +[checkpoint]: Glossary.md#checkpoint +[digital asset]: Glossary.md#digital-asset +[committee]: Glossary.md#committee +[CommitteeSelectionStrategy]: Glossary.md#committeeselectionstrategy +[validator node]: Glossary.md#validator-node +[digital asset network]: Glossary.md#digital-asset-network +[trusted node]: Glossary.md#trusted-node diff --git a/RFC/src/RFC-0311_AssetTemplates.md b/RFC/src/RFC-0311_AssetTemplates.md index 5c9a34fa15..adb81ca649 100644 --- a/RFC/src/RFC-0311_AssetTemplates.md +++ b/RFC/src/RFC-0311_AssetTemplates.md @@ -99,7 +99,7 @@ The instruction is in JSON format and MUST contain the following fields: | template_id | `u64` | The template descriptor. See [Template ID](#template-id) | | asset_expiry | `u64` | A timestamp or block height after which the asset will automatically expire. Zero for arbitrarily long-lived assets | | **Validation Committee selection** | | | -| committee_mode | `u8` | The validation committee mode, either `CREATOR_ASSIGNED` (0) or `NETWORK_ASSIGNED` (1) | +| committee_mode | `u8` | The validation committee nomination mode, either `CREATOR_NOMINATION` (0) or `PUBLIC_NOMINATION` (1) | | committee_parameters | Object | See [Committee Parameters](#committee-parameters). | | asset_creation_fee | `u64` | The fee the issuer is paying, in microTari, for the asset creation process | | commitment | `u256` | A time-locked commitment for the asset creation fee | @@ -113,7 +113,7 @@ The instruction is in JSON format and MUST contain the following fields: #### Committee parameters -If `committee_mode` is `CREATOR_ASSIGNED` the `committee_mode` object is +If `committee_mode` is `CREATOR_NOMINATION` the `committee_parameters` object is | Name | Type | Description | |:-----------------|:-------------|:------------| @@ -121,7 +121,7 @@ If `committee_mode` is `CREATOR_ASSIGNED` the `committee_mode` object is Only the nodes in the trusted node set will be allowed to execute instructions for this asset. -If `committee_mode` is `NETWORK_ASSIGNED` the `committee_mode` object is +If `committee_mode` is `PUBLIC_NOMINATION` the `committee_parameters` object is | Name | Type | Description | |:------------------------|:------|:----------------------------------------------------------------------------------------------------------------------| diff --git a/RFC/src/RFC-0322_VNRegistration.md b/RFC/src/RFC-0322_VNRegistration.md index a0de812f15..f267a80573 100644 --- a/RFC/src/RFC-0322_VNRegistration.md +++ b/RFC/src/RFC-0322_VNRegistration.md @@ -1 +1,169 @@ -# RFC-0322: Validator Node Registration +# RFC-0322/VNRegistration + +## Validator Node Registration + +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: [Cayle Sharrock](https://github.com/CjS77) + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2019. The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals + +This document describes Validator Node registration. Registration accomplishes two goals: + +1. Provide a register of Validator Nodes with an authority (the Tari base layer). +2. Offer Sybil resistance against gaining probabilistic majority of any given publicly nominated VN committee. + +## Related RFCs + +* [RFC-0302: Validator Nodes](RFC-0302_ValidatorNodes.md) +* [RFC-0304: Validator Node committee selection](RFC-0304_VNCommittees.md) +* [RFC-0170: The Tari Communication Network and Network Communication Protocol](RFC-0170_NetworkCommunicationProtocol.md) +* [RFC-0341: Asset Registration](RFC-0341_AssetRegistration.md) +* [RFC-0200: Base Layer Extensions](BaseLayerExtensions.md) + +## Description + +Validator Nodes register themselves on the [Base layer] using a special [transaction] type. The registration +[transaction] type requires the spending of a certain minimum amount of [Tari coin], the ([Registration Deposit]), +that has a time-lock on the output for a minimum amount of time ([Registration Term]) as well as some metadata, such as +the VNs public key. + +The Node ID is calculated after registration to prevent mining of VN public keys that can be used to manipulate routing +on the DHT. + +Once a VNs [Registration Term] has expired so will this specific VN registration. The UTXO timelock will have elapsed so +the [Registration Deposit] can be reclaimed and a new VN registration need to be performed. This automatic +registration expiry will ensure that the VN registry stays up to date with active VN registrations and inactive +registrations will naturally be removed. + +Requiring nodes to register themselves serves two purposes: +* Makes VN Sybil attacks expensive, +* Provides an authoritative "central-but-not-centralised" registry of validator nodes from the base layer. + +### Node ID + +The Validator node ID can be calculated deterministically after the VN registration transaction is mined. This ensures +that VNs are randomly distributed over the DHT network. + +VN Node IDs MUST be calculated as follows: + +```text + NodeId = Hash( pk || h || kh ) +``` + +Where + +| Field | Description | +|:------|:------------------------------------------------------------------------------| +| pk | The Validator Node's DHT public key | +| h | The block height of the block in which the registration transaction was mined | +| kh | The hash of the registration transaction's kernel | + +Base Nodes SHOULD maintain a cached list of Validator Nodes and MUST return the Node ID in response to a +`get_validator_node_id` request. + +### Validator node registration + +A validator node MUST register on the base layer before it can join any DAN [committee]s. Registration happens by virtue +of a Validator Node Registration [transaction]. + +VN registrations are valid for the [Registration Term]. + +The registration term is set at SIX months. + +A VN registration transaction is a special transaction. + +* The transaction MUST have EXACTLY ONE UTXO with the `VN_Deposit` flag set. +* This UTXO MUST also: + * Set a time lock for AT LEAST the [Registration Term] (or equivalent block periods) + * Provide the _value_ of the UTXO in the signature metadata + * Provide the _public key_ for the spending key for the output in the signature metadata +* The value of this output MUST be equal or greater than the [Registration Deposit]. +* The UTXO MUST store: + * The value of the VN deposit UTXO as a u64. + * The value of the _public key_ for the spending key for the output as 32 bytes in little endian order. +* The `KernelFeatures` bit flag MUST have the `VN_Registration` flag set. +* The kernel MUST also store the VN's DHT public key as 32 bytes in little endian order. + +### Validator node registration renewal + +If a VN owner does not _renew_ the registration before the [Registration Term] has expired, the registration will lapse +and the VN will no longer be allowed to participate in any [committee]s. + +The number of consecutive renewals MAY increase the VN's reputation score. + +A VN may only renew a registration in the TWO WEEK period prior to the current term expiring. + +A VN renewal transaction is a special transaction: + +* The transaction MUST have EXACTLY ONE UTXO with the `VN_Deposit` flag set. +* The transaction MUST spend the previous VN Deposit UTXO for this VN. +* This UTXO MUST also: + * Set a time lock for AT LEAST 6 months (or equivalent block periods) + * Provide the _value_ of the transaction in the signature metadata + * Provide the _public key_ for the spending key for the output in the signature metadata +* This UTXO MUST also store + * The value of the VN deposit UTXO as a u64. + * The value of the _public key_ for the spending key for the output as 32 bytes in little endian order. + * The VN's Node ID. This can be validated by following the Renewal transaction kernel chain. + * The kernel hash of this transaction's kernel. + * A counter indicating that this is the n-th consecutive renewal. This counter will be confirmed by nodes and miners. + The first renewal will have counter value of one. +* The previous VN deposit UTXO MUST NOT be spendable in a standard transaction (i.e. its time lock has not expired). +* The previous VN deposit UTXO MUST expire within the next TWO WEEKS. +* The transaction MAY provide additional inputs to cover transaction fees and increases in the [Registration Deposit]. +* The transaction kernel MUST have the `VN_Renewal` bit flag set. +* The transaction kernel MUST also store + * The hash of the previous renewal transaction kernel, or the registration kernel if this is the first renewal. + +One will notice that a validator node's Node ID does not change as a result of a renewal transaction. Rather, every +renewal adds to a chain linking back to the original registration transaction. It may be desirable to establish a long +chain of renewals, in order to offer evidence of longevity and improve a VN's reputation. + +[Tari Coin]: Glossary.md#tari-coin +[Transaction]: Glossary.md#transaction +[Node ID]: Glossary.md#node-id +[Base layer]: Glossary.md#base-layer +[Committee]: Glossary.md#committee +[Registration Term]: Glossary.md#registration-term +[Registration Deposit]: Glossary.md#registration-deposit diff --git a/RFC/src/RFC-0341_AssetRegistration.md b/RFC/src/RFC-0341_AssetRegistration.md index 395be835ef..4a3c5d6476 100644 --- a/RFC/src/RFC-0341_AssetRegistration.md +++ b/RFC/src/RFC-0341_AssetRegistration.md @@ -1 +1,99 @@ # RFC-0341: Asset registration +## Asset registration process + +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: Philip Robinson + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2019. The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals + +This document will describe the process that an [Asset Issuer] will need to engage in to register a [digital asset] and commence its operation on the [digital asset network]. + +## Related RFCs +* [RFC-0311: Digital Asset templates](RFC-0311_AssetTemplates.md) +* [RFC-0302: Validator Nodes](RFC-0302_ValidatorNodes.md) +* [RFC-0304: Validator Node committee selection](RFC-0304_VNCommittees.md) +* [RFC-0220: Asset Checkpoints](RFC-0220_AssetCheckpoints.md) + +## Description + +### Abstract +This document will describe the process an [Asset Issuer] (AI) will go through in order to: +- Register a [digital asset] (DA) on the [base layer], +- assemble a [committee] of [Validator Node]s (VNs) and +- commence operation of the (DA) on the [Digital Asset Network] (DAN). + +### Asset creation instruction +The first step in registering and commencing the operation of an asset is that the AI MUST issue an asset creation transaction to the [base layer]. + +This transaction will be time-locked for the length of the desired nomination period. This ensures that this transaction cannot be spent until the nomination period has elapsed so that it is present during the entire nomination process. The value of the transaction will be the `asset_creation_fee` described in [RFC-0311](RFC-0311_AssetTemplates.md). The AI will spend the transaction back to themselves but locking this fee up at this stage achieves 2 goals. Firstly, it makes it expensive to spam the network with asset creation transactions that a malicious AI does not intend to complete. Secondly, it proves to the VNs that participate in the nomination process that the AI does indeed have the funds required to commence operation of the asset once the committee has been selected. If the asset registration process fails, for example if there are not enough available VNs for the committee, then the AI can refund the fee to themselves after the time-lock expires. + +The transaction will contain the following extra metadata to facilitate the registration process: + +1. The value of the transaction in clear text and the public spending key of the commitment so that it can be verified by third parties. A third party can verify the value of the commitment by doing the following: + + 1. The output commitment is $ C = k.G + v.H $ + 2. $ v $ and $ k.G $ are provided in the metadata + 3. A verifier can calculate $ C - k.G = v.H $ and verify this value by multiplying the clear text $ v $ by $ H $ themselves. + +2. A commitment (hash) to the asset parameters as defined by a [DigitalAssetTemplate] described in [RFC-0311](RFC-0311_AssetTemplates.md). This template will define all the parameters of the asset the AI intends to register including information the VNs need to know like what the required [AssetCollateral] is to be part of the committee. + +Once this transaction has been confirmed to the required depth on the blockchain the nomination phase can begin. + +### Nomination phase +The next step in registering an asset is for the AI to select a committee of VNs to manage the asset. The process to do this is described in [RFC-0304](RFC-0304_VNCommittees.md). This process lasts as long as the time-lock on the asset creation transaction described above. The VNs have until that time-lock elapses to nominate themselves (in the case of an asset being registered using the `committee_mode::PUBLIC_NOMINATION` parameter in the [DigitalAssetTemplate]). + +### Asset commencement +Once the Nomination phase is complete and the AI has selected a committee as described in [RFC-0304](RFC-0304_VNCommittees.md) the chosen committee and AI are ready to commit their `asset_creation_fee` and [AssetCollateral]s to commence the operation of the asset. This is done by the AI and the committee members collaborating to build the initial [Checkpoint] of the asset. When this [Checkpoint] transaction is published to the [base layer] the [digital asset] will be live on the DAN. The [Checkpoint] transaction is described in [RFC_0220](RFC-0220_AssetCheckpoints.md). + +[assetcollateral]: Glossary.md#assetcollateral +[asset issuer]: Glossary.md#asset-issuer +[base layer]: Glossary.md#base-layer +[checkpoint]: Glossary.md#checkpoint +[digital asset]: Glossary.md#digital-asset +[DigitalAssetTemplate]: Glossary.md#digitalassettemplate +[committee]: Glossary.md#committee +[CommitteeSelectionStrategy]: Glossary.md#committeeselectionstrategy +[validator node]: Glossary.md#validator-node +[digital asset network]: Glossary.md#digital-asset-network +[trusted node]: Glossary.md#trusted-node diff --git a/RFC/src/RFC-0400_TariApplications.md b/RFC/src/RFC-0400_TariApplications.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/RFC/src/RFC-1000_TariUseCases.md b/RFC/src/RFC-1000_TariUseCases.md new file mode 100644 index 0000000000..e135bcb8f6 --- /dev/null +++ b/RFC/src/RFC-1000_TariUseCases.md @@ -0,0 +1,199 @@ +# RFC-1000/TariUseCases + +## A Digital Asset Framework + +![status: draft](./theme/images/status-draft.svg) + +**Maintainer(s)**: [Leland Lee](https://github.com/lelandlee) + +# License + +[ The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2019 The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", +"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as +shown here. + +## Disclaimer + +The purpose of this document and its content is for information purposes only and may be subject to change or update +without notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community regarding the +technological merits of the potential system outlined herein. + +## Goals +There will be many types of digital assets that can be issued on Tari. This document is intended to help potential asset +issuers identify use cases for Tari that lead to the design of new types of digital assets that may or may not exist in +their ecosystems today. + +## Related RFCs +* [RFC-0001: An overview of the Tari network](RFC-0001_overview.md) +* [RFC-0300: The Digital Assets Network](RFC-0300_DAN.md) + +## Description + +Tari [digital asset]s may exist on the Tari [Digital Assets Network] (DAN), are perceived to have value and can be +owned, with the associated digital data being classified as intangible, personal property. + +### Types +Digital assets may have the following high level classification scheme: + +* Symbolic + * Insignia + * Mascots + * Event-driven or historical (eg. a rivalry or a highly temporal event) +* Artifacts or Objects + * Legendary + * Rare + * High demand + * Artistic + * Historical +* Utility + * Tickets + * In-game items + * Points + * Currency + * Full or fractional representation + * Bearer instruments / access token + * Chat stickers +* Persona + * Personality trait(s) + * Emotion(s) + * Statistics + * Superpower(s) + * Storyline(s) + * Relationship(s) + * Avatar + +### Behaviors +Digital asset tokens may influence the following behavioral types: + +* Advance purchase +* Experience enhancement +* Sharing +* Loyalty +* Reward for performance +* Tipping +* Donating to a charity +* Collecting +* Trading +* Building / combining + +### Attributes +Digital assets have many different properties, which may be one or more of the following: + +* Digital assets can be interactive: + * Easter eggs + * Media + * Dynamic (eg. Imagine if assets were similar to sounds, visualisations or other kinds of "demos" or "gifs") + * Game mechanics + * Evolutionary +* Digital assets can be combined to create super assets. +* Digital assets can be attribute(s) of another digital asset (e.g. wheels of a vehicle or a VIP ticket has two drink cards). +* Digital assets can have contingencies (e.g. ownership of a digital asset is contingent on ownership of a different digital asset, using this digital asset is contingent on holding it for a particular duration, etc) +* Digital assets can have utility (e.g. be useful). +* Digital assets can be used across platforms (e.g. a digital asset for a game could be used as avatars in a social network). +* Digital assets can have history. +* Digital assets can have user-generated tags and/or metadata. + +### Interactions + +Digital asset owners may have the following interactions with the DAN and/or other people: +* Digital asset owners can attest that they have ownership over there assets at time `t`. +* Digital assets owners may attest ownership to an individual, to a group of friends or to the entire world. + +### Rules +Rules are the governance of how digital assets may be used or transferred, as defined by the asset issuer: + +* Royalty Fees - Digital asset issuers can set a royalty that charges a fee every time the digital asset is transferred between parties. The fee as defined by the issuer can be fixed or dynamic or follow a complex formula and value is granted to the issuer(s) and/or other entities. +* Contingency - Digital asset ownership/interaction may be contingent/dependent on another asset. +* Timing Controls - Digital assets can only be transferred or used at particular times. +* Sharing - Digital assets can be shared to others or even co-owned. +* Privacy - Ownership of a digital asset can be changed from private to public. +* Upgradability/Versioning - Digital assets can be upgraded and/or versioned. +* Redeemability - Digital assets can be used once or multiple times. + +### Examples +Some examples of how different types of digital assets with different attributes, rules and interactions may be +manifested are shown below. + +#### Crystal Skull of Akador + +* Is rare + * Is 1 of 5 +* Is legendary + * https://indianajones.fandom.com/wiki/Crystal_Skull_of_Akator + * Press reports that this artifact could be worth $X +* Drives collectibility +* Drives advance purchase + * If you are one of the first 100,000 people to buy tickets to Indiana Jones World you have a chance of winning this 1 of 5 artifact. +* Has superpowers and utility + * If you have this item while visiting Indiana Jones World, you get to skip the line three times. +* Is a contingency for another asset + * If you collect this item, two Sankara stones, and the Cross of Coronado, you can buy the ark of the covenant: + * Ark of the covenant is rare. It is 1 of 1. + * Ark of the covenant is legendary. + * Ark of the covenant gives you lifetime access to Indiana Jones World. + * Ark of the covenant has rules; 20% of the resale price goes to Indiana Jones World. + +#### AB de Villiers' bat + +* Isn’t Rare + * Is 1 of 100,000 +* Is Legendary + * https://www.youtube.com/watch?v=HK6B2da3DPA +* Drives collectibility + * Is part of a series of bats from famous batsmen. +* Can be combined with other assets + * Be one of the first 10 people to combine six bats to turn this asset into a One Day International (ODI) century bat: + * ODI century bats are rare, they are 1 of 10. + * ODI century bats are legendary. +* Has no superpowers +* Has no utility +* Has no rules + +#### OVO Owl x Supreme + +* Is Rare + * Is 1 of 200 +* Is Legendary + * https://www.supremenewyork.com + * https://us.octobersveryown.com +* Has a game mechanic + * Every time it’s transferred, it may become a golden ticket that grants you access to any Drake show. + * If its become a golden ticket and is transferred, it loses its golden ticket superpower. +* Has utility + * Unlocks exclusive media content feat. Drake hosted by OVO SOUND. + * May become a golden ticket that grants you access to any Drake show. +* Has rules + * Every time its transferred Supreme and OVO SOUND receive 25% of the transaction value. + +[digital asset]: Glossary.md#digital-asset +[Digital Assets Network]:Glossary.md#digital-asset-network +[Validator Nodes]:Glossary.md#validator-node +[asset issuer]:Glossary.md#asset-issuer diff --git a/RFC/src/SUMMARY.md b/RFC/src/SUMMARY.md index 401fa31371..052deeb29a 100644 --- a/RFC/src/SUMMARY.md +++ b/RFC/src/SUMMARY.md @@ -2,41 +2,38 @@ [About the Tari RFC documents](about.md) - [RFC-0001: An overview of the Tari network](RFC-0001_overview.md) + - [RFC-1000: Tari Use cases](RFC-1000_TariUseCases.md) - [RFC-0010: Tari code structure and organisation](RFC-0010_CodeStructure.md) - [RFC-0100: The Tari Base Layer](RFC-0100_BaseLayer.md) - - [RFC-0110: Base nodes](RFC-0110_BaseNodes.md) - - [RFC-0130: Mining](RFC-0130_Mining.md) - - [RFC-0140: Sync and Seeding](RFC-0140_Syncing_and_seeding.md) - - [RFC-0150: Wallets](RFC-0150_Wallets.md) - - [RFC-0170: Network Communication Protocol](RFC-0170_NetworkCommunicationProtocol.md) - - [RFC-0171: Message Serialisation](RFC-0171_MessageSerialisation.md) -- [RFC-0200: Tari-specific extensions to Mimblewimble](RFC-0200_BaseLayerExtensions.md) - - [RFC-0220: Asset checkpoints](RFC-0220_AssetCheckpoints.md) - - [RFC-0230: Hash time locked contracts](RFC-0230_HTLC.md) - - [RFC-0322: Validator Node Registration](RFC-0322_VNRegistration.md) - - [RFC-0341: Asset registration](RFC-0341_AssetRegistration.md) + - [RFC-0110: Base nodes](RFC-0110_BaseNodes.md) + - [RFC-0130: Mining](RFC-0130_Mining.md) + - [RFC-0140: Sync and Seeding](RFC-0140_Syncing_and_seeding.md) + - [RFC-0150: Wallets](RFC-0150_Wallets.md) + - [RFC-0151: Transaction protocol](RFC-0151_TransactionProtocol.md) + - [RFC-0170: Network Communication Protocol](RFC-0170_NetworkCommunicationProtocol.md) + - [RFC-0171: Message Serialisation](RFC-0171_MessageSerialisation.md) + - [RFC-0172: Peer to Peer Messaging Protocol](RFC-0172_PeerToPeerMessagingProtocol.md) + - [RFC-0190: Mempool](RFC-0190_Mempool.md) + - [Tari-specific extensions to Mimblewimble](BaseLayerExtensions.md) + - [RFC-0220: Asset checkpoints](RFC-0220_AssetCheckpoints.md) + - [RFC-0230: Hash time locked contracts](RFC-0230_HTLC.md) + - [RFC-0322: Validator Node Registration](RFC-0322_VNRegistration.md) + - [RFC-0341: Asset registration](RFC-0341_AssetRegistration.md) - [RFC-0300: The Digital Assets Network](RFC-0300_DAN.md) - - [RFC-0301: Namespace Registration](RFC-0301_NamespaceRegistration.md) - - [RFC-0302: Validator Nodes](RFC-0302_ValidatorNodes.md) - - [RFC-0304: Validator Node committee selection](RFC-0304_VNCommittees.md) -- [RFC-0310: Asset Management](RFC-0310_AssetManagement.md) - - [RFC-0311: Digital Asset templates](RFC-0311_AssetTemplates.md) - - [RFC-0313: The asset read-only API](RFC-0313_AssetReadAPI.md) - - [RFC-0314: Asset discovery](RFC-0314_AssetDiscovery.md) - - [RFC-0315: Asset transfer mechanism](RFC-0315_AssetTransfer.md) - - [RFC-0316: Asset retirement](RFC-0316_AssetRetirement.md) -- [RFC-0340: Validator Node Consensus](RFC-0340_VNConsensusOverview.md) - - [RFC-0342: Validator node instructions](RFC-0342_VNInstructions.md) - - [RFC-0343: The VN consensus algorithm](RFC-0343_VNConsensusAlgorithm.md) -- [RFC-0350: DAN Dispute resolution mechanism](RFC-0350_DisputeResolution.md) -- [RFC-0370: Tari Collections](RFC-0370_TariCollections.md) - - [RFC-0371: Token wallet](RFC-0371_TokenWallet.md) - - [RFC-0372: Tari Collections Peer-to-peer communication](RFC-0372_TCP2PComm.md) - - [RFC-0373: Instruction API](RFC-0373_TCInstructionAPI.md) - - [RFC-0374: Asset Creation and management](RFC-0374_TCAssetManagement.md) - - [RFC-0375: Tari wallet bridge](RFC-0375_TCWalletBridge.md) + - [RFC-0301: Namespace Registration](RFC-0301_NamespaceRegistration.md) + - [RFC-0302: Validator Nodes](RFC-0302_ValidatorNodes.md) + - [RFC-0304: Validator Node committee selection](RFC-0304_VNCommittees.md) + - [Asset Management](AssetManagement.md) + - [RFC-0311: Digital Asset templates](RFC-0311_AssetTemplates.md) + - [RFC-0313: The asset read-only API](RFC-0313_AssetReadAPI.md) + - [RFC-0315: Asset transfer mechanism](RFC-0315_AssetTransfer.md) + - [RFC-0316: Asset retirement](RFC-0316_AssetRetirement.md) + - [RFC-0340: Validator Node Consensus](RFC-0340_VNConsensusOverview.md) + - [RFC-0342: Validator node instructions](RFC-0342_VNInstructions.md) + - [RFC-0343: The VN consensus algorithm](RFC-0343_VNConsensusAlgorithm.md) + - [RFC-0350: DAN Dispute resolution mechanism](RFC-0350_DisputeResolution.md) - [RFC-0500: Tari payment channels](RFC-0500_PaymentChannels.md) - - [RFC-0530: Peer-to-peer payments](RFC-0530_P2PPayments.md) - - [RFC-0550: Instruction fees](RFC-0550_VNFees.md) -- [Glossary](Glossary.md) +- [RFC-0400: Tari Application Suite](RFC-0400_TariApplications.md) +- [RFC-1000: Tari Use Cases](RFC-1000_TariUseCases.md) +- [Glossary](Glossary.md) diff --git a/RFC/src/assets/Tari Network Overview.drawio b/RFC/src/assets/Tari Network Overview.drawio new file mode 100644 index 0000000000..c19af7b1fc --- /dev/null +++ b/RFC/src/assets/Tari Network Overview.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/RFC/src/theme/css/chrome.css b/RFC/src/theme/css/chrome.css index 3405dc0afd..bdf210a860 100644 --- a/RFC/src/theme/css/chrome.css +++ b/RFC/src/theme/css/chrome.css @@ -190,7 +190,8 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta vertical-align: middle; padding: 0.1em 0.3em; border-radius: 3px; - color: var(--inline-code-color); + background-color: #f1f1f1; + color: #6e6b5e; } a:hover > .hljs { diff --git a/RFC/src/theme/images/tari_network_overview.png b/RFC/src/theme/images/tari_network_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..db60d47a9964d976f695c77844ec9d8d5774894b GIT binary patch literal 68369 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

^RT?(?SQtl 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 SigningData { let mut rng = OsRng::new().unwrap(); - let msg = b"This parrot is dead"; - let k = Curve25519SecretKey::random(&mut rng); - let p = Curve25519PublicKey::from_secret_key(&k); - b.iter(|| { - let sig = Curve25519EdDSA::sign(&k, &p, msg); - assert!(sig.verify(&p, msg)); - }) + let mut msg = [0u8; 32]; + rng.fill_bytes(&mut msg); + let (k, p) = RistrettoPublicKey::random_keypair(&mut rng); + let r = RistrettoSecretKey::random(&mut rng); + let m = RistrettoSecretKey::from_bytes(&msg).unwrap(); + SigningData { k, p, r, m } +} + +fn sign_message(c: &mut Criterion) { + c.bench_function("Create RistrettoSchnorr", move |b| { + b.iter_batched( + || gen_keypair(), + |d| { + let _ = RistrettoSchnorr::sign(d.k, d.r, &d.m.to_vec()).unwrap(); + }, + BatchSize::SmallInput, + ); + }); + + // assert!(sig.verify(&p, &msg_key)); +} + +fn verify_message(c: &mut Criterion) { + c.bench_function("Verify RistrettoSchnorr", move |b| { + b.iter_batched( + || { + let d = gen_keypair(); + let s = RistrettoSchnorr::sign(d.k.clone(), d.r.clone(), &d.m.to_vec()).unwrap(); + (d, s) + }, + |(d, s)| assert!(s.verify(&d.p, &d.m)), + BatchSize::SmallInput, + ); + }); } + +criterion_group!( +name = signatures; +config = Criterion::default().warm_up_time(Duration::from_millis(500)); +targets = generate_secret_key, native_keypair, sign_message, verify_message +); +criterion_main!(signatures); diff --git a/infrastructure/crypto/src/commitment.rs b/infrastructure/crypto/src/commitment.rs index c7c1e8ba27..84669ffbbc 100644 --- a/infrastructure/crypto/src/commitment.rs +++ b/infrastructure/crypto/src/commitment.rs @@ -20,7 +20,13 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::keys::SecretKey; +use crate::keys::PublicKey; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + ops::{Add, Sub}, +}; +use tari_utilities::{ByteArray, ByteArrayError}; /// A commitment is like a sealed envelope. You put some information inside the envelope, and then seal (commit) it. /// You can't change what you've said, but also, no-one knows what you've said until you're ready to open (open) the @@ -38,18 +44,96 @@ use crate::keys::SecretKey; /// C_2 &= v_2.G + k_2.H \\\\ /// \therefore C_1 + C_2 &= (v_1 + v_2)G + (k_1 + k_2)H /// \end{aligned} $$ -pub trait HomomorphicCommitment { - type K: SecretKey; +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(bound(deserialize = "P: PublicKey"))] +pub struct HomomorphicCommitment

(pub(crate) P) +where P: PublicKey; - fn open(&self, k: &Self::K, v: &Self::K) -> bool; - fn as_bytes(&self) -> &[u8]; +impl

HomomorphicCommitment

+where P: PublicKey +{ + pub fn as_public_key(&self) -> &P { + &self.0 + } + + pub fn from_public_key(p: &P) -> HomomorphicCommitment

{ + HomomorphicCommitment(p.clone()) + } +} + +impl

ByteArray for HomomorphicCommitment

+where P: PublicKey +{ + fn from_bytes(bytes: &[u8]) -> Result { + let p = P::from_bytes(bytes)?; + Ok(Self(p)) + } + + fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl

PartialOrd for HomomorphicCommitment

+where P: PublicKey +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(&other.0)) + } +} + +impl

Ord for HomomorphicCommitment

+where P: PublicKey +{ + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +/// Add two commitments together. Note! There is no check that the bases are equal. +impl<'b, P> Add for &'b HomomorphicCommitment

+where + P: PublicKey, + &'b P: Add<&'b P, Output = P>, +{ + type Output = HomomorphicCommitment

; + + fn add(self, rhs: &'b HomomorphicCommitment

) -> Self::Output { + HomomorphicCommitment(&self.0 + &rhs.0) + } +} + +/// Subtracts the left commitment from the right commitment. Note! There is no check that the bases are equal. +impl<'b, P> Sub for &'b HomomorphicCommitment

+where + P: PublicKey, + &'b P: Sub<&'b P, Output = P>, +{ + type Output = HomomorphicCommitment

; + + fn sub(self, rhs: &'b HomomorphicCommitment

) -> Self::Output { + HomomorphicCommitment(&self.0 - &rhs.0) + } } pub trait HomomorphicCommitmentFactory { - type K: SecretKey; - type C: HomomorphicCommitment; - fn create(k: &Self::K, v: &Self::K) -> Self::C; + type P: PublicKey; + + /// Create a new commitment with the value and blinding factor provided. The implementing type will provide the + /// base values + fn commit(&self, k: &::K, v: &::K) -> HomomorphicCommitment; /// return an identity point for addition using the specified base point. This is a commitment to zero with a zero /// blinding factor on the base point - fn zero() -> Self::C; + fn zero(&self) -> HomomorphicCommitment; + /// Test whether the given keys open the given commitment + fn open( + &self, + k: &::K, + v: &::K, + commitment: &HomomorphicCommitment, + ) -> bool; + /// Create a commitment from a spending key and a integer value + fn commit_value(&self, k: &::K, value: u64) -> HomomorphicCommitment; + /// Test whether the given private key and value open the given commitment + fn open_value(&self, k: &::K, v: u64, commitment: &HomomorphicCommitment) -> bool; } diff --git a/infrastructure/crypto/src/common.rs b/infrastructure/crypto/src/common.rs index 7f99866673..4370396623 100644 --- a/infrastructure/crypto/src/common.rs +++ b/infrastructure/crypto/src/common.rs @@ -20,22 +20,32 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use blake2::{ - digest::{Input, VariableOutput}, - VarBlake2b, -}; +use blake2::VarBlake2b; use digest::{ generic_array::{typenum::U32, GenericArray}, FixedOutput, + Input, Reset, + VariableOutput, }; /// A convenience wrapper produce 256 bit hashes from Blake2b #[derive(Clone, Debug)] pub struct Blake256(VarBlake2b); +impl Blake256 { + pub fn new() -> Self { + let h = VarBlake2b::new(32).unwrap(); + Blake256(h) + } + + pub fn result(self) -> GenericArray { + self.fixed_result() + } +} + impl Default for Blake256 { - fn default() -> Blake256 { + fn default() -> Self { let h = VarBlake2b::new(32).unwrap(); Blake256(h) } @@ -61,3 +71,20 @@ impl Reset for Blake256 { (self.0).reset() } } + +#[cfg(test)] +mod test { + use crate::common::Blake256; + use digest::Input; + use tari_utilities::hex; + + #[test] + fn blake256() { + let e = Blake256::new().chain(b"one").chain(b"two").result().to_vec(); + let h = hex::to_hex(&e); + assert_eq!( + h, + "03521c1777639fc6e5c3d8c3b4600870f18becc155ad7f8053d2c65bc78e4aa0".to_string() + ); + } +} diff --git a/infrastructure/crypto/src/keys.rs b/infrastructure/crypto/src/keys.rs index 3868ff8b43..1136ff882f 100644 --- a/infrastructure/crypto/src/keys.rs +++ b/infrastructure/crypto/src/keys.rs @@ -26,6 +26,7 @@ //! implementation without worrying too much about the impact on upstream code. use rand::{CryptoRng, Rng}; +use serde::{de::DeserializeOwned, ser::Serialize}; use std::ops::Add; use tari_utilities::ByteArray; @@ -54,7 +55,9 @@ pub trait SecretKey: ByteArray + Clone + PartialEq + Eq + Add + D /// implementations need to implement this trait for them to be used in Tari. /// /// See [SecretKey](trait.SecretKey.html) for an example. -pub trait PublicKey: ByteArray + Add + Clone + PartialOrd + Ord + Default { +pub trait PublicKey: + ByteArray + Add + Clone + PartialOrd + Ord + Default + Serialize + DeserializeOwned +{ type K: SecretKey; /// Calculate the public key associated with the given secret key. This should not fail; if a /// failure does occur (implementation error?), the function will panic. @@ -62,7 +65,7 @@ pub trait PublicKey: ByteArray + Add + Clone + PartialOrd + Ord + fn key_length() -> usize; - fn batch_mul(scalars: &Vec, points: &Vec) -> Self; + fn batch_mul(scalars: &[Self::K], points: &[Self]) -> Self; fn random_keypair(rng: &mut R) -> (Self::K, Self) { let k = Self::K::random(rng); @@ -70,3 +73,10 @@ pub trait PublicKey: ByteArray + Add + Clone + PartialOrd + Ord + (k, pk) } } + +/// This trait provides a common mechanism to calculate a shared secret using the private and public key of two parties +pub trait DiffieHellmanSharedSecret: ByteArray + Clone + PartialEq + Eq + Add + Default { + type PK: PublicKey; + /// Generate a shared secret from one party's private key and another party's public key + fn shared_secret(k: &::K, pk: &Self::PK) -> Self::PK; +} diff --git a/infrastructure/crypto/src/lib.rs b/infrastructure/crypto/src/lib.rs index 4165219c19..ac6e7f2b71 100644 --- a/infrastructure/crypto/src/lib.rs +++ b/infrastructure/crypto/src/lib.rs @@ -1,5 +1,4 @@ extern crate serde; -extern crate serde_derive; extern crate serde_json; #[macro_use] @@ -7,12 +6,13 @@ extern crate lazy_static; #[macro_use] pub mod macros; -pub mod challenge; pub mod commitment; pub mod common; pub mod keys; pub mod musig; +pub mod range_proof; pub mod signatures; // Implementations +#[allow(clippy::op_ref)] pub mod ristretto; diff --git a/infrastructure/crypto/src/musig.rs b/infrastructure/crypto/src/musig.rs index 9604b96baf..e6d94d4546 100644 --- a/infrastructure/crypto/src/musig.rs +++ b/infrastructure/crypto/src/musig.rs @@ -23,16 +23,10 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. -use crate::{ - challenge::Challenge, - keys::{PublicKey, SecretKey}, -}; +use crate::keys::{PublicKey, SecretKey}; use derive_error::Error; use digest::Digest; -use std::{ - ops::{Add, Mul}, - prelude::v1::Vec, -}; +use std::{ops::Mul, prelude::v1::Vec}; //---------------------------------------------- Constants ------------------------------------------------// pub const MAX_SIGNATURES: usize = 32768; // If you need more, call customer support @@ -200,11 +194,11 @@ where /// You should ensure that the SecretKey constructor protects against failures and that the hash digest given /// produces a byte array of the correct length. fn calculate_common(&self) -> K { - let mut common = Challenge::::new(); + let mut common = D::new(); for k in self.pub_keys.iter() { - common = common.concat(k.as_bytes()); + common = common.chain(k.as_bytes()); } - K::from_vec(&common.hash()) + K::from_bytes(&common.result()) .expect("Could not calculate Scalar from hash value. Your crypto/hash combination might be inconsistent") } @@ -214,8 +208,8 @@ where /// You should ensure that the SecretKey constructor protects against failures and that the hash digest given /// produces a byte array of the correct length. fn calculate_partial_key(common: &[u8], pubkey: &P) -> K { - let k = Challenge::::new().concat(common).concat(pubkey.as_bytes()).hash(); - K::from_vec(&k) + let k = D::new().chain(common).chain(pubkey.as_bytes()).result(); + K::from_bytes(&k) .expect("Could not calculate Scalar from hash value. Your crypto/hash combination might be inconsistent") } @@ -236,7 +230,7 @@ where /// Calculate the value of the Joint MuSig public key. **NB**: you should usually sort the participant's keys /// before calculating the joint key. - fn calculate_joint_key(scalars: &Vec, pub_keys: &Vec

) -> P { + fn calculate_joint_key(scalars: &[K], pub_keys: &[P]) -> P { P::batch_mul(scalars, pub_keys) } } @@ -280,171 +274,3 @@ where &self.joint_pub_key } } - -//------------------------------------------- Fixed Set ---------------------------------------------// - -pub struct FixedSet { - items: Vec>, -} - -impl FixedSet { - /// Creates a new fixed set of size n. - pub fn new(n: usize) -> FixedSet { - FixedSet { items: vec![None; n] } - } - - /// Returns the size of the fixed set, NOT the number of items that have been set - pub fn size(&self) -> usize { - self.items.len() - } - - /// Set the `index`th item to `val`. Any existing item is overwritten. The set takes ownership of `val`. - pub fn set_item(&mut self, index: usize, val: T) -> bool { - if index >= self.items.len() { - return false; - } - self.items[index] = Some(val); - true - } - - /// Return a reference to the `index`th item, or `None` if that item has not been set yet. - pub fn get_item(&self, index: usize) -> Option<&T> { - match self.items.get(index) { - None => None, - Some(option) => option.as_ref(), - } - } - - /// Delete an item from the set by setting the `index`th value to None - pub fn clear_item(&mut self, index: usize) { - if index < self.items.len() { - self.items[index] = None; - } - } - - /// Returns true if every item in the set has been set. An empty set returns true as well. - pub fn is_full(&self) -> bool { - self.items.iter().all(|v| v.is_some()) - } - - /// Return the index of the given item in the set by performing a linear search through the set - pub fn search(&self, val: &T) -> Option { - let key = self - .items - .iter() - .enumerate() - .find(|v| v.1.is_some() && v.1.as_ref().unwrap() == val); - match key { - Some(item) => Some(item.0), - None => None, - } - } - - /// Produces the sum of the values in the set, provided the set is full - pub fn sum(&self) -> Option - where for<'a> &'a T: Add<&'a T, Output = T> { - // This function uses HTRB to work: See https://doc.rust-lang.org/nomicon/hrtb.html - // or here https://users.rust-lang.org/t/lifetimes-for-type-constraint-where-one-reference-is-local/11087 - if self.size() == 0 || !self.is_full() { - return None; - } - let mut iter = self.items.iter().filter_map(|v| v.as_ref()); - // Take the first item - let mut sum = iter.next().unwrap().clone(); - for v in iter { - sum = &sum + v; - } - Some(sum) - } -} - -//------------------------------------------- Tests ---------------------------------------------// - -#[cfg(test)] -mod test { - use super::FixedSet; - - #[derive(Eq, PartialEq, Clone, Debug)] - struct Foo { - baz: String, - } - - #[test] - fn zero_sized_fixed_set() { - let mut s = FixedSet::::new(0); - assert!(s.is_full(), "Set should be full"); - assert_eq!(s.set_item(1, 1), false, "Should not be able to set item"); - assert_eq!(s.get_item(0), None, "Should not return a value"); - } - - fn data(s: &str) -> Foo { - match s { - "patrician" => Foo { - baz: "The Patrician".into(), - }, - "rincewind" => Foo { - baz: "Rincewind".into(), - }, - "vimes" => Foo { - baz: "Commander Vimes".into(), - }, - "librarian" => Foo { - baz: "The Librarian".into(), - }, - "carrot" => Foo { - baz: "Captain Carrot".into(), - }, - _ => Foo { baz: "None".into() }, - } - } - - #[test] - fn small_set() { - let mut s = FixedSet::::new(3); - // Set is empty - assert_eq!(s.is_full(), false); - // Add an item - assert!(s.set_item(1, data("patrician"))); - assert_eq!(s.is_full(), false); - // Add an item - assert!(s.set_item(0, data("vimes"))); - assert_eq!(s.is_full(), false); - // Replace an item - assert!(s.set_item(1, data("rincewind"))); - assert_eq!(s.is_full(), false); - // Add item, filling set - assert!(s.set_item(2, data("carrot"))); - assert_eq!(s.is_full(), true); - // Try add an invalid item - assert_eq!(s.set_item(3, data("librarian")), false); - assert_eq!(s.is_full(), true); - // Clear an item - s.clear_item(1); - assert_eq!(s.is_full(), false); - // Check contents - assert_eq!(s.get_item(0).unwrap().baz, "Commander Vimes"); - assert!(s.get_item(1).is_none()); - assert_eq!(s.get_item(2).unwrap().baz, "Captain Carrot"); - // Size is 3 - assert_eq!(s.size(), 3); - // Slow search - assert_eq!(s.search(&data("carrot")), Some(2)); - assert_eq!(s.search(&data("vimes")), Some(0)); - assert_eq!(s.search(&data("librarian")), None); - } - - #[test] - fn sum_values() { - let mut s = FixedSet::::new(4); - s.set_item(0, 5); - assert_eq!(s.sum(), None); - s.set_item(1, 4); - assert_eq!(s.sum(), None); - s.set_item(2, 3); - assert_eq!(s.sum(), None); - s.set_item(3, 2); - assert_eq!(s.sum(), Some(14)); - s.set_item(1, 0); - assert_eq!(s.sum(), Some(10)); - } -} diff --git a/infrastructure/crypto/src/range_proof.rs b/infrastructure/crypto/src/range_proof.rs new file mode 100644 index 0000000000..6c39eabec9 --- /dev/null +++ b/infrastructure/crypto/src/range_proof.rs @@ -0,0 +1,55 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + commitment::HomomorphicCommitment, + keys::{PublicKey, SecretKey}, +}; +use derive_error::Error; + +#[derive(Debug, Clone, Error, PartialEq)] +pub enum RangeProofError { + /// Could not construct range proof + ProofConstructionError, + /// The deserialization of the range proof failed + InvalidProof, + /// Invalid input was provided to the RangeProofService constructor + InitializationError, +} + +pub trait RangeProofService { + type P: Sized; + type K: SecretKey; + type PK: PublicKey; + + /// Construct a new range proof for the given secret key and value. The resulting proof will be sufficient + /// evidence that the prover knows the secret key and value, and that the value lies in the range determined by + /// the service. + fn construct_proof(&self, key: &Self::K, value: u64) -> Result; + + /// Verify the range proof against the given commitment. If this function returns true, it attests to the + /// commitment having a value in the range [0; 2^64-1] and that the prover knew both the value and private key. + fn verify(&self, proof: &Self::P, commitment: &HomomorphicCommitment) -> bool; +} diff --git a/infrastructure/crypto/src/ristretto/constants.rs b/infrastructure/crypto/src/ristretto/constants.rs index cc487a41e1..79c3255ad4 100644 --- a/infrastructure/crypto/src/ristretto/constants.rs +++ b/infrastructure/crypto/src/ristretto/constants.rs @@ -20,12 +20,12 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use curve25519_dalek::ristretto::CompressedRistretto; +use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; /// These points on the Ristretto curve have been created by sequentially hashing the Generator point with SHA512 and /// using the byte string representation of the hash as input into the `from_uniform_bytes` constructor in /// [RistrettoPoint](Struct.RistrettoPoint.html). This process is validated with the `check_nums_points` test below. -pub const RISTRETTO_NUMS_POINTS: [CompressedRistretto; 10] = [ +pub const RISTRETTO_NUMS_POINTS_COMPRESSED: [CompressedRistretto; 10] = [ CompressedRistretto([ 144, 202, 17, 205, 108, 98, 39, 203, 10, 188, 57, 226, 113, 12, 68, 74, 230, 97, 126, 168, 24, 152, 231, 22, 53, 63, 52, 16, 217, 101, 102, 5, @@ -68,39 +68,55 @@ pub const RISTRETTO_NUMS_POINTS: [CompressedRistretto; 10] = [ ]), ]; -pub const RISTRETTO_PEDERSEN_H: CompressedRistretto = RISTRETTO_NUMS_POINTS[0]; +lazy_static! { + pub static ref RISTRETTO_NUMS_POINTS: [RistrettoPoint; 10] = { + let mut arr = [RistrettoPoint::default(); 10]; + for i in 0..10 { + arr[i] = RISTRETTO_NUMS_POINTS_COMPRESSED[i].decompress().unwrap(); + } + arr + }; +} + +pub const RISTRETTO_PEDERSEN_H: CompressedRistretto = RISTRETTO_NUMS_POINTS_COMPRESSED[0]; #[cfg(test)] mod test { - use crate::ristretto::constants::RISTRETTO_NUMS_POINTS; - use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint}; + use crate::ristretto::constants::{RISTRETTO_NUMS_POINTS, RISTRETTO_NUMS_POINTS_COMPRESSED}; + use curve25519_dalek::{ + constants::RISTRETTO_BASEPOINT_POINT, + ristretto::{CompressedRistretto, RistrettoPoint}, + }; use sha2::{Digest, Sha512}; /// Generate a set of NUMS points by sequentially hashing the Ristretto255 generator point. By using /// `from_uniform_bytes`, the resulting point is a NUMS point if the input bytes are from a uniform distribution. - fn nums_ristretto(n: usize) -> Vec { - let mut v = RISTRETTO_BASEPOINT_POINT.compress().to_bytes(); - let mut result = Vec::new(); + fn nums_ristretto(n: usize) -> (Vec, Vec) { + let mut val = RISTRETTO_BASEPOINT_POINT.compress().to_bytes(); + let mut points = Vec::with_capacity(n); + let mut compressed_points = Vec::with_capacity(n); let mut a: [u8; 64] = [0; 64]; for _ in 0..n { - let b = Sha512::digest(&v[..]); - a.copy_from_slice(&b); - let y = RistrettoPoint::from_uniform_bytes(&a); - result.push(y); - v = y.compress().to_bytes(); + let hashed_v = Sha512::digest(&val[..]); + a.copy_from_slice(&hashed_v); + let next_val = RistrettoPoint::from_uniform_bytes(&a); + points.push(next_val); + let next_compressed = next_val.compress(); + val = next_compressed.to_bytes(); + compressed_points.push(next_compressed); } - result + (points, compressed_points) } /// Confirm that the [RISTRETTO_NUM_POINTS array](Const.RISTRETTO_NUMS_POINTS.html) is generated with Nothing Up /// My Sleeve (NUMS). #[test] pub fn check_nums_points() { - let n = RISTRETTO_NUMS_POINTS.len(); + let n = RISTRETTO_NUMS_POINTS_COMPRESSED.len(); let v_arr = nums_ristretto(n); for i in 0..n { - let nums = RISTRETTO_NUMS_POINTS[i].decompress().unwrap(); - assert_eq!(v_arr[i], nums); + assert_eq!(v_arr.0[i], RISTRETTO_NUMS_POINTS[i]); + assert_eq!(v_arr.1[i], RISTRETTO_NUMS_POINTS_COMPRESSED[i]); } } } diff --git a/infrastructure/crypto/src/ristretto/dalek_range_proof.rs b/infrastructure/crypto/src/ristretto/dalek_range_proof.rs new file mode 100644 index 0000000000..6c4f688404 --- /dev/null +++ b/infrastructure/crypto/src/ristretto/dalek_range_proof.rs @@ -0,0 +1,165 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + range_proof::{RangeProofError, RangeProofService}, + ristretto::{ + pedersen::{PedersenCommitment, PedersenCommitmentFactory}, + RistrettoPublicKey, + RistrettoSecretKey, + }, +}; +use bulletproofs::{BulletproofGens, PedersenGens, RangeProof as DalekProof}; +use merlin::Transcript; + +/// A wrapper aroubd the Dalek library implementation of Bulletproof range proofs. +pub struct DalekRangeProofService { + range: usize, + pc_gens: PedersenGens, + bp_gens: BulletproofGens, +} + +const MASK: usize = 0b111_1000; // Mask for 8,16,32,64; the valid ranges on the Dalek library + +impl DalekRangeProofService { + /// Create a new RangeProofService. The Dalek library can only generate proofs for ranges between [0; 2^range), + /// where valid range values are 8, 16, 32 and 64. + pub fn new(range: usize, base: &PedersenCommitmentFactory) -> Result { + if range == 0 || (range | MASK != MASK) { + return Err(RangeProofError::InitializationError); + } + let pc_gens = PedersenGens { + B_blinding: base.G.clone(), + B: base.H.clone(), + }; + let bp_gens = BulletproofGens::new(64, 1); + Ok(DalekRangeProofService { + range, + pc_gens, + bp_gens, + }) + } +} + +impl RangeProofService for DalekRangeProofService { + type K = RistrettoSecretKey; + type P = Vec; + type PK = RistrettoPublicKey; + + fn construct_proof(&self, key: &RistrettoSecretKey, value: u64) -> Result, RangeProofError> { + let mut pt = Transcript::new(b"tari"); + let k = key.0; + let (proof, _) = DalekProof::prove_single(&self.bp_gens, &self.pc_gens, &mut pt, value, &k, self.range) + .map_err(|_| RangeProofError::ProofConstructionError)?; + Ok(proof.to_bytes()) + } + + fn verify(&self, proof: &Self::P, commitment: &PedersenCommitment) -> bool { + let rp = DalekProof::from_bytes(&proof).map_err(|_| RangeProofError::InvalidProof); + if rp.is_err() { + return false; + } + let rp = rp.unwrap(); + let mut pt = Transcript::new(b"tari"); + let c = &commitment.0; + rp.verify_single(&self.bp_gens, &self.pc_gens, &mut pt, &c.compressed, self.range) + .is_ok() + } +} + +#[cfg(test)] +mod test { + use crate::{ + commitment::HomomorphicCommitmentFactory, + keys::SecretKey, + range_proof::{RangeProofError, RangeProofService}, + ristretto::{ + dalek_range_proof::DalekRangeProofService, + pedersen::PedersenCommitmentFactory, + RistrettoSecretKey, + }, + }; + use rand::OsRng; + + #[test] + fn create_and_verify_proof() { + let base = PedersenCommitmentFactory::default(); + let n: usize = 5; + let prover = DalekRangeProofService::new(1 << 5, &base).unwrap(); + let mut rng = OsRng::new().unwrap(); + let k = RistrettoSecretKey::random(&mut rng); + let v = RistrettoSecretKey::from(42); + let commitment_factory: PedersenCommitmentFactory = PedersenCommitmentFactory::default(); + let c = commitment_factory.commit(&k, &v); + let proof = prover.construct_proof(&k, 42).unwrap(); + assert_eq!(proof.len(), (2 * n + 9) * 32); + assert!(prover.verify(&proof, &c)); + // Invalid value + let v2 = RistrettoSecretKey::from(43); + let c = commitment_factory.commit(&k, &v2); + assert_eq!(prover.verify(&proof, &c), false); + // Invalid key + let k = RistrettoSecretKey::random(&mut rng); + let c = commitment_factory.commit(&k, &v); + assert_eq!(prover.verify(&proof, &c), false); + // Both invalid + let c = commitment_factory.commit(&k, &v2); + assert_eq!(prover.verify(&proof, &c), false); + } + + #[test] + fn non_power_of_two_range() { + let base = PedersenCommitmentFactory::default(); + match DalekRangeProofService::new(10, &base) { + Err(RangeProofError::InitializationError) => (), + Err(_) => panic!("Wrong error type"), + Ok(_) => panic!("Should fail with non power of two range"), + } + } + + #[test] + fn cannot_create_proof_for_out_of_range_value() { + let base = PedersenCommitmentFactory::default(); + let prover = DalekRangeProofService::new(8, &base).unwrap(); + let in_range = 255; + let out_of_range = 256; + let mut rng = OsRng::new().unwrap(); + let k = RistrettoSecretKey::random(&mut rng); + // Test with value in range + let v = RistrettoSecretKey::from(in_range); + let commitment_factory = PedersenCommitmentFactory::default(); + let c = commitment_factory.commit(&k, &v); + let proof = prover.construct_proof(&k, in_range).unwrap(); + assert!(prover.verify(&proof, &c)); + // Test value out of range + let proof = prover.construct_proof(&k, out_of_range).unwrap(); + // Test every single value from 0..255 - the proof should fail for every one + for i in 0..257 { + let v = RistrettoSecretKey::from(i); + let c = commitment_factory.commit(&k, &v); + assert_eq!(prover.verify(&proof, &c), false); + } + } +} diff --git a/infrastructure/crypto/src/ristretto/mod.rs b/infrastructure/crypto/src/ristretto/mod.rs index bb898ce48b..f83fea5ec0 100644 --- a/infrastructure/crypto/src/ristretto/mod.rs +++ b/infrastructure/crypto/src/ristretto/mod.rs @@ -21,10 +21,12 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pub mod constants; +pub mod dalek_range_proof; pub mod musig; pub mod pedersen; pub mod ristretto_keys; pub mod ristretto_sig; +pub mod serialize; // Re-export pub use self::{ diff --git a/infrastructure/crypto/src/ristretto/musig.rs b/infrastructure/crypto/src/ristretto/musig.rs index 8292b4f365..3cba6faef0 100644 --- a/infrastructure/crypto/src/ristretto/musig.rs +++ b/infrastructure/crypto/src/ristretto/musig.rs @@ -24,19 +24,20 @@ // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. use crate::{ - challenge::{Challenge, MessageHash}, - musig::{FixedSet, JointKey, JointKeyBuilder, MuSigError}, + musig::{JointKey, JointKeyBuilder, MuSigError}, ristretto::{RistrettoPublicKey, RistrettoSchnorr, RistrettoSecretKey}, signatures::SchnorrSignature, }; use digest::Digest; use std::marker::PhantomData; -use tari_utilities::ByteArray; +use tari_utilities::{fixed_set::FixedSet, ByteArray}; //----------------------------------------- Constants and aliases ------------------------------------------------// type JKBuilder = JointKeyBuilder; type JointPubKey = JointKey; +type MessageHash = Vec; +type MessageHashSlice = [u8]; /// MuSig signature aggregation. [MuSig](https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures/) /// is a 3-round signature aggregation protocol. @@ -67,8 +68,8 @@ type JointPubKey = JointKey; /// # use tari_crypto::ristretto::{ musig::RistrettoMuSig, ristretto_keys::* }; /// # use tari_utilities::ByteArray; /// # use tari_crypto::keys::PublicKey; -/// # use tari_crypto::challenge::Challenge; /// # use sha2::Sha256; +/// # use digest::Digest; /// let mut rng = rand::OsRng::new().unwrap(); /// // Create a new MuSig instance. The number of signing parties must be known at this time. /// let mut alice = RistrettoMuSig::::new(2); @@ -90,8 +91,8 @@ type JointPubKey = JointKey; /// // Round 1 - Collect nonce hashes - each party does this individually and keeps the secret keys secret. /// let (r_a, pr_a) = RistrettoPublicKey::random_keypair(&mut rng); /// let (r_b, pr_b) = RistrettoPublicKey::random_keypair(&mut rng); -/// let h_a = Challenge::::hash_input(pr_a.to_vec()); -/// let h_b = Challenge::::hash_input(pr_b.to_vec()); +/// let h_a = Sha256::digest(pr_a.as_bytes()).to_vec(); +/// let h_b = Sha256::digest(pr_b.as_bytes()).to_vec(); /// bob = bob /// .add_nonce_commitment(&p_b, h_b.clone()) /// .add_nonce_commitment(&p_a, h_a.clone()); @@ -103,12 +104,12 @@ type JointPubKey = JointKey; /// assert!(alice.is_collecting_nonces()); /// // Round 2 - Collect Nonces /// bob = bob -/// .add_nonce(&p_b, pr_b) -/// .add_nonce(&p_a, pr_a); +/// .add_nonce(&p_b, pr_b.clone()) +/// .add_nonce(&p_a, pr_a.clone()); /// assert!(bob.is_collecting_signatures()); /// alice = alice -/// .add_nonce(&p_a, pr_a) -/// .add_nonce(&p_b, pr_b); +/// .add_nonce(&p_a, pr_a.clone()) +/// .add_nonce(&p_b, pr_b.clone()); /// assert!(alice.is_collecting_signatures()); /// // round 3 - Collect partial signatures /// let s_a = alice.calculate_partial_signature(&p_a, &k_a, &r_a).unwrap(); @@ -493,9 +494,9 @@ impl NonceCollection { } } - fn is_valid_nonce(nonce: &RistrettoPublicKey, expected: &MessageHash) -> bool { - let calc = Challenge::::hash_input(nonce.to_vec()); - &calc == expected + fn is_valid_nonce(nonce: &RistrettoPublicKey, expected: &MessageHashSlice) -> bool { + let calc = D::digest(nonce.as_bytes()).to_vec(); + &calc[..] == expected } // We definitely want to consume `nonce` here to discourage nonce re-use @@ -560,15 +561,15 @@ impl SignatureCollection { fn calculate_challenge( r_agg: &RistrettoPublicKey, p_agg: &RistrettoPublicKey, - m: &MessageHash, + m: &MessageHashSlice, ) -> RistrettoSecretKey { - let e = Challenge::::new() - .concat(r_agg.as_bytes()) - .concat(p_agg.as_bytes()) - .concat(m) - .hash(); - RistrettoSecretKey::from_vec(&e).expect("Found a u256 that does not map to a valid Ristretto scalar") + let e = D::new() + .chain(r_agg.as_bytes()) + .chain(p_agg.as_bytes()) + .chain(m) + .result(); + RistrettoSecretKey::from_bytes(&e).expect("Found a u256 that does not map to a valid Ristretto scalar") } fn validate_partial_signature(&self, index: usize, signature: &RistrettoSchnorr) -> bool { @@ -663,7 +664,7 @@ mod test { ) { let (k, pubkey) = RistrettoPublicKey::random_keypair(rng); let (r, nonce) = RistrettoPublicKey::random_keypair(rng); - let hash = Challenge::::hash_input(nonce.to_vec()); + let hash = Sha256::digest(nonce.as_bytes()).to_vec(); (k, pubkey, r, nonce, hash) } @@ -764,7 +765,7 @@ mod test { let (mut musig, data) = create_round_three_musig(n, Some(msg)); assert_eq!(musig.has_failed(), false); // Add the partial signatures - for &s in data.partial_sigs.iter() { + for s in data.partial_sigs.iter() { musig = musig.add_signature(&s, true); assert_eq!( musig.has_failed(), @@ -878,7 +879,7 @@ mod test { #[test] fn invalid_nonce_causes_failure() { let (mut musig, data) = create_round_two_musig(25, None); - musig = musig.add_nonce(&data.pub_keys[0], data.public_nonces[1]); + musig = musig.add_nonce(&data.pub_keys[0], data.public_nonces[1].clone()); assert!(musig.has_failed()); assert_eq!(musig.failure_reason(), Some(MuSigError::MismatchedNonces)); } @@ -889,7 +890,7 @@ mod test { let (mut musig, data) = create_round_three_musig(15, Some(b"message")); let s = RistrettoSecretKey::random(&mut rng); // Create a signature with a valid nonce, but the signature is invalid - let bad_sig = RistrettoSchnorr::new(data.public_nonces[1], s); + let bad_sig = RistrettoSchnorr::new(data.public_nonces[1].clone(), s); let index = data.indices[1]; musig = musig.add_signature(&bad_sig, true); assert!(musig.has_failed()); @@ -930,12 +931,13 @@ mod test { let (musig, data, s_agg) = create_final_musig(15, b"message"); let sig = musig.get_aggregated_signature().unwrap(); let p_agg = musig.get_aggregated_public_key().unwrap(); - let m_hash = Challenge::::hash_input(b"message".to_vec()); - let challenge = Challenge::::new() - .concat(data.r_agg.as_bytes()) - .concat(p_agg.as_bytes()) - .concat(&m_hash); - assert!(sig.verify_challenge(p_agg, challenge)); + let m_hash = Sha256::digest(b"message"); + let challenge = Sha256::new() + .chain(data.r_agg.as_bytes()) + .chain(p_agg.as_bytes()) + .chain(&m_hash) + .result(); + assert!(sig.verify_challenge(p_agg, &challenge)); assert_eq!(&s_agg, sig); } @@ -980,6 +982,7 @@ mod test_joint_key { use super::*; use crate::{keys::PublicKey, musig::MAX_SIGNATURES}; use sha2::Sha256; + use tari_utilities::hex::Hex; #[test] fn zero_sized_jk() { @@ -1043,7 +1046,10 @@ mod test_joint_key { RistrettoPublicKey::from_hex("46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e").unwrap(); let p3 = RistrettoPublicKey::from_hex("e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e").unwrap(); - assert_eq!(key_builder.add_keys(vec![p1, p2, p3]).unwrap(), 3); + assert_eq!( + key_builder.add_keys(vec![p1.clone(), p2.clone(), p3.clone()]).unwrap(), + 3 + ); assert!(key_builder.is_full()); let joint_key = key_builder.build::().unwrap(); assert_eq!(joint_key.size(), 3); diff --git a/infrastructure/crypto/src/ristretto/pedersen.rs b/infrastructure/crypto/src/ristretto/pedersen.rs index 3adcaac99d..357ad6127b 100644 --- a/infrastructure/crypto/src/ristretto/pedersen.rs +++ b/infrastructure/crypto/src/ristretto/pedersen.rs @@ -24,138 +24,84 @@ use crate::{ commitment::HomomorphicCommitment, ristretto::{constants::RISTRETTO_NUMS_POINTS, RistrettoPublicKey}, }; -use curve25519_dalek::{ - constants::RISTRETTO_BASEPOINT_POINT, - ristretto::{CompressedRistretto, RistrettoPoint}, -}; +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, traits::MultiscalarMul}; use crate::{commitment::HomomorphicCommitmentFactory, ristretto::RistrettoSecretKey}; use curve25519_dalek::scalar::Scalar; -use std::ops::{Add, Sub}; - -#[derive(Debug, PartialEq, Eq, Clone)] -#[allow(non_snake_case)] -pub struct PedersenBaseOnRistretto255 { - G: RistrettoPoint, - H: RistrettoPoint, -} +use std::{borrow::Borrow, iter::Sum}; pub const RISTRETTO_PEDERSEN_G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; -pub const RISTRETTO_PEDERSEN_H_COMPRESSED: CompressedRistretto = RISTRETTO_NUMS_POINTS[0]; - -impl Default for PedersenBaseOnRistretto255 { - fn default() -> Self { - PedersenBaseOnRistretto255 { - G: RISTRETTO_PEDERSEN_G, - H: RISTRETTO_PEDERSEN_H_COMPRESSED.decompress().unwrap(), - } - } +lazy_static! { + pub static ref RISTRETTO_PEDERSEN_H: RistrettoPoint = RISTRETTO_NUMS_POINTS[0]; } -lazy_static! { - pub static ref DEFAULT_RISTRETTO_PEDERSON_BASE: PedersenBaseOnRistretto255 = PedersenBaseOnRistretto255::default(); +pub type PedersenCommitment = HomomorphicCommitment; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[allow(non_snake_case)] +pub struct PedersenCommitmentFactory { + pub(crate) G: RistrettoPoint, + pub(crate) H: RistrettoPoint, } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct PedersenOnRistretto255 { - base: &'static PedersenBaseOnRistretto255, - commitment: RistrettoPublicKey, +impl PedersenCommitmentFactory { + /// Create a new Ristretto Commitment factory with the given points as the bases. It's very cheap to create + /// factories, since we only hold references to the static generator points. + #[allow(non_snake_case)] + pub fn new(G: RistrettoPoint, H: RistrettoPoint) -> PedersenCommitmentFactory { + PedersenCommitmentFactory { G, H } + } } -impl PedersenOnRistretto255 { - pub fn as_public_key(&self) -> &RistrettoPublicKey { - &self.commitment +/// The default Ristretto Commitment factory uses the Base point for x25519 and its first Blake256 hash. +impl Default for PedersenCommitmentFactory { + fn default() -> Self { + PedersenCommitmentFactory::new(RISTRETTO_PEDERSEN_G.clone(), RISTRETTO_PEDERSEN_H.clone()) } } -impl HomomorphicCommitmentFactory for PedersenBaseOnRistretto255 { - type C = PedersenOnRistretto255; - type K = RistrettoSecretKey; +impl HomomorphicCommitmentFactory for PedersenCommitmentFactory { + type P = RistrettoPublicKey; - fn create(k: &RistrettoSecretKey, v: &RistrettoSecretKey) -> PedersenOnRistretto255 { - let base = &DEFAULT_RISTRETTO_PEDERSON_BASE; - let c: RistrettoPoint = k.0 * base.G + v.0 * base.H; - PedersenOnRistretto255 { - base, - commitment: RistrettoPublicKey::new_from_pk(c), - } + fn commit(&self, k: &RistrettoSecretKey, v: &RistrettoSecretKey) -> PedersenCommitment { + let c = RistrettoPoint::multiscalar_mul(&[v.0, k.0], &[self.H, self.G]); + HomomorphicCommitment(RistrettoPublicKey::new_from_pk(c)) } - fn zero() -> PedersenOnRistretto255 { - let base = &DEFAULT_RISTRETTO_PEDERSON_BASE; + fn zero(&self) -> PedersenCommitment { let zero = Scalar::zero(); - let c: RistrettoPoint = &zero * base.G + &zero * base.H; - PedersenOnRistretto255 { - base, - commitment: RistrettoPublicKey::new_from_pk(c), - } + let c = RistrettoPoint::multiscalar_mul(&[zero, zero], &[self.H, self.G]); + HomomorphicCommitment(RistrettoPublicKey::new_from_pk(c)) } -} -impl HomomorphicCommitment for PedersenOnRistretto255 { - type K = RistrettoSecretKey; - - fn open(&self, k: &RistrettoSecretKey, v: &RistrettoSecretKey) -> bool { - let c: RistrettoPoint = (v.0 * self.base.H) + (k.0 * self.base.G); - c == self.commitment.point + fn open(&self, k: &RistrettoSecretKey, v: &RistrettoSecretKey, commitment: &PedersenCommitment) -> bool { + let c_test = self.commit(k, v); + commitment.0 == c_test.0 } - fn as_bytes(&self) -> &[u8] { - self.commitment.compressed.as_bytes() + fn commit_value(&self, k: &RistrettoSecretKey, value: u64) -> PedersenCommitment { + let v = RistrettoSecretKey::from(value); + self.commit(k, &v) } -} -/// Add two commitments together -/// #panics -/// * If the base values are not equal -impl<'b> Add for &'b PedersenOnRistretto255 { - type Output = PedersenOnRistretto255; - - fn add(self, rhs: &'b PedersenOnRistretto255) -> Self::Output { - assert_eq!(self.base, rhs.base, "Bases are unequal"); - let lhp = &self.commitment.point; - let rhp = &rhs.commitment.point; - let sum = lhp + rhp; - PedersenOnRistretto255 { - base: self.base, - commitment: RistrettoPublicKey::new_from_pk(sum), - } + fn open_value(&self, k: &RistrettoSecretKey, v: u64, commitment: &HomomorphicCommitment) -> bool { + let kv = RistrettoSecretKey::from(v); + self.open(k, &kv, commitment) } } -/// Add two commitments together -/// #panics -/// * If the base values are not equal -impl Add for PedersenOnRistretto255 { - type Output = PedersenOnRistretto255; - - fn add(self, rhs: PedersenOnRistretto255) -> Self::Output { - assert_eq!(self.base, rhs.base, "Bases are unequal"); - let lhp = self.commitment.point; - let rhp = rhs.commitment.point; - let sum = lhp + rhp; - PedersenOnRistretto255 { - base: self.base, - commitment: RistrettoPublicKey::new_from_pk(sum), - } - } -} - -/// Subtracts the left commitment from the right commitment -/// #panics -/// * If the base values are not equal -impl<'b> Sub for &'b PedersenOnRistretto255 { - type Output = PedersenOnRistretto255; - - fn sub(self, rhs: &'b PedersenOnRistretto255) -> Self::Output { - assert_eq!(self.base, rhs.base, "Bases are unequal"); - let lhp = &self.commitment.point; - let rhp = &rhs.commitment.point; - let sum = lhp - rhp; - PedersenOnRistretto255 { - base: self.base, - commitment: RistrettoPublicKey::new_from_pk(sum), +impl Sum for PedersenCommitment +where T: Borrow +{ + fn sum(iter: I) -> Self + where I: Iterator { + let mut total = RistrettoPoint::default(); + for c in iter { + let commitment = c.borrow(); + total = total + (commitment.0).point } + let sum = RistrettoPublicKey::new_from_pk(total); + HomomorphicCommitment(sum) } } @@ -165,43 +111,39 @@ mod test { use crate::keys::SecretKey; use rand; use std::convert::From; - - lazy_static! { - static ref TEST_RISTRETTO_PEDERSON_BASE: PedersenBaseOnRistretto255 = PedersenBaseOnRistretto255 { - G: RISTRETTO_NUMS_POINTS[0].decompress().unwrap(), - H: RISTRETTO_NUMS_POINTS[1].decompress().unwrap(), - }; - } + use tari_utilities::message_format::MessageFormat; #[test] fn check_default_base() { - let base = PedersenBaseOnRistretto255::default(); + let base = PedersenCommitmentFactory::default(); assert_eq!(base.G, RISTRETTO_PEDERSEN_G); - assert_eq!(base.H.compress(), RISTRETTO_PEDERSEN_H_COMPRESSED) + assert_eq!(base.H, *RISTRETTO_PEDERSEN_H) } #[test] fn check_g_ne_h() { - assert_ne!(RISTRETTO_PEDERSEN_G.compress(), RISTRETTO_PEDERSEN_H_COMPRESSED); + assert_ne!(RISTRETTO_PEDERSEN_G, *RISTRETTO_PEDERSEN_H); } /// Simple test for open: Generate 100 random sets of scalars and calculate the Pedersen commitment for them. /// Then check that the commitment = k.G + v.H, and that `open` returns `true` for `open(&k, &v)` #[test] + #[allow(non_snake_case)] fn check_open() { - let base = &DEFAULT_RISTRETTO_PEDERSON_BASE; + let factory = PedersenCommitmentFactory::default(); + let H = RISTRETTO_PEDERSEN_H.clone(); let mut rng = rand::OsRng::new().unwrap(); for _ in 0..100 { let v = RistrettoSecretKey::random(&mut rng); let k = RistrettoSecretKey::random(&mut rng); - let c = PedersenBaseOnRistretto255::create(&k, &v); - let c_calc: RistrettoPoint = v.0 * base.H + k.0 * base.G; + let c = factory.commit(&k, &v); + let c_calc: RistrettoPoint = v.0 * H + k.0 * RISTRETTO_PEDERSEN_G; assert_eq!(RistrettoPoint::from(c.as_public_key()), c_calc); - assert!(c.open(&k, &v)); + assert!(factory.open(&k, &v, &c)); // A different value doesn't open the commitment - assert!(!c.open(&k, &(&v + &v))); + assert!(!factory.open(&k, &(&v + &v), &c)); // A different blinding factor doesn't open the commitment - assert!(!c.open(&(&k + &v), &v)); + assert!(!factory.open(&(&k + &v), &v, &c)); } } @@ -217,35 +159,57 @@ mod test { for _ in 0..100 { let v1 = RistrettoSecretKey::random(&mut rng); let v2 = RistrettoSecretKey::random(&mut rng); - let v_sum = v1 + v2; + let v_sum = &v1 + &v2; let k1 = RistrettoSecretKey::random(&mut rng); let k2 = RistrettoSecretKey::random(&mut rng); - let k_sum = k1 + k2; - let c1 = PedersenBaseOnRistretto255::create(&k1, &v1); - let c2 = PedersenBaseOnRistretto255::create(&k2, &v2); + let k_sum = &k1 + &k2; + let factory = PedersenCommitmentFactory::default(); + let c1 = factory.commit(&k1, &v1); + let c2 = factory.commit(&k2, &v2); let c_sum = &c1 + &c2; - let c_sum2 = PedersenBaseOnRistretto255::create(&k_sum, &v_sum); - assert!(c1.open(&k1, &v1)); - assert!(c2.open(&k2, &v2)); + let c_sum2 = factory.commit(&k_sum, &v_sum); + assert!(factory.open(&k1, &v1, &c1)); + assert!(factory.open(&k2, &v2, &c2)); assert_eq!(c_sum, c_sum2); - assert!(c_sum.open(&k_sum, &v_sum)); + assert!(factory.open(&k_sum, &v_sum, &c_sum)); } } #[test] - #[should_panic] - fn summing_different_bases_panics() { + fn sum_commitment_vector() { let mut rng = rand::OsRng::new().unwrap(); - let base2 = &TEST_RISTRETTO_PEDERSON_BASE; - let k = RistrettoSecretKey::random(&mut rng); - let v = RistrettoSecretKey::random(&mut rng); - let c1 = PedersenBaseOnRistretto255::create(&k, &v); - let c: RistrettoPoint = k.0 * base2.G + v.0 * base2.H; - let c2 = PedersenOnRistretto255 { - base: base2, - commitment: RistrettoPublicKey::new_from_pk(c), - }; - let _ = &c1 + &c2; + let mut v_sum = RistrettoSecretKey::default(); + let mut k_sum = RistrettoSecretKey::default(); + let zero = RistrettoSecretKey::default(); + let commitment_factory = PedersenCommitmentFactory::default(); + let mut c_sum = commitment_factory.commit(&zero, &zero); + let mut commitments = Vec::with_capacity(100); + for _ in 0..100 { + let v = RistrettoSecretKey::random(&mut rng); + v_sum = &v_sum + &v; + let k = RistrettoSecretKey::random(&mut rng); + k_sum = &k_sum + &k; + let c = commitment_factory.commit(&k, &v); + c_sum = &c_sum + &c; + commitments.push(c); + } + assert!(commitment_factory.open(&k_sum, &v_sum, &c_sum)); + assert_eq!(c_sum, commitments.iter().sum()); } + #[test] + fn serialize_deserialize() { + let mut rng = rand::OsRng::new().unwrap(); + let factory = PedersenCommitmentFactory::default(); + let k = RistrettoSecretKey::random(&mut rng); + let c = factory.commit_value(&k, 420); + // Base64 + let ser_c = c.to_base64().unwrap(); + let c2 = PedersenCommitment::from_base64(&ser_c).unwrap(); + assert!(factory.open_value(&k, 420, &c2)); + // MessagePack + let ser_c = c.to_binary().unwrap(); + let c2 = PedersenCommitment::from_binary(&ser_c).unwrap(); + assert!(factory.open_value(&k, 420, &c2)); + } } diff --git a/infrastructure/crypto/src/ristretto/ristretto_keys.rs b/infrastructure/crypto/src/ristretto/ristretto_keys.rs index 2667434f95..cc06ecc3b1 100644 --- a/infrastructure/crypto/src/ristretto/ristretto_keys.rs +++ b/infrastructure/crypto/src/ristretto/ristretto_keys.rs @@ -21,24 +21,26 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! The Tari-compatible implementation of Ristretto based on the curve25519-dalek implementation -use crate::keys::{PublicKey, SecretKey}; +use crate::keys::{DiffieHellmanSharedSecret, PublicKey, SecretKey}; +use blake2::Blake2b; +use clear_on_drop::clear::Clear; use curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_TABLE, ristretto::{CompressedRistretto, RistrettoPoint}, scalar::Scalar, traits::MultiscalarMul, }; +use digest::Digest; use rand::{CryptoRng, Rng}; -use serde::{ - de::{Deserialize, Deserializer, Visitor}, - ser::{Serialize, Serializer}, -}; +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, - fmt, + hash::{Hash, Hasher}, ops::{Add, Mul, Sub}, }; -use tari_utilities::{ByteArray, ByteArrayError}; +use tari_utilities::{ByteArray, ByteArrayError, ExtendBytes, Hashable}; + +type HashDigest = Blake2b; /// The [SecretKey](trait.SecretKey.html) implementation for [Ristretto](https://ristretto.group) is a thin wrapper /// around the Dalek [Scalar](struct.Scalar.html) type, representing a 256-bit integer (mod the group order). @@ -50,7 +52,7 @@ use tari_utilities::{ByteArray, ByteArrayError}; /// /// ```edition2018 /// use tari_crypto::ristretto::RistrettoSecretKey; -/// use tari_utilities::ByteArray; +/// use tari_utilities::{ ByteArray, hex::Hex }; /// use tari_crypto::keys::SecretKey; /// use rand; /// @@ -59,42 +61,9 @@ use tari_utilities::{ByteArray, ByteArrayError}; /// let _k2 = RistrettoSecretKey::from_hex(&"100000002000000030000000040000000"); /// let _k3 = RistrettoSecretKey::random(&mut rng); /// ``` -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct RistrettoSecretKey(pub(crate) Scalar); -/// Requires custom Serde Serialize and Deserialize for RistrettoSecretKey as Scalar do not implement these traits -impl Serialize for RistrettoSecretKey { - fn serialize(&self, serializer: S) -> Result - where S: Serializer { - serializer.serialize_str(&self.to_hex()) - } -} - -struct DeserializeVisitor; - -impl<'de> Visitor<'de> for DeserializeVisitor { - type Value = RistrettoSecretKey; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("expecting a hex string") - } - - fn visit_str(self, str_data: &str) -> Result - where E: serde::de::Error { - match RistrettoSecretKey::from_hex(str_data) { - Ok(k) => Ok(k), - Err(parse_error) => Err(E::custom(format!("SecretKey parser error: {}", parse_error))), - } - } -} - -impl<'de> Deserialize<'de> for RistrettoSecretKey { - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> { - deserializer.deserialize_string(DeserializeVisitor) - } -} - const SCALAR_LENGTH: usize = 32; const PUBLIC_KEY_LENGTH: usize = 32; @@ -118,6 +87,15 @@ impl Default for RistrettoSecretKey { } } +//---------------------------------- Ristretto Secret Key Default -----------------------------------------------// + +/// Clear the secret key value in memory when it goes out of scope +impl Drop for RistrettoSecretKey { + fn drop(&mut self) { + self.0.clear(); + } +} + //------------------------------------- Ristretto Secret Key ByteArray ---------------------------------------------// impl ByteArray for RistrettoSecretKey { @@ -204,7 +182,7 @@ impl From for RistrettoSecretKey { /// `RistrettoPublicKey` so all of the following will work: /// ```edition2018 /// use tari_crypto::ristretto::{ RistrettoPublicKey, RistrettoSecretKey }; -/// use tari_utilities::ByteArray; +/// use tari_utilities::{ ByteArray, hex::Hex }; /// use tari_crypto::keys::{ PublicKey, SecretKey }; /// use rand; /// @@ -215,9 +193,10 @@ impl From for RistrettoSecretKey { /// let sk = RistrettoSecretKey::random(&mut rng); /// let _p3 = RistrettoPublicKey::from_secret_key(&sk); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct RistrettoPublicKey { pub(crate) point: RistrettoPoint, + #[serde(skip)] pub(crate) compressed: CompressedRistretto, } @@ -244,14 +223,47 @@ impl PublicKey for RistrettoPublicKey { PUBLIC_KEY_LENGTH } - fn batch_mul(scalars: &Vec, points: &Vec) -> Self { - let p: Vec = points.iter().map(|p| p.point.clone()).collect(); - let s: Vec = scalars.iter().map(|k| k.0.clone()).collect(); + fn batch_mul(scalars: &[Self::K], points: &[Self]) -> Self { + let p: Vec<&RistrettoPoint> = points.iter().map(|p| &p.point).collect(); + let s: Vec<&Scalar> = scalars.iter().map(|k| &k.0).collect(); let p = RistrettoPoint::multiscalar_mul(s, p); RistrettoPublicKey::new_from_pk(p) } } +impl DiffieHellmanSharedSecret for RistrettoPublicKey { + type PK = RistrettoPublicKey; + + /// Generate a shared secret from one party's private key and another party's public key + fn shared_secret(k: &::K, pk: &Self::PK) -> Self::PK { + k * pk + } +} + +// Requires custom Hashable implementation for RistrettoPublicKey as CompressedRistretto doesnt implement this trait +impl Hashable for RistrettoPublicKey { + fn hash(&self) -> Vec { + let mut hasher = HashDigest::new(); + hasher.input(&self.to_vec()); + hasher.result().to_vec() + } +} + +// Requires custom Extendbytes implementation for RistrettoPublicKey as CompressedRistretto doesnt implement this trait +impl ExtendBytes for RistrettoPublicKey { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.as_bytes(); + buf.extend_from_slice(&bytes); + } +} + +impl Hash for RistrettoPublicKey { + /// Require the implementation of the Hash trait for Hashmaps + fn hash(&self, state: &mut H) { + self.to_vec().hash(state); + } +} + //---------------------------------- Ristretto Public Key Default -----------------------------------------------// impl Default for RistrettoPublicKey { @@ -406,7 +418,7 @@ mod test { use super::*; use crate::{keys::PublicKey, ristretto::test_common::get_keypair}; use rand; - use tari_utilities::ByteArray; + use tari_utilities::{hex::Hex, message_format::MessageFormat, ByteArray}; #[test] fn test_generation() { @@ -549,8 +561,8 @@ mod test { fn batch_mul() { let (k1, p1) = get_keypair(); let (k2, p2) = get_keypair(); - let p_slow = &(k1 * &p1) + &(k2 * &p2); - let b_batch = RistrettoPublicKey::batch_mul(&vec![k1, k2], &vec![p1, p2]); + let p_slow = &(&k1 * &p1) + &(&k2 * &p2); + let b_batch = RistrettoPublicKey::batch_mul(&[k1, k2], &vec![p1, p2]); assert_eq!(p_slow, b_batch); } @@ -560,4 +572,54 @@ mod test { let (k, pk) = RistrettoPublicKey::random_keypair(&mut rng); assert_eq!(pk, RistrettoPublicKey::from_secret_key(&k)); } + + #[test] + fn secret_keys_are_cleared_after_drop() { + let zero = &vec![0u8; 32][..]; + let mut rng = rand::OsRng::new().unwrap(); + let ptr; + { + let k = RistrettoSecretKey::random(&mut rng); + ptr = (k.0).as_bytes().as_ptr(); + } + // In release mode, the memory can already be reclaimed by this stage due to optimisations, and so this test + // can fail in release mode, even though the values were effectively scrubbed. + if cfg!(debug_assertions) { + unsafe { + use std::slice; + assert_eq!(slice::from_raw_parts(ptr, 32), zero); + } + } + } + + #[test] + fn convert_from_u64() { + let k = RistrettoSecretKey::from(42u64); + assert_eq!( + k.to_hex(), + "2a00000000000000000000000000000000000000000000000000000000000000" + ); + let k = RistrettoSecretKey::from(256u64); + assert_eq!( + k.to_hex(), + "0001000000000000000000000000000000000000000000000000000000000000" + ); + let k = RistrettoSecretKey::from(100_000_000u64); + assert_eq!( + k.to_hex(), + "00e1f50500000000000000000000000000000000000000000000000000000000" + ); + } + + #[test] + fn serialize_deserialize_base64() { + let mut rng = rand::OsRng::new().unwrap(); + let (k, pk) = RistrettoPublicKey::random_keypair(&mut rng); + let ser_k = k.to_base64().unwrap(); + let ser_pk = pk.to_base64().unwrap(); + let k2: RistrettoSecretKey = RistrettoSecretKey::from_base64(&ser_k).unwrap(); + assert_eq!(k, k2, "Deserialised secret key"); + let pk2: RistrettoPublicKey = RistrettoPublicKey::from_base64(&ser_pk).unwrap(); + assert_eq!(pk, pk2, "Deserialized public key"); + } } diff --git a/infrastructure/crypto/src/ristretto/ristretto_sig.rs b/infrastructure/crypto/src/ristretto/ristretto_sig.rs index ce39e3890c..a9e27e9ccc 100644 --- a/infrastructure/crypto/src/ristretto/ristretto_sig.rs +++ b/infrastructure/crypto/src/ristretto/ristretto_sig.rs @@ -44,6 +44,7 @@ use crate::{ /// # use tari_crypto::keys::*; /// # use tari_crypto::signatures::SchnorrSignature; /// # use tari_utilities::ByteArray; +/// # use tari_utilities::hex::Hex; /// /// let public_r = RistrettoPublicKey::from_hex("6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919").unwrap(); /// let s = RistrettoSecretKey::from_bytes(b"10000000000000000000000000000000").unwrap(); @@ -57,7 +58,7 @@ use crate::{ /// # use tari_crypto::keys::*; /// # use tari_crypto::signatures::SchnorrSignature; /// # use tari_crypto::common::*; -/// # use tari_crypto::challenge::*; +/// # use digest::Digest; /// /// fn get_keypair() -> (RistrettoSecretKey, RistrettoPublicKey) { /// let mut rng = rand::OsRng::new().unwrap(); @@ -70,8 +71,8 @@ use crate::{ /// fn main() { /// let (k, P) = get_keypair(); /// let (r, R) = get_keypair(); -/// let e = Challenge::::new().concat(b"Small Gods"); -/// let sig = RistrettoSchnorr::sign(k, r, e); +/// let e = Blake256::digest(b"Small Gods"); +/// let sig = RistrettoSchnorr::sign(k, r, &e); /// } /// ``` /// @@ -83,11 +84,11 @@ use crate::{ /// ```edition2018 /// # use tari_crypto::ristretto::*; /// # use tari_crypto::keys::*; -/// # use tari_crypto::challenge::*; /// # use tari_crypto::signatures::SchnorrSignature; /// # use tari_crypto::common::*; /// # use tari_utilities::hex::*; /// # use tari_utilities::ByteArray; +/// # use digest::Digest; /// /// # #[allow(non_snake_case)] /// # fn main() { @@ -95,9 +96,8 @@ use crate::{ /// let R = RistrettoPublicKey::from_hex("fa14cb581ce5717248444721242e6b195a482d503a853dea4acb513074d8d803").unwrap(); /// let s = RistrettoSecretKey::from_hex("bd0b253a619310340a4fa2de54cdd212eac7d088ee1dc47e305c3f6cbd020908").unwrap(); /// let sig = RistrettoSchnorr::new(R, s); -/// let e = Challenge::::new() -/// .concat(b"Maskerade"); -/// assert!(sig.verify_challenge(&P, e)); +/// let e = Blake256::digest(b"Maskerade"); +/// assert!(sig.verify_challenge(&P, &e)); /// # } /// ``` pub type RistrettoSchnorr = SchnorrSignature; @@ -105,11 +105,11 @@ pub type RistrettoSchnorr = SchnorrSignature::new(); - let e = c.concat(P.as_bytes()).concat(R.as_bytes()).concat(b"Small Gods"); - let sig = RistrettoSchnorr::sign(k, r, e.clone()).unwrap(); + let e = Blake256::new() + .chain(P.as_bytes()) + .chain(R.as_bytes()) + .chain(b"Small Gods") + .result(); + let sig = RistrettoSchnorr::sign(k, r, &e).unwrap(); let R_calc = sig.get_public_nonce(); assert_eq!(R, *R_calc); - assert!(sig.verify_challenge(&P, e.clone())); + assert!(sig.verify_challenge(&P, &e)); // Doesn't work for invalid credentials - assert!(!sig.verify_challenge(&R, e)); + assert!(!sig.verify_challenge(&R, &e)); // Doesn't work for different challenge - let wrong_challenge = Challenge::::new().concat(b"Guards! Guards!"); - assert!(!sig.verify_challenge(&P, wrong_challenge)); + let wrong_challenge = Blake256::digest(b"Guards! Guards!"); + assert!(!sig.verify_challenge(&P, &wrong_challenge)); } /// This test checks that the linearity of Schnorr signatures hold, i.e. that s = s1 + s2 is validated by R1 + R2 @@ -150,22 +153,20 @@ mod test { let (k2, P2) = get_keypair(); let (r2, R2) = get_keypair(); // Each of them creates the Challenge = H(R1 || R2 || P1 || P2 || m) - let challenge = Challenge::::new() - .concat(R1.as_bytes()) - .concat(R2.as_bytes()) - .concat(P1.as_bytes()) - .concat(P2.as_bytes()) - .concat(b"Moving Pictures"); - let e1 = challenge.clone(); - let e2 = challenge.clone(); + let e = Blake256::new() + .chain(R1.as_bytes()) + .chain(R2.as_bytes()) + .chain(P1.as_bytes()) + .chain(P2.as_bytes()) + .chain(b"Moving Pictures") + .result(); // Calculate Alice's signature - let s1 = RistrettoSchnorr::sign(k1, r1, e1).unwrap(); + let s1 = RistrettoSchnorr::sign(k1, r1, &e).unwrap(); // Calculate Bob's signature - let s2 = RistrettoSchnorr::sign(k2, r2, e2).unwrap(); + let s2 = RistrettoSchnorr::sign(k2, r2, &e).unwrap(); // Now add the two signatures together let s_agg = &s1 + &s2; - let e3 = challenge.clone(); // Check that the multi-sig verifies - assert!(s_agg.verify_challenge(&(P1 + P2), e3)); + assert!(s_agg.verify_challenge(&(P1 + P2), &e)); } } diff --git a/infrastructure/crypto/src/ristretto/serialize.rs b/infrastructure/crypto/src/ristretto/serialize.rs new file mode 100644 index 0000000000..c74920096a --- /dev/null +++ b/infrastructure/crypto/src/ristretto/serialize.rs @@ -0,0 +1,103 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Custom serializers for Ristretto keys +//! +//! The Dalek libraries only serialize to binary (understandably), but this has 2 yucky implications: +//! +//! 1. Exporting to "human readable" formats like JSON yield crappy looking 'binary arrays', e.g. /[12, 223, 65, .../] +//! 2. Reading back from JSON is broken because serde doesn't read this back as a byte string, but as a seq. +//! +//! The workaround is to have binary serialization by default, but if a struct is going to be saved in JSON format, +//! then you can override that behaviour with `with_serialize`, e.g. +//! +//! ```nocompile +//! #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +//! pub struct KeyManager { +//! #[serde(serialize_with = "serialise_to_hex", deserialize_with = "secret_from_hex")] +//! pub master_key: K, +//! pub branch_seed: String, +//! pub primary_key_index: usize, +//! digest_type: PhantomData, +//! } +//! ``` + +use crate::keys::{PublicKey, SecretKey}; +use serde::{de, Deserializer, Serializer}; +use std::{fmt, marker::PhantomData}; +use tari_utilities::hex::Hex; + +pub fn serialize_to_hex(k: &K, ser: S) -> Result +where + S: Serializer, + K: Hex, +{ + ser.serialize_str(&k.to_hex()) +} + +pub fn secret_from_hex<'de, D, K>(des: D) -> Result +where + D: Deserializer<'de>, + K: Hex + SecretKey, +{ + struct KeyStringVisitor { + marker: PhantomData, + }; + + impl<'de, K: SecretKey> de::Visitor<'de> for KeyStringVisitor { + type Value = K; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a secret key in hex format") + } + + fn visit_str(self, v: &str) -> Result + where E: de::Error { + K::from_hex(v).map_err(E::custom) + } + } + des.deserialize_str(KeyStringVisitor { marker: PhantomData }) +} + +pub fn pubkey_from_hex<'de, D, K>(des: D) -> Result +where + D: Deserializer<'de>, + K: Hex + PublicKey, +{ + struct KeyStringVisitor { + marker: PhantomData, + }; + + impl<'de, K: PublicKey> de::Visitor<'de> for KeyStringVisitor { + type Value = K; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a public key in hex format") + } + + fn visit_str(self, v: &str) -> Result + where E: de::Error { + K::from_hex(v).map_err(E::custom) + } + } + des.deserialize_str(KeyStringVisitor { marker: PhantomData }) +} diff --git a/infrastructure/crypto/src/signatures.rs b/infrastructure/crypto/src/signatures.rs index 47c2352c8e..a56d942adf 100644 --- a/infrastructure/crypto/src/signatures.rs +++ b/infrastructure/crypto/src/signatures.rs @@ -2,22 +2,23 @@ //! This module defines generic traits for handling the digital signature operations, agnostic //! of the underlying elliptic curve implementation -use crate::{ - challenge::Challenge, - keys::{PublicKey, SecretKey}, -}; +use crate::keys::{PublicKey, SecretKey}; use derive_error::Error; -use digest::Digest; -use std::ops::{Add, Mul}; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + ops::{Add, Mul}, +}; +use tari_utilities::{hex::Hex, ByteArray}; -#[derive(Debug, Error, PartialEq, Eq)] +#[derive(Clone, Debug, Error, PartialEq, Eq)] pub enum SchnorrSignatureError { // An invalid challenge was provided InvalidChallenge, } #[allow(non_snake_case)] -#[derive(PartialEq, Eq, Copy, Debug, Clone)] +#[derive(PartialEq, Eq, Copy, Debug, Clone, Serialize, Deserialize)] pub struct SchnorrSignature { public_nonce: P, signature: K, @@ -39,16 +40,10 @@ where P::from_secret_key(&self.signature) } - pub fn sign<'a, 'b, D: Digest>( - secret: K, - nonce: K, - challenge: Challenge, - ) -> Result - where - K: Add + Mul + Mul, - { + pub fn sign(secret: K, nonce: K, challenge: &[u8]) -> Result + where K: Add + Mul + Mul { // s = r + e.k - let e = match K::from_vec(&challenge.hash()) { + let e = match K::from_bytes(challenge) { Ok(e) => e, Err(_) => return Err(SchnorrSignatureError::InvalidChallenge), }; @@ -58,12 +53,12 @@ where Ok(Self::new(public_nonce, s)) } - pub fn verify_challenge<'a, D: Digest>(&self, public_key: &'a P, challenge: Challenge) -> bool + pub fn verify_challenge<'a>(&self, public_key: &'a P, challenge: &[u8]) -> bool where for<'b> &'b K: Mul<&'a P, Output = P>, for<'b> &'b P: Add, { - let e = match K::from_vec(&challenge.hash()) { + let e = match K::from_bytes(&challenge) { Ok(e) => e, Err(_) => return false, }; @@ -133,3 +128,34 @@ where SchnorrSignature::new(P::default(), K::default()) } } + +/// Provide an efficient ordering algorithm for Schnorr signatures. It's probably not a good idea to implement `Ord` +/// for secret keys, but in this instance, the signature is publicly known and is simply a scalar, so we use the hex +/// representation of the scalar as the canonical ordering metric. This conversion is done if and only if the public +/// nonces are already equal, otherwise the public nonce ordering determines the SchnorrSignature order. +impl Ord for SchnorrSignature +where + P: Eq + Ord, + K: Eq + ByteArray, +{ + fn cmp(&self, other: &Self) -> Ordering { + match self.public_nonce.cmp(&other.public_nonce) { + Ordering::Equal => { + let this = self.signature.to_hex(); + let that = other.signature.to_hex(); + this.cmp(&that) + }, + v => v, + } + } +} + +impl PartialOrd for SchnorrSignature +where + P: Eq + Ord, + K: Eq + ByteArray, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/infrastructure/merklemountainrange/Cargo.toml b/infrastructure/merklemountainrange/Cargo.toml index 53a039861f..b3dd73cbed 100644 --- a/infrastructure/merklemountainrange/Cargo.toml +++ b/infrastructure/merklemountainrange/Cargo.toml @@ -10,7 +10,7 @@ version = "0.0.1" edition = "2018" [dependencies] -tari_utilities = { path = "../tari_util", version = "0.0.1" } +tari_utilities = { path = "../tari_util", version = "^0.0" } derive-error = "0.0.4" digest = "0.8.0" diff --git a/infrastructure/tari_util/Cargo.toml b/infrastructure/tari_util/Cargo.toml index decde23028..3f9e0730b8 100644 --- a/infrastructure/tari_util/Cargo.toml +++ b/infrastructure/tari_util/Cargo.toml @@ -6,10 +6,20 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.0.1" +version = "0.0.2" edition = "2018" [dependencies] derive-error = "0.0.4" +clear_on_drop = "0.2.3" +chrono = {version = "0.4.6", optional = true} +rmp-serde = "0.13.7" +base64 = "0.10.1" +serde_json = "1.0" +serde = {version = "1.0.89", features = ["derive"] } +rand = "0.5.5" [dev-dependencies] + +[features] +chrono_dt = ["chrono"] diff --git a/infrastructure/tari_util/src/bit.rs b/infrastructure/tari_util/src/bit.rs index 6c948fb0bf..74ff72fc03 100644 --- a/infrastructure/tari_util/src/bit.rs +++ b/infrastructure/tari_util/src/bit.rs @@ -38,8 +38,8 @@ pub fn uint_to_bits(value: usize, bit_count: usize) -> Vec { (bits) } -/// Converts a vector of input bits (little-endian) to a single byte -pub fn bits_to_byte(bits: &[bool; 8]) -> u8 { +/// Converts a array of input bits (little-endian) to a single byte +pub fn bits_to_byte(bits: [bool; 8]) -> u8 { let mut value: u8 = 0; for i in 0..8 { value |= (bits[i] as u8) << i; @@ -48,7 +48,7 @@ pub fn bits_to_byte(bits: &[bool; 8]) -> u8 { } /// Converts a vector of input bits (little-endian) to its integer representation -pub fn bits_to_uint(bits: &Vec) -> usize { +pub fn bits_to_uint(bits: &[bool]) -> usize { let mut value: usize = 0; for i in 0..bits.len() { value |= (bits[i] as usize) << i; @@ -57,7 +57,7 @@ pub fn bits_to_uint(bits: &Vec) -> usize { } /// Converts a vector of input bytes to a vector of bits -pub fn bytes_to_bits(bytes: &Vec) -> Vec { +pub fn bytes_to_bits(bytes: &[u8]) -> Vec { let mut bits: Vec = vec![false; bytes.len() * 8]; for i in 0..bytes.len() { let bit_index = i * 8; @@ -67,13 +67,13 @@ pub fn bytes_to_bits(bytes: &Vec) -> Vec { } /// Converts a vector of bits to a vector of bytes -pub fn bits_to_bytes(bits: &Vec) -> Vec { +pub fn bits_to_bytes(bits: &[bool]) -> Vec { let mut bytes: Vec = vec![0; bits.len() / 8]; let mut curr_bits: [bool; 8] = [false; 8]; for i in 0..bytes.len() { let byte_index = i * 8; curr_bits.copy_from_slice(&bits[byte_index..(byte_index + 8)]); - bytes[i] = bits_to_byte(&curr_bits) as u8; + bytes[i] = bits_to_byte(curr_bits) as u8; } (bytes) } diff --git a/infrastructure/tari_util/src/byte_array.rs b/infrastructure/tari_util/src/byte_array.rs index fb40ff582c..f27015cea0 100644 --- a/infrastructure/tari_util/src/byte_array.rs +++ b/infrastructure/tari_util/src/byte_array.rs @@ -20,16 +20,14 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::hex::{from_hex, to_hex, HexError}; +use crate::hex::{from_hex, to_hex, Hex, HexError}; use derive_error::Error; -#[derive(Debug, Error)] +#[derive(Debug, Error, PartialEq)] pub enum ByteArrayError { // Could not create a ByteArray when converting from a different format #[error(msg_embedded, non_std, no_from)] ConversionError(String), - // Invalid hex representation for ByteArray - HexConversionError(HexError), // The input data was the incorrect length to perform the desired conversion IncorrectLength, } @@ -37,20 +35,8 @@ pub enum ByteArrayError { /// Many of the types in this crate are just large numbers (256 bit usually). This trait provides the common /// functionality for types like secret keys, signatures, commitments etc. to be converted to and from byte arrays /// and hexadecimal formats. -pub trait ByteArray { - /// Return the hexadecimal string representation of the type - fn to_hex(&self) -> String { - to_hex(&self.to_vec()) - } - - /// Try and convert the given hexadecimal string to the type. Any failures (incorrect string length, non hex - /// characters, etc) return a [KeyError](enum.KeyError.html) with an explanatory note. - fn from_hex(hex: &str) -> Result - where Self: Sized { - let v = from_hex(hex)?; - Self::from_vec(&v) - } - +#[allow(clippy::ptr_arg)] +pub trait ByteArray: Sized { /// Return the type as a byte vector fn to_vec(&self) -> Vec { self.as_bytes().to_vec() @@ -58,44 +44,28 @@ pub trait ByteArray { /// Try and convert the given byte vector to the implemented type. Any failures (incorrect string length etc) /// return a [KeyError](enum.KeyError.html) with an explanatory note. - fn from_vec(v: &Vec) -> Result - where Self: Sized { + fn from_vec(v: &Vec) -> Result { Self::from_bytes(v.as_slice()) } /// Try and convert the given byte array to the implemented type. Any failures (incorrect array length, /// implementation-specific checks, etc) return a [ByteArrayError](enum.ByteArrayError.html). - fn from_bytes(bytes: &[u8]) -> Result - where Self: Sized; + fn from_bytes(bytes: &[u8]) -> Result; /// Return the type as a byte array fn as_bytes(&self) -> &[u8]; } impl ByteArray for Vec { - fn to_hex(&self) -> String { - to_hex(self) - } - - /// Try and convert the given hexadecimal string to the type. Any failures (incorrect string length, non hex - /// characters, etc) return a [KeyError](enum.KeyError.html) with an explanatory note. - fn from_hex(hex: &str) -> Result - where Self: Sized { - let v = from_hex(hex)?; - Self::from_vec(&v) - } - fn to_vec(&self) -> Vec { self.clone() } - fn from_vec(v: &Vec) -> Result - where Self: Sized { + fn from_vec(v: &Vec) -> Result { Ok(v.clone()) } - fn from_bytes(bytes: &[u8]) -> Result - where Self: Sized { + fn from_bytes(bytes: &[u8]) -> Result { Ok(bytes.to_vec()) } @@ -105,8 +75,7 @@ impl ByteArray for Vec { } impl ByteArray for [u8; 32] { - fn from_bytes(bytes: &[u8]) -> Result - where Self: Sized { + fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != 32 { return Err(ByteArrayError::IncorrectLength); } @@ -119,3 +88,16 @@ impl ByteArray for [u8; 32] { self } } + +impl Hex for T { + type T = T; + + fn from_hex(hex: &str) -> Result { + let v = from_hex(hex)?; + Self::from_vec(&v).map_err(|_| HexError::HexConversionError) + } + + fn to_hex(&self) -> String { + to_hex(&self.to_vec()) + } +} diff --git a/infrastructure/tari_util/src/ciphers/chacha20.rs b/infrastructure/tari_util/src/ciphers/chacha20.rs new file mode 100644 index 0000000000..931f93aac3 --- /dev/null +++ b/infrastructure/tari_util/src/ciphers/chacha20.rs @@ -0,0 +1,427 @@ +// Copyright 2019 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + ciphers::cipher::{Cipher, CipherError}, + ByteArray, +}; +use clear_on_drop::clear::Clear; +use rand::{OsRng, RngCore}; +/// This in an implementation of the ChaCha20 stream cipher developed using the Internet Research Task Force (IRTF) 8439 RFC (https://tools.ietf.org/html/rfc8439) +/// ChaCha20 is a high-speed cipher proposed by D. Bernstein that is not sensitive to timing attacks (http://cr.yp.to/chacha/chacha-20080128.pdf). +/// Data used in the unit tests were derived from the examples from the IRTF 8439 RFC +use std::num::Wrapping; + +pub struct ChaCha20; + +impl ChaCha20 { + /// Perform a single chacha quarter round at the provided indices in the state + fn quarter_round(state: &mut [u32; 16], a_index: usize, b_index: usize, c_index: usize, d_index: usize) { + let mut a = Wrapping(state[a_index]); + let mut b = Wrapping(state[b_index]); + let mut c = Wrapping(state[c_index]); + let mut d = Wrapping(state[d_index]); + a += b; + d = Wrapping((d ^ a).0.rotate_left(16)); + c += d; + b = Wrapping((b ^ c).0.rotate_left(12)); + a += b; + d = Wrapping((d ^ a).0.rotate_left(8)); + c += d; + b = Wrapping((b ^ c).0.rotate_left(7)); + state[a_index] = a.0; + state[b_index] = b.0; + state[c_index] = c.0; + state[d_index] = d.0; + } + + /// Construct a chacha block by performing a number of column and diagonal quarter round operations + fn chacha20_block(state: &[u32; 16]) -> [u32; 16] { + let mut working_state = *state; + for _iter in 0..10 { + // 20 total => odd and even round performed for every iteration + // Odd round + Self::quarter_round(&mut working_state, 0, 4, 8, 12); + Self::quarter_round(&mut working_state, 1, 5, 9, 13); + Self::quarter_round(&mut working_state, 2, 6, 10, 14); + Self::quarter_round(&mut working_state, 3, 7, 11, 15); + // Even round + Self::quarter_round(&mut working_state, 0, 5, 10, 15); + Self::quarter_round(&mut working_state, 1, 6, 11, 12); + Self::quarter_round(&mut working_state, 2, 7, 8, 13); + Self::quarter_round(&mut working_state, 3, 4, 9, 14); + } + let mut output: [u32; 16] = [0; 16]; + for i in 0..output.len() { + output[i] = (Wrapping(working_state[i]) + Wrapping(state[i])).0; + } + (output) + } + + /// Construct an initial state from a 128-bit constant, 256-bit key, 96-bit nonce and a 32-bit block counter + #[allow(clippy::needless_range_loop)] + fn construct_state(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> [u32; 16] { + let constant: [u8; 16] = [101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]; // 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 + let mut state_bytes = constant.to_vec(); // 128 bit + state_bytes.extend_from_slice(key); // 256-bit + state_bytes.extend_from_slice(&counter.to_ne_bytes()); // 32-bit + state_bytes.extend_from_slice(nonce); // 96-bit + + // Convert [u8;64] to [u32;16] + let mut curr_bytes: [u8; 4] = [0; 4]; + let mut state: [u32; 16] = [0; 16]; + for i in 0..state.len() { + let byte_index = i * 4; + curr_bytes.copy_from_slice(&state_bytes[byte_index..(byte_index + 4)]); + state[i] = u32::from_le_bytes(curr_bytes); + } + state_bytes.clear(); + (state) + } + + /// Generate a keystream consisting of a number of chacha blocks + fn chacha20_cipher_keystream(key: &[u8; 32], nonce: &[u8; 12], block_count: usize) -> Vec { + const BYTES_PER_BLOCK: usize = 64; + let block_byte_count = block_count * BYTES_PER_BLOCK; + let mut cipher_bytes: Vec = Vec::with_capacity(block_byte_count as usize); + for counter in 1..=block_count as u32 { + let mut state = Self::construct_state(key, nonce, counter); + let cipher_block = Self::chacha20_block(&state); + state.clear(); + // convert cipher block to bytes + let mut block_bytes: Vec = Vec::with_capacity(BYTES_PER_BLOCK); + for block in cipher_block.iter() { + block_bytes.append(&mut block.to_ne_bytes().to_vec()) + } + cipher_bytes.append(&mut block_bytes) + } + (cipher_bytes) + } + + /// Encode the provided input bytes using a chacha20 keystream + fn encode_with_nonce(bytes: &[u8], key: &[u8; 32], nonce: &[u8; 12]) -> Vec { + const BYTES_PER_BLOCK: usize = 64; + let block_count = (bytes.len() as f64 / BYTES_PER_BLOCK as f64).ceil() as usize; + let cipher_bytes = Self::chacha20_cipher_keystream(key, nonce, block_count); + let mut encoded_bytes = Vec::with_capacity(bytes.len()); + for i in 0..bytes.len() { + encoded_bytes.push(cipher_bytes[i] ^ bytes[i]); + } + (encoded_bytes) + } + + /// Decode the provided input bytes using a chacha20 keystream + fn decode_with_nonce(bytes: &[u8], key: &[u8; 32], nonce: &[u8; 12]) -> Vec { + (Self::encode_with_nonce(bytes, &key, &nonce)) + } +} + +impl Cipher for ChaCha20 +where D: ByteArray +{ + fn seal(plain_text: &D, key: &Vec, nonce: &Vec) -> Result, CipherError> { + // Validation + if key.len() != 32 { + return Err(CipherError::KeyLengthError); + } + if nonce.len() != 12 { + return Err(CipherError::NonceLengthError); + } + if plain_text.as_bytes().len() == 0 { + return Err(CipherError::NoDataError); + } + + let mut sized_key = [0; 32]; + sized_key.copy_from_slice(key.as_slice()); + + let mut sized_nonce = [0; 12]; + sized_nonce.copy_from_slice(nonce.as_slice()); + + let cipher_text = ChaCha20::encode_with_nonce(plain_text.as_bytes(), &sized_key, &sized_nonce); + // Clear copied private data + sized_key.clear(); + sized_nonce.clear(); + + Ok(cipher_text) + } + + fn open(cipher_text: &Vec, key: &Vec, nonce: &Vec) -> Result { + // Validation + if key.len() != 32 { + return Err(CipherError::KeyLengthError); + } + if nonce.len() != 12 { + return Err(CipherError::NonceLengthError); + } + if cipher_text.len() == 0 { + return Err(CipherError::NoDataError); + } + + let mut sized_key = [0; 32]; + sized_key.copy_from_slice(key.as_slice()); + + let mut sized_nonce = [0; 12]; + sized_nonce.copy_from_slice(nonce.as_slice()); + + let plain_text = ChaCha20::decode_with_nonce(cipher_text, &sized_key, &sized_nonce); + // Clear copied private data + sized_key.clear(); + sized_nonce.clear(); + + Ok(D::from_vec(&plain_text)?) + } + + fn seal_with_integral_nonce(plain_text: &D, key: &Vec) -> Result, CipherError> { + // Validation + if key.len() != 32 { + return Err(CipherError::KeyLengthError); + } + if plain_text.as_bytes().len() == 0 { + return Err(CipherError::NoDataError); + } + + let mut sized_key = [0; 32]; + sized_key.copy_from_slice(key.as_slice()); + + let mut rng = OsRng::new().unwrap(); + let mut nonce = [0u8; 12]; + rng.fill_bytes(&mut nonce); + + let cipher_text = ChaCha20::encode_with_nonce(plain_text.as_bytes(), &sized_key, &nonce); + let mut nonce_with_cipher_text: Vec = nonce.to_vec(); + nonce_with_cipher_text.extend(cipher_text); + + // Clear copied private data + sized_key.clear(); + nonce.clear(); + + Ok(nonce_with_cipher_text) + } + + fn open_with_integral_nonce(cipher_text: &Vec, key: &Vec) -> Result { + // Validation + if key.len() != 32 { + return Err(CipherError::KeyLengthError); + } + // If the cipher text is shorter than the required nonce length then the nonce is not properly included + if cipher_text.len() < 12 { + return Err(CipherError::NonceLengthError); + } else if cipher_text.len() < 13 { + return Err(CipherError::NoDataError); + } + + let mut sized_key = [0; 32]; + sized_key.copy_from_slice(key.as_slice()); + + let mut nonce = [0u8; 12]; + nonce.copy_from_slice(&cipher_text.clone()[0..12]); + + let plain_text = ChaCha20::decode_with_nonce(&cipher_text[12..], &sized_key, &nonce); + // Clear copied private data + sized_key.clear(); + nonce.clear(); + + Ok(D::from_vec(&plain_text)?) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_quarter_round() { + // Partial state quarter round update + let mut state: [u32; 16] = [0; 16]; + state[0] = 286331153; + state[1] = 16909060; + state[2] = 2609737539; + state[3] = 19088743; + ChaCha20::quarter_round(&mut state, 0, 1, 2, 3); + assert_eq!(state[0], 3928658676); + assert_eq!(state[1], 3407673550); + assert_eq!(state[2], 1166100270); + assert_eq!(state[3], 1484899515); + // Full state quarter round update + let mut state: [u32; 16] = [ + 2274701792, 3320640381, 1365533105, 3383111562, 1153568499, 865120127, 3657197835, 710897996, 1396123495, + 2953467441, 2538361882, 899586403, 1553404001, 1029904009, 546888150, 2447102752, + ]; + let desired_state: [u32; 16] = [ + 2274701792, 3320640381, 3182986972, 3383111562, 1153568499, 865120127, 3657197835, 3484200914, 3832277632, + 2953467441, 2538361882, 899586403, 1553404001, 3435166841, 546888150, 2447102752, + ]; + ChaCha20::quarter_round(&mut state, 2, 7, 8, 13); + assert_eq!(state, desired_state); + } + + #[test] + fn test_init_state() { + let key: [u8; 32] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ]; + let nonce: [u8; 12] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00]; + let counter: u32 = 1; + let state = ChaCha20::construct_state(&key, &nonce, counter); + let desired_state: [u32; 16] = [ + 1634760805, 857760878, 2036477234, 1797285236, 50462976, 117835012, 185207048, 252579084, 319951120, + 387323156, 454695192, 522067228, 1, 0, 1241513984, 0, + ]; + assert_eq!(state, desired_state); + } + + #[test] + fn test_chacha20_block() { + let state: [u32; 16] = [ + 1634760805, 857760878, 2036477234, 1797285236, 50462976, 117835012, 185207048, 252579084, 319951120, + 387323156, 454695192, 522067228, 1, 150994944, 1241513984, 0, + ]; + let block_state = ChaCha20::chacha20_block(&state); + let desired_block_state: [u32; 16] = [ + 3840405776, 358169553, 534581072, 3295748259, 3354710471, 57196595, 2594841092, 1315755203, 1180992210, + 162176775, 98026004, 2718075865, 3516666549, 3108902622, 3900952779, 1312575650, + ]; + assert_eq!(block_state, desired_block_state); + } + + #[test] + fn test_chacha20_cipher_keystream() { + let key: [u8; 32] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ]; + let nonce: [u8; 12] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00]; + let block_count: usize = 1; + let desired_cipher_bytes: Vec = vec![ + 0x22, 0x4f, 0x51, 0xf3, 0x40, 0x1b, 0xd9, 0xe1, 0x2f, 0xde, 0x27, 0x6f, 0xb8, 0x63, 0x1d, 0xed, 0x8c, 0x13, + 0x1f, 0x82, 0x3d, 0x2c, 0x06, 0xe2, 0x7e, 0x4f, 0xca, 0xec, 0x9e, 0xf3, 0xcf, 0x78, 0x8a, 0x3b, 0x0a, 0xa3, + 0x72, 0x60, 0x0a, 0x92, 0xb5, 0x79, 0x74, 0xcd, 0xed, 0x2b, 0x93, 0x34, 0x79, 0x4c, 0xba, 0x40, 0xc6, 0x3e, + 0x34, 0xcd, 0xea, 0x21, 0x2c, 0x4c, 0xf0, 0x7d, 0x41, 0xb7, + ]; + let cipher_bytes = ChaCha20::chacha20_cipher_keystream(&key, &nonce, block_count); + assert_eq!(cipher_bytes, desired_cipher_bytes); + } + + #[test] + fn test_encode_and_decode() { + let key: [u8; 32] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ]; + + let nonce: [u8; 12] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00]; + // Text: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen + // would be it." + // The test data bytes are from IRTF 8439 RFC + let data_bytes: Vec = vec![ + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, + 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, + 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, + 0x62, 0x65, 0x20, 0x69, 0x74, 0x2e, + ]; + // Encode + let encoded_bytes = ChaCha20::encode_with_nonce(&data_bytes, &key, &nonce); + let desired_encoded_bytes: Vec = vec![ + 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, 0xe9, 0x7e, + 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, 0xf9, 0x1b, 0x65, 0xc5, + 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, + 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, + 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, 0x81, 0x8c, + 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, + 0xf2, 0x78, 0x5e, 0x42, 0x87, 0x4d, + ]; + assert_eq!(encoded_bytes, desired_encoded_bytes); + // Decode + let decoded_bytes = ChaCha20::decode_with_nonce(&encoded_bytes, &key, &nonce); + assert_eq!(decoded_bytes, data_bytes); + } + + #[test] + fn test_cipher_trait() { + let key: Vec = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ]; + + let nonce: Vec = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00]; + // Text: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen + // would be it." The test data bytes are from IRTF 8439 RFC + let data_bytes: Vec = vec![ + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, + 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, + 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, + 0x62, 0x65, 0x20, 0x69, 0x74, 0x2e, + ]; + + let desired_encoded_bytes: Vec = vec![ + 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, 0xe9, 0x7e, + 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, 0xf9, 0x1b, 0x65, 0xc5, + 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, + 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, + 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, 0x81, 0x8c, + 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, + 0xf2, 0x78, 0x5e, 0x42, 0x87, 0x4d, + ]; + + assert_eq!( + ChaCha20::seal(&data_bytes, &key[..31].to_vec(), &nonce), + Err(CipherError::KeyLengthError) + ); + + assert_eq!( + ChaCha20::seal(&data_bytes, &key, &nonce[..11].to_vec()), + Err(CipherError::NonceLengthError) + ); + + let cipher_text = ChaCha20::seal(&data_bytes, &key, &nonce).unwrap(); + + assert_eq!(cipher_text, desired_encoded_bytes); + + let plain_text: Vec = ChaCha20::open(&cipher_text, &key, &nonce).unwrap(); + + assert_eq!(plain_text, data_bytes); + } + + #[test] + fn test_integral_nonce_cipher() { + let key: Vec = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ]; + let data_bytes: Vec = "One Ring to rule them all, One Ring to find them, One Ring to bring them all, and \ + in the darkness bind them" + .as_bytes() + .to_vec(); + + let cipher_text = ChaCha20::seal_with_integral_nonce(&data_bytes, &key).unwrap(); + let plain_text: Vec = ChaCha20::open_with_integral_nonce(&cipher_text, &key).unwrap(); + + assert_eq!(plain_text, data_bytes); + } +} diff --git a/infrastructure/tari_util/src/ciphers/cipher.rs b/infrastructure/tari_util/src/ciphers/cipher.rs new file mode 100644 index 0000000000..ee52384c3e --- /dev/null +++ b/infrastructure/tari_util/src/ciphers/cipher.rs @@ -0,0 +1,53 @@ +// Copyright 2019 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ByteArray, ByteArrayError}; +use derive_error::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum CipherError { + /// Provided key is the incorrect size to be used by the Cipher + KeyLengthError, + /// Provided Nonce is the incorrect size to be used by the Cipher + NonceLengthError, + /// No data was provided for encryption/decryption + NoDataError, + /// Byte Array conversion error + ByteArrayError(ByteArrayError), +} + +/// A trait describing an interface to a symmetrical encryption scheme +pub trait Cipher +where D: ByteArray +{ + /// Encrypt using a cipher and provided key and nonce + fn seal(plain_text: &D, key: &Vec, nonce: &Vec) -> Result, CipherError>; + + /// Decrypt using a cipher and provided key and nonce + fn open(cipher_text: &Vec, key: &Vec, nonce: &Vec) -> Result; + + /// Encrypt using a cipher and provided key, the nonce will be generate internally and appended to the cipher text + fn seal_with_integral_nonce(plain_text: &D, key: &Vec) -> Result, CipherError>; + + /// Decrypt using a cipher and provided key. The integral nonce will be read from the cipher text + fn open_with_integral_nonce(cipher_text: &Vec, key: &Vec) -> Result; +} diff --git a/infrastructure/tari_util/src/ciphers/mod.rs b/infrastructure/tari_util/src/ciphers/mod.rs new file mode 100644 index 0000000000..9ddd83ba16 --- /dev/null +++ b/infrastructure/tari_util/src/ciphers/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2019 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + +pub mod chacha20; +pub mod cipher; diff --git a/infrastructure/tari_util/src/extend_bytes.rs b/infrastructure/tari_util/src/extend_bytes.rs new file mode 100644 index 0000000000..bae8ebca4e --- /dev/null +++ b/infrastructure/tari_util/src/extend_bytes.rs @@ -0,0 +1,140 @@ +// Copyright 2019 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[cfg(feature = "chrono_dt")] +use chrono::{DateTime, Utc}; + +/// this trait allows us to call append_raw_bytes and get the raw bytes of the type +pub trait ExtendBytes { + fn append_raw_bytes(&self, buf: &mut Vec); +} + +impl ExtendBytes for Vec +where T: ExtendBytes +{ + fn append_raw_bytes(&self, buf: &mut Vec) { + for t in self { + t.append_raw_bytes(buf); + } + } +} + +impl ExtendBytes for [T] +where T: ExtendBytes +{ + fn append_raw_bytes(&self, buf: &mut Vec) { + for t in self { + t.append_raw_bytes(buf); + } + } +} + +impl ExtendBytes for str { + fn append_raw_bytes(&self, buf: &mut Vec) { + buf.extend(self.as_bytes()) + } +} + +impl ExtendBytes for &str { + fn append_raw_bytes(&self, buf: &mut Vec) { + buf.extend(self.as_bytes()) + } +} + +impl ExtendBytes for String { + fn append_raw_bytes(&self, buf: &mut Vec) { + buf.extend(self.as_bytes()) + } +} + +impl ExtendBytes for i8 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} +impl ExtendBytes for i16 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} +impl ExtendBytes for i32 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} + +impl ExtendBytes for i128 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} + +impl ExtendBytes for u8 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} +impl ExtendBytes for u16 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} +impl ExtendBytes for u32 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} + +impl ExtendBytes for u64 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} + +impl ExtendBytes for u128 { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} + +impl ExtendBytes for bool { + fn append_raw_bytes(&self, buf: &mut Vec) { + buf.extend_from_slice(if *self { &[1u8] } else { &[0u8] }); + } +} + +#[cfg(feature = "chrono_dt")] +impl ExtendBytes for DateTime { + fn append_raw_bytes(&self, buf: &mut Vec) { + let bytes = self.timestamp().to_le_bytes(); + buf.extend_from_slice(&bytes); + } +} diff --git a/infrastructure/tari_util/src/fixed_set.rs b/infrastructure/tari_util/src/fixed_set.rs new file mode 100644 index 0000000000..dc63427f31 --- /dev/null +++ b/infrastructure/tari_util/src/fixed_set.rs @@ -0,0 +1,199 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ops::Add; + +#[derive(Clone, Debug)] +pub struct FixedSet { + items: Vec>, +} + +impl FixedSet { + /// Creates a new fixed set of size n. + pub fn new(n: usize) -> FixedSet { + FixedSet { items: vec![None; n] } + } + + /// Returns the size of the fixed set, NOT the number of items that have been set + pub fn size(&self) -> usize { + self.items.len() + } + + /// Set the `index`th item to `val`. Any existing item is overwritten. The set takes ownership of `val`. + pub fn set_item(&mut self, index: usize, val: T) -> bool { + if index >= self.items.len() { + return false; + } + self.items[index] = Some(val); + true + } + + /// Return a reference to the `index`th item, or `None` if that item has not been set yet. + pub fn get_item(&self, index: usize) -> Option<&T> { + match self.items.get(index) { + None => None, + Some(option) => option.as_ref(), + } + } + + /// Delete an item from the set by setting the `index`th value to None + pub fn clear_item(&mut self, index: usize) { + if index < self.items.len() { + self.items[index] = None; + } + } + + /// Returns true if every item in the set has been set. An empty set returns true as well. + pub fn is_full(&self) -> bool { + self.items.iter().all(Option::is_some) + } + + /// Return the index of the given item in the set by performing a linear search through the set + pub fn search(&self, val: &T) -> Option { + let key = self + .items + .iter() + .enumerate() + .find(|v| v.1.is_some() && v.1.as_ref().unwrap() == val); + match key { + Some(item) => Some(item.0), + None => None, + } + } + + /// Produces the sum of the values in the set, provided the set is full + pub fn sum(&self) -> Option + where for<'a> &'a T: Add<&'a T, Output = T> { + // This function uses HTRB to work: See https://doc.rust-lang.org/nomicon/hrtb.html + // or here https://users.rust-lang.org/t/lifetimes-for-type-constraint-where-one-reference-is-local/11087 + if self.size() == 0 { + return Some(T::default()); + } + if !self.is_full() { + return None; + } + let mut iter = self.items.iter().filter_map(Option::as_ref); + // Take the first item + let mut sum = iter.next().unwrap().clone(); + for v in iter { + sum = &sum + v; + } + Some(sum) + } + + /// Collects all non-empty elements of the set into a Vec instance + pub fn into_vec(self) -> Vec { + self.items.into_iter().filter_map(|v| v).collect() + } +} + +//------------------------------------------- Tests ---------------------------------------------// + +#[cfg(test)] +mod test { + use super::FixedSet; + + #[derive(Eq, PartialEq, Clone, Debug, Default)] + struct Foo { + baz: String, + } + + #[test] + fn zero_sized_fixed_set() { + let mut s = FixedSet::::new(0); + assert!(s.is_full(), "Set should be full"); + assert_eq!(s.set_item(1, 1), false, "Should not be able to set item"); + assert_eq!(s.get_item(0), None, "Should not return a value"); + assert_eq!(s.sum(), Some(0)); + } + + fn data(s: &str) -> Foo { + match s { + "patrician" => Foo { + baz: "The Patrician".into(), + }, + "rincewind" => Foo { + baz: "Rincewind".into(), + }, + "vimes" => Foo { + baz: "Commander Vimes".into(), + }, + "librarian" => Foo { + baz: "The Librarian".into(), + }, + "carrot" => Foo { + baz: "Captain Carrot".into(), + }, + _ => Foo { baz: "None".into() }, + } + } + + #[test] + fn small_set() { + let mut s = FixedSet::::new(3); + // Set is empty + assert_eq!(s.is_full(), false); + // Add an item + assert!(s.set_item(1, data("patrician"))); + assert_eq!(s.is_full(), false); + // Add an item + assert!(s.set_item(0, data("vimes"))); + assert_eq!(s.is_full(), false); + // Replace an item + assert!(s.set_item(1, data("rincewind"))); + assert_eq!(s.is_full(), false); + // Add item, filling set + assert!(s.set_item(2, data("carrot"))); + assert_eq!(s.is_full(), true); + // Try add an invalid item + assert_eq!(s.set_item(3, data("librarian")), false); + assert_eq!(s.is_full(), true); + // Clear an item + s.clear_item(1); + assert_eq!(s.is_full(), false); + // Check contents + assert_eq!(s.get_item(0).unwrap().baz, "Commander Vimes"); + assert!(s.get_item(1).is_none()); + assert_eq!(s.get_item(2).unwrap().baz, "Captain Carrot"); + // Size is 3 + assert_eq!(s.size(), 3); + // Slow search + assert_eq!(s.search(&data("carrot")), Some(2)); + assert_eq!(s.search(&data("vimes")), Some(0)); + assert_eq!(s.search(&data("librarian")), None); + } + + #[test] + fn sum_values() { + let mut s = FixedSet::::new(4); + s.set_item(0, 5); + assert_eq!(s.sum(), None); + s.set_item(1, 4); + assert_eq!(s.sum(), None); + s.set_item(2, 3); + assert_eq!(s.sum(), None); + s.set_item(3, 2); + assert_eq!(s.sum(), Some(14)); + s.set_item(1, 0); + assert_eq!(s.sum(), Some(10)); + } +} diff --git a/infrastructure/tari_util/src/hex.rs b/infrastructure/tari_util/src/hex.rs index 7edd74b99c..be3ac6878e 100644 --- a/infrastructure/tari_util/src/hex.rs +++ b/infrastructure/tari_util/src/hex.rs @@ -1,5 +1,19 @@ use derive_error::Error; -use std::{fmt::Write, num::ParseIntError}; +use std::{ + fmt::{LowerHex, Write}, + num::ParseIntError, +}; + +/// Any object implementing this trait has the ability to represent itself as a hexadecimal string and convert from it. +pub trait Hex { + type T; + /// Try and convert the given hexadecimal string to the type. Any failures (incorrect string length, non hex + /// characters, etc) return a [KeyError](enum.KeyError.html) with an explanatory note. + fn from_hex(hex: &str) -> Result; + + /// Return the hexadecimal string representation of the type + fn to_hex(&self) -> String; +} #[derive(Debug, Error)] pub enum HexError { @@ -7,10 +21,13 @@ pub enum HexError { InvalidCharacter(ParseIntError), /// Hex string lengths must be a multiple of 2 LengthError, + /// Invalid hex representation for the target type + HexConversionError, } /// Encode the provided bytes into a hex string -pub fn to_hex(bytes: &Vec) -> String { +pub fn to_hex(bytes: &[T]) -> String +where T: LowerHex { let mut s = String::new(); for byte in bytes { write!(&mut s, "{:02x}", byte).expect("Unable to write"); @@ -19,7 +36,7 @@ pub fn to_hex(bytes: &Vec) -> String { } /// Encode the provided vector of bytes into a hex string -pub fn to_hex_multiple(bytearray: &Vec>) -> Vec { +pub fn to_hex_multiple(bytearray: &[Vec]) -> Vec { let mut result = Vec::new(); for bytes in bytearray { result.push(to_hex(bytes)) @@ -54,6 +71,7 @@ pub fn from_hex(hex_str: &str) -> Result, HexError> { mod test { use super::*; use std::error::Error; + #[test] fn test_to_hex() { assert_eq!(to_hex(&vec![0, 0, 0, 0]), "00000000"); diff --git a/infrastructure/tari_util/src/lib.rs b/infrastructure/tari_util/src/lib.rs index ba42cb34c4..7d1faa15e5 100644 --- a/infrastructure/tari_util/src/lib.rs +++ b/infrastructure/tari_util/src/lib.rs @@ -19,10 +19,17 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#[allow(clippy::needless_range_loop)] pub mod bit; pub mod byte_array; +pub mod ciphers; +pub mod extend_bytes; +pub mod fixed_set; pub mod hash; pub mod hex; +pub mod message_format; + +pub use self::extend_bytes::ExtendBytes; pub use self::{ byte_array::{ByteArray, ByteArrayError}, diff --git a/infrastructure/tari_util/src/message_format.rs b/infrastructure/tari_util/src/message_format.rs new file mode 100644 index 0000000000..9f2b48a00b --- /dev/null +++ b/infrastructure/tari_util/src/message_format.rs @@ -0,0 +1,219 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use base64; +use derive_error::Error; +use rmp_serde; +use serde::{Deserialize, Serialize}; +use serde_json; + +#[derive(Debug, Error)] +pub enum MessageFormatError { + // An error occurred serialising an object into binary + BinarySerializeError(rmp_serde::encode::Error), + // An error occurred deserialising binary data into an object + BinaryDeserializeError(rmp_serde::decode::Error), + // An error occurred de-/serialising an object from/into JSON + JSONError(serde_json::error::Error), + // An error occurred deserialising an object from Base64 + Base64DeserializeError(base64::DecodeError), +} + +pub trait MessageFormat: Sized { + fn to_binary(&self) -> Result, MessageFormatError>; + fn to_json(&self) -> Result; + fn to_base64(&self) -> Result; + + fn from_binary(msg: &[u8]) -> Result; + fn from_json(msg: &str) -> Result; + fn from_base64(msg: &str) -> Result; +} + +impl<'a, T> MessageFormat for T +where T: Deserialize<'a> + Serialize +{ + fn to_binary(&self) -> Result, MessageFormatError> { + let mut buf = Vec::new(); + self.serialize(&mut rmp_serde::Serializer::new(&mut buf)) + .map_err(MessageFormatError::BinarySerializeError)?; + Ok(buf.to_vec()) + } + + fn to_json(&self) -> Result { + serde_json::to_string(self).map_err(MessageFormatError::JSONError) + } + + fn to_base64(&self) -> Result { + let val = self.to_binary()?; + Ok(base64::encode(&val)) + } + + fn from_binary(msg: &[u8]) -> Result { + let mut de = rmp_serde::Deserializer::new(msg); + Deserialize::deserialize(&mut de).map_err(MessageFormatError::BinaryDeserializeError) + } + + fn from_json(msg: &str) -> Result { + let mut de = serde_json::Deserializer::from_reader(msg.as_bytes()); + Deserialize::deserialize(&mut de).map_err(MessageFormatError::JSONError) + } + + fn from_base64(msg: &str) -> Result { + let buf = base64::decode(msg)?; + Self::from_binary(&buf) + } +} + +#[cfg(test)] +mod test { + use super::*; + use base64::DecodeError as Base64Error; + use rmp_serde::decode::Error as RMPError; + use serde::{Deserialize, Serialize}; + use std::io::ErrorKind; + + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] + struct TestMessage { + key: String, + value: u64, + sub_message: Option>, + } + + impl TestMessage { + pub fn new(key: &str, value: u64) -> TestMessage { + TestMessage { + key: key.to_string(), + value, + sub_message: None, + } + } + + pub fn set_sub_message(&mut self, msg: TestMessage) { + self.sub_message = Some(Box::new(msg)); + } + } + + #[test] + fn binary_simple() { + let val = TestMessage::new("twenty", 20); + let msg = val.to_binary().unwrap(); + assert_eq!(msg, b"\x93\xA6\x74\x77\x65\x6E\x74\x79\x14\xC0"); + let val2 = TestMessage::from_binary(&msg).unwrap(); + assert_eq!(val, val2); + } + + #[test] + fn base64_simple() { + let val = TestMessage::new("twenty", 20); + let msg = val.to_base64().unwrap(); + assert_eq!(msg, "k6Z0d2VudHkUwA=="); + let val2 = TestMessage::from_base64(&msg).unwrap(); + assert_eq!(val, val2); + } + + #[test] + fn json_simple() { + let val = TestMessage::new("twenty", 20); + let msg = val.to_json().unwrap(); + assert_eq!(msg, "{\"key\":\"twenty\",\"value\":20,\"sub_message\":null}"); + let val2 = TestMessage::from_json(&msg).unwrap(); + assert_eq!(val, val2); + } + + #[test] + fn nested_message() { + let inner = TestMessage::new("today", 100); + let mut val = TestMessage::new("tomorrow", 50); + val.set_sub_message(inner); + + let msg_json = val.to_json().unwrap(); + assert_eq!( + msg_json, + "{\"key\":\"tomorrow\",\"value\":50,\"sub_message\":{\"key\":\"today\",\"value\":100,\"sub_message\":\ + null}}" + ); + + let msg_base64 = val.to_base64().unwrap(); + assert_eq!(msg_base64, "k6h0b21vcnJvdzKTpXRvZGF5ZMA="); + + let msg_bin = val.to_binary().unwrap(); + assert_eq!( + msg_bin, + b"\x93\xA8\x74\x6F\x6D\x6F\x72\x72\x6F\x77\x32\x93\xA5\x74\x6F\x64\x61\x79\x64\xC0" + ); + + let val2 = TestMessage::from_json(&msg_json).unwrap(); + assert_eq!(val, val2); + + let val2 = TestMessage::from_base64(&msg_base64).unwrap(); + assert_eq!(val, val2); + + let val2 = TestMessage::from_binary(&msg_bin).unwrap(); + assert_eq!(val, val2); + } + + #[test] + fn fail_json() { + let err = TestMessage::from_json("{\"key\":5}").err().unwrap(); + match err { + MessageFormatError::JSONError(e) => { + assert_eq!(e.line(), 1); + assert_eq!(e.column(), 9); + assert!(e.is_data()); + }, + _ => panic!("JSON conversion should fail"), + }; + } + + #[test] + fn fail_base64() { + let err = TestMessage::from_base64("aaaaa$aaaaa").err().unwrap(); + match err { + MessageFormatError::Base64DeserializeError(Base64Error::InvalidByte(offset, val)) => { + assert_eq!(offset, 5); + assert_eq!(val, '$' as u8); + }, + _ => panic!("Base64 conversion should fail"), + }; + + let err = TestMessage::from_base64("j6h0b21vcnJvdzKTpXRvZGF5ZMA=").err().unwrap(); + match err { + MessageFormatError::BinaryDeserializeError(RMPError::Syntax(s)) => { + assert_eq!(s, "invalid type: sequence, expected field identifier"); + }, + _ => panic!("Base64 conversion should fail"), + }; + } + + #[test] + fn fail_binary() { + let err = TestMessage::from_binary(b"").err().unwrap(); + match err { + MessageFormatError::BinaryDeserializeError(RMPError::InvalidMarkerRead(e)) => { + assert_eq!(e.kind(), ErrorKind::UnexpectedEof, "Unexpected error type: {:?}", e); + }, + _ => { + panic!("Base64 conversion should fail"); + }, + } + } +} diff --git a/scripts/publish_crates.sh b/scripts/publish_crates.sh index 404c751dab..49c2ca281b 100755 --- a/scripts/publish_crates.sh +++ b/scripts/publish_crates.sh @@ -1,14 +1,12 @@ #!/usr/bin/env bash -infrastructure_packages=('tari_util' 'derive' 'crypto' 'merklemountainrange') -base_layer_packages=('core') +packages=${@:-'infrastructure/tari_util infrastructure/derive infrastructure/crypto infrastructure/merklemountainrange base_layer/core'} +p_arr=($packages) function build_package { - path=$1 - shift list=($@) for p in "${list[@]}"; do echo "************************ Building $path/$p package ************************" - cargo publish --manifest-path=${path}/${p}/Cargo.toml + cargo publish --manifest-path=./${p}/Cargo.toml done echo "************************ $path packages built ************************" @@ -16,5 +14,4 @@ function build_package { # You need a token with write access to publish these crates cargo login -build_package "infrastructure" ${infrastructure_packages[@]} -build_package "base_layer" ${base_layer_packages[@]} +build_package ${p_arr[@]}