Trust Graph: implement WASM built-in (#18)

This commit is contained in:
Aleksey Proshutisnkiy 2021-11-12 16:19:16 +03:00 committed by GitHub
parent 18f010c710
commit 53562bc8a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 18871 additions and 11933 deletions

View File

@ -18,14 +18,16 @@ jobs:
keys: keys:
- trust-graph00-{{ checksum "./service/Cargo.lock" }}-{{ checksum "./Cargo.lock" }}-{{ checksum "./keypair/Cargo.lock" }} - trust-graph00-{{ checksum "./service/Cargo.lock" }}-{{ checksum "./Cargo.lock" }}-{{ checksum "./keypair/Cargo.lock" }}
- run: | - run: |
rustup toolchain install nightly-2021-04-24-x86_64-unknown-linux-gnu rustup target add wasm32-wasi
rustup default nightly-2021-04-24-x86_64-unknown-linux-gnu cargo test --no-fail-fast --release --all-features --
cd ./service
./build.sh
cargo test --no-fail-fast --release --all-features -- cargo test --no-fail-fast --release --all-features --
- save_cache: - save_cache:
paths: paths:
- ~/.cargo - ~/.cargo
- ~/.rustup - ~/.rustup
key: trust-graph00-{{ checksum "./Cargo.lock" }}-{{ checksum "./keypair/Cargo.lock" }} key: trust-graph00-{{ checksum "./Cargo.lock" }-{{ checksum "./service/Cargo.lock" }}}-{{ checksum "./keypair/Cargo.lock" }}
workflows: workflows:

14
.github/download_marine.sh vendored Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
set -o pipefail -o errexit -o nounset
set -x
MARINE_RELEASE="https://api.github.com/repos/fluencelabs/marine/releases/latest"
OUT_DIR=/usr/local/bin
# get metadata about release
curl -s -H "Accept: application/vnd.github.v3+json" $MARINE_RELEASE |
# extract url and name for asset with name "marine"
# also append $OUT_DIR to each name so file is saved to $OUT_DIR
jq -r ".assets | .[] | select(.name == \"marine\") | \"\(.browser_download_url) $OUT_DIR/\(.name)\"" |
# download assets
xargs -n2 bash -c 'curl -L $0 -o $1 && chmod +x $1'

View File

@ -0,0 +1,5 @@
{
"template": "${{CHANGELOG}}\n\n${{UNCATEGORIZED}}",
"pr_template": "- #${{NUMBER}} ${{TITLE}}",
"empty_template": "- no changes"
}

158
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,158 @@
name: "publish-release"
on:
push:
tags:
- "v*"
jobs:
npm-publish:
name: "Publish"
runs-on: ubuntu-latest
container: rust
defaults:
run:
shell: bash
steps:
### Setup
- name: Checkout repository
uses: actions/checkout@v2
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Download jq
run: |
curl -L https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 -o /usr/local/bin/jq
chmod +x /usr/local/bin/jq
- name: Download marine
run: bash $GITHUB_WORKSPACE/.github/download_marine.sh
- name: Cache npm
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-v01-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-v01-
- uses: actions/setup-node@v2
with:
node-version: "15"
registry-url: "https://registry.npmjs.org"
- uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2021-09-01
override: true
### Build
- name: trust-graph.wasm
working-directory: ./service
run: ./build.sh
- name: Check Aqua compiles
working-directory: ./aqua
run: |
npm i
npm run build
- name: Create builtin distribution package
run: |
./builtin-package/package.sh
- name: Build Changelog
id: changelog
uses: mikepenz/release-changelog-builder-action@v1
with:
configuration: ".github/workflows/changelog_config.json"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
## Publish
- name: Release
id: release
uses: softprops/action-gh-release@v1
with:
name: trust-graph ${{ env.RELEASE_VERSION }}
tag_name: ${{ env.RELEASE_VERSION }}
files: |
trust-graph.tar.gz
body: ${{steps.changelog.outputs.changelog}}
draft: false
prerelease: false
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
### Publish Aqua API
- name: Publish Aqua API
run: |
npm version ${{ env.RELEASE_VERSION }} --allow-same-version
npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
working-directory: ./aqua
## Update node-distro repo
- name: Calculate SHA256
run: |
du -hs trust-graph.tar.gz
echo $(sha256sum trust-graph.tar.gz)
echo "SHA256=$(sha256sum trust-graph.tar.gz | awk '{ print $1 }')" >> $GITHUB_ENV
- name: Get tar.gz URL
id: package-url
uses: actions/github-script@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
try {
let assets = await github.repos.listReleaseAssets({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: "${{ steps.release.outputs.id }}",
});
console.dir(assets);
let package = assets.data.find((a) => a.name === 'trust-graph.tar.gz');
let url = package.browser_download_url;
console.log("URL: " + url);
return url;
} catch (e) {
console.log("Err: " + e);
throw e;
}
- name: Update version in node-distro repo
uses: benc-uk/workflow-dispatch@v1
with:
workflow: update_service
repo: fluencelabs/node-distro
ref: 'main'
token: ${{ secrets.PERSONAL_TOKEN }}
inputs: '{
"name": "trust-graph",
"version": "${{ env.RELEASE_VERSION }}",
"url": "${{ steps.package-url.outputs.result }}",
"sha256": "${{ env.SHA256 }}"
}'
- name: Log notice
uses: actions/github-script@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
console.dir(core);
core.info("trust-graph was updated to ${{ env.RELEASE_VERSION }} in node-distro repo");

17
.github/workflows/tag.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: "tag"
on:
workflow_dispatch:
jobs:
tag:
name: "Tag"
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v2
- name: Bump version and push tag
id: tag_version
uses: mathieudutour/github-tag-action@v5.5
with:
github_token: ${{ secrets.PERSONAL_TOKEN }}

20
.gitignore vendored
View File

@ -1,2 +1,18 @@
.idea service/target
target/ service/artifacts
builtin-package/*.wasm
trust-graph.tar.gz
**/*.rs.bk
**/.idea
**/artifacts
**/.DS_Store
**/node_modules
**/dist
# Remove after https://github.com/fluencelabs/aqua/issues/287
aqua/target/typescript/**
example/src/generated/**
example/generated/**
admin/src/generated/**
admin/generated/**

1543
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "trust-graph" name = "trust-graph"
version = "0.2.8" version = "0.3.0"
authors = ["Fluence Labs"] authors = ["Fluence Labs"]
edition = "2018" edition = "2018"
description = "trust graph" description = "trust graph"
@ -8,25 +8,24 @@ license = "Apache-2.0"
repository = "https://github.com/fluencelabs/trust-graph" repository = "https://github.com/fluencelabs/trust-graph"
[dependencies] [dependencies]
libp2p-core = { package = "fluence-fork-libp2p-core", version = "0.27.2" } libp2p-core = { package = "fluence-fork-libp2p-core", version = "0.27.2", features = ["secp256k1"] }
serde = { version = "=1.0.118", features = ["derive"] } serde = { version = "=1.0.118", features = ["derive"] }
fluence-keypair = { path = "./keypair", version = "0.4.0" } fluence-keypair = { path = "./keypair", version = "0.5.0" }
serde_json = "1.0.58" serde_json = "1.0.58"
bs58 = "0.3.1" bs58 = "0.3.1"
failure = "0.1.6" failure = "0.1.6"
log = "0.4.11" log = "0.4.11"
ref-cast = "1.0.2" ref-cast = "1.0.2"
derivative = "2.1.1" derivative = "2.1.1"
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
rand = "0.7.0"
signature = "1.3.0" signature = "1.3.0"
serde_with = "1.6.0" serde_with = "1.6.0"
thiserror = "1.0.23" thiserror = "1.0.23"
libsecp256k1 = "0.3.5" sha2 = "0.9.5"
ring = "0.16.20" rand = "0.7.0"
[workspace] [workspace]
members = [ members = [
"keypair" "keypair",
"service"
] ]

View File

@ -6,61 +6,8 @@ The network-wide peer relationship layer is used to manage connectivity and perm
`/.` is the main project with all trust graph logic and in-memory storage as a default `/.` is the main project with all trust graph logic and in-memory storage as a default
`identity` directory is an abstracted cryptographical layer (key pairs, signature, etc.) `keypair` directory is an abstracted cryptographical layer (key pairs, public keys, signatures, etc.)
`wasm` is a package that provides `fce` API and could be compiled to a Wasm file. It is used `SQLite` as storage and could be used only with `SQLite` Wasm file near. `service` is a package that provides `marine` API and could be compiled to a Wasm file. It is uses `SQLite` as storage.
`js` is a `npm` package that allows you to create and serialize certificates
### Use trust-graph as a library
```
// Generate a new key pair
let root_kp = KeyPair::generate();
// Generate a key for which a certificate will be issued
let issued_for = KeyPair::generate();
// A time when the certificate will be issued and whet it will be expired
let now = Duration::from_secs(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u64)
let expires_at = Duration::from_secs(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u64 + 10000)
// Create a certificate
let mut cert = Certificate::issue_root(&root_kp, issued_for.public_key(), expires_at, now);
// We can add more keys to extend created certificate
// The method requires current_time to check if the old certificate is valid
let new_key = KeyPair::generate();
let new_cert = Certificate::issue(
&issued_for,
new_key.public_key(),
&cert,
expires_at,
now,
current_time(),
)?;
// Create new trust graph instance
let st = Box::new(InMemoryStorage::new());
let mut graph = TrustGraph::new(st);
// Add root weights. Basic keys that certificates should start with
graph.add_root_weight(root_kp.public_key().into(), 1);
// Add the certificate to a trust graph
// current_time is to check if certificate is still valid
// Could throw an error if the certificate is expired or malformed
graph.add(new_cert, current_time()).unwrap();
// We can check a weight of a key based on certificates we added and root weights
// If one public key have multiple trusts, we will get the maximum
let w = graph.weight(new_key.public_key()).unwrap().unwrap();
// Every trust or chain of trusts could be revoked by owners of keys in certificates
let revoke = Revoke::create(&issued_for, new_key.public_key(), current_time());
graph.revoke(revoke).unwrap();
```
`example` is a `js` script that shows how to issue, sign trusts/revokes, get certificates

6
admin/README.md Normal file
View File

@ -0,0 +1,6 @@
# How to generate export certificates
1. Go to `local-network`
2. Run `docker compose up -d` to start Fluence node
3. Go back to `../admin`
4. Put `root_secret_key.ed25519` and `issuer_secret_key.ed25519` to folder
5. Run `npm run start`

9
admin/aqua/export.aqua Normal file
View File

@ -0,0 +1,9 @@
import get_trust_bytes, issue_trust from "../../aqua/trust-graph-api.aqua"
export get_trust_bytes, issue_trust
import "@fluencelabs/aqua-lib/builtin.aqua"
func timestamp_sec(node: string) -> u64:
on node:
result <- Peer.timestamp_sec()
<- result

105
admin/index.ts Normal file
View File

@ -0,0 +1,105 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
get_trust_bytes,
issue_trust,
timestamp_sec,
} from "./generated/export";
import { Fluence, KeyPair } from "@fluencelabs/fluence";
import { Node, krasnodar, stage, testNet } from "@fluencelabs/fluence-network-environment";
import * as fs from "fs";
const bs58 = require('bs58');
let local: Node[] = [
{
peerId: "12D3KooWHBG9oaVx4i3vi6c1rSBUm7MLBmyGmmbHoZ23pmjDCnvK",
multiaddr:
"/ip4/127.0.0.1/tcp/9990/ws/p2p/12D3KooWHBG9oaVx4i3vi6c1rSBUm7MLBmyGmmbHoZ23pmjDCnvK",
}
];
async function issue_trust_helper(node: string, issuer_kp: KeyPair, issuer_peer_id: string, issued_for_peer_id: string, expires_at_sec: number, issued_at_sec: number) {
let trust_metadata = await get_trust_bytes(node, issued_for_peer_id, expires_at_sec, issued_at_sec);
const signed_metadata = await issuer_kp.Libp2pPeerId.privKey.sign(Uint8Array.from(trust_metadata.result));
let trust = await issue_trust(node, issued_for_peer_id, expires_at_sec, issued_at_sec, Array.from(signed_metadata));
return trust.trust
}
async function main(environment: Node[]) {
let node = environment[0].peerId;
await Fluence.start({ connectTo: environment[0]});
console.log(
"📗 created a fluence peer %s with relay %s",
Fluence.getStatus().peerId,
Fluence.getStatus().relayPeerId
);
let root_sk_b58 = fs.readFileSync("./root_secret_key.ed25519").toString();
let issuer_sk_b58 = fs.readFileSync("./issuer_secret_key.ed25519").toString();
let root_kp = await KeyPair.fromEd25519SK(bs58.decode(root_sk_b58));
let issuer_kp = await KeyPair.fromEd25519SK(bs58.decode(issuer_sk_b58));
console.log("Root private key: %s", root_sk_b58);
console.log("Root peer id: %s", root_kp.Libp2pPeerId.toB58String());
console.log("Issuer private key: %s", issuer_sk_b58);
let cur_time = await timestamp_sec(node);
let expires_at = cur_time + 60 * 60 * 24 * 365;
let common_chain = [] as any;
// self-signed root trust
common_chain.push(await issue_trust_helper(node, root_kp, root_kp.Libp2pPeerId.toB58String(), root_kp.Libp2pPeerId.toB58String(), expires_at, cur_time));
// from root to issuer
common_chain.push(await issue_trust_helper(node, root_kp, root_kp.Libp2pPeerId.toB58String(), issuer_kp.Libp2pPeerId.toB58String(), expires_at, cur_time));
let certificates = [];
for (let i = 0; i < krasnodar.length; i++) {
// from issuer to node
let trust = await issue_trust_helper(node, issuer_kp, issuer_kp.Libp2pPeerId.toB58String(), krasnodar[i].peerId, expires_at, cur_time);
let cert = {chain: [...common_chain, trust]};
certificates.push(cert);
}
fs.writeFileSync("../builtin-package/on_start.json", JSON.stringify({certs: certificates}));
return;
}
let args = process.argv.slice(2);
let environment: Node[];
if (args[0] == "testnet") {
environment = testNet;
console.log("📘 Will connect to testNet");
} else if (args[0] == "stage") {
environment = stage;
console.log("📘 Will connect to stage");
} else if (args[0] == "krasnodar") {
environment = krasnodar;
console.log("📘 Will connect to krasnodar");
} else if (args[0] == "local") {
environment = local;
console.log("📘 Will connect to local");
} else {
throw "Specify environment: krasnodar, testnet, stage or local";
}
main(environment)
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

4129
admin/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
admin/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "trust-graph-aqua-example",
"version": "1.0.0",
"description": "A simple example of how to use trust-graph in TS",
"main": "index.js",
"scripts": {
"compile-aqua": "aqua -i aqua -o generated",
"prebuild": "npm run compile-aqua",
"build": "tsc",
"start": "node dist/index.js",
"prestart": "npm run build"
},
"author": "Fluence Labs",
"license": "MIT",
"dependencies": {
"@fluencelabs/aqua": "0.4.1-240",
"@fluencelabs/aqua-lib": "0.2.0",
"@fluencelabs/fluence": "0.14.3",
"@fluencelabs/fluence-network-environment": "^1.0.10",
"@fluencelabs/trust-graph": "file:../aqua",
"bs58": "^4.0.1"
},
"devDependencies": {
"typescript": "^4.4.3"
}
}

69
admin/tsconfig.json Normal file
View File

@ -0,0 +1,69 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

5544
aqua/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
aqua/package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "@fluencelabs/trust-graph",
"version": "0.1.12",
"description": "Aqua Trust Graph API library",
"files": [
"*.aqua"
],
"dependencies": {
"@fluencelabs/aqua-lib": "0.2.0"
},
"scripts": {
"generate-aqua": "../service/build.sh",
"compile-aqua": "aqua -i . -o ./target/typescript",
"build": "npm run compile-aqua"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fluencelabs/trust-graph",
"directory": "aqua"
},
"keywords": [
"aqua",
"fluence",
"trust-graph",
"p2p"
],
"author": "Fluence Labs",
"license": "MIT",
"bugs": {
"url": "https://github.com/fluencelabs/trust-graph/issues"
},
"homepage": "https://github.com/fluencelabs/trust-graph#readme",
"devDependencies": {
"@fluencelabs/aqua": "0.4.1-240"
}
}

101
aqua/trust-graph-api.aqua Normal file
View File

@ -0,0 +1,101 @@
import "trust-graph.aqua"
import "@fluencelabs/aqua-lib/builtin.aqua"
func get_trust_bytes(node: string, issued_for_peer_id: string, expires_at_sec: u64, issued_at_sec: u64) -> GetTrustBytesResult:
on node:
result <- TrustGraph.get_trust_bytes(issued_for_peer_id, expires_at_sec, issued_at_sec)
<- result
func issue_trust(node: string, issued_for_peer_id: string, expires_at_sec: u64, issued_at_sec: u64, trust_bytes: []u8) -> IssueTrustResult:
on node:
result <- TrustGraph.issue_trust(issued_for_peer_id, expires_at_sec, issued_at_sec, trust_bytes)
<- result
func verify_trust(node: string, trust: Trust, issuer_peer_id: string) -> VerifyTrustResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.verify_trust(trust, issuer_peer_id, timestamp_sec)
<- result
func add_trust(node: string, trust: Trust, issuer_peer_id: string) -> AddTrustResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.add_trust(trust, issuer_peer_id, timestamp_sec)
<- result
func add_root(node: string, peer_id: string, weight_factor: u32) -> AddRootResult:
on node:
result <- TrustGraph.add_root(peer_id, weight_factor)
<- result
func get_weight(node: string, peer_id: string) -> WeightResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.get_weight(peer_id, timestamp_sec)
<- result
func get_all_certs(node: string, issued_for: string) -> AllCertsResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.get_all_certs(issued_for, timestamp_sec)
<- result
func get_host_certs(node: string, issued_for: string) -> AllCertsResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.get_host_certs(timestamp_sec)
<- result
func get_host_certs_from(node: string, issuer: string) -> AllCertsResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.get_host_certs_from(issuer, timestamp_sec)
<- result
func insert_cert(node: string, certificate: Certificate) -> InsertResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.insert_cert(certificate, timestamp_sec)
<- result
func get_revoke_bytes(node: string, revoked_peer_id: string, revoked_at: u64) -> GetRevokeBytesResult:
on node:
result <- TrustGraph.get_revoke_bytes(revoked_peer_id, revoked_at)
<- result
func issue_revocation(node: string, revoked_peer_id: string, revoked_by_peer_id: string, revoked_at_sec: u64, signature_bytes: []u8) -> IssueRevocationResult:
on node:
result <- TrustGraph.issue_revocation(revoked_peer_id, revoked_by_peer_id, revoked_at_sec, signature_bytes)
<- result
func revoke(node: string, revoke: Revoke) -> RevokeResult:
on node:
timestamp_sec <- Peer.timestamp_sec()
result <- TrustGraph.revoke(revoke, timestamp_sec)
<- result
service TrustOp("op"):
array_length(a: []Trust) -> u64
service BoolOp("op"):
array_length(a: []bool) -> u64
-- https://github.com/fluencelabs/trust-graph/issues/26
func isFluencePeer() -> bool:
certs_result <- get_host_certs_from(HOST_PEER_ID, "12D3KooWM45u7AQxsb4MuQJNYT3NWHHMLU7JTbBV66RTfF3KSzdR")
resultBox: *bool
if certs_result.success:
for cert <- certs_result.certificates:
len <- TrustOp.array_length(cert.chain)
if len == 3:
if cert.chain!0.issued_for == "12D3KooWNbZKaPWRZ8wgjGvrxdJFz9Fq5uVwkR6ERV1f74HhPdyB":
if cert.chain!1.issued_for == "12D3KooWM45u7AQxsb4MuQJNYT3NWHHMLU7JTbBV66RTfF3KSzdR":
resultBox <<- true
result_len <- BoolOp.array_length(resultBox)
result: *bool
if result_len == 0:
result <<- false
else:
result <<- true
<- result!

87
aqua/trust-graph.aqua Normal file
View File

@ -0,0 +1,87 @@
module TrustGraph declares *
data AddRootResult:
success: bool
error: string
data AddTrustResult:
success: bool
error: string
weight: u32
data Trust:
issued_for: string
expires_at: u64
signature: string
sig_type: string
issued_at: u64
data Certificate:
chain: []Trust
data AllCertsResult:
success: bool
certificates: []Certificate
error: string
data GetRevokeBytesResult:
success: bool
error: string
result: []u8
data GetTrustBytesResult:
success: bool
error: string
result: []u8
data InsertResult:
success: bool
error: string
data Revoke:
revoked_peer_id: string
revoked_at: u64
signature: string
sig_type: string
revoked_by: string
data IssueRevocationResult:
success: bool
error: string
revoke: Revoke
data IssueTrustResult:
success: bool
error: string
trust: Trust
data RevokeResult:
success: bool
error: string
data VerifyTrustResult:
success: bool
error: string
data WeightResult:
success: bool
weight: u32
peer_id: string
error: string
service TrustGraph("trust-graph"):
add_root(peer_id: string, weight_factor: u32) -> AddRootResult
add_trust(trust: Trust, issuer_peer_id: string, timestamp_sec: u64) -> AddTrustResult
get_all_certs(issued_for: string, timestamp_sec: u64) -> AllCertsResult
get_host_certs(timestamp_sec: u64) -> AllCertsResult
get_host_certs_from(issuer: string, timestamp_sec: u64) -> AllCertsResult
get_revoke_bytes(revoked_peer_id: string, revoked_at: u64) -> GetRevokeBytesResult
get_trust_bytes(issued_for_peer_id: string, expires_at_sec: u64, issued_at_sec: u64) -> GetTrustBytesResult
get_weight(peer_id: string, timestamp_sec: u64) -> WeightResult
get_weight_factor(max_chain_len: u32) -> u32
insert_cert(certificate: Certificate, timestamp_sec: u64) -> InsertResult
insert_cert_raw(certificate: string, timestamp_sec: u64) -> InsertResult
issue_revocation(revoked_peer_id: string, revoked_by_peer_id: string, revoked_at_sec: u64, signature_bytes: []u8) -> IssueRevocationResult
issue_trust(issued_for_peer_id: string, expires_at_sec: u64, issued_at_sec: u64, trust_bytes: []u8) -> IssueTrustResult
revoke(revoke: Revoke, timestamp_sec: u64) -> RevokeResult
verify_trust(trust: Trust, issuer_peer_id: string, timestamp_sec: u64) -> VerifyTrustResult

View File

@ -0,0 +1,7 @@
{
"name": "trust-graph",
"dependencies": [
"name:sqlite3",
"name:trust-graph"
]
}

View File

@ -0,0 +1,31 @@
(seq
(seq
(call relay ("trust-graph" "add_root") ["12D3KooWNbZKaPWRZ8wgjGvrxdJFz9Fq5uVwkR6ERV1f74HhPdyB" 2] add_root_res)
(xor
(match add_root_res.$.success! true
(null)
)
(call relay ("op" "return") [add_root_res.$.error!])
)
)
(seq
(fold certs i
(seq
(seq
(seq
(call relay ("peer" "timestamp_sec") [] cur_time)
(call relay ("trust-graph" "insert_cert") [i cur_time] insert_result)
)
(xor
(match insert_result.$.success! true
(null)
)
(call relay ("op" "return") [insert_result.$.error!])
)
)
(next i)
)
)
(call relay ("op" "return") [true])
)
)

File diff suppressed because one or more lines are too long

22
builtin-package/package.sh Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -o pipefail -o nounset -o errexit
# set current working directory to script directory to run script from everywhere
cd "$(dirname "$0")"
SCRIPT_DIR="$(pwd)"
(
echo "*** copy wasm files ***"
cd ../service
cp artifacts/*.wasm "$SCRIPT_DIR"
)
(
echo "*** create builtin distribution package ***"
cd ..
mv builtin-package trust-graph
tar --exclude="package.sh" -f trust-graph.tar.gz -zcv ./trust-graph
mv trust-graph builtin-package
)
echo "*** done ***"

View File

@ -0,0 +1,3 @@
{
"name": "sqlite3"
}

View File

@ -0,0 +1,9 @@
{
"name": "trust-graph",
"preopened_files": [
"/tmp"
],
"mapped_dirs": {
"tmp": "./tmp"
}
}

5
example/README.md Normal file
View File

@ -0,0 +1,5 @@
# Run example locally
1. Go to `local-network`
2. Run `docker compose up -d` to start Fluence node
3. Go back to `../example`
4. Run `npm run start`

10
example/aqua/export.aqua Normal file
View File

@ -0,0 +1,10 @@
import get_trust_bytes, issue_trust, verify_trust, add_trust, add_root, get_weight, get_all_certs, insert_cert, get_revoke_bytes, issue_revocation, revoke, isFluencePeer from "../../aqua/trust-graph-api.aqua"
export get_trust_bytes, issue_trust, verify_trust, add_trust, add_root, get_weight, get_all_certs, insert_cert, get_revoke_bytes, issue_revocation, revoke, isFluencePeer
import "@fluencelabs/aqua-lib/builtin.aqua"
func timestamp_sec(node: string) -> u64:
on node:
result <- Peer.timestamp_sec()
<- result

View File

@ -1,811 +0,0 @@
/**
*
* This file is auto-generated. Do not edit manually: changes may be erased.
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* Aqua version: 0.3.1-228
*
*/
import { Fluence, FluencePeer } from '@fluencelabs/fluence';
import {
ResultCodes,
RequestFlow,
RequestFlowBuilder,
CallParams,
} from '@fluencelabs/fluence/dist/internal/compilerSupport/v1';
// Services
// Functions
export function verify_trust(node: string, trust: {expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}, issuer_peer_id: string, config?: {ttl?: number}) : Promise<{error:string;success:boolean}>;
export function verify_trust(peer: FluencePeer, node: string, trust: {expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}, issuer_peer_id: string, config?: {ttl?: number}) : Promise<{error:string;success:boolean}>;
export function verify_trust(...args: any) {
let peer: FluencePeer;
let node: any;
let trust: any;
let issuer_peer_id: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
trust = args[2];
issuer_peer_id = args[3];
config = args[4];
} else {
peer = Fluence.getPeer();
node = args[0];
trust = args[1];
issuer_peer_id = args[2];
config = args[3];
}
let request: RequestFlow;
const promise = new Promise<{error:string;success:boolean}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "trust") [] trust)
)
(call %init_peer_id% ("getDataSrv" "issuer_peer_id") [] issuer_peer_id)
)
(call -relay- ("op" "noop") [])
)
(xor
(seq
(call node ("peer" "timestamp_sec") [] timestamp_sec)
(call node ("trust-graph" "verify_trust") [trust issuer_peer_id timestamp_sec] result)
)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'trust', () => {return trust;});
h.on('getDataSrv', 'issuer_peer_id', () => {return issuer_peer_id;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for verify_trust');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
export function issue_trust(node: string, issued_for_peer_id: string, expires_at_sec: number, issued_at_sec: number, trust_bytes: number[], config?: {ttl?: number}) : Promise<{error:string;success:boolean;trust:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}}>;
export function issue_trust(peer: FluencePeer, node: string, issued_for_peer_id: string, expires_at_sec: number, issued_at_sec: number, trust_bytes: number[], config?: {ttl?: number}) : Promise<{error:string;success:boolean;trust:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}}>;
export function issue_trust(...args: any) {
let peer: FluencePeer;
let node: any;
let issued_for_peer_id: any;
let expires_at_sec: any;
let issued_at_sec: any;
let trust_bytes: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
issued_for_peer_id = args[2];
expires_at_sec = args[3];
issued_at_sec = args[4];
trust_bytes = args[5];
config = args[6];
} else {
peer = Fluence.getPeer();
node = args[0];
issued_for_peer_id = args[1];
expires_at_sec = args[2];
issued_at_sec = args[3];
trust_bytes = args[4];
config = args[5];
}
let request: RequestFlow;
const promise = new Promise<{error:string;success:boolean;trust:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "issued_for_peer_id") [] issued_for_peer_id)
)
(call %init_peer_id% ("getDataSrv" "expires_at_sec") [] expires_at_sec)
)
(call %init_peer_id% ("getDataSrv" "issued_at_sec") [] issued_at_sec)
)
(call %init_peer_id% ("getDataSrv" "trust_bytes") [] trust_bytes)
)
(call -relay- ("op" "noop") [])
)
(xor
(call node ("trust-graph" "issue_trust") [issued_for_peer_id expires_at_sec issued_at_sec trust_bytes] result)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'issued_for_peer_id', () => {return issued_for_peer_id;});
h.on('getDataSrv', 'expires_at_sec', () => {return expires_at_sec;});
h.on('getDataSrv', 'issued_at_sec', () => {return issued_at_sec;});
h.on('getDataSrv', 'trust_bytes', () => {return trust_bytes;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for issue_trust');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
export function insert_cert(node: string, certificate: {chain:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}[]}, config?: {ttl?: number}) : Promise<{error:string;success:boolean}>;
export function insert_cert(peer: FluencePeer, node: string, certificate: {chain:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}[]}, config?: {ttl?: number}) : Promise<{error:string;success:boolean}>;
export function insert_cert(...args: any) {
let peer: FluencePeer;
let node: any;
let certificate: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
certificate = args[2];
config = args[3];
} else {
peer = Fluence.getPeer();
node = args[0];
certificate = args[1];
config = args[2];
}
let request: RequestFlow;
const promise = new Promise<{error:string;success:boolean}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "certificate") [] certificate)
)
(call -relay- ("op" "noop") [])
)
(xor
(seq
(call node ("peer" "timestamp_sec") [] timestamp_sec)
(call node ("trust-graph" "insert_cert") [certificate timestamp_sec] result)
)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'certificate', () => {return certificate;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for insert_cert');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
export function get_all_certs(node: string, issued_for: string, config?: {ttl?: number}) : Promise<{certificates:{chain:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}[]}[];error:string;success:boolean}>;
export function get_all_certs(peer: FluencePeer, node: string, issued_for: string, config?: {ttl?: number}) : Promise<{certificates:{chain:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}[]}[];error:string;success:boolean}>;
export function get_all_certs(...args: any) {
let peer: FluencePeer;
let node: any;
let issued_for: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
issued_for = args[2];
config = args[3];
} else {
peer = Fluence.getPeer();
node = args[0];
issued_for = args[1];
config = args[2];
}
let request: RequestFlow;
const promise = new Promise<{certificates:{chain:{expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}[]}[];error:string;success:boolean}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "issued_for") [] issued_for)
)
(call -relay- ("op" "noop") [])
)
(xor
(seq
(call node ("peer" "timestamp_sec") [] timestamp_sec)
(call node ("trust-graph" "get_all_certs") [issued_for timestamp_sec] result)
)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'issued_for', () => {return issued_for;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for get_all_certs');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
export function add_trust(node: string, trust: {expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}, issuer_peer_id: string, config?: {ttl?: number}) : Promise<{error:string;success:boolean;weight:number}>;
export function add_trust(peer: FluencePeer, node: string, trust: {expires_at:number;issued_at:number;issued_for:string;sig_type:string;signature:string}, issuer_peer_id: string, config?: {ttl?: number}) : Promise<{error:string;success:boolean;weight:number}>;
export function add_trust(...args: any) {
let peer: FluencePeer;
let node: any;
let trust: any;
let issuer_peer_id: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
trust = args[2];
issuer_peer_id = args[3];
config = args[4];
} else {
peer = Fluence.getPeer();
node = args[0];
trust = args[1];
issuer_peer_id = args[2];
config = args[3];
}
let request: RequestFlow;
const promise = new Promise<{error:string;success:boolean;weight:number}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "trust") [] trust)
)
(call %init_peer_id% ("getDataSrv" "issuer_peer_id") [] issuer_peer_id)
)
(call -relay- ("op" "noop") [])
)
(xor
(seq
(call node ("peer" "timestamp_sec") [] timestamp_sec)
(call node ("trust-graph" "add_trust") [trust issuer_peer_id timestamp_sec] result)
)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'trust', () => {return trust;});
h.on('getDataSrv', 'issuer_peer_id', () => {return issuer_peer_id;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for add_trust');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
export function add_root(node: string, peer_id: string, weight_factor: number, config?: {ttl?: number}) : Promise<{error:string;success:boolean}>;
export function add_root(peer: FluencePeer, node: string, peer_id: string, weight_factor: number, config?: {ttl?: number}) : Promise<{error:string;success:boolean}>;
export function add_root(...args: any) {
let peer: FluencePeer;
let node: any;
let peer_id: any;
let weight_factor: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
peer_id = args[2];
weight_factor = args[3];
config = args[4];
} else {
peer = Fluence.getPeer();
node = args[0];
peer_id = args[1];
weight_factor = args[2];
config = args[3];
}
let request: RequestFlow;
const promise = new Promise<{error:string;success:boolean}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "peer_id") [] peer_id)
)
(call %init_peer_id% ("getDataSrv" "weight_factor") [] weight_factor)
)
(call -relay- ("op" "noop") [])
)
(xor
(call node ("trust-graph" "add_root") [peer_id weight_factor] result)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'peer_id', () => {return peer_id;});
h.on('getDataSrv', 'weight_factor', () => {return weight_factor;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for add_root');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
export function get_weight(node: string, peer_id: string, config?: {ttl?: number}) : Promise<{error:string;peer_id:string;success:boolean;weight:number}>;
export function get_weight(peer: FluencePeer, node: string, peer_id: string, config?: {ttl?: number}) : Promise<{error:string;peer_id:string;success:boolean;weight:number}>;
export function get_weight(...args: any) {
let peer: FluencePeer;
let node: any;
let peer_id: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
peer_id = args[2];
config = args[3];
} else {
peer = Fluence.getPeer();
node = args[0];
peer_id = args[1];
config = args[2];
}
let request: RequestFlow;
const promise = new Promise<{error:string;peer_id:string;success:boolean;weight:number}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "peer_id") [] peer_id)
)
(call -relay- ("op" "noop") [])
)
(xor
(seq
(call node ("peer" "timestamp_sec") [] timestamp_sec)
(call node ("trust-graph" "get_weight") [peer_id timestamp_sec] result)
)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'peer_id', () => {return peer_id;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for get_weight');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}
export function get_trust_bytes(node: string, issued_for_peer_id: string, expires_at_sec: number, issued_at_sec: number, config?: {ttl?: number}) : Promise<{error:string;result:number[];success:boolean}>;
export function get_trust_bytes(peer: FluencePeer, node: string, issued_for_peer_id: string, expires_at_sec: number, issued_at_sec: number, config?: {ttl?: number}) : Promise<{error:string;result:number[];success:boolean}>;
export function get_trust_bytes(...args: any) {
let peer: FluencePeer;
let node: any;
let issued_for_peer_id: any;
let expires_at_sec: any;
let issued_at_sec: any;
let config: any;
if (FluencePeer.isInstance(args[0])) {
peer = args[0];
node = args[1];
issued_for_peer_id = args[2];
expires_at_sec = args[3];
issued_at_sec = args[4];
config = args[5];
} else {
peer = Fluence.getPeer();
node = args[0];
issued_for_peer_id = args[1];
expires_at_sec = args[2];
issued_at_sec = args[3];
config = args[4];
}
let request: RequestFlow;
const promise = new Promise<{error:string;result:number[];success:boolean}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "node") [] node)
)
(call %init_peer_id% ("getDataSrv" "issued_for_peer_id") [] issued_for_peer_id)
)
(call %init_peer_id% ("getDataSrv" "expires_at_sec") [] expires_at_sec)
)
(call %init_peer_id% ("getDataSrv" "issued_at_sec") [] issued_at_sec)
)
(call -relay- ("op" "noop") [])
)
(xor
(call node ("trust-graph" "get_trust_bytes") [issued_for_peer_id expires_at_sec issued_at_sec] result)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [result])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return peer.getStatus().relayPeerId;
});
h.on('getDataSrv', 'node', () => {return node;});
h.on('getDataSrv', 'issued_for_peer_id', () => {return issued_for_peer_id;});
h.on('getDataSrv', 'expires_at_sec', () => {return expires_at_sec;});
h.on('getDataSrv', 'issued_at_sec', () => {return issued_at_sec;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for get_trust_bytes');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
peer.internals.initiateFlow(request!);
return promise;
}

126
example/index.ts Normal file
View File

@ -0,0 +1,126 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
get_trust_bytes,
issue_trust,
verify_trust,
add_trust,
add_root,
get_weight,
timestamp_sec,
get_all_certs,
get_revoke_bytes,
issue_revocation,
revoke
} from "./generated/export";
import { Fluence, KeyPair } from "@fluencelabs/fluence";
import { Node } from "@fluencelabs/fluence-network-environment";
import assert from "assert";
const bs58 = require('bs58');
let local: Node[] = [
{
peerId: "12D3KooWHBG9oaVx4i3vi6c1rSBUm7MLBmyGmmbHoZ23pmjDCnvK",
multiaddr:
"/ip4/127.0.0.1/tcp/9990/ws/p2p/12D3KooWHBG9oaVx4i3vi6c1rSBUm7MLBmyGmmbHoZ23pmjDCnvK",
},
{
peerId: "12D3KooWRABanQHUn28dxavN9ZS1zZghqoZVAYtFpoN7FdtoGTFv",
multiaddr:
"/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWRABanQHUn28dxavN9ZS1zZghqoZVAYtFpoN7FdtoGTFv",
},
];
async function add_trust_helper(node: string, issuer_kp: KeyPair, issuer_peer_id: string, issued_for_peer_id: string, expires_at_sec: number, issued_at_sec: number) {
let trust_metadata = await get_trust_bytes(node, issued_for_peer_id, expires_at_sec, issued_at_sec);
const signed_metadata = await issuer_kp.Libp2pPeerId.privKey.sign(Uint8Array.from(trust_metadata.result));
let trust = await issue_trust(node, issued_for_peer_id, expires_at_sec, issued_at_sec, Array.from(signed_metadata));
console.log("Issued trust %s", trust.trust);
let result = await verify_trust(node, trust.trust, issuer_peer_id);
console.log("Verify trust result: %s", result);
let result_add = await add_trust(node, trust.trust, issuer_peer_id);
console.log("Add trust result: %s", result_add);
}
async function revoke_helper(node: string, issuer_kp: KeyPair, revoked_by_peer_id: string, revoked_peer_id: string, revoked_at_sec: number) {
let trust_metadata = await get_revoke_bytes(node, revoked_peer_id, revoked_at_sec);
const signed_metadata = await issuer_kp.Libp2pPeerId.privKey.sign(Uint8Array.from(trust_metadata.result));
let revocation = await issue_revocation(node, revoked_peer_id, revoked_by_peer_id, revoked_at_sec, Array.from(signed_metadata));
console.log("Issued revocation %s", revocation.revoke);
let result_add = await revoke(node, revocation.revoke);
console.log("Revoke result: %s", result_add);
}
async function main() {
console.log("📘 Will connect to local nodes");
// key from local-network/builtins_secret_key.ed25519 to connect as builtins owner
let sk = bs58.decode("5FwE32bDcphFzuMca7Y2qW1gdR64fTBYoRNvD4MLE1hecDGhCMQGKn8aseMr5wRo4Xo2CRFdrEAawUNLYkgQD78K").slice(0, 32); // first 32 bytes - secret key, second - public key
let builtins_keypair = await KeyPair.fromEd25519SK(sk);
await Fluence.start({ connectTo: local[0], KeyPair: builtins_keypair});
console.log(
"📗 created a fluence peer %s with relay %s",
Fluence.getStatus().peerId,
Fluence.getStatus().relayPeerId
);
const issued_timestamp_sec = await timestamp_sec(local[0].peerId);
const expires_at_sec = issued_timestamp_sec + 999999999;
const issuer_kp = await KeyPair.fromEd25519SK(bs58.decode("29Apzfedhw2Jxh94Jj4rNSmavQ1TkNe8ALYRA7bMegobwp423aLrURxLk32WtXgXHDqoSz7GAT9fQfoMhVd1e5Ww"));
let add_root_result = await add_root(local[0].peerId, local[0].peerId, 2);
console.log("Add root weight result: %s", add_root_result);
// add root trust
await add_trust_helper(local[0].peerId, issuer_kp, local[0].peerId, local[0].peerId, expires_at_sec, issued_timestamp_sec);
let root_weight_result = await get_weight(local[0].peerId, local[0].peerId);
console.log("Root weight: %s", root_weight_result);
// issue trust by local[0].peerId for local[1].peerId and add to tg
await add_trust_helper(local[0].peerId, issuer_kp, local[0].peerId, local[1].peerId, expires_at_sec, issued_timestamp_sec);
let weight_result = await get_weight(local[0].peerId, local[1].peerId);
console.log("Trust weight: %s", weight_result);
assert(root_weight_result.weight / 2 === weight_result.weight);
let certs = await get_all_certs(local[0].peerId, local[1].peerId);
console.log("Certs: %s", JSON.stringify(certs.certificates));
assert(certs.certificates.length === 1);
// wait to create revoke after trust (because timestamp in secs)
await new Promise(f => setTimeout(f, 1000));
// revoke local[1].peerId trust
await revoke_helper(local[0].peerId, issuer_kp, local[0].peerId, local[1].peerId, await timestamp_sec(local[0].peerId));
let empty_certs = await get_all_certs(local[0].peerId, local[1].peerId);
assert(empty_certs.certificates.length === 0);
return;
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

4129
example/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
example/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "trust-graph-aqua-example",
"version": "1.0.0",
"description": "A simple example of how to use trust-graph in TS",
"main": "index.js",
"scripts": {
"compile-aqua": "aqua -i aqua -o generated",
"prebuild": "npm run compile-aqua",
"build": "tsc",
"start": "node dist/index.js",
"prestart": "npm run build"
},
"author": "Fluence Labs",
"license": "MIT",
"dependencies": {
"@fluencelabs/aqua": "0.4.1-240",
"@fluencelabs/aqua-lib": "0.2.0",
"@fluencelabs/fluence": "0.14.3",
"@fluencelabs/fluence-network-environment": "^1.0.10",
"@fluencelabs/trust-graph": "file:../aqua",
"bs58": "^4.0.1"
},
"devDependencies": {
"typescript": "^4.4.3"
}
}

