wip
This commit is contained in:
parent
9c8fcfd768
commit
78ce899036
|
@ -466,6 +466,7 @@ version = "0.0.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"cryptoxide",
|
||||
"ed25519-dalek",
|
||||
"futures",
|
||||
"hex",
|
||||
|
@ -583,6 +584,12 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptoxide"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "382ce8820a5bb815055d3553a610e8cb542b2d767bbacea99038afda96cd760d"
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
|
@ -735,6 +742,7 @@ dependencies = [
|
|||
"rand_core",
|
||||
"serde",
|
||||
"sha2",
|
||||
"signature",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -1568,6 +1576,7 @@ dependencies = [
|
|||
"libp2p-quic",
|
||||
"libp2p-relay",
|
||||
"libp2p-rendezvous",
|
||||
"libp2p-request-response",
|
||||
"libp2p-swarm",
|
||||
"libp2p-tcp",
|
||||
"libp2p-upnp",
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -8,13 +8,22 @@ publish = false
|
|||
[package.metadata.release]
|
||||
release = false
|
||||
|
||||
[[bin]] # Bin to run the server
|
||||
name = "cli"
|
||||
path = "app/cli.rs"
|
||||
#
|
||||
# [[bin]] # Bin to run the client
|
||||
# name = "client"
|
||||
# path = "app/client.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.95"
|
||||
clap = { version = "4.5.18", features = ["derive"] }
|
||||
ed25519-dalek = "2.1.1"
|
||||
cryptoxide = "0.4.4"
|
||||
ed25519-dalek = { version = "2.1.1", features = ["digest"] }
|
||||
futures = "0.3.30"
|
||||
hex = "0.4.3"
|
||||
libp2p = { version = "0.54.1", features = ["tokio", "gossipsub", "mdns", "noise", "macros", "tcp", "yamux", "quic", "identify", "ping", "relay", "dcutr", "rendezvous", "kad"] }
|
||||
libp2p = { version = "0.54.1", features = ["tokio", "gossipsub", "mdns", "noise", "macros", "tcp", "yamux", "quic", "identify", "ping", "relay", "dcutr", "rendezvous", "kad", "request-response"] }
|
||||
libp2p-identity = { version = "0.2.9", features = ["ed25519", "peerid"] }
|
||||
owo-colors = "4.1.0"
|
||||
quick-protobuf = "0.8.1"
|
||||
|
|
66
README.md
66
README.md
|
@ -57,29 +57,7 @@ An ephemeral key consists of the following properties:
|
|||
1. Expires at
|
||||
1. Signature
|
||||
|
||||
### Actions
|
||||
|
||||
#### Sign
|
||||
|
||||
A sign action is the "standard" action that will occur.
|
||||
|
||||
A `sign` request has the following fields
|
||||
|
||||
1. Verification key
|
||||
2. Payload
|
||||
3. Signature
|
||||
|
||||
The request is deemed valid
|
||||
|
||||
1. The verification key exists in the database
|
||||
1. The verification key has not expired
|
||||
1. The signature is valid wrt the payload and verification key
|
||||
1. The payload is "sensible" (TBC) - this is context specific.
|
||||
|
||||
The response to a valid request
|
||||
|
||||
1. The signature produced by the persistent key associated to the verification
|
||||
key for the same payload.
|
||||
### Signing Server Actions
|
||||
|
||||
#### Add
|
||||
|
||||
|
@ -103,6 +81,28 @@ database with the obvious fields.
|
|||
|
||||
The response is either `Ok()` or `Error("help message!")`
|
||||
|
||||
#### Sign
|
||||
|
||||
A sign action is the "standard" action that will occur.
|
||||
|
||||
A `sign` request has the following fields
|
||||
|
||||
1. Verification key
|
||||
2. Payload
|
||||
3. Signature
|
||||
|
||||
The request is deemed valid
|
||||
|
||||
1. The verification key exists in the database
|
||||
1. The verification key has not expired
|
||||
1. The signature is valid wrt the payload and verification key
|
||||
1. The payload is "sensible" (TBC) - this is context specific.
|
||||
|
||||
The response to a valid request
|
||||
|
||||
1. The signature produced by the persistent key associated to the verification
|
||||
key for the same payload.
|
||||
|
||||
#### Revoke
|
||||
|
||||
A revoke is the opposite of add. It is also performed exclusively by admin.
|
||||
|
@ -123,6 +123,22 @@ database.
|
|||
|
||||
The response is either `Ok()` or `Error("help message!")`
|
||||
|
||||
#### SIMPLIFICATION
|
||||
|
||||
The `add` and `revoke` endpoints are needed only by admin, and infrequently, or
|
||||
in case of emergency. In contrast, `sign` is needed with relatively high
|
||||
frequency, and called by less trusted machines.
|
||||
|
||||
Thus, we assume that admin accesses the machine directly and simply adds the
|
||||
ephemeral keys to the db. This does not require a valid signature - if the pub
|
||||
key is registered, then a signature request is honored.
|
||||
|
||||
### Client server actions
|
||||
|
||||
#### Demo task
|
||||
|
||||
Some way to trigger the client service to make a request to the server.
|
||||
|
||||
## Notes
|
||||
|
||||
### sqlx
|
||||
|
@ -138,3 +154,7 @@ sqlx migration run
|
|||
```
|
||||
|
||||
The database must be initialised before starting the application.
|
||||
|
||||
### libp2p
|
||||
|
||||
TODO
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
use std::env;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use ed25519_dalek::{Signature, SigningKey};
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
|
||||
use cll2v0::{db, keys};
|
||||
|
||||
/// cll2v0 is a playground for rust libraries.
|
||||
/// This is signing service.
|
||||
#[derive(Parser)]
|
||||
#[command(arg_required_else_help(true), version, about)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
cmd: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
/// Add new ephemeral key to an existing persistent key
|
||||
Add {
|
||||
ephemeral_key: String,
|
||||
persistent_key: String,
|
||||
expires_at: i64,
|
||||
},
|
||||
/// Revoke an existing ephemeral key
|
||||
Revoke { ephemeral_key: String },
|
||||
/// Check persistent keys are available and display
|
||||
Check,
|
||||
/// Gen from seed some sensible ephemeral key args to be used with `add`
|
||||
Gen { seed: u8 },
|
||||
/// Sign a message
|
||||
Sign {
|
||||
signing_key: String,
|
||||
message: String,
|
||||
},
|
||||
/// Verify a message
|
||||
Verify {
|
||||
verifying_key: String,
|
||||
message: String,
|
||||
signature: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let keys_path = env::var("CLL2V0_KEYS").expect("Expect `CLL2V0_KEYS` to be set");
|
||||
let keychain = keys::mk_keychain(&keys_path);
|
||||
|
||||
let db_url = env::var("DATABASE_URL").expect("Expect `DATABASE_URL` to be set");
|
||||
let pool = SqlitePool::connect(&db_url).await?;
|
||||
for vkey_bytes in keychain.keys() {
|
||||
let vkey = keys::from_bytes(vkey_bytes.into())?;
|
||||
let _ = db::add_persistent_key(&pool, &vkey).await;
|
||||
}
|
||||
|
||||
match args.cmd {
|
||||
Some(Command::Add {
|
||||
ephemeral_key,
|
||||
persistent_key,
|
||||
expires_at,
|
||||
}) => {
|
||||
println!("Adding new ephemeral key '{ephemeral_key}'");
|
||||
let e = keys::from_hex(&ephemeral_key).expect("cannot parse key");
|
||||
let p = keys::from_hex(&persistent_key).expect("cannot parse key");
|
||||
let res = db::add_ephemeral_key(&pool, &e, &p, expires_at).await?;
|
||||
println!("Added new ekey: {}", res);
|
||||
}
|
||||
Some(Command::Revoke { ephemeral_key }) => {
|
||||
let e = keys::from_hex(&ephemeral_key).expect("cannot parse key");
|
||||
let res = db::revoke_ephemeral_key(&pool, &e).await?;
|
||||
println!("Revoked key: {}", res);
|
||||
}
|
||||
Some(Command::Check) => {
|
||||
eprintln!("!Check skipped");
|
||||
let res = db::list_persistent_keys(&pool).await?;
|
||||
println!("Persistent keys:");
|
||||
res.into_iter()
|
||||
.for_each(|key| println!("{}", keys::to_hex(&key)))
|
||||
}
|
||||
Some(Command::Gen { seed }) => {
|
||||
let ekey = SigningKey::from_bytes(&[
|
||||
seed, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
]);
|
||||
println!("Ephemeral signing key:");
|
||||
println!("{}", keys::signing_key_to_hex(&ekey));
|
||||
let res = db::list_persistent_keys(&pool).await?;
|
||||
let idx = (seed as usize) % res.len();
|
||||
let pkey = res.get(idx);
|
||||
match pkey {
|
||||
None => eprintln!("No persistent keys found"),
|
||||
Some(pkey) => {
|
||||
let now: usize = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let twenty_mins_from_now = 20usize * 60 * 1000 + now;
|
||||
println!("Args");
|
||||
println!(
|
||||
"{} {} {}",
|
||||
keys::to_hex(&ekey.verifying_key()),
|
||||
keys::to_hex(pkey),
|
||||
twenty_mins_from_now
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Command::Sign {
|
||||
signing_key,
|
||||
message,
|
||||
}) => {
|
||||
let mut skey = keys::signing_key_from_hex(&signing_key).unwrap();
|
||||
let msg = hex::decode(message).unwrap();
|
||||
let sig = keys::sign(&mut skey, msg);
|
||||
println!("{}", hex::encode(sig.to_bytes()))
|
||||
}
|
||||
Some(Command::Verify {
|
||||
verifying_key,
|
||||
message,
|
||||
signature,
|
||||
}) => {
|
||||
let vkey = keys::from_hex(&verifying_key).unwrap();
|
||||
let msg = hex::decode(message).unwrap();
|
||||
let sig = Signature::from_bytes(
|
||||
&TryInto::<[u8; 64]>::try_into(hex::decode(signature).unwrap()).unwrap(),
|
||||
);
|
||||
let res = keys::verify(&vkey, &msg, &sig).unwrap();
|
||||
println!("{:?}", res);
|
||||
}
|
||||
None => {
|
||||
println!("See help");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
60
flake.lock
60
flake.lock
|
@ -1,5 +1,20 @@
|
|||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1736566337,
|
||||
"narHash": "sha256-SC0eDcZPqISVt6R0UfGPyQLrI0+BppjjtQ3wcSlk0oI=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "9172acc1ee6c7e1cbafc3044ff850c568c75a5a3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
|
@ -124,16 +139,16 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1728538411,
|
||||
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
||||
"owner": "NixOS",
|
||||
"lastModified": 1715447595,
|
||||
"narHash": "sha256-VsVAUQOj/cS1LCOmMjAGeRksXIAdPnFIjCQ0XLkCsT0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
||||
"rev": "062ca2a9370a27a35c524dc82d540e6e9824b652",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -143,20 +158,43 @@
|
|||
"flake-parts": "flake-parts",
|
||||
"git-hooks-nix": "git-hooks-nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay",
|
||||
"rust-flake": "rust-flake",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"rust-flake": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1736806612,
|
||||
"narHash": "sha256-WioA+Vk7suDK+Ek77rDlbuxV6WqwFt30JsKHrmDCSiU=",
|
||||
"owner": "juspay",
|
||||
"repo": "rust-flake",
|
||||
"rev": "b5f39885e2fcf137bfaf75decc077f9cca2bd984",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "juspay",
|
||||
"repo": "rust-flake",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
"nixpkgs": [
|
||||
"rust-flake",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735352767,
|
||||
"narHash": "sha256-3zXufMRWUdwmp8/BTmxVW/k4MyqsPjLnnt/IlQyZvhc=",
|
||||
"lastModified": 1736700680,
|
||||
"narHash": "sha256-9gmWIb8xsycWHEYpd2SiVIAZnUULX6Y+IMMZBcDUCQU=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "a16b9a7cac7f4d39a84234d62e91890370c57d76",
|
||||
"rev": "5d1865c0da63b4c949f383d982b6b43519946e8f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
34
flake.nix
34
flake.nix
|
@ -2,7 +2,7 @@
|
|||
description = "CL L2 V0";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
rust-flake.url = "github:juspay/rust-flake";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
git-hooks-nix.url = "github:cachix/git-hooks.nix";
|
||||
git-hooks-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
@ -13,17 +13,13 @@
|
|||
outputs = inputs:
|
||||
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
|
||||
imports = [
|
||||
./nix/rust.nix
|
||||
inputs.rust-flake.flakeModules.default
|
||||
inputs.rust-flake.flakeModules.nixpkgs
|
||||
inputs.git-hooks-nix.flakeModule
|
||||
inputs.treefmt-nix.flakeModule
|
||||
];
|
||||
flake = {
|
||||
# Put your original flake attributes here.
|
||||
};
|
||||
systems = [
|
||||
# systems for which you want to build the `perSystem` attributes
|
||||
"x86_64-linux"
|
||||
# ...
|
||||
];
|
||||
perSystem = {
|
||||
self',
|
||||
|
@ -34,6 +30,21 @@
|
|||
system,
|
||||
...
|
||||
}: {
|
||||
devShells.default = pkgs.mkShell {
|
||||
inputsFrom = [
|
||||
self'.devShells.rust
|
||||
config.pre-commit.devShell
|
||||
];
|
||||
shellHook = ''
|
||||
echo 1>&2 "Welcome to the development shell!"
|
||||
${config.pre-commit.installationScript}
|
||||
export DATABASE_URL="sqlite:./db/cll2v0.db";
|
||||
export CLL2V0_KEYS="db/keys";
|
||||
'';
|
||||
name = "cardano-lightning";
|
||||
packages = with pkgs; [
|
||||
];
|
||||
};
|
||||
treefmt = {
|
||||
projectRootFile = "flake.nix";
|
||||
flakeFormatter = true;
|
||||
|
@ -50,13 +61,8 @@
|
|||
};
|
||||
};
|
||||
pre-commit.settings.hooks.treefmt.enable = true;
|
||||
_module.args.pkgs = import inputs.nixpkgs {
|
||||
inherit system;
|
||||
overlays = [
|
||||
inputs.rust-overlay.overlays.default
|
||||
];
|
||||
config = {};
|
||||
};
|
||||
};
|
||||
flake = {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ CREATE TABLE IF NOT EXISTS persistent_keys
|
|||
|
||||
CREATE TABLE IF NOT EXISTS ephemeral_keys
|
||||
(
|
||||
id BLOB PRIMARY KEY NOT NULL,
|
||||
id BLOB PRIMARY KEY NOT NULL,
|
||||
persistent_key BLOB NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
signature BLOB NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
FOREIGN KEY(persistent_key) REFERENCES persistent_keys(id)
|
||||
);
|
||||
|
|
92
nix/rust.nix
92
nix/rust.nix
|
@ -1,92 +0,0 @@
|
|||
{
|
||||
perSystem = {
|
||||
config,
|
||||
self',
|
||||
inputs',
|
||||
pkgs,
|
||||
lib,
|
||||
system,
|
||||
...
|
||||
}: let
|
||||
osxDependencies = with pkgs;
|
||||
lib.optionals stdenv.isDarwin
|
||||
[
|
||||
darwin.apple_sdk.frameworks.Security
|
||||
darwin.apple_sdk.frameworks.CoreServices
|
||||
];
|
||||
|
||||
cargoTomlContents = builtins.readFile ../Cargo.toml;
|
||||
version = (builtins.fromTOML cargoTomlContents).package.version;
|
||||
echo-net = pkgs.rustPlatform.buildRustPackage {
|
||||
inherit version;
|
||||
name = "cll2v0";
|
||||
buildInputs = with pkgs; [openssl] ++ osxDependencies;
|
||||
nativeBuildInputs = with pkgs; [pkg-config openssl.dev];
|
||||
src = let
|
||||
baseDir = ./../.;
|
||||
fileHasAnySuffix = fileSuffixes: file: (lib.lists.any (s: lib.hasSuffix s file.name) fileSuffixes);
|
||||
rustFilter = basePath: (
|
||||
let
|
||||
mainFilter = lib.fileset.fileFilter (fileHasAnySuffix [".rs" ".toml"]) basePath;
|
||||
in
|
||||
lib.fileset.unions [mainFilter (basePath + "/Cargo.toml") (basePath + "/Cargo.lock")]
|
||||
);
|
||||
in
|
||||
pkgs.lib.fileset.toSource {
|
||||
root = baseDir;
|
||||
fileset = rustFilter baseDir;
|
||||
};
|
||||
cargoLock.lockFile = ../Cargo.lock;
|
||||
meta = {
|
||||
description = "Cardano Lightning L2 V0";
|
||||
homepage = "cardano-lightning.org";
|
||||
# license = licenses.mit;
|
||||
};
|
||||
};
|
||||
|
||||
packages = {
|
||||
echo-net = echo-net;
|
||||
default = packages.echo-net;
|
||||
};
|
||||
|
||||
# FIXME :: I don't know if this is necessary,
|
||||
# but I don't know to fix it.
|
||||
# overlays.default = final: prev: {echo-net = packages.echo-net;};
|
||||
|
||||
gitRev =
|
||||
if (builtins.hasAttr "rev" self')
|
||||
then self'.rev
|
||||
else "dirty";
|
||||
in {
|
||||
inherit packages; # overlays;
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs = [
|
||||
config.treefmt.build.wrapper
|
||||
];
|
||||
shellHook = ''
|
||||
export GIT_REVISION=${gitRev}
|
||||
${config.pre-commit.installationScript}
|
||||
'';
|
||||
buildInputs = with pkgs;
|
||||
[
|
||||
pkg-config
|
||||
openssl
|
||||
cargo-insta
|
||||
(pkgs.rust-bin.stable.latest.default.override {
|
||||
extensions = ["rust-src" "clippy" "rustfmt" "rust-analyzer"];
|
||||
# targets = [ "wasm32-unknown-unknown" ];
|
||||
})
|
||||
sqlx-cli
|
||||
sqlite
|
||||
# nodePackages_latest.nodejs
|
||||
# nodePackages_latest.typescript-language-server
|
||||
# cmake
|
||||
# wasm-pack
|
||||
# protobuf
|
||||
# sqlite
|
||||
]
|
||||
++ osxDependencies;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
[toolchain]
|
||||
channel = "stable"
|
||||
components = [ "rustfmt", "rustc-dev", "rust-analyzer", ]
|
||||
targets = [ "wasm32-unknown-unknown", "x86_64-unknown-linux-gnu" ]
|
||||
profile = "default"
|
|
@ -0,0 +1,74 @@
|
|||
use ed25519_dalek::VerifyingKey;
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
|
||||
use crate::keys;
|
||||
|
||||
pub async fn add_persistent_key(pool: &SqlitePool, vkey: &VerifyingKey) -> anyhow::Result<i64> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let b = keys::to_bytes(&vkey);
|
||||
let id = sqlx::query!(r#"INSERT INTO persistent_keys ( id ) VALUES ( ?1 )"#, b)
|
||||
.execute(&mut *conn)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub async fn add_ephemeral_key(
|
||||
pool: &SqlitePool,
|
||||
ephemeral: &VerifyingKey,
|
||||
persistent: &VerifyingKey,
|
||||
expires_at: i64,
|
||||
) -> anyhow::Result<i64> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let eph = keys::to_bytes(ephemeral);
|
||||
let per = keys::to_bytes(persistent);
|
||||
let res = sqlx::query!(
|
||||
r#" INSERT INTO ephemeral_keys ( id, persistent_key, expires_at) VALUES ( ?1, ?2, ?3 ) "#,
|
||||
eph,
|
||||
per,
|
||||
expires_at,
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn revoke_ephemeral_key(
|
||||
pool: &SqlitePool,
|
||||
ephemeral_key: &VerifyingKey,
|
||||
) -> anyhow::Result<u64> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let b = keys::to_bytes(ephemeral_key);
|
||||
let res = sqlx::query!(r#" DELETE FROM ephemeral_keys WHERE id = ? "#, b)
|
||||
.execute(&mut *conn)
|
||||
.await?
|
||||
.rows_affected();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn get_persistent_key(
|
||||
pool: &SqlitePool,
|
||||
ephemeral_key: &VerifyingKey,
|
||||
) -> anyhow::Result<VerifyingKey> {
|
||||
let b = keys::to_bytes(ephemeral_key);
|
||||
let rec = sqlx::query!(
|
||||
r#" SELECT persistent_key FROM ephemeral_keys WHERE id = ? AND expires_at > date(); "#,
|
||||
b,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
keys::from_bytes(rec.persistent_key)
|
||||
}
|
||||
|
||||
pub async fn list_persistent_keys(pool: &SqlitePool) -> anyhow::Result<Vec<VerifyingKey>> {
|
||||
let recs = sqlx::query!(r#" SELECT id FROM persistent_keys ORDER BY id "#)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let keys = recs
|
||||
.into_iter()
|
||||
.map(|b| keys::from_bytes(b.id).expect("illegal"))
|
||||
.collect::<Vec<VerifyingKey>>();
|
||||
Ok(keys)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
use cryptoxide::{blake2b::Blake2b, digest::Digest};
|
||||
|
||||
pub fn hash(msg: &Vec<u8>) -> [u8; 32] {
|
||||
let mut digest = [0u8; 32];
|
||||
let mut context = Blake2b::new(32);
|
||||
context.input(msg);
|
||||
context.result(&mut digest);
|
||||
digest
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
use std::{collections::HashMap, fs::read_to_string};
|
||||
|
||||
use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, VerifyingKey};
|
||||
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Result<VerifyingKey, anyhow::Error> {
|
||||
let arr: [u8; 32] = TryInto::try_into(bytes).expect("");
|
||||
Ok(VerifyingKey::from_bytes(&arr)?)
|
||||
}
|
||||
|
||||
pub fn to_bytes(key: &VerifyingKey) -> Vec<u8> {
|
||||
Into::<Vec<u8>>::into(key.to_bytes())
|
||||
}
|
||||
|
||||
pub fn from_hex(s: &str) -> Result<VerifyingKey, anyhow::Error> {
|
||||
from_bytes(hex::decode(s)?.try_into()?)
|
||||
}
|
||||
|
||||
pub fn to_hex(key: &VerifyingKey) -> String {
|
||||
hex::encode(key.to_bytes())
|
||||
}
|
||||
|
||||
pub fn signing_key_from_bytes(bytes: Vec<u8>) -> Result<SigningKey, anyhow::Error> {
|
||||
let arr: [u8; 32] = TryInto::try_into(bytes).expect("");
|
||||
Ok(SigningKey::from_bytes(&arr))
|
||||
}
|
||||
|
||||
pub fn signing_key_from_hex(s: &str) -> Result<SigningKey, anyhow::Error> {
|
||||
signing_key_from_bytes(hex::decode(s)?.try_into()?)
|
||||
}
|
||||
|
||||
pub fn signing_key_to_hex(key: &SigningKey) -> String {
|
||||
hex::encode(key.to_bytes())
|
||||
}
|
||||
|
||||
pub fn sign(key: &mut SigningKey, msg: Vec<u8>) -> Signature {
|
||||
key.try_sign(&msg).unwrap()
|
||||
}
|
||||
|
||||
pub fn verify(key: &VerifyingKey, msg: &Vec<u8>, sig: &Signature) -> Result<(), anyhow::Error> {
|
||||
let _ = key.verify_strict(msg, sig)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub type Keychain = HashMap<[u8; 32], SigningKey>;
|
||||
|
||||
pub fn mk_keychain(keys_path: &str) -> Keychain {
|
||||
let signing_keys = read_to_string(&keys_path).expect("Error reading file");
|
||||
let mut keychain: Keychain = HashMap::new();
|
||||
for key in signing_keys.lines() {
|
||||
let secret_key = hex::decode(key.trim()).unwrap().try_into().unwrap();
|
||||
let skey = SigningKey::from_bytes(&secret_key);
|
||||
let vkey = skey.verifying_key();
|
||||
keychain.insert(vkey.to_bytes(), skey);
|
||||
}
|
||||
keychain
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pub mod db;
|
||||
pub mod hash;
|
||||
pub mod keys;
|
||||
pub mod protobuf;
|
||||
pub mod server;
|
162
src/main.rs
162
src/main.rs
|
@ -1,162 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
|
||||
use hex;
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
cmd: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
Add {
|
||||
ephemeral_key: String,
|
||||
persistent_key: String,
|
||||
expires_at: i64,
|
||||
signature: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let keys_path = env::var("CLL2V0_KEYS").expect("Expect `CLL2V0_KEYS` to be set");
|
||||
// let db_url = env::var("CLL2V0_DB").expect("Expect `CLL2V0_DB` to be set");
|
||||
let db_url = env::var("DATABASE_URL").expect("Expect `DATABASE_URL` to be set");
|
||||
let signing_keys = read_to_string(&keys_path).expect("Error reading file");
|
||||
|
||||
let pool = SqlitePool::connect(&db_url).await?;
|
||||
|
||||
let mut keychain: HashMap<[u8; 32], SigningKey> = HashMap::new();
|
||||
for key in signing_keys.lines() {
|
||||
let secret_key = hex::decode(key.trim()).unwrap().try_into().unwrap();
|
||||
let skey = SigningKey::from_bytes(&secret_key);
|
||||
let vkey = skey.verifying_key();
|
||||
keychain.insert(vkey.to_bytes(), skey);
|
||||
let _ = add_persistent_key(&pool, vkey).await;
|
||||
}
|
||||
|
||||
match args.cmd {
|
||||
Some(Command::Add {
|
||||
ephemeral_key,
|
||||
persistent_key,
|
||||
expires_at,
|
||||
signature,
|
||||
}) => {
|
||||
println!("Adding new ephemeral key '{ephemeral_key}'");
|
||||
let e = VerifyingKey::from_bytes(
|
||||
&hex::decode(ephemeral_key)
|
||||
.expect("Cannot parse ephemeral key")
|
||||
.try_into()
|
||||
.expect("wrong length"),
|
||||
)
|
||||
.expect("bad key");
|
||||
let p = VerifyingKey::from_bytes(
|
||||
&hex::decode(persistent_key)
|
||||
.expect("Cannot parse persistent")
|
||||
.try_into()
|
||||
.expect("wrong length"),
|
||||
)
|
||||
.expect("bad key");
|
||||
let s = Signature::from_bytes(
|
||||
&hex::decode(signature)
|
||||
.expect("Cannot parse persistent")
|
||||
.try_into()
|
||||
.expect("wrong length"),
|
||||
);
|
||||
|
||||
let res = add_ephemeral_key(&pool, &e, &p, expires_at, &s).await?;
|
||||
println!("Added new ekey: {}", res);
|
||||
}
|
||||
None => {
|
||||
println!("Printing list of all todos");
|
||||
list_persistent_keys(&pool).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_persistent_key(pool: &SqlitePool, vkey: VerifyingKey) -> anyhow::Result<i64> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let b = Into::<Vec<u8>>::into(vkey.to_bytes());
|
||||
|
||||
// Insert the task, then obtain the ID of this row
|
||||
let id = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO persistent_keys ( id )
|
||||
VALUES ( ?1 )
|
||||
"#,
|
||||
b
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn add_ephemeral_key(
|
||||
pool: &SqlitePool,
|
||||
ephemeral_key: &VerifyingKey,
|
||||
persistent_key: &VerifyingKey,
|
||||
expires_at: i64,
|
||||
signature: &Signature,
|
||||
) -> anyhow::Result<i64> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
|
||||
let ephemeral_key_ = Into::<Vec<u8>>::into(ephemeral_key.to_bytes());
|
||||
let persistent_key_ = Into::<Vec<u8>>::into(persistent_key.to_bytes());
|
||||
let signature_ = Into::<Vec<u8>>::into(signature.to_bytes());
|
||||
// Insert the task, then obtain the ID of this row
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO ephemeral_keys (
|
||||
id,
|
||||
persistent_key,
|
||||
expires_at,
|
||||
signature
|
||||
)
|
||||
VALUES ( ?1, ?2, ?3, ?4 )
|
||||
"#,
|
||||
ephemeral_key_,
|
||||
persistent_key_,
|
||||
expires_at,
|
||||
signature_,
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn list_persistent_keys(pool: &SqlitePool) -> anyhow::Result<()> {
|
||||
let recs = sqlx::query!(
|
||||
r#"
|
||||
SELECT id
|
||||
FROM persistent_keys
|
||||
ORDER BY id
|
||||
"#
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
for rec in recs {
|
||||
println!("- [{}]", hex::encode(rec.id),);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_key() {
|
||||
// Useful for setting up some keys
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
println!("{}", hex::encode(SigningKey::generate(&mut rng).to_bytes()));
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
syntax="proto3";
|
||||
package messages;
|
||||
|
||||
message SignRequest {
|
||||
bytes vkey = 1;
|
||||
bytes body = 2;
|
||||
bytes sig = 3;
|
||||
}
|
||||
|
||||
message SignResponse {
|
||||
oneof result {
|
||||
Okay okay = 1;
|
||||
Fail fail = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Okay {
|
||||
bytes sig = 1;
|
||||
}
|
||||
|
||||
enum Fail {
|
||||
UnrecognisedKey = 1;
|
||||
ExpiredKey = 2;
|
||||
Other = 3;
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
// Automatically generated rust module for 'messages.proto' file
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
|
||||
use std::borrow::Cow;
|
||||
use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result};
|
||||
use quick_protobuf::sizeofs::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Fail {
|
||||
UnrecognisedKey = 1,
|
||||
ExpiredKey = 2,
|
||||
Other = 3,
|
||||
}
|
||||
|
||||
impl Default for Fail {
|
||||
fn default() -> Self {
|
||||
Fail::UnrecognisedKey
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Fail {
|
||||
fn from(i: i32) -> Self {
|
||||
match i {
|
||||
1 => Fail::UnrecognisedKey,
|
||||
2 => Fail::ExpiredKey,
|
||||
3 => Fail::Other,
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Fail {
|
||||
fn from(s: &'a str) -> Self {
|
||||
match s {
|
||||
"UnrecognisedKey" => Fail::UnrecognisedKey,
|
||||
"ExpiredKey" => Fail::ExpiredKey,
|
||||
"Other" => Fail::Other,
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct SignRequest<'a> {
|
||||
pub vkey: Cow<'a, [u8]>,
|
||||
pub body: Cow<'a, [u8]>,
|
||||
pub sig: Cow<'a, [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for SignRequest<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.vkey = r.read_bytes(bytes).map(Cow::Borrowed)?,
|
||||
Ok(18) => msg.body = r.read_bytes(bytes).map(Cow::Borrowed)?,
|
||||
Ok(26) => msg.sig = r.read_bytes(bytes).map(Cow::Borrowed)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for SignRequest<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.vkey == Cow::Borrowed(b"") { 0 } else { 1 + sizeof_len((&self.vkey).len()) }
|
||||
+ if self.body == Cow::Borrowed(b"") { 0 } else { 1 + sizeof_len((&self.body).len()) }
|
||||
+ if self.sig == Cow::Borrowed(b"") { 0 } else { 1 + sizeof_len((&self.sig).len()) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.vkey != Cow::Borrowed(b"") { w.write_with_tag(10, |w| w.write_bytes(&**&self.vkey))?; }
|
||||
if self.body != Cow::Borrowed(b"") { w.write_with_tag(18, |w| w.write_bytes(&**&self.body))?; }
|
||||
if self.sig != Cow::Borrowed(b"") { w.write_with_tag(26, |w| w.write_bytes(&**&self.sig))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct SignResponse<'a> {
|
||||
pub result: messages::mod_SignResponse::OneOfresult<'a>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for SignResponse<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.result = messages::mod_SignResponse::OneOfresult::okay(r.read_message::<messages::Okay>(bytes)?),
|
||||
Ok(16) => msg.result = messages::mod_SignResponse::OneOfresult::fail(r.read_enum(bytes)?),
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for SignResponse<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ match self.result {
|
||||
messages::mod_SignResponse::OneOfresult::okay(ref m) => 1 + sizeof_len((m).get_size()),
|
||||
messages::mod_SignResponse::OneOfresult::fail(ref m) => 1 + sizeof_varint(*(m) as u64),
|
||||
messages::mod_SignResponse::OneOfresult::None => 0,
|
||||
} }
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
match self.result { messages::mod_SignResponse::OneOfresult::okay(ref m) => { w.write_with_tag(10, |w| w.write_message(m))? },
|
||||
messages::mod_SignResponse::OneOfresult::fail(ref m) => { w.write_with_tag(16, |w| w.write_enum(*m as i32))? },
|
||||
messages::mod_SignResponse::OneOfresult::None => {},
|
||||
} Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mod_SignResponse {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum OneOfresult<'a> {
|
||||
okay(messages::Okay<'a>),
|
||||
fail(messages::Fail),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> Default for OneOfresult<'a> {
|
||||
fn default() -> Self {
|
||||
OneOfresult::None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct Okay<'a> {
|
||||
pub sig: Cow<'a, [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> MessageRead<'a> for Okay<'a> {
|
||||
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
|
||||
let mut msg = Self::default();
|
||||
while !r.is_eof() {
|
||||
match r.next_tag(bytes) {
|
||||
Ok(10) => msg.sig = r.read_bytes(bytes).map(Cow::Borrowed)?,
|
||||
Ok(t) => { r.read_unknown(bytes, t)?; }
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageWrite for Okay<'a> {
|
||||
fn get_size(&self) -> usize {
|
||||
0
|
||||
+ if self.sig == Cow::Borrowed(b"") { 0 } else { 1 + sizeof_len((&self.sig).len()) }
|
||||
}
|
||||
|
||||
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
|
||||
if self.sig != Cow::Borrowed(b"") { w.write_with_tag(10, |w| w.write_bytes(&**&self.sig))?; }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
// Automatically generated mod.rs
|
||||
pub mod messages;
|
|
@ -0,0 +1,206 @@
|
|||
use std::{error::Error, time::Duration};
|
||||
|
||||
use futures::prelude::*;
|
||||
use libp2p::{
|
||||
mdns, noise, ping,
|
||||
request_response::{self, Codec},
|
||||
swarm::SwarmEvent,
|
||||
tcp, yamux, Multiaddr,
|
||||
};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
// We create a custom network behaviour
|
||||
|
||||
#[derive(NetworkBehaviour)]
|
||||
struct MyBehaviour {
|
||||
req_res: request_response::Behaviour,
|
||||
mdns: mdns::tokio::Behaviour,
|
||||
}
|
||||
|
||||
struct MyRequest {
|
||||
key: [u8; 32],
|
||||
body: Vec<u8>,
|
||||
sig: [u8; 64],
|
||||
}
|
||||
|
||||
struct MyResponse {
|
||||
sig: [u8; 64],
|
||||
}
|
||||
|
||||
struct MyCodec;
|
||||
|
||||
impl Codec for MyCodec {
|
||||
#[doc = " The type of protocol(s) or protocol versions being negotiated."]
|
||||
type Protocol = String;
|
||||
|
||||
#[doc = " The type of inbound and outbound requests."]
|
||||
type Request = MyRequest;
|
||||
|
||||
#[doc = " The type of inbound and outbound responses."]
|
||||
type Response = MyResponse;
|
||||
|
||||
#[doc = " Reads a request from the given I/O stream according to the"]
|
||||
#[doc = " negotiated protocol."]
|
||||
#[must_use]
|
||||
#[allow(
|
||||
elided_named_lifetimes,
|
||||
clippy::type_complexity,
|
||||
clippy::type_repetition_in_bounds
|
||||
)]
|
||||
fn read_request<'life0, 'life1, 'life2, 'async_trait, T>(
|
||||
&'life0 mut self,
|
||||
_protocol: &'life1 Self::Protocol,
|
||||
io: &'life2 mut T,
|
||||
) -> ::core::pin::Pin<
|
||||
Box<
|
||||
dyn ::core::future::Future<Output = io::Result<Self::Request>>
|
||||
+ ::core::marker::Send
|
||||
+ 'async_trait,
|
||||
>,
|
||||
>
|
||||
where
|
||||
T: AsyncRead + Unpin + Send,
|
||||
T: 'async_trait,
|
||||
'life0: 'async_trait,
|
||||
'life1: 'async_trait,
|
||||
'life2: 'async_trait,
|
||||
Self: 'async_trait,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[doc = " Reads a response from the given I/O stream according to the"]
|
||||
#[doc = " negotiated protocol."]
|
||||
#[must_use]
|
||||
#[allow(
|
||||
elided_named_lifetimes,
|
||||
clippy::type_complexity,
|
||||
clippy::type_repetition_in_bounds
|
||||
)]
|
||||
fn read_response<'life0, 'life1, 'life2, 'async_trait, T>(
|
||||
&'life0 mut self,
|
||||
protocol: &'life1 Self::Protocol,
|
||||
io: &'life2 mut T,
|
||||
) -> ::core::pin::Pin<
|
||||
Box<
|
||||
dyn ::core::future::Future<Output = io::Result<Self::Response>>
|
||||
+ ::core::marker::Send
|
||||
+ 'async_trait,
|
||||
>,
|
||||
>
|
||||
where
|
||||
T: AsyncRead + Unpin + Send,
|
||||
T: 'async_trait,
|
||||
'life0: 'async_trait,
|
||||
'life1: 'async_trait,
|
||||
'life2: 'async_trait,
|
||||
Self: 'async_trait,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[doc = " Writes a request to the given I/O stream according to the"]
|
||||
#[doc = " negotiated protocol."]
|
||||
#[must_use]
|
||||
#[allow(
|
||||
elided_named_lifetimes,
|
||||
clippy::type_complexity,
|
||||
clippy::type_repetition_in_bounds
|
||||
)]
|
||||
fn write_request<'life0, 'life1, 'life2, 'async_trait, T>(
|
||||
&'life0 mut self,
|
||||
protocol: &'life1 Self::Protocol,
|
||||
io: &'life2 mut T,
|
||||
req: Self::Request,
|
||||
) -> ::core::pin::Pin<
|
||||
Box<
|
||||
dyn ::core::future::Future<Output = io::Result<()>>
|
||||
+ ::core::marker::Send
|
||||
+ 'async_trait,
|
||||
>,
|
||||
>
|
||||
where
|
||||
T: AsyncWrite + Unpin + Send,
|
||||
T: 'async_trait,
|
||||
'life0: 'async_trait,
|
||||
'life1: 'async_trait,
|
||||
'life2: 'async_trait,
|
||||
Self: 'async_trait,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[doc = " Writes a response to the given I/O stream according to the"]
|
||||
#[doc = " negotiated protocol."]
|
||||
#[must_use]
|
||||
#[allow(
|
||||
elided_named_lifetimes,
|
||||
clippy::type_complexity,
|
||||
clippy::type_repetition_in_bounds
|
||||
)]
|
||||
fn write_response<'life0, 'life1, 'life2, 'async_trait, T>(
|
||||
&'life0 mut self,
|
||||
protocol: &'life1 Self::Protocol,
|
||||
io: &'life2 mut T,
|
||||
res: Self::Response,
|
||||
) -> ::core::pin::Pin<
|
||||
Box<
|
||||
dyn ::core::future::Future<Output = io::Result<()>>
|
||||
+ ::core::marker::Send
|
||||
+ 'async_trait,
|
||||
>,
|
||||
>
|
||||
where
|
||||
T: AsyncWrite + Unpin + Send,
|
||||
T: 'async_trait,
|
||||
'life0: 'async_trait,
|
||||
'life1: 'async_trait,
|
||||
'life2: 'async_trait,
|
||||
Self: 'async_trait,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.try_init();
|
||||
|
||||
let mut swarm = libp2p::SwarmBuilder::with_new_identity()
|
||||
.with_tokio()
|
||||
.with_tcp(
|
||||
tcp::Config::default(),
|
||||
noise::Config::new,
|
||||
yamux::Config::default,
|
||||
)?
|
||||
.with_behaviour(|key| {
|
||||
let mdns =
|
||||
mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id())?;
|
||||
let req_res = request_response::Behaviour::with_codec(MyCodec, protocol, config);
|
||||
Ok(MyBehaviour { req_res, mdns })
|
||||
})?
|
||||
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX)))
|
||||
.build();
|
||||
|
||||
// Tell the swarm to listen on all interfaces and a random, OS-assigned
|
||||
// port.
|
||||
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
|
||||
|
||||
// Dial the peer identified by the multi-address given as the second
|
||||
// command-line argument, if any.
|
||||
if let Some(addr) = std::env::args().nth(1) {
|
||||
let remote: Multiaddr = addr.parse()?;
|
||||
swarm.dial(remote)?;
|
||||
println!("Dialed {addr}")
|
||||
}
|
||||
|
||||
loop {
|
||||
match swarm.select_next_some().await {
|
||||
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"),
|
||||
SwarmEvent::Behaviour(event) => println!("{event:?}"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue