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

master
Niko PLP 1 month 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*
.DS_Store
node_modules
/p2p-repo/tests/*.ng
*/tests/*.ng
.vscode/settings.json

337
Cargo.lock generated

@ -343,6 +343,12 @@ dependencies = [
"url",
]
[[package]]
name = "async-once-cell"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb"
[[package]]
name = "async-oneshot"
version = "0.5.0"
@ -3108,16 +3114,37 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ng-app"
version = "0.1.0"
dependencies = [
"async-std",
"async-tungstenite",
"nextgraph",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-wallet",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"serde",
"serde_json",
"tauri",
@ -3125,6 +3152,115 @@ dependencies = [
"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]]
name = "ng-sdk-js"
version = "0.1.0"
@ -3135,10 +3271,12 @@ dependencies = [
"getrandom 0.1.16",
"gloo-timers",
"js-sys",
"nextgraph",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-wallet",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"once_cell",
"pharos",
"rand 0.7.3",
"serde",
@ -3152,6 +3290,31 @@ dependencies = [
"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]]
name = "ng-wallet"
version = "0.1.0"
@ -3160,13 +3323,14 @@ dependencies = [
"argon2",
"async-std",
"base64-url",
"blake3",
"chacha20poly1305",
"crypto_box",
"getrandom 0.1.16",
"image",
"lazy_static",
"p2p-net",
"p2p-repo",
"ng-net",
"ng-repo",
"rand 0.7.3",
"safe-transmute",
"serde",
@ -3187,10 +3351,10 @@ dependencies = [
"duration-str",
"env_logger",
"log",
"ng-client-ws",
"ng-net",
"ng-repo",
"ng-wallet",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"rust-embed",
"serde",
"serde-big-array",
@ -3218,9 +3382,9 @@ dependencies = [
"futures",
"getrandom 0.2.10",
"log",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"ng-client-ws",
"ng-net",
"ng-repo",
"rand 0.7.3",
"serde",
"serde_bare",
@ -3241,9 +3405,9 @@ dependencies = [
"env_logger",
"lazy_static",
"log",
"p2p-broker",
"p2p-net",
"p2p-repo",
"ng-broker",
"ng-net",
"ng-repo",
"regex",
"serde",
"serde_bare",
@ -3260,9 +3424,10 @@ dependencies = [
"bytes",
"env_logger",
"log",
"ng-net",
"ng-repo",
"ng-stores-rocksdb",
"ng-wallet",
"p2p-net",
"p2p-repo",
"rust-embed",
"serde",
"serde-big-array",
@ -3270,7 +3435,6 @@ dependencies = [
"serde_bytes",
"serde_json",
"slice_as_array",
"stores-rocksdb",
"tokio",
"warp",
"warp-embed",
@ -3530,127 +3694,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "packed_simd_2"
version = "0.3.8"
@ -4897,18 +4940,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stores-rocksdb"
version = "0.1.0"
dependencies = [
"hex",
"p2p-repo",
"rocksdb",
"serde",
"serde_bare",
"tempfile",
]
[[package]]
name = "string_cache"
version = "0.8.7"

@ -1,20 +1,37 @@
[workspace]
members = [
"p2p-repo",
"p2p-net",
"p2p-broker",
"p2p-client-ws",
"p2p-verifier",
"stores-rocksdb",
"nextgraph",
"ngcli",
"ngd",
"ng-repo",
"ng-net",
"ng-broker",
"ng-client-ws",
"ng-verifier",
"ng-wallet",
"ng-stores-rocksdb",
"ngone",
"ngaccount",
"ng-sdk-js",
"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]
lto = true

@ -1,5 +1,14 @@
<p align="center">
<img src=".github/header.png" alt="nextgraph-header" />
</p>
# 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
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.
>
> 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)
@ -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)
[![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
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 :
- p2p-repo : NextGraph repositories common library
- p2p-net : P2P network common library
- p2p-broker : the broker code (as server and core node)
- p2p-client-ws : the client connecting to a broker with WebSocket, used by the apps and verifier
- p2p-verifier : the code of the verifier
- stores-rocksdb : RocksDB backed stores. see [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb)
- ngcli : CLI tool to manipulate the repos and administrate the server
- ngd : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application
- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server
- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- ng-repo : Repositories common library
- ng-net : Network common library
- ng-broker : Core and Server Broker library
- ng-client-ws : Websocket client library
- 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-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.
- [ngone](ngone/README.md) : server for nextgraph.one (helps user bootstrap into the right app)
- [ngaccount](ngaccount/README.md) : server for nextgraph's Broker Service Provider account manager.
- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
### Run
@ -87,7 +100,7 @@ cargo test --all --verbose -- --show-output --nocapture
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:
@ -106,7 +119,7 @@ wasm-pack test --chrome --headless
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
@ -155,7 +168,7 @@ For building the apps, see this [documentation](ng-app/README.md).
#### OpenBSD
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]
@ -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.
[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]
name = "ng-app"
version = "0.1.0"
# version = "0.1.0"
description = "NextGraph App"
authors = ["Niko PLP <niko@nextgraph.org>"]
license = "MIT/Apache-2.0"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
edition = "2021"
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
[lib]
name = "nativelib"
crate-type = ["staticlib", "cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2.0.0-alpha.8", features = [] }
# 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 = [] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
p2p-repo = { path = "../../p2p-repo" }
p2p-net = { path = "../../p2p-net" }
p2p-client-ws = { path = "../../p2p-client-ws" }
ng-repo = { path = "../../ng-repo" }
ng-net = { path = "../../ng-net" }
ng-client-ws = { path = "../../ng-client-ws" }
ng-wallet = { path = "../../ng-wallet" }
nextgraph = { path = "../../nextgraph" }
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 = "2.0.0-alpha.1"

@ -7,18 +7,20 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
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::*;
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 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::utils::config::WindowConfig;
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>;
// 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")]
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
.path()
.resolve("storage", BaseDirectory::AppLocalData)
@ -51,227 +65,270 @@ async fn test(app: tauri::AppHandle) -> Result<(), ()> {
#[tauri::command(rename_all = "snake_case")]
async fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> Result<ShuffledPazzle, ()> {
log_debug!(
"wallet_gen_shuffle_for_pazzle_opening from rust {}",
pazzle_length
);
// log_debug!(
// "wallet_gen_shuffle_for_pazzle_opening from rust {}",
// pazzle_length
// );
Ok(gen_shuffle_for_pazzle_opening(pazzle_length))
}
#[tauri::command(rename_all = "snake_case")]
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())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_open_wallet_with_pazzle(
async fn wallet_open_with_pazzle(
wallet: Wallet,
pazzle: Vec<u8>,
pin: [u8; 4],
) -> Result<EncryptedWallet, String> {
log_debug!("wallet_open_wallet_with_pazzle from rust {:?}", pazzle);
open_wallet_with_pazzle(wallet, pazzle, pin).map_err(|e| e.to_string())
app: tauri::AppHandle,
) -> Result<SensitiveWallet, 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")]
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,
app: tauri::AppHandle,
) -> Result<(CreateWalletResultV0, Option<SessionWalletStorageV0>), String> {
//log_debug!("wallet_create_wallet from rust {:?}", params);
) -> Result<CreateWalletResultV0, String> {
//log_debug!("wallet_create from rust {:?}", params);
params.result_with_wallet_file = !params.local_save;
let local_save = params.local_save;
let res = create_wallet_v0(params).await.map_err(|e| e.to_string());
if res.is_ok() {
let mut cwr = res.unwrap();
if local_save {
// save in local store
let session = save_wallet_locally(&cwr, app).await;
if session.is_err() {
return Err("Cannot save wallet locally".to_string());
}
return Ok((cwr, Some(session.unwrap())));
} else {
// save wallet file to Downloads folder
let path = app
.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));
}
let mut cwr = nextgraph::local_broker::wallet_create_v0(params)
.await
.map_err(|e| e.to_string())?;
if !local_save {
// save wallet file to Downloads folder
let path = app
.path()
.resolve(
format!("wallet-{}.ngw", cwr.wallet_name),
BaseDirectory::Download,
)
.unwrap();
let _r = write(path, &cwr.wallet_file);
cwr.wallet_file = vec![];
}
Err(res.unwrap_err())
Ok(cwr)
}
async fn save_wallet_locally(
res: &CreateWalletResultV0,
app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> {
let path = app
.path()
.resolve("wallets", BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_wallets_from_localstorage(app.clone())
.await
.unwrap_or(Some(HashMap::new()))
.unwrap_or(HashMap::new());
// check that the wallet is not already present in localStorage
if wallets.get(&res.wallet_name).is_none() {
let lws: LocalWalletStorageV0 = res.into();
wallets.insert(res.wallet_name.clone(), lws);
let lws_ser = LocalWalletStorage::v0_to_vec(wallets);
let r = write(path.clone(), &lws_ser);
if r.is_err() {
log_debug!("write {:?} {}", path, r.unwrap_err());
return Err(());
// // TODO: use https://lib.rs/crates/keyring instead of AppLocalData
// async fn save_wallet_locally(
// res: &CreateWalletResultV0,
// app: tauri::AppHandle,
// ) -> Result<SessionWalletStorageV0, ()> {
// let path = app
// .path()
// .resolve("wallets", BaseDirectory::AppLocalData)
// .map_err(|_| ())?;
// let mut wallets: HashMap<String, LocalWalletStorageV0> =
// get_all_wallets().unwrap_or(HashMap::new());
// // check that the wallet is not already present in localStorage
// if wallets.get(&res.wallet_name).is_none() {
// let lws: LocalWalletStorageV0 = res.into();
// wallets.insert(res.wallet_name.clone(), lws);
// let lws_ser = LocalWalletStorage::v0_to_vec(&wallets);
// let r = write(path.clone(), &lws_ser);
// if r.is_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)?;
Ok(sws)
} else {
Err(())
}
Err(_) => (
File::create(path).map_err(|_| NgError::SerializationError)?,
0,
),
};
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")]
async fn wallet_open_file(file: Vec<u8>, app: tauri::AppHandle) -> Result<Wallet, String> {
let ngf: NgFile = file.try_into().map_err(|e: NgError| e.to_string())?;
if let NgFile::V0(NgFileV0::Wallet(wallet)) = ngf {
let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_wallets_from_localstorage(app.clone())
.await
.unwrap_or(Some(HashMap::new()))
.unwrap_or(HashMap::new());
// check that the wallet is not already present in localStorage
let wallet_name = wallet.name();
if wallets.get(&wallet_name).is_none() {
Ok(wallet)
} else {
Err("Wallet already present on this device".to_string())
}
} else {
Err("File does not contain a wallet".to_string())
}
async fn wallet_read_file(file: Vec<u8>, app: tauri::AppHandle) -> Result<Wallet, String> {
nextgraph::local_broker::wallet_read_file(file)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_was_opened(
opened_wallet: SensitiveWallet,
app: tauri::AppHandle,
) -> Result<ClientV0, String> {
nextgraph::local_broker::wallet_was_opened(opened_wallet)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_import(
previous_wallet: Wallet,
opened_wallet: EncryptedWallet,
encrypted_wallet: Wallet,
opened_wallet: SensitiveWallet,
in_memory: bool,
app: tauri::AppHandle,
) -> Result<(String, ClientV0), String> {
let path = app
.path()
.resolve("wallets", BaseDirectory::AppLocalData)
.map_err(|_| "wallet directory error".to_string())?;
let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_wallets_from_localstorage(app.clone())
.await
.unwrap_or(Some(HashMap::new()))
.unwrap_or(HashMap::new());
// check that the wallet is not already present in localStorage
let EncryptedWallet::V0(mut opened_wallet_v0) = opened_wallet;
let wallet_name = opened_wallet_v0.wallet_id.clone();
if wallets.get(&wallet_name).is_none() {
let session = save_new_session(
&wallet_name,
opened_wallet_v0.wallet_privkey.to_pub(),
opened_wallet_v0.personal_site,
app,
)
.map_err(|_| "Cannot create new session".to_string())?;
let (wallet, client_id, client) = opened_wallet_v0
.import(previous_wallet, session)
.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);
let r = write(path.clone(), &lws_ser);
if r.is_err() {
log_debug!("write {:?} {}", path, r.unwrap_err());
Err("Write error".to_string())
} else {
Ok((client_id, client))
}
} else {
Err("Already present on this device".to_string())
}
) -> Result<ClientV0, String> {
nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory)
.await
.map_err(|e: NgError| e.to_string())
// let path = app
// .path()
// .resolve("wallets", BaseDirectory::AppLocalData)
// .map_err(|_| "wallet directory error".to_string())?;
// let mut wallets: HashMap<String, LocalWalletStorageV0> =
// get_all_wallets().unwrap_or(HashMap::new());
// // check that the wallet is not already present in localStorage
// let SensitiveWallet::V0(mut opened_wallet_v0) = opened_wallet;
// let wallet_name = opened_wallet_v0.wallet_id.clone();
// if wallets.get(&wallet_name).is_none() {
// let session = save_new_session(
// &wallet_name,
// opened_wallet_v0.wallet_privkey.to_pub(),
// opened_wallet_v0.personal_site,
// app,
// )
// .map_err(|_| "Cannot create new session".to_string())?;
// let lws = opened_wallet_v0
// .import(previous_wallet)
// .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);
// let r = write(path.clone(), &lws_ser);
// if r.is_err() {
// log_debug!("write {:?} {}", path, r.unwrap_err());
// Err("Write error".to_string())
// } else {
// Ok(client)
// }
// } else {
// Err("Already present on this device".to_string())
// }
}
#[tauri::command(rename_all = "snake_case")]
async fn get_wallets_from_localstorage(
async fn get_wallets(
app: tauri::AppHandle,
) -> Result<Option<HashMap<String, LocalWalletStorageV0>>, ()> {
) -> Result<Option<HashMap<String, LocalWalletStorageV0>>, String> {
let path = app
.path()
.resolve("wallets", BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let map_ser = read(path);
if map_ser.is_ok() {
let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap());
let LocalWalletStorage::V0(v0) = wallets;
return Ok(Some(v0));
.resolve("", BaseDirectory::AppLocalData)
.map_err(|_| NgError::SerializationError)
.unwrap();
init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await;
let res = get_all_wallets().await.map_err(|e| {
log_err!("get_all_wallets error {}", e.to_string());
});
if res.is_ok() {
return Ok(Some(res.unwrap()));
}
Ok(None)
}
fn save_new_session(
wallet_name: &String,
wallet_id: PubKey,
user: PubKey,
app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> {
let mut path = app
.path()
.resolve("sessions", BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let session_v0 = create_new_session(wallet_id, user);
if session_v0.is_err() {
log_debug!("create_new_session failed {}", session_v0.unwrap_err());
return Err(());
}
let sws = session_v0.unwrap();
std::fs::create_dir_all(path.clone()).unwrap();
path.push(wallet_name);
let res = write(path.clone(), &sws.1);
if res.is_err() {
log_debug!("write {:?} {}", path, res.unwrap_err());
return Err(());
}
Ok(sws.0)
}
// fn save_new_session(
// wallet_name: &String,
// wallet_id: PubKey,
// user: PubKey,
// app: tauri::AppHandle,
// ) -> Result<SessionWalletStorageV0, ()> {
// let mut path = app
// .path()
// .resolve("sessions", BaseDirectory::AppLocalData)
// .map_err(|_| ())?;
// let session_v0 = create_new_session(wallet_id, user);
// if session_v0.is_err() {
// log_debug!("create_new_session failed {}", session_v0.unwrap_err());
// return Err(());
// }
// let sws = session_v0.unwrap();
// std::fs::create_dir_all(path.clone()).unwrap();
// path.push(wallet_name);
// let res = write(path.clone(), &sws.1);
// if res.is_err() {
// log_debug!("write {:?} {}", path, res.unwrap_err());
// return Err(());
// }
// Ok(sws.0)
// }
#[tauri::command(rename_all = "snake_case")]
async fn get_local_session(
id: String,
key: PrivKey,
async fn session_start(
wallet_name: String,
user: PubKey,
app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> {
let path = app
.path()
.resolve(format!("sessions/{id}"), BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let res = read(path.clone());
if res.is_ok() {
log_debug!("RESUMING SESSION");
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)
) -> Result<SessionPeerStorageV0, String> {
let config = SessionConfig::V0(SessionConfigV0 {
user_id: user,
wallet_name,
});
nextgraph::local_broker::session_start(config)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
@ -368,6 +425,7 @@ async fn disconnections_subscribe(app: tauri::AppHandle) -> Result<(), ()> {
main_window: tauri::Window,
) -> ResultSend<()> {
while let Some(user_id) = reader.next().await {
log_debug!("DISCONNECTION FOR {user_id}");
main_window.emit("disconnections", user_id).unwrap();
}
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")]
async fn broker_disconnect() {
Broker::close_all_connections().await;
async fn session_stop(user_id: UserId) -> Result<(), String> {
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)]
@ -419,24 +493,16 @@ struct ConnectionInfo {
}
#[tauri::command(rename_all = "snake_case")]
async fn broker_connect(
client: PubKey,
async fn user_connect(
info: ClientInfo,
session: HashMap<String, SessionPeerStorageV0>,
opened_wallet: EncryptedWallet,
user_id: UserId,
location: Option<String>,
) -> Result<HashMap<String, ConnectionInfo>, String> {
let mut opened_connections: HashMap<String, ConnectionInfo> = HashMap::new();
let results = connect_wallet(
client,
info,
session,
opened_wallet,
None,
Box::new(ConnectionWebSocket {}),
)
.await?;
let results = nextgraph::local_broker::user_connect(info, user_id, None)
.await
.map_err(|e| e.to_string())?;
log_debug!("{:?}", results);
@ -452,7 +518,7 @@ async fn broker_connect(
);
}
BROKER.read().await.print_status();
//BROKER.read().await.print_status();
Ok(opened_connections)
}
@ -488,6 +554,7 @@ impl AppBuilder {
if let Some(setup) = setup {
(setup)(app)?;
}
for domain in ALLOWED_BSP_DOMAINS {
app.ipc_scope().configure_remote_access(
RemoteDomainAccessScope::new(domain)
@ -506,18 +573,22 @@ impl AppBuilder {
doc_get_file_from_store_with_object_ref,
wallet_gen_shuffle_for_pazzle_opening,
wallet_gen_shuffle_for_pin,
wallet_open_wallet_with_pazzle,
wallet_create_wallet,
wallet_open_file,
wallet_open_with_pazzle,
wallet_was_opened,
wallet_create,
wallet_read_file,
wallet_download_file,
wallet_import,
wallet_close,
encode_create_account,
get_local_session,
get_wallets_from_localstorage,
session_start,
session_stop,
get_wallets,
open_window,
decode_invitation,
disconnections_subscribe,
broker_connect,
broker_disconnect,
user_connect,
user_disconnect,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

@ -52,6 +52,11 @@
let wallet_channel;
let unsub_main_close;
// window.refresh_wallets = async () => {
// let walls = await ng.get_wallets();
// wallets.set(walls);
// };
onMount(async () => {
try {
await disconnections_subscribe();
@ -59,15 +64,27 @@
console.log("called disconnections_subscribe twice");
}
let tauri_platform = import.meta.env.TAURI_PLATFORM;
//console.log(await ng.test());
if (tauri_platform) {
//console.log(await ng.test());
let walls = await ng.get_wallets_from_localstorage();
let walls = await ng.get_wallets();
wallets.set(walls);
unsubscribe = active_wallet.subscribe((value) => {
if (value && !value.wallet) {
active_wallet.set(undefined);
push("#/wallet/login");
unsubscribe = active_wallet.subscribe(async (value) => {
if (value) {
if (value.wallet) {
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 {
// ON WEB CLIENTS
window.addEventListener("storage", async (event) => {
if (event.storageArea != localStorage) return;
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.postMessage({ cmd: "is_opened" }, location.href);
window.wallet_channel = wallet_channel;
wallet_channel.postMessage({ cmd: "startup" }, location.href);
wallet_channel.onmessage = async (event) => {
console.log(event.data.cmd, event.data);
if (!location.href.startsWith(event.origin)) return;
switch (event.data.cmd) {
case "is_opened":
if ($active_wallet && $active_wallet.wallet) {
wallet_channel.postMessage(
{ cmd: "opened", wallet: $active_wallet },
location.href
);
case "startup":
for (let saved_id of Object.keys($wallets)) {
if ($wallets[saved_id].in_memory) {
wallet_channel.postMessage(
{
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)) {
wallet_channel.postMessage(
{
@ -116,20 +154,54 @@
location.href
);
}
break;
case "opened":
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) => {
w[event.data.wallet.id] = event.data.wallet.wallet;
return w;
});
}
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":
opened_wallets.update((w) => {
delete w[event.data.walletid];
return w;
});
await ng.wallet_close(event.data.walletid);
if ($active_wallet && $active_wallet.id == event.data.walletid) {
await close_active_session();
active_wallet.set(undefined);
@ -138,9 +210,13 @@
break;
}
};
unsubscribe = active_wallet.subscribe((value) => {
unsubscribe = active_wallet.subscribe(async (value) => {
if (value) {
if (value.wallet) {
opened_wallets.update((w) => {
w[value.id] = value.wallet;
return w;
});
wallet_channel.postMessage(
{ cmd: "opened", wallet: value },
location.href
@ -151,6 +227,7 @@
location.href
);
active_wallet.set(undefined);
await ng.wallet_close(value.id);
//active_session.set(undefined);
opened_wallets.update((w) => {
delete w[value.id];
@ -171,9 +248,10 @@
</script>
<!-- <p>
{JSON.stringify(Object.keys($wallets))}
{JSON.stringify($active_wallet)}
{JSON.stringify(Object.keys($opened_wallets))}
{JSON.stringify($active_session)}
</p> -->
{!$active_session}
{JSON.stringify(Object.keys($wallets))}
{JSON.stringify($active_wallet)}
{JSON.stringify(Object.keys($opened_wallets))}
{JSON.stringify($active_session)}
</p> -->
<Router {routes} />

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

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

@ -10,13 +10,14 @@
-->
<script lang="ts">
import { Alert } from "flowbite-svelte";
import { Alert, Toggle } from "flowbite-svelte";
import { onMount, createEventDispatcher, tick } from "svelte";
import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
import { PuzzlePiece } from "svelte-heros-v2";
//import Worker from "../worker.js?worker&inline";
export let wallet;
export let for_import = false;
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
@ -81,6 +82,8 @@
let error;
let trusted = false;
function order() {
step = "order";
ordered = [];
@ -130,20 +133,30 @@
// open the wallet
try {
if (tauri_platform) {
let secret_wallet = await ng.wallet_open_wallet_with_pazzle(
let opened_wallet = await ng.wallet_open_with_pazzle(
wallet,
pazzle,
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";
dispatch("opened", {
wallet: secret_wallet,
id: secret_wallet.V0.wallet_id,
wallet: opened_wallet,
id: opened_wallet.V0.wallet_id,
trusted,
});
} else {
let worker_import = await import("../worker.js?worker&inline");
const myWorker = new worker_import.default();
//const myWorker = new Worker();
myWorker.onerror = (e) => {
console.error(e);
error = e;
@ -156,16 +169,28 @@
step = "end";
dispatch("error", { error: e });
};
myWorker.onmessage = (msg) => {
myWorker.onmessage = async (msg) => {
//console.log("Message received from worker", msg.data);
if (msg.data.loaded) {
myWorker.postMessage({ wallet, pazzle, pin_code });
//console.log("postMessage");
} 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";
dispatch("opened", {
wallet: msg.data.success,
id: msg.data.success.V0.wallet_id,
trusted,
});
} else {
console.error(msg.data.error);
@ -216,13 +241,13 @@
</script>
{#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>
<ul class="mb-8 ml-3 space-y-4 text-left list-decimal">
<li>
For each category of images, you will be presented with the 15 possible
image choices. The categories are shuffled at every login. They will not
always appear in the same order.
For each one of the 9 categories of images, you will be presented with
the 15 possible image choices. The categories are shuffled at every
login. They will not always appear in the same order.
</li>
<li>
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
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
space bar.
space bar to select each one.
</li>
<li>
Once you completed the last category, you will be presented with all the
@ -254,11 +279,11 @@
</li>
</ul>
</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}
Loading...
Loading pazzle...
<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"
fill="none"
stroke="currentColor"
@ -291,6 +316,24 @@
</button>
{/if}
</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"}
<div
class="h-screen aspect-[3/5] pazzleline"

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

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

@ -9,6 +9,8 @@
// according to those terms.
-->
<script>
// @ts-nocheck
import { Button, Alert, Dropzone, Toggle } from "flowbite-svelte";
import { link, push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
@ -21,12 +23,15 @@
Signal,
SignalSlash,
ArrowRightOnRectangle,
ArrowsRightLeft,
Cog6Tooth,
PuzzlePiece,
Key,
User,
Gift,
InformationCircle,
DocumentArrowDown,
NoSymbol,
} from "svelte-heros-v2";
import { onMount, tick } from "svelte";
import {
@ -39,6 +44,7 @@
import {
online,
close_active_wallet,
close_active_session,
active_session,
active_wallet,
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";
let top;
async function scrollToTop() {
await tick();
top.scrollIntoView();
@ -75,8 +82,40 @@
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_id = $active_wallet?.wallet?.V0.personal_site;
$: personal_site_status = $connections[personal_site];
const displayPopup = async (url, title) => {
@ -152,7 +191,88 @@
/>
<span class="ml-3">Logout</span>
</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">
<svelte:fragment slot="icon">
<Cog6Tooth
@ -201,7 +321,7 @@
} else {
$connections[personal_site].error = "Stopped";
personal_site_status.since = new Date();
await ng.broker_disconnect();
await ng.user_disconnect(personal_site_id);
}
}}
checked={personal_site_status &&

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

@ -26,6 +26,9 @@
set_active_session,
has_wallets,
} from "../store";
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let wallet;
let selected;
let step;
@ -59,9 +62,8 @@
active_wallet_unsub = active_wallet.subscribe(async (value) => {
if (value && value.wallet) {
if (!$active_session) {
let session = await ng.get_local_session(
let session = await ng.session_start(
value.id,
value.wallet.V0.wallet_privkey,
value.wallet.V0.personal_site
);
//console.log(session);
@ -91,20 +93,52 @@
async function gotWallet(event) {
if (importing) {
try {
let new_client = await ng.wallet_import(wallet, event.detail.wallet);
event.detail.wallet.V0.clients[new_client[0]] = new_client[1];
let walls = await ng.get_wallets_from_localstorage();
wallets.set(walls);
let in_memory = !event.detail.trusted;
//console.log("IMPORTING", in_memory, event.detail.wallet, wallet);
let client = await ng.wallet_import(
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) {
importing = false;
wallet = undefined;
error = e;
return;
}
} else {
let client = await ng.wallet_was_opened(event.detail.wallet);
event.detail.wallet.V0.client = client;
}
active_wallet.set(event.detail);
// wallet
// id
// { wallet,
// id }
}
function cancelLogin(event) {
importing = false;
@ -127,7 +161,7 @@
reader.onload = async (e) => {
try {
//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;
} catch (e) {
error = e;
@ -178,6 +212,7 @@
{:else if wallet}
<Login
{wallet}
bind:for_import={importing}
on:error={gotError}
on:opened={gotWallet}
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) {
active_session.set(session.users);
active_session.set(session);
};
export { writable, readonly, derived };
@ -64,12 +64,21 @@ export const close_active_wallet = async function() {
delete w.wallet;
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() {
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);
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...");
try {
let client = get(wallets)[get(active_wallet).id].client;
let info = await ng.client_info()
//console.log("Connecting with",client,info);
connections.set(await ng.broker_connect(
client,
connections.set(await ng.user_connect(
info,
get(active_session),
get(active_wallet).wallet,
get(active_session).user,
location.href
));
}catch (e) {

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

@ -7,7 +7,7 @@ onmessage = (e) => {
//console.log("Message received by worker", e.data);
(async function() {
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.pazzle,
e.data.pin_code

@ -1,17 +1,25 @@
[package]
name = "p2p-broker"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
description = "P2P Broker module of NextGraph"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
name = "ng-broker"
# version = "0.1.0"
description = "Broker library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
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]
p2p-repo = { path = "../p2p-repo" }
p2p-net = { path = "../p2p-net" }
p2p-client-ws = { path = "../p2p-client-ws" }
stores-rocksdb = { path = "../stores-rocksdb" }
ng-repo = { path = "../ng-repo", version = "0.1.0" }
ng-net = { path = "../ng-net", version = "0.1.0" }
ng-client-ws = { path = "../ng-client-ws", version = "0.1.0" }
ng-stores-rocksdb = { path = "../ng-stores-rocksdb", version = "0.1.0" }
chacha20 = "0.9.0"
serde = { version = "1.0", features = ["derive"] }
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::time::SystemTime;
use p2p_net::types::*;
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::log::*;
use p2p_repo::store::*;
use p2p_repo::types::UserId;
use ng_net::types::*;
use ng_repo::kcv_store::KCVStore;
use ng_repo::log::*;
use ng_repo::store::*;
use ng_repo::types::UserId;
use serde_bare::{from_slice, to_vec};
pub struct Account<'a> {
@ -225,11 +225,11 @@ impl<'a> Account<'a> {
#[cfg(test)]
mod test {
use p2p_repo::store::*;
use p2p_repo::types::*;
use p2p_repo::utils::*;
use ng_repo::store::*;
use ng_repo::types::*;
use ng_repo::utils::*;
use ng_stores_rocksdb::kcv_store::RocksdbKCVStore;
use std::fs;
use stores_rocksdb::kcv_store::RocksdbKCVStore;
use tempfile::Builder;
use crate::broker_store::account::Account;

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

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

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

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

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

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

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

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

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

@ -9,25 +9,31 @@
* 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::invitation::Invitation;
use crate::broker_store::wallet::Wallet;
use crate::types::*;
use p2p_net::errors::ProtocolError;
use p2p_net::server_storage::*;
use p2p_net::types::{BootstrapContentV0, InvitationCode, InvitationV0};
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::log::*;
use p2p_repo::store::StorageError;
use p2p_repo::types::{PubKey, SymKey};
use stores_rocksdb::kcv_store::RocksdbKCVStore;
use ng_net::errors::{ProtocolError, ServerError};
use ng_net::server_storage::*;
use ng_net::types::{BootstrapContentV0, InvitationCode, InvitationV0};
use ng_repo::kcv_store::KCVStore;
use ng_repo::log::*;
use ng_repo::store::StorageError;
use ng_repo::types::{PeerId, PubKey, SymKey};
use ng_stores_rocksdb::kcv_store::RocksdbKCVStore;
pub struct RocksdbServerStorage {
wallet_storage: RocksdbKCVStore,
accounts_storage: RocksdbKCVStore,
peers_storage: RocksdbKCVStore,
peers_last_seq_path: PathBuf,
peers_last_seq: Mutex<HashMap<PeerId, u64>>,
}
impl RocksdbServerStorage {
@ -63,7 +69,7 @@ impl RocksdbServerStorage {
&Some("admin user automatically invited at first startup".to_string()),
&accounts_storage,
)?;
let invitation = p2p_net::types::Invitation::V0(InvitationV0 {
let invitation = ng_net::types::Invitation::V0(InvitationV0 {
code: Some(symkey),
name: Some("your NG Box, as admin".into()),
url: None,
@ -92,15 +98,52 @@ impl RocksdbServerStorage {
//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())?;
// 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 {
wallet_storage,
accounts_storage,
peers_storage,
peers_last_seq_path,
peers_last_seq: Mutex::new(HashMap::new()),
})
}
}
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> {
log_debug!("get_user {user_id}");
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 once_cell::sync::Lazy;
use once_cell::sync::OnceCell;
use p2p_client_ws::remote_ws::ConnectionWebSocket;
use p2p_net::broker::*;
use p2p_net::connection::IAccept;
use p2p_net::types::*;
use p2p_net::utils::get_domain_without_port;
use p2p_net::utils::is_private_ip;
use p2p_net::utils::is_public_ip;
use p2p_net::NG_BOOTSTRAP_LOCAL_PATH;
use p2p_repo::log::*;
use p2p_repo::types::SymKey;
use p2p_repo::types::{PrivKey, PubKey};
use p2p_repo::utils::generate_keypair;
use ng_client_ws::remote_ws::ConnectionWebSocket;
use ng_net::broker::*;
use ng_net::connection::IAccept;
use ng_net::types::*;
use ng_net::utils::get_domain_without_port;
use ng_net::utils::is_private_ip;
use ng_net::utils::is_public_ip;
use ng_net::NG_BOOTSTRAP_LOCAL_PATH;
use ng_repo::log::*;
use ng_repo::types::SymKey;
use ng_repo::types::{PrivKey, PubKey};
use ng_repo::utils::generate_keypair;
use rust_embed::RustEmbed;
use serde_json::json;
use std::collections::HashMap;

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

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

@ -1,15 +1,20 @@
[package]
name = "p2p-client-ws"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
description = "P2P Client module of NextGraph"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
name = "ng-client-ws"
# version = "0.1.0"
description = "Websocket client library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
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
[dependencies]
p2p-repo = { path = "../p2p-repo" }
p2p-net = { path = "../p2p-net" }
ng-repo = { path = "../ng-repo", version = "0.1.0" }
ng-net = { path = "../ng-net", version = "0.1.0" }
chacha20 = "0.9.0"
serde = { version = "1.0", features = ["derive"] }
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 async_std::task;
use p2p_net::errors::*;
use p2p_net::types::*;
use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender};
use p2p_net::{connection::*, WS_PORT};
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, now_timestamp};
use ng_net::errors::*;
use ng_net::types::*;
use ng_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender};
use ng_net::{connection::*, WS_PORT};
use ng_repo::log::*;
use ng_repo::types::*;
use ng_repo::utils::{generate_keypair, now_timestamp};
use async_tungstenite::async_std::{connect_async, ConnectStream};
use async_tungstenite::tungstenite::{Error, Message};
@ -301,13 +301,13 @@ mod test {
use crate::remote_ws::*;
use async_std::task;
use p2p_net::broker::*;
use p2p_net::errors::NetError;
use p2p_net::types::IP;
use p2p_net::utils::{spawn_and_log_error, ResultSend};
use p2p_repo::errors::NgError;
use p2p_repo::log::*;
use p2p_repo::utils::generate_keypair;
use ng_net::broker::*;
use ng_net::errors::NetError;
use ng_net::types::IP;
use ng_net::utils::{spawn_and_log_error, ResultSend};
use ng_repo::errors::NgError;
use ng_repo::log::*;
use ng_repo::utils::generate_keypair;
use std::net::IpAddr;
use std::str::FromStr;
@ -335,9 +335,9 @@ mod test {
server_key,
StartConfig::Client(ClientConfig {
url: format!("ws://localhost:{}", WS_PORT),
user,
name: None,
user_priv,
client,
client_priv,
info: ClientInfo::new(ClientType::Cli, "".into(), "".into()),
registration: None,
}),

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

@ -1,14 +1,23 @@
[package]
name = "p2p-net"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
description = "P2P network module of NextGraph"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
name = "ng-net"
# version = "0.1.0"
description = "Network library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
categories = ["network-programming"]
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]
p2p-repo = { path = "../p2p-repo" }
ng-repo = { path = "../ng-repo", version = "0.1.0" }
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
@ -39,4 +48,4 @@ features = ["js"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
getrandom = "0.2.7"
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 async_std::sync::Mutex;
use p2p_repo::log::*;
use p2p_repo::types::PubKey;
use ng_repo::log::*;
use ng_repo::types::PubKey;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

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

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

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

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

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

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

@ -20,18 +20,20 @@ use async_std::sync::{Arc, RwLock};
use either::Either;
use futures::channel::mpsc;
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 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::path::PathBuf;
#[derive(Debug)]
pub enum PeerConnection {
Core(IP),
Core(BindAddress),
Client(ConnectionBase),
NONE,
}
@ -44,7 +46,7 @@ pub struct BrokerPeerInfo {
#[derive(Debug)]
pub struct DirectConnection {
ip: IP,
addr: BindAddress,
remote_peer_id: X25519PrivKey,
tp: TransportProtocol,
//dir: ConnectionDir,
@ -62,10 +64,12 @@ pub struct ServerConfig {
pub bootstrap: BootstrapContent,
}
pub trait ILocalBroker: Send + Sync {}
pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new())));
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.
peers: HashMap<(Option<PubKey>, X25519PubKey), BrokerPeerInfo>,
/// (local,remote) -> ConnectionBase
@ -79,10 +83,13 @@ pub struct Broker<'a> {
closing: bool,
server_storage: Option<Box<dyn ServerStorage + Send + Sync + 'a>>,
test: u32,
tauri_streams: HashMap<String, Sender<Commit>>,
disconnections_sender: Sender<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> {
@ -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> {
self.config.as_ref()
}
@ -123,10 +149,10 @@ impl<'a> Broker<'a> {
self.server_storage = Some(Box::new(storage));
}
// pub fn set_local_storage(&mut self, storage: impl LocalStorage + 'a) {
// //log_debug!("set_storage");
// self.local_storage = Some(Box::new(storage));
// }
pub fn set_local_broker(&mut self, broker: impl ILocalBroker + 'a) {
//log_debug!("set_local_broker");
self.local_broker = Some(Box::new(broker));
}
pub fn set_server_config(&mut self, config: ServerConfig) {
self.config = Some(config);
@ -159,6 +185,13 @@ impl<'a> Broker<'a> {
.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"))]
pub fn authorize(
&self,
@ -288,7 +321,7 @@ impl<'a> Broker<'a> {
//Err(ProtocolError::AccessDenied)
// let f = std::fs::File::open(
// "../p2p-repo/tests/e4e4b57524ce29df826055c368894e912ab03af46f61f6270b4c8796bc6f4221.ng",
// "../ng-repo/tests/e4e4b57524ce29df826055c368894e912ab03af46f61f6270b4c8796bc6f4221.ng",
// )
// .expect("open of block.ng");
// let mut reader = BufReader::new(f);
@ -340,10 +373,12 @@ impl<'a> Broker<'a> {
let (member_privkey, member_pubkey) = generate_keypair();
let overlay = OverlayId::nil();
let commit = Commit::new(
member_privkey,
member_pubkey,
1,
&member_privkey,
&member_pubkey,
overlay,
PubKey::nil(),
QuorumType::NoSigning,
vec![],
@ -412,13 +447,9 @@ impl<'a> Broker<'a> {
}
}
pub fn test(&self) -> u32 {
self.test
}
// #[cfg(not(target_arch = "wasm32"))]
// 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 test_storage = RocksdbKCVStore::open(&path, key);
@ -449,10 +480,13 @@ impl<'a> Broker<'a> {
peers: HashMap::new(),
tauri_streams: HashMap::new(),
closing: false,
test: u32::from_be_bytes(random_buf),
server_storage: None,
disconnections_sender,
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)?;
connection.reset_shutdown(remote_peer_id).await;
let ip = remote_bind_address.ip;
let connected = if !is_core {
PeerConnection::Client(connection)
} else {
let dc = DirectConnection {
ip,
addr: remote_bind_address,
remote_peer_id,
tp: connection.transport_protocol(),
cnx: connection,
};
self.direct_connections.insert(ip, dc);
PeerConnection::Core(ip)
self.direct_connections.insert(remote_bind_address, dc);
PeerConnection::Core(remote_bind_address)
};
let bpi = BrokerPeerInfo {
lastPeerAdvert: None,
@ -796,15 +829,14 @@ impl<'a> Broker<'a> {
let connected = match &config {
StartConfig::Core(config) => {
let ip = config.addr.ip.clone();
let dc = DirectConnection {
ip,
addr: config.addr,
remote_peer_id: *remote_peer_id_dh.slice(),
tp: connection.transport_protocol(),
cnx: connection,
};
self.direct_connections.insert(ip, dc);
PeerConnection::Core(ip)
self.direct_connections.insert(config.addr, dc);
PeerConnection::Core(config.addr)
}
StartConfig::Client(_config) => PeerConnection::Client(connection),
_ => unimplemented!(),

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

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

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

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

@ -9,8 +9,11 @@
* according to those terms.
*/
use crate::{errors::ProtocolError, types::*};
use p2p_repo::types::PubKey;
use crate::{
errors::{ProtocolError, ServerError},
types::*,
};
use ng_repo::types::{PeerId, PubKey};
pub trait ServerStorage: Send + Sync {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>;
@ -31,4 +34,6 @@ pub trait ServerStorage: Send + Sync {
) -> Result<(), ProtocolError>;
fn get_invitation_type(&self, invite: [u8; 32]) -> Result<u8, 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 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 std::{
any::{Any, TypeId},
@ -1156,49 +1156,6 @@ pub enum RegistrationConfig {
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
///
/// 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
/// 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
/// 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.
pub store_overlay_readcap: ReadCap,
}
@ -1397,11 +1354,13 @@ pub enum ClientType {
NativeService,
NodeService,
Verifier,
Box,
Stick,
WalletMaster,
ClientBroker,
Cli,
}
/// IP transport address
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct ClientInfoV0 {
pub client_type: ClientType,
@ -1539,69 +1498,6 @@ pub enum UnsubReq {
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
///
/// Sent along the reverse path of a pub/sub topic
@ -1741,8 +1637,7 @@ pub enum ForwardedPeerAdvert {
/// ForwardedPeerConflictV0
/// 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 disconnects the client(s).
/// When the forwarding broker receives the conflict (or notices it), it sends a notification
/// 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.
/// 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
/// 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>,
/// 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)
pub max_peer_count: u16,
@ -2595,6 +2496,7 @@ pub struct OpenRepoV0 {
/// list of topics for which we will be a publisher
/// 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>,
}
@ -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
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,
/// 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>,
/// Maximum number of peers to connect to for this overlay (only valid for an inner (RW/WO) overlay)
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
/// If the repo has previously been opened (during the same session) then ro_topics info can be omitted
pub ro_topics: Vec<TopicId>,
@ -3514,6 +3422,9 @@ pub struct ClientAuthV0 {
/// Signature by user key
pub sig: Sig,
/// Signature by client key
pub client_sig: Sig,
}
/// 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.
/// 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.
/// For durable read capabilities of non-members, see PermaReadCap.
/// In most cases, the link is shared and the recipient opens it and subscribes soon afterward.
/// For durable capabilities, see PermaCap.
/// 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.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RepoLinkV0 {
@ -3616,7 +3527,7 @@ pub struct RepoLinkV0 {
pub read_cap: ReadCap,
/// 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
pub overlay: OverlayLink,
@ -3637,11 +3548,6 @@ impl RepoLink {
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> {
match self {
RepoLink::V0(o) => &o.peers,
@ -3695,7 +3601,7 @@ pub struct ReadBranchLinkV0 {
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
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
/// (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.
/// 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
pub topic: Option<TopicId>,
@ -3769,7 +3675,7 @@ pub enum NgLink {
mod test {
use crate::types::{BootstrapContentV0, BrokerServerTypeV0, BrokerServerV0, Invitation};
use p2p_repo::types::PubKey;
use ng_repo::types::PubKey;
#[test]
pub fn invitation() {

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

@ -1,16 +1,24 @@
[package]
name = "p2p-repo"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
description = "P2P repository module of NextGraph"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
name = "ng-repo"
# version = "0.1.0"
description = "Repository library of NextGraph, a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
categories = ["asynchronous","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" }
[features]
server_log_output = []
[dependencies]
blake3 = "1.3.1"
chacha20 = "0.9.0"
@ -35,6 +43,7 @@ once_cell = "1.17.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
debug_print = "1.0.0"
log = "0.4"
getrandom = "0.2.7"
[target.'cfg(target_arch = "wasm32")'.dependencies]
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
// 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
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,

@ -10,6 +10,7 @@
//! Branch of a Repository
use std::collections::HashSet;
use zeroize::{Zeroize, ZeroizeOnDrop};
// use fastbloom_rs::{BloomFilter as Filter, Membership};
@ -17,6 +18,7 @@ use crate::errors::*;
use crate::object::*;
use crate::store::*;
use crate::types::*;
use crate::utils::encrypt_in_place;
impl BranchV0 {
pub fn new(
@ -41,6 +43,32 @@ impl BranchV0 {
}
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(
id: PubKey,
repo: ObjectRef,
@ -213,6 +241,8 @@ mod test {
acks.iter().map(|r| r.id).collect(),
);
let overlay = store_pubkey.overlay_id_for_read_purpose();
let obj_ref = ObjectRef {
id: ObjectId::Blake3Digest32([1; 32]),
key: SymKey::ChaCha20Key([2; 32]),
@ -221,9 +251,9 @@ mod test {
let metadata = vec![5u8; 55];
let commit = CommitV0::new(
author_privkey,
author_pubkey,
seq,
&author_privkey,
&author_pubkey,
overlay,
branch,
QuorumType::NoSigning,
deps,
@ -301,6 +331,7 @@ mod test {
&repo_pubkey,
&member_pubkey,
&[PermissionV0::WriteAsync],
store_repo.overlay_id_for_read_purpose(),
t.s(),
);
@ -337,10 +368,14 @@ mod test {
// commit bodies
let branch_body =
add_body_branch(branch.clone(), &store_repo, &repo_secret, repo.get_store());
let branch_body = add_body_branch(
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
@ -355,7 +390,7 @@ mod test {
branch_body.clone(),
&store_repo,
&repo_secret,
repo.get_store(),
repo.get_storage(),
);
log_debug!(">> t1");
@ -369,7 +404,7 @@ mod test {
trans_body.clone(),
&store_repo,
&repo_secret,
repo.get_store(),
repo.get_storage(),
);
log_debug!(">> t2");
@ -383,7 +418,7 @@ mod test {
trans_body.clone(),
&store_repo,
&repo_secret,
repo.get_store(),
repo.get_storage(),
);
// log_debug!(">> a3");
@ -411,7 +446,7 @@ mod test {
trans_body.clone(),
&store_repo,
&repo_secret,
repo.get_store(),
repo.get_storage(),
);
log_debug!(">> t5");
@ -425,7 +460,7 @@ mod test {
trans_body.clone(),
&store_repo,
&repo_secret,
repo.get_store(),
repo.get_storage(),
);
log_debug!(">> a6");
@ -439,7 +474,7 @@ mod test {
trans_body.clone(),
&store_repo,
&repo_secret,
repo.get_store(),
repo.get_storage(),
);
log_debug!(">> a7");
@ -453,10 +488,10 @@ mod test {
trans_body.clone(),
&store_repo,
&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();
// let mut filter = Filter::new(FilterBuilder::new(10, 0.01));
@ -481,7 +516,7 @@ mod test {
&[t5.id, a6.id, a7.id],
&[t5.id],
//&their_commits,
repo.get_store(),
repo.get_storage(),
)
.unwrap();

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

@ -19,7 +19,9 @@ use std::error::Error;
#[repr(u16)]
pub enum NgError {
InvalidSignature,
IncompleteSignature,
SerializationError,
EncryptionError,
InvalidKey,
InvalidInvitation,
InvalidCreateAccount,
@ -29,14 +31,29 @@ pub enum NgError {
CommitLoadError(CommitLoadError),
StorageError(StorageError),
NotFound,
IoError,
CommitVerifyError(CommitVerifyError),
LocalBrokerNotInitialized,
JsStorageReadError,
JsStorageWriteError(String),
CannotSaveWhenInMemoryConfig,
WalletNotFound,
WalletAlreadyAdded,
WalletAlreadyOpened,
WalletError(String),
BrokerError,
LockError,
SessionNotFound,
}
impl Error for NgError {}
impl fmt::Display for NgError {
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::ChaCha20;
use zeroize::Zeroize;
use crate::errors::*;
use crate::log::*;
@ -160,6 +161,7 @@ pub struct 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.
/// `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> {
if size == 0 {
return Err(FileError::InvalidArgument);
@ -378,7 +380,7 @@ impl<'a> RandomAccessFile<'a> {
conv_key,
);
//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
// 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 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![];
@ -453,6 +455,8 @@ impl<'a> RandomAccessFile<'a> {
storage,
)?;
conv_key.zeroize();
Ok(Self {
storage,
meta,
@ -596,6 +600,9 @@ impl<'a> RandomAccessFile<'a> {
self.storage,
)?;
self.conv_key.as_mut().unwrap().zeroize();
self.conv_key = None;
self.id = Some(root_block.0);
self.key = Some(root_block.1.clone());
self.content_block = Some(content_block);

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

@ -1,7 +1,5 @@
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// 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
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
@ -12,11 +10,13 @@
//! Merkle hash tree of Objects
use core::fmt;
use std::borrow::BorrowMut;
use std::cmp::max;
use std::collections::{HashMap, HashSet};
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::ChaCha20;
use zeroize::Zeroize;
use crate::errors::*;
use crate::log::*;
@ -63,13 +63,15 @@ impl Object {
store_pubkey: &StoreRepo,
store_readcap_secret: &ReadCapSecret,
) -> [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, secret].concat()
}
(_, _) => 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(
@ -272,8 +274,10 @@ impl Object {
store: &StoreRepo,
store_secret: &ReadCapSecret,
) -> Object {
let conv_key = Self::convergence_key(store, store_secret);
Self::new_with_convergence_key(content, header, block_size, &conv_key)
let mut conv_key = Self::convergence_key(store, store_secret);
let res = Self::new_with_convergence_key(content, header, block_size, &conv_key);
conv_key.zeroize();
res
}
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"
);
}
log_debug!("header {:?}", header);
// log_debug!("header {:?}", header);
// create blocks by chunking + encrypting content
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_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_len = content_ser.len();
log_debug!(
"only one block? {} {} {}",
content_len <= max_data_payload_size,
content_len,
max_data_payload_size
);
// log_debug!(
// "only one block? {} {} {}",
// content_len <= max_data_payload_size,
// content_len,
// max_data_payload_size
// );
let header_blocks = if content_len <= max_data_payload_size {
// content fits in root node
let data_chunk = ChunkContentV0::DataChunk(content_ser.clone());
@ -371,7 +375,7 @@ impl Object {
&mut block_contents,
&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;
}
@ -394,11 +398,11 @@ impl Object {
};
if header_blocks.len() > 0 {
log_debug!(
"header_blocks.len() {} {}",
header_blocks.len(),
header_blocks.last().unwrap().id()
);
// log_debug!(
// "header_blocks.len() {} {}",
// header_blocks.len(),
// header_blocks.last().unwrap().id()
// );
header
.as_mut()
.unwrap()
@ -513,10 +517,11 @@ impl Object {
}
/// 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();
//.chain(self.header_blocks.iter())
for block_id in self.blocks.iter() {
deduplicated.insert(*block_id);
store.put(self.block_contents.get(block_id).unwrap())?;
}
for block in &self.header_blocks {
@ -526,14 +531,20 @@ impl Object {
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)]
pub fn save_in_test(
&mut self,
store: &Box<impl RepoStore + ?Sized>,
) -> Result<(), StorageError> {
) -> Result<Vec<BlockId>, StorageError> {
assert!(self.already_saved == false);
self.already_saved = true;
@ -625,12 +636,12 @@ impl Object {
let mut total = 0;
self.blocks().for_each(|b| {
let s = b.size();
log_debug!("@@@@ {}", s);
//log_debug!("@@@@ {}", s);
total += s;
});
self.header_blocks.iter().for_each(|b| {
let s = b.size();
log_debug!("@@@@ {}", s);
//log_debug!("@@@@ {}", s);
total += s;
});
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 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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
// 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
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// 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
// 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
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
@ -13,6 +11,8 @@ use crate::errors::*;
use crate::log::*;
use crate::types::*;
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::ChaCha20;
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
use ed25519_dalek::*;
use futures::channel::mpsc;
@ -161,6 +161,12 @@ pub fn generate_keypair() -> (PrivKey, PubKey) {
(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.
pub fn now_timestamp() -> Timestamp {
((SystemTime::now()

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

@ -1,11 +1,17 @@
[package]
name = "ng-sdk-js"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
# version = "0.1.0"
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]
wasm-opt = false
@ -16,10 +22,12 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
ws_stream_wasm = "0.7"
p2p-net = { path = "../p2p-net" }
p2p-repo = { path = "../p2p-repo" }
p2p-client-ws = { path = "../p2p-client-ws" }
ng-net = { path = "../ng-net" }
ng-repo = { path = "../ng-repo" }
ng-client-ws = { path = "../ng-client-ws" }
ng-wallet = { path = "../ng-wallet" }
once_cell = "1.17.1"
nextgraph = { path = "../nextgraph" }
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
futures = "0.3.24"
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.
>
> 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)

@ -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",
"version": "0.1.0",
"description": "NodeJS app for NextGraph",
"description": "NodeJS app example for NextGraph",
"main": "index.js",
"scripts": {
"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
React app client of NextGraph
React example app for NextGraph
## 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.
>
> 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)
## For contributors
@ -30,9 +30,10 @@ Open this URL in browser : [http://localhost:8080](http://localhost:8080)
## 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.
- 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`

@ -1,7 +1,7 @@
{
"name": "ng-app-react",
"version": "0.1.0",
"description": "React based web application of NextGraph",
"description": "React based example web application for NextGraph",
"main": "src/index.jsx",
"scripts": {
"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.
>
> 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)
## For contributors
@ -30,9 +30,10 @@ Open this URL in browser : [http://localhost:8080](http://localhost:8080)
## 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.
- 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`

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

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

@ -10,30 +10,32 @@
*/
use async_std::task;
use once_cell::sync::Lazy;
// #[cfg(target_arch = "wasm32")]
// use js_sys::Reflect;
use async_std::stream::StreamExt;
#[cfg(target_arch = "wasm32")]
use js_sys::Uint8Array;
use ng_wallet::types::*;
use ng_wallet::*;
#[cfg(target_arch = "wasm32")]
use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket;
use p2p_net::broker::*;
use p2p_net::connection::{ClientConfig, StartConfig};
use p2p_net::types::{
use ng_client_ws::remote_ws_wasm::ConnectionWebSocket;
use ng_net::broker::*;
use ng_net::connection::{ClientConfig, StartConfig};
use ng_net::types::{
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")]
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 p2p_repo::errors::NgError;
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::generate_keypair;
use nextgraph::local_broker::*;
use ng_net::WS_PORT;
use ng_repo::errors::NgError;
use ng_repo::log::*;
use ng_repo::types::*;
use ng_repo::utils::generate_keypair;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
@ -114,16 +116,16 @@ pub fn wallet_gen_shuffle_for_pin() -> Vec<u8> {
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn wallet_open_wallet_with_pazzle(
pub fn wallet_open_with_pazzle(
js_wallet: JsValue,
pazzle: Vec<u8>,
js_pin: 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")?;
let mut pin = serde_wasm_bindgen::from_value::<[u8; 4]>(js_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 {
Ok(r) => Ok(r
.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")]
#[wasm_bindgen]
pub fn get_wallets_from_localstorage() -> JsValue {
let res = get_local_wallets_v0();
if res.is_ok() {
return serde_wasm_bindgen::to_value(&res.unwrap()).unwrap();
}
JsValue::UNDEFINED
}
pub async fn get_wallets() -> Result<JsValue, JsValue> {
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
fn save_new_session(
wallet_name: &String,
wallet_id: PubKey,
user: PubKey,
) -> Result<SessionWalletStorageV0, String> {
let res = create_new_session(wallet_id, user);
let res = get_all_wallets().await.map_err(|e| {
log_err!("{}", e.to_string());
});
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());
return Ok(serde_wasm_bindgen::to_value(&res.unwrap()).unwrap());
}
Ok(JsValue::UNDEFINED)
}
// 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)
} else {
Err(res.unwrap_err().to_string())
None => 0,
};
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")]
#[wasm_bindgen]
pub fn get_local_session(id: String, key_js: JsValue, user_js: JsValue) -> JsValue {
let res = session_get(format!("ng_wallet@{}", id));
if res.is_some() {
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();
}
}
pub async fn session_start(wallet_name: String, user_js: JsValue) -> Result<JsValue, String> {
let user_id = serde_wasm_bindgen::from_value::<PubKey>(user_js)
.map_err(|_| "Deserialization error of user_id")?;
// create a new session
let user = serde_wasm_bindgen::from_value::<PubKey>(user_js).unwrap();
let wallet_id: PubKey = id.as_str().try_into().unwrap();
let session_v0 = save_new_session(&id, wallet_id, user);
if session_v0.is_err() {
return JsValue::UNDEFINED;
}
return serde_wasm_bindgen::to_value(&session_v0.unwrap()).unwrap();
}
fn save_wallet_locally(res: &CreateWalletResultV0) -> Result<SessionWalletStorageV0, String> {
let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user)?;
let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_local_wallets_v0().unwrap_or(HashMap::new());
// TODO: check that the wallet is not already present in localStorage
let lws: LocalWalletStorageV0 = res.into();
wallets.insert(res.wallet_name.clone(), lws);
let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
let encoded = base64_url::encode(&lws_ser);
let r = local_save("ng_wallets".to_string(), encoded);
if r.is_some() {
return Err(r.unwrap());
let config = SessionConfig::V0(SessionConfigV0 {
user_id,
wallet_name,
});
let res = nextgraph::local_broker::session_start(config)
.await
.map_err(|e: NgError| e.to_string())?;
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)?;
// let mut wallets: HashMap<String, LocalWalletStorageV0> =
// get_all_wallets().unwrap_or(HashMap::new());
// // TODO: check that the wallet is not already present in localStorage
// let lws: LocalWalletStorageV0 = res.into();
// wallets.insert(res.wallet_name.clone(), lws);
// let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
// let encoded = base64_url::encode(&lws_ser);
// let r = local_save("ng_wallets".to_string(), encoded);
// 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"))]
@ -245,92 +344,146 @@ extern "C" {
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")]
#[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)
.map_err(|_| "Deserialization error of args")?;
params.result_with_wallet_file = true;
let local_save = params.local_save;
let res = create_wallet_v0(params).await;
let res = nextgraph::local_broker::wallet_create_v0(params).await;
match res {
Ok(r) => {
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())
}
}
Ok(r) => Ok(serde_wasm_bindgen::to_value(&r).unwrap()),
Err(e) => Err(e.to_string().into()),
}
}
#[cfg(target_arch = "wasm32")]
#[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)
.map_err(|_| "Deserialization error of file".to_string())?;
let ngf: NgFile = file
.into_vec()
.try_into()
let wallet = nextgraph::local_broker::wallet_read_file(file.into_vec())
.await
.map_err(|e: NgError| e.to_string())?;
if let NgFile::V0(NgFileV0::Wallet(wallet)) = ngf {
let wallets: HashMap<String, LocalWalletStorageV0> =
get_local_wallets_v0().unwrap_or(HashMap::new());
// check that the wallet is not already present in localStorage
let wallet_name = wallet.name();
if wallets.get(&wallet_name).is_none() {
Ok(serde_wasm_bindgen::to_value(&wallet).unwrap())
} else {
Err("Wallet already present on this device".to_string())
}
} else {
Err("File does not contain a wallet".to_string())
}
Ok(serde_wasm_bindgen::to_value(&wallet).unwrap())
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn wallet_was_opened(
js_opened_wallet: JsValue, //SensitiveWallet
in_memory: bool,
) -> Result<JsValue, String> {
let mut opened_wallet = serde_wasm_bindgen::from_value::<SensitiveWallet>(js_opened_wallet)
.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")]
#[wasm_bindgen]
pub async fn wallet_import(
js_previous_wallet: JsValue, //Wallet,
js_opened_wallet: JsValue, //EncryptedWallet
js_encrypted_wallet: JsValue, //Wallet,
js_opened_wallet: JsValue, //SensitiveWallet
in_memory: bool,
) -> 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())?;
let mut opened_wallet = serde_wasm_bindgen::from_value::<EncryptedWallet>(js_opened_wallet)
.map_err(|_| "Deserialization error of EncryptedWalletV0".to_string())?;
let EncryptedWallet::V0(mut opened_wallet_v0) = opened_wallet;
let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_local_wallets_v0().unwrap_or(HashMap::new());
// check that the wallet is not already present in localStorage
let wallet_name = opened_wallet_v0.wallet_id.clone();
if wallets.get(&wallet_name).is_none() {
let session = save_new_session(
&wallet_name,
opened_wallet_v0.wallet_privkey.to_pub(),
opened_wallet_v0.personal_site,
)
.map_err(|e| format!("Cannot create new session: {e}"))?;
let (wallet, client_id, client) = opened_wallet_v0
.import(previous_wallet, session)
.map_err(|e| e.to_string())?;
let lws = LocalWalletStorageV0::new(wallet, &client);
wallets.insert(wallet_name, lws);
let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
let encoded = base64_url::encode(&lws_ser);
let r = local_save("ng_wallets".to_string(), encoded);
if r.is_some() {
return Err(r.unwrap());
}
Ok(serde_wasm_bindgen::to_value(&(client_id, client)).unwrap())
} else {
Err("Wallet already present on this device".to_string())
}
let mut opened_wallet = serde_wasm_bindgen::from_value::<SensitiveWallet>(js_opened_wallet)
.map_err(|_| "Deserialization error of SensitiveWallet".to_string())?;
let client = nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(serde_wasm_bindgen::to_value(&client).unwrap())
// let SensitiveWallet::V0(mut opened_wallet_v0) = opened_wallet;
// let mut wallets: HashMap<String, LocalWalletStorageV0> =
// get_all_wallets().unwrap_or(HashMap::new());
// // check that the wallet is not already present in localStorage
// let wallet_name = opened_wallet_v0.wallet_id.clone();
// if wallets.get(&wallet_name).is_none() {
// let session = save_new_session(
// &wallet_name,
// opened_wallet_v0.wallet_privkey.to_pub(),
// opened_wallet_v0.personal_site,
// )
// .map_err(|e| format!("Cannot create new session: {e}"))?;
// let (lws, client) = opened_wallet_v0
// .import(previous_wallet, session)
// .map_err(|e| e.to_string())?;
// //let lws = LocalWalletStorageV0::new(wallet, &client);
// wallets.insert(wallet_name, lws);
// let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
// let encoded = base64_url::encode(&lws_ser);
// 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")]
@ -445,7 +598,8 @@ pub fn client_info() -> JsValue {
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
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();
log_debug!("{:?}", client_info);
}
@ -458,12 +612,7 @@ pub async fn doc_get_file_from_store_with_object_ref(
) -> Result<JsValue, JsValue> {
let obj_ref = serde_wasm_bindgen::from_value::<ObjectRef>(obj_ref_js).unwrap();
log_debug!(
"doc_get_file {} {:?} {}",
nuri,
obj_ref.id,
BROKER.read().await.test()
);
log_debug!("doc_get_file {} {:?}", nuri, obj_ref.id,);
// let vec: Vec<u8> = vec![2; 10];
// let view = unsafe { Uint8Array::view(&vec) };
@ -596,7 +745,7 @@ pub async fn start() {
let server_key: PubKey = "X0nh-gOTGKSx0yL0LYJviOWRNacyqIzjQW_LKdK6opU".try_into()?;
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 keys = generate_keypair();
let x_from_ed = keys.1.to_dh_from_ed();
@ -617,9 +766,9 @@ pub async fn start() {
server_key,
StartConfig::Client(ClientConfig {
url: format!("ws://127.0.0.1:{}", WS_PORT),
user,
name: None,
user_priv,
client,
client_priv,
info: ClientInfo::V0(client_info_()),
registration: None,
}),
@ -660,28 +809,45 @@ pub async fn start() {
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn broker_disconnect() {
Broker::close_all_connections().await;
pub async fn session_stop(user_id_js: JsValue) -> Result<(), String> {
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")]
#[wasm_bindgen]
pub async fn broker_connect(
client_pub_key_js: JsValue,
pub async fn user_disconnect(user_id_js: JsValue) -> Result<(), String> {
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,
session_js: JsValue,
opened_wallet_js: JsValue,
user_id_js: JsValue,
location: Option<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)
.map_err(|_| "serde error on info")?;
let session =
serde_wasm_bindgen::from_value::<HashMap<String, SessionPeerStorageV0>>(session_js)
.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")?;
let user_id = serde_wasm_bindgen::from_value::<UserId>(user_id_js)
.map_err(|_| "serde error on user_id")?;
#[derive(Serialize, Deserialize)]
struct ConnectionInfo {
@ -694,15 +860,9 @@ pub async fn broker_connect(
let mut opened_connections: HashMap<String, ConnectionInfo> = HashMap::new();
let results = connect_wallet(
client,
info,
session,
opened_wallet,
location,
Box::new(ConnectionWebSocket {}),
)
.await?;
let results = nextgraph::local_broker::user_connect(info, user_id, location)
.await
.map_err(|e: NgError| e.to_string())?;
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
.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