69
example/tsconfig.json Normal file
View File

@ -0,0 +1,69 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -1,23 +0,0 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module', // Allows for the use of imports
},
env: {
browser: true,
es2021: true,
},
extends: [
'airbnb-base',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
// Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
'plugin:prettier/recommended',
],
plugins: ['@typescript-eslint', 'prettier'],
rules: {},
settings: {
'import/extensions': ['.js', '.ts'],
},
};

17
js/.gitignore vendored
View File

@ -1,17 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.idea
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
bundle/
# Dependency directories
node_modules/
jspm_packages/
/dist/

View File

@ -1,12 +0,0 @@
.idea
.gitignore
node_modules
types
src/
tsconfig.json
webpack.config.js
bundle
pkg

View File

@ -1,8 +0,0 @@
module.exports = {
semi: true,
trailingComma: "all",
singleQuote: true,
printWidth: 120,
tabWidth: 4,
useTabs: false
};

View File

@ -1,4 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

7295
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
{
"name": "@fluencelabs/trust-graph",
"version": "0.1.0",
"description": "Trust graph implementation",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"scripts": {
"test": "jest --watch",
"test:all": "jest",
"build": "tsc"
},
"repository": "https://github.com/fluencelabs/fluence-js",
"author": "Fluence Labs",
"license": "Apache-2.0",
"dependencies": {
"bs58": "4.0.1",
"libp2p-crypto": "0.19.0",
"loglevel": "1.7.0",
"peer-id": "0.13.12"
},
"devDependencies": {
"@types/base64-js": "1.2.5",
"@types/bs58": "4.0.1",
"assert": "2.0.0",
"libp2p-ts": "https://github.com/ChainSafe/libp2p-ts.git#fca072c9764436ef71f974a211bb1befae432575",
"@types/node": "14.14.28",
"mocha-loader": "^5.1.5",
"ts-loader": "7.0.5",
"ts-mocha": "8.0.0",
"typescript": "^3.9.5",
"jest": "^26.6.3",
"@types/jest": "^26.0.20",
"ts-jest": "^26.5.0"
}
}

View File

@ -1,63 +0,0 @@
import {certificateFromString, certificateToString} from "../certificate";
describe('Typescript usage suite', () => {
it('should serialize and deserialize certificate correctly', async function () {
let cert = `11
1111
5566Dn4ZXXbBK5LJdUsE7L3pG9qdAzdPY47adjzkhEx9
3HNXpW2cLdqXzf4jz5EhsGEBFkWzuVdBCyxzJUZu2WPVU7kpzPjatcqvdJMjTtcycVAdaV5qh2fCGphSmw8UMBkr
158981172690500
1589974723504
2EvoZAZaGjKWFVdr36F1jphQ5cW7eK3yM16mqEHwQyr7
4UAJQWzB3nTchBtwARHAhsn7wjdYtqUHojps9xV6JkuLENV8KRiWM3BhQByx5KijumkaNjr7MhHjouLawmiN1A4d
1590061123504
1589974723504`;
let deser = await certificateFromString(cert);
let ser = certificateToString(deser);
expect(ser).toEqual(cert);
});
})
// TODO implement this test when `trust-graph-js - fluence-js - fluence node` chain will be available
// export async function testCerts() {
// const key1 = await generatePeerId();
// const key2 = await generatePeerId();
//
// // connect to two different nodes
// const cl1 = new FluenceClientImpl(key1);
// const cl2 = new FluenceClientImpl(key2);
//
// await cl1.connect('/dns4/134.209.186.43/tcp/9003/ws/p2p/12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb');
// await cl2.connect('/ip4/134.209.186.43/tcp/9002/ws/p2p/12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er');
//
// let trustGraph1 = new TrustGraph(/* cl1 */);
// let trustGraph2 = new TrustGraph(/* cl2 */);
//
// let issuedAt = new Date();
// let expiresAt = new Date();
// // certificate expires after one day
// expiresAt.setDate(new Date().getDate() + 1);
//
// // create root certificate for key1 and extend it with key2
// let rootCert = await nodeRootCert(key1);
// let extended = await issue(key1, key2, rootCert, expiresAt.getTime(), issuedAt.getTime());
//
// // publish certificates to Fluence network
// await trustGraph1.publishCertificates(key2.toB58String(), [extended]);
//
// // get certificates from network
// let certs = await trustGraph2.getCertificates(key2.toB58String());
//
// // root certificate could be different because nodes save trusts with bigger `expiresAt` date and less `issuedAt` date
// expect(certs[0].chain[1].issuedFor.toB58String()).to.be.equal(extended.chain[1].issuedFor.toB58String());
// expect(certs[0].chain[1].signature).to.be.equal(extended.chain[1].signature);
// expect(certs[0].chain[1].expiresAt).to.be.equal(extended.chain[1].expiresAt);
// expect(certs[0].chain[1].issuedAt).to.be.equal(extended.chain[1].issuedAt);
//
// await cl1.disconnect();
// await cl2.disconnect();
// }

View File

@ -1,107 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createTrust, Trust, trustFromString, trustToString } from './trust';
import * as PeerId from 'peer-id';
const FORMAT = '11';
const VERSION = '1111';
// TODO verify certificate
// Chain of trusts started from self-signed root trust.
export interface Certificate {
chain: Trust[];
}
export function certificateToString(cert: Certificate): string {
let certStr = cert.chain.map((t) => trustToString(t)).join('\n');
return `${FORMAT}\n${VERSION}\n${certStr}`;
}
export async function certificateFromString(str: string): Promise<Certificate> {
let lines = str.split('\n');
// last line could be empty
if (!lines[lines.length - 1]) {
lines.pop();
}
// TODO do match different formats and versions
let _format = lines[0];
let _version = lines[1];
// every trust is 4 lines, certificate lines number without format and version should be divided by 4
if ((lines.length - 2) % 4 !== 0) {
throw Error('Incorrect format of the certificate:\n' + str);
}
let chain: Trust[] = [];
let i;
for (i = 2; i < lines.length; i = i + 4) {
chain.push(await trustFromString(lines[i], lines[i + 1], lines[i + 2], lines[i + 3]));
}
return { chain };
}
// Creates new certificate with root trust (self-signed public key) from a key pair.
export async function issueRoot(
issuedBy: PeerId,
forPk: PeerId,
expiresAt: number,
issuedAt: number,
): Promise<Certificate> {
if (expiresAt < issuedAt) {
throw Error('Expiration time should be greater then issued time.');
}
let maxDate = new Date(158981172690500).getTime();
let rootTrust = await createTrust(issuedBy, issuedBy, maxDate, issuedAt);
let trust = await createTrust(forPk, issuedBy, expiresAt, issuedAt);
let chain = [rootTrust, trust];
return {
chain: chain,
};
}
// Adds a new trust into chain of trust in certificate.
export async function issue(
issuedBy: PeerId,
forPk: PeerId,
extendCert: Certificate,
expiresAt: number,
issuedAt: number,
): Promise<Certificate> {
if (expiresAt < issuedAt) {
throw Error('Expiration time should be greater then issued time.');
}
let lastTrust = extendCert.chain[extendCert.chain.length - 1];
if (lastTrust.issuedFor !== issuedBy) {
throw Error('`issuedFor` should be equal to `issuedBy` in the last trust of the chain.');
}
let trust = await createTrust(forPk, issuedBy, expiresAt, issuedAt);
let chain = [...extendCert.chain];
chain.push(trust);
return {
chain: chain,
};
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './certificate'
export * from './trust'
export * from './trust_graph'
export * from './misc'

View File

@ -1,36 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as PeerId from 'peer-id';
import { keys } from 'libp2p-crypto';
import { Certificate, issueRoot } from './certificate';
/**
* Generate root certificate with one of the Fluence trusted key for one day.
*/
export async function nodeRootCert(issuedFor: PeerId): Promise<Certificate> {
// prettier-ignore
let seed = [46, 188, 245, 171, 145, 73, 40, 24, 52, 233, 215, 163, 54, 26, 31, 221, 159, 179, 126, 106, 27, 199, 189, 194, 80, 133, 235, 42, 42, 247, 80, 201];
let privateK = await keys.generateKeyPairFromSeed('Ed25519', Uint8Array.from(seed), 256);
let peerId = await PeerId.createFromPrivKey(Buffer.from(privateK.bytes));
let issuedAt = new Date();
let expiresAt = new Date();
expiresAt.setDate(new Date().getDate() + 1);
return await issueRoot(peerId, issuedFor, expiresAt.getTime(), issuedAt.getTime());
}

View File

@ -1,90 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as PeerId from 'peer-id';
import { decode, encode } from 'bs58';
import crypto from 'libp2p-crypto';
const ed25519 = crypto.keys.supportedKeys.ed25519;
// One element in chain of trust in a certificate.
export interface Trust {
issuedFor: PeerId;
expiresAt: number;
signature: string;
issuedAt: number;
}
export function trustToString(trust: Trust): string {
return `${encode(trust.issuedFor.pubKey.marshal())}\n${trust.signature}\n${trust.expiresAt}\n${trust.issuedAt}`;
}
export async function trustFromString(
issuedFor: string,
signature: string,
expiresAt: string,
issuedAt: string,
): Promise<Trust> {
let pubKey = ed25519.unmarshalEd25519PublicKey(decode(issuedFor));
let peerId = await PeerId.createFromPubKey(Buffer.from(pubKey.bytes));
return {
issuedFor: peerId,
signature: signature,
expiresAt: parseInt(expiresAt),
issuedAt: parseInt(issuedAt),
};
}
export async function createTrust(
forPk: PeerId,
issuedBy: PeerId,
expiresAt: number,
issuedAt: number,
): Promise<Trust> {
let bytes = toSignMessage(forPk, expiresAt, issuedAt);
let signature = await issuedBy.privKey.sign(Buffer.from(bytes));
let signatureStr = encode(signature);
return {
issuedFor: forPk,
expiresAt: expiresAt,
signature: signatureStr,
issuedAt: issuedAt,
};
}
function toSignMessage(pk: PeerId, expiresAt: number, issuedAt: number): Uint8Array {
let bytes = new Uint8Array(48);
let pkEncoded = pk.pubKey.marshal();
bytes.set(pkEncoded, 0);
bytes.set(numToArray(expiresAt), 32);
bytes.set(numToArray(issuedAt), 40);
return bytes;
}
function numToArray(n: number): number[] {
let byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
for (let index = 0; index < byteArray.length; index++) {
let byte = n & 0xff;
byteArray[index] = byte;
n = (n - byte) / 256;
}
return byteArray;
}

View File

@ -1,75 +0,0 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Certificate, certificateFromString, certificateToString } from './certificate';
import * as log from 'loglevel';
// TODO inherit this with FluenceClient in fluence-js
// The client to interact with the Fluence trust graph API
export class TrustGraph {
//client: FluenceClient;
constructor() {}
// Publish certificate to Fluence network. It will be published in Kademlia neighbourhood by `peerId` key.
async publishCertificates(peerId: string, certs: Certificate[]) {
let certsStr = [];
for (let cert of certs) {
certsStr.push(await certificateToString(cert));
}
// TODO inherit this with FluenceClient in fluence-js
throw new Error("unimplemented")
/*let response = await this.client.callPeer("add_certificates", {
certificates: certsStr,
peer_id: peerId
});*/
let response: any = {};
if (response.reason) {
throw Error(response.reason);
} else if (response.status) {
return response.status;
} else {
throw Error(
`Unexpected response: ${response}. Should be 'status' field for a success response or 'reason' field for an error.`,
);
}
}
// Get certificates that stores in Kademlia neighbourhood by `peerId` key.
async getCertificates(peerId: string): Promise<Certificate[]> {
let resp: any = {};
// TODO inherit this with FluenceClient in fluence-js
throw new Error("unimplemented")
/*let resp = await this.client.callPeer("certificates", {
peer_id: peerId
});*/
let certificatesRaw = resp.certificates;
if (!(certificatesRaw && Array.isArray(certificatesRaw))) {
log.error(Array.isArray(certificatesRaw));
throw Error('Unexpected. Certificates should be presented in the response as an array.');
}
let certs = [];
for (let cert of certificatesRaw) {
certs.push(await certificateFromString(cert));
}
return certs;
}
}

View File

@ -1,35 +0,0 @@
{
"compilerOptions": {
"typeRoots": [
"./node_modules/@types",
"./node_modules/libp2p-ts/types",
"./types"
],
"outDir": "./dist/",
"baseUrl": ".",
"sourceMap": true,
"inlineSources": true,
"strictFunctionTypes": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"pretty": true,
"target": "ES5",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"esModuleInterop": true,
"declarationMap": true,
"strict": true,
"noImplicitAny": false,
"alwaysStrict": true,
"noImplicitThis": true,
"strictNullChecks": false
},
"exclude": [
"node_modules",
"dist",
"bundle",
"src/__test__"
],
"include": ["src/**/*"]
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "fluence-keypair" name = "fluence-keypair"
version = "0.4.0" version = "0.5.1"
authors = ["Fluence Labs"] authors = ["Fluence Labs"]
edition = "2018" edition = "2018"
description = "identity" description = "identity"

View File

@ -19,13 +19,13 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
//! Ed25519 keys. //! Ed25519 keys.
use crate::error::{DecodingError, SigningError}; use crate::error::{DecodingError, SigningError, VerificationError};
use core::fmt;
use ed25519_dalek::{self as ed25519, Signer as _, Verifier as _}; use ed25519_dalek::{self as ed25519, Signer as _, Verifier as _};
use rand::RngCore; use rand::RngCore;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use zeroize::Zeroize; use zeroize::Zeroize;
use core::fmt;
use serde::{Deserialize, Serialize};
/// An Ed25519 keypair. /// An Ed25519 keypair.
pub struct Keypair(ed25519::Keypair); pub struct Keypair(ed25519::Keypair);
@ -73,7 +73,9 @@ impl Keypair {
impl fmt::Debug for Keypair { impl fmt::Debug for Keypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Keypair").field("public", &self.0.public).finish() f.debug_struct("Keypair")
.field("public", &self.0.public)
.finish()
} }
} }
@ -81,7 +83,8 @@ impl Clone for Keypair {
fn clone(&self) -> Self { fn clone(&self) -> Self {
let mut sk_bytes = self.0.secret.to_bytes(); let mut sk_bytes = self.0.secret.to_bytes();
let secret = SecretKey::from_bytes(&mut sk_bytes) let secret = SecretKey::from_bytes(&mut sk_bytes)
.expect("ed25519::SecretKey::from_bytes(to_bytes(k)) != k").0; .expect("ed25519::SecretKey::from_bytes(to_bytes(k)) != k")
.0;
let public = ed25519::PublicKey::from_bytes(&self.0.public.to_bytes()) let public = ed25519::PublicKey::from_bytes(&self.0.public.to_bytes())
.expect("ed25519::PublicKey::from_bytes(to_bytes(k)) != k"); .expect("ed25519::PublicKey::from_bytes(to_bytes(k)) != k");
Keypair(ed25519::Keypair { secret, public }) Keypair(ed25519::Keypair { secret, public })
@ -107,7 +110,10 @@ impl From<SecretKey> for Keypair {
fn from(sk: SecretKey) -> Self { fn from(sk: SecretKey) -> Self {
let secret: ed25519::ExpandedSecretKey = (&sk.0).into(); let secret: ed25519::ExpandedSecretKey = (&sk.0).into();
let public = ed25519::PublicKey::from(&secret); let public = ed25519::PublicKey::from(&secret);
Keypair(ed25519::Keypair { secret: sk.0, public }) Keypair(ed25519::Keypair {
secret: sk.0,
public,
})
} }
} }
@ -117,8 +123,16 @@ pub struct PublicKey(ed25519::PublicKey);
impl PublicKey { impl PublicKey {
/// Verify the Ed25519 signature on a message using the public key. /// Verify the Ed25519 signature on a message using the public key.
pub fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), SigningError> { pub fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), VerificationError> {
ed25519::Signature::try_from(sig).and_then(|s| self.0.verify(msg, &s)).map_err(SigningError::Ed25519) ed25519::Signature::try_from(sig)
.and_then(|s| self.0.verify(msg, &s))
.map_err(|e| {
VerificationError::Ed25519(
e,
bs58::encode(sig).into_string(),
bs58::encode(self.0.as_bytes()).into_string(),
)
})
} }
/// Encode the public key into a byte array in compressed form, i.e. /// Encode the public key into a byte array in compressed form, i.e.
@ -148,8 +162,7 @@ impl AsRef<[u8]> for SecretKey {
impl Clone for SecretKey { impl Clone for SecretKey {
fn clone(&self) -> Self { fn clone(&self) -> Self {
let mut sk_bytes = self.0.to_bytes(); let mut sk_bytes = self.0.to_bytes();
Self::from_bytes(&mut sk_bytes) Self::from_bytes(&mut sk_bytes).expect("ed25519::SecretKey::from_bytes(to_bytes(k)) != k")
.expect("ed25519::SecretKey::from_bytes(to_bytes(k)) != k")
} }
} }
@ -164,8 +177,11 @@ impl SecretKey {
pub fn generate() -> Self { pub fn generate() -> Self {
let mut bytes = [0u8; 32]; let mut bytes = [0u8; 32];
rand::thread_rng().fill_bytes(&mut bytes); rand::thread_rng().fill_bytes(&mut bytes);
SecretKey(ed25519::SecretKey::from_bytes(&bytes) SecretKey(
.expect("this returns `Err` only if the length is wrong; the length is correct; qed")) ed25519::SecretKey::from_bytes(&bytes).expect(
"this returns `Err` only if the length is wrong; the length is correct; qed",
),
)
} }
/// Create an Ed25519 secret key from a byte slice, zeroing the input on success. /// Create an Ed25519 secret key from a byte slice, zeroing the input on success.
@ -173,8 +189,7 @@ impl SecretKey {
/// returned. /// returned.
pub fn from_bytes(mut sk_bytes: impl AsMut<[u8]>) -> Result<Self, DecodingError> { pub fn from_bytes(mut sk_bytes: impl AsMut<[u8]>) -> Result<Self, DecodingError> {
let sk_bytes = sk_bytes.as_mut(); let sk_bytes = sk_bytes.as_mut();
let secret = ed25519::SecretKey::from_bytes(&*sk_bytes) let secret = ed25519::SecretKey::from_bytes(&*sk_bytes).map_err(DecodingError::Ed25519)?;
.map_err(DecodingError::Ed25519)?;
sk_bytes.zeroize(); sk_bytes.zeroize();
Ok(SecretKey(secret)) Ok(SecretKey(secret))
} }
@ -189,9 +204,7 @@ mod tests {
use quickcheck::*; use quickcheck::*;
fn eq_keypairs(kp1: &Keypair, kp2: &Keypair) -> bool { fn eq_keypairs(kp1: &Keypair, kp2: &Keypair) -> bool {
kp1.public() == kp2.public() kp1.public() == kp2.public() && kp1.0.secret.as_bytes() == kp2.0.secret.as_bytes()
&&
kp1.0.secret.as_bytes() == kp2.0.secret.as_bytes()
} }
#[test] #[test]
@ -200,9 +213,7 @@ mod tests {
let kp1 = Keypair::generate(); let kp1 = Keypair::generate();
let mut kp1_enc = kp1.encode(); let mut kp1_enc = kp1.encode();
let kp2 = Keypair::decode(&mut kp1_enc).unwrap(); let kp2 = Keypair::decode(&mut kp1_enc).unwrap();
eq_keypairs(&kp1, &kp2) eq_keypairs(&kp1, &kp2) && kp1_enc.iter().all(|b| *b == 0)
&&
kp1_enc.iter().all(|b| *b == 0)
} }
QuickCheck::new().tests(10).quickcheck(prop as fn() -> _); QuickCheck::new().tests(10).quickcheck(prop as fn() -> _);
} }
@ -213,9 +224,7 @@ mod tests {
let kp1 = Keypair::generate(); let kp1 = Keypair::generate();
let mut sk = kp1.0.secret.to_bytes(); let mut sk = kp1.0.secret.to_bytes();
let kp2 = Keypair::from(SecretKey::from_bytes(&mut sk).unwrap()); let kp2 = Keypair::from(SecretKey::from_bytes(&mut sk).unwrap());
eq_keypairs(&kp1, &kp2) eq_keypairs(&kp1, &kp2) && sk == [0u8; 32]
&&
sk == [0u8; 32]
} }
QuickCheck::new().tests(10).quickcheck(prop as fn() -> _); QuickCheck::new().tests(10).quickcheck(prop as fn() -> _);
} }

View File

@ -35,7 +35,7 @@ pub enum DecodingError {
Ed25519( Ed25519(
#[from] #[from]
#[source] #[source]
ed25519_dalek::ed25519::Error ed25519_dalek::ed25519::Error,
), ),
#[error("Failed to decode with RSA")] #[error("Failed to decode with RSA")]
Rsa, Rsa,
@ -49,6 +49,8 @@ pub enum DecodingError {
Base58DecodeError(#[source] bs58::decode::Error), Base58DecodeError(#[source] bs58::decode::Error),
#[error("Raw signature decoding failed: type {0} not supported")] #[error("Raw signature decoding failed: type {0} not supported")]
RawSignatureUnsupportedType(String), RawSignatureUnsupportedType(String),
#[error("public key is not inlined in peer id: {0}")]
PublicKeyNotInlined(String),
} }
/// An error during signing of a message. /// An error during signing of a message.
@ -58,7 +60,7 @@ pub enum SigningError {
Ed25519( Ed25519(
#[from] #[from]
#[source] #[source]
ed25519_dalek::ed25519::Error ed25519_dalek::ed25519::Error,
), ),
#[error("Failed to sign with RSA")] #[error("Failed to sign with RSA")]
Rsa, Rsa,
@ -66,6 +68,20 @@ pub enum SigningError {
Secp256k1( Secp256k1(
#[from] #[from]
#[source] #[source]
secp256k1::Error secp256k1::Error,
), ),
} }
/// An error during verification of a message.
#[derive(ThisError, Debug)]
pub enum VerificationError {
#[error("Failed to verify signature {1} with {2} ed25519 public key: {0}")]
Ed25519(#[source] ed25519_dalek::ed25519::Error, String, String),
#[cfg(not(target_arch = "wasm32"))]
#[error("Failed to verify signature {1} with {2} RSA public key: {0}")]
Rsa(#[source] ring::error::Unspecified, String, String),
#[error("Failed to verify signature {1} with {2} secp256k1 public key: {0}")]
Secp256k1(#[source] secp256k1::Error, String, String),
}

View File

@ -20,15 +20,15 @@
//! A node's network identity keys. //! A node's network identity keys.
use crate::ed25519; use crate::ed25519;
use crate::error::{DecodingError, Error, SigningError, VerificationError};
use crate::public_key::PublicKey;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::rsa; use crate::rsa;
use crate::secp256k1; use crate::secp256k1;
use crate::public_key::PublicKey;
use crate::signature::Signature; use crate::signature::Signature;
use crate::error::{Error, DecodingError, SigningError};
use std::str::FromStr;
use std::convert::TryFrom;
use libp2p_core::PeerId; use libp2p_core::PeerId;
use std::convert::TryFrom;
use std::str::FromStr;
/// Identity keypair of a node. /// Identity keypair of a node.
/// ///
@ -48,7 +48,6 @@ use libp2p_core::PeerId;
/// ``` /// ```
/// ///
pub enum KeyFormat { pub enum KeyFormat {
Ed25519, Ed25519,
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -66,7 +65,7 @@ impl FromStr for KeyFormat {
"secp256k1" => Ok(KeyFormat::Secp256k1), "secp256k1" => Ok(KeyFormat::Secp256k1),
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
"rsa" => Ok(KeyFormat::Rsa), "rsa" => Ok(KeyFormat::Rsa),
_ => Err(Error::InvalidKeyFormat(s.to_string())) _ => Err(Error::InvalidKeyFormat(s.to_string())),
} }
} }
} }
@ -80,7 +79,7 @@ impl TryFrom<u8> for KeyFormat {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
1 => Ok(KeyFormat::Rsa), 1 => Ok(KeyFormat::Rsa),
2 => Ok(KeyFormat::Secp256k1), 2 => Ok(KeyFormat::Secp256k1),
_ => Err(DecodingError::InvalidTypeByte) _ => Err(DecodingError::InvalidTypeByte),
} }
} }
} }
@ -96,6 +95,16 @@ impl From<KeyFormat> for u8 {
} }
} }
impl From<KeyFormat> for String {
fn from(kf: KeyFormat) -> Self {
match kf {
KeyFormat::Ed25519 => "ed25519".to_string(),
#[cfg(not(target_arch = "wasm32"))]
KeyFormat::Rsa => "rsa".to_string(),
KeyFormat::Secp256k1 => "secp256k1".to_string(),
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub enum KeyPair { pub enum KeyPair {
/// An Ed25519 keypair. /// An Ed25519 keypair.
@ -153,7 +162,9 @@ impl KeyPair {
Ed25519(ref pair) => Ok(Signature::Ed25519(ed25519::Signature(pair.sign(msg)?))), Ed25519(ref pair) => Ok(Signature::Ed25519(ed25519::Signature(pair.sign(msg)?))),
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
Rsa(ref pair) => Ok(Signature::Rsa(rsa::Signature(pair.sign(msg)?))), Rsa(ref pair) => Ok(Signature::Rsa(rsa::Signature(pair.sign(msg)?))),
Secp256k1(ref pair) => Ok(Signature::Secp256k1(secp256k1::Signature(pair.secret().sign(msg)?))) Secp256k1(ref pair) => Ok(Signature::Secp256k1(secp256k1::Signature(
pair.secret().sign(msg)?,
))),
} }
} }
@ -179,7 +190,11 @@ impl KeyPair {
} }
/// Verify the signature on a message using the public key. /// Verify the signature on a message using the public key.
pub fn verify(pk: &PublicKey, msg: &[u8], signature: &Signature) -> Result<(), SigningError> { pub fn verify(
pk: &PublicKey,
msg: &[u8],
signature: &Signature,
) -> Result<(), VerificationError> {
pk.verify(msg, signature) pk.verify(msg, signature)
} }
@ -200,12 +215,12 @@ impl KeyPair {
KeyFormat::Ed25519 => Ok(Ed25519(ed25519::Keypair::decode(&mut bytes)?)), KeyFormat::Ed25519 => Ok(Ed25519(ed25519::Keypair::decode(&mut bytes)?)),
KeyFormat::Secp256k1 => Ok(Secp256k1(secp256k1::SecretKey::from_bytes(bytes)?.into())), KeyFormat::Secp256k1 => Ok(Secp256k1(secp256k1::SecretKey::from_bytes(bytes)?.into())),
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
KeyFormat::Rsa => Err(DecodingError::KeypairDecodingIsNotSupported) KeyFormat::Rsa => Err(DecodingError::KeypairDecodingIsNotSupported),
} }
} }
pub fn get_peer_id(&self) -> PeerId { pub fn get_peer_id(&self) -> PeerId {
self.public().to_peer_id() self.public().to_peer_id()
} }
} }
@ -217,23 +232,34 @@ impl From<libp2p_core::identity::Keypair> for KeyPair {
Ed25519(kp) => KeyPair::Ed25519(ed25519::Keypair::decode(&mut kp.encode()).unwrap()), Ed25519(kp) => KeyPair::Ed25519(ed25519::Keypair::decode(&mut kp.encode()).unwrap()),
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
// safety: these Keypair structures are identical // safety: these Keypair structures are identical
Rsa(kp) => KeyPair::Rsa(unsafe { std::mem::transmute::<libp2p_core::identity::rsa::Keypair, rsa::Keypair>(kp) }), Rsa(kp) => KeyPair::Rsa(unsafe {
Secp256k1(kp) => KeyPair::Secp256k1(secp256k1::Keypair::from(secp256k1::SecretKey::from_bytes(kp.secret().to_bytes()).unwrap())), std::mem::transmute::<libp2p_core::identity::rsa::Keypair, rsa::Keypair>(kp)
}),
Secp256k1(kp) => KeyPair::Secp256k1(secp256k1::Keypair::from(
secp256k1::SecretKey::from_bytes(kp.secret().to_bytes()).unwrap(),
)),
} }
} }
} }
impl From<KeyPair> for libp2p_core::identity::Keypair { impl From<KeyPair> for libp2p_core::identity::Keypair {
fn from(key: KeyPair) -> Self { fn from(key: KeyPair) -> Self {
use KeyPair::*;
use libp2p_core::identity::Keypair;
use libp2p_core::identity; use libp2p_core::identity;
use libp2p_core::identity::Keypair;
use KeyPair::*;
match key { match key {
Ed25519(kp) => Keypair::Ed25519(identity::ed25519::Keypair::decode(kp.encode().to_vec().as_mut_slice()).unwrap()), Ed25519(kp) => Keypair::Ed25519(
identity::ed25519::Keypair::decode(kp.encode().to_vec().as_mut_slice()).unwrap(),
),
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
Rsa(kp) => Keypair::Rsa(unsafe { std::mem::transmute::<rsa::Keypair, libp2p_core::identity::rsa::Keypair>(kp) }), // safety: these Keypair structures are identical
Secp256k1(kp) => Keypair::Secp256k1(identity::secp256k1::Keypair::from(identity::secp256k1::SecretKey::from_bytes(kp.secret().to_bytes()).unwrap())), Rsa(kp) => Keypair::Rsa(unsafe {
std::mem::transmute::<rsa::Keypair, libp2p_core::identity::rsa::Keypair>(kp)
}),
Secp256k1(kp) => Keypair::Secp256k1(identity::secp256k1::Keypair::from(
identity::secp256k1::SecretKey::from_bytes(kp.secret().to_bytes()).unwrap(),
)),
} }
} }
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use crate::ed25519; use crate::ed25519;
use crate::error::{DecodingError, SigningError}; use crate::error::{DecodingError, VerificationError};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::rsa; use crate::rsa;
use crate::secp256k1; use crate::secp256k1;
@ -43,7 +43,7 @@ impl PublicKey {
/// private key (authenticity), and that the message has not been /// private key (authenticity), and that the message has not been
/// tampered with (integrity). /// tampered with (integrity).
// TODO: add VerificationError // TODO: add VerificationError
pub fn verify(&self, msg: &[u8], sig: &Signature) -> Result<(), SigningError> { pub fn verify(&self, msg: &[u8], sig: &Signature) -> Result<(), VerificationError> {
use PublicKey::*; use PublicKey::*;
match self { match self {
Ed25519(pk) => pk.verify(msg, sig.to_vec()), Ed25519(pk) => pk.verify(msg, sig.to_vec()),
@ -111,6 +111,17 @@ impl PublicKey {
pub fn to_peer_id(&self) -> PeerId { pub fn to_peer_id(&self) -> PeerId {
PeerId::from_public_key(self.clone().into()) PeerId::from_public_key(self.clone().into())
} }
pub fn get_key_format(&self) -> KeyFormat {
use PublicKey::*;
match self {
Ed25519(_) => KeyFormat::Ed25519,
#[cfg(not(target_arch = "wasm32"))]
Rsa(_) => KeyFormat::Rsa,
Secp256k1(_) => KeyFormat::Secp256k1,
}
}
} }
impl From<libp2p_core::identity::PublicKey> for PublicKey { impl From<libp2p_core::identity::PublicKey> for PublicKey {
@ -150,15 +161,12 @@ impl From<PublicKey> for libp2p_core::identity::PublicKey {
} }
impl TryFrom<libp2p_core::PeerId> for PublicKey { impl TryFrom<libp2p_core::PeerId> for PublicKey {
type Error = eyre::Error; type Error = DecodingError;
fn try_from(peer_id: libp2p_core::PeerId) -> eyre::Result<PublicKey> { fn try_from(peer_id: libp2p_core::PeerId) -> Result<Self, Self::Error> {
Ok(peer_id Ok(peer_id
.as_public_key() .as_public_key()
.ok_or(eyre::eyre!( .ok_or(DecodingError::PublicKeyNotInlined(peer_id.to_base58()))?
"public key is not inlined in peer id: {}",
peer_id
))?
.into()) .into())
} }
} }

View File

@ -19,16 +19,19 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
//! RSA keys. //! RSA keys.
use crate::error::{DecodingError, SigningError}; use crate::error::{DecodingError, SigningError, VerificationError};
use asn1_der::{Asn1Der, FromDerObject, IntoDerObject, DerObject, DerTag, DerValue, Asn1DerError}; use asn1_der::{Asn1Der, Asn1DerError, DerObject, DerTag, DerValue, FromDerObject, IntoDerObject};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use ring::rand::SystemRandom; use ring::rand::SystemRandom;
use ring::signature::{self, RsaKeyPair, RSA_PKCS1_SHA256, RSA_PKCS1_2048_8192_SHA256};
use ring::signature::KeyPair; use ring::signature::KeyPair;
use std::{fmt::{self, Write}, sync::Arc}; use ring::signature::{self, RsaKeyPair, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256};
use zeroize::Zeroize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Write},
sync::Arc,
};
use zeroize::Zeroize;
/// An RSA keypair. /// An RSA keypair.
#[derive(Clone)] #[derive(Clone)]
@ -40,8 +43,7 @@ impl Keypair {
/// ///
/// [RFC5208]: https://tools.ietf.org/html/rfc5208#section-5 /// [RFC5208]: https://tools.ietf.org/html/rfc5208#section-5
pub fn from_pkcs8(der: &mut [u8]) -> Result<Self, DecodingError> { pub fn from_pkcs8(der: &mut [u8]) -> Result<Self, DecodingError> {
let kp = RsaKeyPair::from_pkcs8(&der) let kp = RsaKeyPair::from_pkcs8(&der).map_err(|_| DecodingError::Rsa)?;
.map_err(|_| DecodingError::Rsa)?;
der.zeroize(); der.zeroize();
Ok(Keypair(Arc::new(kp))) Ok(Keypair(Arc::new(kp)))
} }
@ -57,7 +59,7 @@ impl Keypair {
let rng = SystemRandom::new(); let rng = SystemRandom::new();
match self.0.sign(&RSA_PKCS1_SHA256, &rng, &data, &mut signature) { match self.0.sign(&RSA_PKCS1_SHA256, &rng, &data, &mut signature) {
Ok(()) => Ok(signature), Ok(()) => Ok(signature),
Err(_) => Err(SigningError::Rsa) Err(_) => Err(SigningError::Rsa),
} }
} }
} }
@ -68,9 +70,15 @@ pub struct PublicKey(Vec<u8>);
impl PublicKey { impl PublicKey {
/// Verify an RSA signature on a message using the public key. /// Verify an RSA signature on a message using the public key.
pub fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), SigningError> { pub fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), VerificationError> {
let key = signature::UnparsedPublicKey::new(&RSA_PKCS1_2048_8192_SHA256, &self.0); let key = signature::UnparsedPublicKey::new(&RSA_PKCS1_2048_8192_SHA256, &self.0);
key.verify(msg, sig).map_err(|_| SigningError::Rsa) key.verify(msg, sig).map_err(|e| {
VerificationError::Rsa(
e,
bs58::encode(sig).into_string(),
bs58::encode(&self.0).into_string(),
)
})
} }
/// Encode the RSA public key in DER as a PKCS#1 RSAPublicKey structure, /// Encode the RSA public key in DER as a PKCS#1 RSAPublicKey structure,
@ -99,7 +107,8 @@ impl PublicKey {
subjectPublicKey: Asn1SubjectPublicKey(self.clone()), subjectPublicKey: Asn1SubjectPublicKey(self.clone()),
}; };
let mut buf = vec![0u8; spki.serialized_len()]; let mut buf = vec![0u8; spki.serialized_len()];
spki.serialize(buf.iter_mut()).map(|_| buf) spki.serialize(buf.iter_mut())
.map(|_| buf)
.expect("RSA X.509 public key encoding failed.") .expect("RSA X.509 public key encoding failed.")
} }
@ -121,9 +130,7 @@ impl fmt::Debug for PublicKey {
write!(hex, "{:02x}", byte).expect("Can't fail on writing to string"); write!(hex, "{:02x}", byte).expect("Can't fail on writing to string");
} }
f.debug_struct("PublicKey") f.debug_struct("PublicKey").field("pkcs1", &hex).finish()
.field("pkcs1", &hex)
.finish()
} }
} }
@ -273,6 +280,8 @@ mod tests {
fn prop(SomeKeypair(kp): SomeKeypair, msg: Vec<u8>) -> Result<bool, SigningError> { fn prop(SomeKeypair(kp): SomeKeypair, msg: Vec<u8>) -> Result<bool, SigningError> {
kp.sign(&msg).map(|s| kp.public().verify(&msg, &s).is_ok()) kp.sign(&msg).map(|s| kp.public().verify(&msg, &s).is_ok())
} }
QuickCheck::new().tests(10).quickcheck(prop as fn(_, _) -> _); QuickCheck::new()
.tests(10)
.quickcheck(prop as fn(_, _) -> _);
} }
} }

View File

