Rust API for local_broker + refactor ng-wallet and apps for login + reorganize git repo

master
Niko PLP 2 months ago
parent 6878452ab3
commit fc4924ac87
  1. BIN
      .github/header.png
  2. 2
      .gitignore
  3. 337
      Cargo.lock
  4. 33
      Cargo.toml
  5. 47
      README.md
  6. 32
      nextgraph/Cargo.toml
  7. 57
      nextgraph/README.md
  8. 1
      nextgraph/src/lib.rs
  9. 751
      nextgraph/src/local_broker.rs
  10. 25
      ng-app/src-tauri/Cargo.toml
  11. 499
      ng-app/src-tauri/src/lib.rs
  12. 120
      ng-app/src/App.svelte
  13. 51
      ng-app/src/api.ts
  14. 8
      ng-app/src/lib/Home.svelte
  15. 71
      ng-app/src/lib/Login.svelte
  16. 9
      ng-app/src/routes/Home.svelte
  17. 9
      ng-app/src/routes/Install.svelte
  18. 124
      ng-app/src/routes/User.svelte
  19. 29
      ng-app/src/routes/WalletCreate.svelte
  20. 53
      ng-app/src/routes/WalletLogin.svelte
  21. 20
      ng-app/src/store.ts
  22. 86
      ng-app/src/wallet_emojis.ts
  23. 2
      ng-app/src/worker.js
  24. 30
      ng-broker/Cargo.toml
  25. 55
      ng-broker/README.md
  26. 18
      ng-broker/src/broker_store/account.rs
  27. 8
      ng-broker/src/broker_store/config.rs
  28. 20
      ng-broker/src/broker_store/invitation.rs
  29. 0
      ng-broker/src/broker_store/mod.rs
  30. 10
      ng-broker/src/broker_store/overlay.rs
  31. 8
      ng-broker/src/broker_store/peer.rs
  32. 8
      ng-broker/src/broker_store/repostoreinfo.rs
  33. 8
      ng-broker/src/broker_store/topic.rs
  34. 12
      ng-broker/src/broker_store/wallet.rs
  35. 4
      ng-broker/src/interfaces.rs
  36. 0
      ng-broker/src/lib.rs
  37. 24
      ng-broker/src/server.rs
  38. 63
      ng-broker/src/server_storage.rs
  39. 24
      ng-broker/src/server_ws.rs
  40. 4
      ng-broker/src/types.rs
  41. 2
      ng-broker/src/utils.rs
  42. 23
      ng-client-ws/Cargo.toml
  43. 55
      ng-client-ws/README.md
  44. 0
      ng-client-ws/src/lib.rs
  45. 32
      ng-client-ws/src/remote_ws.rs
  46. 16
      ng-client-ws/src/remote_ws_wasm.rs
  47. 27
      ng-net/Cargo.toml
  48. 55
      ng-net/README.md
  49. 0
      ng-net/src/actor.rs
  50. 4
      ng-net/src/actors/add_invitation.rs
  51. 4
      ng-net/src/actors/add_user.rs
  52. 4
      ng-net/src/actors/connecting.rs
  53. 2
      ng-net/src/actors/del_user.rs
  54. 4
      ng-net/src/actors/list_invitations.rs
  55. 4
      ng-net/src/actors/list_users.rs
  56. 0
      ng-net/src/actors/mod.rs
  57. 0
      ng-net/src/actors/noise.rs
  58. 0
      ng-net/src/actors/probe.rs
  59. 2
      ng-net/src/actors/start.rs
  60. 96
      ng-net/src/broker.rs
  61. 10
      ng-net/src/broker_connection.rs
  62. 27
      ng-net/src/connection.rs
  63. 18
      ng-net/src/errors.rs
  64. 2
      ng-net/src/lib.rs
  65. 9
      ng-net/src/server_storage.rs
  66. 0
      ng-net/src/tests/file.rs
  67. 0
      ng-net/src/tests/mod.rs
  68. 158
      ng-net/src/types.rs
  69. 6
      ng-net/src/utils.rs
  70. 25
      ng-repo/Cargo.toml
  71. 55
      ng-repo/README.md
  72. 2
      ng-repo/src/block.rs
  73. 65
      ng-repo/src/branch.rs
  74. 418
      ng-repo/src/commit.rs
  75. 19
      ng-repo/src/errors.rs
  76. 119
      ng-repo/src/event.rs
  77. 11
      ng-repo/src/file.rs
  78. 0
      ng-repo/src/kcv_store.rs
  79. 2
      ng-repo/src/lib.rs
  80. 94
      ng-repo/src/object.rs
  81. 574
      ng-repo/src/repo.rs
  82. 138
      ng-repo/src/site.rs
  83. 2
      ng-repo/src/store.rs
  84. 701
      ng-repo/src/types.rs
  85. 10
      ng-repo/src/utils.rs
  86. 0
      ng-repo/tests/test.jpg
  87. 24
      ng-sdk-js/Cargo.toml
  88. 2
      ng-sdk-js/README.md
  89. 201
      ng-sdk-js/app-node/LICENSE-APACHE
  90. 25
      ng-sdk-js/app-node/LICENSE-MIT
  91. 2
      ng-sdk-js/app-node/package.json
  92. 201
      ng-sdk-js/app-react/LICENSE-APACHE
  93. 25
      ng-sdk-js/app-react/LICENSE-MIT
  94. 13
      ng-sdk-js/app-react/README.md
  95. 2
      ng-sdk-js/app-react/package.json
  96. 11
      ng-sdk-js/app-web/README.md
  97. 2
      ng-sdk-js/app-web/package.json
  98. 2
      ng-sdk-js/js/node.js
  99. 520
      ng-sdk-js/src/lib.rs
  100. 26
      ng-stores-lmdb/Cargo.toml
  101. Some files were not shown because too many files have changed in this diff Show More

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

2
.gitignore vendored

