Просмотр исходного кода

Merge branch 'master' into raft_optimized

ghassmo 4 лет назад
Родитель
Сommit
9df075dfb8
42 измененных файлов с 1817 добавлено и 1349 удалено
  1. 38 223
      Cargo.lock
  2. 5 0
      Cargo.toml
  3. 5 0
      bin/ircd/src/server.rs
  4. 11 0
      bin/tau/tau-cli/src/main.rs
  5. 10 0
      bin/tau/tau-cli/src/rpc.rs
  6. 2 1
      bin/tau/taud/Cargo.toml
  7. 69 17
      bin/tau/taud/src/jsonrpc.rs
  8. 61 144
      bin/tau/taud/src/main.rs
  9. 25 6
      bin/tau/taud/src/month_tasks.rs
  10. 7 1
      bin/tau/taud/src/task_info.rs
  11. 50 2
      bin/tau/taud/src/util.rs
  12. 10 2
      bin/tau/taud_config.toml
  13. 1 5
      script/research/dhtd/Cargo.toml
  14. 2 2
      script/research/dhtd/src/error.rs
  15. 75 178
      script/research/dhtd/src/main.rs
  16. 0 184
      script/research/dhtd/src/structures.rs
  17. 20 0
      script/research/ec/intersection-multiplicity.sage
  18. 110 0
      script/research/ec/pairing-modified-tate.sage
  19. 3 2
      script/research/ec/pairing.sage
  20. 1 0
      script/research/ec/pairing/3.1.2-classgroup.sage
  21. 31 0
      script/research/ec/pairing/3.1.3-divlines.sage
  22. 34 0
      script/research/ec/unique-y-intersect.sage
  23. 1 24
      src/blockchain/mod.rs
  24. 0 1
      src/consensus/proto/protocol_participant.rs
  25. 22 21
      src/consensus/proto/protocol_tx.rs
  26. 273 0
      src/dht/dht.rs
  27. 95 0
      src/dht/messages.rs
  28. 6 0
      src/dht/mod.rs
  29. 46 33
      src/dht/protocol.rs
  30. 9 0
      src/error.rs
  31. 5 2
      src/lib.rs
  32. 16 0
      src/util/serial.rs
  33. 2 1
      src/zk/circuit/burn_contract.rs
  34. 2 1
      src/zk/circuit/mint_contract.rs
  35. 2 2
      src/zk/gadget/arithmetic.rs
  36. 0 246
      src/zk/gadget/even_bits.rs
  37. 0 214
      src/zk/gadget/greater_than.rs
  38. 0 0
      src/zk/gadget/is_zero.rs
  39. 336 0
      src/zk/gadget/less_than.rs
  40. 7 7
      src/zk/gadget/mod.rs
  41. 424 0
      src/zk/gadget/native_range_check.rs
  42. 1 30
      src/zk/vm.rs

+ 38 - 223
Cargo.lock

@@ -59,7 +59,7 @@ version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -170,7 +170,7 @@ dependencies = [
  "slab",
  "socket2",
  "waker-fn",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -219,7 +219,7 @@ dependencies = [
  "libc",
  "once_cell",
  "signal-hook",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -303,7 +303,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 dependencies = [
  "hermit-abi",
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -654,7 +654,7 @@ dependencies = [
  "num-integer",
  "num-traits",
  "time 0.1.44",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -756,7 +756,7 @@ dependencies = [
  "regex",
  "terminal_size",
  "unicode-width",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -987,11 +987,11 @@ dependencies = [
  "bitflags",
  "crossterm_winapi",
  "libc",
- "mio 0.8.4",
+ "mio",
  "parking_lot 0.12.1",
  "signal-hook",
  "signal-hook-mio",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1000,7 +1000,7 @@ version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1094,7 +1094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865"
 dependencies = [
  "nix 0.24.1",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1105,7 +1105,7 @@ checksum = "598e9d68e769aa1283460a3b0ec0d049ccfb6170277aea37089fa3f58fd721a1"
 dependencies = [
  "async-std",
  "nix 0.23.1",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1411,7 +1411,7 @@ checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
 dependencies = [
  "libc",
  "redox_users 0.3.5",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1441,7 +1441,7 @@ checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
 dependencies = [
  "libc",
  "redox_users 0.4.3",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1452,7 +1452,7 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
 dependencies = [
  "libc",
  "redox_users 0.4.3",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1514,7 +1514,7 @@ checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
 dependencies = [
  "lazy_static",
  "libc",
- "winapi 0.3.9",
+ "winapi",
  "wio",
 ]
 
@@ -1707,18 +1707,6 @@ dependencies = [
  "subtle",
 ]
 
-[[package]]
-name = "filetime"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
-dependencies = [
- "cfg-if 1.0.0",
- "libc",
- "redox_syscall 0.2.13",
- "windows-sys 0.36.1",
-]
-
 [[package]]
 name = "float-ord"
 version = "0.2.0"
@@ -1765,7 +1753,7 @@ dependencies = [
  "pathfinder_simd",
  "servo-fontconfig",
  "walkdir",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1821,44 +1809,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
 dependencies = [
  "libc",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "fsevent"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
-dependencies = [
- "bitflags",
- "fsevent-sys",
-]
-
-[[package]]
-name = "fsevent-sys"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "fuchsia-zircon"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
-dependencies = [
- "bitflags",
- "fuchsia-zircon-sys",
+ "winapi",
 ]
 
-[[package]]
-name = "fuchsia-zircon-sys"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-
 [[package]]
 name = "funty"
 version = "2.0.0"
@@ -2262,26 +2215,6 @@ dependencies = [
  "unicode-width",
 ]
 
-[[package]]
-name = "inotify"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
-dependencies = [
- "bitflags",
- "inotify-sys",
- "libc",
-]
-
-[[package]]
-name = "inotify-sys"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -2291,15 +2224,6 @@ dependencies = [
  "cfg-if 1.0.0",
 ]
 
-[[package]]
-name = "iovec"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "ircd"
 version = "0.3.0"
@@ -2367,16 +2291,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "kernel32-sys"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
-dependencies = [
- "winapi 0.2.8",
- "winapi-build",
-]
-
 [[package]]
 name = "kv-log-macro"
 version = "1.0.7"
@@ -2398,12 +2312,6 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
-[[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
 [[package]]
 name = "leb128"
 version = "0.2.5"
@@ -2423,7 +2331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
 dependencies = [
  "cfg-if 1.0.0",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2547,25 +2455,6 @@ dependencies = [
  "adler",
 ]
 
-[[package]]
-name = "mio"
-version = "0.6.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
-dependencies = [
- "cfg-if 0.1.10",
- "fuchsia-zircon",
- "fuchsia-zircon-sys",
- "iovec",
- "kernel32-sys",
- "libc",
- "log",
- "miow",
- "net2",
- "slab",
- "winapi 0.2.8",
-]
-
 [[package]]
 name = "mio"
 version = "0.8.4"
@@ -2578,30 +2467,6 @@ dependencies = [
  "windows-sys 0.36.1",
 ]
 
-[[package]]
-name = "mio-extras"
-version = "2.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
-dependencies = [
- "lazycell",
- "log",
- "mio 0.6.23",
- "slab",
-]
-
-[[package]]
-name = "miow"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
-dependencies = [
- "kernel32-sys",
- "net2",
- "winapi 0.2.8",
- "ws2_32-sys",
-]
-
 [[package]]
 name = "more-asserts"
 version = "0.2.2"
@@ -2626,17 +2491,6 @@ dependencies = [
  "tempfile",
 ]
 
-[[package]]
-name = "net2"
-version = "0.2.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
-dependencies = [
- "cfg-if 0.1.10",
- "libc",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "nix"
 version = "0.23.1"
@@ -2671,24 +2525,6 @@ dependencies = [
  "minimal-lexical",
 ]
 
-[[package]]
-name = "notify"
-version = "4.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
-dependencies = [
- "bitflags",
- "filetime",
- "fsevent",
- "fsevent-sys",
- "inotify",
- "libc",
- "mio 0.6.23",
- "mio-extras",
- "walkdir",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "num-bigint"
 version = "0.4.3"
@@ -2886,7 +2722,7 @@ dependencies = [
  "libc",
  "redox_syscall 0.2.13",
  "smallvec",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3072,7 +2908,7 @@ dependencies = [
  "libc",
  "log",
  "wepoll-ffi",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3406,7 +3242,7 @@ dependencies = [
  "bitflags",
  "libc",
  "mach",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3415,7 +3251,7 @@ version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3439,7 +3275,7 @@ dependencies = [
  "spin 0.5.2",
  "untrusted",
  "web-sys",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3755,7 +3591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
 dependencies = [
  "libc",
- "mio 0.8.4",
+ "mio",
  "signal-hook",
 ]
 
@@ -3847,7 +3683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4124,6 +3960,7 @@ dependencies = [
  "async-executor",
  "async-std",
  "async-trait",
+ "bs58",
  "chrono",
  "crypto_box",
  "ctrlc-async",
@@ -4133,7 +3970,6 @@ dependencies = [
  "fxhash",
  "hex",
  "log",
- "notify",
  "rand",
  "serde",
  "serde_json",
@@ -4142,6 +3978,7 @@ dependencies = [
  "structopt",
  "structopt-toml",
  "thiserror",
+ "toml",
  "url",
 ]
 
@@ -4156,7 +3993,7 @@ dependencies = [
  "libc",
  "redox_syscall 0.2.13",
  "remove_dir_all",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4167,7 +4004,7 @@ checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42"
 dependencies = [
  "byteorder",
  "dirs 1.0.5",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4186,7 +4023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4244,7 +4081,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
 dependencies = [
  "libc",
  "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4544,7 +4381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
 dependencies = [
  "same-file",
- "winapi 0.3.9",
+ "winapi",
  "winapi-util",
 ]
 
@@ -4665,7 +4502,7 @@ dependencies = [
  "wasmer-types",
  "wasmer-vm",
  "wat",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4816,7 +4653,7 @@ dependencies = [
  "wasmer-engine-universal-artifact",
  "wasmer-types",
  "wasmer-vm",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4900,7 +4737,7 @@ dependencies = [
  "thiserror",
  "wasmer-artifact",
  "wasmer-types",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4959,7 +4796,7 @@ dependencies = [
  "cfg-if 0.1.10",
  "libc",
  "memory_units",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4988,12 +4825,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "winapi"
-version = "0.2.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -5004,12 +4835,6 @@ dependencies = [
  "winapi-x86_64-pc-windows-gnu",
 ]
 
-[[package]]
-name = "winapi-build"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
-
 [[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
@@ -5022,7 +4847,7 @@ version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -5123,17 +4948,7 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
 dependencies = [
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "ws2_32-sys"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
-dependencies = [
- "winapi 0.2.8",
- "winapi-build",
+ "winapi",
 ]
 
 [[package]]

+ 5 - 0
Cargo.toml

@@ -160,6 +160,7 @@ websockets = [
 ]
 
 util = [
+    "blake3",
 	"bs58",
 	"hex",
 	"bincode",
@@ -299,6 +300,10 @@ tx = [
 	"util",
 ]
 
+dht = [
+    "util",
+]
+
 [[example]]
 name = "net"
 path = "example/net.rs"

+ 5 - 0
bin/ircd/src/server.rs

@@ -496,6 +496,11 @@ impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcServerConnection<C>
             return Err(Error::ChannelStopped)
         }
 
+        if line == "\n" {
+            warn!("Closing connection.");
+            return Err(Error::ChannelStopped)
+        }
+
         Ok(line.clone())
     }
 }

+ 11 - 0
bin/tau/tau-cli/src/main.rs

@@ -70,6 +70,12 @@ enum TauSubcommand {
 
     /// Get task info by ID
     Info { task_id: u64 },
+
+    /// Switch workspace
+    Switch {
+        /// Tau workspace
+        workspace: String,
+    },
 }
 
 pub struct Tau {
@@ -142,6 +148,11 @@ async fn main() -> Result<()> {
                 let task = tau.get_task_by_id(task_id).await?;
                 print_task_info(task)
             }
+
+            TauSubcommand::Switch { workspace } => {
+                tau.switch_ws(workspace).await?;
+                Ok(())
+            }
         },
         None => {
             let task_ids = tau.get_ids().await?;

+ 10 - 0
bin/tau/tau-cli/src/rpc.rs

@@ -69,4 +69,14 @@ impl Tau {
 
         Ok(serde_json::from_value(rep)?)
     }
+
+    /// Switch workspace.
+    pub async fn switch_ws(&self, workspace: String) -> Result<()> {
+        let req = JsonRequest::new("switch_ws", json!([workspace]));
+        let rep = self.rpc_client.request(req).await?;
+
+        debug!("Got reply: {:?}", rep);
+
+        Ok(())
+    }
 }

+ 2 - 1
bin/tau/taud/Cargo.toml

@@ -39,4 +39,5 @@ structopt = "0.3.26"
 structopt-toml = "0.5.0"
 crypto_box = {version = "0.7.2", features = ["std"]}
 hex = "0.4.3"
-notify = "4.0.17"
+bs58 = "0.4.0"
+toml = "0.5.9"

+ 69 - 17
bin/tau/taud/src/jsonrpc.rs

@@ -1,7 +1,9 @@
+use async_std::sync::{Arc, Mutex};
 use std::path::PathBuf;
 
 use async_trait::async_trait;
-use log::debug;
+use fxhash::FxHashMap;
+use log::{debug, warn};
 use serde::{Deserialize, Serialize};
 use serde_json::{json, Value};
 
@@ -11,17 +13,22 @@ use darkfi::{
         server::RequestHandler,
     },
     util::Timestamp,
+    Error,
 };
 
 use crate::{
     error::{to_json_result, TaudError, TaudResult},
     month_tasks::MonthTasks,
     task_info::{Comment, TaskInfo},
+    util::Workspace,
 };
 
 pub struct JsonRpcInterface {
     dataset_path: PathBuf,
+    notify_queue_sender: async_channel::Sender<TaskInfo>,
     nickname: String,
+    workspace: Arc<Mutex<String>>,
+    configured_ws: FxHashMap<String, Workspace>,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
@@ -50,6 +57,7 @@ impl RequestHandler for JsonRpcInterface {
             Some("set_state") => self.set_state(params).await,
             Some("set_comment") => self.set_comment(params).await,
             Some("get_task_by_id") => self.get_task_by_id(params).await,
+            Some("switch_ws") => self.switch_ws(params).await,
             Some(_) | None => return JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
         };
 
@@ -58,8 +66,14 @@ impl RequestHandler for JsonRpcInterface {
 }
 
 impl JsonRpcInterface {
-    pub fn new(dataset_path: PathBuf, nickname: String) -> Self {
-        Self { dataset_path, nickname }
+    pub fn new(
+        dataset_path: PathBuf,
+        notify_queue_sender: async_channel::Sender<TaskInfo>,
+        nickname: String,
+        workspace: Arc<Mutex<String>>,
+        configured_ws: FxHashMap<String, Workspace>,
+    ) -> Self {
+        Self { dataset_path, nickname, workspace, configured_ws, notify_queue_sender }
     }
 
     // RPCAPI:
@@ -81,7 +95,9 @@ impl JsonRpcInterface {
         debug!(target: "tau", "JsonRpc::add() params {:?}", params);
 
         let task: BaseTaskInfo = serde_json::from_value(params[0].clone())?;
+        let ws = self.workspace.lock().await.clone();
         let mut new_task: TaskInfo = TaskInfo::new(
+            ws,
             &task.title,
             &task.desc,
             &self.nickname,
@@ -92,7 +108,7 @@ impl JsonRpcInterface {
         new_task.set_project(&task.project);
         new_task.set_assign(&task.assign);
 
-        new_task.save(&self.dataset_path)?;
+        self.notify_queue_sender.send(new_task).await.map_err(Error::from)?;
         Ok(json!(true))
     }
 
@@ -102,7 +118,8 @@ impl JsonRpcInterface {
     // <-- {"jsonrpc": "2.0", "result": [task_id, ...], "id": 1}
     async fn get_ids(&self, params: &[Value]) -> TaudResult<Value> {
         debug!(target: "tau", "JsonRpc::get_ids() params {:?}", params);
-        let tasks = MonthTasks::load_current_open_tasks(&self.dataset_path)?;
+        let ws = self.workspace.lock().await.clone();
+        let tasks = MonthTasks::load_current_open_tasks(&self.dataset_path, ws)?;
         let task_ids: Vec<u32> = tasks.iter().map(|task| task.get_id()).collect();
         Ok(json!(task_ids))
     }
@@ -117,9 +134,10 @@ impl JsonRpcInterface {
         if params.len() != 2 {
             return Err(TaudError::InvalidData("len of params should be 2".into()))
         }
+        let ws = self.workspace.lock().await.clone();
 
-        let task = self.check_params_for_update(&params[0], &params[1])?;
-        task.save(&self.dataset_path)?;
+        let task = self.check_params_for_update(&params[0], &params[1], ws)?;
+        self.notify_queue_sender.send(task).await.map_err(Error::from)?;
         Ok(json!(true))
     }
 
@@ -138,14 +156,15 @@ impl JsonRpcInterface {
         }
 
         let state: String = serde_json::from_value(params[1].clone())?;
+        let ws = self.workspace.lock().await.clone();
 
-        let mut task: TaskInfo = self.load_task_by_id(&params[0])?;
+        let mut task: TaskInfo = self.load_task_by_id(&params[0], ws)?;
 
         if states.contains(&state.as_str()) {
             task.set_state(&state);
         }
 
-        task.save(&self.dataset_path)?;
+        self.notify_queue_sender.send(task).await.map_err(Error::from)?;
 
         Ok(json!(true))
     }
@@ -162,11 +181,12 @@ impl JsonRpcInterface {
         }
 
         let comment_content: String = serde_json::from_value(params[1].clone())?;
+        let ws = self.workspace.lock().await.clone();
 
-        let mut task: TaskInfo = self.load_task_by_id(&params[0])?;
+        let mut task: TaskInfo = self.load_task_by_id(&params[0], ws)?;
         task.set_comment(Comment::new(&comment_content, &self.nickname));
 
-        task.save(&self.dataset_path)?;
+        self.notify_queue_sender.send(task).await.map_err(Error::from)?;
 
         Ok(json!(true))
     }
@@ -181,23 +201,55 @@ impl JsonRpcInterface {
         if params.len() != 1 {
             return Err(TaudError::InvalidData("len of params should be 1".into()))
         }
+        let ws = self.workspace.lock().await.clone();
 
-        let task: TaskInfo = self.load_task_by_id(&params[0])?;
+        let task: TaskInfo = self.load_task_by_id(&params[0], ws)?;
 
         Ok(json!(task))
     }
 
-    fn load_task_by_id(&self, task_id: &Value) -> TaudResult<TaskInfo> {
-        let task_id: u64 = serde_json::from_value(task_id.clone())?;
+    // RPCAPI:
+    // Switch tasks workspace.
+    // --> {"jsonrpc": "2.0", "method": "switch_ws", "params": [workspace], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "true", "id": 1}
+    async fn switch_ws(&self, params: &[Value]) -> TaudResult<Value> {
+        debug!(target: "tau", "JsonRpc::switch_ws() params {:?}", params);
+
+        if params.len() != 1 {
+            return Err(TaudError::InvalidData("len of params should be 1".into()))
+        }
+
+        if !params[0].is_string() {
+            return Err(TaudError::InvalidData("Invalid workspace".into()))
+        }
 
-        let tasks = MonthTasks::load_current_open_tasks(&self.dataset_path)?;
+        let ws = params[0].as_str().unwrap().to_string();
+        let mut s = self.workspace.lock().await;
+
+        if self.configured_ws.contains_key(&ws) {
+            *s = ws
+        } else {
+            warn!("Workspace \"{}\" is not configured", ws);
+        }
+
+        Ok(json!(true))
+    }
+
+    fn load_task_by_id(&self, task_id: &Value, ws: String) -> TaudResult<TaskInfo> {
+        let task_id: u64 = serde_json::from_value(task_id.clone())?;
+        let tasks = MonthTasks::load_current_open_tasks(&self.dataset_path, ws)?;
         let task = tasks.into_iter().find(|t| (t.get_id() as u64) == task_id);
 
         task.ok_or(TaudError::InvalidId)
     }
 
-    fn check_params_for_update(&self, task_id: &Value, fields: &Value) -> TaudResult<TaskInfo> {
-        let mut task: TaskInfo = self.load_task_by_id(task_id)?;
+    fn check_params_for_update(
+        &self,
+        task_id: &Value,
+        fields: &Value,
+        ws: String,
+    ) -> TaudResult<TaskInfo> {
+        let mut task: TaskInfo = self.load_task_by_id(task_id, ws)?;
 
         if !fields.is_object() {
             return Err(TaudError::InvalidData("Invalid task's data".into()))

+ 61 - 144
bin/tau/taud/src/main.rs

@@ -1,11 +1,11 @@
 use async_std::sync::{Arc, Mutex};
-use std::{env, fs::create_dir_all, sync::mpsc, time::Duration};
+use std::{env, fs::create_dir_all};
 
 use async_executor::Executor;
-use crypto_box::{aead::Aead, Box, SecretKey, KEY_SIZE};
+use crypto_box::{aead::Aead, Box, SecretKey};
 use futures::{select, FutureExt};
+use fxhash::FxHashMap;
 use log::{debug, error, info, warn};
-use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
 use smol::future;
 use structopt_toml::StructOptToml;
 use fxhash::FxHashMap;
@@ -35,64 +35,50 @@ use crate::{
     jsonrpc::JsonRpcInterface,
     settings::{Args, CONFIG_FILE, CONFIG_FILE_CONTENTS},
     task_info::TaskInfo,
-    util::{load, save},
+    util::{parse_workspaces, Workspace},
 };
 
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct EncryptedTask {
+    workspace: String,
     nonce: Vec<u8>,
     payload: Vec<u8>,
 }
 
 fn encrypt_task(
     task: &TaskInfo,
-    secret_key: &SecretKey,
+    workspace: &String,
+    salsa_box: &Box,
     rng: &mut crypto_box::rand_core::OsRng,
 ) -> TaudResult<EncryptedTask> {
     debug!("start encrypting task");
-    let public_key = secret_key.public_key();
-    let msg_box = Box::new(&public_key, secret_key);
 
     let nonce = crypto_box::generate_nonce(rng);
     let payload = &serialize(task)[..];
-    let payload = msg_box.encrypt(&nonce, payload)?;
+    let payload = salsa_box.encrypt(&nonce, payload)?;
 
     let nonce = nonce.to_vec();
-    Ok(EncryptedTask { nonce, payload })
+    Ok(EncryptedTask { workspace: workspace.to_string(), nonce, payload })
 }
 
-fn decrypt_task(encrypt_task: &EncryptedTask, secret_key: &SecretKey) -> TaudResult<TaskInfo> {
+fn decrypt_task(encrypt_task: &EncryptedTask, salsa_box: &Box) -> TaudResult<TaskInfo> {
     debug!("start decrypting task");
-    let public_key = secret_key.public_key();
-    let msg_box = Box::new(&public_key, secret_key);
 
     let nonce = encrypt_task.nonce.as_slice();
-    let decrypted_task = msg_box.decrypt(nonce.into(), &encrypt_task.payload[..])?;
+    let decrypted_task = salsa_box.decrypt(nonce.into(), &encrypt_task.payload[..])?;
 
     let task = deserialize(&decrypted_task)?;
 
     Ok(task)
 }
 
-fn load_task_path_from_osstr(task_path: std::path::PathBuf) -> Option<String> {
-    task_path.file_name()?;
-
-    let task_path = task_path.file_name().unwrap().to_str().unwrap_or("");
-
-    if task_path.is_empty() {
-        return None
-    }
-
-    Some(task_path.to_string())
-}
-
 async fn start_sync_loop(
     commits_received: Arc<Mutex<Vec<String>>>,
     broadcast_rcv: async_channel::Receiver<TaskInfo>,
     raft_msgs_sender: async_channel::Sender<EncryptedTask>,
     commits_recv: async_channel::Receiver<EncryptedTask>,
     datastore_path: std::path::PathBuf,
-    secret_key: SecretKey,
+    configured_ws: FxHashMap<String, Workspace>,
     mut rng: crypto_box::rand_core::OsRng,
 ) -> TaudResult<()> {
     loop {
@@ -100,87 +86,38 @@ async fn start_sync_loop(
             task = broadcast_rcv.recv().fuse() => {
                 let tk = task.map_err(Error::from)?;
                 info!(target: "tau", "Save the task: ref: {}", tk.ref_id);
-                let encrypted_task = encrypt_task(&tk, &secret_key,&mut rng)?;
-                raft_msgs_sender.send(encrypted_task).await.map_err(Error::from)?;
+                if configured_ws.contains_key(&tk.workspace) {
+                    let ws_info = configured_ws.get(&tk.workspace).unwrap();
+                    if let Some(salsa_box) = &ws_info.encryption {
+                        let encrypted_task = encrypt_task(&tk, &tk.workspace, salsa_box, &mut rng)?;
+                        raft_msgs_sender.send(encrypted_task).await.map_err(Error::from)?;
+                    }
+                }
             }
             task = commits_recv.recv().fuse() => {
                 let recv = task.map_err(Error::from)?;
-                let task = decrypt_task(&recv, &secret_key);
-
-                if let Err(e) = task {
-                    warn!("unable to decrypt the task: {}", e);
-                    continue
-                }
-
-                let task = task.unwrap();
-                if !commits_received.lock().await.contains(&task.ref_id) {
-                    commits_received.lock().await.push(task.ref_id.clone());
+                if configured_ws.contains_key(&recv.workspace) {
+                    let ws_info = configured_ws.get(&recv.workspace).unwrap();
+                    if let Some(salsa_box) = &ws_info.encryption {
+                        let task = decrypt_task(&recv, salsa_box);
+                        if let Err(e) = task {
+                            warn!("unable to decrypt the task: {}", e);
+                            continue
+                        }
+
+                        let task = task.unwrap();
+                        if !commits_received.lock().await.contains(&task.ref_id) {
+                            commits_received.lock().await.push(task.ref_id.clone());
+                        }
+                        info!(target: "tau", "Update the task: ref: {}", task.ref_id);
+                        task.save(&datastore_path)?;
+                    }
                 }
-                info!(target: "tau", "Update the task: ref: {}", task.ref_id);
-                task.save(&datastore_path)?;
             }
         }
     }
 }
 
-async fn watch_files(
-    commits_received: Arc<Mutex<Vec<String>>>,
-    broadcast_snd: async_channel::Sender<TaskInfo>,
-    datastore_path: std::path::PathBuf,
-    (tx, rx): (mpsc::Sender<DebouncedEvent>, mpsc::Receiver<DebouncedEvent>),
-) -> TaudResult<()> {
-    let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_millis(500)).unwrap();
-
-    let watch_path = datastore_path.join("task");
-    info!("Start watching local tasks files: {:?}", &watch_path);
-    watcher.watch(watch_path, RecursiveMode::Recursive).unwrap();
-
-    loop {
-        let event = rx.recv();
-
-        if let Err(e) = event {
-            error!("Watch files error: {:?}", e);
-            continue
-        }
-
-        let event = event.unwrap();
-        match event {
-            DebouncedEvent::Write(ev) | DebouncedEvent::Create(ev) => {
-                let task_path = load_task_path_from_osstr(ev);
-
-                if task_path.is_none() {
-                    continue
-                }
-
-                let task = TaskInfo::load(&task_path.unwrap(), &datastore_path);
-
-                if task.is_err() {
-                    continue
-                }
-
-                let task = task.unwrap();
-
-                let mut commits_received = commits_received.lock().await;
-                if commits_received.contains(&task.ref_id) {
-                    let index_task =
-                        commits_received.iter().position(|r| r == &task.ref_id).unwrap();
-                    commits_received.remove(index_task);
-                    continue
-                }
-                drop(commits_received);
-
-                broadcast_snd.send(task).await.map_err(Error::from)?;
-            }
-            DebouncedEvent::Error(err, _) => {
-                debug!("Watching files Error: {}", err);
-                break
-            }
-            _ => {}
-        }
-    }
-    Ok(())
-}
-
 async_daemonize!(realmain);
 async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
     let datastore_path = expand_path(&settings.datastore)?;
@@ -193,44 +130,44 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
         return Ok(())
     }
 
-    // mkdir datastore_path if not exists
-    create_dir_all(datastore_path.join("month"))?;
-    create_dir_all(datastore_path.join("task"))?;
-
     let mut rng = crypto_box::rand_core::OsRng;
 
-    let secret_key = if settings.key_gen {
+    if settings.key_gen {
         info!(target: "tau", "Generating a new secret key");
-        let secret = SecretKey::generate(&mut rng);
-        let sk_string = hex::encode(secret.as_bytes());
-        save::<String>(&datastore_path.join("secret_key"), &sk_string)?;
-        secret
-    } else {
-        let loaded_key = load::<String>(&datastore_path.join("secret_key"));
-
-        if loaded_key.is_err() {
-            error!(
-                "Could not load secret key from file, \
-                 Please run \"taud --help\" for more information"
-            );
-            return Ok(())
-        }
+        let secret_key = SecretKey::generate(&mut rng);
+        let encoded = bs58::encode(secret_key.as_bytes());
+        println!("Secret key: {}", encoded.into_string());
+        return Ok(())
+    }
 
-        let sk_bytes = hex::decode(loaded_key.unwrap())?;
-        let sk_bytes: [u8; KEY_SIZE] = sk_bytes.as_slice().try_into()?;
-        SecretKey::try_from(sk_bytes)?
-    };
+    // Pick up workspace settings from the TOML configuration
+    let cfg_path = get_config_path(settings.config, CONFIG_FILE)?;
+    let configured_ws = parse_workspaces(&cfg_path)?;
+
+    // mkdir datastore_path if not exists
+    create_dir_all(datastore_path.join("month"))?;
+    create_dir_all(datastore_path.join("task"))?;
+
+    // start at the first configured workspace
+    let key = configured_ws.keys().next().ok_or(Error::ConfigInvalid)?;
+    let workspace = Arc::new(Mutex::new(key.to_owned()));
 
     let (broadcast_snd, broadcast_rcv) = async_channel::unbounded::<TaskInfo>();
 
     //
     // RPC
     //
-    let rpc_interface = Arc::new(JsonRpcInterface::new(datastore_path.clone(), nickname.unwrap()));
+    let rpc_interface = Arc::new(JsonRpcInterface::new(
+        datastore_path.clone(),
+        broadcast_snd,
+        nickname.unwrap(),
+        workspace,
+        configured_ws.clone(),
+    ));
     executor.spawn(listen_and_serve(settings.rpc_listen.clone(), rpc_interface)).detach();
 
     //
-    //Raft
+    // Raft
     //
     let net_settings = settings.net;
     let seen_net_msgs = Arc::new(Mutex::new(FxHashMap::default()));
@@ -251,7 +188,7 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
             raft.get_msgs_channel(),
             raft.get_commits_channel(),
             datastore_path.clone(),
-            secret_key,
+            configured_ws,
             rng,
         ))
         .detach();
@@ -282,19 +219,6 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
 
     executor.spawn(p2p.clone().run(executor.clone())).detach();
 
-    //
-    // Watch changes in tasks files
-    //
-    let (tx, rx) = mpsc::channel();
-    executor
-        .spawn(watch_files(
-            commits_received,
-            broadcast_snd,
-            datastore_path.clone(),
-            (tx.clone(), rx),
-        ))
-        .detach();
-
     //
     // Waiting Exit signal
     //
@@ -310,12 +234,5 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
 
     raft.start(p2p.clone(), p2p_recv_channel.clone(), executor.clone(), shutdown.clone()).await?;
 
-    if tx
-        .send(DebouncedEvent::Error(notify::Error::Generic("Catch exit signal".into()), None))
-        .is_ok()
-    {
-        warn!(target: "tau", "Terminating..");
-    }
-
     Ok(())
 }

+ 25 - 6
bin/tau/taud/src/month_tasks.rs

@@ -120,9 +120,14 @@ impl MonthTasks {
         }
     }
 
-    pub fn load_current_open_tasks(dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
+    pub fn load_current_open_tasks(dataset_path: &Path, ws: String) -> TaudResult<Vec<TaskInfo>> {
         let mt = Self::load_or_create(None, dataset_path)?;
-        Ok(mt.objects(dataset_path)?.into_iter().filter(|t| t.get_state() != "stop").collect())
+        Ok(mt
+            .objects(dataset_path)?
+            .into_iter()
+            .filter(|t| t.get_state() != "stop")
+            .filter(|t| t.workspace == ws)
+            .collect())
     }
 }
 
@@ -156,8 +161,15 @@ mod tests {
         // load and save TaskInfo
         ///////////////////////
 
-        let mut task =
-            TaskInfo::new("test_title", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
+        let mut task = TaskInfo::new(
+            "darkfi".to_string(),
+            "test_title",
+            "test_desc",
+            "NICKNAME",
+            None,
+            0.0,
+            &dataset_path,
+        )?;
 
         task.save(&dataset_path)?;
 
@@ -197,8 +209,15 @@ mod tests {
         // activate task
         ///////////////////////
 
-        let task =
-            TaskInfo::new("test_title_3", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
+        let task = TaskInfo::new(
+            "darkfi".to_string(),
+            "test_title_3",
+            "test_desc",
+            "NICKNAME",
+            None,
+            0.0,
+            &dataset_path,
+        )?;
 
         task.save(&dataset_path)?;
 

+ 7 - 1
bin/tau/taud/src/task_info.rs

@@ -58,6 +58,7 @@ pub struct TaskAssigns(Vec<String>);
 #[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
 pub struct TaskInfo {
     pub(crate) ref_id: String,
+    pub(crate) workspace: String,
     id: u32,
     title: String,
     desc: String,
@@ -73,6 +74,7 @@ pub struct TaskInfo {
 
 impl TaskInfo {
     pub fn new(
+        workspace: String,
         title: &str,
         desc: &str,
         owner: &str,
@@ -86,7 +88,10 @@ impl TaskInfo {
         let created_at = Timestamp::current_time();
 
         let task_ids: Vec<u32> =
-            MonthTasks::load_current_open_tasks(dataset_path)?.into_iter().map(|t| t.id).collect();
+            MonthTasks::load_current_open_tasks(dataset_path, workspace.clone())?
+                .into_iter()
+                .map(|t| t.id)
+                .collect();
 
         let id: u32 = find_free_id(&task_ids);
 
@@ -98,6 +103,7 @@ impl TaskInfo {
 
         Ok(Self {
             ref_id,
+            workspace,
             id,
             title: title.into(),
             desc: desc.into(),

+ 50 - 2
bin/tau/taud/src/util.rs

@@ -1,10 +1,58 @@
-use std::{fs::File, io::BufReader, path::Path};
-
+use std::{
+    fs::File,
+    io::BufReader,
+    path::{Path, PathBuf},
+};
+
+use fxhash::FxHashMap;
+use log::info;
 use rand::{distributions::Alphanumeric, thread_rng, Rng};
 use serde::{de::DeserializeOwned, Serialize};
 
 use darkfi::Result;
 
+#[derive(Clone)]
+pub struct Workspace {
+    pub encryption: Option<crypto_box::Box>,
+}
+
+impl Workspace {
+    pub fn new() -> Result<Self> {
+        Ok(Self { encryption: None })
+    }
+}
+
+/// Parse the configuration file for any configured workspaces and return
+/// a map containing said configurations.
+pub fn parse_workspaces(config_file: &PathBuf) -> Result<FxHashMap<String, Workspace>> {
+    let toml_contents = std::fs::read_to_string(config_file)?;
+    let mut ret = FxHashMap::default();
+
+    if let toml::Value::Table(map) = toml::from_str(&toml_contents)? {
+        if map.contains_key("workspace") && map["workspace"].is_table() {
+            for ws in map["workspace"].as_table().unwrap() {
+                info!("Found configuration for workspace {}", ws.0);
+                let mut workspace_info = Workspace::new()?;
+
+                if ws.1.as_table().unwrap().contains_key("secret") {
+                    // Build the NaCl box
+                    let s = ws.1["secret"].as_str().unwrap();
+                    let bytes: [u8; 32] = bs58::decode(s).into_vec()?.try_into().unwrap();
+                    let secret = crypto_box::SecretKey::from(bytes);
+                    let public = secret.public_key();
+                    let msg_box = crypto_box::Box::new(&public, &secret);
+                    workspace_info.encryption = Some(msg_box);
+                    info!("Instantiated NaCl box for workspace {}", ws.0);
+                }
+
+                ret.insert(ws.0.to_string(), workspace_info);
+            }
+        }
+    };
+
+    Ok(ret)
+}
+
 pub fn random_ref_id() -> String {
     thread_rng().sample_iter(&Alphanumeric).take(30).map(char::from).collect()
 }

+ 10 - 2
bin/tau/taud_config.toml

@@ -13,7 +13,7 @@
 #inbound="tcp://127.0.0.1:12002" 
 
 ## Connection slots
-#outbound_connections=0 
+outbound_connections=5
 
 ## P2P external address
 #external_addr="tls://127.0.0.1:12002"
@@ -22,7 +22,7 @@
 #peers=["tls://127.0.0.1:12003"]
 
 ## Seed nodes to connect to 
-#seeds=["tls://127.0.0.1:12001"]
+seeds=["tls://tau0.dark.fi:12001", "tls://tau1.dark.fi:12001"]
 
 ## these are the default configuration for the p2p network
 #manual_attempt_limit=0
@@ -30,3 +30,11 @@
 #connect_timeout_seconds=10
 #channel_handshake_seconds=4
 #channel_heartbeat_seconds=10
+
+## Per-workspace settings
+[workspace."darkfi"]
+## Create with `taud --key-gen`
+secret = "7CkVuFgwTUpJn5Sv67Q3fyEDpa28yrSeL5Hg2GqQ4jfM"
+[workspace."general"]
+## Create with `taud --key-gen`
+secret = "6ZkvojUcSRML7wSGc3AMmM5meyyEXLoykT23cyUUB6GM"

+ 1 - 5
script/research/dhtd/Cargo.toml

@@ -5,7 +5,7 @@ edition = "2021"
 
 [dependencies.darkfi]
 path = "../../../"
-features = ["util"]
+features = ["dht"]
 
 [dependencies]
 async-channel = "1.6.1"
@@ -13,14 +13,10 @@ async-executor = "1.4.1"
 async-std = "1.12.0"
 async-trait = "0.1.56"
 blake3 = "1.3.1"
-chrono = "0.4.19"
 ctrlc-async = {version = "3.2.2", default-features = false, features = ["async-std", "termination"]}
 easy-parallel = "3.2.0"
-futures = "0.3.21"
 futures-lite = "1.12.0"
-fxhash = "0.2.1"
 log = "0.4.17"
-rand = "0.8.5"
 serde_json = "1.0.82"
 simplelog = "0.12.0"
 url = "2.2.2"

+ 2 - 2
script/research/dhtd/src/error.rs

@@ -5,18 +5,18 @@ use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
 pub enum RpcError {
     UnknownKey = -35107,
     QueryFailed = -35108,
-    RequestBroadcastFail = -35109,
     KeyInsertFail = -35110,
     KeyRemoveFail = -35111,
+    WaitingNetworkError = -35112,
 }
 
 fn to_tuple(e: RpcError) -> (i64, String) {
     let msg = match e {
         RpcError::UnknownKey => "Did not find key",
         RpcError::QueryFailed => "Failed to query key",
-        RpcError::RequestBroadcastFail => "Failed to broadcast request",
         RpcError::KeyInsertFail => "Failed to insert key",
         RpcError::KeyRemoveFail => "Failed to remove key",
+        RpcError::WaitingNetworkError => "Error while waiting network response.",
     };
 
     (e as i64, msg.to_string())

+ 75 - 178
script/research/dhtd/src/main.rs

@@ -1,20 +1,18 @@
 use async_executor::Executor;
 use async_std::sync::Arc;
 use async_trait::async_trait;
-use chrono::Utc;
-use futures::{select, FutureExt};
 use futures_lite::future;
-use log::{debug, error, info, warn};
+use log::{error, info};
 use serde_derive::Deserialize;
 use serde_json::{json, Value};
-use std::time::Duration;
 use structopt::StructOpt;
 use structopt_toml::StructOptToml;
 use url::Url;
 
 use darkfi::{
-    async_daemonize, cli_desc, net,
-    net::P2pPtr,
+    async_daemonize, cli_desc,
+    dht::{waiting_for_response, Dht, DhtPtr},
+    net,
     rpc::{
         jsonrpc::{
             ErrorCode::{InvalidParams, MethodNotFound},
@@ -25,24 +23,15 @@ use darkfi::{
     util::{
         cli::{get_log_config, get_log_level, spawn_config},
         path::get_config_path,
-        sleep,
+        serial::serialize,
     },
     Result,
 };
 
 mod error;
 use error::{server_error, RpcError};
-
-mod structures;
-use structures::{KeyRequest, KeyResponse, LookupRequest, State, StatePtr};
-
-mod protocol;
-use protocol::Protocol;
-
 const CONFIG_FILE: &str = "dhtd_config.toml";
 const CONFIG_FILE_CONTENTS: &str = include_str!("../dhtd_config.toml");
-const REQUEST_TIMEOUT: u64 = 2400;
-const SEEN_DURATION: i64 = 120;
 
 #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
 #[serde(default)]
@@ -81,28 +70,17 @@ struct Args {
     verbose: u8,
 }
 
-/// Struct representing DHT daemon state.
+/// Struct representing DHT daemon.
 /// This example/temp-impl stores String data.
 /// In final version everything will be in bytes (Vec<u8).
 pub struct Dhtd {
-    /// Daemon state
-    state: StatePtr,
-    /// P2P network pointer
-    p2p: P2pPtr,
-    /// Channel to receive responses from P2P
-    p2p_recv_channel: async_channel::Receiver<KeyResponse>,
-    /// Stop signal channel to terminate background processes
-    stop_signal: async_channel::Receiver<()>,
+    /// Daemon dht state
+    dht: DhtPtr,
 }
 
 impl Dhtd {
-    pub async fn new(
-        state: StatePtr,
-        p2p: P2pPtr,
-        p2p_recv_channel: async_channel::Receiver<KeyResponse>,
-        stop_signal: async_channel::Receiver<()>,
-    ) -> Result<Self> {
-        Ok(Self { state, p2p, p2p_recv_channel, stop_signal })
+    pub async fn new(dht: DhtPtr) -> Result<Self> {
+        Ok(Self { dht })
     }
 
     // RPCAPI:
@@ -115,94 +93,69 @@ impl Dhtd {
             return JsonError::new(InvalidParams, None, id).into()
         }
 
-        // Node verifies the key exist in the lookup map.
         let key = params[0].to_string();
-        let peers = match self.state.read().await.lookup.get(&key) {
-            Some(v) => v.clone(),
-            None => {
-                info!("Did not find key: {}", key);
-                return server_error(RpcError::UnknownKey, id).into()
-            }
-        };
-
-        debug!("Key is in peers: {:?}", peers);
+        let key_hash = blake3::hash(&serialize(&key));
 
-        // Each node holds a local map, acting as its cache.
-        // When the node receives a request for a key it doesn't hold,
-        // it will query the P2P network and saves the response in its local cache.
-        match self.state.read().await.map.get(&key) {
-            Some(v) => {
-                let string = std::str::from_utf8(&v).unwrap();
-                return JsonResponse::new(json!(string), id).into()
-            }
-            None => info!("Requested key doesn't exist locally, querying the network..."),
-        };
-
-        // We retrieve p2p network connected channels, to verify if we
-        // are connected to a network.
-        // Using len here because is_empty() uses unstable library feature
-        // called 'exact_size_is_empty'.
-        if self.p2p.channels().lock().await.values().len() == 0 {
-            warn!("Node is not connected to other nodes");
+        // We execute this sequence to prevent lock races between threads
+        // Verify key exists
+        let exists = self.dht.read().await.contains_key(key_hash.clone());
+        if let None = exists {
+            info!("Did not find key: {}", key);
             return server_error(RpcError::UnknownKey, id).into()
         }
 
-        // We create a key request, and broadcast it to the network
-        let daemon = self.state.read().await.id.to_string();
-        // We choose last known peer as request recipient
-        let peer = peers.iter().last().unwrap().to_string();
-        let request = KeyRequest::new(daemon.clone(), peer, key.clone());
-        // TODO: ask connected peers directly, not broadcast
-        if let Err(e) = self.p2p.broadcast(request).await {
-            error!("Failed broadcasting request: {}", e);
-            return server_error(RpcError::RequestBroadcastFail, id)
-        }
-
-        // Waiting network response
-        match self.waiting_for_response().await {
-            Ok(resp) => match resp {
-                Some(response) => {
-                    info!("Key found!");
-                    let string = std::str::from_utf8(&response.value).unwrap().to_string();
-                    self.insert_pair(id, response.key, string).await
+        // Check if key is local or shoud query network
+        let local = exists.unwrap();
+        if local {
+            match self.dht.read().await.get(key_hash.clone()) {
+                Some(value) => {
+                    let string = std::str::from_utf8(&value).unwrap().to_string();
+                    return JsonResponse::new(json!((key, string)), id).into()
                 }
                 None => {
                     info!("Did not find key: {}", key);
-                    server_error(RpcError::UnknownKey, id).into()
+                    return server_error(RpcError::UnknownKey, id).into()
                 }
-            },
-            Err(e) => {
-                error!("Failed to query key: {}", e);
-                server_error(RpcError::QueryFailed, id).into()
             }
         }
-    }
 
-    // Auxilary function to wait for a key response from the P2P network.
-    async fn waiting_for_response(&self) -> Result<Option<KeyResponse>> {
-        let ex = Arc::new(async_executor::Executor::new());
-        let (timeout_s, timeout_r) = async_channel::unbounded::<()>();
-        ex.spawn(async move {
-            sleep(Duration::from_millis(REQUEST_TIMEOUT).as_secs()).await;
-            timeout_s.send(()).await.unwrap_or(());
-        })
-        .detach();
-
-        loop {
-            select! {
-                msg =  self.p2p_recv_channel.recv().fuse() => {
-                    let response = msg?;
-                    return Ok(Some(response))
-                },
-                _ = self.stop_signal.recv().fuse() => break,
-                _ = timeout_r.recv().fuse() => break,
+        info!("Key doesn't exist locally, querring network...");
+        if let Err(e) = self.dht.read().await.request_key(key_hash).await {
+            error!("Failed to query key: {}", e);
+            return server_error(RpcError::QueryFailed, id).into()
+        }
+
+        info!("Waiting response...");
+        match waiting_for_response(self.dht.clone()).await {
+            Ok(response) => {
+                match response {
+                    Some(resp) => {
+                        info!("Key found!");
+                        // Optionally, we insert the key to our local map
+                        if let Err(e) =
+                            self.dht.write().await.insert(resp.key, resp.value.clone()).await
+                        {
+                            error!("Failed to insert key: {}", e);
+                            return server_error(RpcError::KeyInsertFail, id)
+                        }
+                        let string = std::str::from_utf8(&resp.value).unwrap().to_string();
+                        JsonResponse::new(json!((key, string)), id).into()
+                    }
+                    None => {
+                        info!("Did not find key: {}", key);
+                        server_error(RpcError::UnknownKey, id).into()
+                    }
+                }
+            }
+            Err(e) => {
+                error!("Error while waiting network response: {}", e);
+                server_error(RpcError::WaitingNetworkError, id).into()
             }
         }
-        Ok(None)
     }
 
     // RPCAPI:
-    // Insert key value pair in local map.
+    // Insert key value pair in dht.
     // --> {"jsonrpc": "2.0", "method": "insert", "params": ["key", "value"], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": "(key, value)", "id": 1}
     async fn insert(&self, id: Value, params: &[Value]) -> JsonResult {
@@ -211,25 +164,14 @@ impl Dhtd {
         }
 
         let key = params[0].to_string();
+        let key_hash = blake3::hash(&serialize(&key));
         let value = params[1].to_string();
 
-        self.insert_pair(id, key, value).await
-    }
-
-    /// Auxilary function to handle pair insertion to state
-    async fn insert_pair(&self, id: Value, key: String, value: String) -> JsonResult {
-        if let Err(e) = self.state.write().await.insert(key.clone(), value.as_bytes().to_vec()) {
+        if let Err(e) = self.dht.write().await.insert(key_hash, value.as_bytes().to_vec()).await {
             error!("Failed to insert key: {}", e);
             return server_error(RpcError::KeyInsertFail, id)
         }
 
-        let daemon = self.state.read().await.id.to_string();
-        let request = LookupRequest::new(daemon, key.clone(), 0);
-        if let Err(e) = self.p2p.broadcast(request).await {
-            error!("Failed broadcasting request: {}", e);
-            return server_error(RpcError::RequestBroadcastFail, id)
-        }
-
         JsonResponse::new(json!((key, value)), id).into()
     }
 
@@ -243,21 +185,15 @@ impl Dhtd {
         }
 
         let key = params[0].to_string();
+        let key_hash = blake3::hash(&serialize(&key));
+
         // Check if key value pair existed and act accordingly
-        let result = self.state.write().await.remove(key.clone());
+        let result = self.dht.write().await.remove(key_hash).await;
         match result {
             Ok(option) => match option {
                 Some(k) => {
-                    info!("Key removed: {}", k);
-
-                    let daemon = self.state.read().await.id.to_string();
-                    let request = LookupRequest::new(daemon, key.clone(), 1);
-                    if let Err(e) = self.p2p.broadcast(request).await {
-                        error!("Failed broadcasting request: {}", e);
-                        return server_error(RpcError::RequestBroadcastFail, id)
-                    }
-
-                    JsonResponse::new(json!(k), id).into()
+                    info!("Hash key removed: {}", k);
+                    JsonResponse::new(json!(k.to_string()), id).into()
                 }
                 None => {
                     info!("Did not find key: {}", key);
@@ -276,8 +212,9 @@ impl Dhtd {
     // --> {"jsonrpc": "2.0", "method": "map", "params": [], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": "map", "id": 1}
     pub async fn map(&self, id: Value, _params: &[Value]) -> JsonResult {
-        let map = self.state.read().await.map.clone();
-        JsonResponse::new(json!(map), id).into()
+        let map = self.dht.read().await.map.clone();
+        let map_string = format!("{:#?}", map);
+        JsonResponse::new(json!(map_string), id).into()
     }
 
     // RPCAPI:
@@ -285,8 +222,9 @@ impl Dhtd {
     // --> {"jsonrpc": "2.0", "method": "lookup", "params": [], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": "lookup", "id": 1}
     pub async fn lookup(&self, id: Value, _params: &[Value]) -> JsonResult {
-        let lookup = self.state.read().await.lookup.clone();
-        JsonResponse::new(json!(lookup), id).into()
+        let lookup = self.dht.read().await.lookup.clone();
+        let lookup_string = format!("{:#?}", lookup);
+        JsonResponse::new(json!(lookup_string), id).into()
     }
 }
 
@@ -310,32 +248,6 @@ impl RequestHandler for Dhtd {
     }
 }
 
-// Auxilary function to periodically prun seen messages, based on when they were received.
-// This helps us to prevent broadcasting loops.
-async fn prune_seen_messages(state: StatePtr) {
-    loop {
-        sleep(SEEN_DURATION as u64).await;
-        debug!("Pruning seen messages");
-
-        let now = Utc::now().timestamp();
-
-        let mut prune = vec![];
-        let map = state.read().await.seen.clone();
-        for (k, v) in map.iter() {
-            if now - v > SEEN_DURATION {
-                prune.push(k);
-            }
-        }
-
-        let mut map = map.clone();
-        for i in prune {
-            map.remove(i);
-        }
-
-        state.write().await.seen = map;
-    }
-}
-
 async_daemonize!(realmain);
 async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
     // We use this handler to block this function after detaching all
@@ -347,9 +259,6 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
     })
     .unwrap();
 
-    // Initialize daemon state
-    let state = State::new().await?;
-
     // P2P network
     let network_settings = net::Settings {
         inbound: args.p2p_accept,
@@ -360,26 +269,14 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
         ..Default::default()
     };
 
-    let (p2p_send_channel, p2p_recv_channel) = async_channel::unbounded::<KeyResponse>();
     let p2p = net::P2p::new(network_settings).await;
-    let registry = p2p.protocol_registry();
-
-    info!("Registering P2P protocols...");
-    let _state = state.clone();
-    registry
-        .register(net::SESSION_ALL, move |channel, p2p| {
-            let sender = p2p_send_channel.clone();
-            let state = _state.clone();
-            async move { Protocol::init(channel, sender, state, p2p).await.unwrap() }
-        })
-        .await;
-
-    // Initialize program state
-    let dhtd = Dhtd::new(state.clone(), p2p.clone(), p2p_recv_channel, shutdown.clone()).await?;
-    let dhtd = Arc::new(dhtd);
 
-    // Task to periodically clean up daemon seen messages
-    ex.spawn(prune_seen_messages(state.clone())).detach();
+    // Initialize daemon dht
+    let dht = Dht::new(None, p2p.clone(), shutdown.clone(), ex.clone()).await?;
+
+    // Initialize daemon
+    let dhtd = Dhtd::new(dht.clone()).await?;
+    let dhtd = Arc::new(dhtd);
 
     // JSON-RPC server
     info!("Starting JSON-RPC server");

+ 0 - 184
script/research/dhtd/src/structures.rs

@@ -1,184 +0,0 @@
-use async_std::sync::{Arc, RwLock};
-use fxhash::FxHashMap;
-use rand::Rng;
-use std::collections::HashSet;
-
-use darkfi::{
-    net,
-    util::serial::{serialize, SerialDecodable, SerialEncodable},
-    Result,
-};
-
-/// Atomic pointer to DHT daemon state
-pub type StatePtr = Arc<RwLock<State>>;
-
-// TODO: lookup table to be based on directly connected peers, not broadcast based
-// TODO: replace Strings with blake3 hashes
-// Using string in structures because we are at an external crate
-// and cant use blake3 serialization. To be replaced once merged with core src.
-
-/// Struct representing DHT daemon state.
-pub struct State {
-    /// Daemon id
-    pub id: blake3::Hash,
-    /// Daemon hasmap
-    pub map: FxHashMap<String, Vec<u8>>,
-    /// Network lookup map, containing nodes that holds each key
-    pub lookup: FxHashMap<String, HashSet<String>>,
-    /// Daemon seen requests/responses ids and timestamp,
-    /// to prevent rebroadcasting and loops
-    pub seen: FxHashMap<String, i64>,
-}
-
-impl State {
-    pub async fn new() -> Result<StatePtr> {
-        // Generate a random id
-        let mut rng = rand::thread_rng();
-        let n: u16 = rng.gen();
-        let id = blake3::hash(&serialize(&n));
-        let map = FxHashMap::default();
-        let lookup = FxHashMap::default();
-        let seen = FxHashMap::default();
-
-        let state = Arc::new(RwLock::new(State { id, map, lookup, seen }));
-
-        Ok(state)
-    }
-
-    /// Store provided key value pair and update local lookup map
-    pub fn insert(&mut self, key: String, value: Vec<u8>) -> Result<()> {
-        self.map.insert(key.clone(), value);
-        self.lookup_insert(key, self.id.to_string())
-    }
-
-    /// Remove provided key value pair and update local lookup map
-    pub fn remove(&mut self, key: String) -> Result<Option<String>> {
-        // Check if key value pair existed and act accordingly
-        let result = match self.map.remove(&key) {
-            Some(_) => {
-                self.lookup_remove(key.clone(), self.id.to_string())?;
-                Some(key)
-            }
-            None => None,
-        };
-
-        Ok(result)
-    }
-
-    /// Store provided key node pair in local lookup map
-    pub fn lookup_insert(&mut self, key: String, node_id: String) -> Result<()> {
-        let mut lookup_set = match self.lookup.get(&key) {
-            Some(s) => s.clone(),
-            None => HashSet::new(),
-        };
-
-        lookup_set.insert(node_id);
-        self.lookup.insert(key, lookup_set);
-
-        Ok(())
-    }
-
-    /// Remove provided node id from keys set in local lookup map
-    pub fn lookup_remove(&mut self, key: String, node_id: String) -> Result<()> {
-        if let Some(s) = self.lookup.get(&key) {
-            let mut lookup_set = s.clone();
-            lookup_set.remove(&node_id);
-            if lookup_set.is_empty() {
-                self.lookup.remove(&key);
-            } else {
-                self.lookup.insert(key, lookup_set);
-            }
-        }
-
-        Ok(())
-    }
-}
-
-/// This struct represents a DHT key request
-#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
-pub struct KeyRequest {
-    /// Request id    
-    pub id: String,
-    /// Daemon id requesting the key
-    pub from: String,
-    /// Daemon id holding the key
-    pub to: String,
-    /// Key entry
-    pub key: String,
-}
-
-impl KeyRequest {
-    pub fn new(from: String, to: String, key: String) -> Self {
-        // Generate a random id
-        let mut rng = rand::thread_rng();
-        let n: u16 = rng.gen();
-        let id = blake3::hash(&serialize(&n)).to_string();
-        Self { id, from, to, key }
-    }
-}
-
-impl net::Message for KeyRequest {
-    fn name() -> &'static str {
-        "keyrequest"
-    }
-}
-
-/// This struct represents a DHT key request response
-#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
-pub struct KeyResponse {
-    /// Response id
-    pub id: String,
-    /// Daemon id holding the key
-    pub from: String,
-    /// Daemon id holding the key
-    pub to: String,
-    /// Key entry
-    pub key: String,
-    /// Key value
-    pub value: Vec<u8>,
-}
-
-impl KeyResponse {
-    pub fn new(from: String, to: String, key: String, value: Vec<u8>) -> Self {
-        // Generate a random id
-        let mut rng = rand::thread_rng();
-        let n: u16 = rng.gen();
-        let id = blake3::hash(&serialize(&n)).to_string();
-        Self { id, from, to, key, value }
-    }
-}
-
-impl net::Message for KeyResponse {
-    fn name() -> &'static str {
-        "keyresponse"
-    }
-}
-
-/// This struct represents a lookup map request
-#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
-pub struct LookupRequest {
-    /// Request id    
-    pub id: String,
-    /// Daemon id executing the request
-    pub daemon: String,
-    /// Key entry
-    pub key: String,
-    /// Request type
-    pub req_type: u8, // 0 for insert, 1 for remove
-}
-
-impl LookupRequest {
-    pub fn new(daemon: String, key: String, req_type: u8) -> Self {
-        // Generate a random id
-        let mut rng = rand::thread_rng();
-        let n: u16 = rng.gen();
-        let id = blake3::hash(&serialize(&n)).to_string();
-        Self { id, daemon, key, req_type }
-    }
-}
-
-impl net::Message for LookupRequest {
-    fn name() -> &'static str {
-        "lookuprequest"
-    }
-}

+ 20 - 0
script/research/ec/intersection-multiplicity.sage

@@ -0,0 +1,20 @@
+# Calculate intersection multiplicity of a point in sage
+q = 47
+K = GF(q)
+E = EllipticCurve(K, (0, 5))
+Q = E(10, 26)
+
+C = E.defining_polynomial()
+
+R.<x, y, z> = PolynomialRing(K)
+f = y - Q[1]
+# Homogenize f
+f = z^f.degree() * f(x/z, y/z, 1)
+
+P.<x,y,z> = ProjectiveSpace(K, 2)
+X = P.subscheme([C(x, y, z)])
+Y = P.subscheme([f(x, y, z)])
+
+Q = X([Q[0], Q[1]])
+print(Q.intersection_multiplicity(Y))
+

+ 110 - 0
script/research/ec/pairing-modified-tate.sage

@@ -0,0 +1,110 @@
+# for more info check Washington example 11.7
+# https://github.com/narodnik/elliptic-curves-washington-solutions
+p = 11
+K = GF(p)
+E = EllipticCurve(K, [-1, 1])
+R.<x, y> = PolynomialRing(K)
+
+n = 5
+P = E(3, 6)
+assert P.order() == 5
+Q = P
+inf = E(0)
+
+# We are computing <P, P>_5
+#   v_5 = f_5(P) / f_5(P)
+# where
+#   div(f_5) = n[P + R] - n[R]
+
+D_P = ((1, P), (-1, inf))
+
+# Random point for D_Q
+R = E(0, 1)
+D_Q = ((1, Q + R), (-1, R))
+
+# n = 5 = 1 + 4
+# so we perform one addition, two doublings and another addition
+
+# Step 1
+i = n
+j = 0
+k = 1
+fj = fk = 1
+# Compute f1 such that
+#   div(f1) = [P + R] - [P] - [R] + [∞]
+# But D_P = [(3, 6)] - [∞]
+# so R = ∞, and hence D_1 = [P + ∞] - [P] - [∞] + [∞]
+#                         = 0
+# hence f1 = 1
+
+# First valuations of f0, f1
+(vj, vk) = (1, 1)
+
+dydx = lambda x, y: (3*x^2 - 1) / (2*y)
+
+print(f"i = {i}, j = {j}, k = {k}")
+while i != 0:
+    if i % 2 == 0:
+        i /= 2
+
+        # We are computing div(l) which is
+        # the line between kP and kP
+        kP = k*P
+        m = dydx(kP[0], kP[1])
+        c = kP[1] - m*kP[0]
+        l = y - m*x - c
+        # And now the vertical line through 2kP
+        _2kP = 2*kP
+        v = x - _2kP[0]
+
+        f = l / v
+
+        f_valuation = 1
+        for d, X in D_Q:
+            f_valuation *= f(X[0], X[1])^d
+        vk = vk^2 * f_valuation
+        print(f"  f = {f}")
+        print(f"  vk = {vk}")
+
+        k *= 2
+    else:
+        i -= 1
+
+        if j + k == 1:
+            assert k == 1
+            # fj = fk
+            # vj = vk
+        else:
+            # Interpolate jP and kP
+            assert j != k
+            jP = j*P
+            kP = k*P
+            print(f"{jP}, {kP}")
+            if jP[0] != kP[0]:
+                m = (jP[1] - kP[1]) / (jP[0] - kP[0])
+                c = jP[1] - m*jP[0]
+                l = y - m*x - c
+            else:
+                l = x - jP[0]
+            # Vertical line through (j + k)P
+            jkP = (j + k)*P
+            if jkP != inf:
+                v = x - jkP[0]
+            else:
+                v = K(1)
+
+            f = l / v
+            print(f"  f = {f}")
+            
+            f_valuation = 1
+            for d, X in D_Q:
+                f_valuation *= f(X[0], X[1])^d
+            vj = vj * vk * f_valuation
+            print(f"  vj = {vj}")
+
+        j += k
+
+    print(f"i = {i}, j = {j}, k = {k}")
+
+modified_tate = vj^((p - 1) / n)
+print(f"result = {modified_tate}")

+ 3 - 2
script/research/ec/pairing.sage

@@ -59,8 +59,9 @@ G2 = E2(x2, y2)
 k = GF(r)(q).multiplicative_order()
 assert k == 12
 
-#assert G1.tate_pairing(G1, r, k, q) == 1
-#assert G2.tate_pairing(G2, r, k, q) == 1
+# We need to map G1 -> G2
+# and then G2 -> G12
+#assert G1.tate_pairing(G2, r, k, q) == 1
 
 # G₁ ⊂ E(F)
 # G₂ ⊂ E'(F₂)

+ 1 - 0
script/research/ec/pairing/3.1.2-classgroup.sage

@@ -26,6 +26,7 @@ def degree(D):
 def neg(D):
     return tuple((-order, pnt) for order, pnt in D)
 
+# This means D1 ~ D2 because D1 - D2 ∈ Pic(E)
 D = D1 + neg(D2)
 assert degree(D) == 0
 assert dsum(D) == inf

+ 31 - 0
script/research/ec/pairing/3.1.3-divlines.sage

@@ -0,0 +1,31 @@
+q = 47
+Fq = GF(q)
+E = EllipticCurve(Fq, (0, 5))
+
+R.<x, y> = PolynomialRing(Fq)
+
+def add(P, Q):
+    Px, Py = P[0], P[1]
+    Qx, Qy = Q[0], Q[1]
+    # We don't handle this case yet :p
+    assert Px != Qx
+    gradient = (Qy - Py) / (Qx - Px)
+    intersect = Py - gradient * Px
+    f = y - gradient * x - intersect
+    assert f(Px, Py) == 0
+    assert f(Qx, Qy) == 0
+    R = P + Q
+    Rx, Ry = R[0], R[1]
+    assert f(Rx, -Ry) == 0
+    g = x - Rx
+    assert g(Rx, Ry) == g(Rx, -Ry) == 0
+    return f / g
+
+P = E(33, 9)
+Q = E(34, 39)
+# div(f) = [P] + [Q] - [P + Q] - [∞] ∈ Pic(E)
+#        = ([P] + [Q] - 2[∞]) - ([P + Q] - [∞])
+# => [P] + [Q] - 2[∞] ~ [P + Q] - [∞]
+f = add(P, Q)
+print(f)
+

+ 34 - 0
script/research/ec/unique-y-intersect.sage

@@ -0,0 +1,34 @@
+# Lets show that div(f) = [P] - [∞]
+# So any divisor with supp(D) = {P},
+# with an effective size of 1 can be represented
+# by the horizontal line f = y - P.y
+q = 47
+K = GF(q)
+E = EllipticCurve(K, (0, 5))
+C = E.defining_polynomial()
+
+R.<x, y> = PolynomialRing(K)
+
+for i in range(100):
+    P = E.random_point()
+    Px, Py = P[0], P[1]
+
+    # Skip points at infinity
+    if P[2] == 0:
+        continue
+    assert P[2] == 1
+
+    f = y - Py
+
+    I = Ideal([C(x, y, 1), f])
+    V = I.variety()
+    print(P, V)
+    assert len(V) == 1
+    assert V[0][x] == Px
+    assert V[0][y] == Py
+
+# Note: to reduce D = [P] - [Q], just note that
+# D = [P] - [Q]
+#   = ([P] - [∞]) - ([Q] - [∞])
+#   = div(f) - div(g) = div(f/g)
+

+ 1 - 24
src/blockchain/mod.rs

@@ -1,14 +1,8 @@
-use std::io;
-
 use log::debug;
 
 use crate::{
     consensus::{Block, BlockInfo},
-    impl_vec,
-    util::{
-        serial::{Decodable, Encodable, ReadExt, VarInt, WriteExt},
-        time::Timestamp,
-    },
+    util::time::Timestamp,
     Result,
 };
 
@@ -161,20 +155,3 @@ impl Blockchain {
         self.order.get_last()
     }
 }
-
-impl Encodable for blake3::Hash {
-    fn encode<S: io::Write>(&self, mut s: S) -> Result<usize> {
-        s.write_slice(self.as_bytes())?;
-        Ok(32)
-    }
-}
-
-impl Decodable for blake3::Hash {
-    fn decode<D: io::Read>(mut d: D) -> Result<Self> {
-        let mut bytes = [0u8; 32];
-        d.read_slice(&mut bytes)?;
-        Ok(bytes.into())
-    }
-}
-
-impl_vec!(blake3::Hash);

+ 0 - 1
src/consensus/proto/protocol_participant.rs

@@ -65,7 +65,6 @@ impl ProtocolParticipant {
                     self.p2p.broadcast_with_exclude(participant_copy, &exclude_list).await
                 {
                     error!("ProtocolParticipant::handle_receive_participant(): p2p broadcast failed: {}", e);
-                    continue
                 };
             }
         }

+ 22 - 21
src/consensus/proto/protocol_tx.rs

@@ -69,8 +69,9 @@ impl ProtocolTx {
             let tx_copy = (*tx).clone();
             let tx_hash = blake3::hash(&serialize(&tx_copy));
 
-            let tx_in_txstore =
-                match self.state.read().await.blockchain.transactions.contains(&tx_hash) {
+            {
+                let state = &mut self.state.write().await;
+                let tx_in_txstore = match state.blockchain.transactions.contains(&tx_hash) {
                     Ok(v) => v,
                     Err(e) => {
                         error!("handle_receive_tx(): Failed querying txstore: {}", e);
@@ -78,28 +79,28 @@ impl ProtocolTx {
                     }
                 };
 
-            if self.state.read().await.unconfirmed_txs.contains(&tx_copy) || tx_in_txstore {
-                debug!("ProtocolTx::handle_receive_tx(): We have already seen this tx.");
-                continue
-            }
-
-            debug!("ProtocolTx::handle_receive_tx(): Starting state transition validation");
-            let canon_state_clone = self.state.read().await.state_machine.lock().await.clone();
-            let mem_state = MemoryState::new(canon_state_clone);
-            match ValidatorState::validate_state_transitions(mem_state, &[tx_copy.clone()]) {
-                Ok(_) => debug!("ProtocolTx::handle_receive_tx(): State transition valid"),
-                Err(e) => {
-                    warn!("ProtocolTx::handle_receive_tx(): State transition fail: {}", e);
+                if state.unconfirmed_txs.contains(&tx_copy) || tx_in_txstore {
+                    debug!("ProtocolTx::handle_receive_tx(): We have already seen this tx.");
                     continue
                 }
-            }
 
-            // Nodes use unconfirmed_txs vector as seen_txs pool.
-            if self.state.write().await.append_tx(tx_copy.clone()) {
-                if let Err(e) = self.p2p.broadcast_with_exclude(tx_copy, &exclude_list).await {
-                    error!("handle_receive_tx(): p2p broadcast fail: {}", e);
-                    continue
-                };
+                debug!("ProtocolTx::handle_receive_tx(): Starting state transition validation");
+                let canon_state_clone = state.state_machine.lock().await.clone();
+                let mem_state = MemoryState::new(canon_state_clone);
+                match ValidatorState::validate_state_transitions(mem_state, &[tx_copy.clone()]) {
+                    Ok(_) => debug!("ProtocolTx::handle_receive_tx(): State transition valid"),
+                    Err(e) => {
+                        warn!("ProtocolTx::handle_receive_tx(): State transition fail: {}", e);
+                        continue
+                    }
+                }
+
+                // Nodes use unconfirmed_txs vector as seen_txs pool.
+                if state.append_tx(tx_copy.clone()) {
+                    if let Err(e) = self.p2p.broadcast_with_exclude(tx_copy, &exclude_list).await {
+                        error!("handle_receive_tx(): p2p broadcast fail: {}", e);
+                    };
+                }
             }
         }
     }

+ 273 - 0
src/dht/dht.rs

@@ -0,0 +1,273 @@
+use async_executor::Executor;
+use async_std::sync::{Arc, RwLock};
+use chrono::Utc;
+use futures::{select, FutureExt};
+use fxhash::FxHashMap;
+use log::{debug, error};
+use rand::Rng;
+use std::{collections::HashSet, time::Duration};
+
+use crate::{
+    net,
+    net::P2pPtr,
+    util::{serial::serialize, sleep},
+    Error::{NetworkNotConnected, UnknownKey},
+    Result,
+};
+
+use super::{
+    messages::{KeyRequest, KeyResponse, LookupRequest},
+    protocol::Protocol,
+};
+
+// Constants configuration
+const REQUEST_TIMEOUT: u64 = 2400;
+const SEEN_DURATION: i64 = 120;
+
+/// Atomic pointer to DHT state
+pub type DhtPtr = Arc<RwLock<Dht>>;
+
+// TODO: proper errors
+// TODO: lookup table to be based on directly connected peers, not broadcast based
+// Using string in structures because we are at an external crate
+// and cant use blake3 serialization. To be replaced once merged with core src.
+
+/// Struct representing DHT state.
+pub struct Dht {
+    /// Daemon id
+    pub id: blake3::Hash,
+    /// Daemon hasmap
+    pub map: FxHashMap<blake3::Hash, Vec<u8>>,
+    /// Network lookup map, containing nodes that holds each key
+    pub lookup: FxHashMap<blake3::Hash, HashSet<blake3::Hash>>,
+    /// P2P network pointer
+    p2p: P2pPtr,
+    /// Channel to receive responses from P2P
+    p2p_recv_channel: async_channel::Receiver<KeyResponse>,
+    /// Stop signal channel to terminate background processes
+    stop_signal: async_channel::Receiver<()>,
+    /// Daemon seen requests/responses ids and timestamp,
+    /// to prevent rebroadcasting and loops
+    pub seen: FxHashMap<blake3::Hash, i64>,
+}
+
+impl Dht {
+    pub async fn new(
+        initial: Option<FxHashMap<blake3::Hash, HashSet<blake3::Hash>>>,
+        p2p_ptr: P2pPtr,
+        stop_signal: async_channel::Receiver<()>,
+        ex: Arc<Executor<'_>>,
+    ) -> Result<DhtPtr> {
+        // Generate a random id
+        let mut rng = rand::thread_rng();
+        let n: u16 = rng.gen();
+        let id = blake3::hash(&serialize(&n));
+        let map = FxHashMap::default();
+        let lookup = match initial {
+            Some(l) => l,
+            None => FxHashMap::default(),
+        };
+        let p2p = p2p_ptr.clone();
+        let (p2p_send_channel, p2p_recv_channel) = async_channel::unbounded::<KeyResponse>();
+        let seen = FxHashMap::default();
+
+        let dht = Arc::new(RwLock::new(Dht {
+            id,
+            map,
+            lookup,
+            p2p,
+            p2p_recv_channel,
+            stop_signal,
+            seen,
+        }));
+
+        // Registering P2P protocols
+        let registry = p2p_ptr.protocol_registry();
+        let _dht = dht.clone();
+        registry
+            .register(net::SESSION_ALL, move |channel, p2p_ptr| {
+                let sender = p2p_send_channel.clone();
+                let dht = _dht.clone();
+                async move { Protocol::init(channel, sender, dht, p2p_ptr).await.unwrap() }
+            })
+            .await;
+
+        // Task to periodically clean up daemon seen messages
+        ex.spawn(prune_seen_messages(dht.clone())).detach();
+
+        Ok(dht)
+    }
+
+    /// Store provided key value pair, update lookup map and broadcast new insert to network
+    pub async fn insert(
+        &mut self,
+        key: blake3::Hash,
+        value: Vec<u8>,
+    ) -> Result<Option<blake3::Hash>> {
+        self.map.insert(key.clone(), value);
+
+        if let Err(e) = self.lookup_insert(key, self.id) {
+            error!("Failed to insert record to lookup map: {}", e);
+            return Err(e)
+        };
+
+        let request = LookupRequest::new(self.id, key.clone(), 0);
+        if let Err(e) = self.p2p.broadcast(request).await {
+            error!("Failed broadcasting request: {}", e);
+            return Err(e)
+        }
+
+        Ok(Some(key))
+    }
+
+    /// Remove provided key value pair and update lookup map
+    pub async fn remove(&mut self, key: blake3::Hash) -> Result<Option<blake3::Hash>> {
+        // Check if key value pair existed and act accordingly
+        match self.map.remove(&key) {
+            Some(_) => {
+                debug!("Key removed: {}", key);
+                let request = LookupRequest::new(self.id, key.clone(), 1);
+                if let Err(e) = self.p2p.broadcast(request).await {
+                    error!("Failed broadcasting request: {}", e);
+                    return Err(e)
+                }
+
+                self.lookup_remove(key.clone(), self.id)
+            }
+            None => Ok(None),
+        }
+    }
+
+    /// Store provided key node pair in lookup map and update network
+    pub fn lookup_insert(
+        &mut self,
+        key: blake3::Hash,
+        node_id: blake3::Hash,
+    ) -> Result<Option<blake3::Hash>> {
+        let mut lookup_set = match self.lookup.get(&key) {
+            Some(s) => s.clone(),
+            None => HashSet::new(),
+        };
+
+        lookup_set.insert(node_id);
+        self.lookup.insert(key.clone(), lookup_set);
+
+        Ok(Some(key))
+    }
+
+    /// Remove provided node id from keys set in local lookup map
+    pub fn lookup_remove(
+        &mut self,
+        key: blake3::Hash,
+        node_id: blake3::Hash,
+    ) -> Result<Option<blake3::Hash>> {
+        if let Some(s) = self.lookup.get(&key) {
+            let mut lookup_set = s.clone();
+            lookup_set.remove(&node_id);
+            if lookup_set.is_empty() {
+                self.lookup.remove(&key);
+            } else {
+                self.lookup.insert(key.clone(), lookup_set);
+            }
+        }
+
+        Ok(Some(key))
+    }
+
+    /// Verify if provided key exists and return flag if local or in network
+    pub fn contains_key(&self, key: blake3::Hash) -> Option<bool> {
+        match self.lookup.contains_key(&key) {
+            true => Some(self.map.contains_key(&key)),
+            false => None,
+        }
+    }
+
+    /// Get key from local map, acting as daemon cache
+    pub fn get(&self, key: blake3::Hash) -> Option<&Vec<u8>> {
+        self.map.get(&key)
+    }
+
+    /// Generate key request and broadcast it to the network
+    pub async fn request_key(&self, key: blake3::Hash) -> Result<()> {
+        // Verify the key exist in the lookup map.
+        let peers = match self.lookup.get(&key) {
+            Some(v) => v.clone(),
+            None => return Err(UnknownKey),
+        };
+
+        debug!("Key is in peers: {:?}", peers);
+
+        // We retrieve p2p network connected channels, to verify if we
+        // are connected to a network.
+        // Using len here because is_empty() uses unstable library feature
+        // called 'exact_size_is_empty'.
+        if self.p2p.channels().lock().await.values().len() == 0 {
+            return Err(NetworkNotConnected)
+        }
+
+        // We create a key request, and broadcast it to the network
+        // We choose last known peer as request recipient
+        let peer = peers.iter().last().unwrap().clone();
+        let request = KeyRequest::new(self.id, peer, key);
+        // TODO: ask connected peers directly, not broadcast
+        if let Err(e) = self.p2p.broadcast(request).await {
+            error!("Failed broadcasting request: {}", e);
+            return Err(e)
+        }
+
+        Ok(())
+    }
+}
+
+// Auxilary function to wait for a key response from the P2P network.
+pub async fn waiting_for_response(dht: DhtPtr) -> Result<Option<KeyResponse>> {
+    let (p2p_recv_channel, stop_signal) = {
+        let _dht = dht.read().await;
+        (_dht.p2p_recv_channel.clone(), _dht.stop_signal.clone())
+    };
+    let ex = Arc::new(async_executor::Executor::new());
+    let (timeout_s, timeout_r) = async_channel::unbounded::<()>();
+    ex.spawn(async move {
+        sleep(Duration::from_millis(REQUEST_TIMEOUT).as_secs()).await;
+        timeout_s.send(()).await.unwrap_or(());
+    })
+    .detach();
+
+    loop {
+        select! {
+            msg = p2p_recv_channel.recv().fuse() => {
+                let response = msg?;
+                return Ok(Some(response))
+            },
+            _ = stop_signal.recv().fuse() => break,
+            _ = timeout_r.recv().fuse() => break,
+        }
+    }
+    Ok(None)
+}
+
+// Auxilary function to periodically prun seen messages, based on when they were received.
+// This helps us to prevent broadcasting loops.
+async fn prune_seen_messages(dht: DhtPtr) {
+    loop {
+        sleep(SEEN_DURATION as u64).await;
+        debug!("Pruning seen messages");
+
+        let now = Utc::now().timestamp();
+
+        let mut prune = vec![];
+        let map = dht.read().await.seen.clone();
+        for (k, v) in map.iter() {
+            if now - v > SEEN_DURATION {
+                prune.push(k);
+            }
+        }
+
+        let mut map = map.clone();
+        for i in prune {
+            map.remove(i);
+        }
+
+        dht.write().await.seen = map;
+    }
+}

+ 95 - 0
src/dht/messages.rs

@@ -0,0 +1,95 @@
+use rand::Rng;
+
+use crate::{
+    net,
+    util::serial::{serialize, SerialDecodable, SerialEncodable},
+};
+
+/// This struct represents a DHT key request
+#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
+pub struct KeyRequest {
+    /// Request id    
+    pub id: blake3::Hash,
+    /// Daemon id requesting the key
+    pub from: blake3::Hash,
+    /// Daemon id holding the key
+    pub to: blake3::Hash,
+    /// Key entry
+    pub key: blake3::Hash,
+}
+
+impl KeyRequest {
+    pub fn new(from: blake3::Hash, to: blake3::Hash, key: blake3::Hash) -> Self {
+        // Generate a random id
+        let mut rng = rand::thread_rng();
+        let n: u16 = rng.gen();
+        let id = blake3::hash(&serialize(&n));
+        Self { id, from, to, key }
+    }
+}
+
+impl net::Message for KeyRequest {
+    fn name() -> &'static str {
+        "keyrequest"
+    }
+}
+
+/// This struct represents a DHT key request response
+#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
+pub struct KeyResponse {
+    /// Response id
+    pub id: blake3::Hash,
+    /// Daemon id holding the key
+    pub from: blake3::Hash,
+    /// Daemon id holding the key
+    pub to: blake3::Hash,
+    /// Key entry
+    pub key: blake3::Hash,
+    /// Key value
+    pub value: Vec<u8>,
+}
+
+impl KeyResponse {
+    pub fn new(from: blake3::Hash, to: blake3::Hash, key: blake3::Hash, value: Vec<u8>) -> Self {
+        // Generate a random id
+        let mut rng = rand::thread_rng();
+        let n: u16 = rng.gen();
+        let id = blake3::hash(&serialize(&n));
+        Self { id, from, to, key, value }
+    }
+}
+
+impl net::Message for KeyResponse {
+    fn name() -> &'static str {
+        "keyresponse"
+    }
+}
+
+/// This struct represents a lookup map request
+#[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
+pub struct LookupRequest {
+    /// Request id    
+    pub id: blake3::Hash,
+    /// Daemon id executing the request
+    pub daemon: blake3::Hash,
+    /// Key entry
+    pub key: blake3::Hash,
+    /// Request type
+    pub req_type: u8, // 0 for insert, 1 for remove
+}
+
+impl LookupRequest {
+    pub fn new(daemon: blake3::Hash, key: blake3::Hash, req_type: u8) -> Self {
+        // Generate a random id
+        let mut rng = rand::thread_rng();
+        let n: u16 = rng.gen();
+        let id = blake3::hash(&serialize(&n));
+        Self { id, daemon, key, req_type }
+    }
+}
+
+impl net::Message for LookupRequest {
+    fn name() -> &'static str {
+        "lookuprequest"
+    }
+}

+ 6 - 0
src/dht/mod.rs

@@ -0,0 +1,6 @@
+pub mod dht;
+pub use dht::{waiting_for_response, Dht, DhtPtr};
+
+mod messages;
+
+mod protocol;

+ 46 - 33
script/research/dhtd/src/protocol.rs → src/dht/protocol.rs

@@ -4,7 +4,7 @@ use async_trait::async_trait;
 use chrono::Utc;
 use log::{debug, error};
 
-use darkfi::{
+use crate::{
     net::{
         ChannelPtr, MessageSubscription, P2pPtr, ProtocolBase, ProtocolBasePtr,
         ProtocolJobsManager, ProtocolJobsManagerPtr,
@@ -12,7 +12,10 @@ use darkfi::{
     Result,
 };
 
-use crate::structures::{KeyRequest, KeyResponse, LookupRequest, StatePtr};
+use super::{
+    dht::DhtPtr,
+    messages::{KeyRequest, KeyResponse, LookupRequest},
+};
 
 pub struct Protocol {
     channel: ChannelPtr,
@@ -21,7 +24,7 @@ pub struct Protocol {
     resp_sub: MessageSubscription<KeyResponse>,
     lookup_sub: MessageSubscription<LookupRequest>,
     jobsman: ProtocolJobsManagerPtr,
-    state: StatePtr,
+    dht: DhtPtr,
     p2p: P2pPtr,
 }
 
@@ -29,7 +32,7 @@ impl Protocol {
     pub async fn init(
         channel: ChannelPtr,
         notify_queue_sender: async_channel::Sender<KeyResponse>,
-        state: StatePtr,
+        dht: DhtPtr,
         p2p: P2pPtr,
     ) -> Result<ProtocolBasePtr> {
         debug!("Adding Protocol to the protocol registry");
@@ -49,7 +52,7 @@ impl Protocol {
             resp_sub,
             lookup_sub,
             jobsman: ProtocolJobsManager::new("Protocol", channel),
-            state,
+            dht,
             p2p,
         }))
     }
@@ -69,36 +72,39 @@ impl Protocol {
             let req_copy = (*req).clone();
             debug!("Protocol::handle_receive_request(): req: {:?}", req_copy);
 
-            if self.state.read().await.seen.contains_key(&req_copy.id) {
-                debug!("Protocol::handle_receive_request(): We have already seen this request.");
-                continue
-            }
+            {
+                let dht = &mut self.dht.write().await;
+                if dht.seen.contains_key(&req_copy.id) {
+                    debug!(
+                        "Protocol::handle_receive_request(): We have already seen this request."
+                    );
+                    continue
+                }
 
-            self.state.write().await.seen.insert(req_copy.id.clone(), Utc::now().timestamp());
+                dht.seen.insert(req_copy.id.clone(), Utc::now().timestamp());
+            }
 
-            let daemon = self.state.read().await.id.to_string();
+            let daemon = self.dht.read().await.id;
             if daemon != req_copy.to {
                 if let Err(e) =
                     self.p2p.broadcast_with_exclude(req_copy.clone(), &exclude_list).await
                 {
                     error!("Protocol::handle_receive_response(): p2p broadcast fail: {}", e);
-                    continue
                 };
+                continue
             }
 
-            match self.state.read().await.map.get(&req_copy.key) {
+            match self.dht.read().await.map.get(&req_copy.key) {
                 Some(value) => {
                     let response =
                         KeyResponse::new(daemon, req_copy.from, req_copy.key, value.clone());
                     debug!("Protocol::handle_receive_request(): sending response: {:?}", response);
                     if let Err(e) = self.channel.send(response).await {
                         error!("Protocol::handle_receive_request(): p2p broadcast of response failed: {}", e);
-                        continue
                     };
                 }
                 None => {
                     error!("Protocol::handle_receive_request(): Requested key doesn't exist locally: {}", req_copy.key);
-                    continue
                 }
             }
         }
@@ -119,23 +125,28 @@ impl Protocol {
             let resp_copy = (*resp).clone();
             debug!("Protocol::handle_receive_response(): resp: {:?}", resp_copy);
 
-            if self.state.read().await.seen.contains_key(&resp_copy.id) {
-                debug!("Protocol::handle_receive_response(): We have already seen this response.");
-                continue
-            }
+            {
+                let dht = &mut self.dht.write().await;
+                if dht.seen.contains_key(&resp_copy.id) {
+                    debug!(
+                        "Protocol::handle_receive_request(): We have already seen this request."
+                    );
+                    continue
+                }
 
-            self.state.write().await.seen.insert(resp_copy.id.clone(), Utc::now().timestamp());
+                dht.seen.insert(resp_copy.id.clone(), Utc::now().timestamp());
+            }
 
-            if self.state.read().await.id.to_string() != resp_copy.to {
+            if self.dht.read().await.id != resp_copy.to {
                 if let Err(e) =
                     self.p2p.broadcast_with_exclude(resp_copy.clone(), &exclude_list).await
                 {
                     error!("Protocol::handle_receive_response(): p2p broadcast fail: {}", e);
-                    continue
                 };
+                continue
             }
 
-            self.notify_queue_sender.send(resp_copy).await?;
+            self.notify_queue_sender.send(resp_copy.clone()).await?;
         }
     }
 
@@ -159,23 +170,26 @@ impl Protocol {
                 continue
             }
 
-            if self.state.read().await.seen.contains_key(&req_copy.id) {
-                debug!(
-                    "Protocol::handle_receive_lookup_request(): We have already seen this request."
-                );
-                continue
-            }
+            {
+                let dht = &mut self.dht.write().await;
+                if dht.seen.contains_key(&req_copy.id) {
+                    debug!(
+                        "Protocol::handle_receive_request(): We have already seen this request."
+                    );
+                    continue
+                }
 
-            self.state.write().await.seen.insert(req_copy.id.clone(), Utc::now().timestamp());
+                dht.seen.insert(req_copy.id.clone(), Utc::now().timestamp());
+            }
 
             let result = match req_copy.req_type {
                 0 => self
-                    .state
+                    .dht
                     .write()
                     .await
                     .lookup_insert(req_copy.key.clone(), req_copy.daemon.clone()),
                 _ => self
-                    .state
+                    .dht
                     .write()
                     .await
                     .lookup_remove(req_copy.key.clone(), req_copy.daemon.clone()),
@@ -188,7 +202,6 @@ impl Protocol {
 
             if let Err(e) = self.p2p.broadcast_with_exclude(req_copy, &exclude_list).await {
                 error!("Protocol::handle_receive_lookup_request(): p2p broadcast fail: {}", e);
-                continue
             };
         }
     }

+ 9 - 0
src/error.rs

@@ -156,6 +156,9 @@ pub enum Error {
     #[error("Tor error: {0}")]
     TorError(String),
 
+    #[error("Node is not connected to other nodes.")]
+    NetworkNotConnected,
+
     // =============
     // Crypto errors
     // =============
@@ -318,6 +321,12 @@ pub enum Error {
 
     #[error(transparent)]
     ClientFailed(#[from] ClientFailed),
+
+    // ==============
+    // DHT errors
+    // ==============
+    #[error("Did not find key")]
+    UnknownKey,
 }
 
 /// Transaction verification errors

+ 5 - 2
src/lib.rs

@@ -10,6 +10,9 @@ pub mod consensus;
 #[cfg(feature = "crypto")]
 pub mod crypto;
 
+#[cfg(feature = "dht")]
+pub mod dht;
+
 #[cfg(feature = "crypto")]
 pub mod zk;
 
@@ -19,8 +22,8 @@ pub mod net;
 #[cfg(feature = "node")]
 pub mod node;
 
-#[cfg(feature = "wasm-runtime")]
-pub mod runtime;
+//#[cfg(feature = "wasm-runtime")]
+//pub mod runtime;
 
 #[cfg(feature = "raft")]
 pub mod raft;

+ 16 - 0
src/util/serial.rs

@@ -503,6 +503,7 @@ macro_rules! impl_vec {
 impl_vec!(SocketAddr);
 impl_vec!(Url);
 impl_vec!([u8; 32]);
+impl_vec!(blake3::Hash);
 
 impl Encodable for IpAddr {
     fn encode<S: io::Write>(&self, mut s: S) -> Result<usize> {
@@ -646,6 +647,21 @@ impl Decodable for BigUint {
     }
 }
 
+impl Encodable for blake3::Hash {
+    fn encode<S: io::Write>(&self, mut s: S) -> Result<usize> {
+        s.write_slice(self.as_bytes())?;
+        Ok(32)
+    }
+}
+
+impl Decodable for blake3::Hash {
+    fn decode<D: io::Read>(mut d: D) -> Result<Self> {
+        let mut bytes = [0u8; 32];
+        d.read_slice(&mut bytes)?;
+        Ok(bytes.into())
+    }
+}
+
 // Tuples
 macro_rules! tuple_encode {
     ($($x:ident),*) => (

+ 2 - 1
src/zk/circuit/burn_contract.rs

@@ -576,7 +576,8 @@ mod tests {
         };
 
         use plotters::prelude::*;
-        let root = BitMapBackend::new("burn_circuit_layout.png", (3840, 2160)).into_drawing_area();
+        let root =
+            BitMapBackend::new("target/burn_circuit_layout.png", (3840, 2160)).into_drawing_area();
         root.fill(&WHITE).unwrap();
         let root = root.titled("Burn Circuit Layout", ("sans-serif", 60)).unwrap();
         CircuitLayout::default().render(11, &circuit, &root).unwrap();

+ 2 - 1
src/zk/circuit/mint_contract.rs

@@ -371,7 +371,8 @@ mod tests {
         };
 
         use plotters::prelude::*;
-        let root = BitMapBackend::new("mint_circuit_layout.png", (3840, 2160)).into_drawing_area();
+        let root =
+            BitMapBackend::new("target/mint_circuit_layout.png", (3840, 2160)).into_drawing_area();
         root.fill(&WHITE).unwrap();
         let root = root.titled("Mint Circuit Layout", ("sans-serif", 60)).unwrap();
         CircuitLayout::default().render(8, &circuit, &root).unwrap();

+ 2 - 2
src/zk/gadget/arithmetic.rs

@@ -291,8 +291,8 @@ mod tests {
         };
 
         use plotters::prelude::*;
-        let root =
-            BitMapBackend::new("arithmetic_circuit_layout.png", (3840, 2160)).into_drawing_area();
+        let root = BitMapBackend::new("target/arithmetic_circuit_layout.png", (3840, 2160))
+            .into_drawing_area();
         root.fill(&WHITE).unwrap();
         let root = root.titled("Arithmetic Circuit Layout", ("sans-serif", 60)).unwrap();
         CircuitLayout::default().render(4, &circuit, &root).unwrap();

+ 0 - 246
src/zk/gadget/even_bits.rs

@@ -1,246 +0,0 @@
-use std::{marker::PhantomData, ops::Deref};
-
-use halo2_proofs::{
-    arithmetic::FieldExt,
-    circuit::{AssignedCell, Chip, Layouter, Region, Value},
-    plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector, TableColumn},
-    poly::Rotation,
-};
-
-/// Chip state is stored in a config struct. This is generated by the
-/// chip during configuration, and then stored inside the chip
-#[derive(Clone, Debug)]
-pub struct EvenBitsConfig {
-    advice: [Column<Advice>; 2],
-    even_bits: TableColumn,
-
-    s_decompose: Selector,
-}
-
-impl EvenBitsConfig {
-    pub fn load_private<F: FieldExt>(
-        &self,
-        mut layouter: impl Layouter<F>,
-        value: Value<F>,
-    ) -> Result<AssignedCell<F, F>, Error> {
-        layouter.assign_region(
-            || "load private",
-            |mut region| region.assign_advice(|| "private input", self.advice[0], 0, || value),
-        )
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct EvenBitsChip<F: FieldExt, const WORD_BITS: u32> {
-    config: EvenBitsConfig,
-    _marker: PhantomData<F>,
-}
-
-impl<F: FieldExt, const WORD_BITS: u32> Chip<F> for EvenBitsChip<F, WORD_BITS> {
-    type Config = EvenBitsConfig;
-    type Loaded = ();
-
-    fn config(&self) -> &Self::Config {
-        &self.config
-    }
-
-    fn loaded(&self) -> &Self::Loaded {
-        &()
-    }
-}
-
-impl<F: FieldExt, const WORD_BITS: u32> EvenBitsChip<F, WORD_BITS> {
-    pub fn construct(config: <Self as Chip<F>>::Config) -> Self {
-        Self { config, _marker: PhantomData }
-    }
-
-    pub fn configure(meta: &mut ConstraintSystem<F>) -> <Self as Chip<F>>::Config {
-        let advice = [meta.advice_column(), meta.advice_column()];
-        for column in &advice {
-            meta.enable_equality(*column);
-        }
-
-        let s_decompose = meta.complex_selector();
-        let even_bits = meta.lookup_table_column();
-
-        meta.create_gate("decompose", |meta| {
-            let lhs = meta.query_advice(advice[0], Rotation::cur());
-            let rhs = meta.query_advice(advice[1], Rotation::cur());
-            let out = meta.query_advice(advice[0], Rotation::next());
-            let s_decompose = meta.query_selector(s_decompose);
-
-            // Finally, we return the polynomial expressions that constrain this gate.
-            // For our multiplication gate, we only need a single polynomial constraint.
-            //
-            // The polynomial expressions returned from `create_gate` will be
-            // constrained by the proving system to equal zero.
-            vec![s_decompose * (lhs + Expression::Constant(F::from(2)) * rhs - out)]
-        });
-
-        let _ = meta.lookup(|meta| {
-            let lookup = meta.query_selector(s_decompose);
-            let a = meta.query_advice(advice[0], Rotation::cur());
-
-            vec![(lookup * a, even_bits)]
-        });
-
-        let _ = meta.lookup(|meta| {
-            let lookup = meta.query_selector(s_decompose);
-            let b = meta.query_advice(advice[1], Rotation::cur());
-
-            vec![(lookup * b, even_bits)]
-        });
-
-        EvenBitsConfig { advice, even_bits, s_decompose }
-    }
-
-    // Allocates all even bits in a table for the word size WORD_BITS.
-    // `2^(WORD_BITS/2)` rows of the constraint system
-    pub fn alloc_table(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
-        layouter.assign_table(
-            || "even bits table",
-            |mut table| {
-                for i in 0..2usize.pow(WORD_BITS / 2) {
-                    table.assign_cell(
-                        || format!("even_bits row {}", i),
-                        self.config.even_bits,
-                        i,
-                        || Value::known(F::from(even_bits_at(i) as u64)),
-                    )?;
-                }
-                Ok(())
-            },
-        )
-    }
-}
-
-fn even_bits_at(mut i: usize) -> usize {
-    let mut r = 0;
-    let mut c = 0;
-
-    while i != 0 {
-        let lower_bit = i % 2;
-        r += lower_bit * 4usize.pow(c);
-        i >>= 1;
-        c += 1;
-    }
-
-    r
-}
-
-/// A newtype of a field element containing only bits that were in the
-/// even position of the decomposed element.
-/// All odd bits will be zero.
-#[derive(Clone, Copy, Debug)]
-pub struct EvenBits<W>(pub W);
-
-impl<W> Deref for EvenBits<W> {
-    type Target = W;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-/// A newtype of a field element containing only bits thet were in the
-/// odd position of the decomposed element.
-/// All odd bits will be right shifted by 1 into even positions.
-/// All odd bits will be zero.
-#[derive(Clone, Copy, Debug)]
-pub struct OddBits<W>(pub W);
-
-impl<W> Deref for OddBits<W> {
-    type Target = W;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-pub trait EvenBitsLookup<F: FieldExt>: Chip<F> {
-    type Word;
-
-    #[allow(clippy::type_complexity)]
-    fn decompose(
-        &self,
-        layouter: impl Layouter<F>,
-        c: Self::Word,
-    ) -> Result<(EvenBits<Self::Word>, OddBits<Self::Word>), Error>;
-}
-
-impl<F: FieldExt, const WORD_BITS: u32> EvenBitsLookup<F> for EvenBitsChip<F, WORD_BITS> {
-    type Word = AssignedCell<F, F>;
-
-    fn decompose(
-        &self,
-        mut layouter: impl Layouter<F>,
-        c: Self::Word,
-    ) -> Result<(EvenBits<Self::Word>, OddBits<Self::Word>), Error> {
-        let config = self.config();
-
-        layouter.assign_region(
-            || "decompose",
-            |mut region: Region<'_, F>| {
-                config.s_decompose.enable(&mut region, 0)?;
-
-                let o_eo = c.value().cloned().map(decompose);
-                let e_cell = region
-                    .assign_advice(|| "even bits", config.advice[0], 0, || o_eo.map(|eo| *eo.0))
-                    .map(EvenBits)?;
-
-                let o_cell = region
-                    .assign_advice(|| "odd bits", config.advice[1], 0, || o_eo.map(|eo| *eo.1))
-                    .map(OddBits)?;
-
-                c.copy_advice(|| "out", &mut region, config.advice[0], 1)?;
-                Ok((e_cell, o_cell))
-            },
-        )
-    }
-}
-
-fn decompose<F: FieldExt>(word: F) -> (EvenBits<F>, OddBits<F>) {
-    assert!(word <= F::from_u128(u128::MAX));
-
-    let mut even_only = word.to_repr();
-    even_only.as_mut().iter_mut().for_each(|bits| {
-        *bits &= 0b01010101;
-    });
-
-    let mut odd_only = word.to_repr();
-    odd_only.as_mut().iter_mut().for_each(|bits| {
-        *bits &= 0b10101010;
-    });
-
-    let even_only = EvenBits(F::from_repr(even_only).unwrap());
-    let odd_only = F::from_repr(odd_only).unwrap();
-    let odds_in_even = OddBits(F::from_u128(odd_only.get_lower_128() >> 1));
-    (even_only, odds_in_even)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn even_bits_at_test() {
-        assert_eq!(0b0, even_bits_at(0));
-        assert_eq!(0b1, even_bits_at(1));
-        assert_eq!(0b100, even_bits_at(2));
-        assert_eq!(0b101, even_bits_at(3));
-    }
-
-    #[test]
-    fn decompose_even_odd_test() {
-        use pasta_curves::pallas;
-        let odds = 0xAAAA;
-        let evens = 0x5555;
-        let (e, o) = decompose(pallas::Base::from_u128(odds));
-        assert_eq!(e.get_lower_128(), 0);
-        assert_eq!(o.get_lower_128(), odds >> 1);
-        let (e, o) = decompose(pallas::Base::from_u128(evens));
-        assert_eq!(e.get_lower_128(), evens);
-        assert_eq!(o.get_lower_128(), 0);
-    }
-}
-//

+ 0 - 214
src/zk/gadget/greater_than.rs

@@ -1,214 +0,0 @@
-use std::marker::PhantomData;
-
-use halo2_proofs::{
-    arithmetic::FieldExt,
-    circuit::{AssignedCell, Chip, Layouter, Region, Value},
-    plonk::{Advice, Column, ConstraintSystem, Error, Expression, Instance, Selector},
-    poly::Rotation,
-};
-
-use pasta_curves::pallas;
-
-#[derive(Clone, Debug)]
-pub struct GreaterThanConfig {
-    pub advice: [Column<Advice>; 2],
-    pub instance: Column<Instance>,
-    s_gt: Selector,
-}
-
-pub struct GreaterThanChip<F: FieldExt, const WORD_BITS: u32> {
-    config: GreaterThanConfig,
-    _marker: PhantomData<F>,
-}
-
-impl<F: FieldExt, const WORD_BITS: u32> Chip<F> for GreaterThanChip<F, WORD_BITS> {
-    type Config = GreaterThanConfig;
-    type Loaded = ();
-
-    fn config(&self) -> &Self::Config {
-        &self.config
-    }
-
-    fn loaded(&self) -> &Self::Loaded {
-        &()
-    }
-}
-
-impl<F: FieldExt, const WORD_BITS: u32> GreaterThanChip<F, WORD_BITS> {
-    pub fn construct(config: <Self as Chip<F>>::Config) -> Self {
-        Self { config, _marker: PhantomData }
-    }
-
-    /*
-    pub fn configure(meta: &mut ConstraintSystem<F>) -> <Self as Chip<F>>::Config {
-        //let constant = meta.fixed_column();
-        //meta.enable_constant(constant);
-
-        let advice = [meta.advice_column(), meta.advice_column()];
-
-        for column in &advice {
-            meta.enable_equality(*column);
-        }
-
-        let s_gt = meta.selector();
-
-        meta.create_gate("greater than", |meta| {
-            let lhs = meta.query_advice(advice[0], Rotation::cur());
-            let rhs = meta.query_advice(advice[1], Rotation::cur());
-
-            // This value is `lhs - rhs` if `lhs !> rhs` and `2^W - (lhs - rhs)` if `lhs > rhs`
-            let helper = meta.query_advice(advice[0], Rotation::next());
-
-            let is_greater = meta.query_advice(advice[1], Rotation::next());
-            let s_gt = meta.query_selector(s_gt);
-
-            vec![
-                s_gt * (lhs - rhs + helper -
-                    Expression::Constant(F::from(2_u64.pow(WORD_BITS))) * is_greater),
-            ]
-        });
-
-        GreaterThanConfig { advice, s_gt }
-    }
-
-    pub fn configure(meta: &mut ConstraintSystem<F>) -> <Self as Chip<F>>::Config {
-        let advice = [meta.advice_column(), meta.advice_column()];
-
-
-        let s_gt = meta.selector();
-
-        meta.create_gate("greater than", |meta| {
-            let lhs = meta.query_advice(advice[0], Rotation::cur());
-            let rhs = meta.query_advice(advice[1], Rotation::cur());
-
-            // This value is `lhs - rhs` if `lhs !> rhs` and `2^W - (lhs - rhs)` if `lhs > rhs`
-            let helper = meta.query_advice(advice[0], Rotation::next());
-
-            let is_greater = meta.query_advice(advice[1], Rotation::next());
-            let s_gt = meta.query_selector(s_gt);
-
-            vec![
-                s_gt * (lhs - rhs + helper -
-                    Expression::Constant(F::from(2_u64.pow(WORD_BITS))) * is_greater),
-            ]
-        });
-
-        GreaterThanConfig { advice, s_gt }
-    }
-     */
-    pub fn configure(
-        meta: &mut ConstraintSystem<F>,
-        advice: [Column<Advice>; 2],
-        instance: Column<Instance>,
-    ) -> <Self as Chip<F>>::Config {
-        for column in &advice {
-            meta.enable_equality(*column);
-        }
-
-        let s_gt = meta.selector();
-
-        meta.create_gate("greater than", |meta| {
-            let lhs = meta.query_advice(advice[0], Rotation::cur());
-            let rhs = meta.query_advice(advice[1], Rotation::cur());
-
-            // This value is `lhs - rhs` if `lhs !> rhs` and `2^W - (lhs - rhs)` if `lhs > rhs`
-            let helper = meta.query_advice(advice[0], Rotation::next());
-
-            let is_greater = meta.query_advice(advice[1], Rotation::next());
-            let s_gt = meta.query_selector(s_gt);
-
-            vec![
-                s_gt * (lhs - rhs + helper -
-                    Expression::Constant(F::from(2_u64.pow(WORD_BITS))) * is_greater),
-            ]
-        });
-
-        GreaterThanConfig { advice, instance, s_gt }
-    }
-}
-
-pub trait GreaterThanInstruction<F: FieldExt>: Chip<F> {
-    type Word;
-
-    fn greater_than(
-        &self,
-        layouter: impl Layouter<F>,
-        a: Self::Word,
-        b: Self::Word,
-    ) -> Result<(Self::Word, Self::Word), Error>;
-}
-
-#[derive(Clone, Debug)]
-pub struct Word<F: FieldExt>(pub AssignedCell<F, F>);
-
-impl From<AssignedCell<pallas::Base, pallas::Base>> for Word<pallas::Base> {
-    fn from(cell: AssignedCell<pallas::Base, pallas::Base>) -> Self {
-        Self(cell)
-    }
-}
-
-impl<const WORD_BITS: u32> GreaterThanInstruction<pallas::Base>
-    for GreaterThanChip<pallas::Base, WORD_BITS>
-{
-    type Word = Word<pallas::Base>;
-
-    fn greater_than(
-        &self,
-        mut layouter: impl Layouter<pallas::Base>,
-        a: Self::Word,
-        b: Self::Word,
-    ) -> Result<(Self::Word, Self::Word), Error> {
-        let config = self.config();
-
-        layouter.assign_region(
-            || "greater than",
-            |mut region: Region<'_, pallas::Base>| {
-                config.s_gt.enable(&mut region, 0)?;
-
-                a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?;
-                b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?;
-
-                let helper_cell = region
-                    .assign_advice(
-                        || "max minus diff",
-                        config.advice[0],
-                        1,
-                        || {
-                            let is_greater = a.0.value().inner().unwrap().get_lower_128() >
-                                b.0.value().get_lower_128();
-                            a.0.value().and_then(|a| {
-                                b.0.value().map(|b| {
-                                    let x = *a - *b;
-
-                                    (if is_greater {
-                                        pallas::Base::from(2_u64.pow(WORD_BITS))
-                                    } else {
-                                        pallas::Base::zero()
-                                    }) - x
-                                })
-                            })
-                        },
-                    )
-                    .map(Word)?;
-
-                let is_greater_cell = region
-                    .assign_advice(
-                        || "is greater",
-                        config.advice[1],
-                        1,
-                        || {
-                            let is_greater = a.0.value().inner().unwrap().get_lower_128() >
-                                b.0.value().get_lower_128();
-                            Value::known(if is_greater {
-                                pallas::Base::one()
-                            } else {
-                                pallas::Base::zero()
-                            })
-                        },
-                    )
-                    .map(Word)?;
-                Ok((helper_cell, is_greater_cell))
-            },
-        )
-    }
-}

+ 0 - 0
src/zk/gadget/cmp.rs → src/zk/gadget/is_zero.rs


+ 336 - 0
src/zk/gadget/less_than.rs

@@ -0,0 +1,336 @@
+use halo2_proofs::{
+    arithmetic::FieldExt,
+    circuit::{AssignedCell, Chip, Layouter, Region, Value},
+    pasta::pallas,
+    plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector, TableColumn},
+    poly::Rotation,
+};
+
+use super::native_range_check::{NativeRangeCheckChip, NativeRangeCheckConfig};
+
+#[derive(Clone, Debug)]
+pub struct LessThanConfig<
+    const WINDOW_SIZE: usize,
+    const NUM_OF_BITS: usize,
+    const NUM_OF_WINDOWS: usize,
+> {
+    pub s_lt: Selector,
+    pub a: Column<Advice>,
+    pub b: Column<Advice>,
+    pub a_offset: Column<Advice>,
+    pub range_a_config: NativeRangeCheckConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>,
+    pub range_a_offset_config: NativeRangeCheckConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>,
+    pub k_values_table: TableColumn,
+}
+
+#[derive(Clone, Debug)]
+pub struct LessThanChip<
+    const WINDOW_SIZE: usize,
+    const NUM_OF_BITS: usize,
+    const NUM_OF_WINDOWS: usize,
+> {
+    config: LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>,
+}
+
+impl<const WINDOW_SIZE: usize, const NUM_OF_BITS: usize, const NUM_OF_WINDOWS: usize>
+    Chip<pallas::Base> for LessThanChip<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>
+{
+    type Config = LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>;
+    type Loaded = ();
+
+    fn config(&self) -> &Self::Config {
+        &self.config
+    }
+
+    fn loaded(&self) -> &Self::Loaded {
+        &()
+    }
+}
+
+impl<const WINDOW_SIZE: usize, const NUM_OF_BITS: usize, const NUM_OF_WINDOWS: usize>
+    LessThanChip<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>
+{
+    pub fn construct(config: LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>) -> Self {
+        Self { config }
+    }
+
+    pub fn configure(
+        meta: &mut ConstraintSystem<pallas::Base>,
+        a: Column<Advice>,
+        b: Column<Advice>,
+        a_offset: Column<Advice>,
+        k_values_table: TableColumn,
+    ) -> LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS> {
+        let s_lt = meta.selector();
+
+        meta.enable_equality(a);
+        meta.enable_equality(b);
+        meta.enable_equality(a_offset);
+
+        // configure range check for `a` and `offset`
+        let z = meta.advice_column();
+        let range_a_config =
+            NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::configure(
+                meta,
+                z,
+                k_values_table,
+            );
+        let z = meta.advice_column();
+        let range_a_offset_config =
+            NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::configure(
+                meta,
+                z,
+                k_values_table,
+            );
+
+        let config = LessThanConfig {
+            s_lt,
+            a,
+            b,
+            a_offset,
+            range_a_config,
+            range_a_offset_config,
+            k_values_table,
+        };
+
+        meta.create_gate("a_offset - 2^m + b - a", |meta| {
+            let s_lt = meta.query_selector(config.s_lt);
+            let a = meta.query_advice(config.a, Rotation::cur());
+            let b = meta.query_advice(config.b, Rotation::cur());
+            let a_offset = meta.query_advice(config.a_offset, Rotation::cur());
+            let two_pow_m =
+                Expression::Constant(pallas::Base::from(2).pow(&[NUM_OF_BITS as u64, 0, 0, 0]));
+            // a_offset - 2^m + b - a = 0
+            vec![s_lt * (a_offset - two_pow_m + b - a)]
+        });
+
+        config
+    }
+
+    pub fn witness_less_than(
+        &self,
+        mut layouter: impl Layouter<pallas::Base>,
+        a: Value<pallas::Base>,
+        b: Value<pallas::Base>,
+        offset: usize,
+    ) -> Result<(), Error> {
+        let (a, _, a_offset) = layouter.assign_region(
+            || "a less than b",
+            |mut region: Region<'_, pallas::Base>| {
+                let a = region.assign_advice(|| "a", self.config.a, offset, || a)?;
+                let b = region.assign_advice(|| "b", self.config.b, offset, || b)?;
+                let a_offset = self.less_than(region, a.clone(), b.clone(), offset)?;
+                Ok((a, b, a_offset))
+            },
+        )?;
+
+        self.less_than_range_check(layouter, a, a_offset)?;
+
+        Ok(())
+    }
+
+    pub fn copy_less_than(
+        &self,
+        mut layouter: impl Layouter<pallas::Base>,
+        a: AssignedCell<pallas::Base, pallas::Base>,
+        b: AssignedCell<pallas::Base, pallas::Base>,
+        offset: usize,
+    ) -> Result<(), Error> {
+        let (a, _, a_offset) = layouter.assign_region(
+            || "a less than b",
+            |mut region: Region<'_, pallas::Base>| {
+                let a = a.copy_advice(|| "a", &mut region, self.config.a, offset)?;
+                let b = b.copy_advice(|| "b", &mut region, self.config.b, offset)?;
+                let a_offset = self.less_than(region, a.clone(), b.clone(), offset)?;
+                Ok((a, b, a_offset))
+            },
+        )?;
+
+        self.less_than_range_check(layouter, a, a_offset)?;
+
+        Ok(())
+    }
+
+    pub fn less_than_range_check(
+        &self,
+        mut layouter: impl Layouter<pallas::Base>,
+        a: AssignedCell<pallas::Base, pallas::Base>,
+        a_offset: AssignedCell<pallas::Base, pallas::Base>,
+    ) -> Result<(), Error> {
+        let range_a_chip =
+            NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::construct(
+                self.config.range_a_config.clone(),
+            );
+        let range_a_offset_chip =
+            NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::construct(
+                self.config.range_a_offset_config.clone(),
+            );
+
+        range_a_chip.copy_range_check(layouter.namespace(|| "a copy_range_check"), a)?;
+        range_a_offset_chip
+            .copy_range_check(layouter.namespace(|| "a_offset copy_range_check"), a_offset)?;
+
+        Ok(())
+    }
+
+    pub fn less_than(
+        &self,
+        mut region: Region<'_, pallas::Base>,
+        a: AssignedCell<pallas::Base, pallas::Base>,
+        b: AssignedCell<pallas::Base, pallas::Base>,
+        offset: usize,
+    ) -> Result<AssignedCell<pallas::Base, pallas::Base>, Error> {
+        // enable `less_than` selector
+        self.config.s_lt.enable(&mut region, offset)?;
+
+        // assign `a + offset`
+        let two_pow_m = pallas::Base::from(2).pow(&[NUM_OF_BITS as u64, 0, 0, 0]);
+        let a_offset = a.value().zip(b.value()).map(|(a, b)| *a + (two_pow_m - b));
+        let a_offset =
+            region.assign_advice(|| "a_offset", self.config.a_offset, offset, || a_offset)?;
+
+        Ok(a_offset)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use halo2_proofs::{
+        circuit::{floor_planner, Value},
+        dev::{CircuitLayout, MockProver},
+        pasta::pallas,
+        plonk::Circuit,
+    };
+
+    macro_rules! test_circuit {
+        ($window_size:expr, $num_bits:expr, $num_windows:expr) => {
+            #[derive(Default)]
+            struct LessThanCircuit {
+                a: Value<pallas::Base>,
+                b: Value<pallas::Base>,
+            }
+
+            impl Circuit<pallas::Base> for LessThanCircuit {
+                type Config =
+                    (LessThanConfig<$window_size, $num_bits, $num_windows>, Column<Advice>);
+                type FloorPlanner = floor_planner::V1;
+
+                fn without_witnesses(&self) -> Self {
+                    Self { a: Value::unknown(), b: Value::unknown() }
+                }
+
+                fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
+                    let w = meta.advice_column();
+                    meta.enable_equality(w);
+
+                    let a = meta.advice_column();
+                    let b = meta.advice_column();
+                    let a_offset = meta.advice_column();
+
+                    let k_values_table = meta.lookup_table_column();
+
+                    let constants = meta.fixed_column();
+                    meta.enable_constant(constants);
+
+                    (
+                        LessThanChip::<$window_size, $num_bits, $num_windows>::configure(
+                            meta,
+                            a,
+                            b,
+                            a_offset,
+                            k_values_table,
+                        ),
+                        w,
+                    )
+                }
+
+                fn synthesize(
+                    &self,
+                    config: Self::Config,
+                    mut layouter: impl Layouter<pallas::Base>,
+                ) -> Result<(), Error> {
+                    let less_than_chip =
+                        LessThanChip::<$window_size, $num_bits, $num_windows>::construct(
+                            config.0.clone(),
+                        );
+
+                    NativeRangeCheckChip::<$window_size, $num_bits, $num_windows>::load_k_table(
+                        &mut layouter,
+                        config.0.k_values_table,
+                    )?;
+
+                    less_than_chip.witness_less_than(
+                        layouter.namespace(|| "a < b"),
+                        self.a,
+                        self.b,
+                        0,
+                    )?;
+
+                    Ok(())
+                }
+            }
+        };
+    }
+
+    #[test]
+    fn less_than_64() {
+        test_circuit!(3, 64, 22);
+        let k = 5;
+
+        let valid_a_vals = vec![pallas::Base::from(13), pallas::Base::zero(), pallas::Base::one()];
+        let valid_b_vals = vec![
+            pallas::Base::from(15),
+            pallas::Base::from(u64::MAX),
+            pallas::Base::from(rand::random::<u64>()),
+        ];
+
+        let invalid_a_vals = vec![
+            pallas::Base::from(14),
+            pallas::Base::from(u64::MAX),
+            pallas::Base::zero(),
+            pallas::Base::one(),
+            pallas::Base::from(u64::MAX),
+        ];
+        let invalid_b_vals = vec![
+            pallas::Base::from(11),
+            pallas::Base::zero(),
+            pallas::Base::zero(),
+            pallas::Base::one(),
+            pallas::Base::from(u64::MAX),
+        ];
+
+        use plotters::prelude::*;
+        let circuit = LessThanCircuit {
+            a: Value::known(pallas::Base::zero()),
+            b: Value::known(pallas::Base::one()),
+        };
+        let root = BitMapBackend::new("target/lessthan_circuit_layout.png", (3840, 2160))
+            .into_drawing_area();
+        CircuitLayout::default().render(k, &circuit, &root).unwrap();
+
+        for i in 0..valid_a_vals.len() {
+            let a = valid_a_vals[i];
+            let b = valid_b_vals[i];
+
+            println!("64 bit (valid) {:?} < {:?} check", a, b);
+
+            let circuit = LessThanCircuit { a: Value::known(a), b: Value::known(b) };
+
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            prover.assert_satisfied();
+        }
+
+        for i in 0..invalid_a_vals.len() {
+            let a = invalid_a_vals[i];
+            let b = invalid_b_vals[i];
+
+            println!("64 bit (invalid) {:?} < {:?} check", a, b);
+
+            let circuit = LessThanCircuit { a: Value::known(a), b: Value::known(b) };
+
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            assert!(prover.verify().is_err())
+        }
+    }
+}

+ 7 - 7
src/zk/gadget/mod.rs

@@ -1,11 +1,11 @@
-/// Scalar arithmetic
+/// Base field scalar arithmetic
 pub mod arithmetic;
 
-/// Even-bits lookup table
-pub mod even_bits;
+/// Field-native range check gadget;
+pub mod native_range_check;
 
-// Greater than comparison gadget;
-//pub mod greater_than;
+// Field-native less than comparison gadget
+pub mod less_than;
 
-/// Comparison gadget
-pub mod cmp;
+/// is_zero comparison gadget
+pub mod is_zero;

+ 424 - 0
src/zk/gadget/native_range_check.rs

@@ -0,0 +1,424 @@
+use group::ff::{Field, PrimeFieldBits};
+use halo2_proofs::{
+    circuit::{AssignedCell, Chip, Layouter, Region, Value},
+    pasta::pallas,
+    plonk,
+    plonk::{Advice, Column, ConstraintSystem, Selector, TableColumn},
+    poly::Rotation,
+};
+
+#[derive(Clone, Debug)]
+pub struct NativeRangeCheckConfig<
+    const WINDOW_SIZE: usize,
+    const NUM_BITS: usize,
+    const NUM_WINDOWS: usize,
+> {
+    pub z: Column<Advice>,
+    pub s_rc: Selector,
+    pub k_values_table: TableColumn,
+}
+
+#[derive(Clone, Debug)]
+pub struct NativeRangeCheckChip<
+    const WINDOW_SIZE: usize,
+    const NUM_BITS: usize,
+    const NUM_WINDOWS: usize,
+> {
+    config: NativeRangeCheckConfig<WINDOW_SIZE, NUM_BITS, NUM_WINDOWS>,
+}
+
+impl<const WINDOW_SIZE: usize, const NUM_BITS: usize, const NUM_WINDOWS: usize> Chip<pallas::Base>
+    for NativeRangeCheckChip<WINDOW_SIZE, NUM_BITS, NUM_WINDOWS>
+{
+    type Config = NativeRangeCheckConfig<WINDOW_SIZE, NUM_BITS, NUM_WINDOWS>;
+    type Loaded = ();
+
+    fn config(&self) -> &Self::Config {
+        &self.config
+    }
+
+    fn loaded(&self) -> &Self::Loaded {
+        &()
+    }
+}
+
+impl<const WINDOW_SIZE: usize, const NUM_BITS: usize, const NUM_WINDOWS: usize>
+    NativeRangeCheckChip<WINDOW_SIZE, NUM_BITS, NUM_WINDOWS>
+{
+    pub fn construct(config: NativeRangeCheckConfig<WINDOW_SIZE, NUM_BITS, NUM_WINDOWS>) -> Self {
+        Self { config }
+    }
+
+    pub fn configure(
+        meta: &mut ConstraintSystem<pallas::Base>,
+        z: Column<Advice>,
+        k_values_table: TableColumn,
+    ) -> NativeRangeCheckConfig<WINDOW_SIZE, NUM_BITS, NUM_WINDOWS> {
+        // Enable permutation on z column
+        meta.enable_equality(z);
+
+        let s_rc = meta.complex_selector();
+
+        meta.lookup(|meta| {
+            let s_rc = meta.query_selector(s_rc);
+            let z_curr = meta.query_advice(z, Rotation::cur());
+            let z_next = meta.query_advice(z, Rotation::next());
+
+            //    z_next = (z_curr - k_i) / 2^K
+            // => k_i = z_curr - (z_next * 2^K)
+            vec![(s_rc * (z_curr - z_next * pallas::Base::from(1 << WINDOW_SIZE)), k_values_table)]
+        });
+
+        NativeRangeCheckConfig { z, s_rc, k_values_table }
+    }
+
+    /// `k_values_table` should be reused across different chips
+    /// which is why we don't limit it to a specific instance.
+    pub fn load_k_table(
+        layouter: &mut impl Layouter<pallas::Base>,
+        k_values_table: TableColumn,
+    ) -> Result<(), plonk::Error> {
+        layouter.assign_table(
+            || format!("{} window table", WINDOW_SIZE),
+            |mut table| {
+                for index in 0..(1 << WINDOW_SIZE) {
+                    table.assign_cell(
+                        || format!("{} window assign", WINDOW_SIZE),
+                        k_values_table,
+                        index,
+                        || Value::known(pallas::Base::from(index as u64)),
+                    )?;
+                }
+                Ok(())
+            },
+        )
+    }
+
+    fn decompose_value(value: &pallas::Base) -> Vec<[bool; WINDOW_SIZE]> {
+        let padding = (WINDOW_SIZE - NUM_BITS % WINDOW_SIZE) % WINDOW_SIZE;
+
+        let bits: Vec<bool> = value
+            .to_le_bits()
+            .into_iter()
+            .take(NUM_BITS)
+            .chain(std::iter::repeat(false).take(padding))
+            .collect();
+        assert_eq!(bits.len(), NUM_BITS + padding);
+
+        bits.chunks_exact(WINDOW_SIZE)
+            .map(|x| {
+                let mut chunks = [false; WINDOW_SIZE];
+                chunks.copy_from_slice(x);
+                chunks
+            })
+            .collect()
+    }
+
+    // TODO: strict bool
+    pub fn decompose(
+        &self,
+        region: &mut Region<'_, pallas::Base>,
+        z_0: AssignedCell<pallas::Base, pallas::Base>,
+        offset: usize,
+    ) -> Result<(), plonk::Error> {
+        assert!(WINDOW_SIZE * NUM_WINDOWS < NUM_BITS + WINDOW_SIZE);
+
+        // Enable selectors
+        for index in 0..NUM_WINDOWS {
+            self.config.s_rc.enable(region, index + offset)?;
+        }
+
+        let mut z_values: Vec<AssignedCell<pallas::Base, pallas::Base>> = vec![z_0.clone()];
+        let mut z = z_0.clone();
+        let decomposed_chunks = z_0.value().map(Self::decompose_value).transpose_vec(NUM_WINDOWS);
+
+        let two_pow_k_inverse =
+            Value::known(pallas::Base::from(1 << WINDOW_SIZE as u64).invert().unwrap());
+
+        for (i, chunk) in decomposed_chunks.iter().enumerate() {
+            let z_next = {
+                let z_curr = z.value().copied();
+                let chunk_value = chunk.map(|c| {
+                    pallas::Base::from(c.iter().rev().fold(0, |acc, c| (acc << 1) + *c as u64))
+                });
+                // z_next = (z_curr - k_i) / 2^K
+                let z_next = (z_curr - chunk_value) * two_pow_k_inverse;
+                region.assign_advice(
+                    || format!("z_{}", i + offset + 1),
+                    self.config.z,
+                    i + offset + 1,
+                    || z_next,
+                )?
+            };
+            z_values.push(z_next.clone());
+            z = z_next.clone();
+        }
+
+        assert!(z_values.len() == NUM_WINDOWS + 1);
+        region.constrain_constant(z_values.last().unwrap().cell(), pallas::Base::zero())?;
+        Ok(())
+    }
+
+    pub fn witness_range_check(
+        &self,
+        mut layouter: impl Layouter<pallas::Base>,
+        value: Value<pallas::Base>,
+    ) -> Result<(), plonk::Error> {
+        layouter.assign_region(
+            || format!("witness {}-bit native range check", NUM_BITS),
+            |mut region: Region<'_, pallas::Base>| {
+                let z_0 = region.assign_advice(|| "z_0", self.config.z, 0, || value)?;
+                self.decompose(&mut region, z_0, 0)?;
+                Ok(())
+            },
+        )
+    }
+
+    pub fn copy_range_check(
+        &self,
+        mut layouter: impl Layouter<pallas::Base>,
+        value: AssignedCell<pallas::Base, pallas::Base>,
+    ) -> Result<(), plonk::Error> {
+        layouter.assign_region(
+            || format!("copy {}-bit native range check", NUM_BITS),
+            |mut region: Region<'_, pallas::Base>| {
+                let z_0 = value.copy_advice(|| "z_0", &mut region, self.config.z, 0)?;
+                self.decompose(&mut region, z_0, 0)?;
+                Ok(())
+            },
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::zk::assign_free_advice;
+    use group::ff::PrimeField;
+    use halo2_proofs::{
+        circuit::floor_planner,
+        dev::{CircuitLayout, MockProver},
+        plonk::Circuit,
+    };
+    use pasta_curves::arithmetic::FieldExt;
+
+    macro_rules! test_circuit {
+        ($window_size:expr, $num_bits:expr, $num_windows:expr) => {
+            #[derive(Default)]
+            struct RangeCheckCircuit {
+                a: Value<pallas::Base>,
+            }
+
+            impl Circuit<pallas::Base> for RangeCheckCircuit {
+                type Config =
+                    (NativeRangeCheckConfig<$window_size, $num_bits, $num_windows>, Column<Advice>);
+                type FloorPlanner = floor_planner::V1;
+
+                fn without_witnesses(&self) -> Self {
+                    Self::default()
+                }
+
+                fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
+                    let w = meta.advice_column();
+                    meta.enable_equality(w);
+                    let z = meta.advice_column();
+                    let table_column = meta.lookup_table_column();
+
+                    let constants = meta.fixed_column();
+                    meta.enable_constant(constants);
+                    (
+                        NativeRangeCheckChip::<$window_size, $num_bits, $num_windows>::configure(
+                            meta,
+                            z,
+                            table_column,
+                        ),
+                        w,
+                    )
+                }
+
+                fn synthesize(
+                    &self,
+                    config: Self::Config,
+                    mut layouter: impl Layouter<pallas::Base>,
+                ) -> Result<(), plonk::Error> {
+                    let rangecheck_chip =
+                        NativeRangeCheckChip::<$window_size, $num_bits, $num_windows>::construct(
+                            config.0.clone(),
+                        );
+                    NativeRangeCheckChip::<$window_size, $num_bits, $num_windows>::load_k_table(
+                        &mut layouter,
+                        config.0.k_values_table,
+                    )?;
+
+                    let a = assign_free_advice(layouter.namespace(|| "load a"), config.1, self.a)?;
+                    rangecheck_chip
+                        .copy_range_check(layouter.namespace(|| "copy a and range check"), a)?;
+
+                    rangecheck_chip.witness_range_check(
+                        layouter.namespace(|| "witness a and range check"),
+                        self.a,
+                    )?;
+
+                    Ok(())
+                }
+            }
+        };
+    }
+
+    // cargo test --release --all-features --lib native_range_check -- --nocapture
+    #[test]
+    fn native_range_check_64() {
+        test_circuit!(3, 64, 22);
+        let k = 6;
+
+        let valid_values = vec![
+            pallas::Base::zero(),
+            pallas::Base::one(),
+            pallas::Base::from(u64::MAX),
+            pallas::Base::from(rand::random::<u64>()),
+        ];
+
+        let invalid_values = vec![
+            -pallas::Base::one(),
+            pallas::Base::from_u128(u64::MAX as u128 + 1),
+            -pallas::Base::from_u128(u64::MAX as u128 + 1),
+            pallas::Base::from_u128(rand::random::<u128>()),
+            // The following two are valid
+            // 2 = -28948022309329048855892746252171976963363056481941560715954676764349967630335
+            //-pallas::Base::from_str_vartime(
+            //    "28948022309329048855892746252171976963363056481941560715954676764349967630335",
+            //)
+            //.unwrap(),
+            // 1 = -28948022309329048855892746252171976963363056481941560715954676764349967630336
+            //-pallas::Base::from_str_vartime(
+            //    "28948022309329048855892746252171976963363056481941560715954676764349967630336",
+            //)
+            //.unwrap(),
+        ];
+
+        use plotters::prelude::*;
+        let circuit = RangeCheckCircuit { a: Value::known(pallas::Base::one()) };
+        let root =
+            BitMapBackend::new("target/native_range_check_64_circuit_layout.png", (3840, 2160))
+                .into_drawing_area();
+        root.fill(&WHITE).unwrap();
+        let root =
+            root.titled("64-bit Native Range Check Circuit Layout", ("sans-serif", 60)).unwrap();
+        CircuitLayout::default().render(k, &circuit, &root).unwrap();
+
+        for i in valid_values {
+            println!("64-bit (valid) range check for {:?}", i);
+            let circuit = RangeCheckCircuit { a: Value::known(i) };
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            prover.assert_satisfied();
+            println!("Constraints satisfied");
+        }
+
+        for i in invalid_values {
+            println!("64-bit (invalid) range check for {:?}", i);
+            let circuit = RangeCheckCircuit { a: Value::known(i) };
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            assert!(prover.verify().is_err());
+        }
+    }
+
+    #[test]
+    fn native_range_check_128() {
+        test_circuit!(3, 128, 43);
+        let k = 7;
+
+        let valid_values = vec![
+            pallas::Base::zero(),
+            pallas::Base::one(),
+            pallas::Base::from_u128(u128::MAX),
+            pallas::Base::from_u128(rand::random::<u128>()),
+        ];
+
+        let invalid_values = vec![
+            -pallas::Base::one(),
+            pallas::Base::from_u128(u128::MAX) + pallas::Base::one(),
+            -pallas::Base::from_u128(u128::MAX) + pallas::Base::one(),
+            -pallas::Base::from_u128(u128::MAX),
+        ];
+
+        use plotters::prelude::*;
+        let circuit = RangeCheckCircuit { a: Value::known(pallas::Base::one()) };
+        let root =
+            BitMapBackend::new("target/native_range_check_128_circuit_layout.png", (3840, 2160))
+                .into_drawing_area();
+        root.fill(&WHITE).unwrap();
+        let root =
+            root.titled("128-bit Native Range Check Circuit Layout", ("sans-serif", 60)).unwrap();
+        CircuitLayout::default().render(k, &circuit, &root).unwrap();
+
+        for i in valid_values {
+            println!("128-bit (valid) range check for {:?}", i);
+            let circuit = RangeCheckCircuit { a: Value::known(i) };
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            prover.assert_satisfied();
+            println!("Constraints satisfied");
+        }
+
+        for i in invalid_values {
+            println!("128-bit (invalid) range check for {:?}", i);
+            let circuit = RangeCheckCircuit { a: Value::known(i) };
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            assert!(prover.verify().is_err());
+        }
+    }
+
+    #[test]
+    fn native_range_check_253() {
+        test_circuit!(3, 253, 85);
+        let k = 8;
+
+        let valid_values = vec![
+            pallas::Base::zero(),
+            pallas::Base::one(),
+            // 2^253 - 1
+            pallas::Base::from_str_vartime(
+                "14474011154664524427946373126085988481658748083205070504932198000989141204991",
+            )
+            .unwrap(),
+            // 2^253 / 2
+            pallas::Base::from_str_vartime(
+                "7237005577332262213973186563042994240829374041602535252466099000494570602496",
+            )
+            .unwrap(),
+        ];
+
+        let invalid_values = vec![
+            -pallas::Base::one(),
+            // p - 1
+            pallas::Base::from_str_vartime(
+                "28948022309329048855892746252171976963363056481941560715954676764349967630336",
+            )
+            .unwrap(),
+        ];
+
+        use plotters::prelude::*;
+        let circuit = RangeCheckCircuit { a: Value::known(pallas::Base::one()) };
+        let root =
+            BitMapBackend::new("target/native_range_check_253_circuit_layout.png", (3840, 2160))
+                .into_drawing_area();
+        root.fill(&WHITE).unwrap();
+        let root =
+            root.titled("253-bit Native Range Check Circuit Layout", ("sans-serif", 60)).unwrap();
+        CircuitLayout::default().render(k, &circuit, &root).unwrap();
+
+        for i in valid_values {
+            println!("253-bit (valid) range check for {:?}", i);
+            let circuit = RangeCheckCircuit { a: Value::known(i) };
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            prover.assert_satisfied();
+            println!("Constraints satisfied");
+        }
+
+        for i in invalid_values {
+            println!("253-bit (invalid) range check for {:?}", i);
+            let circuit = RangeCheckCircuit { a: Value::known(i) };
+            let prover = MockProver::run(k, &circuit, vec![]).unwrap();
+            assert!(prover.verify().is_err());
+        }
+    }
+}

+ 1 - 30
src/zk/vm.rs

@@ -24,10 +24,7 @@ use halo2_proofs::{
 use log::debug;
 use pasta_curves::{group::Curve, pallas, Fp};
 
-use super::gadget::{
-    arithmetic::{ArithChip, ArithConfig, ArithInstruction},
-    even_bits::{EvenBitsChip, EvenBitsConfig},
-};
+use super::gadget::arithmetic::{ArithChip, ArithConfig, ArithInstruction};
 
 use super::assign_free_advice;
 pub use super::vm_stack::{StackVar, Witness};
@@ -51,8 +48,6 @@ pub struct VmConfig {
     _sinsemilla_cfg2: SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
     poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
     arith_config: ArithConfig,
-    evenbits_config: EvenBitsConfig,
-    //greaterthan_config: GreaterThanConfig,
 }
 
 impl VmConfig {
@@ -93,14 +88,6 @@ impl VmConfig {
     fn arithmetic_chip(&self) -> ArithChip {
         ArithChip::construct(self.arith_config.clone())
     }
-
-    fn evenbits_chip(&self) -> EvenBitsChip<pallas::Base, 24> {
-        EvenBitsChip::construct(self.evenbits_config.clone())
-    }
-
-    //fn greaterthan_chip(&self) -> GreaterThanChip<pallas::Base, 24> {
-    //  GreaterThanChip::construct(self.greaterthan_config.clone())
-    //    }
 }
 
 #[derive(Clone, Default)]
@@ -200,13 +187,6 @@ impl Circuit<pallas::Base> for ZkCircuit {
         // Configuration for the Arithmetic chip
         let arith_config = ArithChip::configure(meta, advices[7], advices[8], advices[6]);
 
-        // Configuration for the EvenBits chip
-        let evenbits_config = EvenBitsChip::<pallas::Base, 24>::configure(meta);
-
-        // Configuration for the GreaterThan chip
-        //let greaterthan_config =
-        //            GreaterThanChip::<pallas::Base, 24>::configure(meta, [advices[8], advices[9]], primary);
-
         // Configuration for a Sinsemilla hash instantiation and a
         // Merkle hash instantiation using this Sinsemilla instance.
         // Since the Sinsemilla config uses only 5 advice columns,
@@ -247,8 +227,6 @@ impl Circuit<pallas::Base> for ZkCircuit {
             _sinsemilla_cfg2,
             poseidon_config,
             arith_config,
-            evenbits_config,
-            //greaterthan_config,
         }
     }
 
@@ -274,13 +252,6 @@ impl Circuit<pallas::Base> for ZkCircuit {
         // Construct the Arithmetic chip.
         let arith_chip = config.arithmetic_chip();
 
-        // Construct the EvenBits chip.
-        let eb_chip = config.evenbits_chip();
-        eb_chip.alloc_table(&mut layouter.namespace(|| "alloc table"))?;
-
-        // Construct the GreaterThan chip.
-        //let gt_chip = config.greaterthan_chip();
-
         // This constant one is used for short multiplication
         let one = assign_free_advice(
             layouter.namespace(|| "Load constant one"),