@ -19,17 +19,17 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
//! Secp256k1 keys. //! Secp256k1 keys.
use crate::error::{DecodingError, SigningError}; use crate::error::{DecodingError, SigningError, VerificationError};
use asn1_der::{FromDerObject, DerObject}; use asn1_der::{DerObject, FromDerObject};
use rand::RngCore;
use sha2::{Digest as ShaDigestTrait, Sha256};
use secp256k1::Message;
use zeroize::Zeroize;
use core::fmt; use core::fmt;
use serde::{Deserialize, Serialize, Serializer, Deserializer}; use rand::RngCore;
use secp256k1::Message;
use serde::de::Error as SerdeError; use serde::de::Error as SerdeError;
use serde_bytes::{Bytes as SerdeBytes, ByteBuf as SerdeByteBuf}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};
use sha2::{Digest as ShaDigestTrait, Sha256};
use zeroize::Zeroize;
/// A Secp256k1 keypair. /// A Secp256k1 keypair.
#[derive(Clone)] #[derive(Clone)]
@ -57,7 +57,9 @@ impl Keypair {
impl fmt::Debug for Keypair { impl fmt::Debug for Keypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Keypair").field("public", &self.public).finish() f.debug_struct("Keypair")
.field("public", &self.public)
.finish()
} }
} }
@ -106,8 +108,8 @@ impl SecretKey {
/// error is returned. /// error is returned.
pub fn from_bytes(mut sk: impl AsMut<[u8]>) -> Result<Self, DecodingError> { pub fn from_bytes(mut sk: impl AsMut<[u8]>) -> Result<Self, DecodingError> {
let sk_bytes = sk.as_mut(); let sk_bytes = sk.as_mut();
let secret = secp256k1::SecretKey::parse_slice(&*sk_bytes) let secret =
.map_err(|_| DecodingError::Secp256k1)?; secp256k1::SecretKey::parse_slice(&*sk_bytes).map_err(|_| DecodingError::Secp256k1)?;
sk_bytes.zeroize(); sk_bytes.zeroize();
Ok(SecretKey(secret)) Ok(SecretKey(secret))
} }
@ -119,13 +121,12 @@ impl SecretKey {
pub fn from_der(mut der: impl AsMut<[u8]>) -> Result<SecretKey, DecodingError> { pub fn from_der(mut der: impl AsMut<[u8]>) -> Result<SecretKey, DecodingError> {
// TODO: Stricter parsing. // TODO: Stricter parsing.
let der_obj = der.as_mut(); let der_obj = der.as_mut();
let obj: Vec<DerObject> = FromDerObject::deserialize((&*der_obj).iter()) let obj: Vec<DerObject> =
.map_err(|_| DecodingError::Secp256k1)?; FromDerObject::deserialize((&*der_obj).iter()).map_err(|_| DecodingError::Secp256k1)?;
der_obj.zeroize(); der_obj.zeroize();
let sk_obj = obj.into_iter().nth(1) let sk_obj = obj.into_iter().nth(1).ok_or(DecodingError::Secp256k1)?;
.ok_or(DecodingError::Secp256k1)?; let mut sk_bytes: Vec<u8> =
let mut sk_bytes: Vec<u8> = FromDerObject::from_der_object(sk_obj) FromDerObject::from_der_object(sk_obj).map_err(|_| DecodingError::Secp256k1)?;
.map_err(|_| DecodingError::Secp256k1)?;
let sk = SecretKey::from_bytes(&mut sk_bytes)?; let sk = SecretKey::from_bytes(&mut sk_bytes)?;
sk_bytes.zeroize(); sk_bytes.zeroize();
Ok(sk) Ok(sk)
@ -147,9 +148,12 @@ impl SecretKey {
/// Sign a raw message of length 256 bits with this secret key, produces a DER-encoded /// Sign a raw message of length 256 bits with this secret key, produces a DER-encoded
/// ECDSA signature. /// ECDSA signature.
pub fn sign_hashed(&self, msg: &[u8]) -> Result<Vec<u8>, SigningError> { pub fn sign_hashed(&self, msg: &[u8]) -> Result<Vec<u8>, SigningError> {
let m = Message::parse_slice(msg) let m = Message::parse_slice(msg).map_err(SigningError::Secp256k1)?;
.map_err(SigningError::Secp256k1)?; Ok(secp256k1::sign(&m, &self.0)
Ok(secp256k1::sign(&m, &self.0).0.serialize_der().as_ref().into()) .0
.serialize_der()
.as_ref()
.into())
} }
} }
@ -159,15 +163,24 @@ pub struct PublicKey(secp256k1::PublicKey);
impl PublicKey { impl PublicKey {
/// Verify the Secp256k1 signature on a message using the public key. /// Verify the Secp256k1 signature on a message using the public key.
pub fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), SigningError> { pub fn verify(&self, msg: &[u8], sig: &[u8]) -> Result<(), VerificationError> {
self.verify_hashed(Sha256::digest(msg).as_ref(), sig) self.verify_hashed(Sha256::digest(msg).as_ref(), sig)
} }
/// Verify the Secp256k1 DER-encoded signature on a raw 256-bit message using the public key. /// Verify the Secp256k1 DER-encoded signature on a raw 256-bit message using the public key.
pub fn verify_hashed(&self, msg: &[u8], sig: &[u8]) -> Result<(), SigningError> { pub fn verify_hashed(&self, msg: &[u8], sig: &[u8]) -> Result<(), VerificationError> {
Message::parse_slice(msg) Message::parse_slice(msg)
.and_then(|m| secp256k1::Signature::parse_der(sig).map(|s| secp256k1::verify(&m, &s, &self.0))) .and_then(|m| {
.map_err(SigningError::Secp256k1).map(|_| ()) secp256k1::Signature::parse_der(sig).map(|s| secp256k1::verify(&m, &s, &self.0))
})
.map_err(|e| {
VerificationError::Secp256k1(
e,
bs58::encode(sig).into_string(),
bs58::encode(self.0.serialize_compressed()).into_string(),
)
})
.map(|_| ())
} }
/// Encode the public key in compressed form, i.e. with one coordinate /// Encode the public key in compressed form, i.e. with one coordinate
@ -190,11 +203,10 @@ impl PublicKey {
} }
} }
impl Serialize for PublicKey { impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
SerdeBytes::new(self.encode().to_vec().as_slice()).serialize(serializer) SerdeBytes::new(self.encode().to_vec().as_slice()).serialize(serializer)
} }
@ -202,15 +214,14 @@ impl Serialize for PublicKey {
impl<'d> Deserialize<'d> for PublicKey { impl<'d> Deserialize<'d> for PublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'d>, D: Deserializer<'d>,
{ {
let bytes = <SerdeByteBuf>::deserialize(deserializer)?; let bytes = <SerdeByteBuf>::deserialize(deserializer)?;
PublicKey::decode(bytes.as_slice()).map_err(SerdeError::custom) PublicKey::decode(bytes.as_slice()).map_err(SerdeError::custom)
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Signature(pub Vec<u8>); pub struct Signature(pub Vec<u8>);

View File

@ -103,7 +103,7 @@ impl Signature {
} }
} }
pub fn from_bytes_with_public_key(key_format: KeyFormat, bytes: Vec<u8>) -> Self { pub fn from_bytes(key_format: KeyFormat, bytes: Vec<u8>) -> Self {
match key_format { match key_format {
KeyFormat::Ed25519 => Signature::Ed25519(ed25519::Signature(bytes)), KeyFormat::Ed25519 => Signature::Ed25519(ed25519::Signature(bytes)),
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]

View File

@ -0,0 +1 @@
5FwE32bDcphFzuMca7Y2qW1gdR64fTBYoRNvD4MLE1hecDGhCMQGKn8aseMr5wRo4Xo2CRFdrEAawUNLYkgQD78K

View File

@ -0,0 +1,77 @@
# management secret key is NAB5rGwT4qOEB+6nLQawkTfCOV2eiFSjgQK8bfEdZXY=
services:
fluence-0: # /ip4/127.0.0.1/tcp/9990/ws/p2p/12D3KooWHBG9oaVx4i3vi6c1rSBUm7MLBmyGmmbHoZ23pmjDCnvK
command: -f ed25519 -k 29Apzfedhw2Jxh94Jj4rNSmavQ1TkNe8ALYRA7bMegobwp423aLrURxLk32WtXgXHDqoSz7GAT9fQfoMhVd1e5Ww -m 12D3KooWFRgVmb1uWcmCbmJqLr8tBQghL6ysSpK2VyE2VZbaQ6wy -t 7770 -w 9990 # --bootstraps /dns4/fluence-1/tcp/7771 /dns4/fluence-2/tcp/7772
container_name: fluence-0
environment:
RUST_BACKTRACE: full
RUST_LOG: info,network=trace,aquamarine=info,aquamarine::actor=info,tokio_threadpool=info,tokio_reactor=info,mio=info,tokio_io=info,soketto=info,yamux=info,multistream_select=info,libp2p_secio=info,libp2p_websocket::framed=info,libp2p_ping=info,libp2p_core::upgrade::apply=info,libp2p_kad::kbucket=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,particle_server::behaviour::identify=info,libp2p_mplex=info,libp2p_identify=info,walrus=info,particle_protocol::libp2p_protocol::upgrade=info,kademlia::behaviour=info
WASM_LOG: info
image: fluencelabs/node:latest
ports:
- 7770:7770 # tcp
- 9990:9990 # ws
- 5000:5001 # ipfs rpc
- 4000:4001 # ipfs swarm
- 18080:18080 # /metrics
restart: always
volumes:
- fluence-0:/.fluence
- data-0:/config
- ./builtins_secret_key.ed25519:/.fluence/v1/builtins_secret_key.ed25519
networks:
- fluence
# fluence-1: # /ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWRABanQHUn28dxavN9ZS1zZghqoZVAYtFpoN7FdtoGTFv
# command: -f ed25519 -k 5fNENMwkUT4dW3hPs9ZwqV4qA5pdTtUChTazAx9Awe2Vpz1yaJu3VCmcEZow6YgdFBGoZoFAZUZBbF3c2Ebd2iL -m 12D3KooWFRgVmb1uWcmCbmJqLr8tBQghL6ysSpK2VyE2VZbaQ6wy -t 7771 -w 9991 --bootstraps /dns4/fluence-0/tcp/7770 /dns4/fluence-2/tcp/7772 #/dns4/kras-00.fluence.dev/tcp/7770
# container_name: fluence-1
# environment:
# RUST_BACKTRACE: full
# RUST_LOG: info,network=trace,aquamarine=info,aquamarine::actor=info,tokio_threadpool=info,tokio_reactor=info,mio=info,tokio_io=info,soketto=info,yamux=info,multistream_select=info,libp2p_secio=info,libp2p_websocket::framed=info,libp2p_ping=info,libp2p_core::upgrade::apply=info,libp2p_kad::kbucket=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,particle_server::behaviour::identify=info,libp2p_mplex=info,libp2p_identify=info,walrus=info,particle_protocol::libp2p_protocol::upgrade=info,kademlia::behaviour=info
# WASM_LOG: info
# image: fluencelabs/node:latest
# ports:
# - 7771:7771 # tcp
# - 9991:9991 # ws
# - 5001:5001 # ipfs rpc
# - 4001:4001 # ipfs swarm
# - 18081:18080 # /metrics
# restart: always
# volumes:
# - fluence-1:/.fluence
# - data-1:/config
# networks:
# - fluence
#
# fluence-2: # /ip4/127.0.0.1/tcp/9992/ws/p2p/12D3KooWFpQ7LHxcC9FEBUh3k4nSCC12jBhijJv3gJbi7wsNYzJ5
# command: -f ed25519 -k 5DTs9LQS8Ay2dM8xBcikDRwYLMcanhsC6tynSSgpLyBZEv5Ey34LVw1fYcCuUj9A9EfvQJB2bsaGhSRoHQ7D6UE5 -m 12D3KooWFRgVmb1uWcmCbmJqLr8tBQghL6ysSpK2VyE2VZbaQ6wy -t 7772 -w 9992 --bootstraps /dns4/fluence-0/tcp/7770 /dns4/fluence-1/tcp/7771 #/dns4/kras-00.fluence.dev/tcp/7770
# container_name: fluence-2
# environment:
# RUST_BACKTRACE: full
# RUST_LOG: info,network=trace,aquamarine=info,aquamarine::actor=info,tokio_threadpool=info,tokio_reactor=info,mio=info,tokio_io=info,soketto=info,yamux=info,multistream_select=info,libp2p_secio=info,libp2p_websocket::framed=info,libp2p_ping=info,libp2p_core::upgrade::apply=info,libp2p_kad::kbucket=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,particle_server::behaviour::identify=info,libp2p_mplex=info,libp2p_identify=info,walrus=info,particle_protocol::libp2p_protocol::upgrade=info,kademlia::behaviour=info
# WASM_LOG: info
# image: fluencelabs/node:latest
# ports:
# - 7772:7772 # tcp
# - 9992:9992 # ws
# - 5002:5001 # ipfs rpc
# - 4002:4001 # ipfs swarm
# - 18082:18080 # /metrics
# restart: always
# volumes:
# - fluence-2:/.fluence
# - data-2:/config
# networks:
# - fluence
version: "3.5"
volumes:
fluence-0:
# fluence-1:
# fluence-2:
data-0:
# data-1:
# data-2:
networks:
fluence:

3
rust-toolchain.toml Normal file
View File

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly-2021-09-01"
targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu" ]

38
service/Cargo.toml Normal file
View File

@ -0,0 +1,38 @@
[package]
name = "trust-graph-wasm"
version = "0.2.1"
authors = ["Fluence Labs"]
edition = "2018"
description = "trust graph wasm"
license = "Apache-2.0"
[[bin]]
name = "trust-graph"
path = "src/main.rs"
[dependencies]
trust-graph = { version = "0.3.0", path = "../." }
fluence-keypair = { version = "0.5.0", path = "../keypair" }
marine-rs-sdk = { version = "0.6.14", features = ["logger"] }
marine-sqlite-connector = "0.5.2"
libp2p-core = { package = "fluence-fork-libp2p-core", version = "0.27.2", features = ["secp256k1"]}
log = "0.4.8"
anyhow = "1.0.31"
boolinator = "2.4.0"
once_cell = "1.4.1"
parking_lot = "0.11.1"
serde_json = "1.0"
bs58 = "0.3.1"
rmp-serde = "0.15.0"
bincode = "1.3.1"
serde_bencode = "^0.2.3"
thiserror = "1.0.23"
[dev-dependencies]
marine-rs-sdk-test = "0.4.0"
rusqlite = "0.26.1"
[build-dependencies]
marine-rs-sdk-test = "0.4.0"

16
service/Config.toml Normal file
View File

@ -0,0 +1,16 @@
modules_dir = "artifacts/"
[[module]]
name = "sqlite3"
logger_enabled = true
[module.wasi]
preopened_files = ["./data"]
mapped_dirs = { "data" = "data" }
[[module]]
name = "trust-graph"
logger_enabled = true
[module.wasi]
preopened_files = ["./data"]
mapped_dirs = { "data" = "data" }

33
service/build.rs Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use marine_rs_sdk_test::generate_marine_test_env;
use marine_rs_sdk_test::ServiceDescription;
fn main() {
let services = vec![(
"trust_graph".to_string(),
ServiceDescription {
config_path: "Config.toml".to_string(),
modules_dir: Some("artifacts".to_string()),
},
)];
let target = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if target != "wasm32" {
generate_marine_test_env(services, "marine_test_env.rs", file!());
}
println!("cargo:rerun-if-changed=src/main.rs");
}

19
service/build.sh Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -o errexit -o nounset -o pipefail
# set current working directory to script directory to run script from everywhere
cd "$(dirname "$0")"
# build trust-graph.wasm
marine build --release
# copy .wasm to artifacts
rm -f artifacts/*
mkdir -p artifacts
cp ../target/wasm32-wasi/release/trust-graph.wasm artifacts/
# download SQLite 3 to use in tests
curl -L https://github.com/fluencelabs/sqlite/releases/download/v0.15.0_w/sqlite3.wasm -o artifacts/sqlite3.wasm
# generate Aqua bindings
marine aqua artifacts/trust-graph.wasm -s TrustGraph -i trust-graph > ../aqua/trust-graph.aqua

View File

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly-2021-09-01"
targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu" ]

169
service/src/dto.rs Normal file
View File

@ -0,0 +1,169 @@
use crate::dto::DtoConversionError::PeerIdDecodeError;
use fluence_keypair::error::DecodingError;
use fluence_keypair::{KeyFormat, PublicKey, Signature};
use libp2p_core::PeerId;
use marine_rs_sdk::marine;
use std::convert::TryFrom;
use std::str::FromStr;
use std::time::Duration;
use thiserror::Error as ThisError;
#[derive(ThisError, Debug)]
pub enum DtoConversionError {
#[error("Cannot convert base58 string to bytes: {0}")]
Base58Error(
#[from]
#[source]
bs58::decode::Error,
),
#[error("Cannot convert string to PublicKey: {0}")]
PublicKeyDecodeError(
#[from]
#[source]
DecodingError,
),
#[error("Cannot decode peer id from string: {0}")]
PeerIdDecodeError(String),
#[error("{0}")]
InvalidKeyFormat(
#[from]
#[source]
fluence_keypair::error::Error,
),
}
#[marine]
pub struct Certificate {
pub chain: Vec<Trust>,
}
impl From<trust_graph::Certificate> for Certificate {
fn from(c: trust_graph::Certificate) -> Self {
let chain: Vec<Trust> = c.chain.into_iter().map(|t| t.into()).collect();
return Certificate { chain };
}
}
impl TryFrom<Certificate> for trust_graph::Certificate {
type Error = DtoConversionError;
fn try_from(c: Certificate) -> Result<Self, Self::Error> {
let chain: Result<Vec<trust_graph::Trust>, DtoConversionError> = c
.chain
.into_iter()
.map(|t| trust_graph::Trust::try_from(t))
.collect();
let chain = chain?;
return Ok(trust_graph::Certificate { chain });
}
}
#[marine]
#[derive(Default)]
pub struct Trust {
/// For whom this certificate is issued, base58 peer_id
pub issued_for: String,
/// Expiration date of a trust, in secs
pub expires_at: u64,
/// Signature of a previous trust in a chain.
/// Signature is self-signed if it is a root trust, base58
pub signature: String,
pub sig_type: String,
/// Creation time of a trust, in secs
pub issued_at: u64,
}
impl TryFrom<Trust> for trust_graph::Trust {
type Error = DtoConversionError;
fn try_from(t: Trust) -> Result<Self, Self::Error> {
let issued_for = PublicKey::try_from(
PeerId::from_str(&t.issued_for).map_err(|e| PeerIdDecodeError(format!("{:?}", e)))?,
)
.map_err(|e| DtoConversionError::PeerIdDecodeError(e.to_string()))?;
let signature = bs58::decode(&t.signature).into_vec()?;
let signature = Signature::from_bytes(KeyFormat::from_str(&t.sig_type)?, signature);
let expires_at = Duration::from_secs(t.expires_at);
let issued_at = Duration::from_secs(t.issued_at);
return Ok(trust_graph::Trust {
issued_for,
expires_at,
signature,
issued_at,
});
}
}
impl From<trust_graph::Trust> for Trust {
fn from(t: trust_graph::Trust) -> Self {
let issued_for = t.issued_for.to_peer_id().to_base58();
let raw_signature = t.signature.get_raw_signature();
let signature = bs58::encode(raw_signature.bytes).into_string();
let expires_at = t.expires_at.as_secs();
let issued_at = t.issued_at.as_secs();
return Trust {
issued_for,
expires_at,
signature,
sig_type: raw_signature.sig_type.into(),
issued_at,
};
}
}
#[marine]
#[derive(Default)]
pub struct Revoke {
/// who is revoked
pub revoked_peer_id: String,
/// date when revocation was created
pub revoked_at: u64,
/// Signature of a previous trust in a chain.
/// Signature is self-signed if it is a root trust, base58
pub signature: String,
pub sig_type: String,
/// the issuer of this revocation, base58 peer id
pub revoked_by: String,
}
impl TryFrom<Revoke> for trust_graph::Revoke {
type Error = DtoConversionError;
fn try_from(r: Revoke) -> Result<Self, Self::Error> {
let revoked_pk = PublicKey::try_from(
PeerId::from_str(&r.revoked_peer_id)
.map_err(|e| PeerIdDecodeError(format!("{:?}", e)))?,
)
.map_err(|e| DtoConversionError::PeerIdDecodeError(e.to_string()))?;
let revoked_by_pk = PublicKey::try_from(
PeerId::from_str(&r.revoked_by).map_err(|e| PeerIdDecodeError(format!("{:?}", e)))?,
)
.map_err(|e| DtoConversionError::PeerIdDecodeError(e.to_string()))?;
let signature = bs58::decode(&r.signature).into_vec()?;
let signature = Signature::from_bytes(KeyFormat::from_str(&r.sig_type)?, signature);
let revoked_at = Duration::from_secs(r.revoked_at);
return Ok(trust_graph::Revoke {
pk: revoked_pk,
revoked_at,
revoked_by: revoked_by_pk,
signature,
});
}
}
impl From<trust_graph::Revoke> for Revoke {
fn from(r: trust_graph::Revoke) -> Self {
let revoked_by = r.revoked_by.to_peer_id().to_base58();
let revoked_peer_id = r.pk.to_peer_id().to_base58();
let raw_signature = r.signature.get_raw_signature();
let signature = bs58::encode(raw_signature.bytes).into_string();
let revoked_at = r.revoked_at.as_secs();
return Revoke {
revoked_peer_id,
revoked_at,
signature,
sig_type: raw_signature.sig_type.into(),
revoked_by,
};
}
}

23
service/src/main.rs Normal file
View File

@ -0,0 +1,23 @@
#![allow(dead_code)]
use crate::storage_impl::create_tables;
use marine_rs_sdk::module_manifest;
use marine_rs_sdk::WasmLoggerBuilder;
module_manifest!();
mod dto;
mod results;
mod service_api;
mod service_impl;
mod storage_impl;
mod tests;
pub fn main() {
WasmLoggerBuilder::new()
.with_log_level(log::LevelFilter::Trace)
.build()
.unwrap();
create_tables();
}

258
service/src/results.rs Normal file
View File

@ -0,0 +1,258 @@
use crate::dto::{Certificate, Revoke, Trust};
use crate::service_impl::ServiceError;
use marine_rs_sdk::marine;
#[marine]
pub struct InsertResult {
pub success: bool,
pub error: String,
}
impl From<Result<(), ServiceError>> for InsertResult {
fn from(result: Result<(), ServiceError>) -> Self {
match result {
Ok(()) => InsertResult {
success: true,
error: "".to_string(),
},
Err(e) => InsertResult {
success: false,
error: format!("{}", e),
},
}
}
}
#[marine]
pub struct WeightResult {
pub success: bool,
pub weight: u32,
pub peer_id: String,
pub error: String,
}
impl From<Result<(u32, String), ServiceError>> for WeightResult {
fn from(result: Result<(u32, String), ServiceError>) -> Self {
match result {
Ok((weight, peer_id)) => WeightResult {
success: true,
weight,
peer_id,
error: "".to_string(),
},
Err(e) => WeightResult {
success: false,
weight: 0u32,
peer_id: "".to_string(),
error: format!("{}", e),
},
}
}
}
#[marine]
pub struct AllCertsResult {
pub success: bool,
pub certificates: Vec<Certificate>,
pub error: String,
}
impl From<Result<Vec<Certificate>, ServiceError>> for AllCertsResult {
fn from(result: Result<Vec<Certificate>, ServiceError>) -> Self {
match result {
Ok(certs) => AllCertsResult {
success: true,
certificates: certs,
error: "".to_string(),
},
Err(e) => AllCertsResult {
success: false,
certificates: vec![],
error: format!("{}", e),
},
}
}
}
#[marine]
pub struct AddRootResult {
pub success: bool,
pub error: String,
}
impl From<Result<(), ServiceError>> for AddRootResult {
fn from(result: Result<(), ServiceError>) -> Self {
match result {
Ok(()) => AddRootResult {
success: true,
error: "".to_string(),
},
Err(e) => AddRootResult {
success: false,
error: format!("{}", e),
},
}
}
}
#[marine]
pub struct GetTrustBytesResult {
pub success: bool,
pub error: String,
pub result: Vec<u8>,
}
impl From<Result<Vec<u8>, ServiceError>> for GetTrustBytesResult {
fn from(result: Result<Vec<u8>, ServiceError>) -> Self {
match result {
Ok(res) => GetTrustBytesResult {
success: true,
error: "".to_string(),
result: res,
},
Err(e) => GetTrustBytesResult {
success: false,
error: format!("{}", e),
result: vec![],
},
}
}
}
#[marine]
pub struct IssueTrustResult {
pub success: bool,
pub error: String,
pub trust: Trust,
}
impl From<Result<Trust, ServiceError>> for IssueTrustResult {
fn from(result: Result<Trust, ServiceError>) -> Self {
match result {
Ok(trust) => IssueTrustResult {
success: true,
error: "".to_string(),
trust,
},
Err(e) => IssueTrustResult {
success: false,
error: format!("{}", e),
trust: Trust::default(),
},
}
}
}
#[marine]
pub struct VerifyTrustResult {
pub success: bool,
pub error: String,
}
impl From<Result<(), ServiceError>> for VerifyTrustResult {
fn from(result: Result<(), ServiceError>) -> Self {
match result {
Ok(()) => VerifyTrustResult {
success: true,
error: "".to_string(),
},
Err(e) => VerifyTrustResult {
success: false,
error: format!("{}", e),
},
}
}
}
#[marine]
pub struct AddTrustResult {
pub success: bool,
pub error: String,
pub weight: u32,
}
impl From<Result<u32, ServiceError>> for AddTrustResult {
fn from(result: Result<u32, ServiceError>) -> Self {
match result {
Ok(weight) => AddTrustResult {
success: true,
error: "".to_string(),
weight,
},
Err(e) => AddTrustResult {
success: false,
error: format!("{}", e),
weight: u32::default(),
},
}
}
}
#[marine]
pub struct GetRevokeBytesResult {
pub success: bool,
pub error: String,
pub result: Vec<u8>,
}
impl From<Result<Vec<u8>, ServiceError>> for GetRevokeBytesResult {
fn from(result: Result<Vec<u8>, ServiceError>) -> Self {
match result {
Ok(res) => GetRevokeBytesResult {
success: true,
error: "".to_string(),
result: res,
},
Err(e) => GetRevokeBytesResult {
success: false,
error: format!("{}", e),
result: vec![],
},
}
}
}
#[marine]
pub struct IssueRevocationResult {
pub success: bool,
pub error: String,
pub revoke: Revoke,
}
impl From<Result<Revoke, ServiceError>> for IssueRevocationResult {
fn from(result: Result<Revoke, ServiceError>) -> Self {
match result {
Ok(revoke) => IssueRevocationResult {
success: true,
error: "".to_string(),
revoke,
},
Err(e) => IssueRevocationResult {
success: false,
error: format!("{}", e),
revoke: Revoke::default(),
},
}
}
}
#[marine]
pub struct RevokeResult {
pub success: bool,
pub error: String,
}
impl From<Result<(), ServiceError>> for RevokeResult {
fn from(result: Result<(), ServiceError>) -> Self {
match result {
Ok(()) => RevokeResult {
success: true,
error: "".to_string(),
},
Err(e) => RevokeResult {
success: false,
error: format!("{}", e),
},
}
}
}

138
service/src/service_api.rs Normal file
View File

@ -0,0 +1,138 @@
use crate::dto::{Certificate, Revoke, Trust};
use crate::results::{
AddRootResult, AddTrustResult, AllCertsResult, GetRevokeBytesResult, GetTrustBytesResult,
InsertResult, IssueRevocationResult, IssueTrustResult, RevokeResult, VerifyTrustResult,
WeightResult,
};
use crate::service_impl::{
add_root_impl, add_trust_impl, get_all_certs_impl, get_host_certs_impl, get_revoke_bytes_impl,
get_trust_bytes_impl, get_weight_impl, insert_cert_impl, insert_cert_impl_raw,
issue_revocation_impl, issue_trust_impl, revoke_impl, verify_trust_impl, ServiceError,
};
use marine_rs_sdk::{get_call_parameters, marine, CallParameters};
use trust_graph::MAX_WEIGHT_FACTOR;
#[marine]
fn get_weight_factor(max_chain_len: u32) -> u32 {
MAX_WEIGHT_FACTOR.checked_sub(max_chain_len).unwrap_or(0u32)
}
#[marine]
/// could add only a owner of a trust graph service
fn add_root(peer_id: String, weight_factor: u32) -> AddRootResult {
let call_parameters: CallParameters = marine_rs_sdk::get_call_parameters();
let init_peer_id = call_parameters.init_peer_id;
if call_parameters.service_creator_peer_id == init_peer_id {
add_root_impl(peer_id, weight_factor).into()
} else {
return AddRootResult {
success: false,
error: ServiceError::NotOwner.to_string(),
};
}
}
#[marine]
/// add a certificate in string representation to trust graph if it is valid
/// see `trust_graph::Certificate` class for string encoding/decoding
fn insert_cert_raw(certificate: String, timestamp_sec: u64) -> InsertResult {
insert_cert_impl_raw(certificate, timestamp_sec).into()
}
#[marine]
/// add a certificate in JSON representation to trust graph if it is valid
/// see `dto::Certificate` class for structure
fn insert_cert(certificate: Certificate, timestamp_sec: u64) -> InsertResult {
insert_cert_impl(certificate, timestamp_sec).into()
}
#[marine]
fn get_all_certs(issued_for: String, timestamp_sec: u64) -> AllCertsResult {
get_all_certs_impl(issued_for, timestamp_sec).into()
}
#[marine]
fn get_host_certs(timestamp_sec: u64) -> AllCertsResult {
get_host_certs_impl(timestamp_sec).into()
}
#[marine]
fn get_host_certs_from(issuer: String, timestamp_sec: u64) -> AllCertsResult {
let host_id = get_call_parameters().host_id;
get_all_certs_impl(host_id, timestamp_sec)
.map(|certs| {
certs
.into_iter()
.filter(|cert| cert.chain.iter().any(|t| t.issued_for == issuer))
.collect()
})
.into()
}
#[marine]
fn get_weight(peer_id: String, timestamp_sec: u64) -> WeightResult {
get_weight_impl(peer_id.clone(), timestamp_sec)
.map(|w| (w, peer_id))
.into()
}
#[marine]
fn get_trust_bytes(
issued_for_peer_id: String,
expires_at_sec: u64,
issued_at_sec: u64,
) -> GetTrustBytesResult {
get_trust_bytes_impl(issued_for_peer_id, expires_at_sec, issued_at_sec).into()
}
#[marine]
fn issue_trust(
issued_for_peer_id: String,
expires_at_sec: u64,
issued_at_sec: u64,
trust_bytes: Vec<u8>,
) -> IssueTrustResult {
issue_trust_impl(
issued_for_peer_id,
expires_at_sec,
issued_at_sec,
trust_bytes,
)
.into()
}
#[marine]
fn verify_trust(trust: Trust, issuer_peer_id: String, timestamp_sec: u64) -> VerifyTrustResult {
verify_trust_impl(trust, issuer_peer_id, timestamp_sec).into()
}
#[marine]
fn add_trust(trust: Trust, issuer_peer_id: String, timestamp_sec: u64) -> AddTrustResult {
add_trust_impl(trust, issuer_peer_id, timestamp_sec).into()
}
#[marine]
fn get_revoke_bytes(revoked_peer_id: String, revoked_at: u64) -> GetRevokeBytesResult {
get_revoke_bytes_impl(revoked_peer_id, revoked_at).into()
}
#[marine]
fn issue_revocation(
revoked_peer_id: String,
revoked_by_peer_id: String,
revoked_at_sec: u64,
signature_bytes: Vec<u8>,
) -> IssueRevocationResult {
issue_revocation_impl(
revoked_peer_id,
revoked_by_peer_id,
revoked_at_sec,
signature_bytes,
)
.into()
}
#[marine]
fn revoke(revoke: Revoke, timestamp_sec: u64) -> RevokeResult {
revoke_impl(revoke, timestamp_sec).into()
}

255
service/src/service_impl.rs Normal file
View File

@ -0,0 +1,255 @@
use crate::dto::{Certificate, DtoConversionError, Revoke, Trust};
use crate::service_impl::ServiceError::InvalidTimestampTetraplet;
use crate::storage_impl::get_data;
use fluence_keypair::error::DecodingError;
use fluence_keypair::{PublicKey, Signature};
use libp2p_core::PeerId;
use marine_rs_sdk::CallParameters;
use std::convert::{Into, TryFrom, TryInto};
use std::str::FromStr;
use std::time::Duration;
use thiserror::Error as ThisError;
use trust_graph::{CertificateError, TrustError, TrustGraphError};
pub static TRUSTED_TIMESTAMP_SERVICE_ID: &str = "peer";
pub static TRUSTED_TIMESTAMP_FUNCTION_NAME: &str = "timestamp_sec";
/// Check timestamps are generated on the current host with builtin ("peer" "timestamp_sec")
pub(crate) fn check_timestamp_tetraplets(
call_parameters: &CallParameters,
arg_number: usize,
) -> Result<(), ServiceError> {
let tetraplets = call_parameters
.tetraplets
.get(arg_number)
.ok_or(InvalidTimestampTetraplet)?;
let tetraplet = tetraplets.get(0).ok_or(InvalidTimestampTetraplet)?;
(tetraplet.service_id == TRUSTED_TIMESTAMP_SERVICE_ID
&& tetraplet.function_name == TRUSTED_TIMESTAMP_FUNCTION_NAME
&& tetraplet.peer_pk == call_parameters.host_id)
.then(|| ())
.ok_or(InvalidTimestampTetraplet)
}
#[derive(ThisError, Debug)]
pub enum ServiceError {
#[error("peer id parse error: {0}")]
PeerIdParseError(String),
#[error("public key extraction from peer id failed: {0}")]
PublicKeyExtractionError(String),
#[error("{0}")]
PublicKeyDecodeError(
#[from]
#[source]
DecodingError,
),
#[error("{0}")]
TGError(
#[from]
#[source]
TrustGraphError,
),
#[error("{0}")]
CertError(
#[from]
#[source]
CertificateError,
),
#[error("{0}")]
DtoError(
#[from]
#[source]
DtoConversionError,
),
#[error("{0}")]
TrustError(
#[from]
#[source]
TrustError,
),
#[error("you should use host peer.timestamp_sec to pass timestamp")]
InvalidTimestampTetraplet,
#[error("{0} can't be issued later than the current timestamp")]
InvalidTimestamp(String),
#[error("Root could add only by trust graph service owner")]
NotOwner,
}
fn parse_peer_id(peer_id: String) -> Result<PeerId, ServiceError> {
libp2p_core::PeerId::from_str(&peer_id)
.map_err(|e| ServiceError::PeerIdParseError(format!("{:?}", e)))
}
fn extract_public_key(peer_id: String) -> Result<PublicKey, ServiceError> {
PublicKey::try_from(
parse_peer_id(peer_id)
.map_err(|e| ServiceError::PublicKeyExtractionError(e.to_string()))?,
)
.map_err(ServiceError::PublicKeyDecodeError)
}
pub fn get_weight_impl(peer_id: String, timestamp_sec: u64) -> Result<u32, ServiceError> {
check_timestamp_tetraplets(&marine_rs_sdk::get_call_parameters(), 1)?;
let mut tg = get_data().lock();
let public_key = extract_public_key(peer_id)?;
let weight = tg.weight(public_key, Duration::from_secs(timestamp_sec))?;
Ok(weight)
}
fn add_cert(certificate: trust_graph::Certificate, timestamp_sec: u64) -> Result<(), ServiceError> {
let timestamp_sec = Duration::from_secs(timestamp_sec);
let mut tg = get_data().lock();
tg.add(certificate, timestamp_sec)?;
Ok(())
}
pub fn insert_cert_impl_raw(certificate: String, timestamp_sec: u64) -> Result<(), ServiceError> {
let certificate = trust_graph::Certificate::from_str(&certificate)?;
add_cert(certificate, timestamp_sec)?;
Ok(())
}
pub fn get_all_certs_impl(
issued_for: String,
timestamp_sec: u64,
) -> Result<Vec<Certificate>, ServiceError> {
check_timestamp_tetraplets(&marine_rs_sdk::get_call_parameters(), 1)?;
get_certs_helper(issued_for, timestamp_sec)
}
pub fn get_host_certs_impl(timestamp_sec: u64) -> Result<Vec<Certificate>, ServiceError> {
let cp = marine_rs_sdk::get_call_parameters();
check_timestamp_tetraplets(&cp, 0)?;
get_certs_helper(cp.host_id, timestamp_sec)
}
pub fn get_certs_helper(
issued_for: String,
timestamp_sec: u64,
) -> Result<Vec<Certificate>, ServiceError> {
let mut tg = get_data().lock();
let public_key = extract_public_key(issued_for)?;
let certs = tg.get_all_certs(public_key, Duration::from_secs(timestamp_sec))?;
Ok(certs.into_iter().map(|c| c.into()).collect())
}
pub fn insert_cert_impl(certificate: Certificate, timestamp_sec: u64) -> Result<(), ServiceError> {
let certificate: trust_graph::Certificate = certificate.try_into()?;
add_cert(certificate, timestamp_sec)?;
Ok(())
}
pub fn add_root_impl(peer_id: String, weight: u32) -> Result<(), ServiceError> {
let mut tg = get_data().lock();
let public_key = extract_public_key(peer_id)?;
tg.add_root_weight_factor(public_key, weight)?;
Ok(())
}
pub fn get_trust_bytes_impl(
peer_id: String,
expires_at_sec: u64,
issued_at_sec: u64,
) -> Result<Vec<u8>, ServiceError> {
let public_key = extract_public_key(peer_id)?;
Ok(trust_graph::Trust::signature_bytes(
&public_key,
Duration::from_secs(expires_at_sec),
Duration::from_secs(issued_at_sec),
))
}
pub fn issue_trust_impl(
peer_id: String,
expires_at_sec: u64,
issued_at_sec: u64,
trust_bytes: Vec<u8>,
) -> Result<Trust, ServiceError> {
let public_key = extract_public_key(peer_id)?;
let expires_at_sec = Duration::from_secs(expires_at_sec);
let issued_at_sec = Duration::from_secs(issued_at_sec);
let signature = Signature::from_bytes(public_key.get_key_format(), trust_bytes);
Ok(Trust::from(trust_graph::Trust::new(
public_key,
expires_at_sec,
issued_at_sec,
signature,
)))
}
pub fn verify_trust_impl(
trust: Trust,
issuer_peer_id: String,
timestamp_sec: u64,
) -> Result<(), ServiceError> {
check_timestamp_tetraplets(&marine_rs_sdk::get_call_parameters(), 2)?;
let public_key = extract_public_key(issuer_peer_id)?;
trust_graph::Trust::verify(
&trust.try_into()?,
&public_key,
Duration::from_secs(timestamp_sec),
)?;
Ok(())
}
pub fn add_trust_impl(
trust: Trust,
issuer_peer_id: String,
timestamp_sec: u64,
) -> Result<u32, ServiceError> {
let public_key = extract_public_key(issuer_peer_id)?;
check_timestamp_tetraplets(&marine_rs_sdk::get_call_parameters(), 2)?;
if trust.issued_at > timestamp_sec {
return Err(ServiceError::InvalidTimestamp("Trust".to_string()));
}
let mut tg = get_data().lock();
tg.add_trust(
&trust.try_into()?,
public_key,
Duration::from_secs(timestamp_sec),
)
.map_err(ServiceError::TGError)
}
pub fn get_revoke_bytes_impl(
revoked_peer_id: String,
revoked_at: u64,
) -> Result<Vec<u8>, ServiceError> {
let public_key = extract_public_key(revoked_peer_id)?;
Ok(trust_graph::Revoke::signature_bytes(
&public_key,
Duration::from_secs(revoked_at),
))
}
pub fn issue_revocation_impl(
revoked_peer_id: String,
revoked_by_peer_id: String,
revoked_at_sec: u64,
signature_bytes: Vec<u8>,
) -> Result<Revoke, ServiceError> {
let revoked_pk = extract_public_key(revoked_peer_id)?;
let revoked_by_pk = extract_public_key(revoked_by_peer_id)?;
let revoked_at = Duration::from_secs(revoked_at_sec);
let signature = Signature::from_bytes(revoked_by_pk.get_key_format(), signature_bytes);
Ok(trust_graph::Revoke::new(revoked_pk, revoked_by_pk, revoked_at, signature).into())
}
pub fn revoke_impl(revoke: Revoke, timestamp_sec: u64) -> Result<(), ServiceError> {
check_timestamp_tetraplets(&marine_rs_sdk::get_call_parameters(), 1)?;
if revoke.revoked_at > timestamp_sec {
return Err(ServiceError::InvalidTimestamp("Revoke".to_string()));
}
let mut tg = get_data().lock();
tg.revoke(revoke.try_into()?).map_err(ServiceError::TGError)
}

332
service/src/storage_impl.rs Normal file
View File

@ -0,0 +1,332 @@
// store list of trusts
// check if trust is already in list before adding
// if there is an older trust - don't add received trust
use crate::storage_impl::SQLiteStorageError::{
FieldConversionDB, PublicKeyConversion, PublicKeyFromStr, WeightFactorConversionDB,
};
use core::convert::TryFrom;
use fluence_keypair::error::DecodingError;
use fluence_keypair::Signature;
use marine_sqlite_connector::{Connection, Error as InternalSqliteError, Value};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rmp_serde::decode::Error as RmpDecodeError;
use rmp_serde::encode::Error as RmpEncodeError;
use std::convert::From;
use std::str::FromStr;
use std::time::Duration;
use thiserror::Error as ThisError;
use trust_graph::{
Auth, PublicKeyHashable as PK, PublicKeyHashable, Revoke, Storage, StorageError, Trust,
TrustGraph, TrustRelation, WeightFactor,
};
#[allow(dead_code)]
static INSTANCE: OnceCell<Mutex<TrustGraph<SQLiteStorage>>> = OnceCell::new();
static AUTH_TYPE: i64 = 0;
static REVOKE_TYPE: i64 = 1;
pub static DB_PATH: &str = "data/users12312233.sqlite";
pub fn create_tables() {
let connection = marine_sqlite_connector::open(DB_PATH).unwrap();
connection
.execute(
"CREATE TABLE IF NOT EXISTS trust_relations(
relation_type INTEGER,
issued_for TEXT,
issued_by TEXT,
issued_at INTEGER,
expires_at INTEGER,
signature TEXT,
PRIMARY KEY (issued_for, issued_by)
);",
)
.unwrap();
connection
.execute(
"CREATE TABLE IF NOT EXISTS roots(
public_key TEXT,
weight_factor INTEGER
);",
)
.unwrap();
}
#[allow(dead_code)]
pub fn get_data() -> &'static Mutex<TrustGraph<SQLiteStorage>> {
INSTANCE.get_or_init(|| {
let connection = marine_sqlite_connector::open(DB_PATH).unwrap();
Mutex::new(TrustGraph::new(SQLiteStorage::new(connection)))
})
}
pub struct SQLiteStorage {
connection: Connection,
}
#[allow(dead_code)]
impl SQLiteStorage {
pub fn new(connection: Connection) -> SQLiteStorage {
SQLiteStorage { connection }
}
fn update_relation(&mut self, relation: TrustRelation) -> Result<(), SQLiteStorageError> {
match self.get_relation(
relation.issued_for().as_ref(),
relation.issued_by().as_ref(),
)? {
Some(TrustRelation::Auth(auth)) => {
if auth.trust.issued_at < relation.issued_at() {
self.insert(relation)?;
}
}
Some(TrustRelation::Revoke(revoke)) => {
if revoke.revoked_at < relation.issued_at() {
self.insert(relation)?;
}
}
None => {
self.insert(relation)?;
}
}
Ok(())
}
}
#[derive(ThisError, Debug)]
pub enum SQLiteStorageError {
#[error("{0}")]
SQLiteError(
#[from]
#[source]
InternalSqliteError,
),
#[error("{0}")]
PublicKeyFromStr(String),
#[error("{0}")]
EncodeError(
#[from]
#[source]
RmpEncodeError,
),
#[error("{0}")]
DecodeError(
#[from]
#[source]
RmpDecodeError,
),
#[error("Cannot convert field from DB")]
FieldConversionDB,
#[error("Cannot convert weight factor as integer from DB")]
WeightFactorConversionDB,
#[error("Cannot convert public key as binary from DB")]
PublicKeyConversion,
#[error("Cannot revoke. There is no trust with such PublicKey")]
PublicKeyNotFound,
#[error("Cannot decode signature from DB: {0}")]
SignatureDecodeError(
#[from]
#[source]
DecodingError,
),
}
fn parse_relation(row: &[Value]) -> Result<TrustRelation, SQLiteStorageError> {
let relation_type = row[0].as_integer().ok_or(FieldConversionDB)?;
let issued_for = PK::from_str(row[1].as_string().ok_or(FieldConversionDB)?)?;
let issued_by = PK::from_str(row[2].as_string().ok_or(FieldConversionDB)?)?;
let issued_at = Duration::from_secs(row[3].as_integer().ok_or(FieldConversionDB)? as u64);
let expires_at = Duration::from_secs(row[4].as_integer().ok_or(FieldConversionDB)? as u64);
let signature = Signature::decode(row[5].as_binary().ok_or(FieldConversionDB)?.to_vec())?;
if relation_type == AUTH_TYPE {
Ok(TrustRelation::Auth(Auth {
trust: Trust {
issued_for: issued_for.into(),
expires_at,
signature,
issued_at,
},
issued_by: issued_by.into(),
}))
} else {
Ok(TrustRelation::Revoke(Revoke {
pk: issued_for.into(),
revoked_at: issued_at,
revoked_by: issued_by.into(),
signature,
}))
}
}
impl From<SQLiteStorageError> for String {
fn from(err: SQLiteStorageError) -> Self {
err.into()
}
}
impl StorageError for SQLiteStorageError {}
impl Storage for SQLiteStorage {
type Error = SQLiteStorageError;
fn get_relation(
&self,
issued_for: &PK,
issued_by: &PK,
) -> Result<Option<TrustRelation>, Self::Error> {
let mut cursor = self
.connection
.prepare(
"SELECT relation_type, issued_for, issued_by, issued_at, expires_at, signature \
FROM trust_relations WHERE issued_by = ? AND issued_for = ?",
)?
.cursor();
cursor.bind(&[
Value::String(format!("{}", issued_by)),
Value::String(format!("{}", issued_for)),
])?;
if let Some(row) = cursor.next()? {
parse_relation(row).map(Some)
} else {
Ok(None)
}
}
/// return all auths issued for pk
fn get_authorizations(&self, pk: &PublicKeyHashable) -> Result<Vec<Auth>, Self::Error> {
let mut cursor = self
.connection
.prepare(
"SELECT relation_type, issued_for, issued_by, issued_at, expires_at, signature \
FROM trust_relations WHERE issued_for = ? and relation_type = ?",
)?
.cursor();
cursor.bind(&[Value::String(format!("{}", pk)), Value::Integer(AUTH_TYPE)])?;
let mut auths: Vec<Auth> = vec![];
while let Some(row) = cursor.next()? {
if let TrustRelation::Auth(auth) = parse_relation(row)? {
auths.push(auth);
}
}
Ok(auths)
}
fn insert(&mut self, relation: TrustRelation) -> Result<(), Self::Error> {
let mut statement = self
.connection
.prepare("INSERT OR REPLACE INTO trust_relations VALUES (?, ?, ?, ?, ?, ?)")?;
let relation_type = match relation {
TrustRelation::Auth(_) => AUTH_TYPE,
TrustRelation::Revoke(_) => REVOKE_TYPE,
};
statement.bind(1, &Value::Integer(relation_type))?;
statement.bind(
2,
&Value::String(format!("{}", relation.issued_for().as_ref())),
)?;
statement.bind(
3,
&Value::String(format!("{}", relation.issued_by().as_ref())),
)?;
statement.bind(4, &Value::Integer(relation.issued_at().as_secs() as i64))?;
statement.bind(5, &Value::Integer(relation.expires_at().as_secs() as i64))?;
statement.bind(6, &Value::Binary(relation.signature().encode()))?;
statement.next()?;
Ok({})
}
fn get_root_weight_factor(&self, pk: &PK) -> Result<Option<WeightFactor>, Self::Error> {
let mut cursor = self
.connection
.prepare("SELECT public_key, weight_factor FROM roots WHERE public_key = ?")?
.cursor();
cursor.bind(&[Value::String(format!("{}", pk))])?;
if let Some(row) = cursor.next()? {
let w = u32::try_from(row[1].as_integer().ok_or(WeightFactorConversionDB)?)
.map_err(|_e| WeightFactorConversionDB)?;
Ok(Some(w))
} else {
Ok(None)
}
}
fn add_root_weight_factor(
&mut self,
pk: PK,
weight_factor: WeightFactor,
) -> Result<(), Self::Error> {
let mut cursor = self
.connection
.prepare("INSERT OR REPLACE INTO roots VALUES (?, ?)")?
.cursor();
cursor.bind(&[
Value::String(format!("{}", pk)),
Value::Integer(i64::from(weight_factor)),
])?;
cursor.next()?;
Ok({})
}
fn root_keys(&self) -> Result<Vec<PK>, Self::Error> {
let mut cursor = self
.connection
.prepare("SELECT public_key, weight_factor FROM roots")?
.cursor();
let mut roots = vec![];
while let Some(row) = cursor.next()? {
log::info!("row: {:?}", row);
let pk = row[0].as_string().ok_or(PublicKeyConversion)?;
let pk: PK = PK::from_str(pk).map_err(|e| PublicKeyFromStr(e.to_string()))?;
roots.push(pk)
}
Ok(roots)
}
fn revoke(&mut self, revoke: Revoke) -> Result<(), Self::Error> {
self.update_relation(TrustRelation::Revoke(revoke))
}
fn update_auth(&mut self, auth: Auth, _cur_time: Duration) -> Result<(), Self::Error> {
self.update_relation(TrustRelation::Auth(auth))
}
fn remove_expired(&mut self, cur_time: Duration) -> Result<(), Self::Error> {
let mut cursor = self
.connection
.prepare("DELETE FROM trust_relations WHERE expires_at <= ? AND relation_type = ?")?
.cursor();
cursor.bind(&[
Value::Integer(cur_time.as_secs() as i64),
Value::Integer(AUTH_TYPE),
])?;
cursor.next()?;
Ok(())
}
}

832
service/src/tests.rs Normal file
View File

@ -0,0 +1,832 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#[cfg(test)]
mod service_tests {
marine_rs_sdk_test::include_test_env!("/marine_test_env.rs");
use crate::service_impl::{
ServiceError, TRUSTED_TIMESTAMP_FUNCTION_NAME, TRUSTED_TIMESTAMP_SERVICE_ID,
};
use crate::storage_impl::DB_PATH;
use fluence_keypair::KeyPair;
use libp2p_core::PeerId;
use marine_rs_sdk::{CallParameters, SecurityTetraplet};
use marine_test_env::trust_graph::{Certificate, Revoke, ServiceInterface, Trust};
use rusqlite::Connection;
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
static HOST_ID: &str = "some_host_id";
struct Auth {
issuer: PeerId,
trust: Trust,
}
impl PartialEq for Trust {
fn eq(&self, other: &Self) -> bool {
self.expires_at == other.expires_at
&& self.issued_at == other.issued_at
&& self.issued_for == other.issued_for
&& self.signature == other.signature
&& self.sig_type == other.sig_type
}
}
impl Eq for Trust {}
fn current_time() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn clear_env() {
let connection = Connection::open(DB_PATH).unwrap();
connection
.execute("DELETE FROM trust_relations", [])
.unwrap();
connection.execute("DELETE FROM roots", []).unwrap();
}
fn get_correct_timestamp_cp(arg_number: usize) -> CallParameters {
get_correct_timestamp_cp_with_host_id(arg_number, HOST_ID.to_string())
}
fn get_correct_timestamp_cp_with_host_id(arg_number: usize, host_id: String) -> CallParameters {
let mut cp = CallParameters {
host_id: host_id.clone(),
..CallParameters::default()
};
for _ in 0..arg_number {
cp.tetraplets.push(vec![]);
}
cp.tetraplets.push(vec![SecurityTetraplet {
peer_pk: host_id,
service_id: TRUSTED_TIMESTAMP_SERVICE_ID.to_string(),
function_name: TRUSTED_TIMESTAMP_FUNCTION_NAME.to_string(),
json_path: "".to_string(),
}]);
cp
}
fn add_root_peer_id(trust_graph: &mut ServiceInterface, peer_id: PeerId, weight_factor: u32) {
let result = trust_graph.add_root(peer_id.to_base58(), weight_factor);
assert!(result.success, "{}", result.error);
}
fn add_root_with_trust(
trust_graph: &mut ServiceInterface,
issuer_kp: &KeyPair,
issued_at_sec: u64,
expires_at_sec: u64,
weight_factor: u32,
) -> Trust {
let result = trust_graph.add_root(issuer_kp.get_peer_id().to_base58(), weight_factor);
assert!(result.success, "{}", result.error);
add_trust(
trust_graph,
issuer_kp,
&issuer_kp.get_peer_id(),
issued_at_sec,
expires_at_sec,
)
}
fn issue_trust(
trust_graph: &mut ServiceInterface,
issuer_kp: &KeyPair,
issued_for: &PeerId,
issued_at_sec: u64,
expires_at_sec: u64,
) -> Trust {
let result =
trust_graph.get_trust_bytes(issued_for.to_base58(), expires_at_sec, issued_at_sec);
assert!(result.success, "{}", result.error);
let trust_bytes = issuer_kp.sign(&result.result).unwrap().to_vec().to_vec();
let issue_result = trust_graph.issue_trust(
issued_for.to_base58(),
expires_at_sec,
issued_at_sec,
trust_bytes,
);
assert!(issue_result.success, "{}", issue_result.error);
issue_result.trust
}
fn issue_root_trust(
trust_graph: &mut ServiceInterface,
issuer_kp: &KeyPair,
issued_at_sec: u64,
expires_at_sec: u64,
) -> Trust {
issue_trust(
trust_graph,
issuer_kp,
&issuer_kp.get_peer_id(),
issued_at_sec,
expires_at_sec,
)
}
fn add_trust(
trust_graph: &mut ServiceInterface,
issuer_kp: &KeyPair,
issued_for: &PeerId,
issued_at_sec: u64,
expires_at_sec: u64,
) -> Trust {
let trust = issue_trust(
trust_graph,
issuer_kp,
issued_for,
issued_at_sec,
expires_at_sec,
);
let add_trust_result = trust_graph.add_trust_cp(
trust.clone(),
issuer_kp.get_peer_id().to_base58(),
issued_at_sec,
get_correct_timestamp_cp(2),
);
assert!(add_trust_result.success, "{}", add_trust_result.error);
trust
}
fn add_trust_checked(
trust_graph: &mut ServiceInterface,
trust: Trust,
issuer_peer_id: PeerId,
cur_time: u64,
) {
let result = trust_graph.add_trust_cp(
trust,
issuer_peer_id.to_base58(),
cur_time,
get_correct_timestamp_cp(2),
);
assert!(result.success, "{}", result.error);
}
fn add_trusts(trust_graph: &mut ServiceInterface, trusts: &[Auth], cur_time: u64) {
for auth in trusts.iter() {
add_trust_checked(trust_graph, auth.trust.clone(), auth.issuer, cur_time);
}
}
fn revoke(
trust_graph: &mut ServiceInterface,
issuer_kp: &KeyPair,
revoked_peer_id: &PeerId,
revoked_at_sec: u64,
) -> Revoke {
let result = trust_graph.get_revoke_bytes(revoked_peer_id.to_base58(), revoked_at_sec);
assert!(result.success, "{}", result.error);
let revoke_bytes = issuer_kp.sign(&result.result).unwrap().to_vec().to_vec();
let issue_result = trust_graph.issue_revocation(
revoked_peer_id.to_base58(),
issuer_kp.get_peer_id().to_base58(),
revoked_at_sec,
revoke_bytes,
);
assert!(issue_result.success, "{}", issue_result.error);
let revoke_result = trust_graph.revoke_cp(
issue_result.revoke.clone(),
revoked_at_sec,
get_correct_timestamp_cp(1),
);
assert!(revoke_result.success, "{}", revoke_result.error);
issue_result.revoke
}
fn generate_trust_chain_with(
trust_graph: &mut ServiceInterface,
len: usize,
// Map of index to keypair. These key pairs will be used in trust chains at the given indexes
keys: HashMap<usize, KeyPair>,
expires_at: u64,
issued_at: u64,
) -> (Vec<KeyPair>, Vec<Auth>) {
assert!(len > 2);
let root_kp = KeyPair::generate_ed25519();
let second_kp = KeyPair::generate_ed25519();
let root_trust = issue_root_trust(trust_graph, &root_kp, issued_at, expires_at);
let second_trust = issue_trust(
trust_graph,
&root_kp,
&second_kp.get_peer_id(),
issued_at,
expires_at,
);
let mut chain = vec![
Auth {
issuer: root_kp.get_peer_id(),
trust: root_trust,
},
Auth {
issuer: root_kp.get_peer_id(),
trust: second_trust,
},
];
let mut key_pairs = vec![root_kp, second_kp];
for idx in 2..len {
let kp = keys
.get(&idx)
.unwrap_or(&KeyPair::generate_ed25519())
.clone();
let previous_kp = &key_pairs[idx - 1];
let trust = issue_trust(
trust_graph,
&previous_kp,
&kp.get_peer_id(),
issued_at,
expires_at,
);
chain.push(Auth {
issuer: previous_kp.get_peer_id(),
trust,
});
key_pairs.push(kp);
}
(key_pairs, chain)
}
fn generate_trust_chain_with_len(
trust_graph: &mut ServiceInterface,
len: usize,
keys: HashMap<usize, KeyPair>,
) -> (Vec<KeyPair>, Vec<Auth>) {
let cur_time = current_time();
let far_future = cur_time + 60;
generate_trust_chain_with(trust_graph, len, keys, far_future, cur_time)
}
fn get_weight(trust_graph: &mut ServiceInterface, peer_id: PeerId, cur_time: u64) -> u32 {
let result =
trust_graph.get_weight_cp(peer_id.to_base58(), cur_time, get_correct_timestamp_cp(1));
assert!(result.success, "{}", result.error);
result.weight
}
fn get_all_certs(
trust_graph: &mut ServiceInterface,
issued_for: PeerId,
cur_time: u64,
) -> Vec<Certificate> {
let result = trust_graph.get_all_certs_cp(
issued_for.to_base58(),
cur_time,
get_correct_timestamp_cp(1),
);
assert!(result.success, "{}", result.error);
result.certificates
}
#[test]
fn add_root_not_owner() {
let mut trust_graph = ServiceInterface::new();
clear_env();
let cp = CallParameters {
init_peer_id: "other_peer_id".to_string(),
service_creator_peer_id: "some_peer_id".to_string(),
..CallParameters::default()
};
let some_peer_id = KeyPair::generate_ed25519().get_peer_id();
let result = trust_graph.add_root_cp(some_peer_id.to_base58(), 0, cp);
assert!(!result.success);
assert_eq!(result.error, ServiceError::NotOwner.to_string());
}
#[test]
fn add_root_owner() {
let mut trust_graph = ServiceInterface::new();
clear_env();
let peer_id = "some_peer_id".to_string();
let cp = CallParameters {
init_peer_id: peer_id.clone(),
service_creator_peer_id: peer_id,
..CallParameters::default()
};
let some_peer_id = KeyPair::generate_ed25519().get_peer_id();
let result = trust_graph.add_root_cp(some_peer_id.to_base58(), 0, cp);
assert!(result.success, "{}", result.error);
}
#[test]
fn add_root_trust() {
let mut trust_graph = ServiceInterface::new();
clear_env();
let root_kp = KeyPair::generate_ed25519();
let root_peer_id = root_kp.get_peer_id();
let expires_at_sec = 9999u64;
let issued_at_sec = 0u64;
add_root_peer_id(&mut trust_graph, root_kp.get_peer_id(), 4u32);
let result =
trust_graph.get_trust_bytes(root_peer_id.to_base58(), expires_at_sec, issued_at_sec);
assert!(result.success, "{}", result.error);
let trust_bytes = root_kp.sign(&result.result).unwrap().to_vec().to_vec();
let issue_result = trust_graph.issue_trust(
root_peer_id.to_base58(),
expires_at_sec,
issued_at_sec,
trust_bytes,
);
assert!(issue_result.success, "{}", issue_result.error);
let verify_result = trust_graph.verify_trust_cp(
issue_result.trust.clone(),
root_peer_id.to_base58(),
100u64,
get_correct_timestamp_cp(2),
);
assert!(verify_result.success, "{}", verify_result.error);
let add_trust_result = trust_graph.add_trust_cp(
issue_result.trust,
root_peer_id.to_base58(),
100u64,
get_correct_timestamp_cp(2),
);
assert!(add_trust_result.success, "{}", add_trust_result.error);
assert_eq!(
get_weight(&mut trust_graph, root_peer_id, 100u64),
add_trust_result.weight
);
}
#[test]
fn test_expired_root_trust() {
let mut trust_graph = marine_test_env::trust_graph::ServiceInterface::new();
clear_env();
let root_kp = KeyPair::generate_ed25519();
let cur_time = 100u64;
let root_expired_time = cur_time + 10000;
add_root_with_trust(
&mut trust_graph,
&root_kp,
cur_time,
root_expired_time - 1,
4,
);
let trust_kp = KeyPair::generate_ed25519();
add_trust(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
cur_time,
root_expired_time + 99999,
);
let root_weight = get_weight(&mut trust_graph, root_kp.get_peer_id(), cur_time);
let trust_weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), cur_time);
assert_eq!(root_weight / 2, trust_weight);
let certs = get_all_certs(&mut trust_graph, trust_kp.get_peer_id(), cur_time);
assert_eq!(certs.len(), 1);
// get all certs after root expiration
let certs = get_all_certs(&mut trust_graph, trust_kp.get_peer_id(), root_expired_time);
assert_eq!(certs.len(), 0);
// check garbage collector
let certs = get_all_certs(&mut trust_graph, trust_kp.get_peer_id(), cur_time);
assert_eq!(certs.len(), 0);
}
#[test]
fn revoke_test() {
let mut trust_graph = marine_test_env::trust_graph::ServiceInterface::new();
clear_env();
let root_kp = KeyPair::generate_ed25519();
let mut cur_time = 100u64;
add_root_with_trust(&mut trust_graph, &root_kp, cur_time, cur_time + 9999, 4u32);
let trust_kp = KeyPair::generate_ed25519();
add_trust(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
cur_time,
cur_time + 99999,
);
let weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), cur_time);
assert_ne!(weight, 0u32);
cur_time += 1;
revoke(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
cur_time,
);
let weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), cur_time);
assert_eq!(weight, 0u32);
}
#[test]
fn test_add_one_trust_to_cert_last() {
let mut trust_graph = ServiceInterface::new();
let (key_pairs, mut trusts) =
generate_trust_chain_with_len(&mut trust_graph, 5, HashMap::new());
let cur_time = current_time();
let root_peer_id = key_pairs[0].get_peer_id();
add_root_peer_id(&mut trust_graph, root_peer_id, 2);
add_trusts(&mut trust_graph, &trusts, cur_time);
let issued_by = key_pairs.last().unwrap().get_peer_id();
let trust_kp = KeyPair::generate_ed25519();
let issued_for = trust_kp.get_peer_id();
let future = cur_time + 60;
let trust = add_trust(
&mut trust_graph,
&key_pairs.last().unwrap(),
&issued_for,
cur_time,
future,
);
trusts.push(Auth {
issuer: issued_by,
trust,
});
let previous_weight = get_weight(&mut trust_graph, issued_by, cur_time);
assert_ne!(previous_weight, 0u32);
let weight = get_weight(&mut trust_graph, issued_for, cur_time);
assert_eq!(weight * 2, previous_weight);
let certs = get_all_certs(&mut trust_graph, issued_for, cur_time);
assert_eq!(certs.len(), 1);
for (i, trust) in certs[0].chain.iter().enumerate() {
assert_eq!(*trust, trusts[i].trust);
}
}
#[test]
fn test_expired_trust() {
let mut trust_graph = ServiceInterface::new();
let (key_pairs, mut trusts) =
generate_trust_chain_with_len(&mut trust_graph, 5, HashMap::new());
let cur_time = current_time();
let root1_peer_id = key_pairs[0].get_peer_id();
add_root_peer_id(&mut trust_graph, root1_peer_id, 2);
add_trusts(&mut trust_graph, &trusts, cur_time);
let issued_by = key_pairs.last().unwrap().get_peer_id();
let trust_kp = KeyPair::generate_ed25519();
let issued_for = trust_kp.get_peer_id();
let expired_time = cur_time + 60;
let trust = add_trust(
&mut trust_graph,
&key_pairs.last().unwrap(),
&issued_for,
cur_time,
expired_time,
);
trusts.push(Auth {
issuer: issued_by,
trust,
});
let certs = get_all_certs(&mut trust_graph, issued_for, cur_time);
assert_eq!(certs.len(), 1);
for (i, trust) in certs[0].chain.iter().enumerate() {
assert_eq!(*trust, trusts[i].trust);
}
let certs = get_all_certs(&mut trust_graph, issued_for, expired_time);
assert_eq!(certs.len(), 0);
// check garbage collector
let certs = get_all_certs(&mut trust_graph, issued_for, cur_time);
assert_eq!(certs.len(), 0);
}
#[test]
fn test_get_one_cert() {
let mut trust_graph = ServiceInterface::new();
clear_env();
let (key_pairs, trusts) =
generate_trust_chain_with_len(&mut trust_graph, 5, HashMap::new());
let cur_time = current_time();
let root_peer_id = key_pairs[0].get_peer_id();
add_root_peer_id(&mut trust_graph, root_peer_id, 1);
for auth in trusts.iter() {
add_trust_checked(&mut trust_graph, auth.trust.clone(), auth.issuer, cur_time);
}
let certs = trust_graph.get_all_certs_cp(
key_pairs.last().unwrap().get_peer_id().to_base58(),
cur_time,
get_correct_timestamp_cp(1),
);
assert!(certs.success, "{}", certs.error);
let certs = certs.certificates;
assert_eq!(certs.len(), 1);
for (i, trust) in certs[0].chain.iter().enumerate() {
assert_eq!(*trust, trusts[i].trust);
}
}
#[test]
fn test_chain_from_root_to_another_root() {
let mut trust_graph = ServiceInterface::new();
clear_env();
let (kps, trusts) = generate_trust_chain_with_len(&mut trust_graph, 6, HashMap::new());
let cur_time = current_time();
let far_future = cur_time + 9999;
// add first and last trusts as roots
add_root_peer_id(&mut trust_graph, kps[0].get_peer_id(), 0);
add_trusts(&mut trust_graph, &trusts, cur_time);
add_root_with_trust(&mut trust_graph, &kps[5], cur_time, far_future, 0);
let certs = get_all_certs(&mut trust_graph, kps[5].get_peer_id(), cur_time);
// first with self-signed last trust, second - without
assert_eq!(certs.len(), 2);
assert_eq!(certs[0].chain.len(), 6);
assert_eq!(certs[1].chain.len(), 7);
}
#[test]
fn test_revoke_gc() {
let mut trust_graph = marine_test_env::trust_graph::ServiceInterface::new();
clear_env();
let root_kp = KeyPair::generate_ed25519();
let cur_time = 100u64;
add_root_with_trust(&mut trust_graph, &root_kp, cur_time, cur_time + 999, 4u32);
let trust_kp = KeyPair::generate_ed25519();
add_trust(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
cur_time,
cur_time + 99999,
);
let weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), cur_time);
assert_ne!(weight, 0u32);
let revoked_time = cur_time + 1;
revoke(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
revoked_time,
);
let weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), revoked_time);
assert_eq!(weight, 0u32);
// add trust issued earlier than last revoke
add_trust(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
revoked_time - 10,
cur_time + 99999,
);
let weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), revoked_time);
assert_eq!(weight, 0u32);
}
#[test]
fn test_update_trust() {
let mut trust_graph = marine_test_env::trust_graph::ServiceInterface::new();
clear_env();
let root_kp = KeyPair::generate_ed25519();
let mut cur_time = 100u64;
add_root_with_trust(&mut trust_graph, &root_kp, cur_time, cur_time + 999, 4u32);
let trust_kp = KeyPair::generate_ed25519();
let expires_at_sec = cur_time + 10;
add_trust(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
cur_time,
expires_at_sec,
);
let weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), cur_time);
assert_ne!(weight, 0u32);
cur_time = expires_at_sec - 1;
let future_time = expires_at_sec + 10;
// add trust that expires lately
add_trust(
&mut trust_graph,
&root_kp,
&trust_kp.get_peer_id(),
cur_time,
future_time + 99999,
);
// first trust should be replaced by second (and has already been expired)
let weight = get_weight(&mut trust_graph, trust_kp.get_peer_id(), future_time);
assert_ne!(weight, 0u32);
}
#[test]
fn path_from_root_to_root_weight() {
let mut trust_graph = marine_test_env::trust_graph::ServiceInterface::new();
clear_env();
let root1_kp = KeyPair::generate_ed25519();
let root2_kp = KeyPair::generate_ed25519();
let cur_time = 100;
let far_future = cur_time + 99999;
// root with bigger weight (smaller weight factor)
add_root_with_trust(&mut trust_graph, &root1_kp, cur_time, far_future, 0u32);
// opposite
add_root_with_trust(&mut trust_graph, &root2_kp, cur_time, far_future, 5u32);
// issue trust from root2 to any other peer_id
let issued_by_root2_peer_id = KeyPair::generate_ed25519().get_peer_id();
add_trust(
&mut trust_graph,
&root2_kp,
&issued_by_root2_peer_id,
cur_time,
far_future,
);
let root2_weight_before = get_weight(&mut trust_graph, root2_kp.get_peer_id(), cur_time);
let issued_by_root2_peer_id_before =
get_weight(&mut trust_graph, issued_by_root2_peer_id, cur_time);
// issue trust from root1 to root2
add_trust(
&mut trust_graph,
&root1_kp,
&root2_kp.get_peer_id(),
cur_time,
far_future,
);
let root2_weight_after = get_weight(&mut trust_graph, root2_kp.get_peer_id(), cur_time);
let issued_by_root2_peer_id_after =
get_weight(&mut trust_graph, issued_by_root2_peer_id, cur_time);
assert!(issued_by_root2_peer_id_before < issued_by_root2_peer_id_after);
assert!(root2_weight_before < root2_weight_after);
}
#[test]
fn add_self_signed_weight() {
let mut trust_graph = marine_test_env::trust_graph::ServiceInterface::new();
clear_env();
let root_kp = KeyPair::generate_ed25519();
let cur_time = 100;
let far_future = cur_time + 99999;
add_root_with_trust(&mut trust_graph, &root_kp, cur_time, far_future, 0u32);
// issue trust from root to any other peer
let other_peer_kp = KeyPair::generate_ed25519();
add_trust(
&mut trust_graph,
&root_kp,
&other_peer_kp.get_peer_id(),
cur_time,
far_future,
);
let weight_before = get_weight(&mut trust_graph, other_peer_kp.get_peer_id(), cur_time);
// issue self-signed trust
add_trust(
&mut trust_graph,
&other_peer_kp,
&other_peer_kp.get_peer_id(),
cur_time,
far_future,
);
let weight_after = get_weight(&mut trust_graph, other_peer_kp.get_peer_id(), cur_time);
assert_eq!(weight_after, weight_before);
}
#[test]
fn test_get_one_host_cert() {
let mut trust_graph = ServiceInterface::new();
clear_env();
let (key_pairs, trusts) =
generate_trust_chain_with_len(&mut trust_graph, 5, HashMap::new());
let cur_time = current_time();
let root_peer_id = key_pairs[0].get_peer_id();
add_root_peer_id(&mut trust_graph, root_peer_id, 1);
for auth in trusts.iter() {
add_trust_checked(&mut trust_graph, auth.trust.clone(), auth.issuer, cur_time);
}
let mut cp = get_correct_timestamp_cp_with_host_id(
0,
key_pairs.last().unwrap().get_peer_id().to_base58(),
);
let certs = trust_graph.get_host_certs_cp(cur_time, cp);
assert!(certs.success, "{}", certs.error);
let certs = certs.certificates;
assert_eq!(certs.len(), 1);
for (i, trust) in certs[0].chain.iter().enumerate() {
assert_eq!(*trust, trusts[i].trust);
}
}
#[test]
fn test_get_one_host_cert_from() {
let mut trust_graph = ServiceInterface::new();
clear_env();
let (key_pairs, mut trusts) =
generate_trust_chain_with_len(&mut trust_graph, 5, HashMap::new());
let cur_time = current_time();
let root_peer_id = key_pairs[0].get_peer_id();
add_root_peer_id(&mut trust_graph, root_peer_id, 1);
for auth in trusts.iter() {
add_trust_checked(&mut trust_graph, auth.trust.clone(), auth.issuer, cur_time);
}
let mut cp = get_correct_timestamp_cp_with_host_id(
1,
key_pairs.last().unwrap().get_peer_id().to_base58(),
);
let certs = trust_graph.get_host_certs_from_cp(
key_pairs[3].get_peer_id().to_base58(),
cur_time,
cp,
);
assert!(certs.success, "{}", certs.error);
let certs = certs.certificates;
assert_eq!(certs.len(), 1);
assert_eq!(certs[0].chain.len(), 5);
for (i, trust) in certs[0].chain.iter().enumerate() {
assert_eq!(*trust, trusts[i].trust);
}
}
}

View File

@ -74,6 +74,47 @@ impl Certificate {
Self { chain } Self { chain }
} }
pub fn new_from_root_trust(root_trust: Trust, issued_trust: Trust, cur_time: Duration) -> Result<Self, CertificateError> {
Trust::verify(&root_trust, &root_trust.issued_for, cur_time).map_err(MalformedRoot)?;
Trust::verify(&issued_trust, &root_trust.issued_for, cur_time).map_err(|e| VerificationError(1, e))?;
Ok(Self { chain: vec![root_trust, issued_trust] })
}
pub fn issue_with_trust(issued_by: PublicKey, trust: Trust, extend_cert: &Certificate, cur_time: Duration) -> Result<Self, CertificateError> {
if trust.expires_at.lt(&trust.issued_at) {
return Err(ExpirationError {
expires_at: format!("{:?}", trust.expires_at),
issued_at: format!("{:?}", trust.issued_at),
});
}
Certificate::verify(extend_cert, &[extend_cert.chain[0].issued_for.clone()], cur_time)?;
// check if `issued_by` is allowed to issue a certificate (i.e., theres a trust for it in a chain)
let mut previous_trust_num: i32 = -1;
for pk_id in 0..extend_cert.chain.len() {
if extend_cert.chain[pk_id].issued_for == issued_by {
previous_trust_num = pk_id as i32;
}
}
if previous_trust_num == -1 {
return Err(KeyInCertificateError);
};
// splitting old chain to add new trust after given public key
let mut new_chain = extend_cert
.chain
.split_at((previous_trust_num + 1) as usize)
.0
.to_vec();
new_chain.push(trust);
Ok(Self { chain: new_chain })
}
/// Creates new certificate with root trust (self-signed public key) from a key pair. /// Creates new certificate with root trust (self-signed public key) from a key pair.
#[allow(dead_code)] #[allow(dead_code)]
pub fn issue_root( pub fn issue_root(

View File

@ -25,6 +25,7 @@
unused_unsafe, unused_unsafe,
unreachable_patterns unreachable_patterns
)] )]
#![allow(dead_code)]
mod certificate; mod certificate;
pub mod certificate_serde; pub mod certificate_serde;
@ -34,13 +35,13 @@ mod revoke;
mod trust; mod trust;
mod trust_graph; mod trust_graph;
mod trust_graph_storage; mod trust_graph_storage;
mod trust_node; mod trust_relation;
pub use crate::certificate::{Certificate, CertificateError}; pub use crate::certificate::{Certificate, CertificateError};
pub use crate::misc::current_time; pub use crate::misc::current_time;
pub use crate::public_key_hashable::PublicKeyHashable; pub use crate::public_key_hashable::PublicKeyHashable;
pub use crate::revoke::Revoke; pub use crate::revoke::Revoke;
pub use crate::trust::Trust; pub use crate::trust::{Trust, TrustError};
pub use crate::trust_graph::{TrustGraph, TrustGraphError, Weight}; pub use crate::trust_graph::{TrustGraph, TrustGraphError, WeightFactor, MAX_WEIGHT_FACTOR};
pub use crate::trust_graph_storage::{Storage, StorageError, InMemoryStorage, InMemoryStorageError}; pub use crate::trust_graph_storage::{Storage, StorageError};
pub use crate::trust_node::{Auth, TrustNode}; pub use crate::trust_relation::{Auth, TrustRelation};

View File

@ -19,6 +19,7 @@ use fluence_keypair::key_pair::KeyPair;
use fluence_keypair::public_key::PublicKey; use fluence_keypair::public_key::PublicKey;
use fluence_keypair::signature::Signature; use fluence_keypair::signature::Signature;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::Digest;
use std::time::Duration; use std::time::Duration;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
@ -28,7 +29,7 @@ pub enum RevokeError {
IncorrectSignature( IncorrectSignature(
#[from] #[from]
#[source] #[source]
fluence_keypair::error::SigningError fluence_keypair::error::VerificationError,
), ),
} }
@ -43,12 +44,12 @@ pub struct Revoke {
/// the issuer of this revocation /// the issuer of this revocation
pub revoked_by: PublicKey, pub revoked_by: PublicKey,
/// proof of this revocation /// proof of this revocation
signature: Signature, pub signature: Signature,
} }
impl Revoke { impl Revoke {
#[allow(dead_code)] #[allow(dead_code)]
fn new( pub fn new(
pk: PublicKey, pk: PublicKey,
revoked_by: PublicKey, revoked_by: PublicKey,
revoked_at: Duration, revoked_at: Duration,
@ -71,14 +72,14 @@ impl Revoke {
Revoke::new(to_revoke, revoker.public(), revoked_at, signature) Revoke::new(to_revoke, revoker.public(), revoked_at, signature)
} }
fn signature_bytes(pk: &PublicKey, revoked_at: Duration) -> Vec<u8> { pub fn signature_bytes(pk: &PublicKey, revoked_at: Duration) -> Vec<u8> {
let mut msg = Vec::new(); let mut metadata = Vec::new();
let pk_bytes = &pk.encode(); let pk_bytes = &pk.encode();
msg.push(pk_bytes.len() as u8); metadata.push(pk_bytes.len() as u8);
msg.extend(pk_bytes); metadata.extend(pk_bytes);
msg.extend_from_slice(&(revoked_at.as_secs() as u64).to_le_bytes()); metadata.extend_from_slice(&(revoked_at.as_secs() as u64).to_le_bytes());
msg sha2::Sha256::digest(&metadata).to_vec()
} }
/// Verifies that revocation is cryptographically correct. /// Verifies that revocation is cryptographically correct.
@ -87,7 +88,8 @@ impl Revoke {
revoke revoke
.revoked_by .revoked_by
.verify(msg.as_slice(), &revoke.signature).map_err(IncorrectSignature) .verify(msg.as_slice(), &revoke.signature)
.map_err(IncorrectSignature)
} }
} }

View File

@ -22,6 +22,7 @@ use fluence_keypair::key_pair::KeyPair;
use fluence_keypair::public_key::PublicKey; use fluence_keypair::public_key::PublicKey;
use fluence_keypair::signature::Signature; use fluence_keypair::signature::Signature;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::Digest;
use std::convert::TryInto; use std::convert::TryInto;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::time::Duration; use std::time::Duration;
@ -67,7 +68,7 @@ pub enum TrustError {
SignatureError( SignatureError(
#[from] #[from]
#[source] #[source]
fluence_keypair::error::SigningError, fluence_keypair::error::VerificationError,
), ),
/// Errors occurred on trust decoding from different formats /// Errors occurred on trust decoding from different formats
@ -113,7 +114,7 @@ impl Trust {
expires_at: Duration, expires_at: Duration,
issued_at: Duration, issued_at: Duration,
) -> Self { ) -> Self {
let msg = Self::metadata_bytes(&issued_for, expires_at, issued_at); let msg = Self::signature_bytes(&issued_for, expires_at, issued_at);
let signature = issued_by.sign(msg.as_slice()).unwrap(); let signature = issued_by.sign(msg.as_slice()).unwrap();
@ -136,12 +137,12 @@ impl Trust {
} }
let msg: &[u8] = let msg: &[u8] =
&Self::metadata_bytes(&trust.issued_for, trust.expires_at, trust.issued_at); &Self::signature_bytes(&trust.issued_for, trust.expires_at, trust.issued_at);
KeyPair::verify(issued_by, msg, &trust.signature).map_err(SignatureError) KeyPair::verify(issued_by, msg, &trust.signature).map_err(SignatureError)
} }
fn metadata_bytes(pk: &PublicKey, expires_at: Duration, issued_at: Duration) -> Vec<u8> { pub fn signature_bytes(pk: &PublicKey, expires_at: Duration, issued_at: Duration) -> Vec<u8> {
let pk_encoded = pk.encode(); let pk_encoded = pk.encode();
let expires_at_encoded: [u8; EXPIRATION_LEN] = (expires_at.as_secs() as u64).to_le_bytes(); let expires_at_encoded: [u8; EXPIRATION_LEN] = (expires_at.as_secs() as u64).to_le_bytes();
let issued_at_encoded: [u8; ISSUED_LEN] = (issued_at.as_secs() as u64).to_le_bytes(); let issued_at_encoded: [u8; ISSUED_LEN] = (issued_at.as_secs() as u64).to_le_bytes();
@ -151,7 +152,7 @@ impl Trust {
metadata.extend_from_slice(&expires_at_encoded[0..EXPIRATION_LEN]); metadata.extend_from_slice(&expires_at_encoded[0..EXPIRATION_LEN]);
metadata.extend_from_slice(&issued_at_encoded[0..ISSUED_LEN]); metadata.extend_from_slice(&issued_at_encoded[0..ISSUED_LEN]);
metadata sha2::Sha256::digest(&metadata).to_vec()
} }
/// Encode the trust into a byte array /// Encode the trust into a byte array

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use crate::certificate::CertificateError::{CertificateLengthError, Unexpected}; use crate::certificate::CertificateError::CertificateLengthError;
use crate::certificate::{Certificate, CertificateError}; use crate::certificate::{Certificate, CertificateError};
use crate::public_key_hashable::PublicKeyHashable as PK; use crate::public_key_hashable::PublicKeyHashable as PK;
use crate::revoke::Revoke; use crate::revoke::Revoke;
@ -24,8 +24,8 @@ use crate::trust_graph::TrustGraphError::{
CertificateCheckError, EmptyChain, InternalStorageError, NoRoot, CertificateCheckError, EmptyChain, InternalStorageError, NoRoot,
}; };
use crate::trust_graph_storage::Storage; use crate::trust_graph_storage::Storage;
use crate::trust_node::{Auth, TrustNode}; use crate::trust_relation::Auth;
use crate::StorageError; use crate::{StorageError, TrustError};
use fluence_keypair::public_key::PublicKey; use fluence_keypair::public_key::PublicKey;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::{HashSet, VecDeque}; use std::collections::{HashSet, VecDeque};
@ -35,7 +35,9 @@ use std::time::Duration;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
/// for simplicity, we store `n` where Weight = 1/n^2 /// for simplicity, we store `n` where Weight = 1/n^2
pub type Weight = u32; pub type WeightFactor = u32;
pub static MAX_WEIGHT_FACTOR: u32 = 16;
/// Graph to efficiently calculate weights of certificates and get chains of certificates. /// Graph to efficiently calculate weights of certificates and get chains of certificates.
/// TODO serialization/deserialization /// TODO serialization/deserialization
@ -68,6 +70,14 @@ pub enum TrustGraphError {
#[source] #[source]
RevokeError, RevokeError,
), ),
#[error("Path to {0} not found")]
AddTrustError(String),
#[error("Trust verification error: {0}")]
TrustVerificationError(
#[from]
#[source]
TrustError,
),
} }
impl<T: StorageError + 'static> From<T> for TrustGraphError { impl<T: StorageError + 'static> From<T> for TrustGraphError {
@ -82,7 +92,10 @@ impl From<TrustGraphError> for String {
} }
} }
#[allow(dead_code)] pub fn get_weight_from_factor(wf: WeightFactor) -> u32 {
2u32.pow(MAX_WEIGHT_FACTOR.checked_sub(wf).unwrap_or(0u32))
}
impl<S> TrustGraph<S> impl<S> TrustGraph<S>
where where
S: Storage, S: Storage,
@ -92,95 +105,104 @@ where
} }
/// Insert new root weight /// Insert new root weight
pub fn add_root_weight( pub fn add_root_weight_factor(
&mut self, &mut self,
pk: PublicKey, pk: PublicKey,
weight: Weight, weight: WeightFactor,
) -> Result<(), TrustGraphError> { ) -> Result<(), TrustGraphError> {
Ok(self.storage.add_root_weight(pk.into(), weight)?) Ok(self.storage.add_root_weight_factor(pk.into(), weight)?)
} }
/// Get trust by public key pub fn add_trust<T, P>(
pub fn get(&self, pk: PublicKey) -> Result<Option<TrustNode>, TrustGraphError> { &mut self,
Ok(self.storage.get(&pk.into())?) trust: T,
issued_by: P,
cur_time: Duration,
) -> Result<u32, TrustGraphError>
where
T: Borrow<Trust>,
P: Borrow<PublicKey>,
{
Trust::verify(trust.borrow(), issued_by.borrow(), cur_time)?;
let next_weight = self.get_next_weight(
issued_by.borrow().as_ref(),
trust.borrow().issued_for.as_ref(),
cur_time,
)?;
if next_weight == 0u32 {
return Ok(0u32);
}
let auth = Auth {
trust: trust.borrow().clone(),
issued_by: issued_by.borrow().clone(),
};
self.storage.update_auth(auth, cur_time)?;
Ok(next_weight)
} }
// TODO: remove cur_time from api, leave it for tests only
/// Certificate is a chain of trusts, add this chain to graph /// Certificate is a chain of trusts, add this chain to graph
pub fn add<C>(&mut self, cert: C, cur_time: Duration) -> Result<(), TrustGraphError> pub fn add<C>(&mut self, cert: C, cur_time: Duration) -> Result<(), TrustGraphError>
where where
C: Borrow<Certificate>, C: Borrow<Certificate>,
{ {
let roots: Vec<PublicKey> = self let chain = &cert.borrow().chain;
.storage let mut issued_by = chain.get(0).ok_or(EmptyChain)?.issued_for.clone();
.root_keys()?
.iter()
.cloned()
.map(Into::into)
.collect();
// Check that certificate is valid and converges to one of the known roots
Certificate::verify(cert.borrow(), roots.as_slice(), cur_time)?;
let mut chain = cert.borrow().chain.iter(); // TODO: optimize to check only root weight
let root_trust = chain.next().ok_or(EmptyChain)?;
let root_pk: PK = root_trust.issued_for.clone().into();
// Insert new TrustNode for this root_pk if there wasn't one
if self.storage.get(&root_pk)?.is_none() {
let mut trust_node = TrustNode::new(root_trust.issued_for.clone(), cur_time);
let root_auth = Auth {
trust: root_trust.clone(),
issued_by: root_trust.issued_for.clone(),
};
trust_node.update_auth(root_auth);
self.storage.insert(root_pk, trust_node)?;
}
// Insert remaining trusts to the graph
let mut previous_trust = root_trust;
for trust in chain { for trust in chain {
let pk = trust.issued_for.clone().into(); self.add_trust(trust, issued_by, cur_time)?;
issued_by = trust.issued_for.clone();
let auth = Auth {
trust: trust.clone(),
issued_by: previous_trust.issued_for.clone(),
};
self.storage
.update_auth(&pk, auth, &root_trust.issued_for, cur_time)?;
previous_trust = trust;
} }
Ok(()) Ok(())
} }
fn get_next_weight(
&mut self,
issued_by: &PK,
issued_for: &PK,
cur_time: Duration,
) -> Result<u32, TrustGraphError> {
let issued_by_weight = self.weight(issued_by.as_ref(), cur_time)?;
// self-signed trust has same weight as max weight of issuer
if issued_by.eq(issued_for) {
Ok(issued_by_weight)
} else {
Ok(issued_by_weight / 2)
}
}
/// Get the maximum weight of trust for one public key. /// Get the maximum weight of trust for one public key.
pub fn weight<P>(&self, pk: P) -> Result<Option<Weight>, TrustGraphError> pub fn weight<P>(&mut self, pk: P, cur_time: Duration) -> Result<u32, TrustGraphError>
where where
P: Borrow<PublicKey>, P: Borrow<PublicKey>,
{ {
if let Some(weight) = self.storage.get_root_weight(pk.borrow().as_ref())? { let mut max_weight = 0;
return Ok(Some(weight)); if let Some(weight_factor) = self.storage.get_root_weight_factor(pk.borrow().as_ref())? {
max_weight = get_weight_from_factor(weight_factor);
} }
let roots: Vec<PublicKey> = self
.storage
.root_keys()?
.iter()
.map(|pk| pk.clone().into())
.collect();
// get all possible certificates from the given public key to all roots in the graph // get all possible certificates from the given public key to all roots in the graph
let certs = self.get_all_certs(pk, roots.as_slice())?; let certs = self.get_all_certs(pk, cur_time)?;
self.certificates_weight(certs) if let Some(weight_factor) = self.certificates_weight_factor(certs)? {
max_weight = std::cmp::max(max_weight, get_weight_from_factor(weight_factor))
}
Ok(max_weight)
} }
/// Calculate weight from given certificates /// Calculate weight from given certificates
/// Returns None if there is no such public key /// Returns None if there is no such public key
/// or some trust between this key and a root key is revoked. /// or some trust between this key and a root key is revoked.
/// TODO handle non-direct revocations pub fn certificates_weight_factor<C, I>(
pub fn certificates_weight<C, I>(&self, certs: I) -> Result<Option<Weight>, TrustGraphError> &self,
certs: I,
) -> Result<Option<WeightFactor>, TrustGraphError>
where where
C: Borrow<Certificate>, C: Borrow<Certificate>,
I: IntoIterator<Item = C>, I: IntoIterator<Item = C>,
@ -192,7 +214,7 @@ where
return Ok(None); return Ok(None);
} }
let mut weight = std::u32::MAX; let mut weight_factor = u32::MAX;
for cert in certs { for cert in certs {
let c = cert.borrow(); let c = cert.borrow();
@ -202,31 +224,30 @@ where
.first() .first()
.ok_or(CertificateCheckError(CertificateLengthError))?; .ok_or(CertificateCheckError(CertificateLengthError))?;
let root_weight = self let root_weight_factor = self
.storage .storage
.get_root_weight(first.issued_for.as_ref())? .get_root_weight_factor(first.issued_for.as_ref())?
.ok_or(NoRoot)?; .ok_or(NoRoot)?;
// certificate weight = root weight + 1 * every other element in the chain // certificate weight_factor = root weight factor + 1 * every other element in the chain
// (except root, so the formula is `root weight + chain length - 1`) // (except root, so the formula is `root weight factor + chain length - 1`)
weight = std::cmp::min(weight, root_weight + c.chain.len() as u32 - 1) weight_factor =
std::cmp::min(weight_factor, root_weight_factor + c.chain.len() as u32 - 1)
} }
Ok(Some(weight)) Ok(Some(weight_factor))
} }
/// BF search for all converging paths (chains) in the graph /// BF search for all converging paths (chains) in the graph
/// TODO could be optimized with closure, that will calculate the weight on the fly
/// TODO or store auths to build certificates
fn bf_search_paths( fn bf_search_paths(
&self, &self,
node: &TrustNode, pk: &PK,
roots: HashSet<&PK>, roots: HashSet<&PK>,
) -> Result<Vec<Vec<Auth>>, TrustGraphError> { ) -> Result<Vec<Vec<Auth>>, TrustGraphError> {
// queue to collect all chains in the trust graph (each chain is a path in the trust graph) // queue to collect all chains in the trust graph (each chain is a path in the trust graph)
let mut chains_queue: VecDeque<Vec<Auth>> = VecDeque::new(); let mut chains_queue: VecDeque<Vec<Auth>> = VecDeque::new();
let node_auths: Vec<Auth> = node.authorizations().cloned().collect(); let node_auths: Vec<Auth> = self.storage.get_authorizations(pk)?;
// put all auth in the queue as the first possible paths through the graph // put all auth in the queue as the first possible paths through the graph
for auth in node_auths { for auth in node_auths {
chains_queue.push_back(vec![auth]); chains_queue.push_back(vec![auth]);
@ -244,13 +265,9 @@ where
.last() .last()
.expect("`cur_chain` always has at least one element"); .expect("`cur_chain` always has at least one element");
let auths: Vec<Auth> = self let auths = self
.storage .storage
.get(&last.issued_by.clone().into())? .get_authorizations(&last.issued_by.clone().into())?;
.ok_or(CertificateCheckError(Unexpected))?
.authorizations()
.cloned()
.collect();
for auth in auths { for auth in auths {
// if there is auth, that we not visited in the current chain, copy chain and append this auth // if there is auth, that we not visited in the current chain, copy chain and append this auth
@ -266,7 +283,7 @@ where
// to be considered a valid chain, the chain must: // to be considered a valid chain, the chain must:
// - end with a self-signed trust // - end with a self-signed trust
// - that trust must converge to one of the root weights // - that trust must converge to one of the roots
// - there should be more than 1 trust in the chain // - there should be more than 1 trust in the chain
let self_signed = last.issued_by == last.trust.issued_for; let self_signed = last.issued_by == last.trust.issued_for;
let issued_by: &PK = last.issued_by.as_ref(); let issued_by: &PK = last.issued_by.as_ref();
@ -280,402 +297,46 @@ where
Ok(terminated_chains) Ok(terminated_chains)
} }
// TODO: remove `roots` argument from api, leave it for tests and internal usage only
/// Get all possible certificates where `issued_for` will be the last element of the chain /// Get all possible certificates where `issued_for` will be the last element of the chain
/// and one of the destinations is the root of this chain. /// and one of the destinations is the root of this chain.
pub fn get_all_certs<P>( pub fn get_all_certs<P>(
&self, &mut self,
issued_for: P, issued_for: P,
roots: &[PublicKey], cur_time: Duration,
) -> Result<Vec<Certificate>, TrustGraphError> ) -> Result<Vec<Certificate>, TrustGraphError>
where where
P: Borrow<PublicKey>, P: Borrow<PublicKey>,
{ {
// get all auths (edges) for issued public key // garbage collect
let issued_for_node = self.storage.get(issued_for.borrow().as_ref())?; self.storage.remove_expired(cur_time)?;
let roots = roots.iter().map(|pk| pk.as_ref()); // maybe later we should retrieve root keys lazily
let keys = self.storage.root_keys()?; let keys = self.storage.root_keys()?;
let roots = keys.iter().chain(roots).collect(); let roots = keys.iter().collect();
match issued_for_node { Ok(self
Some(node) => Ok(self .bf_search_paths(issued_for.borrow().as_ref(), roots)?
.bf_search_paths(&node, roots)? .into_iter()
.iter() .map(|auths| {
.map(|auths| { let trusts: Vec<Trust> = auths.into_iter().map(|auth| auth.trust).rev().collect();
// TODO: can avoid cloning here by returning &Certificate Certificate::new_unverified(trusts)
let trusts: Vec<Trust> = })
auths.iter().map(|auth| auth.trust.clone()).rev().collect(); .filter(|c| {
Certificate::new_unverified(trusts) // Certificate with one trust means nothing, gotta be a bug. Checking for it here.
}) debug_assert!(
.filter(|c| { c.chain.len() > 1,
// Certificate with one trust means nothing, gotta be a bug. Checking for it here. "certificate with chain of len 1 arose: {:#?}",
debug_assert!( c
c.chain.len() > 1, );
"certificate with chain of len 1 arose: {:#?}", c.chain.len() > 1
c })
); .collect())
c.chain.len() > 1
})
.collect()),
None => Ok(Vec::new()),
}
} }
/// Mark public key as revoked. /// Mark public key as revoked.
pub fn revoke(&mut self, revoke: Revoke) -> Result<(), TrustGraphError> { pub fn revoke(&mut self, revoke: Revoke) -> Result<(), TrustGraphError> {
Revoke::verify(&revoke)?; Revoke::verify(&revoke)?;
let pk: PK = revoke.pk.clone().into(); Ok(self.storage.revoke(revoke)?)
Ok(self.storage.revoke(&pk, revoke)?)
}
/// Check information about new certificates and about revoked certificates.
/// Do it once per some time
// TODO
fn maintain() {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::misc::current_time;
use crate::trust_graph_storage::InMemoryStorage;
use failure::_core::time::Duration;
use fluence_keypair::key_pair::KeyPair;
use std::collections::HashMap;
pub fn one_minute() -> Duration {
Duration::new(60, 0)
}
fn generate_root_cert() -> (KeyPair, KeyPair, Certificate) {
let root_kp = KeyPair::generate_ed25519();
let second_kp = KeyPair::generate_ed25519();
let cur_time = current_time();
(
root_kp.clone(),
second_kp.clone(),
Certificate::issue_root(
&root_kp,
second_kp.public(),
cur_time.checked_add(one_minute()).unwrap(),
cur_time,
),
)
}
fn generate_cert_with(
len: usize,
// Map of index to keypair. These key pairs will be used in trust chains at the given indexes
keys: HashMap<usize, KeyPair>,
expires_at: Duration,
issued_at: Duration,
) -> Result<(Vec<KeyPair>, Certificate), TrustGraphError> {
assert!(len > 2);
let root_kp = KeyPair::generate_ed25519();
let second_kp = KeyPair::generate_ed25519();
let mut cert = Certificate::issue_root(&root_kp, second_kp.public(), expires_at, issued_at);
let mut key_pairs = vec![root_kp, second_kp];
for idx in 2..len {
let kp = keys
.get(&idx)
.unwrap_or(&KeyPair::generate_ed25519())
.clone();
let previous_kp = &key_pairs[idx - 1];
cert = Certificate::issue(
&previous_kp,
kp.public(),
&cert,
expires_at,
// TODO: why `issued_at = issued_at - 60 seconds`?
issued_at.checked_sub(Duration::from_secs(60)).unwrap(),
current_time(),
)?;
key_pairs.push(kp);
}
Ok((key_pairs, cert))
}
fn generate_cert_with_len(
len: usize,
keys: HashMap<usize, KeyPair>,
) -> Result<(Vec<KeyPair>, Certificate), TrustGraphError> {
let cur_time = current_time();
let far_future = cur_time.checked_add(one_minute()).unwrap();
generate_cert_with(len, keys, far_future, cur_time)
}
#[test]
fn test_add_cert_without_trusted_root() {
let (_, _, cert) = generate_root_cert();
let cur_time = current_time();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
let addition = graph.add(cert, cur_time);
assert_eq!(addition.is_ok(), false);
}
#[test]
fn test_add_cert() {
let (root, _, cert) = generate_root_cert();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
graph.add_root_weight(root.public().into(), 0).unwrap();
let addition = graph.add(cert, current_time());
assert_eq!(addition.is_ok(), true);
}
#[test]
fn test_add_certs_with_same_trusts_and_different_expirations_ed25519() {
let cur_time = current_time();
let far_future = cur_time + Duration::from_secs(10);
let far_far_future = cur_time + Duration::from_secs(900);
let key_pair1 = KeyPair::generate_ed25519();
let key_pair2 = KeyPair::generate_ed25519();
// Use key_pair1 and key_pair2 for 5th and 6th trust in the cert chain
let mut chain_keys = HashMap::new();
chain_keys.insert(5, key_pair1.clone());
chain_keys.insert(6, key_pair2.clone());
let (key_pairs1, cert1) =
generate_cert_with(10, chain_keys, far_future * 2, far_future).expect("");
// Use key_pair1 and key_pair2 for 7th and 8th trust in the cert chain
let mut chain_keys = HashMap::new();
chain_keys.insert(7, key_pair1.clone());
chain_keys.insert(8, key_pair2.clone());
let (key_pairs2, cert2) =
generate_cert_with(10, chain_keys, far_far_future * 2, far_far_future).unwrap();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
let root1_pk = key_pairs1[0].public();
let root2_pk = key_pairs2[0].public();
graph.add_root_weight(root1_pk.into(), 1).unwrap();
graph.add_root_weight(root2_pk.into(), 0).unwrap();
graph.add(cert1, cur_time).unwrap();
let node2 = graph.get(key_pair2.public()).unwrap().unwrap();
let auth_by_kp1 = node2
.authorizations()
.find(|a| a.issued_by == key_pair1.public())
.unwrap();
assert_eq!(auth_by_kp1.trust.expires_at, far_future * 2);
graph.add(cert2, cur_time).unwrap();
let node2 = graph.get(key_pair2.public()).unwrap().unwrap();
let auth_by_kp1 = node2
.authorizations()
.find(|a| a.issued_by == key_pair1.public())
.unwrap();
assert_eq!(auth_by_kp1.trust.expires_at, far_far_future * 2);
}
#[test]
fn test_one_cert_in_graph() {
let (key_pairs, cert1) = generate_cert_with_len(10, HashMap::new()).unwrap();
let last_trust = cert1.chain[9].clone();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
let root_pk = key_pairs[0].public();
graph.add_root_weight(root_pk.into(), 1).unwrap();
graph.add(cert1, current_time()).unwrap();
let w1 = graph.weight(key_pairs[0].public()).unwrap().unwrap();
assert_eq!(w1, 1);
let w2 = graph.weight(key_pairs[1].public()).unwrap().unwrap();
assert_eq!(w2, 2);
let w3 = graph.weight(key_pairs[9].public()).unwrap().unwrap();
assert_eq!(w3, 10);
let node = graph.get(key_pairs[9].public()).unwrap().unwrap();
let auths: Vec<&Auth> = node.authorizations().collect();
assert_eq!(auths.len(), 1);
assert_eq!(auths[0].trust, last_trust);
}
#[test]
fn test_cycles_in_graph() {
let key_pair1 = KeyPair::generate_ed25519();
let key_pair2 = KeyPair::generate_ed25519();
let key_pair3 = KeyPair::generate_ed25519();
let mut chain_keys = HashMap::new();
chain_keys.insert(3, key_pair1.clone());
chain_keys.insert(5, key_pair2.clone());
chain_keys.insert(7, key_pair3.clone());
let (key_pairs1, cert1) = generate_cert_with_len(10, chain_keys).unwrap();
let mut chain_keys = HashMap::new();
chain_keys.insert(7, key_pair1.clone());
chain_keys.insert(6, key_pair2.clone());
chain_keys.insert(5, key_pair3.clone());
let (key_pairs2, cert2) = generate_cert_with_len(10, chain_keys).unwrap();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
let root1_pk = key_pairs1[0].public();
let root2_pk = key_pairs2[0].public();
graph.add_root_weight(root1_pk.into(), 1).unwrap();
graph.add_root_weight(root2_pk.into(), 0).unwrap();
let last_pk1 = cert1.chain[9].issued_for.clone();
let last_pk2 = cert2.chain[9].issued_for.clone();
graph.add(cert1, current_time()).unwrap();
graph.add(cert2, current_time()).unwrap();
let revoke1 = Revoke::create(&key_pairs1[3], key_pairs1[4].public(), current_time());
graph.revoke(revoke1).unwrap();
let revoke2 = Revoke::create(&key_pairs2[5], key_pairs2[6].public(), current_time());
graph.revoke(revoke2).unwrap();
let w1 = graph.weight(key_pair1.public()).unwrap().unwrap();
// all upper trusts are revoked for this public key
let w2 = graph.weight(key_pair2.public()).unwrap();
let w3 = graph.weight(key_pair3.public()).unwrap().unwrap();
let w_last1 = graph.weight(last_pk1).unwrap().unwrap();
let w_last2 = graph.weight(last_pk2).unwrap().unwrap();
assert_eq!(w1, 4);
assert_eq!(w2.is_none(), true);
assert_eq!(w3, 5);
assert_eq!(w_last1, 7);
assert_eq!(w_last2, 6);
}
#[test]
fn test_get_one_cert() {
let (key_pairs, cert) = generate_cert_with_len(5, HashMap::new()).unwrap();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
let root1_pk = key_pairs[0].public();
graph.add_root_weight(root1_pk.clone().into(), 1).unwrap();
graph.add(cert.clone(), current_time()).unwrap();
let certs = graph
.get_all_certs(key_pairs.last().unwrap().public(), &[root1_pk])
.unwrap();
assert_eq!(certs.len(), 1);
assert_eq!(certs[0], cert);
}
#[test]
fn test_chain_from_root_to_another_root() {
let (_, cert) = generate_cert_with_len(6, HashMap::new()).unwrap();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
// add first and last trusts as roots
graph
.add_root_weight(cert.chain[0].clone().issued_for.into(), 1)
.unwrap();
graph
.add_root_weight(cert.chain[3].clone().issued_for.into(), 1)
.unwrap();
graph
.add_root_weight(cert.chain[5].clone().issued_for.into(), 1)
.unwrap();
graph.add(cert.clone(), current_time()).unwrap();
let t = cert.chain[5].clone();
let certs = graph.get_all_certs(t.issued_for, &[]).unwrap();
assert_eq!(certs.len(), 1);
}
#[test]
fn test_find_certs() {
let key_pair1 = KeyPair::generate_ed25519();
let key_pair2 = KeyPair::generate_ed25519();
let key_pair3 = KeyPair::generate_ed25519();
let mut chain_keys = HashMap::new();
chain_keys.insert(2, key_pair1.clone());
chain_keys.insert(3, key_pair2.clone());
chain_keys.insert(4, key_pair3.clone());
let (key_pairs1, cert1) = generate_cert_with_len(5, chain_keys).unwrap();
let mut chain_keys = HashMap::new();
chain_keys.insert(4, key_pair1.clone());
chain_keys.insert(3, key_pair2.clone());
chain_keys.insert(2, key_pair3.clone());
let (key_pairs2, cert2) = generate_cert_with_len(5, chain_keys).unwrap();
let mut chain_keys = HashMap::new();
chain_keys.insert(3, key_pair1.clone());
chain_keys.insert(4, key_pair2.clone());
chain_keys.insert(2, key_pair3.clone());
let (key_pairs3, cert3) = generate_cert_with_len(5, chain_keys).unwrap();
let st = InMemoryStorage::new();
let mut graph = TrustGraph::new(st);
let root1_pk = key_pairs1[0].public();
let root2_pk = key_pairs2[0].public();
let root3_pk = key_pairs3[0].public();
graph.add_root_weight(root1_pk.clone().into(), 1).unwrap();
graph.add_root_weight(root2_pk.clone().into(), 0).unwrap();
graph.add_root_weight(root3_pk.clone().into(), 0).unwrap();
graph.add(cert1, current_time()).unwrap();
graph.add(cert2, current_time()).unwrap();
graph.add(cert3, current_time()).unwrap();
let roots_values = [root1_pk, root2_pk, root3_pk];
let certs1 = graph
.get_all_certs(key_pair1.public(), &roots_values)
.unwrap();
let lenghts1: Vec<usize> = certs1.iter().map(|c| c.chain.len()).collect();
let check_lenghts1: Vec<usize> = vec![3, 4, 4, 5, 5];
assert_eq!(lenghts1, check_lenghts1);
let certs2 = graph
.get_all_certs(key_pair2.public(), &roots_values)
.unwrap();
let lenghts2: Vec<usize> = certs2.iter().map(|c| c.chain.len()).collect();
let check_lenghts2: Vec<usize> = vec![4, 4, 4, 5, 5];
assert_eq!(lenghts2, check_lenghts2);
let certs3 = graph
.get_all_certs(key_pair3.public(), &roots_values)
.unwrap();
let lenghts3: Vec<usize> = certs3.iter().map(|c| c.chain.len()).collect();
let check_lenghts3: Vec<usize> = vec![3, 3, 5];
assert_eq!(lenghts3, check_lenghts3);
} }
} }

View File

@ -1,126 +1,28 @@
use crate::public_key_hashable::PublicKeyHashable as PK; use crate::public_key_hashable::PublicKeyHashable as PK;
use crate::revoke::Revoke; use crate::revoke::Revoke;
use crate::trust_graph::Weight; use crate::trust_graph::WeightFactor;
use crate::trust_graph_storage::InMemoryStorageError::RevokeError; use crate::trust_relation::{Auth, TrustRelation};
use crate::trust_node::{Auth, TrustNode};
use fluence_keypair::public_key::PublicKey;
use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::time::Duration; use std::time::Duration;
use thiserror::Error as ThisError;
pub trait StorageError: std::error::Error + Display {} pub trait StorageError: std::error::Error + Display {}
pub trait Storage { pub trait Storage {
type Error: StorageError + 'static; type Error: StorageError + 'static;
fn get(&self, pk: &PK) -> Result<Option<TrustNode>, Self::Error>; fn get_relation(
fn insert(&mut self, pk: PK, node: TrustNode) -> Result<(), Self::Error>; &self,
issued_for: &PK,
issued_by: &PK,
) -> Result<Option<TrustRelation>, Self::Error>;
fn get_root_weight(&self, pk: &PK) -> Result<Option<Weight>, Self::Error>; fn get_authorizations(&self, pk: &PK) -> Result<Vec<Auth>, Self::Error>;
fn add_root_weight(&mut self, pk: PK, weight: Weight) -> Result<(), Self::Error>; fn insert(&mut self, node: TrustRelation) -> Result<(), Self::Error>;
fn get_root_weight_factor(&self, pk: &PK) -> Result<Option<WeightFactor>, Self::Error>;
fn add_root_weight_factor(&mut self, pk: PK, weight: WeightFactor) -> Result<(), Self::Error>;
fn root_keys(&self) -> Result<Vec<PK>, Self::Error>; fn root_keys(&self) -> Result<Vec<PK>, Self::Error>;
fn revoke(&mut self, pk: &PK, revoke: Revoke) -> Result<(), Self::Error>; fn revoke(&mut self, revoke: Revoke) -> Result<(), Self::Error>;
fn update_auth( fn update_auth(&mut self, auth: Auth, cur_time: Duration) -> Result<(), Self::Error>;
&mut self, fn remove_expired(&mut self, current_time: Duration) -> Result<(), Self::Error>;
pk: &PK,
auth: Auth,
issued_for: &PublicKey,
cur_time: Duration,
) -> Result<(), Self::Error>;
}
#[derive(Debug, Default)]
pub struct InMemoryStorage {
nodes: HashMap<PK, TrustNode>,
root_weights: HashMap<PK, Weight>,
}
impl InMemoryStorage {
#[allow(dead_code)]
pub fn new_in_memory(root_weights: Vec<(PublicKey, Weight)>) -> Self {
let root_weights = root_weights
.into_iter()
.map(|(k, w)| (k.into(), w))
.collect();
Self {
nodes: HashMap::new(),
root_weights,
}
}
#[allow(dead_code)]
pub fn new() -> Self {
InMemoryStorage {
nodes: HashMap::new(),
root_weights: HashMap::new(),
}
}
}
#[derive(ThisError, Debug)]
pub enum InMemoryStorageError {
#[error("InMemoryStorageError::RevokeError {0:?}")]
RevokeError(String),
}
impl StorageError for InMemoryStorageError {}
impl Storage for InMemoryStorage {
type Error = InMemoryStorageError;
fn get(&self, pk: &PK) -> Result<Option<TrustNode>, Self::Error> {
Ok(self.nodes.get(pk).cloned())
}
fn insert(&mut self, pk: PK, node: TrustNode) -> Result<(), Self::Error> {
self.nodes.insert(pk, node);
Ok(())
}
fn get_root_weight(&self, pk: &PK) -> Result<Option<Weight>, Self::Error> {
Ok(self.root_weights.get(pk).cloned())
}
fn add_root_weight(&mut self, pk: PK, weight: Weight) -> Result<(), Self::Error> {
self.root_weights.insert(pk, weight);
Ok(())
}
fn root_keys(&self) -> Result<Vec<PK>, Self::Error> {
Ok(self.root_weights.keys().cloned().map(Into::into).collect())
}
fn revoke(&mut self, pk: &PK, revoke: Revoke) -> Result<(), Self::Error> {
match self.nodes.get_mut(&pk) {
Some(trust_node) => {
trust_node.update_revoke(revoke);
Ok(())
}
None => Err(RevokeError(
"There is no trust with such PublicKey".to_string(),
)),
}
}
fn update_auth(
&mut self,
pk: &PK,
auth: Auth,
issued_for: &PublicKey,
cur_time: Duration,
) -> Result<(), Self::Error> {
match self.nodes.get_mut(&pk) {
Some(trust_node) => {
trust_node.update_auth(auth);
Ok(())
}
None => {
let mut trust_node = TrustNode::new(issued_for.clone(), cur_time);
trust_node.update_auth(auth);
self.nodes.insert(pk.clone(), trust_node);
Ok(())
}
}
}
} }

76
src/trust_relation.rs Normal file
View File

@ -0,0 +1,76 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::revoke::Revoke;
use crate::trust::Trust;
use failure::_core::time::Duration;
use fluence_keypair::public_key::PublicKey;
use fluence_keypair::Signature;
use serde::{Deserialize, Serialize};
/// Represents who give a trust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Auth {
/// proof of this authorization
pub trust: Trust,
/// the issuer of this authorization
pub issued_by: PublicKey,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TrustRelation {
Auth(Auth),
Revoke(Revoke),
}
impl TrustRelation {
/// Returns timestamp of when this relation was created
pub fn issued_at(&self) -> Duration {
match self {
TrustRelation::Auth(auth) => auth.trust.issued_at,
TrustRelation::Revoke(revoke) => revoke.revoked_at,
}
}
/// Returns public key of the creator of this relation
pub fn issued_by(&self) -> &PublicKey {
match self {
TrustRelation::Auth(auth) => &auth.issued_by,
TrustRelation::Revoke(revoke) => &revoke.revoked_by,
}
}
pub fn issued_for(&self) -> &PublicKey {
match self {
TrustRelation::Auth(auth) => &auth.trust.issued_for,
TrustRelation::Revoke(revoke) => &revoke.pk,
}
}
pub fn expires_at(&self) -> Duration {
match self {
TrustRelation::Auth(auth) => auth.trust.expires_at,
TrustRelation::Revoke(_) => Duration::from_secs(0),
}
}
pub fn signature(&self) -> &Signature {
match self {
TrustRelation::Auth(auth) => &auth.trust.signature,
TrustRelation::Revoke(revoke) => &revoke.signature,
}
}
}

1781
wasm/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
[package]
name = "trust-graph-wasm"
version = "0.2.0"
authors = ["Fluence Labs"]
edition = "2018"
description = "trust graph wasm"
license = "Apache-2.0"
[[bin]]
name = "trust-graph"
path = "src/main.rs"
[dependencies]
trust-graph = { version = "0.2.1", path = "../" }
fluence-identity = { version = "0.2.1", path = "../identity" }
log = "0.4.8"
fluence = { version = "0.2.18", features = ["logger"] }
anyhow = "1.0.31"
boolinator = "2.4.0"
once_cell = "1.4.1"
parking_lot = "0.11.1"
fce-sqlite-connector = "0.1.3"
serde_json = "1.0"
bs58 = "0.3.1"
rmp-serde = "0.15.0"
bincode = "1.3.1"
serde_bencode = "^0.2.3"
thiserror = "1.0.23"

View File

@ -1,15 +0,0 @@
modules_dir = "artifacts/"
[[module]]
name = "sqlite3"
mem_pages_count = 100
logger_enabled = false
[[module]]
name = "trust-graph"
mem_pages_count = 1
logger_enabled = true
[module.wasi]
preopened_files = ["/tmp"]
mapped_dirs = { "tmp" = "/tmp" }

View File

@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
fce build
rm artifacts/trust-graph.wasm
mv -f target/wasm32-wasi/debug/trust-graph.wasm artifacts/
RUST_LOG="info" fce-repl Config.toml

View File

@ -1,101 +0,0 @@
use fluence::fce;
use fluence_identity::public_key::PKError;
use fluence_identity::signature::SignatureError;
use fluence_identity::{PublicKey, Signature};
use std::convert::TryFrom;
use std::time::Duration;
use thiserror::Error as ThisError;
#[derive(ThisError, Debug)]
pub enum DtoConversionError {
#[error("Cannot convert base58 string to bytes: {0}")]
Base58Error(
#[from]
#[source]
bs58::decode::Error,
),
#[error("Cannot convert string to PublicKey: {0}")]
PublicKeyDecodeError(
#[from]
#[source]
PKError,
),
#[error("Cannot convert string to PublicKey: {0}")]
SignatureDecodeError(
#[from]
#[source]
SignatureError,
),
}
#[fce]
pub struct Certificate {
pub chain: Vec<Trust>,
}
impl From<trust_graph::Certificate> for Certificate {
fn from(c: trust_graph::Certificate) -> Self {
let chain: Vec<Trust> = c.chain.into_iter().map(|t| t.into()).collect();
return Certificate { chain };
}
}
impl TryFrom<Certificate> for trust_graph::Certificate {
type Error = DtoConversionError;
fn try_from(c: Certificate) -> Result<Self, Self::Error> {
let chain: Result<Vec<trust_graph::Trust>, DtoConversionError> = c
.chain
.into_iter()
.map(|t| trust_graph::Trust::try_from(t))
.collect();
let chain = chain?;
return Ok(trust_graph::Certificate { chain });
}
}
#[fce]
pub struct Trust {
/// For whom this certificate is issued, base58
pub issued_for: String,
/// Expiration date of a trust, in secs
pub expires_at: u64,
/// Signature of a previous trust in a chain.
/// Signature is self-signed if it is a root trust, base58
pub signature: String,
/// Creation time of a trust, in secs
pub issued_at: u64,
}
impl TryFrom<Trust> for trust_graph::Trust {
type Error = DtoConversionError;
fn try_from(t: Trust) -> Result<Self, Self::Error> {
let issued_for = PublicKey::from_base58(&t.issued_for)?;
let signature = bs58::decode(&t.signature).into_vec()?;
let signature = Signature::from_bytes(&signature)?;
let expires_at = Duration::from_secs(t.expires_at);
let issued_at = Duration::from_secs(t.issued_at);
return Ok(trust_graph::Trust {
issued_for,
expires_at,
signature,
issued_at,
});
}
}
impl From<trust_graph::Trust> for Trust {
fn from(t: trust_graph::Trust) -> Self {
let issued_for = bs58::encode(t.issued_for.to_bytes()).into_string();
let signature = bs58::encode(t.signature.to_bytes()).into_string();
let expires_at = t.expires_at.as_secs();
let issued_at = t.issued_at.as_secs();
return Trust {
issued_for,
expires_at,
signature,
issued_at,
};
}
}

View File

@ -1,22 +0,0 @@
use fluence::WasmLoggerBuilder;
mod dto;
mod results;
mod service_api;
mod service_impl;
mod storage_impl;
pub fn main() {
WasmLoggerBuilder::new()
.with_log_level(log::Level::Info)
.build()
.unwrap();
}
// only option for now is to copy tests from trust graph,
// change connector to sqlite and fix compilation -_-
// TODO: fix it
/*#[cfg(test)]
mod tests {
}*/

View File

@ -1,93 +0,0 @@
use crate::dto::Certificate;
use crate::service_impl::ServiceError;
use fluence::fce;
#[fce]
pub struct InsertResult {
pub ret_code: u32,
pub error: String,
}
impl From<Result<(), ServiceError>> for InsertResult {
fn from(result: Result<(), ServiceError>) -> Self {
match result {
Ok(()) => InsertResult {
ret_code: 0,
error: "".to_string(),
},
Err(e) => InsertResult {
ret_code: 1,
error: format!("{}", e),
},
}
}
}
#[fce]
pub struct WeightResult {
pub ret_code: u32,
pub weight: Vec<u32>,
pub error: String,
}
impl From<Result<Option<u32>, ServiceError>> for WeightResult {
fn from(result: Result<Option<u32>, ServiceError>) -> Self {
match result {
Ok(wo) => WeightResult {
ret_code: 0,
weight: wo.map(|w| vec![w]).unwrap_or(vec![]),
error: "".to_string(),
},
Err(e) => WeightResult {
ret_code: 1,
weight: vec![],
error: format!("{}", e),
},
}
}
}
#[fce]
pub struct AllCertsResult {
pub ret_code: u32,
pub certificates: Vec<Certificate>,
pub error: String,
}
impl From<Result<Vec<Certificate>, ServiceError>> for AllCertsResult {
fn from(result: Result<Vec<Certificate>, ServiceError>) -> Self {
match result {
Ok(certs) => AllCertsResult {
ret_code: 0,
certificates: certs,
error: "".to_string(),
},
Err(e) => AllCertsResult {
ret_code: 1,
certificates: vec![],
error: format!("{}", e),
},
}
}
}
#[fce]
pub struct AddRootResult {
pub ret_code: u32,
pub error: String,
}
impl From<Result<(), ServiceError>> for AddRootResult {
fn from(result: Result<(), ServiceError>) -> Self {
match result {
Ok(()) => AddRootResult {
ret_code: 0,
error: "".to_string(),
},
Err(e) => AddRootResult {
ret_code: 1,
error: format!("{}", e),
},
}
}
}

View File

@ -1,75 +0,0 @@
use crate::dto::Certificate;
use crate::results::{AddRootResult, AllCertsResult, InsertResult, WeightResult};
use crate::service_impl::{
add_root_impl, get_all_certs_impl, get_weight_impl, insert_cert_impl, insert_cert_impl_raw,
};
use fluence::{fce, CallParameters};
#[fce]
/// add a certificate in string representation to trust graph if it is valid
/// see `trust_graph::Certificate` class for string encoding/decoding
// TODO change `current_time` to time service
fn insert_cert_raw(certificate: String, current_time: u64) -> InsertResult {
insert_cert_impl_raw(certificate, current_time).into()
}
#[fce]
/// add a certificate in JSON representation to trust graph if it is valid
/// see `dto::Certificate` class for structure
fn insert_cert(certificate: Certificate, current_time: u64) -> InsertResult {
insert_cert_impl(certificate, current_time).into()
}
#[fce]
fn get_weight(public_key: String) -> WeightResult {
get_weight_impl(public_key).into()
}
#[fce]
fn get_all_certs(issued_for: String) -> AllCertsResult {
get_all_certs_impl(issued_for).into()
}
#[fce]
/// could add only a host of a trust graph service
fn add_root(pk: String, weight: u32) -> AddRootResult {
let call_parameters: CallParameters = fluence::get_call_parameters();
let init_peer_id = call_parameters.init_peer_id.clone();
if call_parameters.host_id == init_peer_id {
add_root_impl(pk, weight).into()
} else {
return AddRootResult {
ret_code: 1,
error: "Root could add only a host of trust graph service".to_string(),
};
}
}
// TODO rewrite test after #[fce_test] will be implemented
// #[fce]
// fn test() -> String {
// let mut tg = get_data().lock();
//
// let root_kp = KeyPair::generate();
// let root_kp2 = KeyPair::generate();
// let second_kp = KeyPair::generate();
//
// let expires_at = Duration::new(15, 15);
// let issued_at = Duration::new(5, 5);
//
// let cert = trust_graph::Certificate::issue_root(
// &root_kp,
// second_kp.public_key(),
// expires_at,
// issued_at,
// );
// tg.add_root_weight(root_kp.public().into(), 0).unwrap();
// tg.add_root_weight(root_kp2.public().into(), 1).unwrap();
// tg.add(cert, Duration::new(10, 10)).unwrap();
//
// let a = tg.get(second_kp.public_key()).unwrap();
// let str = format!("{:?}", a);
// log::info!("{}", &str);
//
// str
// }

View File

@ -1,86 +0,0 @@
use crate::dto::{Certificate, DtoConversionError};
use crate::storage_impl::get_data;
use fluence_identity::public_key::PKError;
use fluence_identity::PublicKey;
use std::convert::{Into, TryInto};
use std::str::FromStr;
use std::time::Duration;
use thiserror::Error as ThisError;
use trust_graph::{CertificateError, TrustGraphError};
#[derive(ThisError, Debug)]
pub enum ServiceError {
#[error("{0}")]
PublicKeyDecodeError(
#[from]
#[source]
PKError,
),
#[error("{0}")]
TGError(
#[from]
#[source]
TrustGraphError,
),
#[error("{0}")]
CertError(
#[from]
#[source]
CertificateError,
),
#[error("{0}")]
DtoError(
#[from]
#[source]
DtoConversionError,
),
}
pub fn get_weight_impl(public_key: String) -> Result<Option<u32>, ServiceError> {
let tg = get_data().lock();
let public_key = string_to_public_key(public_key)?;
let weight = tg.weight(public_key)?;
Ok(weight)
}
fn add_cert(certificate: trust_graph::Certificate, duration: u64) -> Result<(), ServiceError> {
let duration = Duration::from_millis(duration);
let mut tg = get_data().lock();
tg.add(certificate, duration)?;
Ok(())
}
pub fn insert_cert_impl_raw(certificate: String, duration: u64) -> Result<(), ServiceError> {
let certificate = trust_graph::Certificate::from_str(&certificate)?;
add_cert(certificate, duration)?;
Ok(())
}
fn string_to_public_key(public_key: String) -> Result<PublicKey, ServiceError> {
let public_key = PublicKey::from_base58(&public_key)?;
Ok(public_key)
}
pub fn get_all_certs_impl(issued_for: String) -> Result<Vec<Certificate>, ServiceError> {
let tg = get_data().lock();
let public_key = string_to_public_key(issued_for)?;
let certs = tg.get_all_certs(public_key, &[])?;
Ok(certs.into_iter().map(|c| c.into()).collect())
}
pub fn insert_cert_impl(certificate: Certificate, duration: u64) -> Result<(), ServiceError> {
let certificate: trust_graph::Certificate = certificate.try_into()?;
add_cert(certificate, duration)?;
Ok(())
}
pub fn add_root_impl(pk: String, weight: u32) -> Result<(), ServiceError> {
let mut tg = get_data().lock();
let pk = PublicKey::from_base58(&pk)?.into();
tg.add_root_weight(pk, weight)?;
Ok(())
}

View File

@ -1,229 +0,0 @@
// store list of trusts
// check if trust is already in list before adding
// if there is an older trust - don't add received trust
use crate::storage_impl::SQLiteStorageError::{
PublcKeyNotFound, PublicKeyConversion, PublicKeyFromStr, TrustNodeConversion,
WeightConversionDB,
};
use core::convert::TryFrom;
use fce_sqlite_connector;
use fce_sqlite_connector::Connection;
use fce_sqlite_connector::Error as InternalSqliteError;
use fce_sqlite_connector::Value;
use fluence_identity::public_key::PublicKey;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use rmp_serde::decode::Error as RmpDecodeError;
use rmp_serde::encode::Error as RmpEncodeError;
use std::convert::From;
use std::str::FromStr;
use std::time::Duration;
use thiserror::Error as ThisError;
use trust_graph::{
Auth, PublicKeyHashable as PK, Revoke, Storage, StorageError, TrustGraph, TrustNode, Weight,
};
static INSTANCE: OnceCell<Mutex<TrustGraph<SQLiteStorage>>> = OnceCell::new();
pub fn get_data() -> &'static Mutex<TrustGraph<SQLiteStorage>> {
INSTANCE.get_or_init(|| {
let db_path = "/tmp/users123123.sqlite";
let connection = fce_sqlite_connector::open(db_path).unwrap();
let init_sql = "CREATE TABLE IF NOT EXISTS trustnodes(
public_key TEXT PRIMARY KEY,
trustnode BLOB NOT NULL
);
CREATE TABLE IF NOT EXISTS roots(
public_key TEXT,
weight INTEGER
);";
connection.execute(init_sql).expect("cannot connect to db");
Mutex::new(TrustGraph::new(SQLiteStorage::new(connection)))
})
}
pub struct SQLiteStorage {
connection: Connection,
}
impl SQLiteStorage {
pub fn new(connection: Connection) -> SQLiteStorage {
SQLiteStorage { connection }
}
}
#[derive(ThisError, Debug)]
pub enum SQLiteStorageError {
#[error("{0}")]
SQLiteError(
#[from]
#[source]
InternalSqliteError,
),
#[error("{0}")]
PublicKeyFromStr(String),
#[error("{0}")]
EncodeError(
#[from]
#[source]
RmpEncodeError,
),
#[error("{0}")]
DecodeError(
#[from]
#[source]
RmpDecodeError,
),
#[error("Cannot convert weight as integer from DB")]
WeightConversionDB,
#[error("Cannot convert public key as binary from DB")]
PublicKeyConversion,
#[error("Cannot convert trust node as binary from DB")]
TrustNodeConversion,
#[error("Cannot revoke. There is no trust with such PublicKey")]
PublcKeyNotFound,
}
impl From<SQLiteStorageError> for String {
fn from(err: SQLiteStorageError) -> Self {
err.into()
}
}
impl StorageError for SQLiteStorageError {}
impl Storage for SQLiteStorage {
type Error = SQLiteStorageError;
fn get(&self, pk: &PK) -> Result<Option<TrustNode>, Self::Error> {
let mut cursor = self
.connection
.prepare("SELECT trustnode FROM trustnodes WHERE public_key = ?")?
.cursor();
cursor.bind(&[Value::String(format!("{}", pk))])?;
match cursor.next().unwrap() {
Some(r) => {
log::info!("row: {:?}", r);
let tn_bin: &[u8] = r[0].as_binary().ok_or(TrustNodeConversion)?;
log::info!("binary: {:?}", tn_bin);
let trust_node: TrustNode = rmp_serde::from_read_ref(tn_bin)?;
log::info!("trustnode: {:?}", trust_node);
Ok(Some(trust_node))
}
None => Ok(None),
}
}
fn insert(&mut self, pk: PK, node: TrustNode) -> Result<(), Self::Error> {
let mut cursor = self
.connection
.prepare("INSERT OR REPLACE INTO trustnodes VALUES (?, ?)")?
.cursor();
let tn_vec = rmp_serde::to_vec(&node)?;
log::info!("insert: {:?}", tn_vec);
cursor.bind(&[Value::String(format!("{}", pk)), Value::Binary(tn_vec)])?;
cursor.next()?;
Ok({})
}
fn get_root_weight(&self, pk: &PK) -> Result<Option<Weight>, Self::Error> {
let mut cursor = self
.connection
.prepare("SELECT public_key,weight FROM roots WHERE public_key = ?")?
.cursor();
cursor.bind(&[Value::String(format!("{}", pk))])?;
if let Some(row) = cursor.next()? {
log::info!("row: {:?}", row);
let w = u32::try_from(row[1].as_integer().ok_or(WeightConversionDB)?)
.map_err(|_e| WeightConversionDB)?;
Ok(Some(w))
} else {
Ok(None)
}
}
fn add_root_weight(&mut self, pk: PK, weight: Weight) -> Result<(), Self::Error> {
log::info!("add root: {} weight: {}", pk, weight);
let mut cursor = self
.connection
.prepare("INSERT OR REPLACE INTO roots VALUES (?, ?)")?
.cursor();
cursor.bind(&[
Value::String(format!("{}", pk)),
Value::Integer(i64::from(weight)),
])?;
cursor.next()?;
Ok({})
}
fn root_keys(&self) -> Result<Vec<PK>, Self::Error> {
let mut cursor = self
.connection
.prepare("SELECT public_key,weight FROM roots")?
.cursor();
let mut roots = vec![];
while let Some(row) = cursor.next()? {
log::info!("row: {:?}", row);
let pk = row[0].as_string().ok_or(PublicKeyConversion)?;
let pk: PK = PK::from_str(pk).map_err(|e| PublicKeyFromStr(e.to_string()))?;
roots.push(pk)
}
Ok(roots)
}
fn revoke(&mut self, pk: &PK, revoke: Revoke) -> Result<(), Self::Error> {
match self.get(&pk)? {
Some(mut trust_node) => {
trust_node.update_revoke(revoke);
self.insert(pk.clone(), trust_node)?;
Ok(())
}
None => Err(PublcKeyNotFound),
}
}
fn update_auth(
&mut self,
pk: &PK,
auth: Auth,
issued_for: &PublicKey,
cur_time: Duration,
) -> Result<(), Self::Error> {
match self.get(&pk)? {
Some(mut trust_node) => {
trust_node.update_auth(auth);
self.insert(pk.clone(), trust_node)
}
None => {
let mut trust_node = TrustNode::new(issued_for.clone(), cur_time);
trust_node.update_auth(auth);
self.insert(pk.clone(), trust_node)
}
}
}
}