@ -7,5 +7,5 @@
/result* /result*
.DS_Store .DS_Store
node_modules node_modules
/p2p-repo/tests/*.ng */tests/*.ng
.vscode/settings.json .vscode/settings.json

337
Cargo.lock generated

@ -343,6 +343,12 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "async-once-cell"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb"
[[package]] [[package]]
name = "async-oneshot" name = "async-oneshot"
version = "0.5.0" version = "0.5.0"
@ -3108,16 +3114,37 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nextgraph"
version = "0.1.0"
dependencies = [
"async-once-cell",
"async-std",
"base64-url",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-verifier",
"ng-wallet",
"once_cell",
"serde",
"serde_bare",
"serde_bytes",
"web-time",
"zeroize",
]
[[package]] [[package]]
name = "ng-app" name = "ng-app"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-tungstenite", "async-tungstenite",
"nextgraph",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-wallet", "ng-wallet",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
@ -3125,6 +3152,115 @@ dependencies = [
"tauri-plugin-window", "tauri-plugin-window",
] ]
[[package]]
name = "ng-broker"
version = "0.1.0"
dependencies = [
"async-channel",
"async-std",
"async-trait",
"async-tungstenite",
"blake3",
"chacha20",
"default-net",
"futures",
"getrandom 0.2.10",
"hex",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-stores-rocksdb",
"once_cell",
"rust-embed",
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"tempfile",
]
[[package]]
name = "ng-client-ws"
version = "0.1.0"
dependencies = [
"async-channel",
"async-oneshot",
"async-std",
"async-trait",
"async-tungstenite",
"chacha20",
"either",
"futures",
"getrandom 0.2.10",
"ng-net",
"ng-repo",
"pharos",
"serde",
"serde_bare",
"serde_bytes",
"wasm-bindgen",
"wasm-bindgen-test",
"ws_stream_wasm",
]
[[package]]
name = "ng-net"
version = "0.1.0"
dependencies = [
"async-broadcast 0.4.1",
"async-std",
"async-trait",
"base64-url",
"blake3",
"default-net",
"ed25519-dalek",
"either",
"futures",
"getrandom 0.2.10",
"ng-repo",
"noise-protocol",
"noise-rust-crypto",
"num_enum",
"once_cell",
"reqwest",
"serde",
"serde_bare",
"serde_bytes",
"unique_id",
"url",
"wasm-bindgen",
"web-time",
]
[[package]]
name = "ng-repo"
version = "0.1.0"
dependencies = [
"base64-url",
"blake3",
"chacha20",
"curve25519-dalek 3.2.0",
"debug_print",
"ed25519-dalek",
"fastbloom-rs",
"futures",
"getrandom 0.2.10",
"gloo-timers",
"hex",
"log",
"once_cell",
"rand 0.7.3",
"serde",
"serde_bare",
"serde_bytes",
"slice_as_array",
"threshold_crypto",
"time 0.3.23",
"wasm-bindgen",
"web-time",
"zeroize",
]
[[package]] [[package]]
name = "ng-sdk-js" name = "ng-sdk-js"
version = "0.1.0" version = "0.1.0"
@ -3135,10 +3271,12 @@ dependencies = [
"getrandom 0.1.16", "getrandom 0.1.16",
"gloo-timers", "gloo-timers",
"js-sys", "js-sys",
"nextgraph",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-wallet", "ng-wallet",
"p2p-client-ws", "once_cell",
"p2p-net",
"p2p-repo",
"pharos", "pharos",
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
@ -3152,6 +3290,31 @@ dependencies = [
"ws_stream_wasm", "ws_stream_wasm",
] ]
[[package]]
name = "ng-stores-rocksdb"
version = "0.1.0"
dependencies = [
"hex",
"ng-repo",
"rocksdb",
"serde",
"serde_bare",
"tempfile",
]
[[package]]
name = "ng-verifier"
version = "0.1.0"
dependencies = [
"blake3",
"chacha20",
"ng-net",
"ng-repo",
"serde",
"serde_bare",
"serde_bytes",
]
[[package]] [[package]]
name = "ng-wallet" name = "ng-wallet"
version = "0.1.0" version = "0.1.0"
@ -3160,13 +3323,14 @@ dependencies = [
"argon2", "argon2",
"async-std", "async-std",
"base64-url", "base64-url",
"blake3",
"chacha20poly1305", "chacha20poly1305",
"crypto_box", "crypto_box",
"getrandom 0.1.16", "getrandom 0.1.16",
"image", "image",
"lazy_static", "lazy_static",
"p2p-net", "ng-net",
"p2p-repo", "ng-repo",
"rand 0.7.3", "rand 0.7.3",
"safe-transmute", "safe-transmute",
"serde", "serde",
@ -3187,10 +3351,10 @@ dependencies = [
"duration-str", "duration-str",
"env_logger", "env_logger",
"log", "log",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-wallet", "ng-wallet",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"rust-embed", "rust-embed",
"serde", "serde",
"serde-big-array", "serde-big-array",
@ -3218,9 +3382,9 @@ dependencies = [
"futures", "futures",
"getrandom 0.2.10", "getrandom 0.2.10",
"log", "log",
"p2p-client-ws", "ng-client-ws",
"p2p-net", "ng-net",
"p2p-repo", "ng-repo",
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"serde_bare", "serde_bare",
@ -3241,9 +3405,9 @@ dependencies = [
"env_logger", "env_logger",
"lazy_static", "lazy_static",
"log", "log",
"p2p-broker", "ng-broker",
"p2p-net", "ng-net",
"p2p-repo", "ng-repo",
"regex", "regex",
"serde", "serde",
"serde_bare", "serde_bare",
@ -3260,9 +3424,10 @@ dependencies = [
"bytes", "bytes",
"env_logger", "env_logger",
"log", "log",
"ng-net",
"ng-repo",
"ng-stores-rocksdb",
"ng-wallet", "ng-wallet",
"p2p-net",
"p2p-repo",
"rust-embed", "rust-embed",
"serde", "serde",
"serde-big-array", "serde-big-array",
@ -3270,7 +3435,6 @@ dependencies = [
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
"slice_as_array", "slice_as_array",
"stores-rocksdb",
"tokio", "tokio",
"warp", "warp",
"warp-embed", "warp-embed",
@ -3530,127 +3694,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "p2p-broker"
version = "0.1.0"
dependencies = [
"async-channel",
"async-std",
"async-trait",
"async-tungstenite",
"blake3",
"chacha20",
"default-net",
"futures",
"getrandom 0.2.10",
"hex",
"once_cell",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"rust-embed",
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"stores-rocksdb",
"tempfile",
]
[[package]]
name = "p2p-client-ws"
version = "0.1.0"
dependencies = [
"async-channel",
"async-oneshot",
"async-std",
"async-trait",
"async-tungstenite",
"chacha20",
"either",
"futures",
"getrandom 0.2.10",
"p2p-net",
"p2p-repo",
"pharos",
"serde",
"serde_bare",
"serde_bytes",
"wasm-bindgen",
"wasm-bindgen-test",
"ws_stream_wasm",
]
[[package]]
name = "p2p-net"
version = "0.1.0"
dependencies = [
"async-broadcast 0.4.1",
"async-std",
"async-trait",
"base64-url",
"blake3",
"default-net",
"ed25519-dalek",
"either",
"futures",
"getrandom 0.2.10",
"noise-protocol",
"noise-rust-crypto",
"num_enum",
"once_cell",
"p2p-repo",
"reqwest",
"serde",
"serde_bare",
"serde_bytes",
"unique_id",
"url",
"wasm-bindgen",
"web-time",
]
[[package]]
name = "p2p-repo"
version = "0.1.0"
dependencies = [
"base64-url",
"blake3",
"chacha20",
"curve25519-dalek 3.2.0",
"debug_print",
"ed25519-dalek",
"fastbloom-rs",
"futures",
"gloo-timers",
"hex",
"log",
"once_cell",
"rand 0.7.3",
"serde",
"serde_bare",
"serde_bytes",
"slice_as_array",
"threshold_crypto",
"time 0.3.23",
"wasm-bindgen",
"web-time",
"zeroize",
]
[[package]]
name = "p2p-verifier"
version = "0.1.0"
dependencies = [
"blake3",
"chacha20",
"p2p-net",
"p2p-repo",
"serde",
"serde_bare",
"serde_bytes",
]
[[package]] [[package]]
name = "packed_simd_2" name = "packed_simd_2"
version = "0.3.8" version = "0.3.8"
@ -4897,18 +4940,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stores-rocksdb"
version = "0.1.0"
dependencies = [
"hex",
"p2p-repo",
"rocksdb",
"serde",
"serde_bare",
"tempfile",
]
[[package]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.8.7" version = "0.8.7"

@ -1,20 +1,37 @@
[workspace] [workspace]
members = [ members = [
"p2p-repo", "nextgraph",
"p2p-net",
"p2p-broker",
"p2p-client-ws",
"p2p-verifier",
"stores-rocksdb",
"ngcli", "ngcli",
"ngd", "ngd",
"ng-repo",
"ng-net",
"ng-broker",
"ng-client-ws",
"ng-verifier",
"ng-wallet",
"ng-stores-rocksdb",
"ngone", "ngone",
"ngaccount", "ngaccount",
"ng-sdk-js", "ng-sdk-js",
"ng-app/src-tauri", "ng-app/src-tauri",
"ng-wallet"
] ]
default-members = [ "ngcli", "ngd" ] default-members = [ "nextgraph", "ngcli", "ngd" ]
[workspace.package]
version = "0.1.0"
edition = "2021"
rust-version = "1.64.0"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
homepage = "https://nextgraph.org"
keywords = [
"crdt","dapp","decentralized","e2ee","local-first","p2p","semantic-web","eventual-consistency","json-ld","markdown",
"ocap","z-cap","offline-first","p2p-network","collaboration","privacy-protection","rdf","rich-text-editor","self-hosted",
"sparql","byzantine-fault-tolerance",
"web3"
]
documentation = "https://docs.nextgraph.org/"
[profile.release] [profile.release]
lto = true lto = true

@ -1,5 +1,14 @@
<p align="center">
<img src=".github/header.png" alt="nextgraph-header" />
</p>
# nextgraph-rs # nextgraph-rs
![MSRV][rustc-image]
[![Apache 2.0 Licensed][license-image]][license-link]
[![MIT Licensed][license-image2]][license-link2]
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://forum.nextgraph.org)
Rust implementation of NextGraph Rust implementation of NextGraph
This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues. This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues.
@ -8,7 +17,7 @@ This repository is in active development at [https://git.nextgraph.org/NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. > NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
> >
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with end-to-end encryption, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. > This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
> >
> More info here [https://nextgraph.org](https://nextgraph.org) > More info here [https://nextgraph.org](https://nextgraph.org)
@ -18,6 +27,9 @@ Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgr
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org) And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
[![Mastodon](https://img.shields.io/badge/-MASTODON-%232B90D9?style=for-the-badge&logo=mastodon&logoColor=white)](https://mastodon.lescommuns.org/@nextgraph)
[![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Fnextgraph)](https://twitter.com/nextgraph)
## How to use NextGraph ## How to use NextGraph
NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/). NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
@ -48,19 +60,20 @@ cargo build
The crates are organized as follow : The crates are organized as follow :
- p2p-repo : NextGraph repositories common library - [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application
- p2p-net : P2P network common library - [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server
- p2p-broker : the broker code (as server and core node) - [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- p2p-client-ws : the client connecting to a broker with WebSocket, used by the apps and verifier - ng-repo : Repositories common library
- p2p-verifier : the code of the verifier - ng-net : Network common library
- stores-rocksdb : RocksDB backed stores. see [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb) - ng-broker : Core and Server Broker library
- ngcli : CLI tool to manipulate the repos and administrate the server - ng-client-ws : Websocket client library
- ngd : binary executable of the daemon (that can run a broker, verifier and/or Rust services) - ng-verifier : Verifier library, that exposes the document API to the app
- ng-stores-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb)
- ng-wallet : keeps the secret keys of all identities of the user in a safe wallet - ng-wallet : keeps the secret keys of all identities of the user in a safe wallet
- [ng-sdk-js](ng-sdk-js/README.md) : contains the JS SDK, with example apps: web app, react app, or node service. - [ng-sdk-js](ng-sdk-js/README.md) : contains the JS SDK, with example apps: web app, react app, or node service.
- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the web app. - [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the web app.
- [ngone](ngone/README.md) : server for nextgraph.one (helps user bootstrap into the right app) - ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- [ngaccount](ngaccount/README.md) : server for nextgraph's Broker Service Provider account manager. - ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
### Run ### Run
@ -87,7 +100,7 @@ cargo test --all --verbose -- --show-output --nocapture
Test a single module: Test a single module:
``` ```
cargo test --package p2p-repo --lib -- branch::test --show-output --nocapture cargo test --package ng-repo --lib -- branch::test --show-output --nocapture
``` ```
Test end-to-end client and server: Test end-to-end client and server:
@ -106,7 +119,7 @@ wasm-pack test --chrome --headless
Test Rust websocket Test Rust websocket
``` ```
cargo test --package p2p-client-ws --lib -- remote_ws::test::test_ws --show-output --nocapture cargo test --package ng-client-ws --lib -- remote_ws::test::test_ws --show-output --nocapture
``` ```
### Build release binaries ### Build release binaries
@ -155,7 +168,7 @@ For building the apps, see this [documentation](ng-app/README.md).
#### OpenBSD #### OpenBSD
On OpenBSD, a conflict between the installed LibreSSL library and the reqwest crate, needs a bit of attention. On OpenBSD, a conflict between the installed LibreSSL library and the reqwest crate, needs a bit of attention.
Before compiling the daemon for OpenBSD, please comment out lines 32-33 of `p2p-net/Cargo.toml`. This will be solved soon in a more appropriate way. Before compiling the daemon for OpenBSD, please comment out lines 32-33 of `ng-net/Cargo.toml`. This will be solved soon in a more appropriate way.
``` ```
#[target.'cfg(target_arch = "wasm32")'.dependencies] #[target.'cfg(target_arch = "wasm32")'.dependencies]
@ -203,3 +216,9 @@ Licensed under either of
--- ---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073. NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.
[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg
[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT

@ -0,0 +1,32 @@
[package]
name = "nextgraph"
description = "NextGraph client library. Nextgraph is a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
categories = ["asynchronous","text-editors","web-programming","development-tools","database-implementations"]
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
[badges]
maintenance = { status = "actively-developed" }
[dependencies]
ng-repo = { path = "../ng-repo", version = "0.1.0" }
ng-net = { path = "../ng-net", version = "0.1.0" }
ng-wallet = { path = "../ng-wallet", version = "0.1.0" }
ng-client-ws = { path = "../ng-client-ws", version = "0.1.0" }
ng-verifier = { path = "../ng-verifier", version = "0.1.0" }
async-once-cell = "0.5.3"
once_cell = "1.17.1"
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
base64-url = "2.0.0"
web-time = "0.2.0"
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
zeroize = { version = "1.6.0", features = ["zeroize_derive"] }

@ -0,0 +1,57 @@
<p align="center">
<img src="../.github/header.png" alt="nextgraph-header" />
</p>
# nextgraph
![MSRV][rustc-image]
[![Apache 2.0 Licensed][license-image]][license-link]
[![MIT Licensed][license-image2]][license-link2]
Rust client library of NextGraph
This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues.
## NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
>
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
>
> More info here [https://nextgraph.org](https://nextgraph.org)
## Support
Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org)
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
## How to use the library
NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`
### Contributions license
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as below, without any
additional terms or conditions.
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.
[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg
[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT

@ -0,0 +1 @@
pub mod local_broker;

@ -0,0 +1,751 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use async_once_cell::OnceCell;
use async_std::sync::{Arc, RwLock};
use core::fmt;
use ng_net::connection::{ClientConfig, IConnect, StartConfig};
use ng_net::types::ClientInfo;
use once_cell::sync::Lazy;
use serde_bare::to_vec;
use std::collections::HashMap;
use std::fs::{read, write, File, OpenOptions};
use std::path::PathBuf;
use zeroize::{Zeroize, ZeroizeOnDrop};
use ng_net::broker::*;
use ng_repo::errors::NgError;
use ng_repo::log::*;
use ng_repo::types::*;
use ng_wallet::{create_wallet_v0, types::*};
#[cfg(not(target_arch = "wasm32"))]
use ng_client_ws::remote_ws::ConnectionWebSocket;
#[cfg(target_arch = "wasm32")]
use ng_client_ws::remote_ws_wasm::ConnectionWebSocket;
type JsStorageReadFn = dyn Fn(String) -> Result<String, NgError> + 'static + Sync + Send;
type JsStorageWriteFn = dyn Fn(String, String) -> Result<(), NgError> + 'static + Sync + Send;
type JsCallback = dyn Fn() + 'static + Sync + Send;
pub struct JsStorageConfig {
pub local_read: Box<JsStorageReadFn>,
pub local_write: Box<JsStorageWriteFn>,
pub session_read: Box<JsStorageReadFn>,
pub session_write: Box<JsStorageWriteFn>,
}
impl fmt::Debug for JsStorageConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "JsStorageConfig")
}
}
#[derive(Debug)]
pub enum LocalBrokerConfig {
InMemory,
BasePath(PathBuf),
JsStorage(JsStorageConfig),
}
impl LocalBrokerConfig {
pub fn is_in_memory(&self) -> bool {
match self {
Self::InMemory => true,
_ => false,
}
}
pub fn is_js(&self) -> bool {
match self {
Self::JsStorage(_) => true,
_ => false,
}
}
pub fn js_config(&self) -> Option<&JsStorageConfig> {
match self {
Self::JsStorage(c) => Some(c),
_ => None,
}
}
}
//type LastSeqFn = fn(PubKey, u16) -> Result<u64, NgError>;
pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result<u64, NgError> + 'static + Sync + Send;
/// peer_id: PubKey, seq_num:u64, event_ser: vec<u8>,
pub type OutboxWriteFn =
dyn Fn(PubKey, u64, Vec<u8>) -> Result<(), NgError> + 'static + Sync + Send;
/// peer_id: PubKey,
pub type OutboxReadFn = dyn Fn(PubKey) -> Result<Vec<Vec<u8>>, NgError> + 'static + Sync + Send;
/// Session Config to initiate a session at a local broker V0
pub struct SessionConfigV0 {
pub user_id: UserId,
pub wallet_name: String,
// pub last_seq_function: Box<LastSeqFn>,
// pub outbox_write_function: Box<OutboxWriteFn>,
// pub outbox_read_function: Box<OutboxReadFn>,
}
/// Session Config to initiate a session at a local broker
pub enum SessionConfig {
V0(SessionConfigV0),
}
#[derive(Debug)]
struct Session {
config: SessionConfig,
peer_key: PrivKey,
last_wallet_nonce: u64,
//verifier,
}
impl SessionConfig {
pub fn user_id(&self) -> UserId {
match self {
Self::V0(v0) => v0.user_id,
}
}
pub fn wallet_name(&self) -> String {
match self {
Self::V0(v0) => v0.wallet_name.clone(),
}
}
}
impl fmt::Debug for SessionConfigV0 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SessionConfigV0 user={} wallet={}",
self.user_id, self.wallet_name
)
}
}
impl fmt::Debug for SessionConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SessionConfig::V0(v0) => v0.fmt(f),
}
}
}
#[derive(Debug)]
struct LocalBroker {
pub config: LocalBrokerConfig,
pub wallets: HashMap<String, LocalWalletStorageV0>,
pub opened_wallets: HashMap<String, SensitiveWallet>,
pub sessions: HashMap<UserId, SessionPeerStorageV0>,
pub opened_sessions: HashMap<UserId, Session>,
}
impl ILocalBroker for LocalBroker {}
impl LocalBroker {
fn get_wallet_for_session(&self, config: &SessionConfig) -> Result<&SensitiveWallet, NgError> {
match config {
SessionConfig::V0(v0) => self
.opened_wallets
.get(&v0.wallet_name)
.ok_or(NgError::WalletNotFound),
}
}
}
//static LOCAL_BROKER: OnceCell<Result<(), NgError>> = OnceCell::new();
static LOCAL_BROKER: OnceCell<Result<Arc<RwLock<LocalBroker>>, NgError>> = OnceCell::new(); //Lazy::new(|| Arc::new(RwLock::new(Broker::new())));
pub type ConfigInitFn = dyn Fn() -> LocalBrokerConfig + 'static + Sync + Send;
async fn init_(config: LocalBrokerConfig) -> Result<Arc<RwLock<LocalBroker>>, NgError> {
let wallets = match &config {
LocalBrokerConfig::InMemory => HashMap::new(),
LocalBrokerConfig::BasePath(base_path) => {
// load the wallets and sessions from disk
let mut path = base_path.clone();
path.push("wallets");
let map_ser = read(path);
if map_ser.is_ok() {
let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap())?;
let LocalWalletStorage::V0(wallets) = wallets;
wallets
} else {
HashMap::new()
}
}
LocalBrokerConfig::JsStorage(js_storage_config) => {
// load the wallets from JsStorage
match (js_storage_config.local_read)("ng_wallets".to_string()) {
Err(_) => HashMap::new(),
Ok(wallets_string) => {
let map_ser = base64_url::decode(&wallets_string)
.map_err(|_| NgError::SerializationError)?;
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?;
let LocalWalletStorage::V0(v0) = wallets;
v0
}
}
}
};
let local_broker = LocalBroker {
config,
wallets,
opened_wallets: HashMap::new(),
sessions: HashMap::new(),
opened_sessions: HashMap::new(),
};
//log_debug!("{:?}", &local_broker);
Ok(Arc::new(RwLock::new(local_broker)))
}
//-> &'static Result<Arc<RwLock<LocalBroker>>, NgError>
pub async fn init_local_broker_with_lazy(config_fn: &Lazy<Box<ConfigInitFn>>) {
LOCAL_BROKER
.get_or_init(async {
let config = (&*config_fn)();
init_(config).await
})
.await;
}
pub async fn init_local_broker(config_fn: Box<ConfigInitFn>) {
LOCAL_BROKER
.get_or_init(async {
let config = (config_fn)();
init_(config).await
})
.await;
}
pub async fn get_all_wallets() -> Result<HashMap<String, LocalWalletStorageV0>, NgError> {
let broker = match LOCAL_BROKER.get() {
Some(Err(e)) => {
log_err!("LocalBrokerNotInitialized: {}", e);
return Err(NgError::LocalBrokerNotInitialized);
}
None => {
log_err!("Not initialized");
return Err(NgError::LocalBrokerNotInitialized);
}
Some(Ok(broker)) => broker.read().await,
};
Ok(broker.wallets.clone())
}
pub async fn wallet_create_v0(params: CreateWalletV0) -> Result<CreateWalletResultV0, NgError> {
let res = create_wallet_v0(params)?;
let lws: LocalWalletStorageV0 = (&res).into();
wallet_add(lws).await?;
Ok(res)
}
pub async fn reload_wallets() -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
match &broker.config {
LocalBrokerConfig::JsStorage(js_config) => {
// load the wallets from JsStorage
let wallets_string = (js_config.local_read)("ng_wallets".to_string())?;
let map_ser =
base64_url::decode(&wallets_string).map_err(|_| NgError::SerializationError)?;
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?;
let LocalWalletStorage::V0(v0) = wallets;
broker.wallets.extend(v0);
}
_ => {}
}
Ok(())
}
pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
if !lws.in_memory && broker.config.is_in_memory() {
return Err(NgError::CannotSaveWhenInMemoryConfig);
}
if broker.wallets.get(&lws.wallet.name()).is_some() {
return Err(NgError::WalletAlreadyAdded);
}
let in_memory = lws.in_memory;
broker.wallets.insert(lws.wallet.name(), lws);
if in_memory {
// if broker.config.is_js() {
// (broker.config.js_config().unwrap().wallets_in_mem_changed)();
// }
} else {
match &broker.config {
LocalBrokerConfig::JsStorage(js_config) => {
// JS save
let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets);
let encoded = base64_url::encode(&lws_ser);
(js_config.local_write)("ng_wallets".to_string(), encoded)?;
}
LocalBrokerConfig::BasePath(base_path) => {
// save on disk
// TODO: use https://lib.rs/crates/keyring instead of AppLocalData on Tauri apps
let mut path = base_path.clone();
std::fs::create_dir_all(path.clone()).unwrap();
path.push("wallets");
let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets);
let r = write(path.clone(), &lws_ser);
if r.is_err() {
log_debug!("write {:?} {}", path, r.unwrap_err());
return Err(NgError::IoError);
}
}
_ => panic!("wrong LocalBrokerConfig"),
}
}
Ok(())
}
pub async fn wallet_read_file(file: Vec<u8>) -> Result<Wallet, NgError> {
let ngf: NgFile = file.try_into()?;
if let NgFile::V0(NgFileV0::Wallet(wallet)) = ngf {
let broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
// check that the wallet is not already present in local_broker
let wallet_name = wallet.name();
if broker.wallets.get(&wallet_name).is_none() {
Ok(wallet)
} else {
Err(NgError::WalletAlreadyAdded)
}
} else {
Err(NgError::InvalidFileFormat)
}
}
pub async fn wallet_download_file(wallet_name: &String) -> Result<Vec<u8>, NgError> {
let broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
// check that the wallet exists
match broker.wallets.get(wallet_name) {
None => Err(NgError::WalletNotFound),
Some(lws) => Ok(to_vec(&NgFile::V0(NgFileV0::Wallet(lws.wallet.clone()))).unwrap()),
}
}
pub fn wallet_open_with_pazzle(
wallet: Wallet,
pazzle: Vec<u8>,
pin: [u8; 4],
) -> Result<SensitiveWallet, NgError> {
let opened_wallet = ng_wallet::open_wallet_with_pazzle(wallet, pazzle, pin)?;
Ok(opened_wallet)
}
pub async fn wallet_import(
encrypted_wallet: Wallet,
mut opened_wallet: SensitiveWallet,
in_memory: bool,
) -> Result<ClientV0, NgError> {
{
// in a block to release lock before calling wallet_add
let broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
let wallet_name = encrypted_wallet.name();
if broker.wallets.get(&wallet_name).is_some() {
return Err(NgError::WalletAlreadyOpened);
}
}
let lws = opened_wallet.import_v0(encrypted_wallet, in_memory)?;
wallet_add(lws).await?;
wallet_was_opened(opened_wallet).await
}
pub async fn wallet_was_opened(mut wallet: SensitiveWallet) -> Result<ClientV0, NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
if broker.opened_wallets.get(&wallet.id()).is_some() {
return Err(NgError::WalletAlreadyOpened);
}
match broker.wallets.get(&(wallet.id())) {
Some(lws) => {
if wallet.client().is_none() {
// this case happens when the wallet is opened and not when it is imported (as the client is already there)
wallet.set_client(lws.to_client_v0(wallet.privkey())?);
}
}
None => {
return Err(NgError::WalletNotFound);
}
}
let client = wallet.client().as_ref().unwrap().clone();
broker.opened_wallets.insert(wallet.id(), wallet);
Ok(client)
}
pub async fn session_start(config: SessionConfig) -> Result<SessionPeerStorageV0, NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
let wallet_name = config.wallet_name();
let wallet_id: PubKey = (*wallet_name).try_into()?;
let user_id = config.user_id();
match broker.opened_wallets.get(&wallet_name) {
None => return Err(NgError::WalletNotFound),
Some(wallet) => {
if !wallet.has_user(&user_id) {
return Err(NgError::NotFound);
}
let session = match broker.sessions.get(&user_id) {
Some(session) => session,
None => {
// creating the session now
let closed_wallet = broker.wallets.get(&wallet_name).unwrap();
if closed_wallet.in_memory {
let session = SessionPeerStorageV0::new(user_id);
broker.sessions.insert(user_id, session);
broker.sessions.get(&user_id).unwrap()
} else {
// first check if there is a saved SessionWalletStorage
let mut sws = match &broker.config {
LocalBrokerConfig::InMemory => panic!("cannot open saved session"),
LocalBrokerConfig::JsStorage(js_config) => {
// read session wallet storage from JsStorage
let res =
(js_config.session_read)(format!("ng_wallet@{}", wallet_name));
match res {
Ok(string) => {
let decoded = base64_url::decode(&string)
.map_err(|_| NgError::SerializationError)?;
Some(SessionWalletStorageV0::dec_session(
wallet.privkey(),
&decoded,
)?)
}
Err(_) => None,
}
}
LocalBrokerConfig::BasePath(base_path) => {
// read session wallet storage from disk
let mut path = base_path.clone();
path.push("sessions");
path.push(wallet_name.clone());
let res = read(path);
if res.is_ok() {
Some(SessionWalletStorageV0::dec_session(
wallet.privkey(),
&res.unwrap(),
)?)
} else {
None
}
}
};
let (session, new_sws) = match &mut sws {
None => {
let (s, sws_ser) = SessionWalletStorageV0::create_new_session(
&wallet_id, user_id,
)?;
broker.sessions.insert(user_id, s);
(broker.sessions.get(&user_id).unwrap(), sws_ser)
}
Some(sws) => {
match sws.users.get(&user_id.to_string()) {
Some(sps) => {
broker.sessions.insert(user_id, sps.clone());
(broker.sessions.get(&user_id).unwrap(), vec![])
}
None => {
// the user was not found in the SWS. we need to create a SPS, add it, encrypt and serialize the new SWS,
// add the SPS to broker.sessions, and return the newly created SPS and the new SWS encryped serialization
let sps = SessionPeerStorageV0::new(user_id);
sws.users.insert(user_id.to_string(), sps.clone());
let encrypted = sws.enc_session(&wallet_id)?;
broker.sessions.insert(user_id, sps);
(broker.sessions.get(&user_id).unwrap(), encrypted)
}
}
}
};
// save the new sws
if new_sws.len() > 0 {
match &broker.config {
LocalBrokerConfig::InMemory => {
panic!("cannot save session when InMemory mode")
}
LocalBrokerConfig::JsStorage(js_config) => {
// save session wallet storage to JsStorage
let encoded = base64_url::encode(&new_sws);
(js_config.session_write)(
format!("ng_wallet@{}", wallet_name),
encoded,
)?;
}
LocalBrokerConfig::BasePath(base_path) => {
// save session wallet storage to disk
let mut path = base_path.clone();
path.push("sessions");
std::fs::create_dir_all(path.clone()).unwrap();
path.push(wallet_name);
//log_debug!("{}", path.clone().display());
write(path.clone(), &new_sws).map_err(|_| NgError::IoError)?;
}
}
}
session
}
}
};
let session = session.clone();
broker.opened_sessions.insert(
user_id,
Session {
config,
peer_key: session.peer_key.clone(),
last_wallet_nonce: session.last_wallet_nonce,
},
);
// FIXME: is this return value useful ?
Ok(session)
}
}
}
use web_time::SystemTime;
fn get_unix_time() -> f64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis() as f64
}
/// Result is a list of (user_id, server_id, server_ip, error, since_date)
pub async fn user_connect(
info: ClientInfo,
user_id: UserId,
location: Option<String>,
) -> Result<Vec<(String, String, String, Option<String>, f64)>, NgError> {
let local_broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
let session = local_broker
.opened_sessions
.get(&user_id)
.ok_or(NgError::SessionNotFound)?;
let wallet = local_broker.get_wallet_for_session(&session.config)?;
let mut result: Vec<(String, String, String, Option<String>, f64)> = Vec::new();
let arc_cnx: Arc<Box<dyn IConnect>> = Arc::new(Box::new(ConnectionWebSocket {}));
match wallet {
SensitiveWallet::V0(wallet) => {
let client = wallet.client.as_ref().unwrap();
let client_id = client.id;
let client_priv = &client.sensitive_client_storage.priv_key;
let client_name = &client.name;
let auto_open = &client.auto_open;
// log_info!(
// "XXXX {} name={:?} auto_open={:?} {:?}",
// client_id.to_string(),
// client_name,
// auto_open,
// wallet
// );
for user in auto_open {
let user_id = user.to_string();
let peer_key = &session.peer_key;
let peer_id = peer_key.to_pub();
let site = wallet.sites.get(&user_id);
if site.is_none() {
result.push((
user_id,
"".into(),
"".into(),
Some("Site is missing".into()),
get_unix_time(),
));
continue;
}
let site = site.unwrap();
let user_priv = site.get_individual_user_priv_key().unwrap();
let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover)
let server_key = core.0;
let broker = wallet.brokers.get(&core.0.to_string());
if broker.is_none() {
result.push((
user_id,
core.0.to_string(),
"".into(),
Some("Broker is missing".into()),
get_unix_time(),
));
continue;
}
let brokers = broker.unwrap();
let mut tried: Option<(String, String, String, Option<String>, f64)> = None;
//TODO: on tauri (or forward in local broker, or CLI), prefer a Public to a Domain. Domain always comes first though, so we need to reorder the list
//TODO: use site.bootstraps to order the list of brokerInfo.
for broker_info in brokers {
match broker_info {
BrokerInfoV0::ServerV0(server) => {
let url = server.get_ws_url(&location).await;
log_debug!("URL {:?}", url);
//Option<(String, Vec<BindAddress>)>
if url.is_some() {
let url = url.unwrap();
if url.1.len() == 0 {
// TODO deal with Box(Dyn)Public -> tunnel, and on tauri/forward/CLIs, deal with all Box -> direct connections (when url.1.len is > 0)
let res = BROKER
.write()
.await
.connect(
arc_cnx.clone(),
peer_key.clone(),
peer_id,
server_key,
StartConfig::Client(ClientConfig {
url: url.0.clone(),
name: client_name.clone(),
user_priv: user_priv.clone(),
client_priv: client_priv.clone(),
info: info.clone(),
registration: Some(core.1),
}),
)
.await;
log_debug!("broker.connect : {:?}", res);
tried = Some((
user_id.clone(),
core.0.to_string(),
url.0.into(),
match &res {
Ok(_) => None,
Err(e) => Some(e.to_string()),
},
get_unix_time(),
));
}
if tried.is_some() && tried.as_ref().unwrap().3.is_none() {
// successful. we can stop here
break;
} else {
log_debug!("Failed connection {:?}", tried);
}
}
}
// Core information is discarded
_ => {}
}
}
if tried.is_none() {
tried = Some((
user_id,
core.0.to_string(),
"".into(),
Some("No broker found".into()),
get_unix_time(),
));
}
result.push(tried.unwrap());
}
}
}
Ok(result)
}
pub async fn session_stop(user_id: UserId) -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
if broker.opened_sessions.remove(&user_id).is_some() {
// TODO: change the logic here once it will be possible to have several users connected at the same time
Broker::close_all_connections().await;
}
Ok(())
}
pub async fn user_disconnect(user_id: UserId) -> Result<(), NgError> {
let broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
if broker.opened_sessions.get(&user_id).is_some() {
// TODO: change the logic here once it will be possible to have several users connected at the same time
Broker::close_all_connections().await;
}
Ok(())
}
pub async fn wallet_close(wallet_name: String) -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
match broker.opened_wallets.remove(&wallet_name) {
Some(mut wallet) => {
for user in wallet.sites() {
let key: PubKey = (user.as_str()).try_into().unwrap();
broker.opened_sessions.remove(&key);
}
wallet.zeroize();
}
None => return Err(NgError::WalletNotFound),
}
Broker::close_all_connections().await;
Ok(())
}
pub async fn wallet_remove(wallet_name: String) -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
todo!();
// should close the wallet, then remove all the saved sessions and remove the wallet
Ok(())
}

@ -1,18 +1,22 @@
[package] [package]
name = "ng-app" name = "ng-app"
version = "0.1.0" # version = "0.1.0"
description = "NextGraph App" description = "NextGraph App"
authors = ["Niko PLP <niko@nextgraph.org>"] publish = false
license = "MIT/Apache-2.0" version.workspace = true
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" edition.workspace = true
edition = "2021" license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
[lib] [lib]
name = "nativelib" name = "nativelib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0-alpha.8", features = [] } tauri-build = { version = "2.0.0-alpha.8", features = [] }
# tauri-macros = { version = "=2.0.0-alpha.6" } # tauri-macros = { version = "=2.0.0-alpha.6" }
@ -25,10 +29,11 @@ tauri = { version = "2.0.0-alpha.14", features = [] }
# tauri = { git = "https://github.com/simonhyll/tauri.git", branch="fix/ipc-mixup", features = [] } # tauri = { git = "https://github.com/simonhyll/tauri.git", branch="fix/ipc-mixup", features = [] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
p2p-repo = { path = "../../p2p-repo" } ng-repo = { path = "../../ng-repo" }
p2p-net = { path = "../../p2p-net" } ng-net = { path = "../../ng-net" }
p2p-client-ws = { path = "../../p2p-client-ws" } ng-client-ws = { path = "../../ng-client-ws" }
ng-wallet = { path = "../../ng-wallet" } ng-wallet = { path = "../../ng-wallet" }
nextgraph = { path = "../../nextgraph" }
async-std = { version = "1.12.0", features = ["attributes", "unstable"] } async-std = { version = "1.12.0", features = ["attributes", "unstable"] }
# tauri-plugin-window = { git = "https://git.nextgraph.org/NextGraph/plugins-workspace.git", branch="window-alpha.1-nextgraph" } # tauri-plugin-window = { git = "https://git.nextgraph.org/NextGraph/plugins-workspace.git", branch="window-alpha.1-nextgraph" }
tauri-plugin-window = "2.0.0-alpha.1" tauri-plugin-window = "2.0.0-alpha.1"

@ -7,18 +7,20 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use async_std::stream::StreamExt; use async_std::stream::StreamExt;
use nextgraph::local_broker::*;
use ng_net::broker::*;
use ng_net::types::{ClientInfo, CreateAccountBSP, Invitation};
use ng_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend};
use ng_repo::errors::NgError;
use ng_repo::log::*;
use ng_repo::types::*;
use ng_wallet::types::*; use ng_wallet::types::*;
use ng_wallet::*; use ng_wallet::*;
use p2p_client_ws::remote_ws::ConnectionWebSocket;
use p2p_net::broker::*;
use p2p_net::types::{ClientInfo, CreateAccountBSP, Invitation};
use p2p_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend};
use p2p_repo::errors::NgError;
use p2p_repo::log::*;
use p2p_repo::types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{read, write}; use std::fs::{read, write, File, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use tauri::scope::ipc::RemoteDomainAccessScope; use tauri::scope::ipc::RemoteDomainAccessScope;
use tauri::utils::config::WindowConfig; use tauri::utils::config::WindowConfig;
use tauri::{path::BaseDirectory, App, Manager, Window}; use tauri::{path::BaseDirectory, App, Manager, Window};
@ -30,15 +32,27 @@ pub use mobile::*;
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>; pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
// #[tauri::command(rename_all = "snake_case")]
// fn greet(name: &str) -> String {
// format!("Hello, {}! You've been greeted from Rust!", name)
// }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn test(app: tauri::AppHandle) -> Result<(), ()> { async fn test(app: tauri::AppHandle) -> Result<(), ()> {
log_debug!("test is {}", BROKER.read().await.test()); let path = app
.path()
.resolve("", BaseDirectory::AppLocalData)
.map_err(|_| NgError::SerializationError)
.unwrap();
init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await;
// init_local_broker(&Lazy::new(|| {
// Box::new(move || LocalBrokerConfig::BasePath(path.clone()))
// }))
// .await;
//pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result<u64, NgError> + 'static + Sync + Send;
// let test: Box<LastSeqFn> = Box::new(move |peer_id: PubKey, qty: u16| {
// take_some_peer_last_seq_numbers(peer_id, qty, path.clone())
// });
//pub type ConfigInitFn = dyn Fn() -> LocalBrokerConfig + 'static + Sync + Send;
//log_debug!("test is {}", BROKER.read().await.test());
let path = app let path = app
.path() .path()
.resolve("storage", BaseDirectory::AppLocalData) .resolve("storage", BaseDirectory::AppLocalData)
@ -51,227 +65,270 @@ async fn test(app: tauri::AppHandle) -> Result<(), ()> {
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> Result<ShuffledPazzle, ()> { async fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> Result<ShuffledPazzle, ()> {
log_debug!( // log_debug!(
"wallet_gen_shuffle_for_pazzle_opening from rust {}", // "wallet_gen_shuffle_for_pazzle_opening from rust {}",
pazzle_length // pazzle_length
); // );
Ok(gen_shuffle_for_pazzle_opening(pazzle_length)) Ok(gen_shuffle_for_pazzle_opening(pazzle_length))
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_gen_shuffle_for_pin() -> Result<Vec<u8>, ()> { async fn wallet_gen_shuffle_for_pin() -> Result<Vec<u8>, ()> {
log_debug!("wallet_gen_shuffle_for_pin from rust"); //log_debug!("wallet_gen_shuffle_for_pin from rust");
Ok(gen_shuffle_for_pin()) Ok(gen_shuffle_for_pin())
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_open_wallet_with_pazzle( async fn wallet_open_with_pazzle(
wallet: Wallet, wallet: Wallet,
pazzle: Vec<u8>, pazzle: Vec<u8>,
pin: [u8; 4], pin: [u8; 4],
) -> Result<EncryptedWallet, String> { app: tauri::AppHandle,
log_debug!("wallet_open_wallet_with_pazzle from rust {:?}", pazzle); ) -> Result<SensitiveWallet, String> {
open_wallet_with_pazzle(wallet, pazzle, pin).map_err(|e| e.to_string()) //log_debug!("wallet_open_with_pazzle from rust {:?}", pazzle);
let wallet = nextgraph::local_broker::wallet_open_with_pazzle(wallet, pazzle, pin)
.map_err(|e| e.to_string())?;
Ok(wallet)
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_create_wallet( async fn wallet_download_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> {
let ser = nextgraph::local_broker::wallet_download_file(&wallet_name)
.await
.map_err(|e| e.to_string())?;
// save wallet file to Downloads folder
let path = app
.path()
.resolve(
format!("wallet-{}.ngw", wallet_name),
BaseDirectory::Download,
)
.unwrap();
write(path, &ser).map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_create(
mut params: CreateWalletV0, mut params: CreateWalletV0,
app: tauri::AppHandle, app: tauri::AppHandle,
) -> Result<(CreateWalletResultV0, Option<SessionWalletStorageV0>), String> { ) -> Result<CreateWalletResultV0, String> {
//log_debug!("wallet_create_wallet from rust {:?}", params); //log_debug!("wallet_create from rust {:?}", params);
params.result_with_wallet_file = !params.local_save; params.result_with_wallet_file = !params.local_save;
let local_save = params.local_save; let local_save = params.local_save;
let res = create_wallet_v0(params).await.map_err(|e| e.to_string()); let mut cwr = nextgraph::local_broker::wallet_create_v0(params)
if res.is_ok() { .await
let mut cwr = res.unwrap(); .map_err(|e| e.to_string())?;
if local_save { if !local_save {
// save in local store // save wallet file to Downloads folder
let path = app
let session = save_wallet_locally(&cwr, app).await; .path()
if session.is_err() { .resolve(
return Err("Cannot save wallet locally".to_string()); format!("wallet-{}.ngw", cwr.wallet_name),
} BaseDirectory::Download,
return Ok((cwr, Some(session.unwrap()))); )
} else { .unwrap();
// save wallet file to Downloads folder let _r = write(path, &cwr.wallet_file);
let path = app cwr.wallet_file = vec![];
.path()
.resolve(
format!("wallet-{}.ngw", cwr.wallet_name),
BaseDirectory::Download,
)
.unwrap();
let _r = write(path, &cwr.wallet_file);
cwr.wallet_file = vec![];
return Ok((cwr, None));
}
} }
Err(res.unwrap_err()) Ok(cwr)
} }
async fn save_wallet_locally( // // TODO: use https://lib.rs/crates/keyring instead of AppLocalData
res: &CreateWalletResultV0, // async fn save_wallet_locally(
app: tauri::AppHandle, // res: &CreateWalletResultV0,
) -> Result<SessionWalletStorageV0, ()> { // app: tauri::AppHandle,
let path = app // ) -> Result<SessionWalletStorageV0, ()> {
.path() // let path = app
.resolve("wallets", BaseDirectory::AppLocalData) // .path()
.map_err(|_| ())?; // .resolve("wallets", BaseDirectory::AppLocalData)
let mut wallets: HashMap<String, LocalWalletStorageV0> = // .map_err(|_| ())?;
get_wallets_from_localstorage(app.clone()) // let mut wallets: HashMap<String, LocalWalletStorageV0> =
.await // get_all_wallets().unwrap_or(HashMap::new());
.unwrap_or(Some(HashMap::new())) // // check that the wallet is not already present in localStorage
.unwrap_or(HashMap::new()); // if wallets.get(&res.wallet_name).is_none() {
// check that the wallet is not already present in localStorage // let lws: LocalWalletStorageV0 = res.into();
if wallets.get(&res.wallet_name).is_none() { // wallets.insert(res.wallet_name.clone(), lws);
let lws: LocalWalletStorageV0 = res.into(); // let lws_ser = LocalWalletStorage::v0_to_vec(&wallets);
wallets.insert(res.wallet_name.clone(), lws); // let r = write(path.clone(), &lws_ser);
let lws_ser = LocalWalletStorage::v0_to_vec(wallets); // if r.is_err() {
let r = write(path.clone(), &lws_ser); // log_debug!("write {:?} {}", path, r.unwrap_err());
if r.is_err() { // return Err(());
log_debug!("write {:?} {}", path, r.unwrap_err()); // }
return Err(()); // let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user, app)?;
// Ok(sws)
// } else {
// Err(())
// }
// }
fn take_some_peer_last_seq_numbers(
peer_id: PubKey,
qty: u16,
mut path: PathBuf,
) -> Result<u64, NgError> {
std::fs::create_dir_all(path.clone()).unwrap();
path.push(peer_id.to_string());
log_debug!("{}", path.display());
let file = read(path.clone());
let (mut file_save, val) = match file {
Ok(ser) => {
let old_val = match SessionPeerLastSeq::deser(&ser)? {
SessionPeerLastSeq::V0(v) => v,
_ => unimplemented!(),
};
(
OpenOptions::new()
.write(true)
.open(path)
.map_err(|_| NgError::SerializationError)?,
old_val,
)
} }
let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user, app)?; Err(_) => (
Ok(sws) File::create(path).map_err(|_| NgError::SerializationError)?,
} else { 0,
Err(()) ),
} };
let new_val = val + qty as u64;
let spls = SessionPeerLastSeq::V0(new_val);
let ser = spls.ser()?;
file_save
.write_all(&ser)
.map_err(|_| NgError::SerializationError)?;
file_save
.sync_data()
.map_err(|_| NgError::SerializationError)?;
Ok(val)
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_open_file(file: Vec<u8>, app: tauri::AppHandle) -> Result<Wallet, String> { async fn wallet_read_file(file: Vec<u8>, app: tauri::AppHandle) -> Result<Wallet, String> {
let ngf: NgFile = file.try_into().map_err(|e: NgError| e.to_string())?; nextgraph::local_broker::wallet_read_file(file)
if let NgFile::V0(NgFileV0::Wallet(wallet)) = ngf { .await
let mut wallets: HashMap<String, LocalWalletStorageV0> = .map_err(|e: NgError| e.to_string())
get_wallets_from_localstorage(app.clone()) }
.await
.unwrap_or(Some(HashMap::new())) #[tauri::command(rename_all = "snake_case")]
.unwrap_or(HashMap::new()); async fn wallet_was_opened(
// check that the wallet is not already present in localStorage opened_wallet: SensitiveWallet,
let wallet_name = wallet.name(); app: tauri::AppHandle,
if wallets.get(&wallet_name).is_none() { ) -> Result<ClientV0, String> {
Ok(wallet) nextgraph::local_broker::wallet_was_opened(opened_wallet)
} else { .await
Err("Wallet already present on this device".to_string()) .map_err(|e: NgError| e.to_string())
}
} else {
Err("File does not contain a wallet".to_string())
}
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_import( async fn wallet_import(
previous_wallet: Wallet, encrypted_wallet: Wallet,
opened_wallet: EncryptedWallet, opened_wallet: SensitiveWallet,
in_memory: bool,
app: tauri::AppHandle, app: tauri::AppHandle,
) -> Result<(String, ClientV0), String> { ) -> Result<ClientV0, String> {
let path = app nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory)
.path() .await
.resolve("wallets", BaseDirectory::AppLocalData) .map_err(|e: NgError| e.to_string())
.map_err(|_| "wallet directory error".to_string())?;
let mut wallets: HashMap<String, LocalWalletStorageV0> = // let path = app
get_wallets_from_localstorage(app.clone()) // .path()
.await // .resolve("wallets", BaseDirectory::AppLocalData)
.unwrap_or(Some(HashMap::new())) // .map_err(|_| "wallet directory error".to_string())?;
.unwrap_or(HashMap::new()); // let mut wallets: HashMap<String, LocalWalletStorageV0> =
// check that the wallet is not already present in localStorage // get_all_wallets().unwrap_or(HashMap::new());
let EncryptedWallet::V0(mut opened_wallet_v0) = opened_wallet; // // check that the wallet is not already present in localStorage
let wallet_name = opened_wallet_v0.wallet_id.clone(); // let SensitiveWallet::V0(mut opened_wallet_v0) = opened_wallet;
if wallets.get(&wallet_name).is_none() { // let wallet_name = opened_wallet_v0.wallet_id.clone();
let session = save_new_session( // if wallets.get(&wallet_name).is_none() {
&wallet_name, // let session = save_new_session(
opened_wallet_v0.wallet_privkey.to_pub(), // &wallet_name,
opened_wallet_v0.personal_site, // opened_wallet_v0.wallet_privkey.to_pub(),
app, // opened_wallet_v0.personal_site,
) // app,
.map_err(|_| "Cannot create new session".to_string())?; // )
let (wallet, client_id, client) = opened_wallet_v0 // .map_err(|_| "Cannot create new session".to_string())?;
.import(previous_wallet, session) // let lws = opened_wallet_v0
.map_err(|e| e.to_string())?; // .import(previous_wallet)
let lws = LocalWalletStorageV0::new(wallet, &client); // .map_err(|e| e.to_string())?;
// //let lws = LocalWalletStorageV0::new(wallet, &client);
wallets.insert(wallet_name, lws);
let lws_ser = LocalWalletStorage::v0_to_vec(wallets); // wallets.insert(wallet_name, lws);
let r = write(path.clone(), &lws_ser); // let lws_ser = LocalWalletStorage::v0_to_vec(&wallets);
if r.is_err() { // let r = write(path.clone(), &lws_ser);
log_debug!("write {:?} {}", path, r.unwrap_err()); // if r.is_err() {
Err("Write error".to_string()) // log_debug!("write {:?} {}", path, r.unwrap_err());
} else { // Err("Write error".to_string())
Ok((client_id, client)) // } else {
} // Ok(client)
} else { // }
Err("Already present on this device".to_string()) // } else {
} // Err("Already present on this device".to_string())
// }
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn get_wallets_from_localstorage( async fn get_wallets(
app: tauri::AppHandle, app: tauri::AppHandle,
) -> Result<Option<HashMap<String, LocalWalletStorageV0>>, ()> { ) -> Result<Option<HashMap<String, LocalWalletStorageV0>>, String> {
let path = app let path = app
.path() .path()
.resolve("wallets", BaseDirectory::AppLocalData) .resolve("", BaseDirectory::AppLocalData)
.map_err(|_| ())?; .map_err(|_| NgError::SerializationError)
let map_ser = read(path); .unwrap();
if map_ser.is_ok() { init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await;
let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap());
let LocalWalletStorage::V0(v0) = wallets; let res = get_all_wallets().await.map_err(|e| {
return Ok(Some(v0)); log_err!("get_all_wallets error {}", e.to_string());
});
if res.is_ok() {
return Ok(Some(res.unwrap()));
} }
Ok(None) Ok(None)
} }
fn save_new_session( // fn save_new_session(
wallet_name: &String, // wallet_name: &String,
wallet_id: PubKey, // wallet_id: PubKey,
user: PubKey, // user: PubKey,
app: tauri::AppHandle, // app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> { // ) -> Result<SessionWalletStorageV0, ()> {
let mut path = app // let mut path = app
.path() // .path()
.resolve("sessions", BaseDirectory::AppLocalData) // .resolve("sessions", BaseDirectory::AppLocalData)
.map_err(|_| ())?; // .map_err(|_| ())?;
let session_v0 = create_new_session(wallet_id, user); // let session_v0 = create_new_session(wallet_id, user);
if session_v0.is_err() { // if session_v0.is_err() {
log_debug!("create_new_session failed {}", session_v0.unwrap_err()); // log_debug!("create_new_session failed {}", session_v0.unwrap_err());
return Err(()); // return Err(());
} // }
let sws = session_v0.unwrap(); // let sws = session_v0.unwrap();
std::fs::create_dir_all(path.clone()).unwrap(); // std::fs::create_dir_all(path.clone()).unwrap();
path.push(wallet_name); // path.push(wallet_name);
let res = write(path.clone(), &sws.1); // let res = write(path.clone(), &sws.1);
if res.is_err() { // if res.is_err() {
log_debug!("write {:?} {}", path, res.unwrap_err()); // log_debug!("write {:?} {}", path, res.unwrap_err());
return Err(()); // return Err(());
} // }
Ok(sws.0) // Ok(sws.0)
} // }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn get_local_session( async fn session_start(
id: String, wallet_name: String,
key: PrivKey,
user: PubKey, user: PubKey,
app: tauri::AppHandle, app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> { ) -> Result<SessionPeerStorageV0, String> {
let path = app let config = SessionConfig::V0(SessionConfigV0 {
.path() user_id: user,
.resolve(format!("sessions/{id}"), BaseDirectory::AppLocalData) wallet_name,
.map_err(|_| ())?; });
let res = read(path.clone()); nextgraph::local_broker::session_start(config)
if res.is_ok() { .await
log_debug!("RESUMING SESSION"); .map_err(|e: NgError| e.to_string())
let v0 = dec_session(key, &res.unwrap());
if v0.is_ok() {
return Ok(v0.unwrap());
}
}
// create a new session
let wallet_id: PubKey = id.as_str().try_into().unwrap();
save_new_session(&id, wallet_id, user, app)
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
@ -368,6 +425,7 @@ async fn disconnections_subscribe(app: tauri::AppHandle) -> Result<(), ()> {
main_window: tauri::Window, main_window: tauri::Window,
) -> ResultSend<()> { ) -> ResultSend<()> {
while let Some(user_id) = reader.next().await { while let Some(user_id) = reader.next().await {
log_debug!("DISCONNECTION FOR {user_id}");
main_window.emit("disconnections", user_id).unwrap(); main_window.emit("disconnections", user_id).unwrap();
} }
log_debug!("END OF disconnections listener"); log_debug!("END OF disconnections listener");
@ -406,8 +464,24 @@ async fn doc_get_file_from_store_with_object_ref(
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn broker_disconnect() { async fn session_stop(user_id: UserId) -> Result<(), String> {
Broker::close_all_connections().await; nextgraph::local_broker::session_stop(user_id)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn user_disconnect(user_id: UserId) -> Result<(), String> {
nextgraph::local_broker::user_disconnect(user_id)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_close(wallet_name: String) -> Result<(), String> {
nextgraph::local_broker::wallet_close(wallet_name)
.await
.map_err(|e: NgError| e.to_string())
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -419,24 +493,16 @@ struct ConnectionInfo {
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn broker_connect( async fn user_connect(
client: PubKey,
info: ClientInfo, info: ClientInfo,
session: HashMap<String, SessionPeerStorageV0>, user_id: UserId,
opened_wallet: EncryptedWallet,
location: Option<String>, location: Option<String>,
) -> Result<HashMap<String, ConnectionInfo>, String> { ) -> Result<HashMap<String, ConnectionInfo>, String> {
let mut opened_connections: HashMap<String, ConnectionInfo> = HashMap::new(); let mut opened_connections: HashMap<String, ConnectionInfo> = HashMap::new();
let results = connect_wallet( let results = nextgraph::local_broker::user_connect(info, user_id, None)
client, .await
info, .map_err(|e| e.to_string())?;
session,
opened_wallet,
None,
Box::new(ConnectionWebSocket {}),
)
.await?;
log_debug!("{:?}", results); log_debug!("{:?}", results);
@ -452,7 +518,7 @@ async fn broker_connect(
); );
} }
BROKER.read().await.print_status(); //BROKER.read().await.print_status();
Ok(opened_connections) Ok(opened_connections)
} }
@ -488,6 +554,7 @@ impl AppBuilder {
if let Some(setup) = setup { if let Some(setup) = setup {
(setup)(app)?; (setup)(app)?;
} }
for domain in ALLOWED_BSP_DOMAINS { for domain in ALLOWED_BSP_DOMAINS {
app.ipc_scope().configure_remote_access( app.ipc_scope().configure_remote_access(
RemoteDomainAccessScope::new(domain) RemoteDomainAccessScope::new(domain)
@ -506,18 +573,22 @@ impl AppBuilder {
doc_get_file_from_store_with_object_ref, doc_get_file_from_store_with_object_ref,
wallet_gen_shuffle_for_pazzle_opening, wallet_gen_shuffle_for_pazzle_opening,
wallet_gen_shuffle_for_pin, wallet_gen_shuffle_for_pin,
wallet_open_wallet_with_pazzle, wallet_open_with_pazzle,
wallet_create_wallet, wallet_was_opened,
wallet_open_file, wallet_create,
wallet_read_file,
wallet_download_file,
wallet_import, wallet_import,
wallet_close,
encode_create_account, encode_create_account,
get_local_session, session_start,
get_wallets_from_localstorage, session_stop,
get_wallets,
open_window, open_window,
decode_invitation, decode_invitation,
disconnections_subscribe, disconnections_subscribe,
broker_connect, user_connect,
broker_disconnect, user_disconnect,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

@ -52,6 +52,11 @@
let wallet_channel; let wallet_channel;
let unsub_main_close; let unsub_main_close;
// window.refresh_wallets = async () => {
// let walls = await ng.get_wallets();
// wallets.set(walls);
// };
onMount(async () => { onMount(async () => {
try { try {
await disconnections_subscribe(); await disconnections_subscribe();
@ -59,15 +64,27 @@
console.log("called disconnections_subscribe twice"); console.log("called disconnections_subscribe twice");
} }
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
//console.log(await ng.test());
if (tauri_platform) { if (tauri_platform) {
//console.log(await ng.test()); let walls = await ng.get_wallets();
let walls = await ng.get_wallets_from_localstorage();
wallets.set(walls); wallets.set(walls);
unsubscribe = active_wallet.subscribe((value) => { unsubscribe = active_wallet.subscribe(async (value) => {
if (value && !value.wallet) { if (value) {
active_wallet.set(undefined); if (value.wallet) {
push("#/wallet/login"); opened_wallets.update((w) => {
w[value.id] = value.wallet;
return w;
});
} else {
await ng.wallet_close(value.id);
active_wallet.set(undefined);
opened_wallets.update((w) => {
delete w[value.id];
return w;
});
push("#/wallet/login");
}
} }
}); });
@ -87,26 +104,47 @@
} }
}); });
} else { } else {
// ON WEB CLIENTS
window.addEventListener("storage", async (event) => { window.addEventListener("storage", async (event) => {
if (event.storageArea != localStorage) return; if (event.storageArea != localStorage) return;
if (event.key === "ng_wallets") { if (event.key === "ng_wallets") {
wallets.set(await ng.get_wallets_from_localstorage()); await ng.reload_wallets();
wallets.set(await ng.get_wallets());
} }
}); });
wallets.set(await ng.get_wallets_from_localstorage()); wallets.set(await ng.get_wallets());
// TODO: check the possibility of XS-Leaks. I don't see any, but it should be checked
// https://github.com/privacycg/storage-partitioning
// https://github.com/whatwg/html/issues/5803
// https://w3cping.github.io/privacy-threat-model/
// https://chromium.googlesource.com/chromium/src/+/fa17a6142f99d58de533d65cd8f3cd0e9a8ee58e
// https://bugs.webkit.org/show_bug.cgi?id=229814
wallet_channel = new BroadcastChannel("ng_wallet"); wallet_channel = new BroadcastChannel("ng_wallet");
wallet_channel.postMessage({ cmd: "is_opened" }, location.href); window.wallet_channel = wallet_channel;
wallet_channel.postMessage({ cmd: "startup" }, location.href);
wallet_channel.onmessage = async (event) => { wallet_channel.onmessage = async (event) => {
console.log(event.data.cmd, event.data); console.log(event.data.cmd, event.data);
if (!location.href.startsWith(event.origin)) return; if (!location.href.startsWith(event.origin)) return;
switch (event.data.cmd) { switch (event.data.cmd) {
case "is_opened": case "startup":
if ($active_wallet && $active_wallet.wallet) { for (let saved_id of Object.keys($wallets)) {
wallet_channel.postMessage( if ($wallets[saved_id].in_memory) {
{ cmd: "opened", wallet: $active_wallet }, wallet_channel.postMessage(
location.href {
); cmd: "new_in_mem",
name: saved_id,
lws: $wallets[saved_id],
},
location.href
);
}
} }
// if ($active_wallet && $active_wallet.wallet) {
// wallet_channel.postMessage(
// { cmd: "opened", wallet: $active_wallet },
// location.href
// );
// }
for (let opened of Object.keys($opened_wallets)) { for (let opened of Object.keys($opened_wallets)) {
wallet_channel.postMessage( wallet_channel.postMessage(
{ {
@ -116,20 +154,54 @@
location.href location.href
); );
} }
break; break;
case "opened": case "opened":
if (!$opened_wallets[event.data.wallet.id]) { if (!$opened_wallets[event.data.wallet.id]) {
console.log(
"ADDING TO OPENED",
event.data.wallet.id,
JSON.stringify($opened_wallets),
event.data.wallet.wallet
);
try {
await ng.wallet_was_opened(event.data.wallet.wallet);
} catch (e) {
console.error(e);
}
opened_wallets.update((w) => { opened_wallets.update((w) => {
w[event.data.wallet.id] = event.data.wallet.wallet; w[event.data.wallet.id] = event.data.wallet.wallet;
return w; return w;
}); });
} }
break; break;
case "new_in_mem":
console.log("GOT new_in_mem", event.data);
if (event.data.lws) {
if (!$wallets[event.data.name]) {
await ng.add_in_memory_wallet(event.data.lws);
wallets.update((w) => {
w[event.data.name] = event.data.lws;
return w;
});
}
}
if (event.data.opened) {
if (!$opened_wallets[event.data.name]) {
await ng.wallet_was_opened(event.data.opened);
opened_wallets.update((w) => {
w[event.data.name] = event.data.opened;
return w;
});
}
}
break;
case "closed": case "closed":
opened_wallets.update((w) => { opened_wallets.update((w) => {
delete w[event.data.walletid]; delete w[event.data.walletid];
return w; return w;
}); });
await ng.wallet_close(event.data.walletid);
if ($active_wallet && $active_wallet.id == event.data.walletid) { if ($active_wallet && $active_wallet.id == event.data.walletid) {
await close_active_session(); await close_active_session();
active_wallet.set(undefined); active_wallet.set(undefined);
@ -138,9 +210,13 @@
break; break;
} }
}; };
unsubscribe = active_wallet.subscribe((value) => { unsubscribe = active_wallet.subscribe(async (value) => {
if (value) { if (value) {
if (value.wallet) { if (value.wallet) {
opened_wallets.update((w) => {
w[value.id] = value.wallet;
return w;
});
wallet_channel.postMessage( wallet_channel.postMessage(
{ cmd: "opened", wallet: value }, { cmd: "opened", wallet: value },
location.href location.href
@ -151,6 +227,7 @@
location.href location.href
); );
active_wallet.set(undefined); active_wallet.set(undefined);
await ng.wallet_close(value.id);
//active_session.set(undefined); //active_session.set(undefined);
opened_wallets.update((w) => { opened_wallets.update((w) => {
delete w[value.id]; delete w[value.id];
@ -171,9 +248,10 @@
</script> </script>
<!-- <p> <!-- <p>
{JSON.stringify(Object.keys($wallets))} {!$active_session}
{JSON.stringify($active_wallet)} {JSON.stringify(Object.keys($wallets))}
{JSON.stringify(Object.keys($opened_wallets))} {JSON.stringify($active_wallet)}
{JSON.stringify($active_session)} {JSON.stringify(Object.keys($opened_wallets))}
</p> --> {JSON.stringify($active_session)}
</p> -->
<Router {routes} /> <Router {routes} />

@ -16,20 +16,25 @@ const mapping = {
"doc_get_file_from_store_with_object_ref": [ "nuri","obj_ref" ], "doc_get_file_from_store_with_object_ref": [ "nuri","obj_ref" ],
"wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"], "wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"],
"wallet_gen_shuffle_for_pin": [], "wallet_gen_shuffle_for_pin": [],
"wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"], "wallet_open_with_pazzle": ["wallet","pazzle","pin"],
"wallet_create_wallet": ["params"], "wallet_was_opened": ["opened_wallet"],
"wallet_open_file": ["file"], "wallet_create": ["params"],
"wallet_import": ["previous_wallet","opened_wallet"], "wallet_read_file": ["file"],
"wallet_download_file": ["wallet_name"],
"wallet_import": ["encrypted_wallet","opened_wallet","in_memory"],
"wallet_close": ["wallet_name"],
"encode_create_account": ["payload"], "encode_create_account": ["payload"],
"get_local_session": ["id","key","user"], "session_start": ["wallet_name","user"],
"get_wallets_from_localstorage": [], "session_stop": ["user_id"],
"get_wallets": [],
"open_window": ["url","label","title"], "open_window": ["url","label","title"],
"decode_invitation": ["invite"], "decode_invitation": ["invite"],
"broker_disconnect": [], "user_connect": ["info","user_id","location"],
"broker_connect": ["client","info","session","opened_wallet","location"], "user_disconnect": ["user_id"],
"test": [ ] "test": [ ]
} }
let lastStreamId = 0; let lastStreamId = 0;
const handler = { const handler = {
@ -42,22 +47,14 @@ const handler = {
client_info.V0.version=version; client_info.V0.version=version;
//console.log(client_info); //console.log(client_info);
return client_info; return client_info;
} else if (path[0] === "get_wallets_from_localstorage") { } else if (path[0] === "get_wallets") {
let wallets = await Reflect.apply(sdk[path], caller, args); let wallets = await Reflect.apply(sdk[path], caller, args);
return Object.fromEntries(wallets || []); return Object.fromEntries(wallets || []);
} else if (path[0] === "get_local_session") { } else if (path[0] === "session_start") {
let res = await Reflect.apply(sdk[path], caller, args); let res = await Reflect.apply(sdk[path], caller, args);
let v = res.users.values().next().value;
v.branches_last_seq = Object.fromEntries(v.branches_last_seq);
res.users = Object.fromEntries(res.users);
return res; return res;
} else if (path[0] === "wallet_create_wallet") { } else if (path[0] === "wallet_create") {
let res = await Reflect.apply(sdk[path], caller, args); let res = await Reflect.apply(sdk[path], caller, args);
if (res[1]) {
let v = res[1].users.values().next().value;
v.branches_last_seq = Object.fromEntries(v.branches_last_seq);
res[1].users = Object.fromEntries(res[1].users);
}
return res; return res;
} else { } else {
return Reflect.apply(sdk[path], caller, args) return Reflect.apply(sdk[path], caller, args)
@ -101,7 +98,7 @@ const handler = {
return () => { return () => {
unlisten(); unlisten();
} }
} else if (path[0] === "broker_connect") { } else if (path[0] === "user_connect") {
let arg = {}; let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el) args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
let ret = await tauri.invoke(path[0],arg); let ret = await tauri.invoke(path[0],arg);
@ -132,31 +129,31 @@ const handler = {
res['File'].V0.content = Uint8Array.from(res['File'].V0.content); res['File'].V0.content = Uint8Array.from(res['File'].V0.content);
res['File'].V0.metadata = Uint8Array.from(res['File'].V0.metadata); res['File'].V0.metadata = Uint8Array.from(res['File'].V0.metadata);
return res; return res;
} else if (path[0] === "get_wallets_from_localstorage") { } else if (path[0] === "get_wallets") {
let res = await tauri.invoke(path[0],{}); let res = await tauri.invoke(path[0],{});
if (res) for (let e of Object.entries(res)) { if (res) for (let e of Object.entries(res)) {
e[1].wallet.V0.content.security_img = Uint8Array.from(e[1].wallet.V0.content.security_img); e[1].wallet.V0.content.security_img = Uint8Array.from(e[1].wallet.V0.content.security_img);
} }
return res || {}; return res || {};
} else if (path[0] === "wallet_create_wallet") { } else if (path[0] === "wallet_create") {
let params = args[0]; let params = args[0];
params.result_with_wallet_file = false; params.result_with_wallet_file = false;
params.security_img = Array.from(new Uint8Array(params.security_img)); params.security_img = Array.from(new Uint8Array(params.security_img));
return await tauri.invoke(path[0],{params}) return await tauri.invoke(path[0],{params})
} else if (path[0] === "wallet_open_file") { } else if (path[0] === "wallet_read_file") {
let file = args[0]; let file = args[0];
file = Array.from(new Uint8Array(file)); file = Array.from(new Uint8Array(file));
return await tauri.invoke(path[0],{file}) return await tauri.invoke(path[0],{file})
} else if (path[0] === "wallet_import") { } else if (path[0] === "wallet_import") {
let previous_wallet = args[0]; let encrypted_wallet = args[0];
previous_wallet.V0.content.security_img = Array.from(new Uint8Array(previous_wallet.V0.content.security_img)); encrypted_wallet.V0.content.security_img = Array.from(new Uint8Array(encrypted_wallet.V0.content.security_img));
return await tauri.invoke(path[0],{previous_wallet, opened_wallet:args[1]}) return await tauri.invoke(path[0],{encrypted_wallet, opened_wallet:args[1], in_memory:args[2]})
} else if (path[0] && path[0].startsWith("get_local_bootstrap")) { } else if (path[0] && path[0].startsWith("get_local_bootstrap")) {
return false; return false;
} else if (path[0] === "get_local_url") { } else if (path[0] === "get_local_url") {
return false; return false;
} else if (path[0] === "wallet_open_wallet_with_pazzle") { } else if (path[0] === "wallet_open_with_pazzle") {
let arg:any = {}; let arg:any = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el) args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img)); let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img));

@ -10,13 +10,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { import { online } from "../store";
online,
wallets,
active_wallet,
active_session,
connections,
} from "../store";
import FullLayout from "./FullLayout.svelte"; import FullLayout from "./FullLayout.svelte";
import { PaperAirplane, Bell, ArrowRightOnRectangle } from "svelte-heros-v2"; import { PaperAirplane, Bell, ArrowRightOnRectangle } from "svelte-heros-v2";
// @ts-ignore // @ts-ignore

@ -10,13 +10,14 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Alert } from "flowbite-svelte"; import { Alert, Toggle } from "flowbite-svelte";
import { onMount, createEventDispatcher, tick } from "svelte"; import { onMount, createEventDispatcher, tick } from "svelte";
import ng from "../api"; import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis"; import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
import { PuzzlePiece } from "svelte-heros-v2"; import { PuzzlePiece } from "svelte-heros-v2";
//import Worker from "../worker.js?worker&inline"; //import Worker from "../worker.js?worker&inline";
export let wallet; export let wallet;
export let for_import = false;
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios"; let mobile = tauri_platform == "android" || tauri_platform == "ios";
@ -81,6 +82,8 @@
let error; let error;
let trusted = false;
function order() { function order() {
step = "order"; step = "order";
ordered = []; ordered = [];
@ -130,20 +133,30 @@
// open the wallet // open the wallet
try { try {
if (tauri_platform) { if (tauri_platform) {
let secret_wallet = await ng.wallet_open_wallet_with_pazzle( let opened_wallet = await ng.wallet_open_with_pazzle(
wallet, wallet,
pazzle, pazzle,
pin_code pin_code
); );
// try {
// let client = await ng.wallet_was_opened(opened_wallet);
// opened_wallet.V0.client = client;
// } catch (e) {
// console.log(e);
// error = e;
// step = "end";
// dispatch("error", { error: e });
// return;
// }
step = "end"; step = "end";
dispatch("opened", { dispatch("opened", {
wallet: secret_wallet, wallet: opened_wallet,
id: secret_wallet.V0.wallet_id, id: opened_wallet.V0.wallet_id,
trusted,
}); });
} else { } else {
let worker_import = await import("../worker.js?worker&inline"); let worker_import = await import("../worker.js?worker&inline");
const myWorker = new worker_import.default(); const myWorker = new worker_import.default();
//const myWorker = new Worker();
myWorker.onerror = (e) => { myWorker.onerror = (e) => {
console.error(e); console.error(e);
error = e; error = e;
@ -156,16 +169,28 @@
step = "end"; step = "end";
dispatch("error", { error: e }); dispatch("error", { error: e });
}; };
myWorker.onmessage = (msg) => { myWorker.onmessage = async (msg) => {
//console.log("Message received from worker", msg.data); //console.log("Message received from worker", msg.data);
if (msg.data.loaded) { if (msg.data.loaded) {
myWorker.postMessage({ wallet, pazzle, pin_code }); myWorker.postMessage({ wallet, pazzle, pin_code });
//console.log("postMessage"); //console.log("postMessage");
} else if (msg.data.success) { } else if (msg.data.success) {
//console.log(msg.data);
// try {
// let client = await ng.wallet_was_opened(msg.data.success);
// msg.data.success.V0.client = client;
// } catch (e) {
// console.log(e);
// error = e;
// step = "end";
// dispatch("error", { error: e });
// return;
// }
step = "end"; step = "end";
dispatch("opened", { dispatch("opened", {
wallet: msg.data.success, wallet: msg.data.success,
id: msg.data.success.V0.wallet_id, id: msg.data.success.V0.wallet_id,
trusted,
}); });
} else { } else {
console.error(msg.data.error); console.error(msg.data.error);
@ -216,13 +241,13 @@
</script> </script>
{#if step == "load"} {#if step == "load"}
<div class=" max-w-xl lg:px-8 mx-auto px-4 mb-10"> <div class=" max-w-xl lg:px-8 mx-auto px-4 mt-10">
<h2 class="pb-5 text-xl">How to open your wallet, step by step :</h2> <h2 class="pb-5 text-xl">How to open your wallet, step by step :</h2>
<ul class="mb-8 ml-3 space-y-4 text-left list-decimal"> <ul class="mb-8 ml-3 space-y-4 text-left list-decimal">
<li> <li>
For each category of images, you will be presented with the 15 possible For each one of the 9 categories of images, you will be presented with
image choices. The categories are shuffled at every login. They will not the 15 possible image choices. The categories are shuffled at every
always appear in the same order. login. They will not always appear in the same order.
</li> </li>
<li> <li>
At each category, only one of the 15 displayed choices is the correct At each category, only one of the 15 displayed choices is the correct
@ -230,7 +255,7 @@
The 15 images are shuffled too, they will not appear at the same The 15 images are shuffled too, they will not appear at the same
position at each login. On a computer, you can also use the tab key on position at each login. On a computer, you can also use the tab key on
your keyboard to move to the desired item on the screen, then press the your keyboard to move to the desired item on the screen, then press the
space bar. space bar to select each one.
</li> </li>
<li> <li>
Once you completed the last category, you will be presented with all the Once you completed the last category, you will be presented with all the
@ -254,11 +279,11 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700"> <div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
{#if !loaded} {#if !loaded}
Loading... Loading pazzle...
<svg <svg
class="animate-spin mt-2 h-14 w-14 mx-auto" class="animate-spin my-4 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@ -291,6 +316,24 @@
</button> </button>
{/if} {/if}
</div> </div>
{#if for_import}
<div class=" max-w-xl lg:px-8 mx-auto px-4 mb-8">
<span class="text-xl">Do you trust this device? </span> <br />
<div class="flex justify-center items-center my-4">
<Toggle class="" bind:checked={trusted}
>Save my wallet on this device</Toggle
>
</div>
<p class="text-sm">
If you do, if this device is yours or is used by few trusted persons of
your family or workplace, and you would like to login again from this
device in the future, then you can save your wallet on this device. To
the contrary, if this device is public and shared by strangers, do not
save your wallet here. {#if !tauri_platform}By selecting this option,
you agree to save some cookies on your browser.{/if}<br />
</p>
</div>
{/if}
{:else if step == "pazzle"} {:else if step == "pazzle"}
<div <div
class="h-screen aspect-[3/5] pazzleline" class="h-screen aspect-[3/5] pazzleline"

@ -16,14 +16,7 @@
import NoWallet from "../lib/NoWallet.svelte"; import NoWallet from "../lib/NoWallet.svelte";
import { push } from "svelte-spa-router"; import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte"; import { onMount, onDestroy } from "svelte";
import { import { active_wallet, has_wallets, derived } from "../store";
wallets,
active_wallet,
opened_wallets,
active_session,
has_wallets,
derived,
} from "../store";
let display_login_create = !$has_wallets || !$active_wallet; let display_login_create = !$has_wallets || !$active_wallet;
let unsubscribe; let unsubscribe;

@ -16,14 +16,7 @@
import { push } from "svelte-spa-router"; import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte"; import { onMount, onDestroy } from "svelte";
import CenteredLayout from "../lib/CenteredLayout.svelte"; import CenteredLayout from "../lib/CenteredLayout.svelte";
import { import { has_wallets } from "../store";
wallets,
active_wallet,
opened_wallets,
active_session,
has_wallets,
derived,
} from "../store";
let display_has_wallets_warning: boolean = $has_wallets != 0; let display_has_wallets_warning: boolean = $has_wallets != 0;
let unsubscribe; let unsubscribe;

@ -9,6 +9,8 @@
// according to those terms. // according to those terms.
--> -->
<script> <script>
// @ts-nocheck
import { Button, Alert, Dropzone, Toggle } from "flowbite-svelte"; import { Button, Alert, Dropzone, Toggle } from "flowbite-svelte";
import { link, push } from "svelte-spa-router"; import { link, push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte"; import CenteredLayout from "../lib/CenteredLayout.svelte";
@ -21,12 +23,15 @@
Signal, Signal,
SignalSlash, SignalSlash,
ArrowRightOnRectangle, ArrowRightOnRectangle,
ArrowsRightLeft,
Cog6Tooth, Cog6Tooth,
PuzzlePiece, PuzzlePiece,
Key, Key,
User, User,
Gift, Gift,
InformationCircle, InformationCircle,
DocumentArrowDown,
NoSymbol,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import { import {
@ -39,6 +44,7 @@
import { import {
online, online,
close_active_wallet, close_active_wallet,
close_active_session,
active_session, active_session,
active_wallet, active_wallet,
connections, connections,
@ -59,6 +65,7 @@
"flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"; "flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700";
let top; let top;
async function scrollToTop() { async function scrollToTop() {
await tick(); await tick();
top.scrollIntoView(); top.scrollIntoView();
@ -75,8 +82,40 @@
await close_active_wallet(); await close_active_wallet();
} }
async function close_session() {
await close_active_session();
active_wallet.set(undefined);
push("#/wallet/login");
}
let downloading = false;
let wallet_file_ready = false;
let download_link = false;
let download_error = false;
async function download_wallet() {
try {
downloading = true;
let file = await ng.wallet_download_file($active_wallet.id);
// @ts-ignore
wallet_file_ready = "wallet-" + $active_wallet.id + ".ngw";
if (!tauri_platform) {
const blob = new Blob([file], {
type: "application/octet-stream",
});
// @ts-ignore
download_link = URL.createObjectURL(blob);
} else {
download_link = true;
}
} catch (e) {
download_error = e;
}
}
$: personal_site = $active_wallet?.wallet?.V0.personal_site_id; $: personal_site = $active_wallet?.wallet?.V0.personal_site_id;
$: personal_site_id = $active_wallet?.wallet?.V0.personal_site;
$: personal_site_status = $connections[personal_site]; $: personal_site_status = $connections[personal_site];
const displayPopup = async (url, title) => { const displayPopup = async (url, title) => {
@ -152,7 +191,88 @@
/> />
<span class="ml-3">Logout</span> <span class="ml-3">Logout</span>
</li> </li>
<li
tabindex="0"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={close_session}
on:click={close_session}
>
<ArrowsRightLeft
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">Switch wallet</span>
</li>
{#if !downloading}
<li
tabindex="0"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={download_wallet}
on:click={download_wallet}
>
<DocumentArrowDown
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">Download wallet file</span>
</li>
{:else if download_error}
<li
tabindex="-1"
class="flex items-center p-2 text-base font-normal text-red-700 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<NoSymbol
tabindex="-1"
class="w-7 h-7 text-red-700 transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3 text-left"
>Download failed:<br /> {download_error}</span
>
</li>
{:else if !wallet_file_ready}
<li
tabindex="-1"
class="flex items-center p-2 text-base font-normal text-blue-700 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<DocumentArrowDown
tabindex="-1"
class="w-7 h-7 text-blue-700 transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3 text-left">Download in progress...</span>
</li>
{:else if download_link === true}
<li
tabindex="-1"
class="flex p-2 text-sm text-left break-all font-normal text-blue-700 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<span
>You will find the file named "{wallet_file_ready}" <br />in
your Downloads folder</span
>
</li>
{:else}
<li
tabindex="-1"
class="flex items-center text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<a
href={download_link}
target="_blank"
download={wallet_file_ready}
>
<button
tabindex="-1"
class=" text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<DocumentArrowDown
tabindex="-1"
class="w-14 h-14 transition duration-75 dark:text-white dark:group-hover:text-white"
/>
Click here to download the wallet file
</button>
</a>
</li>
{/if}
<SidebarItem label="Settings" href="#/user/settings" class="p-2"> <SidebarItem label="Settings" href="#/user/settings" class="p-2">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<Cog6Tooth <Cog6Tooth
@ -201,7 +321,7 @@
} else { } else {
$connections[personal_site].error = "Stopped"; $connections[personal_site].error = "Stopped";
personal_site_status.since = new Date(); personal_site_status.since = new Date();
await ng.broker_disconnect(); await ng.user_disconnect(personal_site_id);
} }
}} }}
checked={personal_site_status && checked={personal_site_status &&

@ -44,7 +44,7 @@
reader.readAsArrayBuffer(image); reader.readAsArrayBuffer(image);
reader.onload = async (e) => { reader.onload = async (e) => {
security_img = e.target.result; security_img = e.target.result;
console.log(security_img); //console.log(security_img);
var blob = new Blob([security_img], { var blob = new Blob([security_img], {
type: image.type, type: image.type,
}); });
@ -160,7 +160,7 @@
location.href, location.href,
param.get("i") param.get("i")
); );
console.log(invitation); console.log("invitation", invitation);
if (invitation && invitation.V0.url) { if (invitation && invitation.V0.url) {
pre_invitation = invitation; pre_invitation = invitation;
invitation = undefined; invitation = undefined;
@ -201,7 +201,6 @@
bootstrap: false, bootstrap: false,
pdf: false, pdf: false,
}; };
console.log("saved");
await tick(); await tick();
scrollToTop(); scrollToTop();
} }
@ -230,15 +229,23 @@
core_registration, core_registration,
additional_bootstrap, additional_bootstrap,
}; };
console.log(params); console.log("do wallet with params", params);
try { try {
let res = await ng.wallet_create_wallet(params); ready = await ng.wallet_create(params);
let walls = await ng.get_wallets_from_localstorage(); wallets.set(await ng.get_wallets());
wallets.set(walls); if (!options.trusted && !tauri_platform) {
if (res[1]) { let lws = $wallets[ready.wallet_name];
set_active_session(res[1]); if (lws.in_memory) {
let new_in_mem = {
lws,
name: ready.wallet_name,
opened: false,
cmd: "new_in_mem",
};
window.wallet_channel.postMessage(new_in_mem, location.href);
}
} }
ready = res[0];
console.log(display_pazzle(ready.pazzle)); console.log(display_pazzle(ready.pazzle));
download_name = "wallet-" + ready.wallet_name + ".ngw"; download_name = "wallet-" + ready.wallet_name + ".ngw";
if (options.cloud) { if (options.cloud) {
@ -251,7 +258,7 @@
download_link = URL.createObjectURL(blob); download_link = URL.createObjectURL(blob);
} }
} catch (e) { } catch (e) {
console.log(e); console.error(e);
error = e; error = e;
} }
} }

@ -26,6 +26,9 @@
set_active_session, set_active_session,
has_wallets, has_wallets,
} from "../store"; } from "../store";
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let wallet; let wallet;
let selected; let selected;
let step; let step;
@ -59,9 +62,8 @@
active_wallet_unsub = active_wallet.subscribe(async (value) => { active_wallet_unsub = active_wallet.subscribe(async (value) => {
if (value && value.wallet) { if (value && value.wallet) {
if (!$active_session) { if (!$active_session) {
let session = await ng.get_local_session( let session = await ng.session_start(
value.id, value.id,
value.wallet.V0.wallet_privkey,
value.wallet.V0.personal_site value.wallet.V0.personal_site
); );
//console.log(session); //console.log(session);
@ -91,20 +93,52 @@
async function gotWallet(event) { async function gotWallet(event) {
if (importing) { if (importing) {
try { try {
let new_client = await ng.wallet_import(wallet, event.detail.wallet); let in_memory = !event.detail.trusted;
event.detail.wallet.V0.clients[new_client[0]] = new_client[1]; //console.log("IMPORTING", in_memory, event.detail.wallet, wallet);
let walls = await ng.get_wallets_from_localstorage(); let client = await ng.wallet_import(
wallets.set(walls); wallet,
event.detail.wallet,
in_memory
);
event.detail.wallet.V0.client = client;
// refreshing the wallets
wallets.set(await ng.get_wallets());
//console.log($wallets);
let session = await ng.session_start(
event.detail.id,
event.detail.wallet.V0.personal_site
);
console.log(session);
if (session) {
set_active_session(session);
}
if (in_memory && !tauri_platform) {
// send a message in BroadcastChannel new_in_mem(lws, opened_wallet=event.detail.wallet).
let name = event.detail.id;
let lws = $wallets[name];
if (lws.in_memory) {
let new_in_mem = {
lws,
name,
opened: event.detail.wallet,
cmd: "new_in_mem",
};
window.wallet_channel.postMessage(new_in_mem, location.href);
}
}
} catch (e) { } catch (e) {
importing = false; importing = false;
wallet = undefined; wallet = undefined;
error = e; error = e;
return; return;
} }
} else {
let client = await ng.wallet_was_opened(event.detail.wallet);
event.detail.wallet.V0.client = client;
} }
active_wallet.set(event.detail); active_wallet.set(event.detail);
// wallet // { wallet,
// id // id }
} }
function cancelLogin(event) { function cancelLogin(event) {
importing = false; importing = false;
@ -127,7 +161,7 @@
reader.onload = async (e) => { reader.onload = async (e) => {
try { try {
//console.log(e.target.result); //console.log(e.target.result);
wallet = await ng.wallet_open_file(e.target.result); wallet = await ng.wallet_read_file(e.target.result);
importing = true; importing = true;
} catch (e) { } catch (e) {
error = e; error = e;
@ -178,6 +212,7 @@
{:else if wallet} {:else if wallet}
<Login <Login
{wallet} {wallet}
bind:for_import={importing}
on:error={gotError} on:error={gotError}
on:opened={gotWallet} on:opened={gotWallet}
on:cancel={cancelLogin} on:cancel={cancelLogin}

@ -49,7 +49,7 @@ export const has_wallets = derived(wallets,($wallets) => Object.keys($wallets).l
export const set_active_session = function(session) { export const set_active_session = function(session) {
active_session.set(session.users); active_session.set(session);
}; };
export { writable, readonly, derived }; export { writable, readonly, derived };
@ -64,12 +64,21 @@ export const close_active_wallet = async function() {
delete w.wallet; delete w.wallet;
return w; return w;
}); });
// this will also trigger the removal of the wallet from opened_wallets, and will close the wallet in all tabs.
} }
export const close_active_session = async function() { export const close_active_session = async function() {
let session = get(active_session);
//console.log("close_active_session",session);
if (!session) return;
await ng.session_stop(session.user);
connections.set({});
active_session.set(undefined); active_session.set(undefined);
await ng.broker_disconnect(); //console.log("setting active_session to undefined",get(active_session));
} }
@ -89,14 +98,11 @@ export const reconnect = async function() {
} }
console.log("attempting to connect..."); console.log("attempting to connect...");
try { try {
let client = get(wallets)[get(active_wallet).id].client;
let info = await ng.client_info() let info = await ng.client_info()
//console.log("Connecting with",client,info); //console.log("Connecting with",client,info);
connections.set(await ng.broker_connect( connections.set(await ng.user_connect(
client,
info, info,
get(active_session), get(active_session).user,
get(active_wallet).wallet,
location.href location.href
)); ));
}catch (e) { }catch (e) {

@ -505,7 +505,7 @@ let face = [
}, },
]; ];
let mammal = [ let big_animal = [
{ {
hexcode: "1f981", hexcode: "1f981",
shortcode: "lion", shortcode: "lion",
@ -522,7 +522,6 @@ let face = [
shortcode: "horse_face", shortcode: "horse_face",
code: "horse", code: "horse",
}, },
{ {
hexcode: "1f993", hexcode: "1f993",
shortcode: "zebra", shortcode: "zebra",
@ -538,7 +537,6 @@ let face = [
shortcode: "goat", shortcode: "goat",
code: "goat", code: "goat",
}, },
{ {
hexcode: "1f411", hexcode: "1f411",
shortcode: "ewe", shortcode: "ewe",
@ -554,7 +552,6 @@ let face = [
shortcode: "giraffe", shortcode: "giraffe",
code: "giraffe", code: "giraffe",
}, },
{ {
hexcode: "1f418", hexcode: "1f418",
shortcode: "elephant", shortcode: "elephant",
@ -570,16 +567,15 @@ let face = [
shortcode: "rabbit", shortcode: "rabbit",
code: "rabbit", code: "rabbit",
}, },
{ {
hexcode: "1f994", hexcode: "1f433",
shortcode: "hedgehog", shortcode: "spouting_whale",
code: "hedgehog", code: "whale",
}, },
{ {
hexcode: "1f987", hexcode: "1f42c",
shortcode: "bat", shortcode: "dolphin",
code: "bat", code: "dolphin",
}, },
{ {
hexcode: "1f43b_200d_2744", hexcode: "1f43b_200d_2744",
@ -605,7 +601,6 @@ let face = [
shortcode: "eagle", shortcode: "eagle",
code: "eagle", code: "eagle",
}, },
{ {
hexcode: "1f986", hexcode: "1f986",
shortcode: "duck", shortcode: "duck",
@ -621,7 +616,6 @@ let face = [
shortcode: "flamingo", shortcode: "flamingo",
code: "flamingo", code: "flamingo",
}, },
{ {
hexcode: "1f427", hexcode: "1f427",
shortcode: "penguin", shortcode: "penguin",
@ -637,23 +631,21 @@ let face = [
shortcode: "turtle", shortcode: "turtle",
code: "turtle", code: "turtle",
}, },
{ {
hexcode: "1f40d", hexcode: "1f40d",
shortcode: "snake", shortcode: "snake",
code: "snake", code: "snake",
}, },
{ {
hexcode: "1f433", hexcode: "1f994",
shortcode: "spouting_whale", shortcode: "hedgehog",
code: "whale", code: "hedgehog",
}, },
{ {
hexcode: "1f42c", hexcode: "1f987",
shortcode: "dolphin", shortcode: "bat",
code: "dolphin", code: "bat",
}, },
{ {
hexcode: "1f41f", hexcode: "1f41f",
shortcode: "fish", shortcode: "fish",
@ -688,7 +680,6 @@ let face = [
shortcode: "ant", shortcode: "ant",
code: "ant", code: "ant",
}, },
{ {
hexcode: "1f41d", hexcode: "1f41d",
shortcode: "honeybee", shortcode: "honeybee",
@ -1050,7 +1041,7 @@ let face = [
{ {
hexcode: "2744", hexcode: "2744",
shortcode: "snowflake", shortcode: "snowflake",
code: "snow", code: "snowflake",
}, },
{ {
@ -1470,35 +1461,30 @@ export async function load_svg() {
sport[13].svg = await import("./assets/pazzle/emoji_u1f3d3.svg?component"); sport[13].svg = await import("./assets/pazzle/emoji_u1f3d3.svg?component");
sport[14].svg = await import("./assets/pazzle/emoji_u1f94b.svg?component"); sport[14].svg = await import("./assets/pazzle/emoji_u1f94b.svg?component");
/************** MAMMAL *********************/ /************** BIG ANIMAL *********************/
mammal[0].svg = await import("./assets/pazzle/emoji_u1f981.svg?component"); big_animal[0].svg = await import("./assets/pazzle/emoji_u1f981.svg?component");
mammal[1].svg = await import("./assets/pazzle/emoji_u1f406.svg?component"); big_animal[1].svg = await import("./assets/pazzle/emoji_u1f406.svg?component");
mammal[2].svg = await import("./assets/pazzle/emoji_u1f434.svg?component"); big_animal[2].svg = await import("./assets/pazzle/emoji_u1f434.svg?component");
mammal[3].svg = await import("./assets/pazzle/emoji_u1f993.svg?component"); big_animal[3].svg = await import("./assets/pazzle/emoji_u1f993.svg?component");
mammal[4].svg = await import("./assets/pazzle/emoji_u1f416.svg?component"); big_animal[4].svg = await import("./assets/pazzle/emoji_u1f416.svg?component");
mammal[5].svg = await import("./assets/pazzle/emoji_u1f410.svg?component"); big_animal[5].svg = await import("./assets/pazzle/emoji_u1f410.svg?component");
mammal[6].svg = await import("./assets/pazzle/emoji_u1f411.svg?component"); big_animal[6].svg = await import("./assets/pazzle/emoji_u1f411.svg?component");
mammal[7].svg = await import("./assets/pazzle/emoji_u1f42a.svg?component"); big_animal[7].svg = await import("./assets/pazzle/emoji_u1f42a.svg?component");
mammal[8].svg = await import("./assets/pazzle/emoji_u1f992.svg?component"); big_animal[8].svg = await import("./assets/pazzle/emoji_u1f992.svg?component");
mammal[9].svg = await import("./assets/pazzle/emoji_u1f418.svg?component"); big_animal[9].svg = await import("./assets/pazzle/emoji_u1f418.svg?component");
mammal[10].svg = await import( big_animal[10].svg = await import(
"./assets/pazzle/emoji_u1f98f.svg?component" "./assets/pazzle/emoji_u1f98f.svg?component"
); );
mammal[11].svg = await import( big_animal[11].svg = await import(
"./assets/pazzle/emoji_u1f407.svg?component" "./assets/pazzle/emoji_u1f407.svg?component"
); );
big_animal[12].svg = await import("./assets/pazzle/emoji_u1f433.svg?component");
mammal[12].svg = await import( big_animal[13].svg = await import("./assets/pazzle/emoji_u1f42c.svg?component");
"./assets/pazzle/emoji_u1f994.svg?component" big_animal[14].svg = await import(
);
mammal[13].svg = await import(
"./assets/pazzle/emoji_u1f987.svg?component"
);
mammal[14].svg = await import(
"./assets/pazzle/emoji_u1f43b_200d_2744.svg?component" "./assets/pazzle/emoji_u1f43b_200d_2744.svg?component"
); );
@ -1517,8 +1503,12 @@ export async function load_svg() {
animal[8].svg = await import("./assets/pazzle/emoji_u1f422.svg?component"); animal[8].svg = await import("./assets/pazzle/emoji_u1f422.svg?component");
animal[9].svg = await import("./assets/pazzle/emoji_u1f40d.svg?component"); animal[9].svg = await import("./assets/pazzle/emoji_u1f40d.svg?component");
animal[10].svg = await import("./assets/pazzle/emoji_u1f433.svg?component"); animal[10].svg = await import(
animal[11].svg = await import("./assets/pazzle/emoji_u1f42c.svg?component"); "./assets/pazzle/emoji_u1f994.svg?component"
);
animal[11].svg = await import(
"./assets/pazzle/emoji_u1f987.svg?component"
);
animal[12].svg = await import("./assets/pazzle/emoji_u1f41f.svg?component"); animal[12].svg = await import("./assets/pazzle/emoji_u1f41f.svg?component");
animal[13].svg = await import("./assets/pazzle/emoji_u1f41a.svg?component"); animal[13].svg = await import("./assets/pazzle/emoji_u1f41a.svg?component");
@ -1703,7 +1693,7 @@ export const emojis = {
emotion, emotion,
body, body,
sport, sport,
mammal, big_animal,
animal, animal,
plants, plants,
fruits, fruits,
@ -1717,7 +1707,7 @@ export const emojis = {
export const emoji_cat = [ export const emoji_cat = [
"face", "face",
"sport", "sport",
"mammal", "big_animal",
"animal", "animal",
"plants", "plants",
"fruits", "fruits",

@ -7,7 +7,7 @@ onmessage = (e) => {
//console.log("Message received by worker", e.data); //console.log("Message received by worker", e.data);
(async function() { (async function() {
try { try {
let secret_wallet = await ng.wallet_open_wallet_with_pazzle( let secret_wallet = await ng.wallet_open_with_pazzle(
e.data.wallet, e.data.wallet,
e.data.pazzle, e.data.pazzle,
e.data.pin_code e.data.pin_code

@ -1,17 +1,25 @@
[package] [package]
name = "p2p-broker" name = "ng-broker"
version = "0.1.0" # version = "0.1.0"
edition = "2021" description = "Broker library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
license = "MIT/Apache-2.0" version.workspace = true
authors = ["Niko PLP <niko@nextgraph.org>"] edition.workspace = true
description = "P2P Broker module of NextGraph" license.workspace = true
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
[badges]
maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
p2p-repo = { path = "../p2p-repo" } ng-repo = { path = "../ng-repo", version = "0.1.0" }
p2p-net = { path = "../p2p-net" } ng-net = { path = "../ng-net", version = "0.1.0" }
p2p-client-ws = { path = "../p2p-client-ws" } ng-client-ws = { path = "../ng-client-ws", version = "0.1.0" }
stores-rocksdb = { path = "../stores-rocksdb" } ng-stores-rocksdb = { path = "../ng-stores-rocksdb", version = "0.1.0" }
chacha20 = "0.9.0" chacha20 = "0.9.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0" serde_bare = "0.5.0"

@ -0,0 +1,55 @@
# ng-broker
![MSRV][rustc-image]
[![Apache 2.0 Licensed][license-image]][license-link]
[![MIT Licensed][license-image2]][license-link2]
Broker library of NextGraph
This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues.
## NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
>
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
>
> More info here [https://nextgraph.org](https://nextgraph.org)
## Support
Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org)
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
## How to use the library
NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
This library is used internally by [ngd](../ngd/README.md) the daemon/server of NextGraph. It could potentially be used too by external projects that want to embed the NextGraph daemon in their own program.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`
### Contributions license
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as below, without any
additional terms or conditions.
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.
[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg
[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT

@ -14,11 +14,11 @@ use std::hash::Hash;
use std::hash::Hasher; use std::hash::Hasher;
use std::time::SystemTime; use std::time::SystemTime;
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::UserId; use ng_repo::types::UserId;
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};
pub struct Account<'a> { pub struct Account<'a> {
@ -225,11 +225,11 @@ impl<'a> Account<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use p2p_repo::utils::*; use ng_repo::utils::*;
use ng_stores_rocksdb::kcv_store::RocksdbKCVStore;
use std::fs; use std::fs;
use stores_rocksdb::kcv_store::RocksdbKCVStore;
use tempfile::Builder; use tempfile::Builder;
use crate::broker_store::account::Account; use crate::broker_store::account::Account;

@ -9,10 +9,10 @@
//! Broker Config, persisted to store //! Broker Config, persisted to store
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};

@ -14,13 +14,13 @@ use std::hash::Hash;
use std::hash::Hasher; use std::hash::Hasher;
use std::time::SystemTime; use std::time::SystemTime;
use p2p_net::errors::ProtocolError; use ng_net::errors::ProtocolError;
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::SymKey; use ng_repo::types::SymKey;
use p2p_repo::types::Timestamp; use ng_repo::types::Timestamp;
use p2p_repo::utils::now_timestamp; use ng_repo::utils::now_timestamp;
use serde_bare::from_slice; use serde_bare::from_slice;
use serde_bare::to_vec; use serde_bare::to_vec;
@ -178,9 +178,9 @@ impl<'a> Invitation<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use p2p_repo::utils::*; use ng_repo::utils::*;
use std::fs; use std::fs;
use tempfile::Builder; use tempfile::Builder;

@ -9,11 +9,11 @@
//! Overlay //! Overlay
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use p2p_repo::utils::now_timestamp; use ng_repo::utils::now_timestamp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};

@ -9,10 +9,10 @@
//! Peer //! Peer
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};

@ -12,10 +12,10 @@
//! A repoStore is identified by its repo pubkey if in local mode //! A repoStore is identified by its repo pubkey if in local mode
//! In core mode, it is identified by the overlayid. //! In core mode, it is identified by the overlayid.
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};

@ -9,10 +9,10 @@
//! Topic //! Topic
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};

@ -9,12 +9,12 @@
//! Broker Wallet, persists to store all the SymKeys needed to open other storages //! Broker Wallet, persists to store all the SymKeys needed to open other storages
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::kcv_store::WriteTransaction; use ng_repo::kcv_store::WriteTransaction;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};

@ -8,8 +8,8 @@
* notice may not be copied, modified, or distributed except * notice may not be copied, modified, or distributed except
* according to those terms. * according to those terms.
*/ */
use p2p_net::types::{Interface, InterfaceType}; use ng_net::types::{Interface, InterfaceType};
use p2p_net::utils::{is_ipv4_private, is_public_ipv4}; use ng_net::utils::{is_ipv4_private, is_public_ipv4};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn print_ipv4(ip: &default_net::ip::Ipv4Net) -> String { pub fn print_ipv4(ip: &default_net::ip::Ipv4Net) -> String {

@ -27,15 +27,15 @@ use futures::future::BoxFuture;
use futures::future::OptionFuture; use futures::future::OptionFuture;
use futures::FutureExt; use futures::FutureExt;
use futures::Stream; use futures::Stream;
use p2p_net::actors::*; use ng_net::actors::*;
use p2p_net::errors::*; use ng_net::errors::*;
use p2p_net::types::*; use ng_net::types::*;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::object::Object; use ng_repo::object::Object;
use p2p_repo::store::RepoStore; use ng_repo::store::RepoStore;
use p2p_repo::store::StorageError; use ng_repo::store::StorageError;
use p2p_repo::types::*; use ng_repo::types::*;
use p2p_repo::utils::*; use ng_repo::utils::*;
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub enum BrokerError { pub enum BrokerError {
@ -54,10 +54,10 @@ impl From<BrokerError> for ProtocolError {
} }
} }
impl From<p2p_repo::store::StorageError> for BrokerError { impl From<ng_repo::store::StorageError> for BrokerError {
fn from(e: p2p_repo::store::StorageError) -> Self { fn from(e: ng_repo::store::StorageError) -> Self {
match e { match e {
p2p_repo::store::StorageError::InvalidValue => BrokerError::MismatchedMode, ng_repo::store::StorageError::InvalidValue => BrokerError::MismatchedMode,
_ => BrokerError::CannotStart, _ => BrokerError::CannotStart,
} }
} }

@ -9,25 +9,31 @@
* according to those terms. * according to those terms.
*/ */
use std::path::PathBuf; use std::collections::HashMap;
use std::fs::{read, File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use crate::broker_store::account::Account; use crate::broker_store::account::Account;
use crate::broker_store::invitation::Invitation; use crate::broker_store::invitation::Invitation;
use crate::broker_store::wallet::Wallet; use crate::broker_store::wallet::Wallet;
use crate::types::*; use crate::types::*;
use p2p_net::errors::ProtocolError; use ng_net::errors::{ProtocolError, ServerError};
use p2p_net::server_storage::*; use ng_net::server_storage::*;
use p2p_net::types::{BootstrapContentV0, InvitationCode, InvitationV0}; use ng_net::types::{BootstrapContentV0, InvitationCode, InvitationV0};
use p2p_repo::kcv_store::KCVStore; use ng_repo::kcv_store::KCVStore;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::store::StorageError; use ng_repo::store::StorageError;
use p2p_repo::types::{PubKey, SymKey}; use ng_repo::types::{PeerId, PubKey, SymKey};
use stores_rocksdb::kcv_store::RocksdbKCVStore; use ng_stores_rocksdb::kcv_store::RocksdbKCVStore;
pub struct RocksdbServerStorage { pub struct RocksdbServerStorage {
wallet_storage: RocksdbKCVStore, wallet_storage: RocksdbKCVStore,
accounts_storage: RocksdbKCVStore, accounts_storage: RocksdbKCVStore,
peers_storage: RocksdbKCVStore, peers_storage: RocksdbKCVStore,
peers_last_seq_path: PathBuf,
peers_last_seq: Mutex<HashMap<PeerId, u64>>,
} }
impl RocksdbServerStorage { impl RocksdbServerStorage {
@ -63,7 +69,7 @@ impl RocksdbServerStorage {
&Some("admin user automatically invited at first startup".to_string()), &Some("admin user automatically invited at first startup".to_string()),
&accounts_storage, &accounts_storage,
)?; )?;
let invitation = p2p_net::types::Invitation::V0(InvitationV0 { let invitation = ng_net::types::Invitation::V0(InvitationV0 {
code: Some(symkey), code: Some(symkey),
name: Some("your NG Box, as admin".into()), name: Some("your NG Box, as admin".into()),
url: None, url: None,
@ -92,15 +98,52 @@ impl RocksdbServerStorage {
//TODO redo the whole key passing mechanism in RKV so it uses zeroize all the way //TODO redo the whole key passing mechanism in RKV so it uses zeroize all the way
let peers_storage = RocksdbKCVStore::open(&peers_path, peers_key.slice().clone())?; let peers_storage = RocksdbKCVStore::open(&peers_path, peers_key.slice().clone())?;
// creates the path for peers_last_seq
let mut peers_last_seq_path = path.clone();
peers_last_seq_path.push("peers_last_seq");
std::fs::create_dir_all(peers_last_seq_path.clone()).unwrap();
Ok(RocksdbServerStorage { Ok(RocksdbServerStorage {
wallet_storage, wallet_storage,
accounts_storage, accounts_storage,
peers_storage, peers_storage,
peers_last_seq_path,
peers_last_seq: Mutex::new(HashMap::new()),
}) })
} }
} }
impl ServerStorage for RocksdbServerStorage { impl ServerStorage for RocksdbServerStorage {
fn next_seq_for_peer(&self, peer: &PeerId, seq: u64) -> Result<(), ServerError> {
// for now we don't use the hashmap.
// TODO: let's see if the lock is even needed
let _ = self.peers_last_seq.lock();
let mut filename = self.peers_last_seq_path.clone();
filename.push(format!("{}", peer));
let file = read(filename.clone());
let mut file_save = match file {
Ok(ser) => {
let last: u64 = serde_bare::from_slice(&ser).map_err(|e| ServerError::FileError)?;
if last >= seq {
return Err(ServerError::SequenceMismatch);
}
OpenOptions::new()
.write(true)
.open(filename)
.map_err(|e| ServerError::FileError)?
}
Err(_) => File::create(filename).map_err(|e| ServerError::FileError)?,
};
let ser = serde_bare::to_vec(&seq).unwrap();
file_save
.write_all(&ser)
.map_err(|e| ServerError::FileError)?;
file_save.sync_data().map_err(|e| ServerError::FileError)?;
Ok(())
}
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError> { fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError> {
log_debug!("get_user {user_id}"); log_debug!("get_user {user_id}");
Ok(Account::open(&user_id, &self.accounts_storage)?.is_admin()?) Ok(Account::open(&user_id, &self.accounts_storage)?.is_admin()?)

@ -32,18 +32,18 @@ use async_tungstenite::tungstenite::protocol::Message;
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use p2p_client_ws::remote_ws::ConnectionWebSocket; use ng_client_ws::remote_ws::ConnectionWebSocket;
use p2p_net::broker::*; use ng_net::broker::*;
use p2p_net::connection::IAccept; use ng_net::connection::IAccept;
use p2p_net::types::*; use ng_net::types::*;
use p2p_net::utils::get_domain_without_port; use ng_net::utils::get_domain_without_port;
use p2p_net::utils::is_private_ip; use ng_net::utils::is_private_ip;
use p2p_net::utils::is_public_ip; use ng_net::utils::is_public_ip;
use p2p_net::NG_BOOTSTRAP_LOCAL_PATH; use ng_net::NG_BOOTSTRAP_LOCAL_PATH;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::SymKey; use ng_repo::types::SymKey;
use p2p_repo::types::{PrivKey, PubKey}; use ng_repo::types::{PrivKey, PubKey};
use p2p_repo::utils::generate_keypair; use ng_repo::utils::generate_keypair;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use serde_json::json; use serde_json::json;
use std::collections::HashMap; use std::collections::HashMap;

@ -6,8 +6,8 @@
// at your option. All files in the project carrying such // at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use p2p_net::types::{BrokerOverlayConfigV0, ListenerV0, RegistrationConfig}; use ng_net::types::{BrokerOverlayConfigV0, ListenerV0, RegistrationConfig};
use p2p_repo::types::{PrivKey, PubKey}; use ng_repo::types::{PrivKey, PubKey};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// DaemonConfig Version 0 /// DaemonConfig Version 0

@ -7,7 +7,7 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use p2p_repo::log::*; use ng_repo::log::*;
pub fn gen_broker_keys(key: Option<[u8; 32]>) -> [[u8; 32]; 4] { pub fn gen_broker_keys(key: Option<[u8; 32]>) -> [[u8; 32]; 4] {
let key = match key { let key = match key {

@ -1,15 +1,20 @@
[package] [package]
name = "p2p-client-ws" name = "ng-client-ws"
version = "0.1.0" # version = "0.1.0"
edition = "2021" description = "Websocket client library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
license = "MIT/Apache-2.0" version.workspace = true
authors = ["Niko PLP <niko@nextgraph.org>"] edition.workspace = true
description = "P2P Client module of NextGraph" license.workspace = true
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
[dependencies] [dependencies]
p2p-repo = { path = "../p2p-repo" } ng-repo = { path = "../ng-repo", version = "0.1.0" }
p2p-net = { path = "../p2p-net" } ng-net = { path = "../ng-net", version = "0.1.0" }
chacha20 = "0.9.0" chacha20 = "0.9.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0" serde_bare = "0.5.0"

@ -0,0 +1,55 @@
# ng-client-ws
![MSRV][rustc-image]
[![Apache 2.0 Licensed][license-image]][license-link]
[![MIT Licensed][license-image2]][license-link2]
Websocket client library of NextGraph
This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues.
## NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
>
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
>
> More info here [https://nextgraph.org](https://nextgraph.org)
## Support
Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org)
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
## How to use the library
NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
This library is used internally by [ngcli](../ngcli/README.md), [ng-app](../ng-app/README.md) and by [nextgraph, the Rust client library](../nextgraph/README.md) which you should be using instead. It is not meant to be used by other programs as-is.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`
### Contributions license
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as below, without any
additional terms or conditions.
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.
[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg
[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT

@ -24,13 +24,13 @@ use futures::{future, pin_mut, select, stream, StreamExt};
use futures::{FutureExt, SinkExt}; use futures::{FutureExt, SinkExt};
use async_std::task; use async_std::task;
use p2p_net::errors::*; use ng_net::errors::*;
use p2p_net::types::*; use ng_net::types::*;
use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender}; use ng_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender};
use p2p_net::{connection::*, WS_PORT}; use ng_net::{connection::*, WS_PORT};
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::*; use ng_repo::types::*;
use p2p_repo::utils::{generate_keypair, now_timestamp}; use ng_repo::utils::{generate_keypair, now_timestamp};
use async_tungstenite::async_std::{connect_async, ConnectStream}; use async_tungstenite::async_std::{connect_async, ConnectStream};
use async_tungstenite::tungstenite::{Error, Message}; use async_tungstenite::tungstenite::{Error, Message};
@ -301,13 +301,13 @@ mod test {
use crate::remote_ws::*; use crate::remote_ws::*;
use async_std::task; use async_std::task;
use p2p_net::broker::*; use ng_net::broker::*;
use p2p_net::errors::NetError; use ng_net::errors::NetError;
use p2p_net::types::IP; use ng_net::types::IP;
use p2p_net::utils::{spawn_and_log_error, ResultSend}; use ng_net::utils::{spawn_and_log_error, ResultSend};
use p2p_repo::errors::NgError; use ng_repo::errors::NgError;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::utils::generate_keypair; use ng_repo::utils::generate_keypair;
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
@ -335,9 +335,9 @@ mod test {
server_key, server_key,
StartConfig::Client(ClientConfig { StartConfig::Client(ClientConfig {
url: format!("ws://localhost:{}", WS_PORT), url: format!("ws://localhost:{}", WS_PORT),
user, name: None,
user_priv, user_priv,
client, client_priv,
info: ClientInfo::new(ClientType::Cli, "".into(), "".into()), info: ClientInfo::new(ClientType::Cli, "".into(), "".into()),
registration: None, registration: None,
}), }),

@ -14,14 +14,14 @@
use either::Either; use either::Either;
use futures::FutureExt; use futures::FutureExt;
use futures::{future, pin_mut, select, stream, SinkExt, StreamExt}; use futures::{future, pin_mut, select, stream, SinkExt, StreamExt};
use p2p_net::connection::*; use ng_net::connection::*;
use p2p_net::errors::*; use ng_net::errors::*;
use p2p_net::types::*; use ng_net::types::*;
use p2p_net::utils::*; use ng_net::utils::*;
use p2p_net::WS_PORT; use ng_net::WS_PORT;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::*; use ng_repo::types::*;
use p2p_repo::utils::{generate_keypair, now_timestamp}; use ng_repo::utils::{generate_keypair, now_timestamp};
use std::sync::Arc; use std::sync::Arc;
use { use {

@ -1,14 +1,23 @@
[package] [package]
name = "p2p-net" name = "ng-net"
version = "0.1.0" # version = "0.1.0"
edition = "2021" description = "Network library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
license = "MIT/Apache-2.0" categories = ["network-programming"]
authors = ["Niko PLP <niko@nextgraph.org>"] version.workspace = true
description = "P2P network module of NextGraph" edition.workspace = true
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
[badges]
maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
p2p-repo = { path = "../p2p-repo" } ng-repo = { path = "../ng-repo", version = "0.1.0" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0" serde_bare = "0.5.0"
serde_bytes = "0.11.7" serde_bytes = "0.11.7"
@ -39,4 +48,4 @@ features = ["js"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
getrandom = "0.2.7" getrandom = "0.2.7"
default-net = { git = "https://git.nextgraph.org/NextGraph/default-net.git" } default-net = { git = "https://git.nextgraph.org/NextGraph/default-net.git" }
# stores-rocksdb = { path = "../stores-rocksdb" } # ng-stores-rocksdb = { path = "../ng-stores-rocksdb", version = "0.1.0" }

@ -0,0 +1,55 @@
# ng-net
![MSRV][rustc-image]
[![Apache 2.0 Licensed][license-image]][license-link]
[![MIT Licensed][license-image2]][license-link2]
Network library of NextGraph
This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues.
## NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
>
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
>
> More info here [https://nextgraph.org](https://nextgraph.org)
## Support
Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org)
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
## How to use the library
NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
This library is used internally by [ngd](../ngd/README.md), [ngcli](../ngcli/README.md), [ng-app](../ng-app/README.md) and by [nextgraph, the Rust client library](../nextgraph/README.md) which you should be using instead. It is not meant to be used by other programs as-is.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`
### Contributions license
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as below, without any
additional terms or conditions.
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.
[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg
[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT

@ -14,8 +14,8 @@ use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;

@ -14,8 +14,8 @@ use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;

@ -14,8 +14,8 @@ use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;

@ -13,7 +13,7 @@ use crate::connection::NoiseFSM;
use crate::types::*; use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;

@ -14,8 +14,8 @@ use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;

@ -14,8 +14,8 @@ use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;

@ -17,7 +17,7 @@ use crate::types::{
}; };
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use p2p_repo::log::*; use ng_repo::log::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::sync::Arc; use std::sync::Arc;

@ -20,18 +20,20 @@ use async_std::sync::{Arc, RwLock};
use either::Either; use either::Either;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::SinkExt; use futures::SinkExt;
use ng_repo::errors::NgError;
use ng_repo::errors::ObjectParseError;
use ng_repo::log::*;
use ng_repo::object::Object;
use ng_repo::store::HashMapRepoStore;
use ng_repo::types::*;
use ng_repo::utils::generate_keypair;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use p2p_repo::errors::ObjectParseError;
use p2p_repo::log::*;
use p2p_repo::object::Object;
use p2p_repo::store::HashMapRepoStore;
use p2p_repo::types::*;
use p2p_repo::utils::generate_keypair;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug)] #[derive(Debug)]
pub enum PeerConnection { pub enum PeerConnection {
Core(IP), Core(BindAddress),
Client(ConnectionBase), Client(ConnectionBase),
NONE, NONE,
} }
@ -44,7 +46,7 @@ pub struct BrokerPeerInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct DirectConnection { pub struct DirectConnection {
ip: IP, addr: BindAddress,
remote_peer_id: X25519PrivKey, remote_peer_id: X25519PrivKey,
tp: TransportProtocol, tp: TransportProtocol,
//dir: ConnectionDir, //dir: ConnectionDir,
@ -62,10 +64,12 @@ pub struct ServerConfig {
pub bootstrap: BootstrapContent, pub bootstrap: BootstrapContent,
} }
pub trait ILocalBroker: Send + Sync {}
pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new()))); pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new())));
pub struct Broker<'a> { pub struct Broker<'a> {
direct_connections: HashMap<IP, DirectConnection>, direct_connections: HashMap<BindAddress, DirectConnection>,
/// tuple of optional userId and peer key in montgomery form. userId is always None on the server side. /// tuple of optional userId and peer key in montgomery form. userId is always None on the server side.
peers: HashMap<(Option<PubKey>, X25519PubKey), BrokerPeerInfo>, peers: HashMap<(Option<PubKey>, X25519PubKey), BrokerPeerInfo>,
/// (local,remote) -> ConnectionBase /// (local,remote) -> ConnectionBase
@ -79,10 +83,13 @@ pub struct Broker<'a> {
closing: bool, closing: bool,
server_storage: Option<Box<dyn ServerStorage + Send + Sync + 'a>>, server_storage: Option<Box<dyn ServerStorage + Send + Sync + 'a>>,
test: u32,
tauri_streams: HashMap<String, Sender<Commit>>, tauri_streams: HashMap<String, Sender<Commit>>,
disconnections_sender: Sender<String>, disconnections_sender: Sender<String>,
disconnections_receiver: Option<Receiver<String>>, disconnections_receiver: Option<Receiver<String>>,
//last_seq_function: Option<Box<LastSeqFn>>,
//base_path: Option<PathBuf>,
//in_memory: bool,
local_broker: Option<Box<dyn ILocalBroker + Send + Sync + 'a>>,
} }
impl<'a> Broker<'a> { impl<'a> Broker<'a> {
@ -101,6 +108,25 @@ impl<'a> Broker<'a> {
} }
} }
// pub fn init_local_broker(
// &mut self,
// base_path: Option<PathBuf>,
// in_memory: bool,
// ) -> Result<(), NgError> {
// if in_memory && base_path.is_some() {
// return Err(NgError::InvalidArgument);
// }
// self.base_path = base_path;
// self.in_memory = in_memory;
// Ok(())
// }
// pub fn register_last_seq_function(&mut self, function: Box<LastSeqFn>) {
// if self.last_seq_function.is_none() {
// self.last_seq_function = Some(function);
// }
// }
pub fn get_config(&self) -> Option<&ServerConfig> { pub fn get_config(&self) -> Option<&ServerConfig> {
self.config.as_ref() self.config.as_ref()
} }
@ -123,10 +149,10 @@ impl<'a> Broker<'a> {
self.server_storage = Some(Box::new(storage)); self.server_storage = Some(Box::new(storage));
} }
// pub fn set_local_storage(&mut self, storage: impl LocalStorage + 'a) { pub fn set_local_broker(&mut self, broker: impl ILocalBroker + 'a) {
// //log_debug!("set_storage"); //log_debug!("set_local_broker");
// self.local_storage = Some(Box::new(storage)); self.local_broker = Some(Box::new(broker));
// } }
pub fn set_server_config(&mut self, config: ServerConfig) { pub fn set_server_config(&mut self, config: ServerConfig) {
self.config = Some(config); self.config = Some(config);
@ -159,6 +185,13 @@ impl<'a> Broker<'a> {
.ok_or(ProtocolError::BrokerError) .ok_or(ProtocolError::BrokerError)
} }
pub fn get_local_broker_mut(
&mut self,
) -> Result<&mut Box<dyn ILocalBroker + Send + Sync + 'a>, NgError> {
//log_debug!("GET STORAGE {:?}", self.server_storage);
self.local_broker.as_mut().ok_or(NgError::BrokerError)
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn authorize( pub fn authorize(
&self, &self,
@ -288,7 +321,7 @@ impl<'a> Broker<'a> {
//Err(ProtocolError::AccessDenied) //Err(ProtocolError::AccessDenied)
// let f = std::fs::File::open( // let f = std::fs::File::open(
// "../p2p-repo/tests/e4e4b57524ce29df826055c368894e912ab03af46f61f6270b4c8796bc6f4221.ng", // "../ng-repo/tests/e4e4b57524ce29df826055c368894e912ab03af46f61f6270b4c8796bc6f4221.ng",
// ) // )
// .expect("open of block.ng"); // .expect("open of block.ng");
// let mut reader = BufReader::new(f); // let mut reader = BufReader::new(f);
@ -340,10 +373,12 @@ impl<'a> Broker<'a> {
let (member_privkey, member_pubkey) = generate_keypair(); let (member_privkey, member_pubkey) = generate_keypair();
let overlay = OverlayId::nil();
let commit = Commit::new( let commit = Commit::new(
member_privkey, &member_privkey,
member_pubkey, &member_pubkey,
1, overlay,
PubKey::nil(), PubKey::nil(),
QuorumType::NoSigning, QuorumType::NoSigning,
vec![], vec![],
@ -412,13 +447,9 @@ impl<'a> Broker<'a> {
} }
} }
pub fn test(&self) -> u32 {
self.test
}
// #[cfg(not(target_arch = "wasm32"))] // #[cfg(not(target_arch = "wasm32"))]
// pub fn test_storage(&self, path: PathBuf) { // pub fn test_storage(&self, path: PathBuf) {
// use stores_rocksdb::kcv_store::RocksdbKCVStore; // use ng_stores_rocksdb::kcv_store::RocksdbKCVStore;
// let key: [u8; 32] = [0; 32]; // let key: [u8; 32] = [0; 32];
// let test_storage = RocksdbKCVStore::open(&path, key); // let test_storage = RocksdbKCVStore::open(&path, key);
@ -449,10 +480,13 @@ impl<'a> Broker<'a> {
peers: HashMap::new(), peers: HashMap::new(),
tauri_streams: HashMap::new(), tauri_streams: HashMap::new(),
closing: false, closing: false,
test: u32::from_be_bytes(random_buf),
server_storage: None, server_storage: None,
disconnections_sender, disconnections_sender,
disconnections_receiver: Some(disconnections_receiver), disconnections_receiver: Some(disconnections_receiver),
// last_seq_function: None,
// in_memory: true,
// base_path: None,
local_broker: None,
} }
} }
@ -674,18 +708,17 @@ impl<'a> Broker<'a> {
.ok_or(ProtocolError::BrokerError)?; .ok_or(ProtocolError::BrokerError)?;
connection.reset_shutdown(remote_peer_id).await; connection.reset_shutdown(remote_peer_id).await;
let ip = remote_bind_address.ip;
let connected = if !is_core { let connected = if !is_core {
PeerConnection::Client(connection) PeerConnection::Client(connection)
} else { } else {
let dc = DirectConnection { let dc = DirectConnection {
ip, addr: remote_bind_address,
remote_peer_id, remote_peer_id,
tp: connection.transport_protocol(), tp: connection.transport_protocol(),
cnx: connection, cnx: connection,
}; };
self.direct_connections.insert(ip, dc); self.direct_connections.insert(remote_bind_address, dc);
PeerConnection::Core(ip) PeerConnection::Core(remote_bind_address)
}; };
let bpi = BrokerPeerInfo { let bpi = BrokerPeerInfo {
lastPeerAdvert: None, lastPeerAdvert: None,
@ -796,15 +829,14 @@ impl<'a> Broker<'a> {
let connected = match &config { let connected = match &config {
StartConfig::Core(config) => { StartConfig::Core(config) => {
let ip = config.addr.ip.clone();
let dc = DirectConnection { let dc = DirectConnection {
ip, addr: config.addr,
remote_peer_id: *remote_peer_id_dh.slice(), remote_peer_id: *remote_peer_id_dh.slice(),
tp: connection.transport_protocol(), tp: connection.transport_protocol(),
cnx: connection, cnx: connection,
}; };
self.direct_connections.insert(ip, dc); self.direct_connections.insert(config.addr, dc);
PeerConnection::Core(ip) PeerConnection::Core(config.addr)
} }
StartConfig::Client(_config) => PeerConnection::Client(connection), StartConfig::Client(_config) => PeerConnection::Client(connection),
_ => unimplemented!(), _ => unimplemented!(),

@ -28,11 +28,11 @@ use crate::errors::*;
use crate::types::*; use crate::types::*;
use async_broadcast::{broadcast, Receiver}; use async_broadcast::{broadcast, Receiver};
use futures::{pin_mut, stream, Sink, SinkExt, StreamExt}; use futures::{pin_mut, stream, Sink, SinkExt, StreamExt};
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::object::*; use ng_repo::object::*;
use p2p_repo::store::*; use ng_repo::store::*;
use p2p_repo::types::*; use ng_repo::types::*;
use p2p_repo::utils::*; use ng_repo::utils::*;
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait BrokerConnection { pub trait BrokerConnection {

@ -30,9 +30,9 @@ use either::Either;
use futures::{channel::mpsc, select, FutureExt, SinkExt}; use futures::{channel::mpsc, select, FutureExt, SinkExt};
use noise_protocol::{patterns::noise_xk, CipherState, HandshakeState}; use noise_protocol::{patterns::noise_xk, CipherState, HandshakeState};
use noise_rust_crypto::*; use noise_rust_crypto::*;
use p2p_repo::log::*; use ng_repo::log::*;
use p2p_repo::types::{PrivKey, PubKey, X25519PrivKey}; use ng_repo::types::{DirectPeerId, PrivKey, PubKey, X25519PrivKey};
use p2p_repo::utils::{sign, verify}; use ng_repo::utils::{sign, verify};
use serde_bare::from_slice; use serde_bare::from_slice;
use unique_id::sequence::SequenceGenerator; use unique_id::sequence::SequenceGenerator;
use unique_id::Generator; use unique_id::Generator;
@ -157,10 +157,11 @@ pub enum StepReply {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ClientConfig { pub struct ClientConfig {
pub url: String, pub url: String,
pub user: PubKey, //pub user: PubKey,
pub user_priv: PrivKey, pub user_priv: PrivKey,
pub client: PubKey, pub client_priv: PrivKey,
pub info: ClientInfo, pub info: ClientInfo,
pub name: Option<String>,
//pub peer_advert: PeerAdvert, //pub peer_advert: PeerAdvert,
pub registration: Option<Option<[u8; 32]>>, pub registration: Option<Option<[u8; 32]>>,
} }
@ -204,7 +205,7 @@ impl StartConfig {
} }
pub fn get_user(&self) -> Option<PubKey> { pub fn get_user(&self) -> Option<PubKey> {
match self { match self {
Self::Client(config) => Some(config.user), Self::Client(config) => Some(config.user_priv.to_pub()),
_ => None, _ => None,
} }
} }
@ -702,21 +703,25 @@ impl NoiseFSM {
self.config.as_ref().unwrap() self.config.as_ref().unwrap()
{ {
let ClientInfo::V0(info) = &client_config.info; let ClientInfo::V0(info) = &client_config.info;
let user_pub = client_config.user_priv.to_pub();
let client_pub = client_config.client_priv.to_pub();
let content = ClientAuthContentV0 { let content = ClientAuthContentV0 {
user: client_config.user, user: user_pub,
client: client_config.client, client: client_pub,
/// Nonce from ServerHello /// Nonce from ServerHello
nonce: hello.nonce().clone(), nonce: hello.nonce().clone(),
info: info.clone(), info: info.clone(),
registration: client_config.registration, registration: client_config.registration,
}; };
let ser = serde_bare::to_vec(&content)?; let ser = serde_bare::to_vec(&content)?;
let sig = let sig = sign(&client_config.user_priv, &user_pub, &ser)?;
sign(&client_config.user_priv, &client_config.user, &ser)?; let client_sig =
sign(&client_config.client_priv, &client_pub, &ser)?;
let client_auth = ClientAuth::V0(ClientAuthV0 { let client_auth = ClientAuth::V0(ClientAuthV0 {
content, content,
/// Signature by user key /// Signature by user key
sig, sig,
client_sig,
}); });
self.state = FSMstate::ClientAuth; self.state = FSMstate::ClientAuth;
@ -1232,7 +1237,7 @@ mod test {
use crate::actors::*; use crate::actors::*;
use p2p_repo::log::*; use ng_repo::log::*;
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
#[async_std::test] #[async_std::test]

@ -8,13 +8,19 @@
// according to those terms. // according to those terms.
use core::fmt; use core::fmt;
use ng_repo::errors::ObjectParseError;
use ng_repo::store::StorageError;
use num_enum::IntoPrimitive; use num_enum::IntoPrimitive;
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use p2p_repo::errors::ObjectParseError;
use p2p_repo::store::StorageError;
use std::convert::From; use std::convert::From;
use std::error::Error; use std::error::Error;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum ServerError {
SequenceMismatch,
FileError,
}
#[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive, Clone)] #[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive, Clone)]
#[repr(u16)] #[repr(u16)]
pub enum NetError { pub enum NetError {
@ -129,11 +135,11 @@ impl fmt::Display for ProtocolError {
} }
} }
impl From<p2p_repo::errors::NgError> for ProtocolError { impl From<ng_repo::errors::NgError> for ProtocolError {
fn from(e: p2p_repo::errors::NgError) -> Self { fn from(e: ng_repo::errors::NgError) -> Self {
match e { match e {
p2p_repo::errors::NgError::InvalidSignature => ProtocolError::InvalidSignature, ng_repo::errors::NgError::InvalidSignature => ProtocolError::InvalidSignature,
p2p_repo::errors::NgError::SerializationError => ProtocolError::SerializationError, ng_repo::errors::NgError::SerializationError => ProtocolError::SerializationError,
_ => ProtocolError::OtherError, _ => ProtocolError::OtherError,
} }
} }

@ -9,7 +9,7 @@
* according to those terms. * according to those terms.
*/ */
//#[macro_use] //#[macro_use]
//extern crate p2p_repo; //extern crate ng_repo;
pub mod types; pub mod types;

@ -9,8 +9,11 @@
* according to those terms. * according to those terms.
*/ */
use crate::{errors::ProtocolError, types::*}; use crate::{
use p2p_repo::types::PubKey; errors::{ProtocolError, ServerError},
types::*,
};
use ng_repo::types::{PeerId, PubKey};
pub trait ServerStorage: Send + Sync { pub trait ServerStorage: Send + Sync {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>; fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>;
@ -31,4 +34,6 @@ pub trait ServerStorage: Send + Sync {
) -> Result<(), ProtocolError>; ) -> Result<(), ProtocolError>;
fn get_invitation_type(&self, invite: [u8; 32]) -> Result<u8, ProtocolError>; fn get_invitation_type(&self, invite: [u8; 32]) -> Result<u8, ProtocolError>;
fn remove_invitation(&self, invite: [u8; 32]) -> Result<(), ProtocolError>; fn remove_invitation(&self, invite: [u8; 32]) -> Result<(), ProtocolError>;
fn next_seq_for_peer(&self, peer: &PeerId, seq: u64) -> Result<(), ServerError>;
} }

@ -17,9 +17,9 @@ use crate::utils::{
}; };
use crate::{actor::EActor, actors::*, errors::ProtocolError}; use crate::{actor::EActor, actors::*, errors::ProtocolError};
use core::fmt; use core::fmt;
use p2p_repo::errors::NgError; use ng_repo::errors::NgError;
use p2p_repo::types::*; use ng_repo::types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
@ -1156,49 +1156,6 @@ pub enum RegistrationConfig {
Open, Open,
} }
//
// COMMON TYPES FOR MESSAGES
//
pub type DirectPeerId = PubKey;
pub type ForwardedPeerId = PubKey;
/// Peer ID: public key of the node, or an encrypted version of it
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
pub enum PeerId {
Direct(DirectPeerId),
Forwarded(ForwardedPeerId),
/// BLAKE3 keyed hash over ForwardedPeerId
/// - key: BLAKE3 derive_key ("NextGraph ForwardedPeerId Hash Overlay Id BLAKE3 key", overlayId)
ForwardedObfuscated(Digest),
}
pub type OuterOverlayId = Digest;
pub type InnerOverlayId = Digest;
/// Overlay ID
///
/// - for outer overlays that need to be discovered by public key:
/// BLAKE3 hash over the public key of the store repo
/// - for inner overlays:
/// BLAKE3 keyed hash over the public key of the store repo
/// - key: BLAKE3 derive_key ("NextGraph Overlay ReadCapSecret BLAKE3 key", store repo's overlay's branch ReadCapSecret)
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum OverlayId {
Outer(OuterOverlayId),
Inner(InnerOverlayId),
Global,
}
impl fmt::Display for OverlayId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let overlay_ser = serde_bare::to_vec(&self).unwrap();
write!(f, "{}", base64_url::encode(&overlay_ser))
}
}
/// Overlay Access /// Overlay Access
/// ///
/// Used by the Client when opening or pinning a repo. /// Used by the Client when opening or pinning a repo.
@ -1257,7 +1214,7 @@ pub struct InnerOverlayLink {
/// The store has a special branch called `overlay` that is used to manage access to the InnerOverlay /// The store has a special branch called `overlay` that is used to manage access to the InnerOverlay
/// only the ReadCapSecret is needed to access the InnerOverlay /// only the ReadCapSecret is needed to access the InnerOverlay
/// the full readcap of this branch is needed in order to subscribe to the topic and decrypt the events. The branchId can be found in the branch Definition /// the full readcap of this branch is needed in order to subscribe to the topic and decrypt the events. The branchId can be found in the branch Definition
/// it can be useful to subscribe to this topic i the user is at least a reader of the store's repo, so it will be notified of refreshReadCap on the overlay /// it can be useful to subscribe to this topic if the user is a member of the store's repo, so it will be notified of refreshReadCap on the overlay
/// if the user is an external user to the store, it will lose access to the InnerOverlay after a RefreshReadCap of the overlay branch of the store. /// if the user is an external user to the store, it will lose access to the InnerOverlay after a RefreshReadCap of the overlay branch of the store.
pub store_overlay_readcap: ReadCap, pub store_overlay_readcap: ReadCap,
} }
@ -1397,11 +1354,13 @@ pub enum ClientType {
NativeService, NativeService,
NodeService, NodeService,
Verifier, Verifier,
Box,
Stick,
WalletMaster,
ClientBroker, ClientBroker,
Cli, Cli,
} }
/// IP transport address
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct ClientInfoV0 { pub struct ClientInfoV0 {
pub client_type: ClientType, pub client_type: ClientType,
@ -1539,69 +1498,6 @@ pub enum UnsubReq {
V0(UnsubReqV0), V0(UnsubReqV0),
} }
/// Content of EventV0
/// Contains the objects of newly published Commit, its optional blocks, and optional FILES and their blocks.
/// If a block is not present in the Event, its ID should be present in block_ids and the block should be put on the emitting broker beforehand with BlocksPut.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventContentV0 {
/// Pub/sub topic
pub topic: TopicId,
// TODO: could be obfuscated (or not, if we want to be able to recall events)
// on public repos, should be obfuscated
pub publisher: PeerId,
/// Commit sequence number of publisher
pub seq: u64,
/// Blocks with encrypted content. First in the list is always the commit block followed by its children, then its optional header and body blocks (and eventual children),
/// blocks of the FILES are optional (only sent here if user specifically want to push them to the pub/sub).
/// the first in the list MUST contain a commit_header_key
/// When saved locally (the broker keeps the associated event, until the topic is refreshed(the last heads retain their events) ),
/// so, this `blocks` list is emptied (as the blocked are saved in the overlay storage anyway) and their IDs are kept on the side.
/// then when the event needs to be send in reply to a *TopicSyncReq, the blocks list is regenerated from the IDs,
/// so that a valid EventContent can be sent (and so that its signature can be verified successfully)
pub blocks: Vec<Block>,
/// Ids of additional Blocks (FILES) with encrypted content that are not to be pushed in the pub/sub
/// they will be retrieved later by interested users
pub block_ids: Vec<BlockId>,
/// can be :
/// * Encrypted key for the Commit object (the first Block in blocks vec)
/// The ObjectKey is encrypted using ChaCha20:
/// - key: BLAKE3 derive_key ("NextGraph Event Commit ObjectKey ChaCha20 key",
/// RepoId + BranchId + branch_secret(ReadCapSecret of the branch) + publisher)
/// - nonce: commit_seq
/// * If it is a CertificateRefresh, both the blocks and block_ids vectors are empty.
/// the key here contains an encrypted ObjectRef to the new Certificate.
/// The whole ObjectRef is encrypted (including the ID) to avoid correlation of topics who will have the same Certificate ID (belong to the same repo)
/// Encrypted using ChaCha20, with :
/// - key: BLAKE3 derive_key ("NextGraph Event Certificate ObjectRef ChaCha20 key",
/// RepoId + BranchId + branch_secret(ReadCapSecret of the branch) + publisher)
/// it is the same key as above, because the commit_seq will be different (incremented anyway)
/// - nonce: commit_seq
#[serde(with = "serde_bytes")]
pub key: Vec<u8>,
}
/// Pub/sub event published in a topic
///
/// Forwarded along event routing table entries
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventV0 {
pub content: EventContentV0,
/// Signature over content by topic key
pub sig: Sig,
}
/// Pub/sub event published in a topic
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Event {
V0(EventV0),
}
/// Object search in a pub/sub topic /// Object search in a pub/sub topic
/// ///
/// Sent along the reverse path of a pub/sub topic /// Sent along the reverse path of a pub/sub topic
@ -1741,8 +1637,7 @@ pub enum ForwardedPeerAdvert {
/// ForwardedPeerConflictV0 /// ForwardedPeerConflictV0
/// peer_advert.forwarded_by is matched with sessionid->peerid /// peer_advert.forwarded_by is matched with sessionid->peerid
/// When a conflict occurs, the peer is immediately removed locally /// When the forwarding broker receives the conflict (or notices it), it sends a notification
/// When the forwarding broker receives the conflict (or notices it), it disconnects the client(s).
/// In order to avoid conflicts, the highest version of PeerAdvert should win, when the Forwarding Broker is different. /// In order to avoid conflicts, the highest version of PeerAdvert should win, when the Forwarding Broker is different.
/// Disconnect wins over connect, for the exact same peer, version and forwarding broker. /// Disconnect wins over connect, for the exact same peer, version and forwarding broker.
/// Conflict can occur when same user_hash, on 2 different Forwarding Broker /// Conflict can occur when same user_hash, on 2 different Forwarding Broker
@ -2584,9 +2479,15 @@ pub struct OpenRepoV0 {
/// Broker peers to connect to in order to join the overlay /// Broker peers to connect to in order to join the overlay
/// can be empty for private store (the broker will not connect to any other broker) /// can be empty for private store (the broker will not connect to any other broker)
/// but if the private repo is pinned in other brokers, those brokers should be entered here from syncing. /// but if the private repo is pinned in other brokers, those brokers should be entered here for syncing.
/// can be empty also when we just created the repo, and there are no other brokers in the overlay
pub peers: Vec<PeerAdvert>, pub peers: Vec<PeerAdvert>,
/// a list of core brokers that are allowed to connect to the overlay (only valid for an inner (RW/WO) overlay).
/// an empty list means any core broker is allowed. this is the default behaviour.
/// to restrict the overlay to only the current core, its DirectPeerId should be entered here.
pub allowed_peers: Vec<DirectPeerId>,
/// Maximum number of peers to connect to for this overlay (only valid for an inner (RW/WO) overlay) /// Maximum number of peers to connect to for this overlay (only valid for an inner (RW/WO) overlay)
pub max_peer_count: u16, pub max_peer_count: u16,
@ -2595,6 +2496,7 @@ pub struct OpenRepoV0 {
/// list of topics for which we will be a publisher /// list of topics for which we will be a publisher
/// only possible with inner (RW or WO) overlays. /// only possible with inner (RW or WO) overlays.
/// implies also subscribing to it (no need to put it also in ro_topics)
pub rw_topics: Vec<PublisherAdvert>, pub rw_topics: Vec<PublisherAdvert>,
} }
@ -2625,16 +2527,22 @@ pub struct PinRepoV0 {
/// Root topic of the overlay, used to listen to overlay refreshes. Only used for inner (RW or WO) overlays /// Root topic of the overlay, used to listen to overlay refreshes. Only used for inner (RW or WO) overlays
pub overlay_root_topic: Option<TopicId>, pub overlay_root_topic: Option<TopicId>,
/// only possible for RW overlays /// only possible for RW overlays. not allowed for private or dialog overlay
pub expose_outer: bool, pub expose_outer: bool,
/// Broker peers to connect to in order to join the overlay /// Broker peers to connect to in order to join the overlay
/// If the repo has previously been opened (during the same session) then peers info can be omitted /// If the repo has previously been opened (during the same session) or if it is a private overlay, then peers info can be omitted
pub peers: Vec<PeerAdvert>, pub peers: Vec<PeerAdvert>,
/// Maximum number of peers to connect to for this overlay (only valid for an inner (RW/WO) overlay) /// Maximum number of peers to connect to for this overlay (only valid for an inner (RW/WO) overlay)
pub max_peer_count: u16, pub max_peer_count: u16,
/// a list of core brokers that are allowed to connect to the overlay (only valid for an inner (RW/WO) overlay).
/// an empty list means any core broker is allowed. this is the default behaviour.
/// to restrict the overlay to only the current core, its DirectPeerId should be entered here.
/// not compatible with expose_outer
pub allowed_peers: Vec<DirectPeerId>,
/// list of topics that should be subscribed to /// list of topics that should be subscribed to
/// If the repo has previously been opened (during the same session) then ro_topics info can be omitted /// If the repo has previously been opened (during the same session) then ro_topics info can be omitted
pub ro_topics: Vec<TopicId>, pub ro_topics: Vec<TopicId>,
@ -3514,6 +3422,9 @@ pub struct ClientAuthV0 {
/// Signature by user key /// Signature by user key
pub sig: Sig, pub sig: Sig,
/// Signature by client key
pub client_sig: Sig,
} }
/// Client authentication /// Client authentication
@ -3603,8 +3514,8 @@ impl From<AuthResult> for ProtocolMessage {
/// Those capabilities are not durable: They can be refreshed by the members and previously shared Caps will become obsolete/revoked. /// Those capabilities are not durable: They can be refreshed by the members and previously shared Caps will become obsolete/revoked.
/// As long as the user is a member of the repo and subscribes to the root topic (of the repo, and of the store if needed/applicable), they will receive the updated capabilities. /// As long as the user is a member of the repo and subscribes to the root topic (of the repo, and of the store if needed/applicable), they will receive the updated capabilities.
/// But if they don't subscribe, they will lose access after the refresh. /// But if they don't subscribe, they will lose access after the refresh.
/// For durable read capabilities of non-members, see PermaReadCap. /// For durable capabilities, see PermaCap.
/// In most cases, the link is shared and the recipient opens it and subscribes soon afterward. /// In most cases, the link is shared and then the recipient opens it and subscribes soon afterward, so there is no need for a PermaCap
/// Perma capabilities are needed only when the link is stored on disk and kept there unopened for a long period. /// Perma capabilities are needed only when the link is stored on disk and kept there unopened for a long period.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RepoLinkV0 { pub struct RepoLinkV0 {
@ -3616,7 +3527,7 @@ pub struct RepoLinkV0 {
pub read_cap: ReadCap, pub read_cap: ReadCap,
/// Write capability secret. Only set for editors. in this case, overlay MUST be set to an InnerOverlay /// Write capability secret. Only set for editors. in this case, overlay MUST be set to an InnerOverlay
pub write_cap_secret: Option<RepoWriteCapSecret>, // pub write_cap_secret: Option<RepoWriteCapSecret>,
/// Current overlay link, used to join the overlay /// Current overlay link, used to join the overlay
pub overlay: OverlayLink, pub overlay: OverlayLink,
@ -3637,11 +3548,6 @@ impl RepoLink {
RepoLink::V0(o) => &o.id, RepoLink::V0(o) => &o.id,
} }
} }
pub fn write_cap_secret(&self) -> &Option<RepoWriteCapSecret> {
match self {
RepoLink::V0(o) => &o.write_cap_secret,
}
}
pub fn peers(&self) -> &Vec<PeerAdvert> { pub fn peers(&self) -> &Vec<PeerAdvert> {
match self { match self {
RepoLink::V0(o) => &o.peers, RepoLink::V0(o) => &o.peers,
@ -3695,7 +3601,7 @@ pub struct ReadBranchLinkV0 {
pub branch: BranchId, // must match the one in read_cap pub branch: BranchId, // must match the one in read_cap
/// an optional list of heads that can fetched in this branch /// an optional list of heads that can be fetched in this branch
/// useful if a specific head is to be shared /// useful if a specific head is to be shared
pub heads: Vec<ObjectRef>, pub heads: Vec<ObjectRef>,
@ -3729,7 +3635,7 @@ pub struct ObjectLinkV0 {
/// An optional topic that will be used to retrieve the Certificate of a commit, if needed /// An optional topic that will be used to retrieve the Certificate of a commit, if needed
/// (topic has to be checked with the one inside the commit. the one here might be wrong. it is provided here as an optimization) /// (topic has to be checked with the one inside the commit. the one here might be wrong. it is provided here as an optimization)
/// or can be used to help with BlockSearchTopic. /// or can be used to help with BlockSearchTopic.
/// If the topic is provided, a TopicSyncReq can be performed, and the causal past of the commit will appear (by repeated tried while narrowing down on the ancestors), /// If the topic is provided, a TopicSyncReq can be performed, and the causal past of the commit will appear (by repeated tries while narrowing down on the ancestors),
/// hence defeating the "emptied header" protection /// hence defeating the "emptied header" protection
pub topic: Option<TopicId>, pub topic: Option<TopicId>,
@ -3769,7 +3675,7 @@ pub enum NgLink {
mod test { mod test {
use crate::types::{BootstrapContentV0, BrokerServerTypeV0, BrokerServerV0, Invitation}; use crate::types::{BootstrapContentV0, BrokerServerTypeV0, BrokerServerV0, Invitation};
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
#[test] #[test]
pub fn invitation() { pub fn invitation() {

@ -19,9 +19,9 @@ use noise_protocol::U8Array;
use noise_protocol::DH; use noise_protocol::DH;
use noise_rust_crypto::sensitive::Sensitive; use noise_rust_crypto::sensitive::Sensitive;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use p2p_repo::errors::*; use ng_repo::errors::*;
use p2p_repo::types::PubKey; use ng_repo::types::PubKey;
use p2p_repo::{log::*, types::PrivKey}; use ng_repo::{log::*, types::PrivKey};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use url::Host; use url::Host;
use url::Url; use url::Url;

@ -1,16 +1,24 @@
[package] [package]
name = "p2p-repo" name = "ng-repo"
version = "0.1.0" # version = "0.1.0"
edition = "2021" description = "Repository library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
license = "MIT/Apache-2.0" categories = ["asynchronous","database-implementations"]
authors = ["Niko PLP <niko@nextgraph.org>"] version.workspace = true
description = "P2P repository module of NextGraph" edition.workspace = true
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
[badges]
maintenance = { status = "actively-developed" }
[features] [features]
server_log_output = [] server_log_output = []
[dependencies] [dependencies]
blake3 = "1.3.1" blake3 = "1.3.1"
chacha20 = "0.9.0" chacha20 = "0.9.0"
@ -35,6 +43,7 @@ once_cell = "1.17.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
debug_print = "1.0.0" debug_print = "1.0.0"
log = "0.4" log = "0.4"
getrandom = "0.2.7"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
gloo-timers = "0.2.6" gloo-timers = "0.2.6"

@ -0,0 +1,55 @@
# ng-repo
![MSRV][rustc-image]
[![Apache 2.0 Licensed][license-image]][license-link]
[![MIT Licensed][license-image2]][license-link2]
Repository library of NextGraph
This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues.
## NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
>
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
>
> More info here [https://nextgraph.org](https://nextgraph.org)
## Support
Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org)
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
## How to use the library
NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
This library is used internally by [ngd](../ngd/README.md), [ngcli](../ngcli/README.md), [ng-app](../ng-app/README.md) and by [nextgraph, the Rust client library](../nextgraph/README.md) which you should be using instead. It is not meant to be used by other programs as-is.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`
### Contributions license
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as below, without any
additional terms or conditions.
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/project/NextGraph/index.html), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.
[rustc-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg
[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT

@ -1,7 +1,5 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers // Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved. // All rights reserved.
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
// Copyright 2022 TG x Thoth
// Licensed under the Apache License, Version 2.0 // Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> // <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, // or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,

@ -10,6 +10,7 @@
//! Branch of a Repository //! Branch of a Repository
use std::collections::HashSet; use std::collections::HashSet;
use zeroize::{Zeroize, ZeroizeOnDrop};
// use fastbloom_rs::{BloomFilter as Filter, Membership}; // use fastbloom_rs::{BloomFilter as Filter, Membership};
@ -17,6 +18,7 @@ use crate::errors::*;
use crate::object::*; use crate::object::*;
use crate::store::*; use crate::store::*;
use crate::types::*; use crate::types::*;
use crate::utils::encrypt_in_place;
impl BranchV0 { impl BranchV0 {
pub fn new( pub fn new(
@ -41,6 +43,32 @@ impl BranchV0 {
} }
impl Branch { impl Branch {
/// topic private key (a BranchWriteCapSecret), encrypted with a key derived as follow
/// BLAKE3 derive_key ("NextGraph Branch WriteCap Secret BLAKE3 key",
/// RepoWriteCapSecret, TopicId, BranchId )
/// so that only editors of the repo can decrypt the privkey
/// nonce = 0
pub fn encrypt_topic_priv_key(
privkey: &BranchWriteCapSecret,
topic_id: TopicId,
branch_id: BranchId,
repo_write_cap_secret: &RepoWriteCapSecret,
) -> Vec<u8> {
let mut plaintext = serde_bare::to_vec(privkey).unwrap();
let repo_write_cap_secret = serde_bare::to_vec(repo_write_cap_secret).unwrap();
let topic_id = serde_bare::to_vec(&topic_id).unwrap();
let branch_id = serde_bare::to_vec(&branch_id).unwrap();
let mut key_material = [repo_write_cap_secret, topic_id, branch_id].concat();
let mut key: [u8; 32] = blake3::derive_key(
"NextGraph Branch WriteCap Secret BLAKE3 key",
key_material.as_slice(),
);
encrypt_in_place(&mut plaintext, key, [0; 12]);
key.zeroize();
key_material.zeroize();
plaintext
}
pub fn new( pub fn new(
id: PubKey, id: PubKey,
repo: ObjectRef, repo: ObjectRef,
@ -213,6 +241,8 @@ mod test {
acks.iter().map(|r| r.id).collect(), acks.iter().map(|r| r.id).collect(),
); );
let overlay = store_pubkey.overlay_id_for_read_purpose();
let obj_ref = ObjectRef { let obj_ref = ObjectRef {
id: ObjectId::Blake3Digest32([1; 32]), id: ObjectId::Blake3Digest32([1; 32]),
key: SymKey::ChaCha20Key([2; 32]), key: SymKey::ChaCha20Key([2; 32]),
@ -221,9 +251,9 @@ mod test {
let metadata = vec![5u8; 55]; let metadata = vec![5u8; 55];
let commit = CommitV0::new( let commit = CommitV0::new(
author_privkey, &author_privkey,
author_pubkey, &author_pubkey,
seq, overlay,
branch, branch,
QuorumType::NoSigning, QuorumType::NoSigning,
deps, deps,
@ -301,6 +331,7 @@ mod test {
&repo_pubkey, &repo_pubkey,
&member_pubkey, &member_pubkey,
&[PermissionV0::WriteAsync], &[PermissionV0::WriteAsync],
store_repo.overlay_id_for_read_purpose(),
t.s(), t.s(),
); );
@ -337,10 +368,14 @@ mod test {
// commit bodies // commit bodies
let branch_body = let branch_body = add_body_branch(
add_body_branch(branch.clone(), &store_repo, &repo_secret, repo.get_store()); branch.clone(),
&store_repo,
&repo_secret,
repo.get_storage(),
);
let trans_body = add_body_trans(None, &store_repo, &repo_secret, repo.get_store()); let trans_body = add_body_trans(None, &store_repo, &repo_secret, repo.get_storage());
// create & add commits to store // create & add commits to store
@ -355,7 +390,7 @@ mod test {
branch_body.clone(), branch_body.clone(),
&store_repo, &store_repo,
&repo_secret, &repo_secret,
repo.get_store(), repo.get_storage(),
); );
log_debug!(">> t1"); log_debug!(">> t1");
@ -369,7 +404,7 @@ mod test {
trans_body.clone(), trans_body.clone(),
&store_repo, &store_repo,
&repo_secret, &repo_secret,
repo.get_store(), repo.get_storage(),
); );
log_debug!(">> t2"); log_debug!(">> t2");
@ -383,7 +418,7 @@ mod test {
trans_body.clone(), trans_body.clone(),
&store_repo, &store_repo,
&repo_secret, &repo_secret,
repo.get_store(), repo.get_storage(),
); );
// log_debug!(">> a3"); // log_debug!(">> a3");
@ -411,7 +446,7 @@ mod test {
trans_body.clone(), trans_body.clone(),
&store_repo, &store_repo,
&repo_secret, &repo_secret,
repo.get_store(), repo.get_storage(),
); );
log_debug!(">> t5"); log_debug!(">> t5");
@ -425,7 +460,7 @@ mod test {
trans_body.clone(), trans_body.clone(),
&store_repo, &store_repo,
&repo_secret, &repo_secret,
repo.get_store(), repo.get_storage(),
); );
log_debug!(">> a6"); log_debug!(">> a6");
@ -439,7 +474,7 @@ mod test {
trans_body.clone(), trans_body.clone(),
&store_repo, &store_repo,
&repo_secret, &repo_secret,
repo.get_store(), repo.get_storage(),
); );
log_debug!(">> a7"); log_debug!(">> a7");
@ -453,10 +488,10 @@ mod test {
trans_body.clone(), trans_body.clone(),
&store_repo, &store_repo,
&repo_secret, &repo_secret,
repo.get_store(), repo.get_storage(),
); );
let c7 = Commit::load(a7.clone(), repo.get_store(), true).unwrap(); let c7 = Commit::load(a7.clone(), repo.get_storage(), true).unwrap();
c7.verify(&repo).unwrap(); c7.verify(&repo).unwrap();
// let mut filter = Filter::new(FilterBuilder::new(10, 0.01)); // let mut filter = Filter::new(FilterBuilder::new(10, 0.01));
@ -481,7 +516,7 @@ mod test {
&[t5.id, a6.id, a7.id], &[t5.id, a6.id, a7.id],
&[t5.id], &[t5.id],
//&their_commits, //&their_commits,
repo.get_store(), repo.get_storage(),
) )
.unwrap(); .unwrap();

@ -50,9 +50,9 @@ pub enum CommitVerifyError {
impl CommitV0 { impl CommitV0 {
/// New commit /// New commit
pub fn new( pub fn new(
author_privkey: PrivKey, author_privkey: &PrivKey,
author_pubkey: PubKey, author_pubkey: &PubKey,
seq: u64, overlay: OverlayId,
branch: BranchId, branch: BranchId,
quorum: QuorumType, quorum: QuorumType,
deps: Vec<ObjectRef>, deps: Vec<ObjectRef>,
@ -67,8 +67,7 @@ impl CommitV0 {
let headers = CommitHeader::new_with(deps, ndeps, acks, nacks, files, nfiles); let headers = CommitHeader::new_with(deps, ndeps, acks, nacks, files, nfiles);
let content = CommitContent::V0(CommitContentV0 { let content = CommitContent::V0(CommitContentV0 {
perms: vec![], perms: vec![],
author: (&author_pubkey).into(), author: CommitContent::author_digest(author_pubkey, overlay),
seq,
branch, branch,
header_keys: headers.1, header_keys: headers.1,
quorum, quorum,
@ -78,7 +77,7 @@ impl CommitV0 {
let content_ser = serde_bare::to_vec(&content).unwrap(); let content_ser = serde_bare::to_vec(&content).unwrap();
// sign commit // sign commit
let sig = sign(&author_privkey, &author_pubkey, &content_ser)?; let sig = sign(author_privkey, author_pubkey, &content_ser)?;
Ok(CommitV0 { Ok(CommitV0 {
content: content, content: content,
sig, sig,
@ -86,15 +85,15 @@ impl CommitV0 {
key: None, key: None,
header: headers.0, header: headers.0,
body: OnceCell::new(), body: OnceCell::new(),
blocks: vec![],
}) })
} }
#[cfg(test)] #[cfg(test)]
/// New commit with invalid header, only for test purposes /// New commit with invalid header, only for test purposes
pub fn new_with_invalid_header( pub fn new_with_invalid_header(
author_privkey: PrivKey, author_privkey: &PrivKey,
author_pubkey: PubKey, author_pubkey: &PubKey,
seq: u64,
branch: BranchId, branch: BranchId,
quorum: QuorumType, quorum: QuorumType,
metadata: Vec<u8>, metadata: Vec<u8>,
@ -103,8 +102,7 @@ impl CommitV0 {
let headers = CommitHeader::new_invalid(); let headers = CommitHeader::new_invalid();
let content = CommitContent::V0(CommitContentV0 { let content = CommitContent::V0(CommitContentV0 {
perms: vec![], perms: vec![],
author: (&author_pubkey).into(), author: CommitContent::author_digest(&author_pubkey, OverlayId::dummy()),
seq,
branch, branch,
header_keys: headers.1, header_keys: headers.1,
quorum, quorum,
@ -122,16 +120,70 @@ impl CommitV0 {
key: None, key: None,
header: headers.0, header: headers.0,
body: OnceCell::new(), body: OnceCell::new(),
blocks: vec![],
}) })
} }
pub fn save(
&mut self,
block_size: usize,
store_pubkey: &StoreRepo,
store_secret: &ReadCapSecret,
store: &Box<impl RepoStore + ?Sized>,
) -> Result<ObjectRef, StorageError> {
if self.id.is_some() && self.key.is_some() {
return Ok(ObjectRef::from_id_key(
self.id.unwrap(),
self.key.as_ref().unwrap().clone(),
));
}
// log_debug!("{:?}", self.header);
let mut obj = Object::new(
ObjectContent::V0(ObjectContentV0::Commit(Commit::V0(self.clone()))),
self.header.clone(),
block_size,
store_pubkey,
store_secret,
);
self.blocks = obj.save(store)?;
if let Some(h) = &mut self.header {
if let Some(id) = obj.header().as_ref().unwrap().id() {
h.set_id(*id);
}
}
self.id = Some(obj.get_and_save_id());
self.key = Some(obj.key().unwrap());
Ok(obj.reference().unwrap())
}
}
impl IObject for Commit {
fn block_ids(&self) -> Vec<BlockId> {
self.blocks().clone()
}
/// Get ID of including `Object`,
/// only available if the Commit was loaded from store or saved
fn id(&self) -> Option<ObjectId> {
match self {
Commit::V0(c) => c.id,
}
}
/// Get key of including `Object`
/// only available if the Commit was loaded from store or saved
fn key(&self) -> Option<SymKey> {
match self {
Commit::V0(c) => c.key.clone(),
}
}
} }
impl Commit { impl Commit {
/// New commit /// New commit
pub fn new( pub fn new(
author_privkey: PrivKey, author_privkey: &PrivKey,
author_pubkey: PubKey, author_pubkey: &PubKey,
seq: u64, overlay: OverlayId,
branch: BranchId, branch: BranchId,
quorum: QuorumType, quorum: QuorumType,
deps: Vec<ObjectRef>, deps: Vec<ObjectRef>,
@ -146,7 +198,7 @@ impl Commit {
CommitV0::new( CommitV0::new(
author_privkey, author_privkey,
author_pubkey, author_pubkey,
seq, overlay,
branch, branch,
quorum, quorum,
deps, deps,
@ -161,11 +213,43 @@ impl Commit {
.map(|c| Commit::V0(c)) .map(|c| Commit::V0(c))
} }
/// New commit /// New commit with a body. everything is saved
pub fn new_with_body_acks_deps_and_save(
author_privkey: &PrivKey,
author_pubkey: &PubKey,
branch: BranchId,
quorum: QuorumType,
deps: Vec<ObjectRef>,
acks: Vec<ObjectRef>,
body: CommitBody,
store_pubkey: &StoreRepo,
store_secret: &ReadCapSecret,
storage: &Box<impl RepoStore + ?Sized>,
) -> Result<Commit, NgError> {
Self::new_with_body_and_save(
author_privkey,
author_pubkey,
branch,
quorum,
deps,
vec![],
acks,
vec![],
vec![],
vec![],
vec![],
body,
0,
store_pubkey,
store_secret,
storage,
)
}
/// New commit with a body. everything is saved
pub fn new_with_body_and_save( pub fn new_with_body_and_save(
author_privkey: PrivKey, author_privkey: &PrivKey,
author_pubkey: PubKey, author_pubkey: &PubKey,
seq: u64,
branch: BranchId, branch: BranchId,
quorum: QuorumType, quorum: QuorumType,
deps: Vec<ObjectRef>, deps: Vec<ObjectRef>,
@ -179,16 +263,16 @@ impl Commit {
block_size: usize, block_size: usize,
store_pubkey: &StoreRepo, store_pubkey: &StoreRepo,
store_secret: &ReadCapSecret, store_secret: &ReadCapSecret,
store: &Box<impl RepoStore + ?Sized>, storage: &Box<impl RepoStore + ?Sized>,
) -> Result<Commit, NgError> { ) -> Result<Commit, NgError> {
let body_ref = body let (body_ref, mut saved_body) =
.clone() body.clone()
.save(block_size, store_pubkey, store_secret, store)?; .save(block_size, store_pubkey, store_secret, storage)?;
let overlay = store_pubkey.overlay_id_for_read_purpose();
let mut commit = CommitV0::new( let mut commit_v0 = CommitV0::new(
author_privkey, author_privkey,
author_pubkey, author_pubkey,
seq, overlay,
branch, branch,
quorum, quorum,
deps, deps,
@ -199,16 +283,12 @@ impl Commit {
nfiles, nfiles,
metadata, metadata,
body_ref, body_ref,
) )?;
.map(|c| Commit::V0(c))?; commit_v0.body.set(body).unwrap();
let _commit_ref = commit_v0.save(block_size, store_pubkey, store_secret, storage)?;
commit.set_body(body); commit_v0.blocks.append(&mut saved_body);
let commit_ref = commit.save(block_size, store_pubkey, store_secret, store)?; Ok(Commit::V0(commit_v0))
commit.set_id(commit_ref.id);
commit.set_key(commit_ref.key);
Ok(commit)
} }
pub fn reference(&self) -> Option<ObjectRef> { pub fn reference(&self) -> Option<ObjectRef> {
@ -230,31 +310,13 @@ impl Commit {
store: &Box<impl RepoStore + ?Sized>, store: &Box<impl RepoStore + ?Sized>,
) -> Result<ObjectRef, StorageError> { ) -> Result<ObjectRef, StorageError> {
match self { match self {
Commit::V0(v0) => { Commit::V0(v0) => v0.save(block_size, store_pubkey, store_secret, store),
if v0.id.is_some() && v0.key.is_some() { }
return Ok(ObjectRef::from_id_key( }
v0.id.unwrap(),
v0.key.as_ref().unwrap().clone(), pub fn blocks(&self) -> &Vec<BlockId> {
)); match self {
} Commit::V0(v0) => &v0.blocks,
log_debug!("{:?}", v0.header);
let mut obj = Object::new(
ObjectContent::V0(ObjectContentV0::Commit(Commit::V0(v0.clone()))),
v0.header.clone(),
block_size,
store_pubkey,
store_secret,
);
obj.save(store)?;
if let Some(h) = &mut v0.header {
if let Some(id) = obj.header().as_ref().unwrap().id() {
h.set_id(*id);
}
}
self.set_id(obj.get_and_save_id());
self.set_key(obj.key().unwrap());
Ok(obj.reference().unwrap())
}
} }
} }
@ -325,14 +387,6 @@ impl Commit {
} }
} }
/// Get ID of including `Object`,
/// only available if the Commit was loaded from store or saved
pub fn id(&self) -> Option<ObjectId> {
match self {
Commit::V0(c) => c.id,
}
}
/// Get ID of header `Object` /// Get ID of header `Object`
pub fn header_id(&self) -> &Option<ObjectId> { pub fn header_id(&self) -> &Option<ObjectId> {
match self { match self {
@ -350,14 +404,6 @@ impl Commit {
} }
} }
/// Get key of including `Object`
/// only available if the Commit was loaded from store or saved
pub fn key(&self) -> Option<SymKey> {
match self {
Commit::V0(c) => c.key.clone(),
}
}
/// Set key of including `Object` /// Set key of including `Object`
fn set_key(&mut self, key: SymKey) { fn set_key(&mut self, key: SymKey) {
match self { match self {
@ -444,6 +490,7 @@ impl Commit {
} }
Err(CommitLoadError::HeaderLoadError) Err(CommitLoadError::HeaderLoadError)
} }
CommitBody::V0(CommitBodyV0::Delete) => Ok(true),
_ => Ok(false), _ => Ok(false),
} }
} }
@ -525,15 +572,15 @@ impl Commit {
res res
} }
/// Get seq // /// Get seq
pub fn seq(&self) -> u64 { // pub fn seq(&self) -> u64 {
match self { // match self {
Commit::V0(CommitV0 { // Commit::V0(CommitV0 {
content: CommitContent::V0(c), // content: CommitContent::V0(c),
.. // ..
}) => c.seq, // }) => c.seq,
} // }
} // }
/// Verify commit signature /// Verify commit signature
pub fn verify_sig(&self, repo: &Repo) -> Result<(), CommitVerifyError> { pub fn verify_sig(&self, repo: &Repo) -> Result<(), CommitVerifyError> {
@ -680,7 +727,7 @@ impl Commit {
} }
self.verify_sig(repo)?; self.verify_sig(repo)?;
self.verify_perm(repo)?; self.verify_perm(repo)?;
self.verify_full_object_refs_of_branch_at_commit(repo.get_store())?; self.verify_full_object_refs_of_branch_at_commit(repo.get_storage())?;
Ok(()) Ok(())
} }
} }
@ -726,7 +773,7 @@ impl CommitBody {
store_pubkey: &StoreRepo, store_pubkey: &StoreRepo,
store_secret: &ReadCapSecret, store_secret: &ReadCapSecret,
store: &Box<impl RepoStore + ?Sized>, store: &Box<impl RepoStore + ?Sized>,
) -> Result<ObjectRef, StorageError> { ) -> Result<(ObjectRef, Vec<BlockId>), StorageError> {
let obj = Object::new( let obj = Object::new(
ObjectContent::V0(ObjectContentV0::CommitBody(self)), ObjectContent::V0(ObjectContentV0::CommitBody(self)),
None, None,
@ -734,8 +781,8 @@ impl CommitBody {
store_pubkey, store_pubkey,
store_secret, store_secret,
); );
obj.save(store)?; let blocks = obj.save(store)?;
Ok(obj.reference().unwrap()) Ok((obj.reference().unwrap(), blocks))
} }
pub fn root_branch_commit(&self) -> Result<&RootBranch, CommitLoadError> { pub fn root_branch_commit(&self) -> Result<&RootBranch, CommitLoadError> {
@ -771,7 +818,6 @@ impl CommitBody {
CommitBodyV0::Repository(_) => true, CommitBodyV0::Repository(_) => true,
CommitBodyV0::RootBranch(_) => true, CommitBodyV0::RootBranch(_) => true,
CommitBodyV0::UpdateRootBranch(_) => true, CommitBodyV0::UpdateRootBranch(_) => true,
CommitBodyV0::ChangeMainBranch(_) => true,
CommitBodyV0::AddBranch(_) => true, CommitBodyV0::AddBranch(_) => true,
CommitBodyV0::RemoveBranch(_) => true, CommitBodyV0::RemoveBranch(_) => true,
CommitBodyV0::AddMember(_) => true, CommitBodyV0::AddMember(_) => true,
@ -784,6 +830,7 @@ impl CommitBody {
CommitBodyV0::RefreshReadCap(_) => true, CommitBodyV0::RefreshReadCap(_) => true,
CommitBodyV0::RefreshWriteCap(_) => true, CommitBodyV0::RefreshWriteCap(_) => true,
CommitBodyV0::SyncSignature(_) => true, CommitBodyV0::SyncSignature(_) => true,
CommitBodyV0::Delete => true,
_ => false, _ => false,
}, },
} }
@ -809,15 +856,55 @@ impl CommitBody {
} }
} }
pub fn on_store_branch(&self) -> bool {
match self {
Self::V0(v0) => match v0 {
CommitBodyV0::AddRepo(_) => true,
CommitBodyV0::RemoveRepo(_) => true,
_ => false,
},
}
}
pub fn on_user_branch(&self) -> bool {
match self {
Self::V0(v0) => match v0 {
CommitBodyV0::AddLink(_) => true,
CommitBodyV0::RemoveLink(_) => true,
CommitBodyV0::AddSignerCap(_) => true,
CommitBodyV0::RemoveSignerCap(_) => true,
CommitBodyV0::WalletUpdate(_) => true,
CommitBodyV0::StoreUpdate(_) => true,
_ => false,
},
}
}
pub fn not_allowed_on_individual_private_site(&self) -> bool {
match self {
Self::V0(v0) => match v0 {
CommitBodyV0::SyncTransaction(_) => true,
CommitBodyV0::AddMember(_) => true,
CommitBodyV0::RemoveMember(_) => true,
CommitBodyV0::AddPermission(_) => true,
CommitBodyV0::RemovePermission(_) => true,
_ => false,
},
}
}
pub fn total_order_required(&self) -> bool { pub fn total_order_required(&self) -> bool {
match self { match self {
Self::V0(v0) => match v0 { Self::V0(v0) => match v0 {
CommitBodyV0::UpdateRootBranch(_) => true, CommitBodyV0::UpdateRootBranch(_) => true,
CommitBodyV0::UpdateBranch(_) => true, CommitBodyV0::UpdateBranch(_) => true,
CommitBodyV0::ChangeMainBranch(_) => true, CommitBodyV0::AddBranch(AddBranch::V0(AddBranchV0 {
CommitBodyV0::AddBranch(_) => true, branch_type: BranchType::Transactional,
..
})) => false,
CommitBodyV0::AddBranch(AddBranch::V0(AddBranchV0 { branch_type: _, .. })) => true,
CommitBodyV0::RemoveBranch(_) => true, CommitBodyV0::RemoveBranch(_) => true,
CommitBodyV0::AddMember(_) => true, //CommitBodyV0::AddMember(_) => true,
CommitBodyV0::RemoveMember(_) => true, CommitBodyV0::RemoveMember(_) => true,
CommitBodyV0::RemovePermission(_) => true, CommitBodyV0::RemovePermission(_) => true,
//CommitBodyV0::Quorum(_) => true, //CommitBodyV0::Quorum(_) => true,
@ -871,6 +958,7 @@ impl CommitBody {
PermissionV0::RefreshReadCap, PermissionV0::RefreshReadCap,
PermissionV0::RefreshWriteCap, PermissionV0::RefreshWriteCap,
PermissionV0::RefreshOverlay, PermissionV0::RefreshOverlay,
PermissionV0::ChangeMainBranch,
], ],
CommitBodyV0::RemoveBranch(_) => vec![PermissionV0::RemoveBranch], CommitBodyV0::RemoveBranch(_) => vec![PermissionV0::RemoveBranch],
CommitBodyV0::UpdateBranch(_) => { CommitBodyV0::UpdateBranch(_) => {
@ -881,9 +969,6 @@ impl CommitBody {
vec![PermissionV0::ChangeName, PermissionV0::RemoveBranch] vec![PermissionV0::ChangeName, PermissionV0::RemoveBranch]
} }
CommitBodyV0::Branch(_) => vec![PermissionV0::Create, PermissionV0::AddBranch], CommitBodyV0::Branch(_) => vec![PermissionV0::Create, PermissionV0::AddBranch],
CommitBodyV0::ChangeMainBranch(_) => {
vec![PermissionV0::Create, PermissionV0::ChangeMainBranch]
}
CommitBodyV0::Snapshot(_) => vec![PermissionV0::WriteAsync], CommitBodyV0::Snapshot(_) => vec![PermissionV0::WriteAsync],
CommitBodyV0::Compact(_) => vec![PermissionV0::Compact], CommitBodyV0::Compact(_) => vec![PermissionV0::Compact],
CommitBodyV0::AsyncTransaction(_) => vec![PermissionV0::WriteAsync], CommitBodyV0::AsyncTransaction(_) => vec![PermissionV0::WriteAsync],
@ -909,6 +994,15 @@ impl CommitBody {
], ],
CommitBodyV0::RefreshReadCap(_) => vec![PermissionV0::RefreshReadCap], CommitBodyV0::RefreshReadCap(_) => vec![PermissionV0::RefreshReadCap],
CommitBodyV0::RefreshWriteCap(_) => vec![PermissionV0::RefreshWriteCap], CommitBodyV0::RefreshWriteCap(_) => vec![PermissionV0::RefreshWriteCap],
CommitBodyV0::Delete => vec![],
CommitBodyV0::AddRepo(_)
| CommitBodyV0::RemoveRepo(_)
| CommitBodyV0::AddLink(_)
| CommitBodyV0::RemoveLink(_)
| CommitBodyV0::AddSignerCap(_)
| CommitBodyV0::RemoveSignerCap(_)
| CommitBodyV0::WalletUpdate(_)
| CommitBodyV0::StoreUpdate(_) => vec![],
}, },
}; };
HashSet::from_iter(res.iter().cloned()) HashSet::from_iter(res.iter().cloned())
@ -971,6 +1065,11 @@ impl CommitHeader {
CommitHeader::V0(v0) => v0.acks.clone(), CommitHeader::V0(v0) => v0.acks.clone(),
} }
} }
pub fn files(&self) -> &Vec<ObjectId> {
match self {
CommitHeader::V0(v0) => &v0.files,
}
}
pub fn acks_and_nacks(&self) -> Vec<ObjectId> { pub fn acks_and_nacks(&self) -> Vec<ObjectId> {
match self { match self {
CommitHeader::V0(v0) => { CommitHeader::V0(v0) => {
@ -1253,42 +1352,46 @@ impl fmt::Display for CommitBody {
// //
// for root branch: // for root branch:
// //
CommitBodyV0::Repository(b) => writeln!(f, "Repository {}", b), CommitBodyV0::Repository(b) => write!(f, "Repository {}", b),
CommitBodyV0::RootBranch(b) => writeln!(f, "RootBranch {}", b), CommitBodyV0::RootBranch(b) => write!(f, "RootBranch {}", b),
_ => unimplemented!(),
/*UpdateRootBranch(RootBranch), // total order enforced with total_order_quorum CommitBodyV0::UpdateRootBranch(b) => write!(f, "UpdateRootBranch {}", b), // total order enforced with total_order_quorum
AddMember(AddMember), // total order enforced with total_order_quorum // CommitBodyV0::AddMember(b) => write!(f, "AddMember {}", b), // total order enforced with total_order_quorum
RemoveMember(RemoveMember), // total order enforced with total_order_quorum // CommitBodyV0::RemoveMember(b) => write!(f, "RemoveMember {}", b), // total order enforced with total_order_quorum
AddPermission(AddPermission), // CommitBodyV0::AddPermission(b) => write!(f, "AddPermission {}", b),
RemovePermission(RemovePermission), // CommitBodyV0::RemovePermission(b) => {
AddBranch(AddBranch), // write!(f, "RemovePermission {}", b)
ChangeMainBranch(ChangeMainBranch), // }
RemoveBranch(RemoveBranch), CommitBodyV0::AddBranch(b) => write!(f, "AddBranch {}", b),
AddName(AddName), // CommitBodyV0::RemoveBranch(b) => write!(f, "RemoveBranch {}", b),
RemoveName(RemoveName), // CommitBodyV0::AddName(b) => write!(f, "AddName {}", b),
// TODO? Quorum(Quorum), // changes the quorum without changing the RootBranch // CommitBodyV0::RemoveName(b) => write!(f, "RemoveName {}", b),
// TODO? Quorum(Quorum) => write!(f, "RootBranch {}", b), // changes the quorum without changing the RootBranch
// //
// For transactional branches: // For transactional branches:
// //
Branch(Branch), // singleton and should be first in branch CommitBodyV0::Branch(b) => write!(f, "Branch {}", b), // singleton and should be first in branch
UpdateBranch(Branch), // total order enforced with total_order_quorum // CommitBodyV0::UpdateBranch(b) => write!(f, "UpdateBranch {}", b), // total order enforced with total_order_quorum
Snapshot(Snapshot), // a soft snapshot // CommitBodyV0::Snapshot(b) => write!(f, "Snapshot {}", b), // a soft snapshot
AsyncTransaction(Transaction), // partial_order // CommitBodyV0::AsyncTransaction(b) => write!(f, "AsyncTransaction {}", b), // partial_order
SyncTransaction(Transaction), // total_order // CommitBodyV0::SyncTransaction(b) => write!(f, "SyncTransaction {}", b), // total_order
AddFile(AddFile), // CommitBodyV0::AddFile(b) => write!(f, "AddFile {}", b),
RemoveFile(RemoveFile), // CommitBodyV0::RemoveFile(b) => write!(f, "RemoveFile {}", b),
Compact(Compact), // a hard snapshot. total order enforced with total_order_quorum // CommitBodyV0::Compact(b) => write!(f, "Compact {}", b), // a hard snapshot. total order enforced with total_order_quorum
//Merge(Merge), //Merge(Merge) => write!(f, "RootBranch {}", b),
//Revert(Revert), // only possible on partial order commit //Revert(Revert) => write!(f, "RootBranch {}", b), // only possible on partial order commit
AsyncSignature(AsyncSignature), // CommitBodyV0::AsyncSignature(b) => write!(f, "AsyncSignature {}", b),
// //
// For both // For both
// //
RefreshReadCap(RefreshReadCap), // CommitBodyV0::RefreshReadCap(b) => write!(f, "RefreshReadCap {}", b),
RefreshWriteCap(RefreshWriteCap), // CommitBodyV0::RefreshWriteCap(b) => {
SyncSignature(SyncSignature),*/ // write!(f, "RefreshWriteCap {}", b)
// }
CommitBodyV0::SyncSignature(b) => write!(f, "SyncSignature {}", b),
_ => unimplemented!(),
} }
} }
} }
@ -1301,7 +1404,7 @@ impl fmt::Display for CommitContent {
Self::V0(v0) => { Self::V0(v0) => {
writeln!(f, "=== CommitContent V0 ===")?; writeln!(f, "=== CommitContent V0 ===")?;
writeln!(f, "====== author: {}", v0.author)?; writeln!(f, "====== author: {}", v0.author)?;
writeln!(f, "====== seq: {}", v0.seq)?; //writeln!(f, "====== seq: {}", v0.seq)?;
writeln!(f, "====== BranchID: {}", v0.branch)?; writeln!(f, "====== BranchID: {}", v0.branch)?;
writeln!(f, "====== quorum: {:?}", v0.quorum)?; writeln!(f, "====== quorum: {:?}", v0.quorum)?;
writeln!(f, "====== Ref body: {}", v0.body)?; writeln!(f, "====== Ref body: {}", v0.body)?;
@ -1375,7 +1478,6 @@ mod test {
expect_blocks_len: usize, expect_blocks_len: usize,
) { ) {
let (priv_key, pub_key) = generate_keypair(); let (priv_key, pub_key) = generate_keypair();
let seq = 3;
let obj_ref = ObjectRef::dummy(); let obj_ref = ObjectRef::dummy();
let branch = pub_key; let branch = pub_key;
@ -1383,13 +1485,14 @@ mod test {
let acks = obj_refs.clone(); let acks = obj_refs.clone();
let files = obj_refs.clone(); let files = obj_refs.clone();
let body_ref = obj_ref.clone(); let body_ref = obj_ref.clone();
let overlay = OverlayId::dummy();
let metadata = vec![66; metadata_size]; let metadata = vec![66; metadata_size];
let mut commit = Commit::new( let mut commit = Commit::new(
priv_key, &priv_key,
pub_key, &pub_key,
seq, overlay,
branch, branch,
QuorumType::NoSigning, QuorumType::NoSigning,
deps, deps,
@ -1479,7 +1582,7 @@ mod test {
let hashmap_storage = HashMapRepoStore::new(); let hashmap_storage = HashMapRepoStore::new();
let storage = Box::new(hashmap_storage); let storage = Box::new(hashmap_storage);
obj.save(&storage).expect("save object"); _ = obj.save(&storage).expect("save object");
let commit = Commit::load(obj.reference().unwrap(), &storage, false); let commit = Commit::load(obj.reference().unwrap(), &storage, false);
@ -1489,7 +1592,6 @@ mod test {
#[test] #[test]
pub fn test_load_commit_with_body() { pub fn test_load_commit_with_body() {
let (priv_key, pub_key) = generate_keypair(); let (priv_key, pub_key) = generate_keypair();
let seq = 3;
let obj_ref = ObjectRef::dummy(); let obj_ref = ObjectRef::dummy();
let branch = pub_key; let branch = pub_key;
@ -1514,9 +1616,8 @@ mod test {
let storage = Box::new(hashmap_storage); let storage = Box::new(hashmap_storage);
let commit = Commit::new_with_body_and_save( let commit = Commit::new_with_body_and_save(
priv_key, &priv_key,
pub_key, &pub_key,
seq,
branch, branch,
QuorumType::NoSigning, QuorumType::NoSigning,
deps, deps,
@ -1532,7 +1633,7 @@ mod test {
&store_secret, &store_secret,
&storage, &storage,
) )
.expect("commit::new_With_body_and_save"); .expect("commit::new_with_body_and_save");
log_debug!("{}", commit); log_debug!("{}", commit);
@ -1547,7 +1648,6 @@ mod test {
#[test] #[test]
pub fn test_commit_load_body_fails() { pub fn test_commit_load_body_fails() {
let (priv_key, pub_key) = generate_keypair(); let (priv_key, pub_key) = generate_keypair();
let seq = 3;
let obj_ref = ObjectRef::dummy(); let obj_ref = ObjectRef::dummy();
let obj_refs = vec![obj_ref.clone()]; let obj_refs = vec![obj_ref.clone()];
let branch = pub_key; let branch = pub_key;
@ -1556,11 +1656,12 @@ mod test {
let files = obj_refs.clone(); let files = obj_refs.clone();
let metadata = vec![1, 2, 3]; let metadata = vec![1, 2, 3];
let body_ref = obj_ref.clone(); let body_ref = obj_ref.clone();
let overlay = OverlayId::dummy();
let commit = Commit::new( let commit = Commit::new(
priv_key, &priv_key,
pub_key, &pub_key,
seq, overlay,
branch, branch,
QuorumType::NoSigning, QuorumType::NoSigning,
deps, deps,
@ -1578,9 +1679,10 @@ mod test {
let hashmap_storage = HashMapRepoStore::new(); let hashmap_storage = HashMapRepoStore::new();
let t = Test::storage(hashmap_storage); let t = Test::storage(hashmap_storage);
let repo = Repo::new_with_member(&pub_key, &pub_key, &[PermissionV0::Create], t.s()); let repo =
Repo::new_with_member(&pub_key, &pub_key, &[PermissionV0::Create], overlay, t.s());
match commit.load_body(repo.get_store()) { match commit.load_body(repo.get_storage()) {
Ok(_b) => panic!("Body should not exist"), Ok(_b) => panic!("Body should not exist"),
Err(CommitLoadError::BodyLoadError(missing)) => { Err(CommitLoadError::BodyLoadError(missing)) => {
assert_eq!(missing.len(), 1); assert_eq!(missing.len(), 1);
@ -1597,7 +1699,7 @@ mod test {
Err(e) => panic!("Commit verify perm error: {:?}", e), Err(e) => panic!("Commit verify perm error: {:?}", e),
} }
match commit.verify_full_object_refs_of_branch_at_commit(repo.get_store()) { match commit.verify_full_object_refs_of_branch_at_commit(repo.get_storage()) {
Ok(_) => panic!("Commit should not be Ok"), Ok(_) => panic!("Commit should not be Ok"),
Err(CommitLoadError::BodyLoadError(missing)) => { Err(CommitLoadError::BodyLoadError(missing)) => {
assert_eq!(missing.len(), 1); assert_eq!(missing.len(), 1);
@ -1617,7 +1719,6 @@ mod test {
#[test] #[test]
pub fn test_load_commit_with_body_verify_perms() { pub fn test_load_commit_with_body_verify_perms() {
let (priv_key, pub_key) = generate_keypair(); let (priv_key, pub_key) = generate_keypair();
let seq = 3;
let obj_ref = ObjectRef::dummy(); let obj_ref = ObjectRef::dummy();
let branch = pub_key; let branch = pub_key;
@ -1638,9 +1739,8 @@ mod test {
let t = Test::storage(hashmap_storage); let t = Test::storage(hashmap_storage);
let commit = Commit::new_with_body_and_save( let commit = Commit::new_with_body_and_save(
priv_key, &priv_key,
pub_key, &pub_key,
seq,
branch, branch,
QuorumType::NoSigning, QuorumType::NoSigning,
vec![], vec![],
@ -1660,9 +1760,15 @@ mod test {
log_debug!("{}", commit); log_debug!("{}", commit);
let repo = Repo::new_with_member(&pub_key, &pub_key, &[PermissionV0::Create], t.s()); let repo = Repo::new_with_member(
&pub_key,
&pub_key,
&[PermissionV0::Create],
store_repo.overlay_id_for_read_purpose(),
t.s(),
);
commit.load_body(repo.get_store()).expect("load body"); commit.load_body(repo.get_storage()).expect("load body");
commit.verify_sig(&repo).expect("verify signature"); commit.verify_sig(&repo).expect("verify signature");
commit.verify_perm(&repo).expect("verify perms"); commit.verify_perm(&repo).expect("verify perms");
@ -1671,7 +1777,7 @@ mod test {
.expect("verify_perm_creation"); .expect("verify_perm_creation");
commit commit
.verify_full_object_refs_of_branch_at_commit(repo.get_store()) .verify_full_object_refs_of_branch_at_commit(repo.get_storage())
.expect("verify is at root of branch and singleton"); .expect("verify is at root of branch and singleton");
commit.verify(&repo).expect("verify"); commit.verify(&repo).expect("verify");
@ -1680,7 +1786,6 @@ mod test {
#[test] #[test]
pub fn test_load_commit_with_invalid_header() { pub fn test_load_commit_with_invalid_header() {
let (priv_key, pub_key) = generate_keypair(); let (priv_key, pub_key) = generate_keypair();
let seq = 3;
let obj_ref = ObjectRef::dummy(); let obj_ref = ObjectRef::dummy();
let branch = pub_key; let branch = pub_key;
@ -1691,9 +1796,8 @@ mod test {
let commit = Commit::V0( let commit = Commit::V0(
CommitV0::new_with_invalid_header( CommitV0::new_with_invalid_header(
priv_key, &priv_key,
pub_key, &pub_key,
seq,
branch, branch,
QuorumType::NoSigning, QuorumType::NoSigning,
metadata, metadata,
@ -1707,7 +1811,13 @@ mod test {
let hashmap_storage = HashMapRepoStore::new(); let hashmap_storage = HashMapRepoStore::new();
let t = Test::storage(hashmap_storage); let t = Test::storage(hashmap_storage);
let repo = Repo::new_with_member(&pub_key, &pub_key, &[PermissionV0::Create], t.s()); let repo = Repo::new_with_member(
&pub_key,
&pub_key,
&[PermissionV0::Create],
OverlayId::dummy(),
t.s(),
);
assert_eq!( assert_eq!(
commit.verify(&repo), commit.verify(&repo),

@ -19,7 +19,9 @@ use std::error::Error;
#[repr(u16)] #[repr(u16)]
pub enum NgError { pub enum NgError {
InvalidSignature, InvalidSignature,
IncompleteSignature,
SerializationError, SerializationError,
EncryptionError,
InvalidKey, InvalidKey,
InvalidInvitation, InvalidInvitation,
InvalidCreateAccount, InvalidCreateAccount,
@ -29,14 +31,29 @@ pub enum NgError {
CommitLoadError(CommitLoadError), CommitLoadError(CommitLoadError),
StorageError(StorageError), StorageError(StorageError),
NotFound, NotFound,
IoError,
CommitVerifyError(CommitVerifyError), CommitVerifyError(CommitVerifyError),
LocalBrokerNotInitialized,
JsStorageReadError,
JsStorageWriteError(String),
CannotSaveWhenInMemoryConfig,
WalletNotFound,
WalletAlreadyAdded,
WalletAlreadyOpened,
WalletError(String),
BrokerError,
LockError,
SessionNotFound,
} }
impl Error for NgError {} impl Error for NgError {}
impl fmt::Display for NgError { impl fmt::Display for NgError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self) match self {
Self::WalletError(string) => write!(f, "WalletError: {}", string),
_ => write!(f, "{:?}", self),
}
} }
} }

@ -0,0 +1,119 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! Event
use crate::errors::*;
use crate::object::*;
use crate::store::*;
use crate::types::*;
use crate::utils::*;
use core::fmt;
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::V0(v0) => {
writeln!(f, "V0")?;
writeln!(f, "topic_sig: {}", v0.topic_sig)?;
writeln!(f, "peer_sig: {}", v0.peer_sig)?;
write!(f, "content: {}", v0.content)?;
Ok(())
}
}
}
}
impl fmt::Display for EventContentV0 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "V0")?;
writeln!(f, "topic: {}", self.topic)?;
writeln!(f, "publisher: {}", self.publisher)?;
writeln!(f, "seq: {}", self.seq)?;
writeln!(f, "blocks: {}", self.blocks.len())?;
let mut i = 0;
for block in &self.blocks {
writeln!(f, "========== {:03}: {}", i, block.id())?;
i += 1;
}
writeln!(f, "file ids: {}", self.file_ids.len())?;
let mut i = 0;
for file in &self.file_ids {
writeln!(f, "========== {:03}: {}", i, file)?;
i += 1;
}
writeln!(f, "key: {:?}", self.key)?;
Ok(())
}
}
impl Event {
pub fn new<'a>(
publisher: &PrivKey,
seq: &mut u64,
commit: &Commit,
additional_blocks: &Vec<BlockId>,
topic_id: TopicId,
branch_read_cap_secret: ReadCapSecret,
topic_priv_key: &BranchWriteCapSecret,
storage: &'a Box<dyn RepoStore + Send + Sync + 'a>,
) -> Result<Event, NgError> {
Ok(Event::V0(EventV0::new(
publisher,
seq,
commit,
additional_blocks,
topic_id,
branch_read_cap_secret,
topic_priv_key,
storage,
)?))
}
}
impl EventV0 {
pub fn new<'a>(
publisher: &PrivKey,
seq: &mut u64,
commit: &Commit,
additional_blocks: &Vec<BlockId>,
topic_id: TopicId,
branch_read_cap_secret: ReadCapSecret,
topic_priv_key: &BranchWriteCapSecret,
storage: &'a Box<dyn RepoStore + Send + Sync + 'a>,
) -> Result<EventV0, NgError> {
let mut blocks = vec![];
for bid in commit.blocks().iter() {
blocks.push(storage.get(bid)?);
}
for bid in additional_blocks.iter() {
blocks.push(storage.get(bid)?);
}
(*seq) += 1;
let publisher_pubkey = publisher.to_pub();
let event_content = EventContentV0 {
topic: topic_id,
publisher: PeerId::Forwarded(publisher_pubkey),
seq: *seq,
blocks,
file_ids: commit
.header()
.as_ref()
.map_or_else(|| vec![], |h| h.files().to_vec()),
key: vec![], // TODO
};
let event_content_ser = serde_bare::to_vec(&event_content).unwrap();
let topic_sig = sign(topic_priv_key, &topic_id, &event_content_ser)?;
let peer_sig = sign(publisher, &publisher_pubkey, &event_content_ser)?;
Ok(EventV0 {
content: event_content,
topic_sig,
peer_sig,
})
}
}

@ -15,6 +15,7 @@ use std::collections::HashMap;
use chacha20::cipher::{KeyIvInit, StreamCipher}; use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::ChaCha20; use chacha20::ChaCha20;
use zeroize::Zeroize;
use crate::errors::*; use crate::errors::*;
use crate::log::*; use crate::log::*;
@ -160,6 +161,7 @@ pub struct RandomAccessFile<'a> {
impl<'a> ReadFile for RandomAccessFile<'a> { impl<'a> ReadFile for RandomAccessFile<'a> {
/// reads at most one block from the file. the returned vector should be tested for size. it might be smaller than what you asked for. /// reads at most one block from the file. the returned vector should be tested for size. it might be smaller than what you asked for.
/// `pos`ition can be anywhere in the file. /// `pos`ition can be anywhere in the file.
//TODO: parallelize decryption on multi threads (cores)
fn read(&self, pos: usize, size: usize) -> Result<Vec<u8>, FileError> { fn read(&self, pos: usize, size: usize) -> Result<Vec<u8>, FileError> {
if size == 0 { if size == 0 {
return Err(FileError::InvalidArgument); return Err(FileError::InvalidArgument);
@ -378,7 +380,7 @@ impl<'a> RandomAccessFile<'a> {
conv_key, conv_key,
); );
//log_debug!("saving meta object"); //log_debug!("saving meta object");
meta_object.save(storage)?; _ = meta_object.save(storage)?;
// creating the root block that contains as first child the meta_object, and as second child the content_block // creating the root block that contains as first child the meta_object, and as second child the content_block
// it is added to storage in make_parent_block // it is added to storage in make_parent_block
@ -413,7 +415,7 @@ impl<'a> RandomAccessFile<'a> {
let total_size = content.len() as u64; let total_size = content.len() as u64;
let conv_key = Object::convergence_key(store, store_secret); let mut conv_key = Object::convergence_key(store, store_secret);
let mut blocks: Vec<(BlockId, BlockKey)> = vec![]; let mut blocks: Vec<(BlockId, BlockKey)> = vec![];
@ -453,6 +455,8 @@ impl<'a> RandomAccessFile<'a> {
storage, storage,
)?; )?;
conv_key.zeroize();
Ok(Self { Ok(Self {
storage, storage,
meta, meta,
@ -596,6 +600,9 @@ impl<'a> RandomAccessFile<'a> {
self.storage, self.storage,
)?; )?;
self.conv_key.as_mut().unwrap().zeroize();
self.conv_key = None;
self.id = Some(root_block.0); self.id = Some(root_block.0);
self.key = Some(root_block.1.clone()); self.key = Some(root_block.1.clone());
self.content_block = Some(content_block); self.content_block = Some(content_block);

@ -24,6 +24,8 @@ pub mod repo;
pub mod site; pub mod site;
pub mod event;
pub mod utils; pub mod utils;
pub mod errors; pub mod errors;

@ -1,7 +1,5 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers // Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved. // All rights reserved.
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
// Copyright 2022 TG x Thoth
// Licensed under the Apache License, Version 2.0 // Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> // <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, // or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
@ -12,11 +10,13 @@
//! Merkle hash tree of Objects //! Merkle hash tree of Objects
use core::fmt; use core::fmt;
use std::borrow::BorrowMut;
use std::cmp::max; use std::cmp::max;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use chacha20::cipher::{KeyIvInit, StreamCipher}; use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::ChaCha20; use chacha20::ChaCha20;
use zeroize::Zeroize;
use crate::errors::*; use crate::errors::*;
use crate::log::*; use crate::log::*;
@ -63,13 +63,15 @@ impl Object {
store_pubkey: &StoreRepo, store_pubkey: &StoreRepo,
store_readcap_secret: &ReadCapSecret, store_readcap_secret: &ReadCapSecret,
) -> [u8; blake3::OUT_LEN] { ) -> [u8; blake3::OUT_LEN] {
let key_material = match (*store_pubkey.repo_id(), store_readcap_secret.clone()) { let mut key_material = match (*store_pubkey.repo_id(), store_readcap_secret.clone()) {
(PubKey::Ed25519PubKey(pubkey), SymKey::ChaCha20Key(secret)) => { (PubKey::Ed25519PubKey(pubkey), SymKey::ChaCha20Key(secret)) => {
[pubkey, secret].concat() [pubkey, secret].concat()
} }
(_, _) => panic!("cannot sign with Montgomery key"), (_, _) => panic!("cannot sign with Montgomery key"),
}; };
blake3::derive_key("NextGraph Data BLAKE3 key", key_material.as_slice()) let res = blake3::derive_key("NextGraph Data BLAKE3 key", key_material.as_slice());
key_material.zeroize();
res
} }
fn make_block( fn make_block(
@ -272,8 +274,10 @@ impl Object {
store: &StoreRepo, store: &StoreRepo,
store_secret: &ReadCapSecret, store_secret: &ReadCapSecret,
) -> Object { ) -> Object {
let conv_key = Self::convergence_key(store, store_secret); let mut conv_key = Self::convergence_key(store, store_secret);
Self::new_with_convergence_key(content, header, block_size, &conv_key) let res = Self::new_with_convergence_key(content, header, block_size, &conv_key);
conv_key.zeroize();
res
} }
pub fn new_with_convergence_key( pub fn new_with_convergence_key(
@ -287,10 +291,10 @@ impl Object {
"cannot make a new Object with header if ObjectContent type different from Commit" "cannot make a new Object with header if ObjectContent type different from Commit"
); );
} }
log_debug!("header {:?}", header); // log_debug!("header {:?}", header);
// create blocks by chunking + encrypting content // create blocks by chunking + encrypting content
let valid_block_size = store_valid_value_size(block_size); let valid_block_size = store_valid_value_size(block_size);
log_debug!("valid_block_size {}", valid_block_size); // log_debug!("valid_block_size {}", valid_block_size);
// let max_arity_leaves: usize = (valid_block_size - BLOCK_EXTRA) / CHILD_SIZE; // let max_arity_leaves: usize = (valid_block_size - BLOCK_EXTRA) / CHILD_SIZE;
// let max_arity_root: usize = // let max_arity_root: usize =
@ -318,17 +322,17 @@ impl Object {
} }
} }
}; };
log_debug!("{:?} {:?}", header, header_prepare); // log_debug!("{:?} {:?}", header, header_prepare);
let content_ser = serde_bare::to_vec(&content).unwrap(); let content_ser = serde_bare::to_vec(&content).unwrap();
let content_len = content_ser.len(); let content_len = content_ser.len();
log_debug!( // log_debug!(
"only one block? {} {} {}", // "only one block? {} {} {}",
content_len <= max_data_payload_size, // content_len <= max_data_payload_size,
content_len, // content_len,
max_data_payload_size // max_data_payload_size
); // );
let header_blocks = if content_len <= max_data_payload_size { let header_blocks = if content_len <= max_data_payload_size {
// content fits in root node // content fits in root node
let data_chunk = ChunkContentV0::DataChunk(content_ser.clone()); let data_chunk = ChunkContentV0::DataChunk(content_ser.clone());
@ -371,7 +375,7 @@ impl Object {
&mut block_contents, &mut block_contents,
&mut already_existing, &mut already_existing,
); );
log_debug!("make_block {} of {} - {}%", i, total, i * 100 / total); log_debug!("make_block {} of {} - {}%", i, total + 1, i * 100 / total);
i = i + 1; i = i + 1;
} }
@ -394,11 +398,11 @@ impl Object {
}; };
if header_blocks.len() > 0 { if header_blocks.len() > 0 {
log_debug!( // log_debug!(
"header_blocks.len() {} {}", // "header_blocks.len() {} {}",
header_blocks.len(), // header_blocks.len(),
header_blocks.last().unwrap().id() // header_blocks.last().unwrap().id()
); // );
header header
.as_mut() .as_mut()
.unwrap() .unwrap()
@ -513,10 +517,11 @@ impl Object {
} }
/// Save blocks of the object and the blocks of the header object in the store /// Save blocks of the object and the blocks of the header object in the store
pub fn save(&self, store: &Box<impl RepoStore + ?Sized>) -> Result<(), StorageError> { pub fn save(&self, store: &Box<impl RepoStore + ?Sized>) -> Result<Vec<BlockId>, StorageError> {
let mut deduplicated: HashSet<ObjectId> = HashSet::new(); let mut deduplicated: HashSet<ObjectId> = HashSet::new();
//.chain(self.header_blocks.iter()) //.chain(self.header_blocks.iter())
for block_id in self.blocks.iter() { for block_id in self.blocks.iter() {
deduplicated.insert(*block_id);
store.put(self.block_contents.get(block_id).unwrap())?; store.put(self.block_contents.get(block_id).unwrap())?;
} }
for block in &self.header_blocks { for block in &self.header_blocks {
@ -526,14 +531,20 @@ impl Object {
store.put(block)?; store.put(block)?;
} }
} }
Ok(()) let root_id = self.id();
let mut blocks = vec![root_id];
deduplicated.remove(&root_id);
let mut list = deduplicated.drain();
blocks.append(&mut list.collect());
deduplicated.shrink_to(0);
Ok(blocks)
} }
#[cfg(test)] #[cfg(test)]
pub fn save_in_test( pub fn save_in_test(
&mut self, &mut self,
store: &Box<impl RepoStore + ?Sized>, store: &Box<impl RepoStore + ?Sized>,
) -> Result<(), StorageError> { ) -> Result<Vec<BlockId>, StorageError> {
assert!(self.already_saved == false); assert!(self.already_saved == false);
self.already_saved = true; self.already_saved = true;
@ -625,12 +636,12 @@ impl Object {
let mut total = 0; let mut total = 0;
self.blocks().for_each(|b| { self.blocks().for_each(|b| {
let s = b.size(); let s = b.size();
log_debug!("@@@@ {}", s); //log_debug!("@@@@ {}", s);
total += s; total += s;
}); });
self.header_blocks.iter().for_each(|b| { self.header_blocks.iter().for_each(|b| {
let s = b.size(); let s = b.size();
log_debug!("@@@@ {}", s); //log_debug!("@@@@ {}", s);
total += s; total += s;
}); });
total total
@ -827,6 +838,37 @@ impl Object {
} }
} }
impl IObject for Object {
fn block_ids(&self) -> Vec<BlockId> {
let mut deduplicated: HashSet<ObjectId> = HashSet::new();
//.chain(self.header_blocks.iter())
for block_id in self.blocks.iter() {
deduplicated.insert(*block_id);
}
for block in &self.header_blocks {
let id = block.id();
if deduplicated.get(&id).is_none() {
deduplicated.insert(id);
}
}
let root_id = self.id();
let mut blocks = vec![root_id];
deduplicated.remove(&root_id);
let mut list = deduplicated.drain();
blocks.append(&mut list.collect());
deduplicated.shrink_to(0);
blocks
}
fn id(&self) -> Option<ObjectId> {
Some(self.id())
}
fn key(&self) -> Option<SymKey> {
self.key()
}
}
impl fmt::Display for Object { impl fmt::Display for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "====== Object ID {}", self.id())?; writeln!(f, "====== Object ID {}", self.id())?;

@ -0,0 +1,574 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! Repository serde implementation and in memory helper
use crate::errors::*;
use crate::event::*;
use crate::log::*;
use crate::object::Object;
use crate::store::*;
use crate::types::*;
use crate::utils::generate_keypair;
use crate::utils::sign;
use core::fmt;
use rand::prelude::*;
use std::collections::HashMap;
use std::collections::HashSet;
use threshold_crypto::{SecretKeySet, SecretKeyShare};
impl RepositoryV0 {
pub fn new(id: &PubKey, metadata: &Vec<u8>) -> RepositoryV0 {
RepositoryV0 {
id: id.clone(),
metadata: metadata.clone(),
verification_program: vec![],
creator: None,
}
}
}
impl Repository {
pub fn new(id: &PubKey, metadata: &Vec<u8>) -> Repository {
Repository::V0(RepositoryV0::new(id, metadata))
}
}
#[derive(Debug)]
pub struct UserInfo {
/// list of permissions granted to user, with optional metadata
pub permissions: HashMap<PermissionV0, Vec<u8>>,
pub id: UserId,
}
impl UserInfo {
pub fn has_any_perm(&self, perms: &HashSet<PermissionV0>) -> Result<(), NgError> {
//log_debug!("perms {:?}", perms);
if self.has_perm(&PermissionV0::Owner).is_ok() {
return Ok(());
}
let is_admin = self.has_perm(&PermissionV0::Admin).is_ok();
//log_debug!("is_admin {}", is_admin);
//is_delegated_by_admin
let has_perms: HashSet<&PermissionV0> = self.permissions.keys().collect();
//log_debug!("has_perms {:?}", has_perms);
for perm in perms {
if is_admin && perm.is_delegated_by_admin() || has_perms.contains(perm) {
return Ok(());
}
}
// if has_perms.intersection(perms).count() > 0 {
// Ok(())
// } else {
Err(NgError::PermissionDenied)
}
pub fn has_perm(&self, perm: &PermissionV0) -> Result<&Vec<u8>, NgError> {
self.permissions.get(perm).ok_or(NgError::PermissionDenied)
}
}
/// In memory Repository representation. With helper functions that access the underlying UserStore and keeps proxy of the values
pub struct Repo<'a> {
/// Repo definition
pub repo_def: Repository,
pub signer: Option<SignerCap>,
pub members: HashMap<Digest, UserInfo>,
storage: &'a Box<dyn RepoStore + Send + Sync + 'a>,
}
impl<'a> fmt::Display for Repo<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "====== Repo ======")?;
write!(f, "== repo_def: {}", self.repo_def)?;
if self.signer.is_some() {
writeln!(f, "== signer: {:?}", self.signer)?;
}
writeln!(f, "== members: {:?}", self.members)?;
Ok(())
}
}
impl<'a> Repo<'a> {
/// returns the Repo and the last seq_num of the peer
pub fn new_default(
creator: &UserId,
creator_priv_key: &PrivKey,
publisher_peer: &PrivKey,
peer_last_seq_num: &mut u64,
store_repo: &StoreRepo,
store_secret: &ReadCapSecret,
storage: &'a Box<dyn RepoStore + Send + Sync + 'a>,
) -> Result<(Self, Vec<Event>), NgError> {
let mut events = Vec::with_capacity(6);
// creating the Repository commit
let (repo_priv_key, repo_pub_key) = generate_keypair();
//let overlay = store_repo.overlay_id_for_read_purpose();
let repository = Repository::V0(RepositoryV0 {
id: repo_pub_key,
verification_program: vec![],
creator: None,
metadata: vec![],
});
let repository_commit_body = CommitBody::V0(CommitBodyV0::Repository(repository.clone()));
let repository_commit = Commit::new_with_body_acks_deps_and_save(
&repo_priv_key,
&repo_pub_key,
repo_pub_key,
QuorumType::NoSigning,
vec![],
vec![],
repository_commit_body,
&store_repo,
&store_secret,
storage,
)?;
log_debug!("REPOSITORY COMMIT {}", repository_commit);
let repository_commit_ref = repository_commit.reference().unwrap();
let (topic_priv_key, topic_pub_key) = generate_keypair();
// creating the RootBranch commit, acks to Repository commit
let repo_write_cap_secret = SymKey::random();
let root_branch_commit_body =
CommitBody::V0(CommitBodyV0::RootBranch(RootBranch::V0(RootBranchV0 {
id: repo_pub_key,
repo: repository_commit_ref.clone(),
store: store_repo.into(),
store_sig: None, //TODO: the store signature
topic: topic_pub_key,
topic_privkey: Branch::encrypt_topic_priv_key(
&topic_priv_key,
topic_pub_key,
repo_pub_key,
&repo_write_cap_secret,
),
inherit_perms_users_and_quorum_from_store: None,
quorum: None,
reconciliation_interval: RelTime::None,
owners: vec![creator.clone()],
metadata: vec![],
})));
let root_branch_commit = Commit::new_with_body_acks_deps_and_save(
&repo_priv_key,
&repo_pub_key,
repo_pub_key,
QuorumType::NoSigning,
vec![],
vec![repository_commit_ref.clone()],
root_branch_commit_body,
&store_repo,
&store_secret,
storage,
)?;
log_debug!("ROOT_BRANCH COMMIT {}", root_branch_commit);
// adding the 2 events for the Repository and Rootbranch commits
//peer_last_seq_num += 1;
events.push(Event::new(
publisher_peer,
peer_last_seq_num,
&repository_commit,
&vec![],
topic_pub_key,
root_branch_commit.key().unwrap(),
&topic_priv_key,
storage,
)?);
//peer_last_seq_num += 1;
events.push(Event::new(
publisher_peer,
peer_last_seq_num,
&root_branch_commit,
&vec![],
topic_pub_key,
root_branch_commit.key().unwrap(),
&topic_priv_key,
storage,
)?);
// creating the main branch
let (main_branch_priv_key, main_branch_pub_key) = generate_keypair();
let (main_branch_topic_priv_key, main_branch_topic_pub_key) = generate_keypair();
let main_branch_commit_body = CommitBody::V0(CommitBodyV0::Branch(Branch::V0(BranchV0 {
id: main_branch_pub_key,
repo: repository_commit_ref.clone(),
root_branch_readcap_id: root_branch_commit.id().unwrap(),
topic: main_branch_topic_pub_key,
topic_privkey: Branch::encrypt_topic_priv_key(
&main_branch_topic_priv_key,
main_branch_topic_pub_key,
main_branch_pub_key,
&repo_write_cap_secret,
),
metadata: vec![],
})));
let main_branch_commit = Commit::new_with_body_acks_deps_and_save(
&main_branch_priv_key,
&main_branch_pub_key,
main_branch_pub_key,
QuorumType::NoSigning,
vec![],
vec![],
main_branch_commit_body,
&store_repo,
&store_secret,
storage,
)?;
log_debug!("MAIN BRANCH COMMIT {}", main_branch_commit);
// adding the event for the Branch commit
// peer_last_seq_num += 1;
events.push(Event::new(
publisher_peer,
peer_last_seq_num,
&main_branch_commit,
&vec![],
main_branch_topic_pub_key,
main_branch_commit.key().unwrap(),
&main_branch_topic_priv_key,
storage,
)?);
// creating the AddBranch commit (on root_branch), deps to the RootBranch commit
// author is the owner
let add_branch_commit_body =
CommitBody::V0(CommitBodyV0::AddBranch(AddBranch::V0(AddBranchV0 {
branch_type: BranchType::Main,
topic_id: main_branch_topic_pub_key,
branch_read_cap: main_branch_commit.reference().unwrap(),
})));
let add_branch_commit = Commit::new_with_body_acks_deps_and_save(
creator_priv_key,
creator,
repo_pub_key,
QuorumType::Owners,
vec![root_branch_commit.reference().unwrap()],
vec![],
add_branch_commit_body,
&store_repo,
&store_secret,
storage,
)?;
log_debug!("ADD_BRANCH COMMIT {}", add_branch_commit);
// TODO: optional AddMember and AddPermission, that should be added as deps to the SynSignature below (and to the commits of the SignatureContent)
// using the creator as author (and incrementing their peer's seq_num)
// preparing the threshold keys for the unique owner
let mut rng = rand::thread_rng();
let sk_set = SecretKeySet::random(0, &mut rng);
let pk_set = sk_set.public_keys();
let sk_share = sk_set.secret_key_share(0);
// creating signature for RootBranch, AddBranch and Branch commits
// signed with owner threshold signature (threshold = 0)
let signature_content = SignatureContent::V0(SignatureContentV0 {
commits: vec![
root_branch_commit.id().unwrap(),
add_branch_commit.id().unwrap(),
main_branch_commit.id().unwrap(),
],
});
let signature_content_ser = serde_bare::to_vec(&signature_content).unwrap();
let sig_share = sk_share.sign(signature_content_ser);
let sig = pk_set
.combine_signatures([(0, &sig_share)])
.map_err(|_| NgError::IncompleteSignature)?;
let threshold_sig = ThresholdSignatureV0::Owners((sig));
// creating root certificate of the repo
let cert_content = CertificateContentV0 {
previous: repository_commit_ref,
readcap_id: root_branch_commit.id().unwrap(),
owners_pk_set: pk_set.public_key(),
orders_pk_sets: OrdersPublicKeySetsV0::None,
};
// signing the root certificate
let cert_content_ser = serde_bare::to_vec(&cert_content).unwrap();
let sig = sign(&repo_priv_key, &repo_pub_key, &cert_content_ser)?;
let cert_sig = CertificateSignatureV0::Repo(sig);
let cert = Certificate::V0(CertificateV0 {
content: cert_content,
sig: cert_sig,
});
// saving the certificate
let cert_object = Object::new(
ObjectContent::V0(ObjectContentV0::Certificate(cert)),
None,
0,
&store_repo,
&store_secret,
);
let mut cert_obj_blocks = cert_object.save(storage)?;
// finally getting the signature:
let signature = Signature::V0(SignatureV0 {
content: signature_content,
threshold_sig,
certificate_ref: cert_object.reference().unwrap(),
});
// saving the signature
let sig_object = Object::new(
ObjectContent::V0(ObjectContentV0::Signature(signature)),
None,
0,
&store_repo,
&store_secret,
);
let mut sig_obj_blocks = sig_object.save(storage)?;
// keeping the Secret Key Share of the owner
let signer_cap = SignerCap {
repo: repo_pub_key,
epoch: root_branch_commit.id().unwrap(),
owner: Some(threshold_crypto::serde_impl::SerdeSecret(sk_share)),
total_order: None,
partial_order: None,
};
let sync_signature = SyncSignature::V0(sig_object.reference().unwrap());
// creating the SyncSignature for the root_branch with deps to the AddBranch and acks to the RootBranch commit as it is its direct causal future.
let sync_sig_commit_body = CommitBody::V0(CommitBodyV0::SyncSignature(sync_signature));
let sync_sig_on_root_branch_commit = Commit::new_with_body_acks_deps_and_save(
creator_priv_key,
creator,
repo_pub_key,
QuorumType::IamTheSignature,
vec![add_branch_commit.reference().unwrap()],
vec![root_branch_commit.reference().unwrap()],
sync_sig_commit_body.clone(),
&store_repo,
&store_secret,
storage,
)?;
// adding the event for the sync_sig_on_root_branch_commit
let mut additional_blocks = Vec::with_capacity(
cert_obj_blocks.len() + sig_obj_blocks.len() + add_branch_commit.blocks().len(),
);
additional_blocks.extend(cert_obj_blocks.iter());
additional_blocks.extend(sig_obj_blocks.iter());
additional_blocks.extend(add_branch_commit.blocks().iter());
//peer_last_seq_num += 1;
events.push(Event::new(
publisher_peer,
peer_last_seq_num,
&sync_sig_on_root_branch_commit,
&additional_blocks,
topic_pub_key,
root_branch_commit.key().unwrap(),
&topic_priv_key,
storage,
)?);
// creating the SyncSignature for the main branch with deps to the Branch commit and acks also to this commit as it is its direct causal future.
let sync_sig_on_main_branch_commit = Commit::new_with_body_acks_deps_and_save(
creator_priv_key,
creator,
main_branch_pub_key,
QuorumType::IamTheSignature,
vec![main_branch_commit.reference().unwrap()],
vec![main_branch_commit.reference().unwrap()],
sync_sig_commit_body,
&store_repo,
&store_secret,
storage,
)?;
// adding the event for the sync_sig_on_main_branch_commit
let mut additional_blocks =
Vec::with_capacity(cert_obj_blocks.len() + sig_obj_blocks.len());
additional_blocks.append(&mut cert_obj_blocks);
additional_blocks.append(&mut sig_obj_blocks);
// peer_last_seq_num += 1;
events.push(Event::new(
publisher_peer,
peer_last_seq_num,
&sync_sig_on_main_branch_commit,
&additional_blocks,
main_branch_topic_pub_key,
main_branch_commit.key().unwrap(),
&main_branch_topic_priv_key,
storage,
)?);
// TODO: add the CertificateRefresh event on main branch
// += 1;
// preparing the Repo
let repo = Repo {
repo_def: repository,
signer: Some(signer_cap),
members: HashMap::new(),
storage,
};
Ok((repo, events))
}
pub fn new_with_member(
id: &PubKey,
member: &UserId,
perms: &[PermissionV0],
overlay: OverlayId,
storage: &'a Box<dyn RepoStore + Send + Sync + 'a>,
) -> Self {
let mut members = HashMap::new();
let permissions = HashMap::from_iter(
perms
.iter()
.map(|p| (*p, vec![]))
.collect::<Vec<(PermissionV0, Vec<u8>)>>()
.iter()
.cloned(),
);
members.insert(
CommitContent::author_digest(member, overlay),
UserInfo {
id: *member,
permissions,
},
);
Self {
repo_def: Repository::new(id, &vec![]),
members,
storage,
signer: None,
}
}
pub fn verify_permission(&self, commit: &Commit) -> Result<(), NgError> {
let content_author = commit.content_v0().author;
let body = commit.load_body(&self.storage)?;
match self.members.get(&content_author) {
Some(info) => return info.has_any_perm(&body.required_permission()),
None => {}
}
Err(NgError::PermissionDenied)
}
pub fn member_pubkey(&self, hash: &Digest) -> Result<UserId, NgError> {
match self.members.get(hash) {
Some(user_info) => Ok(user_info.id),
None => Err(NgError::NotFound),
}
}
pub fn get_storage(&self) -> &Box<dyn RepoStore + Send + Sync + 'a> {
self.storage
}
}
#[cfg(test)]
mod test {
use crate::object::*;
use crate::repo::*;
struct Test<'a> {
storage: Box<dyn RepoStore + Send + Sync + 'a>,
}
impl<'a> Test<'a> {
fn storage(s: impl RepoStore + 'a) -> Self {
Test {
storage: Box::new(s),
}
}
fn s(&self) -> &Box<dyn RepoStore + Send + Sync + 'a> {
&self.storage
}
}
#[test]
pub fn test_new_repo_default() {
let (creator_priv_key, creator_pub_key) = generate_keypair();
let (publisher_privkey, publisher_pubkey) = generate_keypair();
let publisher_peer = PeerId::Forwarded(publisher_pubkey);
let mut peer_last_seq_num = 10;
let (store_repo, store_secret) = StoreRepo::dummy_public_v0();
let hashmap_storage = HashMapRepoStore::new();
let t = Test::storage(hashmap_storage);
let (repo, events) = Repo::new_default(
&creator_pub_key,
&creator_priv_key,
&publisher_privkey,
&mut peer_last_seq_num,
&store_repo,
&store_secret,
t.s(),
)
.expect("new_default");
log_debug!("REPO OBJECT {}", repo);
log_debug!("events: {}\n", events.len());
let mut i = 0;
for e in events {
log_debug!("========== EVENT {:03}: {}", i, e);
i += 1;
}
assert_eq!(peer_last_seq_num, 15);
}
}

@ -0,0 +1,138 @@
/*
* Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
use crate::errors::NgError;
use crate::types::*;
use crate::utils::{generate_keypair, sign, verify};
impl SiteV0 {
pub fn get_individual_user_priv_key(&self) -> Option<PrivKey> {
match &self.site_type {
SiteType::Individual((priv_key, _)) => Some(priv_key.clone()),
_ => None,
}
}
pub fn create_personal(
user_priv_key: PrivKey,
private_store_read_cap: ReadCap,
) -> Result<Self, NgError> {
let site_pubkey = user_priv_key.to_pub();
let (public_store_privkey, public_store_pubkey) = generate_keypair();
let (protected_store_privkey, protected_store_pubkey) = generate_keypair();
let (private_store_privkey, private_store_pubkey) = generate_keypair();
let public = SiteStore {
id: public_store_pubkey,
store_type: SiteStoreType::Public,
};
let protected = SiteStore {
id: protected_store_pubkey,
store_type: SiteStoreType::Protected,
};
let private = SiteStore {
id: private_store_pubkey,
store_type: SiteStoreType::Private,
};
Ok(Self {
site_type: SiteType::Individual((user_priv_key, private_store_read_cap)),
id: site_pubkey,
name: SiteName::Personal,
public,
protected,
private,
cores: vec![],
bootstraps: vec![],
})
}
pub fn create_individual(
name: String,
user_priv_key: PrivKey,
private_store_read_cap: ReadCap,
) -> Result<Self, NgError> {
let site_pubkey = user_priv_key.to_pub();
let (public_store_privkey, public_store_pubkey) = generate_keypair();
let (protected_store_privkey, protected_store_pubkey) = generate_keypair();
let (private_store_privkey, private_store_pubkey) = generate_keypair();
let public = SiteStore {
id: public_store_pubkey,
store_type: SiteStoreType::Public,
};
let protected = SiteStore {
id: protected_store_pubkey,
store_type: SiteStoreType::Protected,
};
let private = SiteStore {
id: private_store_pubkey,
store_type: SiteStoreType::Private,
};
Ok(Self {
site_type: SiteType::Individual((user_priv_key, private_store_read_cap)),
id: site_pubkey,
name: SiteName::Name(name),
public,
protected,
private,
cores: vec![],
bootstraps: vec![],
})
}
pub fn create_org(name: String) -> Result<Self, NgError> {
let (site_privkey, site_pubkey) = generate_keypair();
let (public_store_privkey, public_store_pubkey) = generate_keypair();
let (protected_store_privkey, protected_store_pubkey) = generate_keypair();
let (private_store_privkey, private_store_pubkey) = generate_keypair();
let public = SiteStore {
id: public_store_pubkey,
store_type: SiteStoreType::Public,
};
let protected = SiteStore {
id: protected_store_pubkey,
store_type: SiteStoreType::Protected,
};
let private = SiteStore {
id: private_store_pubkey,
store_type: SiteStoreType::Private,
};
Ok(Self {
site_type: SiteType::Org,
id: site_pubkey,
name: SiteName::Name(name),
public,
protected,
private,
cores: vec![],
bootstraps: vec![],
})
}
}

@ -1,7 +1,5 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers // Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved. // All rights reserved.
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
// Copyright 2022 TG x Thoth
// Licensed under the Apache License, Version 2.0 // Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> // <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, // or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,

File diff suppressed because it is too large Load Diff

@ -1,7 +1,5 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers // Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved. // All rights reserved.
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
// Copyright 2022 TG x Thoth
// Licensed under the Apache License, Version 2.0 // Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> // <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, // or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
@ -13,6 +11,8 @@ use crate::errors::*;
use crate::log::*; use crate::log::*;
use crate::types::*; use crate::types::*;
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::ChaCha20;
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
use ed25519_dalek::*; use ed25519_dalek::*;
use futures::channel::mpsc; use futures::channel::mpsc;
@ -161,6 +161,12 @@ pub fn generate_keypair() -> (PrivKey, PubKey) {
(priv_key, pub_key) (priv_key, pub_key)
} }
pub fn encrypt_in_place(plaintext: &mut Vec<u8>, key: [u8; 32], nonce: [u8; 12]) {
let mut cipher = ChaCha20::new(&key.into(), &nonce.into());
let mut content_dec_slice = plaintext.as_mut_slice();
cipher.apply_keystream(&mut content_dec_slice);
}
/// returns the NextGraph Timestamp of now. /// returns the NextGraph Timestamp of now.
pub fn now_timestamp() -> Timestamp { pub fn now_timestamp() -> Timestamp {
((SystemTime::now() ((SystemTime::now()

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

@ -1,11 +1,17 @@
[package] [package]
name = "ng-sdk-js" name = "ng-sdk-js"
version = "0.1.0" # version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
description = "JS app sdk of NextGraph" description = "JS app sdk of NextGraph"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" publish = false
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
[package.metadata.wasm-pack.profile.release] [package.metadata.wasm-pack.profile.release]
wasm-opt = false wasm-opt = false
@ -16,10 +22,12 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
ws_stream_wasm = "0.7" ws_stream_wasm = "0.7"
p2p-net = { path = "../p2p-net" } ng-net = { path = "../ng-net" }
p2p-repo = { path = "../p2p-repo" } ng-repo = { path = "../ng-repo" }
p2p-client-ws = { path = "../p2p-client-ws" } ng-client-ws = { path = "../ng-client-ws" }
ng-wallet = { path = "../ng-wallet" } ng-wallet = { path = "../ng-wallet" }
once_cell = "1.17.1"
nextgraph = { path = "../nextgraph" }
async-std = { version = "1.12.0", features = ["attributes","unstable"] } async-std = { version = "1.12.0", features = ["attributes","unstable"] }
futures = "0.3.24" futures = "0.3.24"
pharos = "0.5" pharos = "0.5"

@ -6,7 +6,7 @@ JS/WASM crate containing the SDK of NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. > NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
> >
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with end-to-end encryption, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. > This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
> >
> More info here [https://nextgraph.org](https://nextgraph.org) > More info here [https://nextgraph.org](https://nextgraph.org)

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,25 @@
Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

@ -1,7 +1,7 @@
{ {
"name": "ng-app-node", "name": "ng-app-node",
"version": "0.1.0", "version": "0.1.0",
"description": "NodeJS app for NextGraph", "description": "NodeJS app example for NextGraph",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "node index.js" "start": "node index.js"

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,25 @@
Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

@ -1,13 +1,13 @@
# ng-app-react # ng-app-react
React app client of NextGraph React example app for NextGraph
## NextGraph ## NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. > NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
> >
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with end-to-end encryption, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. > This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
> >
> More info here [https://nextgraph.org](https://nextgraph.org) > More info here [https://nextgraph.org](https://nextgraph.org)
## For contributors ## For contributors
@ -30,9 +30,10 @@ Open this URL in browser : [http://localhost:8080](http://localhost:8080)
## License ## License
Licensed under either of Licensed under either of
* Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
at your option. - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT` `SPDX-License-Identifier: Apache-2.0 OR MIT`

@ -1,7 +1,7 @@
{ {
"name": "ng-app-react", "name": "ng-app-react",
"version": "0.1.0", "version": "0.1.0",
"description": "React based web application of NextGraph", "description": "React based example web application for NextGraph",
"main": "src/index.jsx", "main": "src/index.jsx",
"scripts": { "scripts": {
"dev": "webpack server" "dev": "webpack server"

@ -6,8 +6,8 @@ Web app client of NextGraph
> NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. > NextGraph brings about the convergence between P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
> >
> This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with end-to-end encryption, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. > This open source ecosystem provides solutions for end-users and software developers alike, wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
> >
> More info here [https://nextgraph.org](https://nextgraph.org) > More info here [https://nextgraph.org](https://nextgraph.org)
## For contributors ## For contributors
@ -30,9 +30,10 @@ Open this URL in browser : [http://localhost:8080](http://localhost:8080)
## License ## License
Licensed under either of Licensed under either of
* Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
at your option. - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT` `SPDX-License-Identifier: Apache-2.0 OR MIT`

@ -1,7 +1,7 @@
{ {
"name": "ng-app-web", "name": "ng-app-web",
"version": "0.1.0", "version": "0.1.0",
"description": "Web app client of NextGraph", "description": "Web-based example application for NextGraph",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",

@ -142,4 +142,4 @@ module.exports.local_save = function(key,value) {
module.exports.local_get = function(key) { module.exports.local_get = function(key) {
} }

@ -10,30 +10,32 @@
*/ */
use async_std::task; use async_std::task;
use once_cell::sync::Lazy;
// #[cfg(target_arch = "wasm32")] // #[cfg(target_arch = "wasm32")]
// use js_sys::Reflect; // use js_sys::Reflect;
use async_std::stream::StreamExt; use async_std::stream::StreamExt;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use js_sys::Uint8Array; use js_sys::Uint8Array;
use ng_wallet::types::*;
use ng_wallet::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket; use ng_client_ws::remote_ws_wasm::ConnectionWebSocket;
use p2p_net::broker::*; use ng_net::broker::*;
use p2p_net::connection::{ClientConfig, StartConfig}; use ng_net::connection::{ClientConfig, StartConfig};
use p2p_net::types::{ use ng_net::types::{
BootstrapContent, BootstrapContentV0, ClientId, ClientInfo, ClientInfoV0, ClientType, BootstrapContent, BootstrapContentV0, ClientId, ClientInfo, ClientInfoV0, ClientType,
CreateAccountBSP, DirectPeerId, IP, CreateAccountBSP, IP,
}; };
use p2p_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend, Sender}; use ng_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend, Sender};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use p2p_net::utils::{retrieve_local_bootstrap, retrieve_local_url}; use ng_net::utils::{retrieve_local_bootstrap, retrieve_local_url};
use ng_wallet::types::*;
use ng_wallet::*;
use p2p_net::WS_PORT; use nextgraph::local_broker::*;
use p2p_repo::errors::NgError; use ng_net::WS_PORT;
use p2p_repo::log::*; use ng_repo::errors::NgError;
use p2p_repo::types::*; use ng_repo::log::*;
use p2p_repo::utils::generate_keypair; use ng_repo::types::*;
use ng_repo::utils::generate_keypair;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use std::collections::HashMap; use std::collections::HashMap;
@ -114,16 +116,16 @@ pub fn wallet_gen_shuffle_for_pin() -> Vec<u8> {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub fn wallet_open_wallet_with_pazzle( pub fn wallet_open_with_pazzle(
js_wallet: JsValue, js_wallet: JsValue,
pazzle: Vec<u8>, pazzle: Vec<u8>,
js_pin: JsValue, js_pin: JsValue,
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let wallet = serde_wasm_bindgen::from_value::<Wallet>(js_wallet) let encrypted_wallet = serde_wasm_bindgen::from_value::<Wallet>(js_wallet)
.map_err(|_| "Deserialization error of wallet")?; .map_err(|_| "Deserialization error of wallet")?;
let mut pin = serde_wasm_bindgen::from_value::<[u8; 4]>(js_pin) let mut pin = serde_wasm_bindgen::from_value::<[u8; 4]>(js_pin)
.map_err(|_| "Deserialization error of pin")?; .map_err(|_| "Deserialization error of pin")?;
let res = open_wallet_with_pazzle(wallet, pazzle, pin); let res = nextgraph::local_broker::wallet_open_with_pazzle(encrypted_wallet, pazzle, pin);
match res { match res {
Ok(r) => Ok(r Ok(r) => Ok(r
.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true)) .serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
@ -146,85 +148,182 @@ pub fn wallet_update(js_wallet_id: JsValue, js_operations: JsValue) -> Result<Js
// } // }
} }
fn get_local_wallets_v0() -> Result<HashMap<String, LocalWalletStorageV0>, ()> {
let wallets_string = local_get("ng_wallets".to_string());
if wallets_string.is_some() {
let map_ser = base64_url::decode(&wallets_string.unwrap()).unwrap();
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser).unwrap();
let LocalWalletStorage::V0(v0) = wallets;
Ok(v0)
} else {
Err(())
}
}
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub fn get_wallets_from_localstorage() -> JsValue { pub async fn get_wallets() -> Result<JsValue, JsValue> {
let res = get_local_wallets_v0(); init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
if res.is_ok() {
return serde_wasm_bindgen::to_value(&res.unwrap()).unwrap();
}
JsValue::UNDEFINED
}
fn save_new_session( let res = get_all_wallets().await.map_err(|e| {
wallet_name: &String, log_err!("{}", e.to_string());
wallet_id: PubKey, });
user: PubKey,
) -> Result<SessionWalletStorageV0, String> {
let res = create_new_session(wallet_id, user);
if res.is_ok() { if res.is_ok() {
let sws = res.unwrap(); return Ok(serde_wasm_bindgen::to_value(&res.unwrap()).unwrap());
let encoded = base64_url::encode(&sws.1); }
let r = session_save(format!("ng_wallet@{}", wallet_name), encoded); Ok(JsValue::UNDEFINED)
if r.is_some() { }
return Err(r.unwrap());
// fn save_new_session(
// wallet_name: &String,
// wallet_id: PubKey,
// user: PubKey,
// ) -> Result<SessionWalletStorageV0, String> {
// let res = create_new_session(wallet_id, user);
// if res.is_ok() {
// let sws = res.unwrap();
// let encoded = base64_url::encode(&sws.1);
// let r = session_save(format!("ng_wallet@{}", wallet_name), encoded);
// if r.is_some() {
// return Err(r.unwrap());
// }
// Ok(sws.0)
// } else {
// Err(res.unwrap_err().to_string())
// }
// }
fn take_some_peer_last_seq_numbers(peer_id: PubKey, qty: u16) -> Result<u64, NgError> {
let res = session_get(format!("ng_peer_last_seq@{}", peer_id));
let val = match res {
Some(old_str) => {
let decoded = base64_url::decode(&old_str).map_err(|_| NgError::SerializationError)?;
match serde_bare::from_slice(&decoded)? {
SessionPeerLastSeq::V0(old_val) => old_val,
_ => unimplemented!(),
}
} }
Ok(sws.0) None => 0,
} else { };
Err(res.unwrap_err().to_string()) let new_val = val + qty as u64;
let spls = SessionPeerLastSeq::V0(new_val);
let ser = serde_bare::to_vec(&spls)?;
//saving the new val
let encoded = base64_url::encode(&ser);
let r = session_save(format!("ng_peer_last_seq@{}", peer_id), encoded);
if r.is_some() {
return Err(NgError::SerializationError);
} }
Ok(val)
}
// #[cfg(target_arch = "wasm32")]
// #[wasm_bindgen]
// async fn init_local_broker_persistent() -> Result<(), String> {
// init_local_broker_(false, None).await
// }
// #[cfg(target_arch = "wasm32")]
// #[wasm_bindgen]
// async fn init_local_broker_with_path(path: String) -> Result<(), String> {
// init_local_broker_(false, Some(path)).await
// }
// #[cfg(target_arch = "wasm32")]
// #[wasm_bindgen]
// async fn init_local_broker_in_memory() -> Result<(), String> {
// init_local_broker_(true, None).await
// }
async fn init_local_broker_(in_memory: bool, path: Option<String>) -> Result<(), String> {
// INIT_BROKER
// .get_or_init(async {
// BROKER
// .write()
// .await
// .register_last_seq_function(Box::new(take_some_peer_last_seq_numbers));
// true
// })
// .await;
Ok(())
}
async fn init_local_broker_internal() -> Result<(), String> {
// INIT_BROKER
// .get_or_init(async {
// BROKER
// .write()
// .await
// .register_last_seq_function(Box::new(take_some_peer_last_seq_numbers));
// true
// })
// .await;
Ok(())
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub fn get_local_session(id: String, key_js: JsValue, user_js: JsValue) -> JsValue { pub async fn session_start(wallet_name: String, user_js: JsValue) -> Result<JsValue, String> {
let res = session_get(format!("ng_wallet@{}", id)); let user_id = serde_wasm_bindgen::from_value::<PubKey>(user_js)
if res.is_some() { .map_err(|_| "Deserialization error of user_id")?;
log_debug!("RESUMING SESSION");
let key = serde_wasm_bindgen::from_value::<PrivKey>(key_js).unwrap();
let decoded = base64_url::decode(&res.unwrap()).unwrap();
let v0 = dec_session(key, &decoded);
if v0.is_ok() {
return serde_wasm_bindgen::to_value(&v0.unwrap()).unwrap();
}
}
// create a new session let config = SessionConfig::V0(SessionConfigV0 {
let user = serde_wasm_bindgen::from_value::<PubKey>(user_js).unwrap(); user_id,
let wallet_id: PubKey = id.as_str().try_into().unwrap(); wallet_name,
let session_v0 = save_new_session(&id, wallet_id, user); });
if session_v0.is_err() { let res = nextgraph::local_broker::session_start(config)
return JsValue::UNDEFINED; .await
} .map_err(|e: NgError| e.to_string())?;
return serde_wasm_bindgen::to_value(&session_v0.unwrap()).unwrap();
} Ok(serde_wasm_bindgen::to_value(&res).unwrap())
}
fn save_wallet_locally(res: &CreateWalletResultV0) -> Result<SessionWalletStorageV0, String> {
let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user)?; // fn save_wallet_locally(res: &CreateWalletResultV0) -> Result<SessionWalletStorageV0, String> {
let mut wallets: HashMap<String, LocalWalletStorageV0> = // let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user)?;
get_local_wallets_v0().unwrap_or(HashMap::new()); // let mut wallets: HashMap<String, LocalWalletStorageV0> =
// TODO: check that the wallet is not already present in localStorage // get_all_wallets().unwrap_or(HashMap::new());
let lws: LocalWalletStorageV0 = res.into(); // // TODO: check that the wallet is not already present in localStorage
wallets.insert(res.wallet_name.clone(), lws); // let lws: LocalWalletStorageV0 = res.into();
let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap(); // wallets.insert(res.wallet_name.clone(), lws);
let encoded = base64_url::encode(&lws_ser); // let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
let r = local_save("ng_wallets".to_string(), encoded); // let encoded = base64_url::encode(&lws_ser);
if r.is_some() { // let r = local_save("ng_wallets".to_string(), encoded);
return Err(r.unwrap()); // if r.is_some() {
// return Err(r.unwrap());
// }
// Ok(sws)
// }
// #[cfg(target_arch = "wasm32")]
// #[wasm_bindgen]
// pub async fn get_wallet_client(
// wallet_id: String,
// wallet_privkey_js: JsValue,
// ) -> Result<JsValue, String> {
// let mut wallet_privkey = serde_wasm_bindgen::from_value::<PrivKey>(wallet_privkey_js)
// .map_err(|_| "Deserialization error of wallet_privkey")?;
// match get_all_wallets().await {
// Ok(wallets) => match wallets.get(&wallet_id) {
// Some(wallet) => {
// let res = wallet
// .to_client_v0(wallet_privkey)
// .map_err(|e| e.to_string())?;
// Ok(serde_wasm_bindgen::to_value(&res).unwrap())
// }
// None => Err("Cannot find this wallet locally".to_string()),
// },
// Err(_) => Err("Cannot find this wallet locally".to_string()),
// }
// }
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn reload_wallets() -> Result<(), String> {
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
nextgraph::local_broker::reload_wallets()
.await
.map_err(|e: NgError| e.to_string())
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn add_in_memory_wallet(lws_js: JsValue) -> Result<(), String> {
let lws = serde_wasm_bindgen::from_value::<LocalWalletStorageV0>(lws_js)
.map_err(|_| "Deserialization error of lws")?;
if !lws.in_memory {
return Err("This is not an in memory wallet".to_string());
} }
Ok(sws) nextgraph::local_broker::wallet_add(lws)
.await
.map_err(|e: NgError| e.to_string())
} }
#[cfg(not(wasmpack_target = "nodejs"))] #[cfg(not(wasmpack_target = "nodejs"))]
@ -245,92 +344,146 @@ extern "C" {
fn local_get(key: String) -> Option<String>; fn local_get(key: String) -> Option<String>;
} }
#[cfg(target_arch = "wasm32")]
fn local_read(key: String) -> Result<String, NgError> {
local_get(key).ok_or(NgError::JsStorageReadError)
}
#[cfg(target_arch = "wasm32")]
fn local_write(key: String, value: String) -> Result<(), NgError> {
match local_save(key, value) {
Some(err) => Err(NgError::JsStorageWriteError(err)),
None => Ok(()),
}
}
#[cfg(target_arch = "wasm32")]
fn session_read(key: String) -> Result<String, NgError> {
session_get(key).ok_or(NgError::JsStorageReadError)
}
#[cfg(target_arch = "wasm32")]
fn session_write(key: String, value: String) -> Result<(), NgError> {
match session_save(key, value) {
Some(err) => Err(NgError::JsStorageWriteError(err)),
None => Ok(()),
}
}
#[cfg(target_arch = "wasm32")]
static INIT_LOCAL_BROKER: Lazy<Box<ConfigInitFn>> = Lazy::new(|| {
Box::new(|| {
LocalBrokerConfig::JsStorage(JsStorageConfig {
local_read: Box::new(local_read),
local_write: Box::new(local_write),
session_read: Box::new(session_read),
session_write: Box::new(session_write),
})
})
});
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn wallet_create_wallet(js_params: JsValue) -> Result<JsValue, JsValue> { pub async fn wallet_create(js_params: JsValue) -> Result<JsValue, JsValue> {
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
let mut params = serde_wasm_bindgen::from_value::<CreateWalletV0>(js_params) let mut params = serde_wasm_bindgen::from_value::<CreateWalletV0>(js_params)
.map_err(|_| "Deserialization error of args")?; .map_err(|_| "Deserialization error of args")?;
params.result_with_wallet_file = true; params.result_with_wallet_file = true;
let local_save = params.local_save; let res = nextgraph::local_broker::wallet_create_v0(params).await;
let res = create_wallet_v0(params).await;
match res { match res {
Ok(r) => { Ok(r) => Ok(serde_wasm_bindgen::to_value(&r).unwrap()),
if local_save {
let session = save_wallet_locally(&r)?;
Ok(serde_wasm_bindgen::to_value(&(r, session)).unwrap())
} else {
Ok(serde_wasm_bindgen::to_value(&(r, false)).unwrap())
}
}
Err(e) => Err(e.to_string().into()), Err(e) => Err(e.to_string().into()),
} }
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn wallet_open_file(js_file: JsValue) -> Result<JsValue, String> { pub async fn wallet_download_file(wallet_name: String) -> Result<JsValue, JsValue> {
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
let res = nextgraph::local_broker::wallet_download_file(&wallet_name).await;
match res {
Ok(r) => Ok(serde_wasm_bindgen::to_value(&serde_bytes::ByteBuf::from(r)).unwrap()),
Err(e) => Err(e.to_string().into()),
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn wallet_read_file(js_file: JsValue) -> Result<JsValue, String> {
let mut file = serde_wasm_bindgen::from_value::<serde_bytes::ByteBuf>(js_file) let mut file = serde_wasm_bindgen::from_value::<serde_bytes::ByteBuf>(js_file)
.map_err(|_| "Deserialization error of file".to_string())?; .map_err(|_| "Deserialization error of file".to_string())?;
let ngf: NgFile = file let wallet = nextgraph::local_broker::wallet_read_file(file.into_vec())
.into_vec() .await
.try_into()
.map_err(|e: NgError| e.to_string())?; .map_err(|e: NgError| e.to_string())?;
if let NgFile::V0(NgFileV0::Wallet(wallet)) = ngf {
let wallets: HashMap<String, LocalWalletStorageV0> = Ok(serde_wasm_bindgen::to_value(&wallet).unwrap())
get_local_wallets_v0().unwrap_or(HashMap::new()); }
// check that the wallet is not already present in localStorage
let wallet_name = wallet.name(); #[cfg(target_arch = "wasm32")]
if wallets.get(&wallet_name).is_none() { #[wasm_bindgen]
Ok(serde_wasm_bindgen::to_value(&wallet).unwrap()) pub async fn wallet_was_opened(
} else { js_opened_wallet: JsValue, //SensitiveWallet
Err("Wallet already present on this device".to_string()) in_memory: bool,
} ) -> Result<JsValue, String> {
} else { let mut opened_wallet = serde_wasm_bindgen::from_value::<SensitiveWallet>(js_opened_wallet)
Err("File does not contain a wallet".to_string()) .map_err(|_| "Deserialization error of SensitiveWallet".to_string())?;
}
let client = nextgraph::local_broker::wallet_was_opened(opened_wallet)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(serde_wasm_bindgen::to_value(&client).unwrap())
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn wallet_import( pub async fn wallet_import(
js_previous_wallet: JsValue, //Wallet, js_encrypted_wallet: JsValue, //Wallet,
js_opened_wallet: JsValue, //EncryptedWallet js_opened_wallet: JsValue, //SensitiveWallet
in_memory: bool,
) -> Result<JsValue, String> { ) -> Result<JsValue, String> {
let previous_wallet = serde_wasm_bindgen::from_value::<Wallet>(js_previous_wallet) let encrypted_wallet = serde_wasm_bindgen::from_value::<Wallet>(js_encrypted_wallet)
.map_err(|_| "Deserialization error of Wallet".to_string())?; .map_err(|_| "Deserialization error of Wallet".to_string())?;
let mut opened_wallet = serde_wasm_bindgen::from_value::<EncryptedWallet>(js_opened_wallet) let mut opened_wallet = serde_wasm_bindgen::from_value::<SensitiveWallet>(js_opened_wallet)
.map_err(|_| "Deserialization error of EncryptedWalletV0".to_string())?; .map_err(|_| "Deserialization error of SensitiveWallet".to_string())?;
let EncryptedWallet::V0(mut opened_wallet_v0) = opened_wallet; let client = nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory)
let mut wallets: HashMap<String, LocalWalletStorageV0> = .await
get_local_wallets_v0().unwrap_or(HashMap::new()); .map_err(|e: NgError| e.to_string())?;
// check that the wallet is not already present in localStorage
let wallet_name = opened_wallet_v0.wallet_id.clone(); Ok(serde_wasm_bindgen::to_value(&client).unwrap())
if wallets.get(&wallet_name).is_none() {
let session = save_new_session( // let SensitiveWallet::V0(mut opened_wallet_v0) = opened_wallet;
&wallet_name, // let mut wallets: HashMap<String, LocalWalletStorageV0> =
opened_wallet_v0.wallet_privkey.to_pub(), // get_all_wallets().unwrap_or(HashMap::new());
opened_wallet_v0.personal_site, // // check that the wallet is not already present in localStorage
) // let wallet_name = opened_wallet_v0.wallet_id.clone();
.map_err(|e| format!("Cannot create new session: {e}"))?; // if wallets.get(&wallet_name).is_none() {
let (wallet, client_id, client) = opened_wallet_v0 // let session = save_new_session(
.import(previous_wallet, session) // &wallet_name,
.map_err(|e| e.to_string())?; // opened_wallet_v0.wallet_privkey.to_pub(),
let lws = LocalWalletStorageV0::new(wallet, &client); // opened_wallet_v0.personal_site,
// )
wallets.insert(wallet_name, lws); // .map_err(|e| format!("Cannot create new session: {e}"))?;
// let (lws, client) = opened_wallet_v0
let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap(); // .import(previous_wallet, session)
let encoded = base64_url::encode(&lws_ser); // .map_err(|e| e.to_string())?;
let r = local_save("ng_wallets".to_string(), encoded); // //let lws = LocalWalletStorageV0::new(wallet, &client);
if r.is_some() {
return Err(r.unwrap()); // wallets.insert(wallet_name, lws);
}
Ok(serde_wasm_bindgen::to_value(&(client_id, client)).unwrap()) // let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
} else { // let encoded = base64_url::encode(&lws_ser);
Err("Wallet already present on this device".to_string()) // let r = local_save("ng_wallets".to_string(), encoded);
} // if r.is_some() {
// return Err(r.unwrap());
// }
// Ok(serde_wasm_bindgen::to_value(&client).unwrap())
// } else {
// Err("Wallet already present on this device".to_string())
// }
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -445,7 +598,8 @@ pub fn client_info() -> JsValue {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn test() { pub async fn test() {
log_debug!("test is {}", BROKER.read().await.test()); init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
//log_debug!("test is {}", BROKER.read().await.test());
let client_info = client_info(); let client_info = client_info();
log_debug!("{:?}", client_info); log_debug!("{:?}", client_info);
} }
@ -458,12 +612,7 @@ pub async fn doc_get_file_from_store_with_object_ref(
) -> Result<JsValue, JsValue> { ) -> Result<JsValue, JsValue> {
let obj_ref = serde_wasm_bindgen::from_value::<ObjectRef>(obj_ref_js).unwrap(); let obj_ref = serde_wasm_bindgen::from_value::<ObjectRef>(obj_ref_js).unwrap();
log_debug!( log_debug!("doc_get_file {} {:?}", nuri, obj_ref.id,);
"doc_get_file {} {:?} {}",
nuri,
obj_ref.id,
BROKER.read().await.test()
);
// let vec: Vec<u8> = vec![2; 10]; // let vec: Vec<u8> = vec![2; 10];
// let view = unsafe { Uint8Array::view(&vec) }; // let view = unsafe { Uint8Array::view(&vec) };
@ -596,7 +745,7 @@ pub async fn start() {
let server_key: PubKey = "X0nh-gOTGKSx0yL0LYJviOWRNacyqIzjQW_LKdK6opU".try_into()?; let server_key: PubKey = "X0nh-gOTGKSx0yL0LYJviOWRNacyqIzjQW_LKdK6opU".try_into()?;
log_debug!("server_key:{}", server_key); log_debug!("server_key:{}", server_key);
//let keys = p2p_net::utils::gen_dh_keys(); //let keys = ng_net::utils::gen_dh_keys();
//let pub_key = PubKey::Ed25519PubKey(keys.1); //let pub_key = PubKey::Ed25519PubKey(keys.1);
let keys = generate_keypair(); let keys = generate_keypair();
let x_from_ed = keys.1.to_dh_from_ed(); let x_from_ed = keys.1.to_dh_from_ed();
@ -617,9 +766,9 @@ pub async fn start() {
server_key, server_key,
StartConfig::Client(ClientConfig { StartConfig::Client(ClientConfig {
url: format!("ws://127.0.0.1:{}", WS_PORT), url: format!("ws://127.0.0.1:{}", WS_PORT),
user, name: None,
user_priv, user_priv,
client, client_priv,
info: ClientInfo::V0(client_info_()), info: ClientInfo::V0(client_info_()),
registration: None, registration: None,
}), }),
@ -660,28 +809,45 @@ pub async fn start() {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn broker_disconnect() { pub async fn session_stop(user_id_js: JsValue) -> Result<(), String> {
Broker::close_all_connections().await; let user_id = serde_wasm_bindgen::from_value::<UserId>(user_id_js)
.map_err(|_| "serde error on user_id")?;
nextgraph::local_broker::session_stop(user_id)
.await
.map_err(|e: NgError| e.to_string())
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn broker_connect( pub async fn user_disconnect(user_id_js: JsValue) -> Result<(), String> {
client_pub_key_js: JsValue, let user_id = serde_wasm_bindgen::from_value::<UserId>(user_id_js)
.map_err(|_| "serde error on user_id")?;
nextgraph::local_broker::user_disconnect(user_id)
.await
.map_err(|e: NgError| e.to_string())
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn wallet_close(wallet_name: String) -> Result<(), String> {
nextgraph::local_broker::wallet_close(wallet_name)
.await
.map_err(|e: NgError| e.to_string())
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn user_connect(
client_info_js: JsValue, client_info_js: JsValue,
session_js: JsValue, user_id_js: JsValue,
opened_wallet_js: JsValue,
location: Option<String>, location: Option<String>,
) -> Result<JsValue, String> { ) -> Result<JsValue, String> {
let client = serde_wasm_bindgen::from_value::<PubKey>(client_pub_key_js)
.map_err(|_| "serde error on client")?;
let info = serde_wasm_bindgen::from_value::<ClientInfo>(client_info_js) let info = serde_wasm_bindgen::from_value::<ClientInfo>(client_info_js)
.map_err(|_| "serde error on info")?; .map_err(|_| "serde error on info")?;
let session = let user_id = serde_wasm_bindgen::from_value::<UserId>(user_id_js)
serde_wasm_bindgen::from_value::<HashMap<String, SessionPeerStorageV0>>(session_js) .map_err(|_| "serde error on user_id")?;
.map_err(|_| "serde error on session")?;
let opened_wallet = serde_wasm_bindgen::from_value::<EncryptedWallet>(opened_wallet_js)
.map_err(|_| "serde error on opened_wallet")?;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct ConnectionInfo { struct ConnectionInfo {
@ -694,15 +860,9 @@ pub async fn broker_connect(
let mut opened_connections: HashMap<String, ConnectionInfo> = HashMap::new(); let mut opened_connections: HashMap<String, ConnectionInfo> = HashMap::new();
let results = connect_wallet( let results = nextgraph::local_broker::user_connect(info, user_id, location)
client, .await
info, .map_err(|e: NgError| e.to_string())?;
session,
opened_wallet,
location,
Box::new(ConnectionWebSocket {}),
)
.await?;
log_debug!("{:?}", results); log_debug!("{:?}", results);
@ -720,7 +880,7 @@ pub async fn broker_connect(
); );
} }
BROKER.read().await.print_status(); //BROKER.read().await.print_status();
Ok(opened_connections Ok(opened_connections
.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true)) .serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))

@ -0,0 +1,26 @@
[package]
name = "ng-stores-lmdb"
# version = "0.1.0"
description = "Stores based on LMDB for NextGraph"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
documentation.workspace = true
rust-version.workspace = true
publish = false
[dependencies]
ng-repo = { path = "../ng-repo" }
serde = { version = "1.0.142", features = ["derive"] }
serde_bare = "0.5.0"
tempfile = "3"
hex = "0.4.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rkv]
git = "https://git.nextgraph.org/NextGraph/rkv.git"
rev = "c746abb443b7bb4541ebbef2b71e8d0f9eb39f6a"
features = [ "lmdb" ]

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save