├── home-node ├── home.cfg ├── src │ ├── lib.rs │ ├── bin │ │ └── mercury-home.rs │ └── config.rs ├── Cargo.toml └── log4rs.yml ├── debian ├── compat ├── prometheus.install ├── changelog ├── rules ├── control └── README ├── storage ├── .gitignore ├── src │ ├── lib.rs │ └── common │ │ ├── mod.rs │ │ └── imp.rs ├── README.md └── Cargo.toml ├── prometheus ├── src │ ├── vault │ │ ├── http │ │ │ ├── mod.rs │ │ │ └── server │ │ │ │ ├── mod.rs │ │ │ │ └── routes.rs │ │ ├── mod.rs │ │ └── api.rs │ ├── home │ │ ├── mod.rs │ │ ├── discovery.rs │ │ └── connection.rs │ ├── dapp │ │ ├── websocket │ │ │ ├── mod.rs │ │ │ ├── routes.rs │ │ │ ├── client.rs │ │ │ └── server.rs │ │ ├── mod.rs │ │ ├── user_interactor.rs │ │ └── dapp_session.rs │ ├── bin │ │ └── prometheusd.rs │ ├── test │ │ └── mod.rs │ ├── lib.rs │ ├── options.rs │ └── names │ │ └── mod.rs ├── log4rs.yml ├── Cargo.toml └── build.rs ├── etc ├── client.id ├── server.id ├── homenode.id ├── client.id.pub ├── server.id.pub └── homenode.id.pub ├── did ├── src │ ├── lib.rs │ ├── paths.rs │ └── model.rs └── Cargo.toml ├── claims ├── src │ ├── lib.rs │ ├── claim_schema │ │ ├── defaults.rs │ │ └── mod.rs │ └── journal.rs └── Cargo.toml ├── home-protocol ├── build.rs ├── src │ ├── lib.rs │ ├── util.rs │ ├── error.rs │ └── crypto.rs ├── Cargo.toml └── protocol │ └── mercury.capnp ├── keyvault-wasm ├── build.sh ├── Cargo.toml └── src │ └── lib.rs ├── rustfmt.toml ├── .gitignore ├── test ├── testdb.sql ├── createdb.sh ├── Cargo.toml └── src │ └── lib.rs ├── forgetfulfuse ├── forgetful.service ├── Cargo.toml ├── src │ └── main.rs └── README.md ├── keyvault ├── src │ ├── secp256k1 │ │ ├── network │ │ │ ├── mod.rs │ │ │ ├── btc.rs │ │ │ ├── iop.rs │ │ │ ├── ark.rs │ │ │ └── hyd.rs │ │ ├── sig.rs │ │ ├── pk.rs │ │ ├── id.rs │ │ ├── sk.rs │ │ └── ext_pk.rs │ ├── tests.rs │ ├── multicipher │ │ └── sk.rs │ ├── bip39.rs │ ├── cc.rs │ └── ed25519 │ │ ├── sk.rs │ │ ├── pk.rs │ │ ├── sig.rs │ │ ├── id.rs │ │ └── ext_sk.rs ├── README.md └── Cargo.toml ├── connect ├── test │ └── samples.json ├── src │ ├── jsonrpc │ │ ├── api.rs │ │ └── mod.rs │ ├── lib.rs │ ├── error.rs │ ├── sdk.rs │ └── net.rs ├── Cargo.toml └── log4rs.yml ├── examples └── TheButton │ ├── README │ ├── Cargo.toml │ ├── log4rs.yml │ └── src │ ├── options.rs │ ├── init.rs │ ├── main.rs │ ├── subscriber.rs │ └── publisher.rs ├── Cargo.toml ├── prometheus-cli ├── log4rs.yml ├── Cargo.toml ├── src │ ├── main.rs │ └── seed.rs └── README.md ├── doc └── adr │ ├── 0002-choosing-capnproto.md │ ├── 0001-choosing-rust.md │ └── 0003-logging.md ├── mercury-wasm ├── Cargo.toml └── src │ └── lib.rs ├── CONTRIBUTING.md └── .gitlab-ci.yml /home-node/home.cfg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /storage/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea -------------------------------------------------------------------------------- /home-node/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod server; 3 | -------------------------------------------------------------------------------- /debian/prometheus.install: -------------------------------------------------------------------------------- 1 | target/release/prometheus-cli usr/bin 2 | -------------------------------------------------------------------------------- /prometheus/src/vault/http/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod server; 3 | -------------------------------------------------------------------------------- /storage/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod asynch; 2 | pub mod common; 3 | pub mod meta; 4 | -------------------------------------------------------------------------------- /prometheus/src/vault/http/server/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod controller; 2 | pub mod routes; 3 | -------------------------------------------------------------------------------- /etc/client.id: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Internet-of-People/mercury-rust/HEAD/etc/client.id -------------------------------------------------------------------------------- /etc/server.id: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Internet-of-People/mercury-rust/HEAD/etc/server.id -------------------------------------------------------------------------------- /prometheus/src/home/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod connection; 2 | pub mod discovery; 3 | pub mod net; 4 | -------------------------------------------------------------------------------- /did/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod model; 2 | pub mod paths; 3 | pub mod vault; 4 | 5 | pub use model::*; 6 | -------------------------------------------------------------------------------- /etc/homenode.id: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Internet-of-People/mercury-rust/HEAD/etc/homenode.id -------------------------------------------------------------------------------- /prometheus/src/dapp/websocket/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod routes; 3 | pub mod server; 4 | -------------------------------------------------------------------------------- /claims/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod claim_schema; 2 | pub mod journal; 3 | pub mod model; 4 | pub mod repo; 5 | -------------------------------------------------------------------------------- /etc/client.id.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Internet-of-People/mercury-rust/HEAD/etc/client.id.pub -------------------------------------------------------------------------------- /etc/server.id.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Internet-of-People/mercury-rust/HEAD/etc/server.id.pub -------------------------------------------------------------------------------- /etc/homenode.id.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Internet-of-People/mercury-rust/HEAD/etc/homenode.id.pub -------------------------------------------------------------------------------- /prometheus/src/dapp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dapp_session; 2 | pub mod user_interactor; 3 | pub mod websocket; 4 | -------------------------------------------------------------------------------- /prometheus/src/vault/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod api_data; 3 | pub mod api_impl; 4 | pub mod http; 5 | -------------------------------------------------------------------------------- /home-protocol/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | capnpc::CompilerCommand::new().file("protocol/mercury.capnp").run().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /keyvault-wasm/build.sh: -------------------------------------------------------------------------------- 1 | # cargo install wasm-pack 2 | cargo build 3 | wasm-pack build --target nodejs --out-name keyvault-wasm 4 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | prometheus (0.1) buster; urgency=medium 2 | 3 | * Initial release 4 | 5 | -- Rache Bartmoss Fri, 22 Feb 2019 15:59:48 +0100 6 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DH_VERBOSE = 1 4 | 5 | %: 6 | dh $@ 7 | 8 | override_dh_auto_build: 9 | # assert the binary has been already built: 10 | [ -f target/release/prometheus-cli ] 11 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # --- Overwritten values 2 | edition = "2018" 3 | use_small_heuristics = "Max" 4 | use_field_init_shorthand = true 5 | use_try_shorthand = true 6 | 7 | # --- Default values 8 | # max_width = 100 9 | # tab_spaces = 4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/.idea 3 | **/.vscode 4 | **/filetest/ 5 | **/log 6 | debian/prometheus 7 | debian/prometheus.substvars 8 | debian/prometheus.*.debhelper 9 | debian/prometheus.debhelper.log 10 | debian/files 11 | debian/.debhelper 12 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: prometheus 2 | Maintainer: IoP Ventures LLC 3 | Section: misc 4 | Standards-Version: 3.9.8 5 | Build-Depends: debhelper (>= 9) 6 | 7 | Package: prometheus 8 | Architecture: all 9 | Description: Prometheus CLI 10 | 11 | -------------------------------------------------------------------------------- /storage/README.md: -------------------------------------------------------------------------------- 1 | Building blocks 2 | =============== 3 | 4 | This is a Rust library collecting basic interfaces and implementations as building blocks to implement decentralized software, including a blockchain. 5 | You can find some documentation [here](doc) like this 6 | [architecture description](doc/Architecture.md). -------------------------------------------------------------------------------- /test/testdb.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS storagetest 2 | ( 3 | id BIGSERIAL PRIMARY KEY, 4 | key TEXT UNIQUE NOT NULL, 5 | data BYTEA NOT NULL 6 | ); 7 | 8 | SELECT column_name, data_type, character_maximum_length 9 | FROM INFORMATION_SCHEMA.COLUMNS 10 | WHERE table_name = 'storagetest'; -------------------------------------------------------------------------------- /forgetfulfuse/forgetful.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mounts a filesystem for temporary secrets 3 | 4 | [Service] 5 | ExecStartPre=/bin/mkdir -p /run/user/%U/forgetful 6 | ExecStart=/bin/forgetfulfuse /run/user/%U/forgetful 7 | ExecStop=/bin/fusermount -u /run/user/%U/forgetful 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/network/mod.rs: -------------------------------------------------------------------------------- 1 | /// ARK.io related `Network` implementations 2 | pub mod ark; 3 | 4 | /// Bitcoin related `Network` implementations 5 | pub mod btc; 6 | 7 | /// Hydra coin related `Network` implementations 8 | pub mod hyd; 9 | 10 | /// Internet of People related `Network` implementations 11 | pub mod iop; 12 | -------------------------------------------------------------------------------- /connect/test/samples.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc": "2.0", "id": 1, "method": "get_session", "params": {"application_id": "TheButton"}} 2 | {"jsonrpc": "2.0", "id": 2, "method": "subscribe_events", "params": {}} 3 | {"jsonrpc": "2.0", "id": 3, "method": "unsubscribe_events", "params": [5]} 4 | {"jsonrpc": "2.0", "id": "whatever", "method": "NoSuchMethodExists", "params": {"dapp": "TheButton"}} 5 | -------------------------------------------------------------------------------- /debian/README: -------------------------------------------------------------------------------- 1 | Steps to create a debian package: 2 | 1. Make sure you have the Rust toolchain installed, use `rustup` from https://rustup.rs/ if not so. 3 | 2. Compile the project by running `cargo build --release` in git root to build binaries. 4 | 3. Install packaging tools using e.g. `sudo apt install dpkg-dev debhelper` 5 | 2. Run `dpkg-buildpackage --no-sign` to build the Debian package. 6 | -------------------------------------------------------------------------------- /examples/TheButton/README: -------------------------------------------------------------------------------- 1 | The Button Decentralized application is right now non-functional. 2 | As of 2018.07.19 it is still only a skeleton that will implement the Mercury Connect sdk to provide example functionality. 3 | 4 | The files in the logging directory should be put where the executable of the compiled source-code is. 5 | These files may or may not be replaced later with an code representation 6 | -------------------------------------------------------------------------------- /did/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "did" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | #bincode = "*" 9 | dirs = "*" 10 | failure = "*" 11 | futures = "0.1" 12 | log = "*" 13 | keyvault = { path="../keyvault" } 14 | serde = "*" 15 | serde_bytes = "*" 16 | serde_derive = "*" 17 | serde_json = "*" 18 | -------------------------------------------------------------------------------- /keyvault-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keyvault-wasm" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | failure = "*" 12 | log = "*" 13 | keyvault = { path="../keyvault" } 14 | serde_json = "*" 15 | wasm-bindgen = { version = "*", features = ["serde-serialize"] } 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "claims", 4 | # "connect", 5 | "did", 6 | "home-protocol", 7 | "home-node", 8 | # "forgetfulfuse", # causes compilation problems on Mac, still experimental 9 | "keyvault", 10 | "keyvault-wasm", 11 | "mercury-wasm", 12 | "prometheus", 13 | "prometheus-cli", 14 | "storage", 15 | # TODO put this back after refactoring 16 | # "test", 17 | "examples/TheButton", 18 | ] 19 | -------------------------------------------------------------------------------- /claims/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claims" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | did = { path="../did" } 9 | dirs = "*" 10 | failure = "*" 11 | futures = "0.1" 12 | log = "*" 13 | keyvault = { path="../keyvault" } 14 | multibase = "*" 15 | multihash = "*" 16 | rand = "*" 17 | serde = "*" 18 | serde_derive = "*" 19 | serde_json = "*" 20 | -------------------------------------------------------------------------------- /keyvault/README.md: -------------------------------------------------------------------------------- 1 | # Key Vault Rust Crate 2 | 3 | ## Table of contents 4 | 5 | - [Key Vault Rust Crate](#key-vault-rust-crate) 6 | - [Table of contents](#table-of-contents) 7 | - [Reporting Vulnerabilities](#reporting-vulnerabilities) 8 | - [Usage](#usage) 9 | - [Contributing](#contributing) 10 | - [License](#license) 11 | 12 | ## Reporting Vulnerabilities 13 | 14 | ## Usage 15 | 16 | ## Contributing 17 | 18 | ## License 19 | 20 | [LGPL-v3-or-later](LICENSE) -------------------------------------------------------------------------------- /prometheus/log4rs.yml: -------------------------------------------------------------------------------- 1 | # Scan this file for changes every 30 seconds 2 | refresh_rate: 30 seconds 3 | 4 | appenders: 5 | # An appender named "stdout" that writes to stdout 6 | stdout: 7 | kind: console 8 | encoder: 9 | pattern: "{m}{n}" 10 | # pattern: "{d(%Y-%m-%d %H:%M:%S)} {h({l})} {M}{f}{L} - {m}{n}" 11 | 12 | # Set the default logging level and attach appenders to the root 13 | root: 14 | level: info 15 | appenders: 16 | - stdout 17 | -------------------------------------------------------------------------------- /prometheus-cli/log4rs.yml: -------------------------------------------------------------------------------- 1 | # Scan this file for changes every 30 seconds 2 | refresh_rate: 30 seconds 3 | 4 | appenders: 5 | # An appender named "stdout" that writes to stdout 6 | stdout: 7 | kind: console 8 | encoder: 9 | pattern: "{m}{n}" 10 | # pattern: "{d(%Y-%m-%d %H:%M:%S)} {h({l})} {M}{f}{L} - {m}{n}" 11 | 12 | # Set the default logging level and attach appenders to the root 13 | root: 14 | level: info 15 | appenders: 16 | - stdout 17 | -------------------------------------------------------------------------------- /doc/adr/0002-choosing-capnproto.md: -------------------------------------------------------------------------------- 1 | # 0002. Choosing Cap'n'Proto 2 | 3 | Date: 2018-03-13 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | We need something better than ProtoBuf 12 | 13 | ## Decision 14 | 15 | We checked the Cap'n'Proto documentation and examples on RPC. 16 | Seems to be a perfect match for our needs, so we started integrating it to our SDK for communication between client/server, replacing the previous ProtoBuf. 17 | 18 | ## Consequences 19 | 20 | - -------------------------------------------------------------------------------- /mercury-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mercury-wasm" 3 | version = "0.1.0" 4 | authors = ["Rache Bartmoss "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | did = { path = "../did" } 11 | keyvault = { path="../keyvault" } 12 | keyvault-wasm = { path = "../keyvault-wasm" } 13 | serde_json = "*" 14 | wasm-bindgen = { version = "*", features = ["serde-serialize"] } -------------------------------------------------------------------------------- /test/createdb.sh: -------------------------------------------------------------------------------- 1 | dropdb testdb 2 | createdb testdb 3 | psql testdb -c "DROP USER IF EXISTS testuser" 4 | psql testdb -c "CREATE USER testuser PASSWORD 'testpass'" 5 | # su -u postgres createuser testuser 6 | psql testdb -c "GRANT ALL PRIVILEGES ON DATABASE testdb TO testuser" 7 | 8 | psql testdb -f test/testdb.sql 9 | 10 | psql testdb -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO testuser" 11 | psql testdb -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO testuser" 12 | -------------------------------------------------------------------------------- /prometheus-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prometheus-cli" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | claims = { path="../claims" } 9 | did = { path="../did" } 10 | failure = "*" 11 | keyvault = { path="../keyvault" } 12 | log = "*" 13 | log4rs = "*" 14 | prometheus = { path="../prometheus" } 15 | serde = "*" 16 | serde_derive = "*" 17 | serde_json = "*" 18 | structopt = "*" 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | All contributions are welcome! We follow this 2 | [code of conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct) in Mercury. 3 | Regarding pull requests, please check and make sure that you agree to the following 4 | [Developer's Certificate of Origin 1.1](https://elinux.org/Developer_Certificate_Of_Origin) 5 | policy we use. 6 | 7 | This basically means that you pledge yourself to be kind with others. 8 | You also state that you own the patches you send and you give them to us. ;-) 9 | -------------------------------------------------------------------------------- /keyvault/src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::Seed; 2 | 3 | const fn as_bytes(bits: usize) -> usize { 4 | bits / 8 5 | } 6 | 7 | #[test] 8 | fn seed_from_bytes_accepts_512_bits() { 9 | let bytes = [0u8; as_bytes(Seed::BITS)]; // 512 bits 10 | let seed_res = Seed::from_bytes(&bytes); 11 | assert!(seed_res.is_ok()); 12 | } 13 | 14 | #[test] 15 | fn seed_from_bytes_rejects_not_512_bits() { 16 | let bytes = [0u8; 32]; // 256 bits 17 | let seed_res = Seed::from_bytes(&bytes); 18 | assert!(seed_res.unwrap_err().to_string().contains("-bit seed")); 19 | } 20 | -------------------------------------------------------------------------------- /connect/src/jsonrpc/api.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use mercury_home_protocol::*; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] 5 | pub struct GetSessionRequest { 6 | pub application_id: ApplicationId, 7 | pub permissions: Option, 8 | } 9 | 10 | #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] 11 | pub struct GetSessionResponse { 12 | pub profile_id: String, 13 | } 14 | 15 | #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] 16 | pub struct EventNotification { 17 | pub kind: String, 18 | } 19 | -------------------------------------------------------------------------------- /forgetfulfuse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "forgetfulfuse" 3 | version = "0.1.0" 4 | license = "GPL-3.0-or-later" 5 | authors = ["wigy "] 6 | edition = "2018" 7 | 8 | [badges] 9 | gitlab = { repository = "https://gitlab.libertaria.community/iop-stack/communication/prometheus" } 10 | github = { repository = "https://github.com/internet-of-people/prometheus" } 11 | maintenance = { status = "actively-developed" } 12 | 13 | [dependencies] 14 | failure = "0.1.5" 15 | fuse = "0.3.1" 16 | fuse_mt = "0.4.4" 17 | libc = "0.2" 18 | log = "0.4.6" 19 | log4rs = "0.8.1" 20 | structopt = "0.2.15" 21 | time = "0.1" 22 | timer = "0.2.0" 23 | users = "0.9.1" 24 | -------------------------------------------------------------------------------- /home-protocol/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod crypto; 3 | pub mod error; 4 | pub mod future; 5 | pub mod handshake; 6 | pub mod mercury_capnp; 7 | pub mod primitives; 8 | pub mod util; 9 | 10 | use std::rc::Rc; 11 | use std::time::Duration; 12 | 13 | use futures::prelude::*; 14 | use futures::sync::{mpsc, oneshot}; 15 | use log::*; 16 | use serde_derive::{Deserialize, Serialize}; 17 | 18 | pub use crate::api::*; 19 | pub use crate::crypto::Validator; 20 | pub(crate) use crate::error::*; 21 | pub use crate::primitives::*; 22 | pub use claims::repo::{ 23 | DistributedPublicProfileRepository, PrivateProfileRepository, ProfileExplorer, 24 | }; 25 | pub use did::*; 26 | pub use keyvault; 27 | pub use keyvault::ed25519; 28 | 29 | pub const CHANNEL_CAPACITY: usize = 1; 30 | -------------------------------------------------------------------------------- /storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mercury-storage" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | repository = "https://github.com/Internet-of-People/mercury-rust" 6 | keywords = ["crypto", "wallet", "iop", "mercury", "morpheus", "prometheus", "vault", "key-vault"] 7 | license = "LGPL-3.0+" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | arrayref = "*" 12 | byteorder = "*" 13 | failure = "*" 14 | futures = "0.1" 15 | futures-state-stream = "0.2" 16 | log = "*" 17 | mercury-home-protocol = { path="../home-protocol" } 18 | multibase = "0.6" 19 | multihash = "*" 20 | multiaddr = "*" 21 | serde = "1" 22 | serde_derive = "1" 23 | serde_json = "1" 24 | tokio = "0.1" 25 | tokio-current-thread = "0.1" -------------------------------------------------------------------------------- /home-node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mercury-home-node" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | repository = "https://github.com/Internet-of-People/mercury-rust" 6 | keywords = ["crypto", "wallet", "iop", "mercury", "morpheus", "prometheus", "vault", "key-vault"] 7 | license = "AGPL-3.0+" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | bincode = "*" 12 | claims = { path="../claims" } 13 | did = { path="../did" } 14 | failure = "*" 15 | futures = "0.1" 16 | log = "*" 17 | log4rs = "*" 18 | mercury-home-protocol = { path="../home-protocol" } 19 | mercury-storage = { path="../storage" } 20 | multiaddr = "*" 21 | structopt = "*" 22 | tokio = "0.1" 23 | tokio-current-thread = "0.1" 24 | toml = "*" 25 | -------------------------------------------------------------------------------- /doc/adr/0001-choosing-rust.md: -------------------------------------------------------------------------------- 1 | # 0001. Choosing Rust 2 | 3 | Date: 2017-10-17 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | IoP had a Mercury/Connect SDK before this, but it was implemented in Java/C# and it was not maintainable anymore. We had to decide if we want to maintain somehow or 12 | rewrite it from scratch. 13 | 14 | ## Decision 15 | 16 | The Rust language was chosen to be used based on it’s almost C level speed and rusts memory safety. 17 | The language also possesses really good bindings. Basically you can bind any code written in C into Rust. 18 | While Rust is still in its early years, it’s growing steadily, and it also has a good, stable, and growing community. 19 | 20 | ## Consequences 21 | 22 | As we rewrite it from scratch, we have the chance to change the architecture. -------------------------------------------------------------------------------- /examples/TheButton/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "the_button" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | repository = "https://github.com/Internet-of-People/mercury-rust" 6 | keywords = ["crypto", "wallet", "iop", "mercury", "morpheus", "prometheus", "vault", "key-vault"] 7 | license = "LGPL-3.0+" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | claims = { path="../../claims" } 12 | did = { path="../../did" } 13 | failure = "*" 14 | futures = "0.1" 15 | log = "*" 16 | log4rs = "*" 17 | mercury-home-protocol = { path="../../home-protocol" } 18 | multiaddr = "*" 19 | multibase = "*" 20 | prometheus = { path="../../prometheus" } 21 | structopt = "*" 22 | tempdir = "*" 23 | tokio = "0.1" 24 | tokio-current-thread = "0.1" 25 | tokio-signal = "0.2" -------------------------------------------------------------------------------- /test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mercury-test" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | repository = "https://github.com/Internet-of-People/mercury-rust" 6 | keywords = ["crypto", "wallet", "iop", "mercury", "morpheus", "prometheus", "vault", "key-vault"] 7 | license = "LGPL-3.0+" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | base64 = "0.9.0" 12 | capnp = "*" 13 | capnp-rpc = "*" 14 | claims = { path="../claims" } 15 | did = { path="../did" } 16 | ed25519-dalek = "1.0.0-pre.1" 17 | futures = "0.1" 18 | memsocket = "*" 19 | mercury-home-protocol = { path="../home-protocol" } 20 | mercury-connect = { path="../connect" } 21 | mercury-home-node = { path="../home-node" } 22 | multiaddr = "*" 23 | multihash = "*" 24 | rand = "0.6" 25 | sha2 = "*" 26 | tokio = "0.1" 27 | tokio-current-thread = "0.1" 28 | -------------------------------------------------------------------------------- /examples/TheButton/log4rs.yml: -------------------------------------------------------------------------------- 1 | # Scan this file for changes every 30 seconds 2 | refresh_rate: 30 seconds 3 | 4 | appenders: 5 | # An appender named "stdout" that writes to stdout 6 | stdout: 7 | kind: console 8 | encoder: 9 | pattern: "{l} {t}:{L} - {m}{n}" 10 | 11 | # An appender named "file" that writes to a file with a custom pattern encoder 12 | file: 13 | kind: file 14 | path: "log/button.log" 15 | encoder: 16 | pattern: "{l} {t}:{L} - {m}{n}" 17 | 18 | # Set the default logging level and attach appenders to the root 19 | root: 20 | level: debug 21 | appenders: 22 | - file 23 | - stdout 24 | 25 | loggers: 26 | # Suppress fine-grained debug info from the Tokio reactor 27 | tokio_core::reactor: 28 | level: info 29 | tokio_reactor: 30 | level: info 31 | tokio_threadpool: 32 | level: info 33 | mio: 34 | level: info 35 | -------------------------------------------------------------------------------- /prometheus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prometheus" 3 | version = "0.1.0" 4 | authors = ["Rache Bartmoss "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix = "*" 9 | actix-rt = "0.2" 10 | actix-cors = "*" 11 | actix-http = { version = "*", features = ["rust-tls"] } 12 | # TODO use latest actix 13 | actix-server = "0.6" 14 | actix-web = "*" 15 | actix-web-actors = "*" 16 | base64 = "*" 17 | blockies = "*" 18 | bytes = "0.4" 19 | claims = { path="../claims" } 20 | did = { path="../did" } 21 | failure = "*" 22 | futures = "0.1" 23 | mercury-home-protocol = { path="../home-protocol" } 24 | keyvault = { path="../keyvault" } 25 | log = "*" 26 | log4rs = "*" 27 | multiaddr = "*" 28 | multibase = "*" 29 | rand = "*" 30 | rand_chacha = "*" 31 | regex = "*" 32 | serde = "*" 33 | serde_derive = "*" 34 | serde_json = "*" 35 | structopt = "*" 36 | tokio = "0.1" 37 | tokio-current-thread = "0.1" 38 | 39 | [dev-dependencies] 40 | tempfile = "*" -------------------------------------------------------------------------------- /prometheus/src/bin/prometheusd.rs: -------------------------------------------------------------------------------- 1 | use failure::Fallible; 2 | use log::*; 3 | use structopt::StructOpt; 4 | 5 | use prometheus::{init_logger, Daemon, Options}; 6 | 7 | fn main() { 8 | match run() { 9 | Ok(()) => {} 10 | Err(e) => error!("Failed with error: {}", e), 11 | } 12 | } 13 | 14 | fn run() -> Fallible<()> { 15 | let options = Options::from_args(); 16 | 17 | init_logger(&options)?; 18 | 19 | let daemon = Daemon::start(options)?; 20 | 21 | // let registry = prometheus::ClaimSchemaRegistry::import_folder(&std::path::PathBuf::from("./schemas"))?; 22 | // for (_k, v) in registry.schemas { 23 | // info!("***\n{:#?}\n***", v); 24 | // } 25 | 26 | // NOTE HTTP server already handles signals internally unless the no_signals option is set. 27 | match daemon.join() { 28 | Err(e) => info!("Daemon thread failed with error: {:?}", e), 29 | Ok(_) => info!("Graceful shut down"), 30 | }; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /prometheus/build.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::prelude::*; 3 | use std::io::{self, BufReader, BufWriter}; 4 | use std::path::Path; 5 | 6 | fn main() -> io::Result<()> { 7 | // NOTE generating vocabulary during build caused CI caching problems, so added files to git instead 8 | // generate(Path::new("vocabulary/adjectives.txt"), "ADJECTIVES", Path::new("src/names/adjectives.rs"))?; 9 | // generate(Path::new("vocabulary/nouns.txt"), "NOUNS", Path::new("src/names/nouns.rs"))?; 10 | Ok(()) 11 | } 12 | 13 | fn generate(src_path: &Path, identifier: &str, dst_path: &Path) -> io::Result<()> { 14 | let src = BufReader::new(File::open(src_path)?); 15 | let mut dst = BufWriter::new(File::create(dst_path)?); 16 | writeln!(dst, "// File generated from \"{}\", do not modify.", src_path.to_string_lossy())?; 17 | writeln!(dst, "pub const {}: &'static [&'static str] = &[", identifier)?; 18 | for word in src.lines() { 19 | writeln!(dst, " \"{}\",", &word?)?; 20 | } 21 | writeln!(dst, "];") 22 | } 23 | -------------------------------------------------------------------------------- /home-protocol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mercury-home-protocol" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | repository = "https://github.com/Internet-of-People/mercury-rust" 6 | keywords = ["crypto", "wallet", "iop", "mercury", "morpheus", "prometheus", "vault", "key-vault"] 7 | license = "LGPL-3.0+" 8 | edition = "2018" 9 | 10 | [build-dependencies] 11 | capnpc = "*" 12 | 13 | [dependencies] 14 | bincode = "*" 15 | # TODO update bytes and capnp to latest versions 16 | bytes = "0.4" 17 | capnp = "0.10" 18 | capnp-rpc = "0.10" 19 | claims = { path="../claims" } 20 | did = { path="../did" } 21 | failure= "*" 22 | futures = "0.1" 23 | keyvault = { path="../keyvault" } 24 | log = "*" 25 | multiaddr = "*" 26 | multibase = "*" 27 | multihash = "*" 28 | serde = "*" 29 | serde_derive = "*" 30 | serde_json = "*" 31 | structopt = "*" 32 | tokio = "0.1" 33 | tokio-current-thread = "0.1" 34 | toml = "*" 35 | 36 | #x25519-dalek = { version="*", default-features=false, features=["std"] } 37 | -------------------------------------------------------------------------------- /connect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mercury-connect" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "Rache Bartmoss "] 5 | repository = "https://github.com/Internet-of-People/mercury-rust" 6 | keywords = ["crypto", "wallet", "iop", "mercury", "morpheus", "prometheus", "vault", "key-vault"] 7 | license = "LGPL-3.0+" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | clap = {version = "*", features = ["yaml"]} 12 | claims = { path="../claims" } 13 | did = { path="../did" } 14 | failure = "*" 15 | futures = "0.1" 16 | jsonrpc-core = { git = "https://github.com/Internet-of-People/jsonrpc" } 17 | jsonrpc-pubsub = { git = "https://github.com/Internet-of-People/jsonrpc" } 18 | log = "*" 19 | log4rs = "*" 20 | mercury-home-protocol = { path="../home-protocol" } 21 | mercury-storage = { path="../storage" } 22 | multiaddr = "*" 23 | serde = "*" 24 | serde_derive = "*" 25 | serde_json = "*" 26 | state_machine_future = "*" 27 | structopt = "*" 28 | tokio-codec = "*" 29 | tokio-core = "*" 30 | tokio-io = "*" 31 | tokio-uds = "0.1" 32 | -------------------------------------------------------------------------------- /keyvault/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keyvault" 3 | version = "0.1.0" 4 | authors = ["IoP Ventures LLC ", "wigy "] 5 | license = "LGPL-3.0-or-later" 6 | repository = "https://github.com/internet-of-people/prometheus" 7 | keywords = ["crypto", "wallet", "iop", "mercury", "morpheus", "prometheus", "vault", "key-vault"] 8 | edition = "2018" 9 | 10 | [dependencies] 11 | base-x = "0.2" 12 | blake2 = "0.8.0" 13 | digest = "0.8.0" 14 | ed25519-dalek = "1.0.0-pre.1" 15 | failure = "0.1.5" 16 | hex = "0.3.2" 17 | hmac = "0.7.0" 18 | # Unfortunately the crate name of the libsecp256k1 collides with the perfect module name 19 | # for the cipher, so we rename the dependency here: 20 | secp = { version = "0.2.2", package = "libsecp256k1" } 21 | log = "0.4.6" 22 | multibase = "0.6.0" 23 | ripemd160 = "0.8.0" 24 | serde = { version = "1.0.87", features = ["derive"] } 25 | serde_bytes = "0.10.4" 26 | sha2 = "0.8.0" 27 | tiny-bip39 = { version = "0.6.1", default-features = false } 28 | 29 | [dev-dependencies] 30 | rmp-serde = "*" 31 | serde_json = "*" -------------------------------------------------------------------------------- /keyvault/src/multicipher/sk.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | erased_type! { 4 | /// Type-erased [`PrivateKey`] 5 | /// 6 | /// [`PrivateKey`]: ../trait.AsymmetricCrypto.html#associatedtype.PrivateKey 7 | #[derive(Debug)] 8 | pub struct MPrivateKey {} 9 | } 10 | 11 | macro_rules! public_key { 12 | ($suite:ident, $self_:tt) => {{ 13 | let result = reify!($suite, sk, $self_).public_key(); 14 | erase!($suite, MPublicKey, result) 15 | }}; 16 | } 17 | 18 | macro_rules! sign { 19 | ($suite:ident, $self_:tt, $data:ident) => {{ 20 | let result = reify!($suite, sk, $self_).sign($data); 21 | erase!($suite, MSignature, result) 22 | }}; 23 | } 24 | 25 | impl PrivateKey for MPrivateKey { 26 | fn public_key(&self) -> MPublicKey { 27 | visit!(public_key(self)) 28 | } 29 | fn sign>(&self, data: D) -> MSignature { 30 | visit!(sign(self, data)) 31 | } 32 | } 33 | 34 | impl From for MPrivateKey { 35 | fn from(src: EdPrivateKey) -> Self { 36 | erase!(e, MPrivateKey, src) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /keyvault/src/bip39.rs: -------------------------------------------------------------------------------- 1 | use bip39::{Language, Mnemonic, MnemonicType, Seed}; 2 | use failure::Fallible; 3 | 4 | /// Re-exported error type used by `tiny-bip39` to allow downcasting `failure` error results. 5 | pub use bip39::ErrorKind as Bip39ErrorKind; 6 | 7 | /// #Panics 8 | /// If words is not in {12, 15, 18, 21, 24} 9 | pub(crate) fn generate_new_phrase(words: usize) -> String { 10 | let mnemonic = Mnemonic::new(MnemonicType::for_word_count(words).unwrap(), Language::English); 11 | mnemonic.into_phrase() 12 | } 13 | 14 | pub(crate) fn generate_new(password: &str) -> Vec { 15 | let mnemonic = Mnemonic::new(MnemonicType::Words24, Language::English); 16 | Seed::new(&mnemonic, password).as_bytes().to_owned() 17 | } 18 | 19 | pub(crate) fn from_phrase>(phrase: S, password: &str) -> Fallible> { 20 | let mnemonic_res = Mnemonic::from_phrase(phrase.as_ref(), Language::English); 21 | mnemonic_res.map(|m| Seed::new(&m, password).as_bytes().to_owned()) 22 | } 23 | 24 | pub(crate) fn check_word(word: &str) -> bool { 25 | Language::English.wordmap().get_bits(word).is_ok() 26 | } 27 | -------------------------------------------------------------------------------- /connect/log4rs.yml: -------------------------------------------------------------------------------- 1 | # Scan this file for changes every 30 seconds 2 | refresh_rate: 30 seconds 3 | 4 | appenders: 5 | # An appender named "stdout" that writes to stdout 6 | stdout: 7 | kind: console 8 | 9 | # An appender named "file" that writes to a file with a custom pattern encoder 10 | file: 11 | kind: file 12 | path: "log/service.log" 13 | encoder: 14 | pattern: "{d} - {m}{n}" 15 | 16 | # Set the default logging level and attach appenders to the root 17 | root: 18 | level: debug 19 | appenders: 20 | - file 21 | - stdout 22 | 23 | loggers: 24 | # Suppress fine-grained debug info from the Tokio reactor 25 | tokio_core::reactor: 26 | level: info 27 | tokio_reactor: 28 | level: info 29 | 30 | #loggers: 31 | ## Raise the maximum log level for events sent to the "app::backend::db" logger to "info" 32 | #app::backend::db: 33 | # level: info 34 | 35 | ## Route log events sent to the "app::requests" logger to the "requests" appender, 36 | ## and *not* the normal appenders installed at the root 37 | #app::requests: 38 | # level: info 39 | # appenders: 40 | # - requests 41 | # additive: false 42 | -------------------------------------------------------------------------------- /home-node/log4rs.yml: -------------------------------------------------------------------------------- 1 | # Scan this file for changes every 30 seconds 2 | refresh_rate: 30 seconds 3 | 4 | appenders: 5 | # An appender named "stdout" that writes to stdout 6 | stdout: 7 | kind: console 8 | 9 | # An appender named "file" that writes to a file with a custom pattern encoder 10 | file: 11 | kind: file 12 | path: "log/home.log" 13 | encoder: 14 | pattern: "{d} - {m}{n}" 15 | 16 | # Set the default logging level and attach appenders to the root 17 | root: 18 | level: debug 19 | appenders: 20 | - file 21 | - stdout 22 | 23 | loggers: 24 | # Suppress fine-grained debug info from the Tokio reactor 25 | tokio_core::reactor: 26 | level: info 27 | tokio_reactor: 28 | level: info 29 | 30 | #loggers: 31 | ## Raise the maximum log level for events sent to the "app::backend::db" logger to "info" 32 | #app::backend::db: 33 | # level: info 34 | 35 | ## Route log events sent to the "app::requests" logger to the "requests" appender, 36 | ## and *not* the normal appenders installed at the root 37 | #app::requests: 38 | # level: info 39 | # appenders: 40 | # - requests 41 | # additive: false 42 | -------------------------------------------------------------------------------- /prometheus/src/dapp/websocket/routes.rs: -------------------------------------------------------------------------------- 1 | use actix::{Actor, StreamHandler}; 2 | use actix_web::{web, Error, HttpRequest, HttpResponse}; 3 | use actix_web_actors::ws; 4 | 5 | pub fn init_url_mapping(service: &mut web::ServiceConfig) { 6 | service.service( 7 | web::scope("/dapp").service(web::resource("").route(web::get().to(redirect_to_ws))), 8 | ); 9 | } 10 | 11 | /// Define http actor 12 | struct DAppWebSocketServer { 13 | counter: u32, 14 | } 15 | 16 | impl Actor for DAppWebSocketServer { 17 | type Context = ws::WebsocketContext; 18 | } 19 | 20 | /// Handler for ws::Message message 21 | impl StreamHandler for DAppWebSocketServer { 22 | fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { 23 | match msg { 24 | ws::Message::Text(text) => ctx.text(format!("{}: {}", self.counter.to_string(), text)), 25 | _ => (), 26 | }; 27 | self.counter += 1; 28 | } 29 | } 30 | 31 | fn redirect_to_ws(req: HttpRequest, stream: web::Payload) -> Result { 32 | let resp = ws::start(DAppWebSocketServer { counter: 0 }, &req, stream); 33 | println!("{:?}", resp); 34 | resp 35 | } 36 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Thanks to the following blog post we are caching cargo 2 | # http://blog.wjlr.org.uk/2016/08/16/fast-rust-gitlab-ci.html 3 | 4 | # This file is a template, and might need editing before it works on your project. 5 | image: "rust" 6 | 7 | # To ensure that dependencies are cached correctly 8 | variables: 9 | CARGO_HOME: $CI_PROJECT_DIR/cargo 10 | 11 | # Use cargo to test the project 12 | test:cargo: 13 | before_script: 14 | - apt-get update 15 | - apt-get install -y capnproto libfuse-dev 16 | script: 17 | - du -hs target || true 18 | - du -hs cargo || true 19 | - rustup toolchain list || true 20 | - rustup component add rustfmt || true 21 | - rustc --version && cargo --version # Print version info for debugging 22 | - export RUST_BACKTRACE=1 23 | - time cargo build --all --all-targets --verbose --release # --all builds whole workspace, --all-targets builds bin,lib,examples,tests,benchmarks 24 | # cargo fmt fails if modules generated by build.rs are not there yet 25 | - time cargo fmt --all -- --check 26 | - time cargo test --all --verbose --jobs 1 --release # Don't parallelize to make errors more readable 27 | cache: 28 | paths: 29 | - target/ 30 | - cargo/ 31 | -------------------------------------------------------------------------------- /did/src/paths.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use failure::{err_msg, Fallible}; 4 | 5 | const BASEDIR_DETECTION_ERROR: &str = 6 | "Failed to detect platform-dependent default directory for profile management"; 7 | 8 | fn default_dir() -> Option { 9 | dirs::config_dir().map(|dir| dir.join("prometheus")) 10 | } 11 | 12 | pub fn vault_path(parent_dir: Option) -> Fallible { 13 | let parent_dir = parent_dir.or_else(default_dir); 14 | parent_dir.map(|base| base.join("vault.dat")).ok_or_else(|| err_msg(BASEDIR_DETECTION_ERROR)) 15 | } 16 | 17 | pub fn profile_repo_path(parent_dir: Option) -> Fallible { 18 | let parent_dir = parent_dir.or_else(default_dir); 19 | parent_dir.map(|base| base.join("profiles.dat")).ok_or_else(|| err_msg(BASEDIR_DETECTION_ERROR)) 20 | } 21 | 22 | pub fn base_repo_path(parent_dir: Option) -> Fallible { 23 | let parent_dir = parent_dir.or_else(default_dir); 24 | parent_dir.map(|base| base.join("bases.dat")).ok_or_else(|| err_msg(BASEDIR_DETECTION_ERROR)) 25 | } 26 | 27 | pub fn schemas_path(schemas_dir: Option) -> Fallible { 28 | schemas_dir 29 | .or_else(|| default_dir().map(|base| base.join("schemas"))) 30 | .ok_or_else(|| err_msg(BASEDIR_DETECTION_ERROR)) 31 | } 32 | -------------------------------------------------------------------------------- /forgetfulfuse/src/main.rs: -------------------------------------------------------------------------------- 1 | mod fs; 2 | 3 | use std::ffi::OsStr; 4 | 5 | use failure::{err_msg, Fallible}; 6 | use log::*; 7 | 8 | use fs::ForgetfulFS; 9 | 10 | fn init_log(level_filter: LevelFilter) -> Fallible<()> { 11 | use log4rs::append::console::ConsoleAppender; 12 | use log4rs::config::{Appender, Config, Root}; 13 | use log4rs::encode::pattern::PatternEncoder; 14 | 15 | let stdout = 16 | ConsoleAppender::builder().encoder(Box::new(PatternEncoder::new("{h({m}{n})}"))).build(); 17 | let config = Config::builder() 18 | .appender(Appender::builder().build("stdout", Box::new(stdout))) 19 | .build(Root::builder().appender("stdout").build(level_filter))?; 20 | 21 | log4rs::init_config(config)?; 22 | Ok(()) 23 | } 24 | 25 | fn main() -> Fallible<()> { 26 | init_log(LevelFilter::Trace)?; 27 | 28 | let args: Vec<_> = std::env::args().collect(); 29 | if args.len() < 2 { 30 | return Err(err_msg("Not enough parameters")); 31 | } 32 | 33 | let mount = &args[1]; 34 | info!("forgetfulfs {}", mount); 35 | let fs = ForgetfulFS::new(users::get_current_uid(), users::get_current_gid()); 36 | let fs_mt = fuse_mt::FuseMT::new(fs, 2); 37 | let options = [OsStr::new("-o"), OsStr::new("auto_unmount,default_permissions,noatime")]; 38 | fuse_mt::mount(fs_mt, mount, &options[..]).map_err(|e| e.into()) 39 | } 40 | -------------------------------------------------------------------------------- /home-protocol/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, env, fs}; 2 | 3 | use structopt::StructOpt; 4 | 5 | use crate::*; 6 | 7 | pub fn parse_config(config_path: &str) -> T { 8 | let cli_args = env::args().collect::>(); 9 | let file_args = read_config_file(config_path).unwrap_or(Vec::new()); // TODO should continue if cfg file was not specified but fail otherwise 10 | 11 | let all_args = cli_args.iter().chain(file_args.iter()); 12 | // println!("File contents: {:?}", all_args.collect::>() ); 13 | 14 | let matches = T::clap().get_matches_from(all_args); 15 | T::from_clap(&matches) 16 | } 17 | 18 | fn read_config_file(config_path: &str) -> Result, ()> { 19 | let file_contents = fs::read_to_string(config_path) 20 | .map_err(|e| warn!("Error reading config file {}: {}", config_path, e))?; 21 | 22 | let file_keyvals: HashMap = toml::from_str(&file_contents).map_err(|e| { 23 | error!("Error parsing file {}: {}", config_path, e); 24 | warn!("Note that only `key = 'value'` format is supported in the config file"); 25 | })?; 26 | 27 | // println!("File contents: {:?}", file_args); 28 | let file_args = file_keyvals 29 | .iter() 30 | .flat_map(|(key, value)| vec![format!("--{}", key), value.to_owned()]) 31 | .collect(); 32 | Ok(file_args) 33 | } 34 | -------------------------------------------------------------------------------- /keyvault/src/cc.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// Size of the chain code in bytes 4 | pub const CHAIN_CODE_SIZE: usize = 32; 5 | 6 | /// Chain code for key derivation in extended private and public keys. 7 | /// This is a 256-bit secret key that is completely independent of the private 8 | /// key and is used as an extension to the cryptographic domain, basically an 9 | /// extra state during iteration. 10 | #[derive(Clone)] 11 | pub struct ChainCode([u8; CHAIN_CODE_SIZE]); 12 | 13 | impl ChainCode { 14 | /// The chain code serialized in a format that can be fed to [`from_bytes`] 15 | /// 16 | /// [`from_bytes`]: #method.from_bytes 17 | pub fn to_bytes(&self) -> [u8; CHAIN_CODE_SIZE] { 18 | self.0 19 | } 20 | 21 | /// Creates a chain code from a byte slice possibly returned by the [`to_bytes`] method. 22 | /// 23 | /// # Error 24 | /// If `bytes` is not [`CHAIN_CODE_SIZE`] long 25 | /// 26 | /// [`to_bytes`]: #method.to_bytes 27 | /// [`CHAIN_CODE_SIZE`]: ../constant.CHAIN_CODE_SIZE 28 | pub fn from_bytes>(bytes: D) -> Fallible { 29 | let bytes = bytes.as_ref(); 30 | ensure! { 31 | bytes.len() == CHAIN_CODE_SIZE, 32 | "Chain code length is not {}", 33 | CHAIN_CODE_SIZE 34 | } 35 | let mut cc = [0u8; CHAIN_CODE_SIZE]; 36 | cc.copy_from_slice(bytes); 37 | Ok(Self(cc)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /prometheus/src/test/mod.rs: -------------------------------------------------------------------------------- 1 | use failure::err_msg; 2 | use futures::IntoFuture; 3 | 4 | use crate::dapp::user_interactor::{DAppAction, UserInteractor}; 5 | use did::model::{AsyncFallible, ProfileId}; 6 | use mercury_home_protocol::{RelationHalfProof, RelationProof}; 7 | 8 | pub struct FakeUserInteractor { 9 | active_profile: Option, 10 | } 11 | 12 | impl FakeUserInteractor { 13 | pub fn new() -> Self { 14 | Self { active_profile: Default::default() } 15 | } 16 | 17 | pub fn set_active_profile(&mut self, profile_id: Option) { 18 | self.active_profile = profile_id; 19 | } 20 | } 21 | 22 | impl UserInteractor for FakeUserInteractor { 23 | fn initialize(&self) -> AsyncFallible<()> { 24 | Box::new(Ok(()).into_future()) 25 | } 26 | 27 | fn confirm_dappaction(&self, _action: &DAppAction) -> AsyncFallible<()> { 28 | Box::new(Ok(()).into_future()) 29 | } 30 | 31 | fn confirm_pairing(&self, _request: &RelationHalfProof) -> AsyncFallible<()> { 32 | Box::new(Ok(()).into_future()) 33 | } 34 | 35 | fn notify_pairing(&self, _response: &RelationProof) -> AsyncFallible<()> { 36 | Box::new(Ok(()).into_future()) 37 | } 38 | 39 | fn select_profile(&self) -> AsyncFallible { 40 | let profile_id = self.active_profile.to_owned().ok_or(err_msg("No profile was selected")); 41 | Box::new(profile_id.into_future()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /prometheus/src/dapp/websocket/client.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use futures::{future::ok, Stream}; 4 | 5 | use crate::dapp::dapp_session::*; 6 | use did::model::ProfileId; 7 | use keyvault::multicipher::MKeyId; 8 | use mercury_home_protocol::{ApplicationId, AsyncFallible}; 9 | 10 | pub struct ServiceClient {} 11 | 12 | impl ServiceClient { 13 | pub fn new() -> Self { 14 | Self {} 15 | } 16 | } 17 | 18 | impl DAppSessionService for ServiceClient { 19 | fn dapp_session(&self, _app: ApplicationId) -> AsyncFallible> { 20 | // TODO 21 | Box::new(ok(Arc::new(SessionClient {}) as Arc)) 22 | } 23 | } 24 | 25 | pub struct SessionClient {} 26 | 27 | impl DAppSession for SessionClient { 28 | fn dapp_id(&self) -> &ApplicationId { 29 | unimplemented!() 30 | } 31 | 32 | fn selected_profile(&self) -> &MKeyId { 33 | unimplemented!() 34 | } 35 | 36 | fn relations(&self) -> AsyncFallible>> { 37 | unimplemented!() 38 | } 39 | 40 | fn relation(&self, _id: &MKeyId) -> AsyncFallible>> { 41 | unimplemented!() 42 | } 43 | 44 | fn initiate_relation(&self, _with_profile: &ProfileId) -> AsyncFallible<()> { 45 | unimplemented!() 46 | } 47 | 48 | fn checkin(&self) -> AsyncFallible>> { 49 | unimplemented!() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /prometheus/src/dapp/websocket/server.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use futures::{future::ok, Stream}; 4 | 5 | use crate::dapp::dapp_session::*; 6 | use did::model::ProfileId; 7 | use keyvault::multicipher::MKeyId; 8 | use mercury_home_protocol::{ApplicationId, AsyncFallible}; 9 | 10 | pub struct ServiceServer {} 11 | 12 | impl ServiceServer { 13 | pub fn new() -> Self { 14 | Self {} 15 | } 16 | } 17 | 18 | impl DAppSessionService for ServiceServer { 19 | fn dapp_session(&self, _app: ApplicationId) -> AsyncFallible> { 20 | // TODO 21 | Box::new(ok(Arc::new(SessionServer {}) as Arc)) 22 | } 23 | } 24 | 25 | pub struct SessionServer {} 26 | 27 | impl DAppSession for SessionServer { 28 | fn dapp_id(&self) -> &ApplicationId { 29 | unimplemented!() 30 | } 31 | 32 | fn selected_profile(&self) -> &MKeyId { 33 | unimplemented!() 34 | } 35 | 36 | fn relations(&self) -> AsyncFallible>> { 37 | unimplemented!() 38 | } 39 | 40 | fn relation(&self, _id: &MKeyId) -> AsyncFallible>> { 41 | unimplemented!() 42 | } 43 | 44 | fn initiate_relation(&self, _with_profile: &ProfileId) -> AsyncFallible<()> { 45 | unimplemented!() 46 | } 47 | 48 | fn checkin(&self) -> AsyncFallible>> { 49 | unimplemented!() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/network/btc.rs: -------------------------------------------------------------------------------- 1 | use crate::secp256k1::Network; 2 | 3 | /// Strategies for the BTC, BCC and BSV main (production) network. 4 | pub struct Mainnet; 5 | 6 | impl Network for Mainnet { 7 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 8 | b"\x00" 9 | } 10 | fn p2sh_addr(&self) -> &'static [u8; 1] { 11 | b"\x05" 12 | } 13 | fn wif(&self) -> &'static [u8; 1] { 14 | b"\x80" 15 | } 16 | fn bip32_xpub(&self) -> &'static [u8; 4] { 17 | b"\x04\x88\xB2\x1E" 18 | } 19 | fn bip32_xprv(&self) -> &'static [u8; 4] { 20 | b"\x04\x88\xAD\xE4" 21 | } 22 | fn message_prefix(&self) -> &'static str { 23 | "\x18Bitcoin Signed Message:\n" 24 | } 25 | fn slip44(&self) -> i32 { 26 | 0 27 | } 28 | } 29 | 30 | /// Strategies for the BTC, BCC and BSV test (staging) network. 31 | pub struct Testnet; 32 | 33 | impl Network for Testnet { 34 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 35 | b"\x6F" 36 | } 37 | fn p2sh_addr(&self) -> &'static [u8; 1] { 38 | b"\xC4" 39 | } 40 | fn wif(&self) -> &'static [u8; 1] { 41 | b"\xEF" 42 | } 43 | fn bip32_xpub(&self) -> &'static [u8; 4] { 44 | b"\x04\x35\x87\xCF" 45 | } 46 | fn bip32_xprv(&self) -> &'static [u8; 4] { 47 | b"\x04\x35\x83\x94" 48 | } 49 | fn message_prefix(&self) -> &'static str { 50 | "\x18Bitcoin Signed Message:\n" 51 | } 52 | fn slip44(&self) -> i32 { 53 | 1 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /prometheus/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod daemon; 2 | pub mod dapp; 3 | pub mod home; 4 | pub mod names; 5 | pub mod options; 6 | pub mod test; 7 | pub mod vault; 8 | 9 | use std::sync::{Arc, Mutex, MutexGuard, RwLock}; 10 | use std::time::Duration; 11 | 12 | use actix_cors::Cors; 13 | use actix_server::Server; 14 | use actix_web::{middleware, web, App, HttpResponse, HttpServer}; 15 | use failure::{err_msg, Fallible}; 16 | use log::*; 17 | 18 | pub use crate::daemon::{Daemon, DaemonState}; 19 | pub use crate::options::Options; 20 | use crate::vault::api::*; 21 | pub use crate::vault::api_data::*; 22 | use claims::repo::*; 23 | use did::vault::*; 24 | 25 | pub fn init_logger(options: &Options) -> Fallible<()> { 26 | if log4rs::init_file(&options.logger_config, Default::default()).is_err() { 27 | println!( 28 | "Failed to initialize loggers from {:?}, using default config", 29 | options.logger_config 30 | ); 31 | 32 | use log4rs::append::console::ConsoleAppender; 33 | use log4rs::config::{Appender, Config, Root}; 34 | use log4rs::encode::pattern::PatternEncoder; 35 | 36 | let stdout = 37 | ConsoleAppender::builder().encoder(Box::new(PatternEncoder::new("{m}{n}"))).build(); 38 | let config = Config::builder() 39 | .appender(Appender::builder().build("stdout", Box::new(stdout))) 40 | .build(Root::builder().appender("stdout").build(LevelFilter::Info))?; 41 | 42 | log4rs::init_config(config)?; 43 | }; 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/network/iop.rs: -------------------------------------------------------------------------------- 1 | use crate::secp256k1::Network; 2 | 3 | /// Strategies for the IOP main (production) network. 4 | pub struct Mainnet; 5 | 6 | impl Network for Mainnet { 7 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 8 | b"\x75" // 115 9 | } 10 | fn p2sh_addr(&self) -> &'static [u8; 1] { 11 | b"\xAE" // 174 12 | } 13 | fn wif(&self) -> &'static [u8; 1] { 14 | b"\x31" // 49 15 | } 16 | fn bip32_xpub(&self) -> &'static [u8; 4] { 17 | b"\x27\x80\x91\x5F" 18 | } 19 | fn bip32_xprv(&self) -> &'static [u8; 4] { 20 | b"\xAE\x34\x16\xF6" 21 | } 22 | fn message_prefix(&self) -> &'static str { 23 | "\x18IoP Signed Message:\n" 24 | } 25 | fn slip44(&self) -> i32 { 26 | 0x42 // 66 27 | } 28 | } 29 | 30 | /// Strategies for the BTC, BCC and BSV test (staging) network. 31 | pub struct Testnet; 32 | 33 | impl Network for Testnet { 34 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 35 | b"\x82" // 130 36 | } 37 | fn p2sh_addr(&self) -> &'static [u8; 1] { 38 | b"\x31" // 49 39 | } 40 | fn wif(&self) -> &'static [u8; 1] { 41 | b"\x4C" // 76 42 | } 43 | fn bip32_xpub(&self) -> &'static [u8; 4] { 44 | b"\xBB\x8F\x48\x52" 45 | } 46 | fn bip32_xprv(&self) -> &'static [u8; 4] { 47 | b"\x2B\x7F\xA4\x2A" 48 | } 49 | fn message_prefix(&self) -> &'static str { 50 | "\x18IoP SignedMessage:\n" 51 | } 52 | fn slip44(&self) -> i32 { 53 | 0x42 // 66 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /prometheus/src/options.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::path::PathBuf; 3 | 4 | use structopt::StructOpt; 5 | 6 | #[derive(Debug, StructOpt)] 7 | #[structopt( 8 | name = "prometheusd", 9 | about = "Prometheus service daemon", 10 | setting = structopt::clap::AppSettings::ColoredHelp 11 | )] 12 | pub struct Options { 13 | #[structopt(long = "listen", default_value = "127.0.0.1:8080", value_name = "IP:PORT")] 14 | /// IPv4/6 address to listen on serving REST requests. 15 | pub listen_on: SocketAddr, 16 | 17 | #[structopt(long, value_name = "DIR", parse(from_os_str))] 18 | /// Configuration directory to pick vault and profile info from. 19 | /// Default: OS-specific app_cfg_dir/prometheus 20 | pub config_dir: Option, 21 | 22 | #[structopt(long, value_name = "DIR", parse(from_os_str))] 23 | /// Directory that contains all claim schema definitions. 24 | /// Default: OS-specific app_cfg_dir/prometheus/schemas 25 | pub schemas_dir: Option, 26 | 27 | #[structopt(long = "repository", default_value = "127.0.0.1:6161", value_name = "IP:PORT")] 28 | /// IPv4/6 address of the remote profile repository. 29 | pub remote_repo_address: SocketAddr, 30 | 31 | #[structopt(long = "timeout", default_value = "10", value_name = "SECS")] 32 | /// Number of seconds used for network timeouts 33 | pub network_timeout_secs: u64, 34 | 35 | #[structopt(long, default_value = "log4rs.yml", value_name = "FILE", parse(from_os_str))] 36 | /// Config file for log4rs (YAML). 37 | pub logger_config: PathBuf, 38 | } 39 | -------------------------------------------------------------------------------- /prometheus-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | mod options; 2 | mod seed; 3 | 4 | use failure::Fallible; 5 | use log::*; 6 | use structopt::StructOpt; 7 | 8 | use crate::options::{Command, Options}; 9 | use prometheus::vault::http::client::VaultClient; 10 | 11 | fn main() { 12 | match run() { 13 | Ok(()) => {} 14 | Err(e) => eprintln!("Error: {}", e), 15 | }; 16 | } 17 | 18 | fn run() -> Fallible<()> { 19 | let options = Options::from_args(); 20 | init_logger(&options)?; 21 | 22 | let command = options.command; 23 | debug!("Got command {:?}", command); 24 | 25 | let mut client = VaultClient::new(&format!("http://{}", options.prometheus_address)); 26 | let command = Box::new(command); 27 | command.execute(&mut client) 28 | } 29 | 30 | fn init_logger(options: &Options) -> Fallible<()> { 31 | if log4rs::init_file(&options.logger_config, Default::default()).is_err() { 32 | println!( 33 | "Failed to initialize loggers from {:?}, using default config", 34 | options.logger_config 35 | ); 36 | 37 | use log4rs::append::console::ConsoleAppender; 38 | use log4rs::config::{Appender, Config, Root}; 39 | use log4rs::encode::pattern::PatternEncoder; 40 | 41 | let stdout = 42 | ConsoleAppender::builder().encoder(Box::new(PatternEncoder::new("{m}{n}"))).build(); 43 | let config = Config::builder() 44 | .appender(Appender::builder().build("stdout", Box::new(stdout))) 45 | .build(Root::builder().appender("stdout").build(LevelFilter::Info))?; 46 | 47 | log4rs::init_config(config)?; 48 | }; 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /prometheus/src/dapp/user_interactor.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use did::model::ProfileId; 4 | use mercury_home_protocol::{AsyncFallible, RelationHalfProof, RelationProof}; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] 7 | pub struct DAppAction(Vec); 8 | 9 | //#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] 10 | //pub struct DeviceAuthorization(Vec); 11 | 12 | // User interface (probably implemented with platform-native GUI) for actions 13 | // that are initiated by the SDK and require some kind of user interaction 14 | pub trait UserInteractor { 15 | // Initialize system components and configuration where user interaction is needed, 16 | // e.g. HD wallets need manually saving generated new seed or entering old one 17 | fn initialize(&self) -> AsyncFallible<()>; 18 | 19 | // An action requested by a distributed application needs 20 | // explicit user confirmation. 21 | // TODO how to show a human-readable summary of the action (i.e. binary to be signed) 22 | // making sure it's not a fake/misinterpreted description? 23 | fn confirm_dappaction(&self, action: &DAppAction) -> AsyncFallible<()>; 24 | 25 | fn confirm_pairing(&self, request: &RelationHalfProof) -> AsyncFallible<()>; 26 | 27 | fn notify_pairing(&self, response: &RelationProof) -> AsyncFallible<()>; 28 | 29 | // Select a profile to be used by a dApp. It can be either an existing one 30 | // or the user can create a new one (using a KeyVault) to be selected. 31 | // TODO this should open something nearly identical to manage_profiles() 32 | fn select_profile(&self) -> AsyncFallible; 33 | } 34 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/sig.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// The serialized byte representation for the current version of the signature algorithm 4 | pub const SIGNATURE_VERSION1: u8 = b'\x01'; 5 | 6 | /// Size of the signature is the version byte plus the actual libsecp256k1 signature size 7 | pub const SIGNATURE_SIZE: usize = secp::util::SIGNATURE_SIZE + VERSION_SIZE; 8 | 9 | /// Implementation of Secp256k1::Signature 10 | #[derive(Debug, Clone, Eq, PartialEq)] 11 | pub struct SecpSignature(pub(super) secp::Signature); 12 | 13 | impl SecpSignature { 14 | /// The signature serialized in a format that can be fed to [`from_bytes`] 15 | /// 16 | /// [`from_bytes`]: #method.from_bytes 17 | pub fn to_bytes(&self) -> Vec { 18 | let mut res = Vec::with_capacity(SIGNATURE_SIZE); 19 | res.push(SIGNATURE_VERSION1); 20 | res.extend_from_slice(&self.0.serialize()[..]); 21 | res 22 | } 23 | 24 | /// Creates a signature from a byte slice possibly returned by the [`to_bytes`] method. 25 | /// 26 | /// # Error 27 | /// If `bytes` is rejected by `libsecp256k1::Signature::parse` 28 | /// 29 | /// [`to_bytes`]: #method.to_bytes 30 | pub fn from_bytes>(bytes: D) -> Fallible { 31 | let bytes = bytes.as_ref(); 32 | ensure!(bytes.len() == SIGNATURE_SIZE, "Signature length is not {}", SIGNATURE_SIZE); 33 | ensure!( 34 | bytes[0] == SIGNATURE_VERSION1, 35 | "Only identifier version {:x} is supported", 36 | SIGNATURE_VERSION1 37 | ); 38 | let mut array = [0u8; secp::util::SIGNATURE_SIZE]; 39 | array.copy_from_slice(&bytes[VERSION_SIZE..]); 40 | let sig = secp::Signature::parse(&array); 41 | Ok(Self(sig)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /storage/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | use failure::Fallible; 2 | 3 | use crate::meta; 4 | use crate::meta::{Attribute, AttributeValue}; 5 | 6 | pub mod imp; 7 | 8 | pub trait Data { 9 | fn blob(&self) -> &[u8]; 10 | fn attributes<'a>(&'a self) -> Box>; 11 | 12 | // Convenience function to access attributes by name/path 13 | fn first_attrval_by_name(&self, name: &str) -> Option { 14 | meta::iter_first_attrval_by_name(self.attributes(), name) 15 | } 16 | 17 | fn first_attrval_by_path(&self, path: &[&str]) -> Option { 18 | meta::iter_first_attrval_by_path(self.attributes(), path) 19 | } 20 | } 21 | 22 | // De/Serialize in-memory data from/to a memory-independent storable 23 | // (binary, e.g. bson or json-utf8) representation 24 | pub trait Serializer { 25 | // TODO error handling: these two operations could return different error types 26 | // (SerErr/DeserErr), consider if that might be clearer 27 | fn serialize(&self, object: ObjectType) -> Fallible; 28 | fn deserialize(&self, serialized_object: SerializedType) -> Fallible; 29 | } 30 | 31 | // Provide (binary, e.g. SHA2) hash for (binary) data and validate hash against data 32 | pub trait Hasher { 33 | fn get_hash(&self, object: &ObjectType) -> Fallible; 34 | fn validate(&self, object: &ObjectType, hash: &HashType) -> Fallible; 35 | } 36 | 37 | // Provide human-readable (e.g. Base64) representation of (binary) hashes 38 | pub trait HashCoder { 39 | fn encode(&self, hash_bytes: &BinaryHashType) -> Fallible; 40 | fn decode(&self, hash_str: &ReadableHashType) -> Fallible; 41 | } 42 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/network/ark.rs: -------------------------------------------------------------------------------- 1 | use crate::secp256k1::Network; 2 | 3 | /// Strategies for the ARK mainnet. 4 | pub struct Mainnet; 5 | 6 | impl Network for Mainnet { 7 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 8 | b"\x17" 9 | } 10 | /// There is no BIP-0016 on ARK, so there is no such prefix either 11 | fn p2sh_addr(&self) -> &'static [u8; 1] { 12 | unimplemented!() 13 | } 14 | fn wif(&self) -> &'static [u8; 1] { 15 | b"\xaa" 16 | } 17 | fn bip32_xprv(&self) -> &'static [u8; 4] { 18 | b"\x46\x08\x95\x20" 19 | } 20 | fn bip32_xpub(&self) -> &'static [u8; 4] { 21 | b"\x46\x09\x06\x00" 22 | } 23 | fn message_prefix(&self) -> &'static str { 24 | // TODO usually there is a binary length prefix, but so many btc forks screwed that 25 | // up (including IoP) that now many include it as part of this string. Wigy could 26 | // not find out whether ARK has a length prefix here and if yes, what is that. 27 | "ARK message:\n" 28 | } 29 | fn slip44(&self) -> i32 { 30 | 0x6f // 111 31 | } 32 | } 33 | 34 | /// Strategies for the ARK test network (devnet). 35 | pub struct Testnet; 36 | 37 | impl Network for Testnet { 38 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 39 | b"\x1e" 40 | } 41 | fn p2sh_addr(&self) -> &'static [u8; 1] { 42 | unimplemented!() 43 | } 44 | fn wif(&self) -> &'static [u8; 1] { 45 | b"\xaa" 46 | } 47 | fn bip32_xprv(&self) -> &'static [u8; 4] { 48 | b"\x46\x08\x95\x20" 49 | } 50 | fn bip32_xpub(&self) -> &'static [u8; 4] { 51 | b"\x46\x09\x06\x00" 52 | } 53 | fn message_prefix(&self) -> &'static str { 54 | "DARK message:\n" 55 | } 56 | fn slip44(&self) -> i32 { 57 | 1 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/network/hyd.rs: -------------------------------------------------------------------------------- 1 | use crate::secp256k1::Network; 2 | 3 | /// Strategies for the Hydra mainnet. 4 | pub struct Mainnet; 5 | 6 | impl Network for Mainnet { 7 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 8 | b"\x29" // 41 9 | } 10 | fn p2sh_addr(&self) -> &'static [u8; 1] { 11 | unimplemented!() 12 | } 13 | fn wif(&self) -> &'static [u8; 1] { 14 | b"\x64" // 100 15 | } 16 | fn bip32_xprv(&self) -> &'static [u8; 4] { 17 | b"\x46\x08\x95\x20" // TODO 18 | } 19 | fn bip32_xpub(&self) -> &'static [u8; 4] { 20 | b"\x46\x09\x06\x00" // TODO 21 | } 22 | fn message_prefix(&self) -> &'static str { 23 | // TODO usually there is a binary length prefix, but so many btc forks screwed that 24 | // up (including IoP) that now many include it as part of this string. Wigy could 25 | // not find out whether ARK has a length prefix here and if yes, what is that. 26 | "HYD message:\n" 27 | } 28 | fn slip44(&self) -> i32 { 29 | 0x485944 // 4741444 30 | } 31 | } 32 | 33 | /// Strategies for the Hydra test network (called devnet in ARK terminology). 34 | pub struct Testnet; 35 | 36 | impl Network for Testnet { 37 | fn p2pkh_addr(&self) -> &'static [u8; 1] { 38 | b"\x1e" 39 | } 40 | fn p2sh_addr(&self) -> &'static [u8; 1] { 41 | unimplemented!() 42 | } 43 | fn wif(&self) -> &'static [u8; 1] { 44 | b"\xaa" 45 | } 46 | fn bip32_xprv(&self) -> &'static [u8; 4] { 47 | b"\x46\x08\x95\x20" // TODO 48 | } 49 | fn bip32_xpub(&self) -> &'static [u8; 4] { 50 | b"\x46\x09\x06\x00" // TODO 51 | } 52 | fn message_prefix(&self) -> &'static str { 53 | "tHYD message:\n" 54 | } 55 | fn slip44(&self) -> i32 { 56 | 0x485944 // 4741444 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /prometheus-cli/src/seed.rs: -------------------------------------------------------------------------------- 1 | use failure::Fallible; 2 | use log::*; 3 | 4 | pub fn generate_phrase() -> Vec { 5 | let new_bip39_phrase = keyvault::Seed::generate_bip39(); 6 | let words = new_bip39_phrase.split(' '); 7 | words.map(|s| s.to_owned()).collect() 8 | } 9 | 10 | pub fn show_generated_phrase() { 11 | let words = generate_phrase(); 12 | warn!( 13 | r#"Make sure you back these words up somewhere safe 14 | and run the 'restore vault' command of this application first!"# 15 | ); 16 | words.iter().enumerate().for_each(|(i, word)| info!(" {:2}: {}", i + 1, word)); 17 | } 18 | 19 | pub fn read_phrase() -> Fallible { 20 | use std::io::BufRead; 21 | use std::io::Write; 22 | 23 | let stdin = std::io::stdin(); 24 | let stdout = std::io::stdout(); 25 | let mut stdin_lock = stdin.lock(); 26 | let mut stdout_lock = stdout.lock(); 27 | stdout_lock.write_fmt(format_args!( 28 | "Please type the words you backed up one-by-one pressing enter after each:\n" 29 | ))?; 30 | 31 | let mut words = Vec::with_capacity(24); 32 | for i in 1..=24 { 33 | loop { 34 | let mut buffer = String::with_capacity(10); 35 | stdout_lock.write_fmt(format_args!(" {:2}> ", i))?; // no newline at the end for this prompt! 36 | stdout_lock.flush()?; // without this, nothing is written on the console 37 | stdin_lock.read_line(&mut buffer)?; 38 | buffer = buffer.trim().to_owned(); 39 | if keyvault::Seed::check_word(&buffer) { 40 | words.push(buffer); 41 | break; 42 | } else { 43 | stdout_lock.write_fmt(format_args!( 44 | "{} is not in the dictionary, please retry entering it\n", 45 | buffer 46 | ))?; 47 | } 48 | } 49 | } 50 | let phrase = words.join(" "); 51 | 52 | debug!("You entered: {}", phrase); 53 | 54 | Ok(phrase) 55 | } 56 | -------------------------------------------------------------------------------- /keyvault/src/ed25519/sk.rs: -------------------------------------------------------------------------------- 1 | use ed25519_dalek as ed; 2 | 3 | use super::*; 4 | 5 | /// The size of the private key in the format used by [`to_bytes`] 6 | /// 7 | /// [`to_bytes`]: #method.to_bytes 8 | pub const PRIVATE_KEY_SIZE: usize = ed::SECRET_KEY_LENGTH; 9 | 10 | /// Implementation of Ed25519::PrivateKey 11 | pub struct EdPrivateKey(ed::Keypair); 12 | 13 | impl EdPrivateKey { 14 | /// The private key serialized in a format that can be fed to [`from_bytes`] 15 | /// 16 | /// [`from_bytes`]: #method.from_bytes 17 | pub fn to_bytes(&self) -> Vec { 18 | let mut res = Vec::with_capacity(PRIVATE_KEY_SIZE); 19 | res.extend_from_slice(self.0.secret.as_bytes()); 20 | res 21 | } 22 | 23 | /// Creates a public key from a byte slice possibly returned by the [`to_bytes`] method. 24 | /// 25 | /// # Error 26 | /// If `bytes` is rejected by `ed25519_dalek::SecretKey::from_bytes` 27 | /// 28 | /// [`to_bytes`]: #method.to_bytes 29 | pub fn from_bytes>(bytes: D) -> Fallible { 30 | let secret = ed::SecretKey::from_bytes(bytes.as_ref())?; 31 | let public = ed::PublicKey::from(&secret); 32 | let key_pair = ed::Keypair { secret, public }; 33 | Ok(Self(key_pair)) 34 | } 35 | } 36 | 37 | impl Clone for EdPrivateKey { 38 | fn clone(&self) -> Self { 39 | let secret_bytes = self.0.secret.as_bytes(); 40 | let public_bytes = self.0.public.as_bytes(); 41 | let secret = ed::SecretKey::from_bytes(secret_bytes).unwrap(); 42 | let public = ed::PublicKey::from_bytes(public_bytes).unwrap(); 43 | Self(ed::Keypair { secret, public }) 44 | } 45 | } 46 | 47 | impl PrivateKey for EdPrivateKey { 48 | fn public_key(&self) -> EdPublicKey { 49 | let pk = self.0.public; 50 | pk.into() 51 | } 52 | fn sign>(&self, data: D) -> EdSignature { 53 | let sig = self.0.sign(data.as_ref()); 54 | sig.into() 55 | } 56 | } 57 | 58 | impl From for EdPrivateKey { 59 | fn from(kp: ed::Keypair) -> Self { 60 | Self(kp) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /keyvault/src/ed25519/pk.rs: -------------------------------------------------------------------------------- 1 | use ed25519_dalek as ed; 2 | 3 | use super::{Ed25519, EdKeyId, EdSignature}; 4 | use crate::*; 5 | 6 | /// The size of the public key in the compressed format used by [`to_bytes`] 7 | /// 8 | /// [`to_bytes`]: #method.to_bytes 9 | pub const PUBLIC_KEY_SIZE: usize = ed::PUBLIC_KEY_LENGTH; 10 | 11 | /// Implementation of Ed25519::PublicKey 12 | #[derive(Copy, Clone, Eq, PartialEq)] 13 | pub struct EdPublicKey(ed::PublicKey); 14 | 15 | impl EdPublicKey { 16 | /// The public key serialized in a format that can be fed to [`from_bytes`] 17 | /// 18 | /// [`from_bytes`]: #method.from_bytes 19 | pub fn to_bytes(&self) -> Vec { 20 | let mut res = Vec::with_capacity(PUBLIC_KEY_SIZE); 21 | res.extend_from_slice(self.0.as_bytes()); 22 | res 23 | } 24 | 25 | /// Creates a public key from a byte slice possibly returned by the [`to_bytes`] method. 26 | /// 27 | /// # Error 28 | /// If `bytes` is rejected by `ed25519_dalek::PublicKey::from_bytes` 29 | /// 30 | /// [`to_bytes`]: #method.to_bytes 31 | pub fn from_bytes>(bytes: D) -> Fallible { 32 | let pk = ed::PublicKey::from_bytes(bytes.as_ref())?; 33 | Ok(Self(pk)) 34 | } 35 | } 36 | 37 | impl From for EdPublicKey { 38 | fn from(pk: ed::PublicKey) -> Self { 39 | Self(pk) 40 | } 41 | } 42 | 43 | impl PublicKey for EdPublicKey { 44 | fn key_id(&self) -> EdKeyId { 45 | EdKeyId::from(self) 46 | } 47 | /// We should never assume that there is only 1 public key that can verify a given 48 | /// signature. Actually, there are 8 public keys. 49 | fn verify>(&self, data: D, sig: &EdSignature) -> bool { 50 | let res = self.0.verify(data.as_ref(), sig.into()); 51 | res.is_ok() 52 | } 53 | } 54 | 55 | impl ExtendedPublicKey for EdPublicKey { 56 | fn derive_normal_child(&self, _idx: i32) -> Fallible { 57 | bail!("Normal derivation of Ed25519 is invalid based on SLIP-0010.") 58 | } 59 | fn as_public_key(&self) -> EdPublicKey { 60 | *self 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /keyvault/src/ed25519/sig.rs: -------------------------------------------------------------------------------- 1 | use ed25519_dalek as ed; 2 | 3 | use super::*; 4 | 5 | /// The serialized byte representation for the current version of the signature algorithm 6 | /// (standard Ed25519 signature uses SHA256 internally and its output is only dependent on 7 | /// the private key and the message. It does not add an extra random keying that it could) 8 | pub const SIGNATURE_VERSION1: u8 = b'\x01'; 9 | 10 | /// Size of the signature is the version byte plus the actual Dalek Ed25519 signature size 11 | pub const SIGNATURE_SIZE: usize = ed::SIGNATURE_LENGTH + VERSION_SIZE; 12 | 13 | /// Implementation of Ed25519::Signature 14 | #[derive(Copy, Clone, Eq, PartialEq)] 15 | pub struct EdSignature(ed::Signature); 16 | 17 | impl EdSignature { 18 | /// The signature serialized in a format that can be fed to [`from_bytes`] 19 | /// 20 | /// [`from_bytes`]: #method.from_bytes 21 | pub fn to_bytes(&self) -> Vec { 22 | let mut res = Vec::with_capacity(SIGNATURE_SIZE); 23 | res.push(SIGNATURE_VERSION1); 24 | res.extend_from_slice(&self.0.to_bytes()[..]); 25 | res 26 | } 27 | 28 | /// Creates a signature from a byte slice possibly returned by the [`to_bytes`] method. 29 | /// 30 | /// # Error 31 | /// If `bytes` is rejected by `ed25519_dalek::SecretKey::from_bytes` 32 | /// 33 | /// [`to_bytes`]: #method.to_bytes 34 | pub fn from_bytes>(bytes: D) -> Fallible { 35 | let bytes = bytes.as_ref(); 36 | ensure!(bytes.len() == SIGNATURE_SIZE, "Signature length is not {}", SIGNATURE_SIZE); 37 | ensure!( 38 | bytes[0] == SIGNATURE_VERSION1, 39 | "Only identifier version {:x} is supported", 40 | SIGNATURE_VERSION1 41 | ); 42 | let sig = ed::Signature::from_bytes(&bytes[VERSION_SIZE..])?; 43 | Ok(Self(sig)) 44 | } 45 | } 46 | 47 | impl From for EdSignature { 48 | fn from(sig: ed::Signature) -> Self { 49 | Self(sig) 50 | } 51 | } 52 | 53 | impl<'a> From<&'a EdSignature> for &'a ed::Signature { 54 | fn from(sig: &'a EdSignature) -> Self { 55 | &sig.0 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use rand::rngs::OsRng; 4 | use tokio_current_thread as reactor; 5 | 6 | use claims::repo::InMemoryProfileRepository; 7 | use mercury_home_node::server::HomeServer; 8 | use mercury_home_protocol::crypto::*; 9 | use mercury_home_protocol::*; 10 | 11 | #[cfg(test)] 12 | pub mod connect; 13 | #[cfg(test)] 14 | pub mod home; 15 | 16 | pub fn generate_keypair() -> PrivateKey { 17 | let mut csprng: OsRng = OsRng::new().unwrap(); 18 | let ed_rnd_keypair = ed25519_dalek::Keypair::generate(&mut csprng); 19 | PrivateKey::from(ed25519::EdPrivateKey::from(ed_rnd_keypair)) 20 | } 21 | 22 | pub fn generate_ownprofile(attributes: AttributeMap) -> (OwnProfile, PrivateKeySigner) { 23 | let private_key = generate_keypair(); 24 | let signer = PrivateKeySigner::new(private_key).expect("TODO: this should not ever fail"); 25 | let profile = Profile::new(signer.public_key(), 1, vec![], attributes); 26 | let own_profile = OwnProfile::without_morpheus_claims(profile, vec![]); 27 | (own_profile, signer) 28 | } 29 | 30 | //facet: ProfileFacet 31 | pub fn generate_profile(attributes: AttributeMap) -> (Profile, PrivateKeySigner) { 32 | let (own_profile, signer) = generate_ownprofile(attributes); 33 | (own_profile.public_data(), signer) 34 | } 35 | 36 | pub fn generate_hosted() -> (OwnProfile, PrivateKeySigner) { 37 | let attributes = HostedFacet::default().to_attributes(); 38 | generate_ownprofile(attributes) 39 | } 40 | 41 | pub fn generate_home() -> (Profile, PrivateKeySigner) { 42 | let attributes = HomeFacet::default().to_attributes(); 43 | generate_profile(attributes) 44 | } 45 | 46 | pub fn default_home_server(handle: &reactor::Handle) -> HomeServer { 47 | HomeServer::new( 48 | handle, 49 | Rc::new(CompositeValidator::default()), 50 | Rc::new(RefCell::new(InMemoryProfileRepository::new())), 51 | Rc::new(RefCell::new(InMemoryProfileRepository::new())), 52 | ) 53 | } 54 | 55 | pub fn first_home_of(own_profile: &OwnProfile) -> RelationProof { 56 | match own_profile.public_data().as_persona() { 57 | Some(ref persona) => persona.homes[0].clone(), 58 | _ => panic!("Profile is not a persona, no home found"), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /prometheus/src/home/discovery.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::Duration; 3 | 4 | use failure::{bail, Fallible}; 5 | use log::*; 6 | use multiaddr::Multiaddr; 7 | 8 | use mercury_home_protocol::{HomeFacet, Profile, ProfileFacets, ProfileId}; 9 | 10 | pub struct KnownHomeNode { 11 | // TODO should we store HomeFacet or the whole Profile here? 12 | pub profile: Profile, 13 | pub latency: Option, 14 | } 15 | 16 | impl KnownHomeNode { 17 | pub fn addrs(&self) -> Vec { 18 | match self.profile.to_home() { 19 | None => vec![], 20 | Some(home_facet) => home_facet.addrs.to_owned(), 21 | } 22 | } 23 | } 24 | 25 | pub struct HomeNodeCrawler { 26 | pub home_profiles: HashMap, 27 | } 28 | 29 | impl Default for HomeNodeCrawler { 30 | fn default() -> Self { 31 | let mut result = Self { home_profiles: Default::default() }; 32 | let facet = HomeFacet::new(vec!["/ip4/127.0.0.1/tcp/2077".parse().unwrap()], vec![]); 33 | let attributes = facet.to_attribute_map(); 34 | result 35 | .add(&Profile::new( 36 | "pez7aYuvoDPM5i7xedjwjsWaFVzL3qRKPv4sBLv3E3pAGi6".parse().unwrap(), 37 | 1, 38 | vec![], 39 | attributes, 40 | )) 41 | .unwrap(); 42 | result 43 | } 44 | } 45 | 46 | impl HomeNodeCrawler { 47 | pub fn add(&mut self, home: &Profile) -> Fallible<()> { 48 | let home_facet = match home.to_home() { 49 | None => bail!("Not a profile of a home node"), 50 | Some(facet) => facet, 51 | }; 52 | self.home_profiles 53 | .entry(home.id()) 54 | .and_modify(|p| { 55 | if p.profile.version() >= home.version() { 56 | info!("Ignored older version {} of profile {}", home.version(), home.id()); 57 | } else { 58 | p.profile = home.to_owned(); 59 | } 60 | }) 61 | .or_insert(KnownHomeNode { profile: home.to_owned(), latency: None }); 62 | Ok(()) 63 | } 64 | 65 | pub fn iter(&self) -> impl Iterator { 66 | self.home_profiles.values() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /forgetfulfuse/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Forgetful Filesystem 3 | 4 | ## Table of contents 5 | 6 | - [Forgetful Filesystem](#forgetful-filesystem) 7 | - [Table of contents](#table-of-contents) 8 | - [Reporting Vulnerabilities](#reporting-vulnerabilities) 9 | - [Usage](#usage) 10 | - [Contributing](#contributing) 11 | - [License](#license) 12 | 13 | ## Reporting Vulnerabilities 14 | 15 | ## Usage 16 | 17 | To mount a new instance of the filesystem, run the binary like this: 18 | 19 | `forgetfulfuse /run/user/1000/forgetful` 20 | 21 | The binary will not exit until the mount point is not unmounted. As a non-privileged user this can help: 22 | 23 | `fusermount -u /run/user/1000/forgetful` 24 | 25 | Of course in practice the filesystem will be mounted by a systemd unit run by the user. For testing it, run these in the project directory: 26 | 27 | ```sh 28 | # Install the binary to a user-specific folder 29 | $ cargo install --path . 30 | # Create a directory for user-specific systemd unit files 31 | $ mkdir -p $HOME/.config/systemd/user 32 | # Copy over the service file, editing the binary path to the user-specific one 33 | $ cat forgetful.service | sed -e "s#/bin/forgetfulfuse#$HOME/.cargo/bin/forgetfulfuse#" > $HOME/.config/systemd/user/forgetful.service 34 | # Systemd needs some nudging to reread files and directories... 35 | $ systemctl --user daemon-reload 36 | # Starting the service mounts the filesystem 37 | $ systemctl --user start forgetful 38 | # The last 100 characters in the sample file is shown 39 | $ tail -c 100 /run/user/1000/forgetful/hello.txt 40 | ellentesque ut metus non nulla luctus condimentum. Etiam quis lectus porta orci sagittis imperdiet. 41 | # Stopping the service properly umounts the filesytem 42 | $ systemctl --user stop forgetful 43 | # So accessing files in it properly fails 44 | $ tail -c 100 /run/user/1000/forgetful/hello.txt 45 | tail: cannot open '/run/user/1000/forgetful/hello.txt' for reading: No such file or directory 46 | ``` 47 | 48 | If you want to always mount the filesystem whenever you login to your developer machine: 49 | 50 | ```sh 51 | $ systemctl --user enable forgetful 52 | Created symlink from $HOME/.config/systemd/user/multi-user.target.wants/forgetful.service to $HOME/.config/systemd/user/forgetful.service. 53 | ``` 54 | 55 | ## Contributing 56 | 57 | ## License 58 | 59 | [GPL-v3-or-later](LICENSE) 60 | -------------------------------------------------------------------------------- /keyvault/src/ed25519/id.rs: -------------------------------------------------------------------------------- 1 | use blake2::VarBlake2b; 2 | use digest::{Input, VariableOutput}; 3 | 4 | use super::*; 5 | 6 | /// This constant is used for keyed hashing of public keys. This does not improve the security 7 | /// of the hash algorithm, but allows for domain separation if some use-case requires a different 8 | /// hash of the public key with the same algorithm. 9 | pub const KEY_ID_SALT: &[u8] = b"open social graph"; 10 | 11 | /// The size of the key identifier in bytes. Since a version byte is prepended to the 12 | /// hash result, it is not a standard size. 13 | pub const KEY_ID_SIZE: usize = 16 + VERSION_SIZE; 14 | 15 | /// The serialized byte representation for the current version of the hash algorithm 16 | /// applied on the public key to obtain the key identifier 17 | pub const KEY_ID_VERSION1: u8 = b'\x01'; 18 | 19 | /// Implementation of Ed25519::KeyId 20 | #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] 21 | pub struct EdKeyId(Vec); 22 | 23 | impl EdKeyId { 24 | /// The key id serialized in a format that can be fed to [`from_bytes`] 25 | /// 26 | /// [`from_bytes`]: #method.from_bytes 27 | pub fn to_bytes(&self) -> Vec { 28 | self.0.clone() 29 | } 30 | 31 | /// Creates a key id from a byte slice possibly returned by the [`to_bytes`] method. 32 | /// 33 | /// # Error 34 | /// If `bytes` is not [`KEY_ID_SIZE`] long 35 | /// 36 | /// [`to_bytes`]: #method.to_bytes 37 | /// [`KEY_ID_SIZE`]: ../constant.KEY_ID_SIZE 38 | pub fn from_bytes>(bytes: D) -> Fallible { 39 | let bytes = bytes.as_ref(); 40 | ensure!(bytes.len() == KEY_ID_SIZE, "Identifier length is not {}", KEY_ID_SIZE); 41 | ensure!( 42 | bytes[0] == KEY_ID_VERSION1, 43 | "Only identifier version {:x} is supported", 44 | KEY_ID_VERSION1 45 | ); 46 | Ok(Self(bytes.to_owned())) 47 | } 48 | } 49 | 50 | impl From<&EdPublicKey> for EdKeyId { 51 | fn from(pk: &EdPublicKey) -> EdKeyId { 52 | let mut hasher = VarBlake2b::new_keyed(KEY_ID_SALT, KEY_ID_SIZE - VERSION_SIZE); 53 | hasher.input(pk.to_bytes()); 54 | let mut hash = Vec::with_capacity(KEY_ID_SIZE); 55 | hash.push(KEY_ID_VERSION1); 56 | hasher.variable_result(|h| hash.extend_from_slice(h)); 57 | EdKeyId(hash) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /prometheus/src/names/mod.rs: -------------------------------------------------------------------------------- 1 | // NOTE based on the 'names' crate but made deterministic https://github.com/fnichol/names 2 | mod adjectives; 3 | mod nouns; 4 | 5 | use adjectives::ADJECTIVES; 6 | use nouns::NOUNS; 7 | 8 | pub struct DeterministicNameGenerator<'a> { 9 | adjectives: &'a [&'a str], 10 | nouns: &'a [&'a str], 11 | } 12 | 13 | impl<'a> DeterministicNameGenerator<'a> { 14 | pub fn new(adjectives: &'a [&'a str], nouns: &'a [&'a str]) -> Self { 15 | DeterministicNameGenerator { adjectives, nouns } 16 | } 17 | 18 | fn random_word(&'a self, data: &[u8], words: &[&'a str]) -> &str { 19 | let mut seed = [0u8; 32]; 20 | let seed_len = std::cmp::min(data.len(), seed.len()); 21 | seed[..seed_len].clone_from_slice(&data[..seed_len]); 22 | 23 | use rand::{distributions::Uniform, Rng, SeedableRng}; 24 | let mut rng = rand_chacha::ChaChaRng::from_seed(seed); 25 | let idx = rng.sample(Uniform::new(0, words.len())); 26 | words[idx] 27 | } 28 | 29 | fn adjective(&self, data: &[u8]) -> &str { 30 | self.random_word(data, self.adjectives) 31 | } 32 | fn noun(&self, data: &[u8]) -> &str { 33 | self.random_word(data, self.nouns) 34 | } 35 | 36 | pub fn name(&self, bin: &[u8]) -> String { 37 | assert!(bin.len() >= 4); 38 | format!("{} {}", self.adjective(&bin[..bin.len() / 2]), self.noun(&bin[bin.len() / 2..])) 39 | } 40 | } 41 | 42 | impl<'a> Default for DeterministicNameGenerator<'a> { 43 | fn default() -> Self { 44 | DeterministicNameGenerator::new(ADJECTIVES, NOUNS) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::*; 51 | 52 | #[test] 53 | fn test_deterministic_names() { 54 | let did = [0u8; 32]; 55 | let name = DeterministicNameGenerator::default().name(&did); 56 | assert_eq!(name, "Neoteric Nurture"); 57 | 58 | let did = [41u8; 64]; 59 | let name = DeterministicNameGenerator::default().name(&did); 60 | assert_eq!(name, "Hortative Heir"); 61 | 62 | let did = [42u8; 32]; 63 | let name = DeterministicNameGenerator::default().name(&did); 64 | assert_eq!(name, "Gratifying Gratitude"); 65 | 66 | let did = [255u8; 16]; 67 | let name = DeterministicNameGenerator::default().name(&did); 68 | assert_eq!(name, "Unhesitating Tutor"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /connect/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod profile; 3 | pub use error::{Error, ErrorKind}; 4 | pub mod net; 5 | pub use net::SimpleTcpHomeConnector; 6 | pub mod jsonrpc; 7 | pub mod sdk; 8 | pub mod service; 9 | 10 | use std::rc::Rc; 11 | 12 | use futures::prelude::*; 13 | use serde_derive::{Deserialize, Serialize}; 14 | 15 | use mercury_home_protocol::*; 16 | use mercury_storage::asynch::KeyValueStore; 17 | 18 | #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] 19 | pub struct DAppPermission(Vec); 20 | 21 | pub trait Contact { 22 | fn proof(&self) -> &RelationProof; 23 | fn call(&self, init_payload: AppMessageFrame) -> AsyncResult; 24 | } 25 | 26 | pub struct DAppCall { 27 | pub outgoing: AppMsgSink, 28 | pub incoming: AppMsgStream, 29 | } 30 | 31 | //impl Drop for DAppCall 32 | // { fn drop(&mut self) { debug!("DAppCall was dropped"); } } 33 | 34 | pub enum DAppEvent { 35 | PairingResponse(Box), 36 | Call(Box), // TODO wrap IncomingCall so as call.answer() could return a DAppCall directly 37 | } 38 | 39 | pub trait DAppEndpoint { 40 | // NOTE this implicitly asks for user interaction (through UI) selecting a profile to be used with the app 41 | fn dapp_session( 42 | &self, 43 | app: &ApplicationId, 44 | authorization: Option, 45 | ) -> AsyncResult, Error>; 46 | } 47 | 48 | // NOTE A specific DApp is logged in to the Connect Service with given details, e.g. a selected profile. 49 | // A DApp might have several sessions, e.g. running in the name of multiple profiles. 50 | pub trait DAppSession { 51 | // After the session was initialized, the profile is selected and can be queried any time 52 | fn selected_profile(&self) -> ProfileId; 53 | 54 | // TODO merge these two operations using an optional profile argument 55 | fn contacts(&self) -> AsyncResult>, Error>; 56 | fn contacts_with_profile( 57 | &self, 58 | profile: &ProfileId, 59 | relation_type: Option<&str>, 60 | ) -> AsyncResult>, Error>; 61 | fn initiate_contact(&self, with_profile: &ProfileId) -> AsyncResult<(), Error>; 62 | 63 | fn app_storage(&self) -> AsyncResult, Error>; 64 | 65 | fn checkin(&self) -> AsyncResult>, Error>; 66 | } 67 | -------------------------------------------------------------------------------- /keyvault/src/ed25519/ext_sk.rs: -------------------------------------------------------------------------------- 1 | use failure::Fallible; 2 | 3 | use super::*; 4 | 5 | /// Implementation of Ed25519::ExtendedPrivateKey 6 | pub struct EdExtPrivateKey { 7 | chain_code: ChainCode, 8 | sk: EdPrivateKey, 9 | } 10 | 11 | impl EdExtPrivateKey { 12 | /// Borrows the chain code of the extended private key 13 | pub fn chain_code(&self) -> &ChainCode { 14 | &self.chain_code 15 | } 16 | 17 | pub(crate) fn cook_new ()>(salt: &[u8], recipe: F) -> Self { 18 | // This unwrap would only panic if the digest algorithm had some inconsistent 19 | // generic parameters, but the SHA512 we use is consistent with itself 20 | let mut hasher = HmacSha512::new_varkey(salt).unwrap(); 21 | 22 | recipe(&mut hasher); 23 | 24 | let hash_arr = hasher.result().code(); 25 | let hash_bytes = hash_arr.as_slice(); 26 | 27 | let sk_bytes = &hash_bytes[..PRIVATE_KEY_SIZE]; 28 | let cc_bytes = &hash_bytes[PRIVATE_KEY_SIZE..]; 29 | 30 | let chain_code = ChainCode::from_bytes(cc_bytes).unwrap(); 31 | let sk = EdPrivateKey::from_bytes(sk_bytes).unwrap(); 32 | 33 | Self { chain_code, sk } 34 | } 35 | } 36 | 37 | impl ExtendedPrivateKey for EdExtPrivateKey { 38 | fn derive_normal_child(&self, _idx: i32) -> Fallible { 39 | bail!("Normal derivation of Ed25519 is invalid based on SLIP-0010.") 40 | } 41 | /// There is a potential [vulnerability](https://forum.web3.foundation/t/key-recovery-attack-on-bip32-ed25519/44) in 42 | /// that might affect all SLIP-0010 compatible Ed25519 wallets. We should never assume that there is only 1 43 | /// public key that can verify a given signature. Actually, there are 8 public keys. 44 | fn derive_hardened_child(&self, idx: i32) -> Fallible { 45 | ensure!(idx >= 0, "Derivation index cannot be negative"); 46 | let idx = idx as u32; 47 | 48 | let xprv = EdExtPrivateKey::cook_new(&self.chain_code.to_bytes(), |hasher| { 49 | hasher.input(&[0x00u8]); 50 | hasher.input(&self.sk.to_bytes()); 51 | hasher.input(&(0x8000_0000u32 + idx).to_be_bytes()); 52 | }); 53 | 54 | Ok(xprv) 55 | } 56 | fn neuter(&self) -> EdPublicKey { 57 | self.sk.public_key() 58 | } 59 | fn as_private_key(&self) -> EdPrivateKey { 60 | self.sk.clone() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/pk.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | 3 | use super::*; 4 | use crate::PublicKey; 5 | 6 | /// The size of the public key in the compressed format used by [`to_bytes`] 7 | /// 8 | /// [`to_bytes`]: #method.to_bytes 9 | pub const PUBLIC_KEY_SIZE: usize = secp::util::COMPRESSED_PUBLIC_KEY_SIZE; 10 | 11 | /// The size of the public key in the uncompressed format used by [`uncompressed`] 12 | /// 13 | /// [`uncompressed`]: #method.uncompressed 14 | pub const PUBLIC_KEY_UNCOMPRESSED_SIZE: usize = secp::util::FULL_PUBLIC_KEY_SIZE; 15 | 16 | /// Implementation of Secp256k1::PublicKey 17 | #[derive(Debug, Clone, Eq, PartialEq)] 18 | pub struct SecpPublicKey(pub(super) secp::PublicKey); 19 | 20 | impl SecpPublicKey { 21 | /// The public key serialized in a format that can be fed to [`from_bytes`] 22 | /// 23 | /// [`from_bytes`]: #method.from_bytes 24 | pub fn to_bytes(&self) -> Vec { 25 | self.0.serialize_compressed().to_vec() 26 | } 27 | 28 | /// Creates a public key from a byte slice possibly returned by the [`to_bytes`] method. 29 | /// 30 | /// # Error 31 | /// If `bytes` is rejected by `libsecp256k1::PublicKey::parse_slice` 32 | /// 33 | /// [`to_bytes`]: #method.to_bytes 34 | pub fn from_bytes>(bytes: D) -> Fallible { 35 | let format = Some(secp::PublicKeyFormat::Compressed); 36 | let pk = secp::PublicKey::parse_slice(bytes.as_ref(), format).map_err(SecpError::from)?; 37 | Ok(Self(pk)) 38 | } 39 | 40 | /// The public key serialized in the uncompressed format used in some places in the bitcoin 41 | /// ecosystem (like address hashing in [`SecpKeyId::bitcoin_address`]) 42 | /// 43 | /// [`SecpKeyId::bitcoin_address`]: ../struct.SecpKeyId.html#method.bitcoin_address 44 | pub fn uncompressed(&self) -> [u8; PUBLIC_KEY_UNCOMPRESSED_SIZE] { 45 | self.0.serialize() 46 | } 47 | } 48 | 49 | impl Add<&[u8]> for &SecpPublicKey { 50 | type Output = Fallible; 51 | 52 | fn add(self, rhs: &[u8]) -> Self::Output { 53 | let sk = secp::SecretKey::parse_slice(rhs).map_err(SecpError::from)?; 54 | let mut sum = self.0.clone(); 55 | sum.tweak_add_assign(&sk).map_err(SecpError::from)?; 56 | Ok(SecpPublicKey(sum)) 57 | } 58 | } 59 | 60 | impl PublicKey for SecpPublicKey { 61 | fn key_id(&self) -> SecpKeyId { 62 | SecpKeyId::from(self) 63 | } 64 | fn verify>(&self, data: D, sig: &SecpSignature) -> bool { 65 | let msg = Secp256k1::hash_message(data); 66 | secp::verify(&msg, &sig.0, &self.0) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /connect/src/jsonrpc/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::rc::Rc; 3 | 4 | use failure::Fail; 5 | use futures::prelude::*; 6 | use log::*; 7 | use tokio_codec::{Decoder, Encoder}; 8 | use tokio_core::reactor; 9 | use tokio_uds::UnixListener; 10 | 11 | use crate::error::*; 12 | use crate::service::*; 13 | use mercury_home_protocol::*; 14 | 15 | pub mod api; 16 | pub mod client_proxy; 17 | pub mod server_dispatcher; 18 | 19 | pub struct UdsServer { 20 | path: PathBuf, 21 | //listener: UnixListener, 22 | handle: reactor::Handle, 23 | } 24 | 25 | impl UdsServer { 26 | pub fn new(sock_path: &PathBuf, handle: reactor::Handle) -> Result { 27 | // let listener = UnixListener::bind(sock_path, &handle)?; 28 | // debug!("listening on {:?}", sock_path); 29 | Ok(Self { path: sock_path.clone(), handle }) 30 | } 31 | 32 | pub fn dispatch(&self, codec: C, service: Rc) -> AsyncResult<(), Error> 33 | where 34 | C: 'static 35 | + Decoder 36 | + Clone 37 | + Encoder, 38 | { 39 | let listener = match UnixListener::bind(&self.path, &self.handle) { 40 | Ok(sock) => sock, 41 | Err(e) => { 42 | let err = Err(e.context(ErrorKind::ConnectionFailed.into()).into()); 43 | return Box::new(err.into_future()); // as AsyncResult<(), Error> ) 44 | } 45 | }; 46 | debug!("listening on {:?}", self.path); 47 | 48 | let rpc_server = server_dispatcher::JsonRpcServer::new(service, self.handle.clone()); 49 | 50 | let handle = self.handle.clone(); // NOTE convince the borrow checker about this partial moved field 51 | let server_fut = listener 52 | .incoming() 53 | .for_each(move |(connection, _peer_addr)| { 54 | let _peer_credentials = connection.peer_cred(); 55 | 56 | let client_fut = rpc_server.serve_duplex_stream(connection, codec.clone()); 57 | handle.spawn(client_fut.map_err(|e| warn!("Serving client failed: {}", e))); 58 | Ok(()) 59 | }) 60 | .map_err(|e| e.context(ErrorKind::ImplementationError.into()).into()); 61 | 62 | Box::new(server_fut) 63 | } 64 | } 65 | 66 | impl Drop for UdsServer { 67 | fn drop(&mut self) { 68 | match std::fs::remove_file(&self.path) { 69 | Ok(()) => debug!("Cleaned up socket file {:?}", self.path), 70 | Err(_e) => warn!("Failed to clean up socket file {:?}", self.path), 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /did/src/model.rs: -------------------------------------------------------------------------------- 1 | use failure::{ensure, Fallible}; 2 | use futures::prelude::*; 3 | use serde_derive::{Deserialize, Serialize}; 4 | 5 | use keyvault::{PrivateKey as KeyVaultPrivateKey, PublicKey as KeyVaultPublicKey}; 6 | 7 | pub type AsyncResult = Box>; 8 | pub type AsyncFallible = Box>; 9 | 10 | pub type ContentId = String; // Something similar to IPFS CIDv1 https://github.com/ipfs/specs/issues/130 11 | 12 | pub type KeyId = keyvault::multicipher::MKeyId; 13 | pub type PublicKey = keyvault::multicipher::MPublicKey; 14 | pub type PrivateKey = keyvault::multicipher::MPrivateKey; 15 | pub type Signature = keyvault::multicipher::MSignature; 16 | 17 | // NOTE a.k.a DID 18 | pub type ProfileId = KeyId; 19 | 20 | /// Something that can sign data, but cannot give out the private key. 21 | /// Usually implemented using a private key internally, but also enables hardware wallets. 22 | pub trait Signer { 23 | fn profile_id(&self) -> &ProfileId; 24 | fn public_key(&self) -> PublicKey; 25 | fn sign(&self, data: &[u8]) -> Fallible; 26 | } 27 | 28 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 29 | pub struct SignedMessage { 30 | public_key: PublicKey, 31 | #[serde(with = "serde_bytes")] 32 | message: Vec, 33 | signature: Signature, 34 | } 35 | 36 | impl SignedMessage { 37 | pub fn new(public_key: PublicKey, message: Vec, signature: Signature) -> Self { 38 | Self { public_key, message, signature } 39 | } 40 | 41 | pub fn public_key(&self) -> &PublicKey { 42 | &self.public_key 43 | } 44 | pub fn message(&self) -> &[u8] { 45 | &self.message 46 | } 47 | pub fn signature(&self) -> &Signature { 48 | &self.signature 49 | } 50 | 51 | pub fn validate(&self) -> bool { 52 | self.public_key.verify(&self.message, &self.signature) 53 | } 54 | } 55 | 56 | pub struct PrivateKeySigner { 57 | private_key: PrivateKey, 58 | profile_id: ProfileId, 59 | } 60 | 61 | impl PrivateKeySigner { 62 | pub fn new(private_key: PrivateKey, profile_id: ProfileId) -> Fallible { 63 | ensure!( 64 | private_key.public_key().validate_id(&profile_id), 65 | "Given private key and DID do not match" 66 | ); 67 | Ok(Self { private_key, profile_id }) 68 | } 69 | } 70 | 71 | impl Signer for PrivateKeySigner { 72 | fn profile_id(&self) -> &ProfileId { 73 | &self.profile_id 74 | } 75 | fn public_key(&self) -> PublicKey { 76 | self.private_key.public_key() 77 | } 78 | fn sign(&self, data: &[u8]) -> Fallible { 79 | Ok(self.private_key.sign(data)) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /claims/src/claim_schema/defaults.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | 3 | use super::SchemaVersion; 4 | 5 | pub fn get() -> Vec { 6 | vec![age_over(), email_address(), full_name()] 7 | } 8 | 9 | fn age_over() -> SchemaVersion { 10 | SchemaVersion::new( 11 | "McL9746fWtE9EXV5", 12 | "iop", 13 | "age-over", 14 | 0, 15 | json![{ 16 | "type": "object", 17 | "properties": { 18 | "age": { 19 | "type": "number", 20 | "min": 0, 21 | "max": 255, 22 | "required": true, 23 | "description": "Eg.: 42. Pick a number between 0 and 255", 24 | }, 25 | }, 26 | }], 27 | ) 28 | } 29 | 30 | fn email_address() -> SchemaVersion { 31 | SchemaVersion::new( 32 | "McL9746fWtE9EXVb", 33 | "iop", 34 | "email-address", 35 | 0, 36 | json![{ 37 | "type": "object", 38 | "properties": { 39 | "email": { 40 | "type": "string", 41 | "regex": r#"/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/"#, 42 | "required": true, 43 | "description": "Eg.: john.doe@ecorp-usa.com", 44 | } 45 | } 46 | }], 47 | ) 48 | } 49 | 50 | fn full_name() -> SchemaVersion { 51 | SchemaVersion::new_with_order( 52 | "McL9746fWtE9EXVa", 53 | "iop", 54 | "full-name", 55 | 0, 56 | json![{ 57 | "type": "object", 58 | "properties": { 59 | "title": { 60 | "type": "string", 61 | "maxLength": 50, 62 | "required": false, 63 | "description": "eg.: Mr.", 64 | }, 65 | "givenName": { 66 | "type": "string", 67 | "maxLength": 50, 68 | "required": true, 69 | "description": "eg.: John", 70 | }, 71 | "middleName": { 72 | "type": "string", 73 | "maxLength": 50, 74 | "required": false, 75 | "description": "eg.: Canary", 76 | }, 77 | "familyName": { 78 | "type": "string", 79 | "maxLength": 50, 80 | "required": true, 81 | "description": "eg.: Doe", 82 | }, 83 | }, 84 | }], 85 | vec!["title", "givenName", "middleName", "familyName"], 86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /examples/TheButton/src/options.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::path::PathBuf; 3 | 4 | use structopt::StructOpt; 5 | 6 | use did::model::*; 7 | 8 | #[derive(Debug, StructOpt)] 9 | #[structopt( 10 | setting = structopt::clap::AppSettings::ColoredHelp 11 | )] 12 | pub struct Options { 13 | #[structopt(long = "repository", default_value = "127.0.0.1:6161", value_name = "IP:PORT")] 14 | /// IPv4/6 address of the remote profile repository. Temporary solution until proper routing is in place. 15 | pub profile_repo_address: SocketAddr, 16 | 17 | #[structopt(long, default_value = "127.0.0.1:2077", value_name = "IP:PORT")] 18 | /// IPv4/6 address to listen on serving REST requests. 19 | pub home_address: SocketAddr, 20 | 21 | #[structopt(long, value_name = "KEY")] 22 | /// Public key (multicipher) of the Home node to host this dApp 23 | pub home_pubkey: PublicKey, 24 | 25 | #[structopt(long, default_value = "log4rs.yml", value_name = "FILE", parse(from_os_str))] 26 | /// Config file for log4rs (YAML). 27 | pub logger_config: PathBuf, 28 | 29 | #[structopt(subcommand)] 30 | pub command: Command, 31 | // #[structopt(long, value_name = "DIR", parse(from_os_str))] 32 | // /// Configuration directory to pick vault and profile info from. 33 | // /// Default: OS-specific app_cfg_dir/prometheus 34 | // pub config_dir: Option, 35 | } 36 | 37 | #[derive(Debug, StructOpt)] 38 | pub enum Command { 39 | #[structopt(name = "publisher")] 40 | /// Generate a phraselist needed to create a profile vault 41 | Pubhlisher(PublisherConfig), 42 | 43 | #[structopt(name = "subscriber")] 44 | /// Restore profile vault from a phraselist or profile from remote repository 45 | Subscriber(SubscriberConfig), 46 | } 47 | 48 | #[derive(Clone, Debug, StructOpt)] 49 | pub struct PublisherConfig { 50 | #[structopt( 51 | long, 52 | default_value = "../../etc/server.id", 53 | value_name = "FILE", 54 | parse(from_os_str) 55 | )] 56 | /// File to load ed25519 server private key from. Temporary solution until keyvault is used here. 57 | pub private_key_file: PathBuf, 58 | 59 | #[structopt(long, value_name = "SECS")] 60 | /// Automatically push button periodically with this given time interval 61 | pub event_timer_secs: Option, 62 | } 63 | 64 | #[derive(Clone, Debug, StructOpt)] 65 | pub struct SubscriberConfig { 66 | #[structopt( 67 | long, 68 | default_value = "../../etc/client.id", 69 | value_name = "FILE", 70 | parse(from_os_str) 71 | )] 72 | /// File to load ed25519 client private key from. Temporary solution until keyvault is used here. 73 | pub private_key_file: PathBuf, 74 | 75 | #[structopt(long, value_name = "DID")] 76 | /// Profile Id of the dApp server side to contact 77 | pub server_id: ProfileId, 78 | } 79 | -------------------------------------------------------------------------------- /examples/TheButton/src/init.rs: -------------------------------------------------------------------------------- 1 | // NOTE Though this is a full-fledged sample application, running it assumes 2 | // a Prometheus environment properly set up in advance, 3 | // e.g. a persona initialized and.registered to a home node. 4 | // This file contains such previous-phase setup code that probably should be moved 5 | use std::cell::RefCell; 6 | 7 | use multiaddr::ToMultiaddr; 8 | use tokio_current_thread as reactor; 9 | 10 | use super::*; 11 | use did::*; 12 | use prometheus::dapp::user_interactor::UserInteractor; 13 | //use prometheus::home::{connection::ConnectionFactory, net::TcpHomeConnector}; 14 | 15 | pub fn ensure_registered_to_home( 16 | reactor: &mut reactor::CurrentThread, 17 | private_profilekey: PrivateKey, 18 | home_addr: &SocketAddr, 19 | app_context: &AppContext, 20 | ) -> Fallible<()> { 21 | use claims::repo::InMemoryProfileRepository; 22 | 23 | let my_profile_id = private_profilekey.public_key().key_id(); 24 | let my_signer = Rc::new(PrivateKeySigner::new(private_profilekey, my_profile_id)?); 25 | 26 | info!("dApp public key: {}", my_signer.public_key()); 27 | info!("dApp profile id: {}", my_signer.profile_id()); 28 | 29 | //let my_profile = Profile::new(my_signer.public_key(), 1, vec![], Default::default()); 30 | let profile_repo = Rc::new(RefCell::new(InMemoryProfileRepository::new())); 31 | 32 | // unimplemented!() 33 | // let home_connector = Rc::new(TcpHomeConnector::new(profile_repo)); 34 | // let conn_factory = ConnectionFactory::new(home_connector, my_signer); 35 | // 36 | // let home_conn_fut = conn_factory.open(&app_context.home_id, Some(home_addr.to_multiaddr()?)); 37 | // let home_conn = reactor.block_on(home_conn_fut)?; 38 | // 39 | // let reg_fut = home_conn.register(); 40 | // reactor.block_on(reg_fut)?; 41 | Ok(()) 42 | } 43 | 44 | pub fn init_publisher(_server: &Server) -> AsyncFallible<()> { 45 | // let handle = server.appctx.handle.clone(); 46 | // let fut = init_app_common(&server.appctx) 47 | // .and_then(move |my_profile: Rc| { 48 | // my_profile.login().map(|session| (my_profile, session)) 49 | // }) 50 | // .and_then(move |(my_profile, session)| { 51 | // debug!("dApp server session is ready, waiting for incoming events"); 52 | // handle.spawn(session.events().for_each(move |event| { 53 | // debug!("dApp server received event: {:?}", event); 54 | // match event { 55 | // ProfileEvent::PairingRequest(half_proof) => { 56 | // let accept_fut = my_profile 57 | // .accept_relation(&half_proof) 58 | // .map(|_proof| ()) 59 | // .map_err(|e| debug!("Failed to accept pairing request: {}", e)); 60 | // Box::new(accept_fut) as AsyncResult<_, _> 61 | // } 62 | // err => Box::new(Ok(debug!("Got event {:?}, ignoring it", err)).into_future()), 63 | // } 64 | // })); 65 | // Ok(()) 66 | // }); 67 | // Box::new(fut) 68 | Box::new(futures::future::ok(())) 69 | } 70 | -------------------------------------------------------------------------------- /connect/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use failure::{Backtrace, Context, Fail}; 4 | 5 | #[derive(Debug)] 6 | pub struct Error { 7 | inner: Context, 8 | } 9 | 10 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] 11 | pub enum ErrorKind { 12 | #[fail(display = "connection to home failed")] 13 | ConnectionToHomeFailed, 14 | 15 | #[fail(display = "handshake failed")] 16 | DiffieHellmanHandshakeFailed, 17 | 18 | #[fail(display = "peer id retreival failed")] 19 | PeerIdRetreivalFailed, 20 | 21 | #[fail(display = "failed to get contacts")] 22 | FailedToGetContacts, 23 | 24 | #[fail(display = "failed to get session")] 25 | FailedToGetSession, 26 | 27 | #[fail(display = "address conversion failed")] 28 | AddressConversionFailed, 29 | 30 | #[fail(display = "failed to connect tcp stream")] 31 | ConnectionFailed, 32 | 33 | #[fail(display = "failed to load profile")] 34 | FailedToLoadProfile, 35 | 36 | #[fail(display = "failed to resolve profile")] 37 | FailedToResolveProfile, 38 | 39 | #[fail(display = "home profile expected")] 40 | HomeProfileExpected, 41 | 42 | #[fail(display = "failed to claim profile")] 43 | FailedToClaimProfile, 44 | 45 | #[fail(display = "registration failed")] 46 | RegistrationFailed, 47 | 48 | #[fail(display = "deregistration failed")] 49 | DeregistrationFailed, 50 | 51 | #[fail(display = "pair request failed")] 52 | PairRequestFailed, 53 | 54 | #[fail(display = "peer response failed")] 55 | PeerResponseFailed, 56 | 57 | #[fail(display = "profile update failed")] 58 | ProfileUpdateFailed, 59 | 60 | #[fail(display = "call failed")] 61 | CallFailed, 62 | 63 | #[fail(display = "call refused")] 64 | CallRefused, 65 | 66 | #[fail(display = "lookup failed")] 67 | LookupFailed, 68 | 69 | #[fail(display = "no proof found for home")] 70 | HomeProofNotFound, 71 | 72 | #[fail(display = "persona profile expected")] 73 | PersonaProfileExpected, 74 | 75 | #[fail(display = "no homes found")] 76 | NoHomesFound, 77 | 78 | #[fail(display = "login failed")] 79 | LoginFailed, 80 | 81 | #[fail(display = "failed to get peer id")] 82 | FailedToGetPeerId, 83 | 84 | #[fail(display = "failed to authorize")] 85 | FailedToAuthorize, 86 | 87 | #[fail(display = "implementation error")] 88 | ImplementationError, 89 | } 90 | 91 | impl PartialEq for Error { 92 | fn eq(&self, other: &Error) -> bool { 93 | self.inner.get_context() == other.inner.get_context() 94 | } 95 | } 96 | 97 | impl Fail for Error { 98 | fn cause(&self) -> Option<&dyn Fail> { 99 | self.inner.cause() 100 | } 101 | 102 | fn backtrace(&self) -> Option<&Backtrace> { 103 | self.inner.backtrace() 104 | } 105 | } 106 | 107 | impl Display for Error { 108 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 109 | Display::fmt(&self.inner, f) 110 | } 111 | } 112 | 113 | impl Error { 114 | pub fn kind(&self) -> ErrorKind { 115 | *self.inner.get_context() 116 | } 117 | } 118 | 119 | impl From for Error { 120 | fn from(kind: ErrorKind) -> Error { 121 | Error { inner: Context::new(kind) } 122 | } 123 | } 124 | 125 | impl From> for Error { 126 | fn from(inner: Context) -> Error { 127 | Error { inner } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/id.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// The size of the key identifier in bytes. Since a version byte is prepended to the 4 | /// hash result, it is not a standard size. 5 | pub const KEY_ID_SIZE: usize = 20 + VERSION_SIZE; 6 | 7 | /// The serialized byte representation for the current version of the hash algorithm 8 | /// applied on the public key to obtain the key identifier 9 | pub const KEY_ID_VERSION1: u8 = b'\x01'; 10 | 11 | /// Implementation of Secp256k1::KeyId 12 | #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 13 | pub struct SecpKeyId(Vec); 14 | 15 | impl SecpKeyId { 16 | /// The key id serialized in a format that can be fed to [`from_bytes`] 17 | /// 18 | /// [`from_bytes`]: #method.from_bytes 19 | pub fn to_bytes(&self) -> Vec { 20 | self.0.clone() 21 | } 22 | 23 | /// Creates a key id from a byte slice possibly returned by the [`to_bytes`] method. 24 | /// 25 | /// # Error 26 | /// If `bytes` is not [`KEY_ID_SIZE`] long 27 | /// 28 | /// [`to_bytes`]: #method.to_bytes 29 | /// [`KEY_ID_SIZE`]: ../constant.KEY_ID_SIZE 30 | pub fn from_bytes>(bytes: D) -> Fallible { 31 | let bytes = bytes.as_ref(); 32 | ensure!(bytes.len() == KEY_ID_SIZE, "Identifier length is not {}", KEY_ID_SIZE); 33 | ensure!( 34 | bytes[0] == KEY_ID_VERSION1, 35 | "Only identifier version {:x} is supported", 36 | KEY_ID_VERSION1 37 | ); 38 | Ok(Self(bytes.to_owned())) 39 | } 40 | 41 | /// Serializes the key identifier as a `p2pkh` bitcoin address 42 | /// 43 | /// # Panics 44 | /// If internal invariants of the key id format are not maintained because of a bug 45 | pub fn to_p2pkh_addr(&self, network: &dyn Network) -> String { 46 | assert_eq!(self.0[0], KEY_ID_VERSION1); 47 | assert_eq!(self.0.len(), KEY_ID_SIZE); 48 | 49 | let prefix = network.p2pkh_addr(); 50 | debug_assert_eq!(prefix.len(), ADDR_PREFIX_SIZE); 51 | let mut address = Vec::with_capacity(ADDR_PREFIX_SIZE + KEY_ID_SIZE - VERSION_SIZE); 52 | address.extend_from_slice(prefix); 53 | address.extend_from_slice(&self.0[VERSION_SIZE..]); 54 | 55 | to_base58check(address) 56 | } 57 | 58 | /// Deserializes the key identifier from a `p2pkh` bitcoin address 59 | pub fn from_p2pkh_addr(addr: &str, network: &dyn Network) -> Fallible { 60 | let expected_prefix = network.p2pkh_addr(); 61 | debug_assert_eq!(expected_prefix.len(), ADDR_PREFIX_SIZE); 62 | debug_assert_eq!(ADDR_PREFIX_SIZE, 1); 63 | 64 | let data = from_base58check(addr)?; 65 | ensure!( 66 | data.len() == ADDR_PREFIX_SIZE + KEY_ID_SIZE - VERSION_SIZE, 67 | "Invalid length of address" 68 | ); 69 | 70 | let actual_prefix = &data[0..1]; 71 | ensure!( 72 | actual_prefix == expected_prefix, 73 | "Invalid network prefix found: {}", 74 | hex::encode(actual_prefix) 75 | ); 76 | 77 | let mut id = Vec::with_capacity(KEY_ID_SIZE); 78 | id.push(KEY_ID_VERSION1); 79 | id.extend_from_slice(&data[1..]); 80 | 81 | Ok(Self(id)) 82 | } 83 | } 84 | 85 | impl From<&SecpPublicKey> for SecpKeyId { 86 | // https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses 87 | fn from(pk: &SecpPublicKey) -> SecpKeyId { 88 | let hash = hash160(&pk.to_bytes()[..]); 89 | 90 | let mut id = Vec::with_capacity(KEY_ID_SIZE); 91 | id.push(KEY_ID_VERSION1); 92 | id.extend_from_slice(&*hash); 93 | 94 | SecpKeyId(id) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/sk.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | 3 | use super::*; 4 | use crate::PrivateKey; 5 | 6 | /// The size of the private key in the format used by [`to_bytes`] 7 | /// 8 | /// [`to_bytes`]: #method.to_bytes 9 | pub const PRIVATE_KEY_SIZE: usize = secp::util::SECRET_KEY_SIZE; 10 | 11 | /// Implementation of Secp256k1::PrivateKey 12 | #[derive(Debug, Clone, Eq, PartialEq)] 13 | pub struct SecpPrivateKey(secp::SecretKey); 14 | 15 | impl SecpPrivateKey { 16 | /// The private key serialized in a format that can be fed to [`from_bytes`] 17 | /// 18 | /// [`from_bytes`]: #method.from_bytes 19 | pub fn to_bytes(&self) -> Vec { 20 | self.0.serialize().to_vec() 21 | } 22 | 23 | /// Creates a public key from a byte slice possibly returned by the [`to_bytes`] method. 24 | /// 25 | /// # Error 26 | /// If `bytes` is rejected by `libsecp256k1::SecretKey::parse_slice` 27 | /// 28 | /// [`to_bytes`]: #method.to_bytes 29 | pub fn from_bytes>(bytes: D) -> Fallible { 30 | let sk = secp::SecretKey::parse_slice(bytes.as_ref()).map_err(SecpError::from)?; 31 | Ok(Self(sk)) 32 | } 33 | 34 | /// Serializes private key into wallet import format supported by many pre-HD wallets 35 | pub fn to_wif(&self, network: &dyn Network, usage: Bip178) -> String { 36 | let mut res = Vec::with_capacity(1 + 1 + PRIVATE_KEY_SIZE); 37 | res.extend_from_slice(network.wif()); 38 | res.extend_from_slice(&self.to_bytes()); 39 | res.extend_from_slice(usage.to_wif_suffix()); 40 | 41 | to_base58check(res) 42 | } 43 | 44 | /// Deserializes private key from wallet import format supported by many pre-HD wallets 45 | pub fn from_wif(wif: &str, network: &dyn Network) -> Fallible<(Self, Bip178)> { 46 | let data = from_base58check(wif)?; 47 | ensure!(data.len() > PRIVATE_KEY_SIZE, "WIF data is too short"); 48 | 49 | let expected_prefix = network.wif(); 50 | debug_assert_eq!(expected_prefix.len(), ADDR_PREFIX_SIZE); 51 | debug_assert_eq!(ADDR_PREFIX_SIZE, 1); 52 | 53 | let (actual_prefix, data) = data.split_at(ADDR_PREFIX_SIZE); 54 | ensure!( 55 | actual_prefix == expected_prefix, 56 | "Invalid network prefix found: {}", 57 | hex::encode(actual_prefix) 58 | ); 59 | 60 | let (sk_bytes, usage_bytes) = data.split_at(PRIVATE_KEY_SIZE); 61 | let sk = Self::from_bytes(sk_bytes)?; 62 | let usage = Bip178::from_wif_suffix(usage_bytes)?; 63 | 64 | Ok((sk, usage)) 65 | } 66 | } 67 | 68 | impl Add<&[u8]> for &SecpPrivateKey { 69 | type Output = Fallible; 70 | 71 | fn add(self, rhs: &[u8]) -> Self::Output { 72 | let mut sum = secp::SecretKey::parse_slice(rhs).map_err(SecpError::from)?; 73 | sum.tweak_add_assign(&self.0).map_err(SecpError::from)?; 74 | Ok(SecpPrivateKey(sum)) 75 | } 76 | } 77 | 78 | impl PrivateKey for SecpPrivateKey { 79 | fn public_key(&self) -> SecpPublicKey { 80 | let pk = secp::PublicKey::from_secret_key(&self.0); 81 | SecpPublicKey(pk) 82 | } 83 | 84 | /// # Panics 85 | /// There is a 2^-256 chance this message cannot be signed 86 | /// by this key. The C implementation in bitcoin does not 87 | /// fail, but this pure rust version does. Then we panic. 88 | fn sign>(&self, data: D) -> SecpSignature { 89 | let msg = Secp256k1::hash_message(data); 90 | let (sig, _recovery) = secp::sign(&msg, &self.0) 91 | .expect("Seems like we should have used the C version of secp256k1"); 92 | SecpSignature(sig) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /home-node/src/bin/mercury-home.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use futures::{Future, Stream}; 5 | use log::*; 6 | use tokio::net::tcp::TcpListener; 7 | use tokio_current_thread as reactor; 8 | 9 | use claims::repo::{DistributedPublicProfileRepository, FileProfileRepository}; 10 | use mercury_home_node::{config::*, server::*}; 11 | use mercury_home_protocol::{ 12 | crypto::*, handshake, mercury_capnp::server_dispatcher::HomeDispatcherCapnProto, *, 13 | }; 14 | use mercury_storage::asynch::fs::FileStore; 15 | use mercury_storage::asynch::KeyAdapter; 16 | 17 | fn main() { 18 | log4rs::init_file("log4rs.yml", Default::default()).unwrap(); 19 | let config = Config::new(); 20 | 21 | let signer = config.signer(); 22 | let validator = Rc::new(CompositeValidator::default()); 23 | 24 | let mut reactor = reactor::CurrentThread::new(); 25 | 26 | let local_storage = 27 | Rc::new(RefCell::new(FileProfileRepository::new(config.profile_backup_path()).unwrap())); 28 | 29 | // TODO make file path configurable, remove rpc_storage address config parameter 30 | // TODO use some kind of real distributed storage here on the long run 31 | let mut distributed_storage = 32 | FileProfileRepository::new(&std::path::PathBuf::from("/tmp/cuccos")).unwrap(); 33 | let avail_prof_res = reactor.block_on(distributed_storage.get_public(&signer.profile_id())); 34 | if avail_prof_res.is_err() { 35 | info!("Home node profile is not found on distributed public storage, saving node profile"); 36 | use multiaddr::ToMultiaddr; 37 | let home_multiaddr = config.listen_socket().to_multiaddr().unwrap(); 38 | let home_attrs = HomeFacet::new(vec![home_multiaddr], vec![]).to_attribute_map(); 39 | let home_profile = Profile::new(signer.public_key(), 1, vec![], home_attrs); 40 | reactor.block_on(distributed_storage.set_public(home_profile)).unwrap(); 41 | } else { 42 | info!("Home node profile is already available on distributed public storage"); 43 | } 44 | 45 | let host_db = Rc::new(RefCell::new(KeyAdapter::new( 46 | FileStore::new(config.host_relations_path()).unwrap(), 47 | ))); 48 | let distributed_storage = Rc::new(RefCell::new(distributed_storage)); 49 | let server = Rc::new(HomeServer::new(validator, distributed_storage, local_storage, host_db)); 50 | 51 | info!("Opening socket {} for incoming TCP clients", config.listen_socket()); 52 | let socket = TcpListener::bind(config.listen_socket()).expect("Failed to bind socket"); 53 | 54 | info!("Server started, waiting for clients"); 55 | let done = socket.incoming().for_each(move |socket| { 56 | info!("Accepted client connection, serving requests"); 57 | 58 | let server_clone = server.clone(); 59 | 60 | // TODO fill this in properly for each connection based on TLS authentication info 61 | let handshake_fut = handshake::temporary_unsafe_tcp_handshake_until_diffie_hellman_done( 62 | socket, 63 | signer.clone(), 64 | ) 65 | .map_err(|e| warn!("Client handshake failed: {:?}", e)) 66 | .and_then(move |(reader, writer, client_context)| { 67 | let home = HomeConnectionServer::new(Rc::new(client_context), server_clone.clone()) 68 | .map_err(|e| warn!("Failed to create server instance: {:?}", e))?; 69 | HomeDispatcherCapnProto::dispatch(Rc::new(home), reader, writer); 70 | Ok(()) 71 | }); 72 | 73 | reactor::spawn(handshake_fut); 74 | Ok(()) 75 | }); 76 | 77 | let res = reactor.block_on(done); 78 | debug!("Reactor finished with result: {:?}", res); 79 | info!("Server shutdown"); 80 | } 81 | -------------------------------------------------------------------------------- /home-protocol/protocol/mercury.capnp: -------------------------------------------------------------------------------- 1 | @0xbf11c96f54b8924d; 2 | 3 | 4 | ### TODO returning errors from the server and differentiating 5 | # remote errors from internal capnp errors is not solved/implemented yet 6 | 7 | 8 | # NOTE that though these types all hold complex serialized data, 9 | # communication must not depend on their format and interpretation 10 | using PublicKey = Data; 11 | using ProfileId = Data; 12 | using Signature = Data; 13 | using ApplicationId = Text; 14 | using AppMessageFrame = Data; 15 | using HomeInvitation = Data; 16 | using Profile = Data; 17 | using OwnProfile = Data; 18 | 19 | 20 | 21 | interface ProfileRepo 22 | { 23 | get @0 (profileId: ProfileId) -> (profile: Profile); 24 | 25 | # TODO what filter criteria should we have in list()? 26 | # list @1 () -> (profiles: List(Profile)); 27 | # resolve @2 (profileUrl: Text) -> (profile: Profile); 28 | } 29 | 30 | # TODO maybe we could optimize pairing data by omitting most fields, signature and sender profile_id is mandatory 31 | struct RelationHalfProof 32 | { 33 | relationType @0 : Text; 34 | signerId @1 : ProfileId; 35 | signerPubKey @2 : PublicKey; 36 | peerId @3 : ProfileId; 37 | signature @4 : Signature; 38 | } 39 | 40 | struct RelationProof 41 | { 42 | relationType @0 : Text; 43 | aId @1 : ProfileId; 44 | aPubKey @2 : PublicKey; 45 | aSignature @3 : Signature; 46 | bId @4 : ProfileId; 47 | bPubKey @5 : PublicKey; 48 | bSignature @6 : Signature; 49 | } 50 | 51 | 52 | 53 | interface AppMessageListener 54 | { 55 | receive @0 (message: AppMessageFrame); 56 | error @1 (error: Text); 57 | } 58 | 59 | 60 | 61 | interface Home extends (ProfileRepo) 62 | { 63 | claim @0 (profileId: ProfileId) -> (hostingProof: RelationProof); # consider returning List(RelationProof) 64 | register @1 (halfProof: RelationHalfProof, invite: HomeInvitation) -> (hostingProof: RelationProof); 65 | login @2 (hostingProof : RelationProof) -> (session : HomeSession); 66 | 67 | pairRequest @3 (halfProof: RelationHalfProof); # NOTE called on acceptor's home 68 | pairResponse @4 (relation: RelationProof); # NOTE called on requestor's home 69 | 70 | call @5 (relation: RelationProof, app: ApplicationId, initPayload: AppMessageFrame, 71 | toCaller: AppMessageListener) -> (toCallee: AppMessageListener); 72 | } 73 | 74 | 75 | 76 | struct CallRequest 77 | { 78 | relation @0 : RelationProof; 79 | initPayload @1 : AppMessageFrame; 80 | toCaller @2 : AppMessageListener; 81 | } 82 | 83 | interface CallListener 84 | { 85 | receive @0 (call: CallRequest) -> (toCallee: AppMessageListener); 86 | error @1 (error: Text); 87 | } 88 | 89 | 90 | 91 | struct ProfileEvent 92 | { 93 | union 94 | { 95 | unknown @0 : Data; 96 | pairingRequest @1 : RelationHalfProof; 97 | pairingResponse @2 : RelationProof; 98 | } 99 | } 100 | 101 | interface ProfileEventListener 102 | { 103 | receive @0 (event: ProfileEvent); 104 | error @1 (error: Text); 105 | } 106 | 107 | 108 | interface HomeSession 109 | { 110 | backup @0 (ownProfile: OwnProfile); 111 | restore @1 () -> (ownProfile: OwnProfile); 112 | unregister @2 (newHome: Profile); # NOTE closes session after successful call 113 | 114 | events @3 (eventListener: ProfileEventListener); 115 | checkinApp @4 (app: ApplicationId, callListener: CallListener); 116 | 117 | # TODO consider removing this, used mostly for testing 118 | ping @5 (txt : Text) -> (pong : Text); 119 | } 120 | -------------------------------------------------------------------------------- /prometheus-cli/README.md: -------------------------------------------------------------------------------- 1 | # Use cases 2 | 3 | ## Help 4 | 5 | TODO ???remove profile??? 6 | 7 | ``` 8 | > prometheus help 9 | Subcommands are: 10 | status 11 | list (profiles/followers) 12 | show profile 13 | create (profile/link) 14 | remove link 15 | set (active-profile/attribute) 16 | clear attribute 17 | help 18 | version 19 | ``` 20 | 21 | 22 | ## Version info 23 | 24 | ``` 25 | > prometheus version 26 | Prometheus version 0.0.1-alpha1 built on Morpheus version 0.0.1-alpha2 27 | ``` 28 | 29 | 30 | ## Status 31 | 32 | Profile number is a Bip32 path fragment used to derive the keys for this profile. 33 | 34 | ``` 35 | > prometheus status 36 | Your active profile is number 1, id: abcdef123456789 37 | ``` 38 | 39 | 40 | ## Create new profile 41 | 42 | ``` 43 | > prometheus create profile [--set-active? or --keep-current?] 44 | profile number 2, id: cab123...987 45 | ``` 46 | 47 | 48 | ## List profiles 49 | 50 | ``` 51 | > prometheus list profiles 52 | (active) profile number 1, id: abcdef123456789 53 | profile number 2, id: cab123...987 54 | ``` 55 | 56 | 57 | ## Activate profile 58 | 59 | ``` 60 | > prometheus set active-profile [--number=1] or [--id=abcdef123456789] 61 | Profile number 1, id: abcdef123456789 is now your active default profile. 62 | ``` 63 | 64 | 65 | ## Remove profile 66 | 67 | TODO what does `remove profile` mean? Is it possible at all? Is it needed? 68 | 69 | 70 | ## Show profile information 71 | 72 | ``` 73 | > prometheus show profile [--id=abcdef123456789] 74 | Details of profile id abcdef123456789: 75 | Public attributes: 76 | "username" = "test", 77 | "com.twitter.account" = "cool-influencer", 78 | "com.youtube.account" = "influence-channel", 79 | ... 80 | Public links: 81 | id: "fff...aaa", type: follow, peer_id: feed....789 82 | ... 83 | 84 | This profile belongs to your keyvault and is your currently active default one. 85 | Private attributes: 86 | "gender" = "male", 87 | "birthday" = "2000", 88 | Private links: 89 | type: "rebellion", peer_id: 2020..2077 90 | ``` 91 | 92 | 93 | ## Set attribute 94 | 95 | ``` 96 | > prometheus set attribute --key=com.linkedin.account --value=tech-expert-123456 [--my_profile_id=abcdef123456789] 97 | Attribute "com.linkedin.account" was set to value "tech-expert-123456" for [active/specified] profile. 98 | ``` 99 | 100 | 101 | ## Clear attribute 102 | 103 | ``` 104 | > prometheus clear attribute --key=com.linkedin.account [--my_profile_id=abcdef123456789] 105 | Attribute "com.linkedin.account" was cleared from [active/specified] profile. 106 | ``` 107 | 108 | 109 | ## Create new link 110 | 111 | ``` 112 | > prometheus create link --peer_id=feed....789 [--my_profile_id=abcdef123456789] [--relation_type=follow] 113 | Created link "aaa...fff" to "feed....789" with type "follow" for [active/specified] profile. 114 | ``` 115 | 116 | 117 | ## Remove link 118 | 119 | ``` 120 | > prometheus remove link --link_id=aaa...fff [--my_profile_id=abcdef123456789] 121 | Removed link "aaa...fff" from [active/specified] profile. 122 | ``` 123 | 124 | 125 | ## List public followers 126 | 127 | This command will list the incoming links pointing to your profile (i.e. your followers). 128 | Note that outgoing links (your subscriptions) are shown by command `show profile`. 129 | 130 | ``` 131 | > prometheus list followers [--my_profile_id=abcdef123456789] 132 | Profiles publicly following [active/specified] profile are: 133 | Profile id: baba...666 134 | Attributes: 135 | "com.twitter.account" = "bestfan", 136 | ... 137 | Profile id: beef...42 138 | Attributes: 139 | "com.linkedin.account" = "btchodler", 140 | ... 141 | ``` 142 | -------------------------------------------------------------------------------- /doc/adr/0003-logging.md: -------------------------------------------------------------------------------- 1 | # 0003. Logging in mercury stack 2 | 3 | Date: 2018-07-19 4 | 5 | ## Status 6 | 7 | RFC 8 | 9 | ## Context 10 | 11 | It'd be convenient to have a well-defined logging strategy for our applications. It makes controlling logging easier, and provides post-mortem debugging with useful data. 12 | 13 | ## Decision 14 | 15 | We use the log4rs (https://github.com/sfackler/log4rs) crate for logging purposes. 16 | 17 | ### Loglevels ### 18 | 19 | We choose loglevels for messages based on the following criteria: 20 | - error!(): When an unexpected condition occurs which likely results in application termination. Usually coupled with an Error struct. 21 | - warn!(): When some error happens, which can be likely recovered. Usually coupled with an Error struct. 22 | - info!(): Status message about application level progress, important state changes. 23 | - debug!(): Additional program state info that can be used for debugging. 24 | - trace!(): High frequency or very detailed messages, that helps precisely tracking program execution/state. 25 | 26 | ### Controlling loglevels from the command line or config file ### 27 | 28 | The default loglevel is "info", this means that info!(), warn!() and error!() level messages are shown. Loglevel can changed by -v/--verbose or -s/--silent command line options. 29 | - -s -s -s => loglevel == none 30 | - -s -s => loglevel == err 31 | - -s => loglevel == warn 32 | - -v => loglevel == debug 33 | - -v -v => loglevel == trace 34 | 35 | [Optional proposal] 36 | 37 | Loglevel can be specified with -l/--loglevel command line option (where loglevel can be trace, debug, info, warn, error, none) 38 | 39 | ### Controlling log output ### 40 | 41 | By default log output shall be sent to the console and to a file defaulting to a well defined place (e.g.: /var/log/.log). Two options can be used to specify alternative output: 42 | - --syslog: send log output to syslog 43 | - --logfile : send log output to a file 44 | 45 | Log rotation should be handled by an external package (logrotate or similiar) if possible. 46 | 47 | ### Printing Error structs ### 48 | 49 | It's very important for post-mortem debugging that error messages are logged properly. Usually an error consists of two parts: 50 | - message (what went wrong?) 51 | - error code (how the system responded?), this is usually having a numeric and a textual description 52 | 53 | Example: 54 | 55 | In case of a network issue we might face connection issues. We should report it as: 56 | 57 | "failed to connect to homeserver at xxx.xxx.xxx:xx (111: Connection refused)" 58 | 59 | Error structs can be stacked. We have to take care to log internal errors as well to see the full error stack. 60 | 61 | ### Log messages ### 62 | 63 | Log messages should be compact readable and informative. It's hard to give exact rules, but the following advices must be followed. 64 | 65 | - avoid irrelevant messages (e.g. instead of the message "program started", one can print also important arguments, program version, ... too) 66 | - for error messages always provide enough context for later analysis (important state variables) 67 | - avoid dumping long binary data, public keys, memory garbage. If binary content should be tracked, consider logging some hash of the content. 68 | - messages which are issued from tight loops with big frequency, should always happen on the trace!() loglevel to avoid flooding the log output 69 | - avoid writing multiple sentences (instead use single sentence without capitalization), that helps log processing with scripts 70 | - types that are usually written into logs shall implement the Display trait, to provide compact and informative output 71 | 72 | ## Consequences 73 | 74 | By following these guidelines we'd have consistent, readable logs that help debugging and operation. -------------------------------------------------------------------------------- /examples/TheButton/src/main.rs: -------------------------------------------------------------------------------- 1 | mod init; 2 | pub mod options; 3 | pub mod publisher; 4 | pub mod subscriber; 5 | 6 | use std::net::SocketAddr; 7 | use std::path::PathBuf; 8 | use std::rc::Rc; 9 | use std::sync::Arc; 10 | 11 | use failure::{err_msg, format_err, Fallible}; 12 | use futures::prelude::*; 13 | use log::*; 14 | use structopt::StructOpt; 15 | use tokio_current_thread as reactor; 16 | use tokio_signal::unix::{Signal, SIGINT}; 17 | 18 | use crate::options::*; 19 | use crate::publisher::Server; 20 | use crate::subscriber::Client; 21 | use keyvault::{PrivateKey as KeyVaultPrivateKey, PublicKey as KeyVaultPublicKey}; 22 | use mercury_home_protocol::*; 23 | use prometheus::dapp::{dapp_session::*, websocket}; 24 | 25 | pub fn signal_recv(sig: i32) -> Box> { 26 | Box::new( 27 | Signal::new(sig).flatten_stream().map_err(|e| format_err!("Failed to get signal: {}", e)), 28 | ) 29 | } 30 | 31 | #[derive(Clone)] 32 | pub struct AppContext { 33 | dapp_service: Rc, 34 | dapp_profile_id: ProfileId, 35 | home_id: ProfileId, 36 | dapp_id: ApplicationId, 37 | } 38 | 39 | impl AppContext { 40 | pub fn new( 41 | profile_privatekey_file: &PathBuf, 42 | home_pubkey: &PublicKey, 43 | home_addr: &SocketAddr, 44 | reactor: &mut reactor::CurrentThread, 45 | ) -> Fallible { 46 | let dapp_service = Rc::new(websocket::client::ServiceClient::new()); 47 | 48 | // TODO private key must never be exposed directly, only a Signer (just like hardware wallets) 49 | let private_key_bytes = std::fs::read(profile_privatekey_file)?; 50 | let private_key_ed = ed25519::EdPrivateKey::from_bytes(private_key_bytes)?; 51 | let private_key = PrivateKey::from(private_key_ed); 52 | let dapp_profile_id = private_key.public_key().key_id(); 53 | 54 | let home_id = home_pubkey.key_id(); 55 | let this = Self { 56 | dapp_service, 57 | dapp_profile_id, 58 | home_id, 59 | dapp_id: ApplicationId("TheButton-dApp-Sample".into()), 60 | }; 61 | init::ensure_registered_to_home(reactor, private_key, home_addr, &this)?; 62 | Ok(this) 63 | } 64 | } 65 | 66 | #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd)] 67 | pub enum OnFail { 68 | Terminate, 69 | Retry, 70 | } 71 | 72 | fn main() -> Fallible<()> { 73 | let options = Options::from_args(); 74 | log4rs::init_file(&options.logger_config, Default::default()).unwrap(); 75 | 76 | // Creating a reactor 77 | let mut reactor = reactor::CurrentThread::new(); 78 | 79 | debug!("Parsed options, initializing application"); 80 | 81 | let priv_key_file = match options.command { 82 | Command::Pubhlisher(ref cfg) => &cfg.private_key_file, 83 | Command::Subscriber(ref cfg) => &cfg.private_key_file, 84 | }; 85 | 86 | // Constructing application context from command line args 87 | let appcx = 88 | AppContext::new(priv_key_file, &options.home_pubkey, &options.home_address, &mut reactor)?; 89 | 90 | // Creating application object 91 | let app_fut = match options.command { 92 | Command::Pubhlisher(cfg) => Server::new(cfg, appcx).into_future(), 93 | Command::Subscriber(cfg) => Client::new(cfg, appcx).into_future(), 94 | }; 95 | 96 | debug!("Initialized application, running"); 97 | 98 | // SIGINT is terminating the server 99 | let sigint_fut = signal_recv(SIGINT) 100 | .into_future() 101 | .map(|_| info!("received SIGINT, terminating application")) 102 | .map_err(|(err, _)| err); 103 | 104 | reactor.block_on(app_fut.select(sigint_fut).map(|(item, _)| item).map_err(|(err, _)| err))?; 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /mercury-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | use did::vault::ProfileVault; 4 | use keyvault::PublicKey as KeyVaultPublicKey; 5 | use keyvault_wasm::*; 6 | 7 | #[wasm_bindgen(js_name = SignedMessage)] 8 | pub struct JsSignedMessage { 9 | public_key: JsPublicKey, 10 | message: Box<[u8]>, 11 | signature: JsSignature, 12 | } 13 | 14 | #[wasm_bindgen(js_class = SignedMessage)] 15 | impl JsSignedMessage { 16 | #[wasm_bindgen(constructor)] 17 | pub fn new(public_key: &JsPublicKey, message: &[u8], signature: &JsSignature) -> Self { 18 | Self::new_owned( 19 | public_key.to_owned(), 20 | message.to_owned().into_boxed_slice(), 21 | signature.to_owned(), 22 | ) 23 | } 24 | 25 | #[wasm_bindgen(getter, js_name = publicKey)] 26 | pub fn public_key(&self) -> JsPublicKey { 27 | self.public_key.to_owned() 28 | } 29 | 30 | #[wasm_bindgen(getter)] 31 | pub fn message(&self) -> Box<[u8]> { 32 | self.message.clone() 33 | } 34 | 35 | #[wasm_bindgen(getter)] 36 | pub fn signature(&self) -> JsSignature { 37 | self.signature.to_owned() 38 | } 39 | 40 | pub fn validate(&self) -> bool { 41 | self.public_key.inner().verify(&self.message, &self.signature.inner()) 42 | } 43 | 44 | #[wasm_bindgen(js_name = validateWithId)] 45 | pub fn validate_with_id(&self, signer_id: &JsKeyId) -> bool { 46 | self.public_key.validate_id(signer_id) && self.validate() 47 | } 48 | } 49 | 50 | impl JsSignedMessage { 51 | pub fn new_owned(public_key: JsPublicKey, message: Box<[u8]>, signature: JsSignature) -> Self { 52 | Self { public_key, message, signature } 53 | } 54 | } 55 | 56 | #[wasm_bindgen(js_name = Vault)] 57 | pub struct JsVault { 58 | inner: did::vault::HdProfileVault, 59 | } 60 | 61 | #[wasm_bindgen(js_class = Vault)] 62 | impl JsVault { 63 | #[wasm_bindgen(constructor)] 64 | pub fn new(seed_phrase: &str) -> Result { 65 | let seed = keyvault::Seed::from_bip39(seed_phrase).map_err(err_to_js)?; 66 | let vault = did::vault::HdProfileVault::create(seed); 67 | Ok(Self { inner: vault }) 68 | } 69 | 70 | pub fn serialize(&self) -> Result { 71 | serde_json::to_string_pretty(&self.inner).map_err(err_to_js) 72 | } 73 | 74 | pub fn deserialize(from: &str) -> Result { 75 | let vault = serde_json::from_str(&from).map_err(err_to_js)?; 76 | Ok(Self { inner: vault }) 77 | } 78 | 79 | pub fn profiles(&self) -> Result, JsValue> { 80 | let profiles = self 81 | .inner 82 | .profiles() 83 | .map_err(err_to_js)? 84 | .iter() 85 | .map(|rec| JsValue::from_str(&rec.id().to_string())) 86 | .collect::>(); 87 | Ok(profiles.into_boxed_slice()) 88 | } 89 | 90 | #[wasm_bindgen(js_name = activeId)] 91 | pub fn active_id(&self) -> Result, JsValue> { 92 | let active_id = self.inner.get_active().map_err(err_to_js)?; 93 | let active_str = active_id.map(|id| JsKeyId::from(id)); 94 | Ok(active_str) 95 | } 96 | 97 | #[wasm_bindgen(js_name = createId)] 98 | pub fn create_id(&mut self) -> Result { 99 | let key = self.inner.create_key(None).map_err(err_to_js)?; 100 | Ok(JsKeyId::from(key.key_id())) 101 | } 102 | 103 | pub fn sign(&self, key_id: &JsKeyId, message: &[u8]) -> Result { 104 | let signed_message = self.inner.sign(&key_id.inner(), message).map_err(err_to_js)?; 105 | 106 | let result = JsSignedMessage::new_owned( 107 | JsPublicKey::from(signed_message.public_key().to_owned()), 108 | signed_message.message().to_owned().into_boxed_slice(), 109 | JsSignature::from(signed_message.signature().to_owned()), 110 | ); 111 | Ok(result) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /claims/src/journal.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::SystemTime; 3 | 4 | use futures::prelude::*; 5 | use serde_derive::{Deserialize, Serialize}; 6 | 7 | use crate::model::*; 8 | 9 | pub type TransactionId = Vec; 10 | 11 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 12 | pub enum JournalState { 13 | TimeStamp(SystemTime), // TODO is this an absolute timestamp or can this be relaxed? 14 | Transaction(TransactionId), 15 | Block { height: u64, hash: Vec }, 16 | } 17 | 18 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 19 | pub enum ProfileAuthOperation { 20 | Grant(ProfileGrant), 21 | Revoke(ProfileGrant), 22 | Remove(ProfileId), 23 | } 24 | 25 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 26 | pub struct ProfileTransaction { 27 | // new_state: ProfileAuthData, // NOTE it's harder to validate state diffs than to add explicit operations 28 | operations: Vec, 29 | succeeds_predecessors: Vec, 30 | } 31 | 32 | impl ProfileTransaction { 33 | pub fn new(ops: &[ProfileAuthOperation], succeeds_predecessors: &[JournalState]) -> Self { 34 | Self { operations: ops.to_owned(), succeeds_predecessors: succeeds_predecessors.to_owned() } 35 | } 36 | 37 | pub fn ops(&self) -> &[ProfileAuthOperation] { 38 | &self.operations 39 | } 40 | 41 | pub fn predecessors(&self) -> &[JournalState] { 42 | &self.succeeds_predecessors 43 | } 44 | } 45 | 46 | pub trait ProfileAuthJournal { 47 | fn last_state(&self) -> AsyncFallible; 48 | fn transactions( 49 | &self, 50 | id: &ProfileId, 51 | until_state: Option, 52 | ) -> AsyncFallible>; 53 | 54 | fn get(&self, id: &ProfileId, state: Option) -> AsyncFallible; 55 | 56 | // TODO do we need an explicit Grant here or will it be handled in within the implementation? 57 | fn update(&self, operations: &[ProfileAuthOperation]) -> AsyncFallible; 58 | fn remove(&self, id: &ProfileId) -> AsyncFallible; 59 | } 60 | 61 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] 62 | pub struct InMemoryProfileAuthJournal { 63 | profiles: HashMap, 64 | } 65 | 66 | impl InMemoryProfileAuthJournal { 67 | pub fn apply(_auth: &ProfileAuthData, _ops: &[ProfileAuthOperation]) -> ProfileAuthData { 68 | unimplemented!() 69 | } 70 | } 71 | 72 | // TODO do something better than just compile 73 | impl ProfileAuthJournal for InMemoryProfileAuthJournal { 74 | fn last_state(&self) -> AsyncFallible { 75 | Box::new(Ok(JournalState::Transaction(vec![])).into_future()) 76 | } 77 | 78 | fn transactions( 79 | &self, 80 | _id: &ProfileId, 81 | _until_state: Option, 82 | ) -> AsyncFallible> { 83 | Box::new(Ok(vec![]).into_future()) 84 | } 85 | 86 | fn get(&self, id: &ProfileId, state: Option) -> AsyncFallible { 87 | let auth = ProfileAuthData::implicit(id); 88 | let fut = self.transactions(id, state).map(move |transactions| { 89 | let ops: Vec<_> = transactions 90 | .iter() 91 | .flat_map(|transaction| transaction.ops().iter().cloned()) 92 | .collect(); 93 | Self::apply(&auth, &ops) 94 | }); 95 | 96 | Box::new(fut) 97 | } 98 | 99 | fn update(&self, operations: &[ProfileAuthOperation]) -> AsyncFallible { 100 | let transaction = ProfileTransaction::new(operations, &[]); 101 | Box::new(Ok(transaction).into_future()) 102 | } 103 | 104 | fn remove(&self, id: &ProfileId) -> AsyncFallible { 105 | let transaction = 106 | ProfileTransaction::new(&[ProfileAuthOperation::Remove(id.to_owned())], &[]); 107 | Box::new(Ok(transaction).into_future()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /keyvault-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | use keyvault::multicipher; 4 | use keyvault::PublicKey as KeyVaultPublicKey; 5 | 6 | // NOTE Always receive function arguments as references (as long as bindgen allows) 7 | // and return results by value. Otherwise the generated code may destroy 8 | // JS variables by moving out underlying pointers 9 | // (at least in your custom structs like SignedMessage below). 10 | 11 | pub fn err_to_js(e: E) -> JsValue { 12 | JsValue::from(e.to_string()) 13 | } 14 | 15 | #[wasm_bindgen(js_name = KeyId)] 16 | #[derive(Clone, Debug)] 17 | pub struct JsKeyId { 18 | inner: multicipher::MKeyId, 19 | } 20 | 21 | #[wasm_bindgen(js_class = KeyId)] 22 | impl JsKeyId { 23 | #[wasm_bindgen(constructor)] 24 | pub fn new(key_id_str: &str) -> Result { 25 | let inner: multicipher::MKeyId = key_id_str.parse().map_err(err_to_js)?; 26 | Ok(Self { inner }) 27 | } 28 | 29 | #[wasm_bindgen] 30 | pub fn prefix() -> String { 31 | multicipher::MKeyId::PREFIX.to_string() 32 | } 33 | 34 | #[wasm_bindgen(js_name = toString)] 35 | pub fn to_string(&self) -> String { 36 | self.inner.to_string() 37 | } 38 | } 39 | 40 | impl From for JsKeyId { 41 | fn from(inner: multicipher::MKeyId) -> Self { 42 | Self { inner } 43 | } 44 | } 45 | 46 | #[wasm_bindgen(js_name = PublicKey)] 47 | #[derive(Clone, Debug)] 48 | pub struct JsPublicKey { 49 | inner: multicipher::MPublicKey, 50 | } 51 | 52 | #[wasm_bindgen(js_class = PublicKey)] 53 | impl JsPublicKey { 54 | #[wasm_bindgen(constructor)] 55 | pub fn new(pub_key_str: &str) -> Result { 56 | let inner: multicipher::MPublicKey = pub_key_str.parse().map_err(err_to_js)?; 57 | Ok(Self { inner }) 58 | } 59 | 60 | #[wasm_bindgen] 61 | pub fn prefix() -> String { 62 | multicipher::MPublicKey::PREFIX.to_string() 63 | } 64 | 65 | #[wasm_bindgen(js_name = keyId)] 66 | pub fn key_id(&self) -> JsKeyId { 67 | JsKeyId { inner: self.inner.key_id() } 68 | } 69 | 70 | #[wasm_bindgen(js_name = validateId)] 71 | pub fn validate_id(&self, key_id: &JsKeyId) -> bool { 72 | self.inner.validate_id(&key_id.inner) 73 | } 74 | 75 | #[wasm_bindgen(js_name = toString)] 76 | pub fn to_string(&self) -> String { 77 | self.inner.to_string() 78 | } 79 | } 80 | 81 | impl From for JsPublicKey { 82 | fn from(inner: multicipher::MPublicKey) -> Self { 83 | Self { inner } 84 | } 85 | } 86 | 87 | #[wasm_bindgen(js_name = Signature)] 88 | #[derive(Clone, Debug)] 89 | pub struct JsSignature { 90 | inner: multicipher::MSignature, 91 | } 92 | 93 | #[wasm_bindgen(js_class = Signature)] 94 | impl JsSignature { 95 | #[wasm_bindgen(constructor)] 96 | pub fn new(sign_str: &str) -> Result { 97 | let inner: multicipher::MSignature = sign_str.parse().map_err(err_to_js)?; 98 | Ok(Self { inner }) 99 | } 100 | 101 | #[wasm_bindgen] 102 | pub fn prefix() -> String { 103 | multicipher::MSignature::PREFIX.to_string() 104 | } 105 | 106 | #[wasm_bindgen(js_name = toString)] 107 | pub fn to_string(&self) -> String { 108 | self.inner.to_string() 109 | } 110 | } 111 | 112 | impl From for JsSignature { 113 | fn from(inner: multicipher::MSignature) -> Self { 114 | Self { inner } 115 | } 116 | } 117 | 118 | pub trait Wraps: From { 119 | fn inner(&self) -> &T; 120 | } 121 | 122 | impl Wraps for JsKeyId { 123 | fn inner(&self) -> &multicipher::MKeyId { 124 | &self.inner 125 | } 126 | } 127 | 128 | impl Wraps for JsPublicKey { 129 | fn inner(&self) -> &multicipher::MPublicKey { 130 | &self.inner 131 | } 132 | } 133 | 134 | impl Wraps for JsSignature { 135 | fn inner(&self) -> &multicipher::MSignature { 136 | &self.inner 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /prometheus/src/dapp/dapp_session.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use futures::{Future, Stream}; 4 | 5 | use crate::dapp::user_interactor::UserInteractor; 6 | use crate::*; 7 | use claims::model::*; 8 | use mercury_home_protocol::{ 9 | AppMessageFrame, AppMsgSink, AppMsgStream, ApplicationId, IncomingCall, RelationProof, 10 | }; 11 | 12 | pub struct DAppCall { 13 | pub outgoing: AppMsgSink, 14 | pub incoming: AppMsgStream, 15 | } 16 | 17 | // - if messaging dApp still does not have access rights to sender profile then request access 18 | // (in first iteration automatically approve it) 19 | // - instantiate some kind of client to a Home node, similarly as done in Connect 20 | // - potentially initiate pairing with profile if not done yet 21 | // - send message via client to target profile 22 | pub trait Relation { 23 | fn proof(&self) -> &RelationProof; 24 | // TODO fn send(&self, message: &MessageContent) -> AsyncFallible<()>; 25 | fn call(&self, init_payload: AppMessageFrame) -> AsyncFallible; 26 | } 27 | 28 | pub enum DAppEvent { 29 | PairingResponse(Box), 30 | //TODO Message(...) 31 | Call(Box), // TODO wrap IncomingCall so as call.answer() could return a DAppCall directly 32 | } 33 | 34 | pub trait DAppSession { 35 | fn dapp_id(&self) -> &ApplicationId; 36 | 37 | // After the session was initialized, the profile is selected and can be queried any time 38 | fn selected_profile(&self) -> &ProfileId; 39 | 40 | //fn app_storage(&self) -> AsyncFallible>; 41 | 42 | fn relations(&self) -> AsyncFallible>>; 43 | fn relation(&self, id: &ProfileId) -> AsyncFallible>>; 44 | fn initiate_relation(&self, with_profile: &ProfileId) -> AsyncFallible<()>; 45 | 46 | fn checkin(&self) -> AsyncFallible>>; 47 | } 48 | 49 | pub struct DAppSessionImpl { 50 | dapp_id: ApplicationId, 51 | profile_id: ProfileId, 52 | } 53 | 54 | impl DAppSessionImpl { 55 | pub fn new(dapp_id: ApplicationId, profile_id: ProfileId) -> Self { 56 | Self { profile_id, dapp_id } 57 | } 58 | } 59 | 60 | impl DAppSession for DAppSessionImpl { 61 | fn dapp_id(&self) -> &ApplicationId { 62 | &self.dapp_id 63 | } 64 | 65 | fn selected_profile(&self) -> &ProfileId { 66 | &self.profile_id 67 | } 68 | 69 | //fn app_storage(&self) -> AsyncFallible> { 70 | // unimplemented!() 71 | //} 72 | 73 | fn relations(&self) -> AsyncFallible>> { 74 | unimplemented!() 75 | } 76 | 77 | fn relation(&self, _id: &ProfileId) -> AsyncFallible>> { 78 | unimplemented!() 79 | } 80 | 81 | fn initiate_relation(&self, _with_profile: &ProfileId) -> AsyncFallible<()> { 82 | unimplemented!() 83 | } 84 | 85 | fn checkin(&self) -> AsyncFallible>> { 86 | unimplemented!() 87 | } 88 | } 89 | 90 | pub trait DAppSessionService { 91 | // NOTE this implicitly asks for user interaction (through UI) selecting a profile to be used with the app 92 | fn dapp_session(&self, app: ApplicationId) -> AsyncFallible>; 93 | } 94 | 95 | pub struct DAppSessionServiceImpl { 96 | interactor: Arc>, 97 | } 98 | 99 | impl DAppSessionServiceImpl { 100 | pub fn new(interactor: Arc>) -> Self { 101 | Self { interactor } 102 | } 103 | } 104 | 105 | impl DAppSessionService for DAppSessionServiceImpl { 106 | fn dapp_session(&self, app: ApplicationId) -> AsyncFallible> { 107 | let interactor = match self.interactor.try_read() { 108 | Ok(interactor) => interactor, 109 | Err(e) => { 110 | error!("BUG: failed to lock user interactor: {}", e); 111 | unreachable!() 112 | } 113 | }; 114 | let session_fut = interactor.select_profile().map(move |profile| { 115 | Arc::new(DAppSessionImpl::new(app, profile)) as Arc 116 | }); 117 | Box::new(session_fut) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /keyvault/src/secp256k1/ext_pk.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub const XPUB_DATA_SIZE: usize = 78; 4 | 5 | /// Implementation of Secp256k1::ExtendedPublicKey 6 | pub struct SecpExtPublicKey { 7 | pub(super) depth: u8, 8 | pub(super) parent_fingerprint: Vec, 9 | pub(super) idx: u32, 10 | pub(super) chain_code: ChainCode, 11 | pub(super) pk: SecpPublicKey, 12 | } 13 | 14 | impl SecpExtPublicKey { 15 | /// #Panics 16 | /// If the resulting private key is 0, we should have looped to fix that, but this implementation 17 | /// just panics then 18 | pub(crate) fn cook_new ()>(&self, idx: u32, recipe: F) -> Self { 19 | let parent = self; 20 | let salt = &parent.chain_code.to_bytes(); 21 | // This unwrap would only panic if the digest algorithm had some inconsistent 22 | // generic parameters, but the SHA512 we use is consistent with itself 23 | let mut hasher = HmacSha512::new_varkey(salt).unwrap(); 24 | 25 | recipe(&mut hasher); 26 | 27 | let hash_arr = hasher.result().code(); 28 | let hash_bytes = hash_arr.as_slice(); 29 | 30 | let sk_bytes = &hash_bytes[..PRIVATE_KEY_SIZE]; 31 | let cc_bytes = &hash_bytes[PRIVATE_KEY_SIZE..]; 32 | 33 | let depth = parent.depth + 1; 34 | let parent_pk = parent.as_public_key(); 35 | // this uses the compressed pk opposed to the addr that uses the uncompressed pk format: 36 | let hash = hash160(parent_pk.to_bytes()); 37 | let parent_fingerprint = Vec::from(&hash[..4]); 38 | let chain_code = ChainCode::from_bytes(cc_bytes).unwrap(); 39 | let pk = (&parent.pk + sk_bytes) 40 | .expect("We should have implemented that loop in the BIP32 specs"); 41 | 42 | Self { depth, parent_fingerprint, idx, chain_code, pk } 43 | } 44 | 45 | /// Serializes the extended public key according to the format defined in [`BIP32`] 46 | /// 47 | /// [`BIP32`]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format 48 | pub fn to_xpub(&self, network: &dyn Network) -> String { 49 | let mut res = Vec::with_capacity(XPUB_DATA_SIZE); 50 | res.extend_from_slice(network.bip32_xpub()); 51 | res.push(self.depth); 52 | res.extend_from_slice(&self.parent_fingerprint); 53 | res.extend_from_slice(&self.idx.to_be_bytes()); 54 | res.extend_from_slice(&self.chain_code.to_bytes()); 55 | res.extend_from_slice(&self.pk.to_bytes()); 56 | 57 | to_base58check(res) 58 | } 59 | 60 | /// Deserializes the extended public key from the format defined in [`BIP32`] 61 | /// 62 | /// [`BIP32`]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format 63 | pub fn from_xpub(xprv: &str, network: &dyn Network) -> Fallible { 64 | let data = from_base58check(xprv)?; 65 | ensure!(data.len() == XPUB_DATA_SIZE, "Length of data must be {}", XPUB_DATA_SIZE); 66 | 67 | let actual_prefix = &data[0..4]; 68 | ensure!( 69 | actual_prefix == network.bip32_xpub(), 70 | "Invalid network prefix found: {}", 71 | hex::encode(actual_prefix) 72 | ); 73 | let depth = data[4]; 74 | let parent_fingerprint = data[5..9].to_vec(); 75 | let idx = { 76 | let mut idx_bytes = [0u8; 4]; 77 | idx_bytes.copy_from_slice(&data[9..13]); 78 | u32::from_be_bytes(idx_bytes) 79 | }; 80 | let chain_code = { 81 | let chain_code_bytes = &data[13..45]; 82 | ChainCode::from_bytes(chain_code_bytes)? 83 | }; 84 | let pk = { 85 | let pk_bytes = &data[45..78]; 86 | SecpPublicKey::from_bytes(pk_bytes)? 87 | }; 88 | 89 | Ok(Self { depth, parent_fingerprint, idx, chain_code, pk }) 90 | } 91 | } 92 | 93 | impl ExtendedPublicKey for SecpExtPublicKey { 94 | fn derive_normal_child(&self, idx: i32) -> Fallible { 95 | ensure!(idx >= 0, "Derivation index cannot be negative"); 96 | let idx = idx as u32; 97 | 98 | let xpub = self.cook_new(idx, |hasher| { 99 | hasher.input(&self.pk.to_bytes()); 100 | hasher.input(&idx.to_be_bytes()); 101 | }); 102 | 103 | Ok(xpub) 104 | } 105 | fn as_public_key(&self) -> SecpPublicKey { 106 | self.pk.clone() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /prometheus/src/home/connection.rs: -------------------------------------------------------------------------------- 1 | //use std::rc::Rc; 2 | // 3 | //use futures::Future; 4 | //use multiaddr::Multiaddr; 5 | // 6 | //use crate::home::net::HomeConnector; 7 | //use mercury_home_protocol::{AsyncFallible, Home, Profile, ProfileId, Signer}; 8 | // 9 | //pub struct ConnectionFactory { 10 | // connector: Rc, 11 | // signer: Rc, 12 | //} 13 | // 14 | //impl ConnectionFactory { 15 | // pub fn new(connector: Rc, signer: Rc) -> Self { 16 | // Self { connector, signer } 17 | // } 18 | // 19 | // pub fn open( 20 | // &self, 21 | // home_profile_id: &ProfileId, 22 | // addr_hint: Option, 23 | // ) -> AsyncFallible { 24 | // let signer = self.signer.to_owned(); 25 | // let open_fut = self 26 | // .connector 27 | // .clone() 28 | // .connect(home_profile_id, addr_hint, self.signer.to_owned()) 29 | // .map(|home| Connection::new(home, signer)) 30 | // .map_err(|e| e.into()); 31 | // Box::new(open_fut) 32 | // } 33 | //} 34 | // 35 | //pub struct Connection { 36 | // proxy: Rc, 37 | // signer: Rc, 38 | // //connector: Rc, 39 | //} 40 | // 41 | //impl Connection { 42 | // pub fn new(proxy: Rc, signer: Rc) -> Self { 43 | // Self { proxy, signer } 44 | // } 45 | // 46 | // pub fn register(&self) -> AsyncFallible<()> { 47 | // unimplemented!() 48 | // } 49 | // pub fn unregister(&self, new_home: Option) -> AsyncFallible<()> { 50 | // unimplemented!() 51 | // } 52 | // 53 | // pub fn backup(&self, data: &Profile) -> AsyncFallible<()> { 54 | // unimplemented!() 55 | // } 56 | // pub fn restore(&self) -> AsyncFallible { 57 | // unimplemented!() 58 | // } 59 | // 60 | // pub fn login(&self) -> AsyncFallible { 61 | // unimplemented!() 62 | // } 63 | // 64 | // // fn join_home( 65 | // // &self, 66 | // // home_id: ProfileId, 67 | // // //invite: Option, 68 | // // ) -> AsyncFallible<()> { 69 | // // let half_proof = RelationHalfProof::new( 70 | // // RelationProof::RELATION_TYPE_HOSTED_ON_HOME, 71 | // // &home_id, 72 | // // &*self.signer, 73 | // // ); 74 | // // 75 | // // let own_profile_cell = self.own_profile.clone(); 76 | // // let own_profile_dataclone = self.own_profile.borrow().to_owned(); 77 | // // //let profile_repo = self.profile_repo.clone(); 78 | // // let reg_fut = self 79 | // // .connect_home(&home_id) 80 | // // .and_then(move |home| { 81 | // // home.register(own_profile_dataclone, half_proof) //, invite) 82 | // // .map_err(|(_own_prof, err)| err.context(ErrorKind::RegistrationFailed).into()) 83 | // // }) 84 | // // // TODO we should also notify the AdminSession here to update its profile_store 85 | // // //.and_then( |own_profile| ... ) 86 | // // .map(move |own_profile| { 87 | // // own_profile_cell.replace(own_profile.clone()); 88 | // // // TODO remove this after testing 89 | // // //debug!("Hack: overwriting persona entry with home added: {:?}", own_profile.public_data()); 90 | // // //profile_repo.borrow_mut().set_public(own_profile.public_data()) 91 | // // // .map_err(|e| e.context(ErrorKind::RegistrationFailed).into()) 92 | // // }); 93 | // // Box::new(reg_fut) 94 | // // } 95 | // // 96 | // // fn leave_home(&self, home_id: ProfileId, newhome_id: Option) -> AsyncFallible<()> { 97 | // // let unreg_fut = self.login_home(home_id) 98 | // // .map_err(|err| err.context(ErrorKind::LoginFailed).into()) 99 | // // .and_then( move |my_session| 100 | // // my_session.session() 101 | // // .unregister(newhome_id) 102 | // // .map_err(|err| err.context(ErrorKind::DeregistrationFailed).into()) 103 | // // ) 104 | // // // TODO we should also notify the AdminSession here to update its profile_store 105 | // // // .and_then( || ... ) 106 | // // ; 107 | // // Box::new(unreg_fut) 108 | // // } 109 | //} 110 | // 111 | //pub struct Session {} 112 | -------------------------------------------------------------------------------- /home-protocol/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use failure::{Backtrace, Context, Fail}; 4 | 5 | #[derive(Debug)] 6 | pub struct Error { 7 | inner: Context, 8 | } 9 | 10 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] 11 | pub enum ErrorKind { 12 | #[fail(display = "profile lookup failed")] 13 | ProfileLookupFailed, 14 | #[fail(display = "profile update failed")] 15 | ProfileUpdateFailed, 16 | #[fail(display = "hash decode failed")] 17 | HashDecodeFailed, 18 | #[fail(display = "hash encode failed")] 19 | HashEncodeFailed, 20 | #[fail(display = "signer creation failed")] 21 | SignerCreationFailed, 22 | #[fail(display = "signature validation failed")] 23 | SignatureValidationFailed, 24 | #[fail(display = "handshake failed")] 25 | DiffieHellmanHandshakeFailed, 26 | #[fail(display = "relation signing failed")] 27 | RelationSigningFailed, 28 | #[fail(display = "relation validation failed")] 29 | RelationValidationFailed, 30 | #[fail(display = "profile validation failed")] 31 | ProfileValidationFailed, 32 | #[fail(display = "multiaddress serialization failed")] 33 | MultiaddrSerializationFailed, 34 | #[fail(display = "multiaddress deserialization failed")] 35 | MultiaddrDeserializationFailed, 36 | #[fail(display = "failed to fetch peer id")] 37 | PeerIdRetreivalFailed, 38 | #[fail(display = "profile claim failed")] 39 | FailedToClaimProfile, 40 | #[fail(display = "already registered")] 41 | AlreadyRegistered, 42 | #[fail(display = "home id mismatch")] 43 | HomeIdMismatch, 44 | #[fail(display = "relation type mismatch")] 45 | RelationTypeMismatch, 46 | #[fail(display = "invalid signature")] 47 | InvalidSignature, 48 | #[fail(display = "storage failed")] 49 | StorageFailed, 50 | #[fail(display = "profile mismatch")] 51 | ProfileMismatch, 52 | #[fail(display = "public key mismatch")] 53 | PublicKeyMismatch, 54 | #[fail(display = "signer mismatch")] 55 | SignerMismatch, 56 | #[fail(display = "peer not hosted here")] 57 | PeerNotHostedHere, 58 | #[fail(display = "invalid relation proof")] 59 | InvalidRelationProof, 60 | #[fail(display = "timeout failed")] 61 | TimeoutFailed, 62 | #[fail(display = "failed to read response")] 63 | FailedToReadResponse, 64 | #[fail(display = "deregistered")] 65 | ProfileDeregistered, 66 | #[fail(display = "profile load failed")] 67 | FailedToLoadProfile, 68 | #[fail(display = "call failed")] 69 | CallFailed, 70 | #[fail(display = "failed to push event")] 71 | FailedToPushEvent, 72 | #[fail(display = "connection to home failed")] 73 | ConnectionToHomeFailed, 74 | #[fail(display = "failed to send")] 75 | FailedToSend, 76 | #[fail(display = "context validation failed")] 77 | ContextValidationFailed, 78 | #[fail(display = "failed to get session")] 79 | FailedToGetSession, 80 | #[fail(display = "failed to resolve URL")] 81 | FailedToResolveUrl, 82 | #[fail(display = "pair request failed")] 83 | PairRequestFailed, 84 | #[fail(display = "pair response failed")] 85 | PairResponseFailed, 86 | #[fail(display = "profile registration failed")] 87 | RegisterFailed, 88 | #[fail(display = "profile deregistration failed")] 89 | UnregisterFailed, 90 | #[fail(display = "failed to create session")] 91 | FailedToCreateSession, 92 | #[fail(display = "DHT lookup failed")] 93 | DhtLookupFailed, 94 | #[fail(display = "ping failed")] 95 | PingFailed, 96 | #[fail(display = "login failed")] 97 | LoginFailed, 98 | } 99 | 100 | impl PartialEq for Error { 101 | fn eq(&self, other: &Error) -> bool { 102 | self.inner.get_context() == other.inner.get_context() 103 | } 104 | } 105 | 106 | impl Fail for Error { 107 | fn cause(&self) -> Option<&dyn Fail> { 108 | self.inner.cause() 109 | } 110 | 111 | fn backtrace(&self) -> Option<&Backtrace> { 112 | self.inner.backtrace() 113 | } 114 | } 115 | 116 | impl Display for Error { 117 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 118 | Display::fmt(&self.inner, f) 119 | } 120 | } 121 | 122 | impl Error { 123 | pub fn kind(&self) -> ErrorKind { 124 | *self.inner.get_context() 125 | } 126 | } 127 | 128 | impl From for Error { 129 | fn from(kind: ErrorKind) -> Error { 130 | Error { inner: Context::new(kind) } 131 | } 132 | } 133 | 134 | impl From> for Error { 135 | fn from(inner: Context) -> Error { 136 | Error { inner } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /storage/src/common/imp.rs: -------------------------------------------------------------------------------- 1 | use failure::Fallible; 2 | 3 | use crate::common::*; 4 | 5 | pub struct MultiHasher { 6 | hash_algorithm: multihash::Hash, 7 | } 8 | 9 | impl MultiHasher { 10 | pub fn new(hash_algorithm: multihash::Hash) -> Self { 11 | MultiHasher { hash_algorithm } 12 | } 13 | 14 | fn get_hash_bytes(&self, data: &Vec) -> Fallible> { 15 | let bytes = multihash::encode(self.hash_algorithm, data)?; 16 | Ok(bytes) 17 | } 18 | } 19 | 20 | impl Hasher, Vec> for MultiHasher { 21 | fn get_hash(&self, data: &Vec) -> Fallible> { 22 | self.get_hash_bytes(&data) 23 | } 24 | 25 | fn validate(&self, data: &Vec, expected_hash: &Vec) -> Fallible { 26 | // // TODO should we do this here or just drop this step and check hash equality? 27 | // let decode_result = decode(expected_hash) 28 | // .map_err(MultiHasher::to_hasher_error)?; 29 | // if decode_result.alg != self.hash_algorithm 30 | // { return Err(HashError::UnsupportedType); } 31 | 32 | let calculated_hash = self.get_hash_bytes(&data)?; 33 | Ok(*expected_hash == calculated_hash) 34 | } 35 | } 36 | 37 | pub struct IdentitySerializer; 38 | 39 | impl Serializer, Vec> for IdentitySerializer { 40 | fn serialize(&self, object: Vec) -> Fallible> { 41 | Ok(object) 42 | } 43 | fn deserialize(&self, serialized_object: Vec) -> Fallible> { 44 | Ok(serialized_object) 45 | } 46 | } 47 | 48 | // TODO this struct should be independent of the serialization format (e.g. JSON): 49 | // Maybe should contain Box data members 50 | pub struct SerdeJsonSerializer; 51 | 52 | impl Serializer> for SerdeJsonSerializer 53 | where 54 | ObjectType: serde::Serialize + serde::de::DeserializeOwned, 55 | { 56 | fn serialize(&self, object: ObjectType) -> Fallible> { 57 | let bytes = serde_json::to_string(&object).map(|str| str.into_bytes())?; 58 | Ok(bytes) 59 | } 60 | 61 | fn deserialize(&self, serialized_object: Vec) -> Fallible { 62 | let json_string = String::from_utf8(serialized_object)?; 63 | let obj = serde_json::from_str(&json_string)?; 64 | Ok(obj) 65 | } 66 | } 67 | 68 | pub struct MultiBaseHashCoder { 69 | base_algorithm: multibase::Base, 70 | } 71 | 72 | impl MultiBaseHashCoder { 73 | pub fn new(base_algorithm: multibase::Base) -> Self { 74 | Self { base_algorithm } 75 | } 76 | } 77 | 78 | impl HashCoder, String> for MultiBaseHashCoder { 79 | fn encode(&self, hash_bytes: &Vec) -> Fallible { 80 | let hash_str = multibase::encode(self.base_algorithm, &hash_bytes); 81 | Ok(hash_str) 82 | } 83 | 84 | fn decode(&self, hash_str: &String) -> Fallible> { 85 | let bytes = multibase::decode(&hash_str).map(|(_, bytes)| bytes)?; 86 | Ok(bytes) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | use serde_derive::{Deserialize, Serialize}; 94 | 95 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] 96 | struct Person { 97 | name: String, 98 | phone: String, 99 | age: u16, 100 | } 101 | 102 | #[test] 103 | fn test_serializer() { 104 | let serializer = SerdeJsonSerializer; 105 | let orig_obj = 106 | Person { name: "Aladar".to_string(), phone: "+36202020202".to_string(), age: 28 }; 107 | let ser_obj = serializer.serialize(orig_obj.clone()); 108 | assert!(ser_obj.is_ok()); 109 | let deser_res = serializer.deserialize(ser_obj.unwrap()); 110 | assert!(deser_res.is_ok()); 111 | assert_eq!(orig_obj, deser_res.unwrap()); 112 | } 113 | 114 | #[test] 115 | fn test_hasher() { 116 | let ser_obj = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; 117 | let hasher = MultiHasher::new(multihash::Hash::Keccak256); 118 | let hash = hasher.get_hash(&ser_obj); 119 | assert!(hash.is_ok()); 120 | let valid = hasher.validate(&ser_obj, &hash.unwrap()); 121 | assert!(valid.is_ok()); 122 | } 123 | 124 | #[test] 125 | fn test_hash_coder() { 126 | let hash_bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; 127 | let coder = MultiBaseHashCoder::new(multibase::Base64); 128 | let hash_str = coder.encode(&hash_bytes); 129 | assert!(hash_str.is_ok()); 130 | let decode_res = coder.decode(&hash_str.unwrap()); 131 | assert!(decode_res.is_ok()); 132 | assert_eq!(hash_bytes, decode_res.unwrap()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /examples/TheButton/src/subscriber.rs: -------------------------------------------------------------------------------- 1 | use futures::prelude::*; 2 | use log::*; 3 | 4 | use crate::options::SubscriberConfig; 5 | use crate::*; 6 | use mercury_home_protocol::*; 7 | 8 | #[derive(Clone)] 9 | pub struct Client { 10 | pub cfg: SubscriberConfig, 11 | pub appctx: AppContext, 12 | } 13 | 14 | impl Client { 15 | pub fn new(cfg: SubscriberConfig, appctx: AppContext) -> Self { 16 | Self { appctx, cfg } 17 | } 18 | 19 | pub fn wait_for_pairing_response( 20 | events: Box>, 21 | my_profile_id: ProfileId, 22 | ) -> AsyncFallible> { 23 | let fut = events 24 | .filter_map(move |event| { 25 | debug!("TheButton got event"); 26 | if let DAppEvent::PairingResponse(relation) = event { 27 | trace!( 28 | "Got pairing response, checking peer id: {:?}", 29 | relation.proof() 30 | ); 31 | if relation.proof().peer_id(&my_profile_id).is_ok() { 32 | return Some(relation); 33 | } 34 | } 35 | return None; 36 | }) 37 | .take(1) 38 | .into_future() // NOTE transforms stream into a future of an (item,stream) pair 39 | .map_err(|((), _stream)| { 40 | debug!("Pairing failed"); 41 | err_msg("Pairing failed") 42 | }) 43 | .and_then(|(proof, _stream)| { 44 | proof.ok_or_else(|| { 45 | debug!("Profile event stream ended without proper response"); 46 | err_msg("Profile event stream ended without proper response") 47 | }) 48 | }); 49 | Box::new(fut) 50 | } 51 | 52 | fn get_or_create_contact( 53 | self, 54 | dapp_session: Arc, 55 | ) -> AsyncFallible> { 56 | let callee_profile_id = self.cfg.server_id.clone(); 57 | let contact_fut = dapp_session.relation(&callee_profile_id).and_then({ 58 | let peer_id = self.cfg.server_id.clone(); 59 | move |relation| { 60 | let init_rel_fut = dapp_session.initiate_relation(&peer_id); 61 | match relation { 62 | Some(relation) => Box::new(Ok(relation).into_future()) as AsyncResult<_, _>, 63 | None => { 64 | debug!("No signed relation to server is available, initiate pairing"); 65 | let persona_id = dapp_session.selected_profile().to_owned(); 66 | let rel_fut = dapp_session 67 | .checkin() 68 | .and_then(|events| init_rel_fut.map(|()| events)) 69 | .and_then(|events| { 70 | debug!("Pairing request sent, start waiting for response"); 71 | Self::wait_for_pairing_response(events, persona_id) 72 | }); 73 | Box::new(rel_fut) 74 | } 75 | } 76 | } 77 | }); 78 | Box::new(contact_fut) 79 | } 80 | } 81 | 82 | impl IntoFuture for Client { 83 | type Future = AsyncResult; 84 | type Item = (); 85 | type Error = failure::Error; 86 | 87 | fn into_future(self) -> Self::Future { 88 | let client_fut = self 89 | .appctx 90 | .dapp_service 91 | .dapp_session(self.appctx.dapp_id.to_owned()) 92 | .and_then({ 93 | let client = self.clone(); 94 | move |dapp_session| client.get_or_create_contact(dapp_session) 95 | }) 96 | .and_then(|contact| { 97 | info!("Contact is available, start calling"); 98 | contact.call(AppMessageFrame(vec![])).map_err(|err| { 99 | error!("call failed: {:?}", err); 100 | err 101 | }) 102 | }) 103 | .and_then(|call| { 104 | info!("call accepted, waiting for incoming messages"); 105 | call.incoming 106 | .for_each(|msg: Result| { 107 | msg.map(|frame| info!("Client received server message {:?}", frame)) 108 | .map_err(|err| warn!("Client got server error {:?}", err)) 109 | }) 110 | .map_err(|()| err_msg("Failed to get next event from publisher")) 111 | }); 112 | 113 | Box::new(client_fut) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /home-node/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::net::{SocketAddr, ToSocketAddrs}; 2 | use std::path::PathBuf; 3 | use std::rc::Rc; 4 | use std::sync::Arc; 5 | 6 | use log::*; 7 | use structopt::StructOpt; 8 | 9 | use did::vault::{HdProfileVault, ProfileVault}; 10 | use did::*; 11 | use mercury_home_protocol::*; 12 | 13 | #[derive(Debug, StructOpt)] 14 | #[structopt( 15 | name = "mercury-home", 16 | about = "Mercury Home Node daemon", 17 | setting = structopt::clap::AppSettings::ColoredHelp 18 | )] 19 | struct CliConfig { 20 | #[structopt(long = "keyvault-dir", value_name = "DIR", parse(from_os_str))] 21 | /// Configuration directory to load keyvault from. 22 | /// Default: OS-specific app_cfg_dir/prometheus 23 | pub keyvault_dir: Option, 24 | 25 | #[structopt(long = "profileid", value_name = "ID")] 26 | /// Key ID within keyvault to be used for authentication by this node. 27 | pub profile_id: Option, 28 | 29 | #[structopt( 30 | long = "profile-backup", 31 | default_value = "/tmp/mercury/home/profile-backups", 32 | parse(from_os_str), 33 | value_name = "PATH" 34 | )] 35 | /// Directory path to store profile backups 36 | profile_backup_path: PathBuf, 37 | 38 | #[structopt( 39 | long = "host-relations", 40 | default_value = "/tmp/mercury/home/host-relations", 41 | parse(from_os_str), 42 | value_name = "PATH" 43 | )] 44 | /// Directory path to store hosted profiles in 45 | host_relations_path: PathBuf, 46 | 47 | #[structopt( 48 | long = "distributed-storage", 49 | default_value = "127.0.0.1:6161", 50 | value_name = "IP:PORT" 51 | )] 52 | /// Network address of public profile storage 53 | distributed_storage_address: String, 54 | 55 | #[structopt(long = "tcp", default_value = "0.0.0.0:2077", value_name = "IP:Port")] 56 | /// Listen on this socket to serve TCP clients 57 | socket_addr: String, 58 | } 59 | 60 | impl CliConfig { 61 | const CONFIG_PATH: &'static str = "home.cfg"; 62 | 63 | pub fn new() -> Self { 64 | util::parse_config::(Self::CONFIG_PATH) 65 | } 66 | } 67 | 68 | pub struct Config { 69 | private_storage_path: PathBuf, 70 | host_relations_path: PathBuf, 71 | distributed_storage_address: SocketAddr, 72 | _vault: Arc, 73 | signer: Rc, 74 | listen_socket: SocketAddr, // TODO consider using Vec if listening on several network devices is needed 75 | } 76 | 77 | impl Config { 78 | pub fn new() -> Self { 79 | let cli = CliConfig::new(); 80 | 81 | let vault_path = 82 | did::paths::vault_path(cli.keyvault_dir).expect("Failed to get keyvault path"); 83 | let vault = Arc::new(HdProfileVault::load(&vault_path).expect(&format!( 84 | "Profile vault is required but failed to load from {}", 85 | vault_path.to_string_lossy() 86 | ))); 87 | 88 | let profile_id = cli.profile_id.or_else(|| vault.get_active().expect("Failed to get active profile") ) 89 | .expect("Profile id is needed for authenticating the node, but neither command line argument is specified, nor active profile is set in vault"); 90 | let signer = vault.clone().signer(&profile_id).unwrap(); 91 | 92 | info!("homenode profile id: {}", signer.profile_id()); 93 | info!("homenode public key: {}", signer.public_key()); 94 | 95 | let listen_socket = cli 96 | .socket_addr 97 | .to_socket_addrs() 98 | .unwrap() 99 | .next() 100 | .expect("Failed to parse socket address for private storage"); 101 | 102 | let distributed_storage_address = cli 103 | .distributed_storage_address 104 | .to_socket_addrs() 105 | .unwrap() 106 | .next() 107 | .expect("Failed to parse socket address for distributed storage"); 108 | 109 | Self { 110 | private_storage_path: cli.profile_backup_path, 111 | host_relations_path: cli.host_relations_path, 112 | distributed_storage_address, 113 | _vault: vault, 114 | signer, 115 | listen_socket, 116 | } 117 | } 118 | 119 | pub fn profile_backup_path(&self) -> &PathBuf { 120 | &self.private_storage_path 121 | } 122 | pub fn host_relations_path(&self) -> &PathBuf { 123 | &self.host_relations_path 124 | } 125 | pub fn distributed_storage_address(&self) -> &SocketAddr { 126 | &self.distributed_storage_address 127 | } 128 | pub fn signer(&self) -> Rc { 129 | self.signer.clone() 130 | } 131 | pub fn listen_socket(&self) -> &SocketAddr { 132 | &self.listen_socket 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /prometheus/src/vault/api.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::str::FromStr; 3 | 4 | use failure::{err_msg, Fallible}; 5 | use serde_derive::{Deserialize, Serialize}; 6 | 7 | use crate::daemon::NetworkState; 8 | use crate::*; 9 | use claims::model::*; 10 | use multiaddr::Multiaddr; 11 | 12 | #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd)] 13 | pub enum ProfileRepositoryKind { 14 | Local, 15 | Base, 16 | Remote, // TODO Differentiate several remotes, e.g. by including a network address here like Remote(addr) 17 | } 18 | 19 | impl FromStr for ProfileRepositoryKind { 20 | type Err = failure::Error; 21 | fn from_str(src: &str) -> Result { 22 | match src { 23 | "local" => Ok(ProfileRepositoryKind::Local), 24 | "base" => Ok(ProfileRepositoryKind::Base), 25 | "remote" => Ok(ProfileRepositoryKind::Remote), 26 | _ => Err(err_msg("Invalid profile repository kind")), 27 | } 28 | } 29 | } 30 | 31 | #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)] 32 | pub struct RestoreCounts { 33 | pub try_count: u32, 34 | pub restore_count: u32, 35 | } 36 | 37 | // TODO expose repository synced/unsynced state of profile here 38 | // TODO error handling better suited for HTTP status codes (analogue to checked/unchecked exceptions) 39 | pub trait VaultApi { 40 | fn restore_vault(&mut self, phrase: String) -> Fallible<()>; 41 | fn restore_all_profiles(&mut self) -> Fallible; 42 | 43 | fn set_active_profile(&mut self, my_profile_id: &ProfileId) -> Fallible<()>; 44 | fn get_active_profile(&self) -> Fallible>; 45 | 46 | fn list_vault_records(&self) -> Fallible>; 47 | fn create_profile(&mut self, label: Option) -> Fallible; 48 | fn get_vault_record(&self, id: Option) -> Fallible; 49 | 50 | fn set_profile_label( 51 | &mut self, 52 | my_profile_id: Option, 53 | label: ProfileLabel, 54 | ) -> Fallible<()>; 55 | 56 | fn get_profile_metadata(&self, my_profile_id: Option) -> Fallible; 57 | fn set_profile_metadata( 58 | &mut self, 59 | my_profile_id: Option, 60 | data: ProfileMetadata, 61 | ) -> Fallible<()>; 62 | 63 | fn get_profile_data( 64 | &self, 65 | id: Option, 66 | repo_kind: ProfileRepositoryKind, 67 | ) -> Fallible; 68 | 69 | fn revert_profile(&mut self, my_profile_id: Option) -> Fallible; 70 | fn publish_profile( 71 | &mut self, 72 | my_profile_id: Option, 73 | force: bool, 74 | ) -> Fallible; 75 | fn restore_profile( 76 | &mut self, 77 | my_profile_id: Option, 78 | force: bool, 79 | ) -> Fallible; 80 | 81 | fn set_attribute( 82 | &mut self, 83 | my_profile_id: Option, 84 | key: &AttributeId, 85 | value: &AttributeValue, 86 | ) -> Fallible<()>; 87 | fn clear_attribute( 88 | &mut self, 89 | my_profile_id: Option, 90 | key: &AttributeId, 91 | ) -> Fallible<()>; 92 | 93 | fn claims(&self, my_profile_id: Option) -> Fallible>; 94 | fn add_claim(&mut self, my_profile_id: Option, claim: Claim) -> Fallible<()>; 95 | fn remove_claim(&mut self, my_profile_id: Option, claim: ClaimId) -> Fallible<()>; 96 | fn sign_claim( 97 | &self, 98 | my_profile_id: Option, 99 | claim: &SignableClaimPart, 100 | ) -> Fallible; 101 | fn add_claim_proof( 102 | &mut self, 103 | my_profile_id: Option, 104 | claim: &ClaimId, 105 | proof: ClaimProof, 106 | ) -> Fallible<()>; 107 | fn license_claim( 108 | &mut self, 109 | my_profile_id: Option, 110 | claim: ClaimId, 111 | // TODO audience, purpose, expiry, etc 112 | ) -> Fallible; 113 | 114 | // NOTE links are derived as a special kind of claims. Maybe they could be removed from here on the long term. 115 | fn create_link( 116 | &mut self, 117 | my_profile_id: Option, 118 | peer_profile_id: &ProfileId, 119 | ) -> Fallible; 120 | fn remove_link( 121 | &mut self, 122 | my_profile_id: Option, 123 | peer_profile_id: &ProfileId, 124 | ) -> Fallible<()>; 125 | 126 | fn did_homes(&self, my_profile_id: Option) -> Fallible>; 127 | fn register_home( 128 | &mut self, 129 | my_id: Option, 130 | home_id: &ProfileId, 131 | addr_hints: &[Multiaddr], 132 | network: &NetworkState, 133 | ) -> AsyncFallible<()>; 134 | 135 | // TODO: This is related to add_claim and other calls, but does not conceptually belong here. 136 | fn claim_schemas(&self) -> Fallible>; 137 | } 138 | -------------------------------------------------------------------------------- /home-protocol/src/crypto.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use keyvault::PublicKey as KeyVaultPublicKey; 3 | 4 | pub trait ProfileIdValidator { 5 | fn validate_profile_auth( 6 | &self, 7 | public_key: &PublicKey, 8 | profile_id: &ProfileId, 9 | ) -> Result; 10 | } 11 | 12 | impl Default for Box { 13 | fn default() -> Self { 14 | Box::new(MultiHashProfileValidator::default()) 15 | } 16 | } 17 | 18 | pub trait SignatureValidator { 19 | // TODO this probably should just return bool instead of Result 20 | fn validate_signature( 21 | &self, 22 | public_key: &PublicKey, 23 | // TODO add here: profile_auth: ProfileAuthData, 24 | data: &[u8], 25 | signature: &Signature, 26 | ) -> Result; 27 | } 28 | 29 | impl Default for Box { 30 | fn default() -> Self { 31 | Box::new(PublicKeyValidator::default()) 32 | } 33 | } 34 | 35 | pub trait Validator: ProfileIdValidator + SignatureValidator { 36 | fn validate_half_proof( 37 | &self, 38 | half_proof: &RelationHalfProof, 39 | signer_pubkey: &PublicKey, 40 | ) -> Result<(), Error> { 41 | self.validate_signature( 42 | signer_pubkey, 43 | &RelationSignablePart::from(half_proof).serialized(), 44 | &half_proof.signature, 45 | )?; 46 | Ok(()) 47 | } 48 | 49 | fn validate_relation_proof( 50 | &self, 51 | relation_proof: &RelationProof, 52 | id_1: &ProfileId, 53 | public_key_1: &PublicKey, 54 | id_2: &ProfileId, 55 | public_key_2: &PublicKey, 56 | ) -> Result<(), Error> { 57 | // TODO consider inverting relation_type for different directions 58 | let signable_a = RelationSignablePart::new( 59 | &relation_proof.relation_type, 60 | &relation_proof.a_id, 61 | &relation_proof.b_id, 62 | ) 63 | .serialized(); 64 | 65 | let signable_b = RelationSignablePart::new( 66 | &relation_proof.relation_type, 67 | &relation_proof.b_id, 68 | &relation_proof.a_id, 69 | ) 70 | .serialized(); 71 | 72 | let peer_of_id_1 = relation_proof.peer_id(&id_1)?; 73 | if peer_of_id_1 != id_2 { 74 | Err(ErrorKind::RelationValidationFailed)? 75 | } 76 | 77 | if *peer_of_id_1 == relation_proof.b_id { 78 | // id_1 is 'proof.id_a' 79 | self.validate_signature(&public_key_1, &signable_a, &relation_proof.a_signature)?; 80 | self.validate_signature(&public_key_2, &signable_b, &relation_proof.b_signature)?; 81 | } else { 82 | // id_1 is 'proof.id_b' 83 | self.validate_signature(&public_key_1, &signable_b, &relation_proof.b_signature)?; 84 | self.validate_signature(&public_key_2, &signable_a, &relation_proof.a_signature)?; 85 | } 86 | 87 | Ok(()) 88 | } 89 | } 90 | 91 | pub struct MultiHashProfileValidator {} 92 | 93 | impl Default for MultiHashProfileValidator { 94 | fn default() -> Self { 95 | Self {} 96 | } 97 | } 98 | 99 | impl ProfileIdValidator for MultiHashProfileValidator { 100 | fn validate_profile_auth( 101 | &self, 102 | public_key: &PublicKey, 103 | profile_id: &ProfileId, 104 | ) -> Result { 105 | Ok(public_key.key_id() == *profile_id) 106 | } 107 | } 108 | 109 | pub struct PublicKeyValidator {} 110 | 111 | impl Default for PublicKeyValidator { 112 | fn default() -> Self { 113 | Self {} 114 | } 115 | } 116 | 117 | impl SignatureValidator for PublicKeyValidator { 118 | fn validate_signature( 119 | &self, 120 | public_key: &PublicKey, 121 | data: &[u8], 122 | signature: &Signature, 123 | ) -> Result { 124 | Ok(public_key.verify(data, signature)) 125 | } 126 | } 127 | 128 | #[derive(Default)] 129 | pub struct CompositeValidator { 130 | profile_validator: Box, 131 | signature_validator: Box, 132 | } 133 | 134 | impl CompositeValidator { 135 | pub fn compose( 136 | profile_validator: Box, 137 | signature_validator: Box, 138 | ) -> Self { 139 | Self { profile_validator, signature_validator } 140 | } 141 | } 142 | 143 | impl ProfileIdValidator for CompositeValidator { 144 | fn validate_profile_auth( 145 | &self, 146 | public_key: &PublicKey, 147 | profile_id: &ProfileId, 148 | ) -> Result { 149 | self.profile_validator.validate_profile_auth(public_key, profile_id) 150 | } 151 | } 152 | 153 | impl SignatureValidator for CompositeValidator { 154 | fn validate_signature( 155 | &self, 156 | public_key: &PublicKey, 157 | data: &[u8], 158 | signature: &Signature, 159 | ) -> Result { 160 | self.signature_validator.validate_signature(public_key, data, signature) 161 | } 162 | } 163 | 164 | impl Validator for CompositeValidator {} 165 | -------------------------------------------------------------------------------- /claims/src/claim_schema/mod.rs: -------------------------------------------------------------------------------- 1 | mod defaults; 2 | 3 | use std::collections::HashMap; 4 | use std::fs; 5 | use std::path::Path; 6 | 7 | use failure::{err_msg, Fallible}; 8 | use log::*; 9 | use serde_derive::{Deserialize, Serialize}; 10 | 11 | use did::model::ContentId; 12 | 13 | pub type SchemaId = ContentId; 14 | 15 | const EMPTY_ORDERING: [String; 0] = []; 16 | 17 | #[derive(Clone, Debug, Deserialize, Serialize)] 18 | pub struct SchemaVersion { 19 | id: SchemaId, 20 | author: String, 21 | name: String, 22 | version: u32, 23 | content: serde_json::Value, 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | ordering: Option>, 26 | } 27 | 28 | impl SchemaVersion { 29 | pub fn new( 30 | id: impl ToString, 31 | author: impl ToString, 32 | name: impl ToString, 33 | version: u32, 34 | content: serde_json::Value, 35 | ) -> Self { 36 | Self { 37 | id: id.to_string(), 38 | author: author.to_string(), 39 | name: name.to_string(), 40 | version, 41 | content, 42 | ordering: None, 43 | } 44 | } 45 | 46 | pub fn new_with_order( 47 | id: impl ToString, 48 | author: impl ToString, 49 | name: impl ToString, 50 | version: u32, 51 | content: serde_json::Value, 52 | ordering: impl IntoIterator, 53 | ) -> Self 54 | where 55 | T: ToString, 56 | { 57 | Self { 58 | id: id.to_string(), 59 | author: author.to_string(), 60 | name: name.to_string(), 61 | version, 62 | content, 63 | ordering: Some(ordering.into_iter().map(|s| s.to_string()).collect()), 64 | } 65 | } 66 | 67 | pub fn id(&self) -> &str { 68 | &self.id 69 | } 70 | 71 | pub fn author(&self) -> &str { 72 | &self.author 73 | } 74 | 75 | pub fn name(&self) -> &str { 76 | &self.name 77 | } 78 | 79 | pub fn version(&self) -> u32 { 80 | self.version 81 | } 82 | 83 | pub fn content(&self) -> &serde_json::Value { 84 | &self.content 85 | } 86 | 87 | pub fn ordering(&self) -> &[String] { 88 | match &self.ordering { 89 | Some(v) => v.as_slice(), 90 | None => &EMPTY_ORDERING, 91 | } 92 | } 93 | } 94 | 95 | pub trait ClaimSchemas { 96 | fn iter<'a>(&'a self) -> Box + 'a>; 97 | fn get(&self, id: &SchemaId) -> Fallible; 98 | } 99 | 100 | pub struct ClaimSchemaRegistry { 101 | schemas: HashMap, 102 | } 103 | 104 | impl ClaimSchemaRegistry { 105 | pub fn populate_folder(path: &Path) -> Fallible<()> { 106 | for schema in defaults::get() { 107 | let file_name = 108 | format!("{}_{}_{}.schema.json", schema.author, schema.name, schema.version); 109 | let file = std::fs::File::create(&path.join(file_name))?; 110 | serde_json::to_writer_pretty(file, &schema)?; 111 | } 112 | Ok(()) 113 | } 114 | 115 | pub fn import_folder(path: &Path) -> Fallible { 116 | let mut root = ClaimSchemaRegistry { schemas: Default::default() }; 117 | for entry in path.read_dir()? { 118 | // Iterator.next() might fail and then iteration should stop 119 | let entry = entry?.path(); 120 | root.import_file(&entry)?; 121 | } 122 | Ok(root) 123 | } 124 | 125 | fn import_file(&mut self, path: &Path) -> Fallible<()> { 126 | use std::io::ErrorKind; 127 | match fs::read_to_string(path) { 128 | Ok(content) => self.import_content(&content).or_else(|e| { 129 | warn!("Claim schema '{}' is not a schema: {}", path.to_string_lossy(), e); 130 | Ok(()) 131 | }), 132 | Err(ref e) if e.kind() == ErrorKind::InvalidInput => { 133 | warn!( 134 | "Claim schema '{}' contains invalid characters: {}", 135 | path.to_string_lossy(), 136 | e 137 | ); 138 | Ok(()) 139 | } 140 | Err(ref e) if e.kind() == ErrorKind::InvalidData => { 141 | debug!("Directory entry '{}' is not a file: {}", path.to_string_lossy(), e); 142 | Ok(()) 143 | } 144 | Err(e) => Err(e.into()), 145 | } 146 | } 147 | 148 | fn import_content(&mut self, content: &str) -> Fallible<()> { 149 | let schema_version = serde_json::from_str::(&content)?; 150 | self.schemas.insert(schema_version.id.clone(), schema_version); 151 | Ok(()) 152 | } 153 | } 154 | 155 | impl ClaimSchemas for ClaimSchemaRegistry { 156 | fn iter<'a>(&'a self) -> Box + 'a> { 157 | Box::new(self.schemas.values()) 158 | } 159 | 160 | fn get(&self, id: &SchemaId) -> Fallible { 161 | self.schemas 162 | .get(id) 163 | .map(|val| val.to_owned()) 164 | .ok_or_else(|| err_msg(format!("Schema id {} not found in registry", id))) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /prometheus/src/vault/http/server/routes.rs: -------------------------------------------------------------------------------- 1 | use actix_web::web; 2 | //use log::*; 3 | 4 | use crate::vault::http::server::controller::*; 5 | 6 | // TODO make URLs const variables and share them between server and client 7 | pub fn init_url_mapping(service: &mut web::ServiceConfig) { 8 | service 9 | .service( 10 | web::scope("/bip39") 11 | .service(web::resource("").route(web::post().to(generate_bip39_phrase))) 12 | .service( 13 | web::resource("/validate-phrase").route(web::post().to(validate_bip39_phrase)), 14 | ) 15 | .service( 16 | web::resource("/validate-word").route(web::post().to(validate_bip39_word)), 17 | ), 18 | ) 19 | .service( 20 | web::scope("/vault") 21 | .service(web::resource("").route(web::post().to(init_vault))) 22 | .service(web::resource("/restore-dids").route(web::post().to(restore_all_dids))) 23 | .service( 24 | web::scope("/default-did").service( 25 | web::resource("") 26 | .route(web::get().to(get_default_did)) 27 | .route(web::put().to(set_default_did)), 28 | ), 29 | ) 30 | .service( 31 | web::scope("/dids") 32 | .service( 33 | web::resource("") 34 | .route(web::get().to(list_did)) 35 | .route(web::post().to(create_did)), 36 | ) 37 | .service( 38 | web::scope("/{did}") 39 | .service(web::resource("").route(web::get().to(get_did))) 40 | .service(web::resource("/label").route(web::put().to(rename_did))) 41 | .service(web::resource("/avatar").route(web::put().to(set_avatar))) 42 | 43 | // TODO URL mapping might be misleading here because these calls 44 | // work with a ProfileRepository, not the DidVault itself. 45 | // Consider a different mapping, e.g. /profiles/{did}/... 46 | .service(web::resource("/profiledata").route(web::get().to(get_profile))) 47 | .service(web::resource("/restore").route(web::post().to(restore))) 48 | .service(web::resource("/revert").route(web::post().to(revert))) 49 | .service(web::resource("/publish").route(web::post().to(publish))) 50 | .service( 51 | web::resource("/attributes/{attribute_id}") 52 | .route(web::post().to(set_did_attribute)) 53 | .route(web::delete().to(clear_did_attribute)), 54 | ) 55 | .service( web::resource("/sign-claim").route(web::post().to(sign_claim))) 56 | .service( 57 | web::scope("/claims") 58 | .service( 59 | web::resource("") 60 | .route(web::get().to(list_did_claims)) 61 | .route(web::post().to(create_did_claim)), 62 | ) 63 | .service( 64 | web::scope("{claim_id}") 65 | .service(web::resource("") 66 | .route(web::delete().to(delete_claim)), 67 | ) 68 | .service(web::resource("/witness-request") 69 | .route(web::get().to(request_claim_signature)) 70 | ) 71 | .service(web::resource("/witness-signature") 72 | .route(web::put().to(add_claim_proof)) 73 | ) 74 | ), 75 | ) 76 | .service(web::scope("/homes") 77 | .service(web::resource("") 78 | .route(web::get().to(list_did_homes)) 79 | .route(web::post().to(register_did_home)) 80 | ) 81 | .service(web::scope("{home_did}") 82 | .service(web::resource("") 83 | .route(web::delete().to(leave_did_home)) 84 | ) 85 | .service(web::resource("online") 86 | .route(web::put().to(set_did_home_online)) 87 | ) 88 | ) 89 | ), 90 | ), 91 | ) 92 | .service(web::resource("/claims").route(web::get().to(list_vault_claims))), 93 | ) 94 | .service(web::resource("/homes").route(web::get().to(list_homes))) 95 | .service(web::resource("/claim-schemas").route(web::get().to(list_schemas))); 96 | } 97 | -------------------------------------------------------------------------------- /connect/src/sdk.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use futures::prelude::*; 4 | use futures::sync::mpsc; 5 | use log::*; 6 | 7 | use super::*; 8 | use profile::MyProfile; 9 | 10 | pub struct RelationImpl { 11 | relation_proof: RelationProof, 12 | my_profile: Rc, 13 | app_id: ApplicationId, 14 | } 15 | 16 | impl RelationImpl { 17 | fn new( 18 | relation_proof: RelationProof, 19 | my_profile: Rc, 20 | app_id: ApplicationId, 21 | ) -> Self { 22 | Self { relation_proof, my_profile, app_id } 23 | } 24 | } 25 | 26 | impl Contact for RelationImpl { 27 | fn proof(&self) -> &RelationProof { 28 | &self.relation_proof 29 | } 30 | 31 | fn call(&self, init_payload: AppMessageFrame) -> AsyncResult { 32 | let (to_caller, from_callee) = mpsc::channel(CHANNEL_CAPACITY); 33 | 34 | let call_fut = self 35 | .my_profile 36 | .call(self.relation_proof.clone(), self.app_id.clone(), init_payload, Some(to_caller)) 37 | .and_then(|to_callee_opt| { 38 | debug!("Call was answered, processing response"); 39 | match to_callee_opt { 40 | None => Err(Error::from(ErrorKind::CallRefused)), 41 | Some(to_callee) => { 42 | info!("Call with duplex channel established"); 43 | Ok(DAppCall { outgoing: to_callee, incoming: from_callee }) 44 | } 45 | } 46 | }); 47 | 48 | Box::new(call_fut) 49 | } 50 | } 51 | 52 | pub struct DAppSessionImpl { 53 | my_profile: Rc, 54 | app_id: ApplicationId, 55 | } 56 | 57 | impl DAppSessionImpl { 58 | pub fn new(my_profile: Rc, app_id: ApplicationId) -> Rc { 59 | Rc::new(Self { my_profile, app_id }) 60 | } 61 | 62 | fn relation_from(&self, proof: RelationProof) -> Box { 63 | Self::relation_from2(proof, self.my_profile.clone(), self.app_id.clone()) 64 | } 65 | 66 | fn relation_from2( 67 | proof: RelationProof, 68 | my_profile: Rc, 69 | app_id: ApplicationId, 70 | ) -> Box { 71 | Box::new(RelationImpl::new(proof, my_profile, app_id)) 72 | } 73 | } 74 | 75 | // TODO this aims only feature-completeness initially for a HelloWorld dApp, 76 | // but we also have to include security with authorization and UI-plugins later 77 | impl DAppSession for DAppSessionImpl { 78 | fn selected_profile(&self) -> ProfileId { 79 | self.my_profile.signer().profile_id() 80 | } 81 | 82 | fn contacts(&self) -> AsyncResult>, Error> { 83 | let mut proofs = self.my_profile.relations(); 84 | let app_contacts = proofs 85 | .drain(..) 86 | .filter(|proof| proof.accessible_by(&self.app_id)) 87 | .map(|proof| self.relation_from(proof)) 88 | .collect(); 89 | Box::new(Ok(app_contacts).into_future()) 90 | } 91 | 92 | fn contacts_with_profile( 93 | &self, 94 | profile: &ProfileId, 95 | relation_type: Option<&str>, 96 | ) -> AsyncResult>, Error> { 97 | let mut proofs = 98 | self.my_profile.relations_with_peer(profile, Some(&self.app_id), relation_type); 99 | let peer_contacts = proofs.drain(..).map(|proof| self.relation_from(proof)).collect(); 100 | Box::new(Ok(peer_contacts).into_future()) 101 | } 102 | 103 | fn initiate_contact(&self, with_profile: &ProfileId) -> AsyncResult<(), Error> { 104 | // TODO relation-type should be more sophisticated once we have a proper metainfo schema there 105 | self.my_profile.initiate_relation(&self.app_id.0, with_profile) 106 | } 107 | 108 | fn app_storage(&self) -> AsyncResult, Error> { 109 | unimplemented!(); 110 | } 111 | 112 | fn checkin(&self) -> AsyncResult>, Error> { 113 | let app = self.app_id.clone(); 114 | let my_profile = self.my_profile.clone(); 115 | let fut = self.my_profile.login().map(move |my_session| { 116 | let app1 = app.clone(); 117 | let calls_stream = my_session 118 | .session() 119 | .checkin_app(&app) 120 | .inspect(move |_| debug!("Checked in app {:?} to receive incoming calls", app1)) 121 | // Filter stream elements, keep only successful calls, drop errors 122 | .filter_map(|inc_call_res| inc_call_res.ok()) 123 | // Transform only Ok(call) into an event 124 | .map(|call| DAppEvent::Call(call)); 125 | 126 | let app2 = app.clone(); 127 | let app3 = app.clone(); 128 | let events_stream = my_session 129 | .events() 130 | .inspect(move |_| debug!("Forwarding events related to app {:?}", app2)) 131 | .filter_map(move |event| match event { 132 | ProfileEvent::PairingResponse(ref proof) if proof.accessible_by(&app) => { 133 | Some(DAppEvent::PairingResponse(Self::relation_from2( 134 | proof.clone(), 135 | my_profile.clone(), 136 | app3.clone(), 137 | ))) 138 | } 139 | _ => None, 140 | }); 141 | 142 | Box::new(calls_stream.select(events_stream)) as Box> 143 | }); 144 | 145 | Box::new(fut) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /examples/TheButton/src/publisher.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::time::{Duration, Instant}; 3 | 4 | use futures::prelude::*; 5 | use futures::sync::mpsc; 6 | use log::*; 7 | use tokio::timer::Interval; 8 | use tokio_signal::unix::SIGUSR1; 9 | 10 | use super::*; 11 | use crate::init::init_publisher; 12 | use crate::options::PublisherConfig; 13 | 14 | pub struct Server { 15 | pub cfg: PublisherConfig, 16 | pub appctx: AppContext, 17 | active_calls: Rc>>, 18 | } 19 | 20 | impl Server { 21 | pub fn new(cfg: PublisherConfig, appctx: AppContext) -> Self { 22 | Self { cfg, appctx, active_calls: Default::default() } 23 | } 24 | } 25 | 26 | impl IntoFuture for Server { 27 | type Item = (); 28 | type Error = failure::Error; 29 | type Future = AsyncResult; 30 | 31 | fn into_future(self) -> Self::Future { 32 | // Create dApp session with Mercury Connect and listen for incoming events, automatically accept calls 33 | let active_calls_rc = self.active_calls.clone(); 34 | 35 | let dapp_events_fut = self.appctx.dapp_service.dapp_session(self.appctx.dapp_id.to_owned()) 36 | .inspect( |_| debug!("dApp session was initialized, checking in") ) 37 | .map_err( |err| { error!("Failed to create dApp session: {:?}", err); err } ) 38 | .and_then(|dapp_session| dapp_session.checkin() ) 39 | .inspect( |_call_stream| debug!("Call stream received with successful checkin, listening for calls") ) 40 | .and_then(move |dapp_events| 41 | { 42 | dapp_events 43 | .map_err( |()| err_msg("Failed to get events") ) 44 | .for_each( move |event| 45 | { 46 | match event 47 | { 48 | DAppEvent::Call(incoming_call) => { 49 | let (to_me, from_caller) = mpsc::channel(1); 50 | let to_caller_opt = incoming_call.answer(Some(to_me)).to_caller; 51 | if let Some(to_caller) = to_caller_opt 52 | { active_calls_rc.borrow_mut().push( 53 | DAppCall{incoming: from_caller, outgoing: to_caller} ); } 54 | Ok( debug!("Answered incoming call, saving channel to caller") ) 55 | }, 56 | 57 | DAppEvent::PairingResponse(response) => Ok( debug!( 58 | "Got incoming pairing response. We do not send such requests, ignoring it {:?}", response.proof()) ), 59 | } 60 | } ) 61 | } ); 62 | 63 | // Forward button press events to all interested clients 64 | let active_calls_rc = self.active_calls.clone(); 65 | let (generate_button_press, got_button_press) = mpsc::channel(CHANNEL_CAPACITY); 66 | let fwd_pressed_fut = got_button_press 67 | .for_each(move |()| { 68 | // TODO use something better here then spawn() for all clients, 69 | // we should also detect and remove failing senders 70 | let calls = active_calls_rc.borrow(); 71 | debug!("Notifying {} connected clients", calls.len()); 72 | for call in calls.iter() { 73 | let to_client = call.outgoing.clone(); 74 | reactor::spawn( 75 | to_client.send(Ok(AppMessageFrame(vec![42]))).map(|_| ()).map_err(|_| ()), 76 | ); 77 | } 78 | Ok(()) 79 | }) 80 | .map_err(|e| format_err!("Failed to detect next button pressed event: {:?}", e)); 81 | 82 | // Receiving a SIGUSR1 signal generates an event 83 | let button_press_generator = generate_button_press.clone(); 84 | let press_on_sigusr1_fut = signal_recv(SIGUSR1).for_each(move |_| { 85 | info!("received SIGUSR1, generating event"); 86 | button_press_generator 87 | .clone() 88 | .send(()) 89 | .map(|_| ()) 90 | .map_err(|e| format_err!("Failed to fetch next interrupt event: {}", e)) 91 | }); 92 | 93 | // Combine all three for_each() tasks to be run in "parallel" on the reactor 94 | let server_fut = dapp_events_fut 95 | .select(fwd_pressed_fut) 96 | .map(|_| ()) 97 | .map_err(|(e, _)| e) 98 | .select(press_on_sigusr1_fut) 99 | .map(|_| ()) 100 | .map_err(|(e, _)| e); 101 | 102 | // Combine an optional fourth one if timer option is present 103 | let server_fut = match self.cfg.event_timer_secs { 104 | None => Box::new(server_fut) as AsyncResult<_, _>, 105 | 106 | // Repeatedly generate an event with the given interval 107 | Some(interval_secs) => { 108 | let press_on_timer_fut = 109 | Interval::new(Instant::now(), Duration::from_secs(interval_secs)) 110 | .map_err(|e| format_err!("Failed to set button pressed timer: {}", e)) 111 | .for_each(move |_| { 112 | info!("interval timer fired, generating event"); 113 | generate_button_press.clone().send(()).map(|_| ()).map_err(|e| { 114 | format_err!("Failed to send event to subscriber: {}", e) 115 | }) 116 | }); 117 | 118 | Box::new(server_fut.select(press_on_timer_fut).map(|_| ()).map_err(|(e, _)| e)) 119 | } 120 | }; 121 | 122 | Box::new(init_publisher(&self).then(|_| server_fut)) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /connect/src/net.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::net::{IpAddr, SocketAddr}; 3 | use std::rc::Rc; 4 | 5 | use failure::Fail; 6 | use futures::{future, Future}; 7 | use log::*; 8 | use multiaddr::{AddrComponent, Multiaddr}; 9 | use tokio_core::net::TcpStream; 10 | use tokio_core::reactor; 11 | 12 | use crate::*; 13 | use mercury_home_protocol::net::HomeConnector; 14 | use std::collections::HashMap; 15 | 16 | /// Convert a TCP/IP multiaddr to a SocketAddr. For multiaddr instances that are not TCP or IP, error is returned. 17 | pub fn multiaddr_to_socketaddr(multiaddr: &Multiaddr) -> Result { 18 | let mut components = multiaddr.iter(); 19 | 20 | let ip_address = match components.next() { 21 | Some(AddrComponent::IP4(address)) => IpAddr::from(address), 22 | Some(AddrComponent::IP6(address)) => IpAddr::from(address), 23 | _ => Err(ErrorKind::AddressConversionFailed)?, 24 | }; 25 | 26 | let ip_port = match components.next() { 27 | Some(AddrComponent::TCP(port)) => port, 28 | Some(AddrComponent::UDP(port)) => port, 29 | _ => return Err(ErrorKind::AddressConversionFailed)?, 30 | }; 31 | 32 | Ok(SocketAddr::new(ip_address, ip_port)) 33 | } 34 | 35 | pub struct SimpleTcpHomeConnector { 36 | handle: reactor::Handle, 37 | // TODO consider tearing down and rebuilding the whole connection in case of a network error 38 | addr_cache: Rc>>>, 39 | } 40 | 41 | impl SimpleTcpHomeConnector { 42 | pub fn new(handle: reactor::Handle) -> Self { 43 | Self { handle, addr_cache: Default::default() } 44 | } 45 | 46 | pub fn connect_addr( 47 | addr: &Multiaddr, 48 | handle: &reactor::Handle, 49 | ) -> AsyncResult { 50 | // TODO handle other multiaddresses, not only TCP 51 | let tcp_addr = match multiaddr_to_socketaddr(addr) { 52 | Ok(res) => res, 53 | Err(err) => return Box::new(future::err(err)), 54 | }; 55 | 56 | debug!("Connecting to socket address {}", tcp_addr); 57 | let tcp_str = TcpStream::connect(&tcp_addr, handle) 58 | .map_err(|err| err.context(ErrorKind::ConnectionFailed).into()); 59 | Box::new(tcp_str) 60 | } 61 | } 62 | 63 | impl HomeConnector for SimpleTcpHomeConnector { 64 | fn connect_to_addrs( 65 | &self, 66 | addresses: &[Multiaddr], 67 | signer: Rc, 68 | ) -> AsyncResult, mercury_home_protocol::error::Error> { 69 | let first_cached = 70 | addresses.iter().filter_map(|addr| self.addr_cache.borrow().get(addr).cloned()).next(); 71 | if let Some(home) = first_cached { 72 | debug!("Home address {:?} found in cache, reuse connection", addresses); 73 | return Box::new(Ok(home).into_future()); 74 | } 75 | 76 | debug!("Home address {:?} not found in cache, connecting", addresses); 77 | let handle_clone = self.handle.clone(); 78 | let tcp_conns = addresses.iter().map(move |addr| { 79 | let addr_clone = addr.to_owned(); 80 | SimpleTcpHomeConnector::connect_addr(&addr, &handle_clone) 81 | .map_err(|err| { 82 | err.context(mercury_home_protocol::error::ErrorKind::ConnectionToHomeFailed) 83 | .into() 84 | }) 85 | .map(move |tcp_stream| (addr_clone, tcp_stream)) 86 | }); 87 | 88 | let handle_clone = self.handle.clone(); 89 | let addr_cache_clone = self.addr_cache.clone(); 90 | let capnp_home = future::select_ok(tcp_conns) 91 | .and_then( move |((addr, tcp_stream), _pending_futs)| 92 | { 93 | use mercury_home_protocol::handshake::temporary_unsafe_tcp_handshake_until_diffie_hellman_done; 94 | temporary_unsafe_tcp_handshake_until_diffie_hellman_done(tcp_stream, signer) 95 | .map_err(|err| err.context(mercury_home_protocol::error::ErrorKind::DiffieHellmanHandshakeFailed).into()) 96 | .map( move |(reader, writer, _peer_ctx)| { 97 | use mercury_home_protocol::mercury_capnp::client_proxy::HomeClientCapnProto; 98 | let home = Rc::new( HomeClientCapnProto::new(reader, writer, handle_clone) ) as Rc; 99 | debug!("Save home {:?} client into cache for reuse", addr); 100 | addr_cache_clone.borrow_mut().insert(addr, home.clone()); 101 | home 102 | }) 103 | }); 104 | 105 | Box::new(capnp_home) 106 | } 107 | 108 | fn connect_to_home( 109 | &self, 110 | home_profile: &Profile, 111 | signer: Rc, 112 | ) -> AsyncResult, mercury_home_protocol::error::Error> { 113 | let addrs = match home_profile.as_home() { 114 | Some(ref home_facet) => home_facet.addrs.clone(), 115 | None => { 116 | return Box::new(future::err( 117 | mercury_home_protocol::error::ErrorKind::ProfileMismatch.into(), 118 | )); 119 | } 120 | }; 121 | 122 | self.connect_to_addrs(&addrs, signer) 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::*; 129 | use std::net::{IpAddr, Ipv4Addr}; 130 | 131 | #[test] 132 | fn test_multiaddr_conversion() { 133 | let multiaddr = "/ip4/127.0.0.1/tcp/22".parse::().unwrap(); 134 | let socketaddr = multiaddr_to_socketaddr(&multiaddr).unwrap(); 135 | assert_eq!(socketaddr, SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 22)); 136 | 137 | let multiaddr = "/ip4/127.0.0.1/utp".parse::().unwrap(); 138 | let socketaddr = multiaddr_to_socketaddr(&multiaddr); 139 | 140 | assert_eq!(socketaddr, Result::Err(ErrorKind::AddressConversionFailed.into())); 141 | } 142 | } 143 | --------------------------------------------------------------------------------