mirror of
https://github.com/tlsnotary/explorer.git
synced 2026-01-09 23:08:12 -05:00
Implementing alpha.7 (#30)
This commit is contained in:
@@ -9,7 +9,7 @@ COPY . .
|
|||||||
RUN curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y
|
RUN curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y
|
||||||
RUN npm i
|
RUN npm i
|
||||||
RUN npm i --prefix rs/verifier/
|
RUN npm i --prefix rs/verifier/
|
||||||
RUN npm i --prefix rs/0.1.0-alpha.6/
|
RUN npm i --prefix rs/0.1.0-alpha.7/
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Create image for the app by copying build artifacts from builder
|
# Create image for the app by copying build artifacts from builder
|
||||||
|
|||||||
292
package-lock.json
generated
292
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,7 @@
|
|||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"multiformats": "^13.1.0",
|
"multiformats": "^13.1.0",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
|
"node-loader": "^2.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "^8.1.2",
|
"react-redux": "^8.1.2",
|
||||||
@@ -54,7 +55,7 @@
|
|||||||
"redux-thunk": "^2.4.2",
|
"redux-thunk": "^2.4.2",
|
||||||
"stream": "^0.0.2",
|
"stream": "^0.0.2",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"tlsn-js": "0.1.0-alpha.6.2",
|
"tlsn-js": "0.1.0-alpha.7.1",
|
||||||
"tlsn-js-v5": "npm:tlsn-js@0.1.0-alpha.5.4"
|
"tlsn-js-v5": "npm:tlsn-js@0.1.0-alpha.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -92,7 +93,6 @@
|
|||||||
"html-loader": "^4.2.0",
|
"html-loader": "^4.2.0",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"image-webpack-loader": "^8.1.0",
|
"image-webpack-loader": "^8.1.0",
|
||||||
"node-loader": "^2.0.0",
|
|
||||||
"nodemon": "^3.0.3",
|
"nodemon": "^3.0.3",
|
||||||
"postcss-loader": "^7.3.3",
|
"postcss-loader": "^7.3.3",
|
||||||
"postcss-preset-env": "^9.1.1",
|
"postcss-preset-env": "^9.1.1",
|
||||||
|
|||||||
228
pnpm-lock.yaml
generated
228
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1584
rs/0.1.0-alpha.7/Cargo.lock
generated
Normal file
1584
rs/0.1.0-alpha.7/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
rs/0.1.0-alpha.7/Cargo.toml
Normal file
22
rs/0.1.0-alpha.7/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "verifier"
|
||||||
|
version = "0.1.0"
|
||||||
|
license = "ISC"
|
||||||
|
edition = "2021"
|
||||||
|
exclude = ["index.node"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = "0.4"
|
||||||
|
neon = "1"
|
||||||
|
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
|
||||||
|
k256 = { version = "0.13", features = ["pem", "ecdsa"] }
|
||||||
|
serde = { version = "1.0.147", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
hex = "0.4"
|
||||||
|
bincode = { version = "1.3" }
|
||||||
|
tlsn-core = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.7", package = "tlsn-core" }
|
||||||
119
rs/0.1.0-alpha.7/README.md
Normal file
119
rs/0.1.0-alpha.7/README.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# verifier
|
||||||
|
|
||||||
|
This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
|
||||||
|
|
||||||
|
## Installing verifier
|
||||||
|
|
||||||
|
Installing verifier requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support).
|
||||||
|
|
||||||
|
You can install the project with npm. In the project directory, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
This fully installs the project, including installing any dependencies and running the build.
|
||||||
|
|
||||||
|
## Building verifier
|
||||||
|
|
||||||
|
If you have already installed the project and only want to run the build, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
This command uses the [cargo-cp-artifact](https://github.com/neon-bindings/cargo-cp-artifact) utility to run the Rust build and copy the built library into `./index.node`.
|
||||||
|
|
||||||
|
## Exploring verifier
|
||||||
|
|
||||||
|
After building verifier, you can explore its exports at the Node REPL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install
|
||||||
|
$ node
|
||||||
|
> require('.').hello()
|
||||||
|
"hello node"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm install`
|
||||||
|
|
||||||
|
Installs the project, including running `npm run build`.
|
||||||
|
|
||||||
|
### `npm build`
|
||||||
|
|
||||||
|
Builds the Node addon (`index.node`) from source.
|
||||||
|
|
||||||
|
Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm build` and `npm build-*` commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build -- --feature=beetle
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `npm build-debug`
|
||||||
|
|
||||||
|
Alias for `npm build`.
|
||||||
|
|
||||||
|
#### `npm build-release`
|
||||||
|
|
||||||
|
Same as [`npm build`](#npm-build) but, builds the module with the [`release`](https://doc.rust-lang.org/cargo/reference/profiles.html#release) profile. Release builds will compile slower, but run faster.
|
||||||
|
|
||||||
|
### `npm test`
|
||||||
|
|
||||||
|
Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/).
|
||||||
|
|
||||||
|
## Project Layout
|
||||||
|
|
||||||
|
The directory structure of this project is:
|
||||||
|
|
||||||
|
```
|
||||||
|
verifier/
|
||||||
|
├── Cargo.toml
|
||||||
|
├── README.md
|
||||||
|
├── index.node
|
||||||
|
├── package.json
|
||||||
|
├── src/
|
||||||
|
| └── lib.rs
|
||||||
|
└── target/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cargo.toml
|
||||||
|
|
||||||
|
The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command.
|
||||||
|
|
||||||
|
### README.md
|
||||||
|
|
||||||
|
This file.
|
||||||
|
|
||||||
|
### index.node
|
||||||
|
|
||||||
|
The Node addon—i.e., a binary Node module—generated by building the project. This is the main module for this package, as dictated by the `"main"` key in `package.json`.
|
||||||
|
|
||||||
|
Under the hood, a [Node addon](https://nodejs.org/api/addons.html) is a [dynamically-linked shared object](https://en.wikipedia.org/wiki/Library_(computing)#Shared_libraries). The `"build"` script produces this file by copying it from within the `target/` directory, which is where the Rust build produces the shared object.
|
||||||
|
|
||||||
|
### package.json
|
||||||
|
|
||||||
|
The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command.
|
||||||
|
|
||||||
|
### src/
|
||||||
|
|
||||||
|
The directory tree containing the Rust source code for the project.
|
||||||
|
|
||||||
|
### src/lib.rs
|
||||||
|
|
||||||
|
The Rust library's main module.
|
||||||
|
|
||||||
|
### target/
|
||||||
|
|
||||||
|
Binary artifacts generated by the Rust build.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Neon, see the [Neon documentation](https://neon-bindings.com).
|
||||||
|
|
||||||
|
To learn more about Rust, see the [Rust documentation](https://www.rust-lang.org).
|
||||||
|
|
||||||
|
To learn more about Node, see the [Node documentation](https://nodejs.org).
|
||||||
BIN
rs/0.1.0-alpha.7/index.node
Executable file
BIN
rs/0.1.0-alpha.7/index.node
Executable file
Binary file not shown.
34
rs/0.1.0-alpha.7/package-lock.json
generated
Normal file
34
rs/0.1.0-alpha.7/package-lock.json
generated
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "verifier",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "verifier",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"cargo-cp-artifact": "^0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cargo-cp-artifact": {
|
||||||
|
"version": "0.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.9.tgz",
|
||||||
|
"integrity": "sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"cargo-cp-artifact": "bin/cargo-cp-artifact.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cargo-cp-artifact": {
|
||||||
|
"version": "0.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.9.tgz",
|
||||||
|
"integrity": "sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
rs/0.1.0-alpha.7/package.json
Normal file
18
rs/0.1.0-alpha.7/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "verifier",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.node",
|
||||||
|
"scripts": {
|
||||||
|
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
|
||||||
|
"build-debug": "npm run build --",
|
||||||
|
"build-release": "npm run build -- --release",
|
||||||
|
"install": "npm run build-release",
|
||||||
|
"test": "cargo test"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"cargo-cp-artifact": "^0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
rs/0.1.0-alpha.7/src/example.json
Normal file
8
rs/0.1.0-alpha.7/src/example.json
Normal file
File diff suppressed because one or more lines are too long
133
rs/0.1.0-alpha.7/src/lib.rs
Normal file
133
rs/0.1.0-alpha.7/src/lib.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
use k256::pkcs8::DecodePublicKey;
|
||||||
|
use neon::prelude::*;
|
||||||
|
use tlsn_core::{
|
||||||
|
presentation::{Presentation, PresentationOutput},
|
||||||
|
signing::VerifyingKey,
|
||||||
|
CryptoProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn verify(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||||
|
let presentation_cx = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||||
|
let notary_key_cx = cx.argument::<JsString>(1)?.value(&mut cx);
|
||||||
|
|
||||||
|
let (sent, recv, time) =
|
||||||
|
verify_presentation(presentation_cx, notary_key_cx).or_else(|e| cx.throw_error(e))?;
|
||||||
|
|
||||||
|
let obj: Handle<JsObject> = cx.empty_object();
|
||||||
|
let sent_str = cx.string(sent);
|
||||||
|
obj.set(&mut cx, "sent", sent_str)?;
|
||||||
|
let recv_str = cx.string(recv);
|
||||||
|
obj.set(&mut cx, "recv", recv_str)?;
|
||||||
|
let session_time = cx.number(time as f64);
|
||||||
|
obj.set(&mut cx, "time", session_time)?;
|
||||||
|
|
||||||
|
Ok(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_presentation(
|
||||||
|
presentation_cx: String,
|
||||||
|
notary_key_cx: String,
|
||||||
|
) -> Result<(String, String, u64), String> {
|
||||||
|
// Deserialize the presentation
|
||||||
|
let bytes: Vec<u8> = hex::decode(presentation_cx.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
let presentation: Presentation = bincode::deserialize(&bytes).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Verify the session proof against the Notary's public key
|
||||||
|
let VerifyingKey {
|
||||||
|
alg: _,
|
||||||
|
data: key_data,
|
||||||
|
} = presentation.verifying_key();
|
||||||
|
|
||||||
|
let notary_key = k256::PublicKey::from_public_key_pem(notary_key_cx.as_str())
|
||||||
|
.map_err(|x| format!("Invalid notary key: {}", x))?;
|
||||||
|
let verifying_key = k256::PublicKey::from_sec1_bytes(key_data)
|
||||||
|
.map_err(|x| format!("Invalid verifying key: {}", x))?;
|
||||||
|
|
||||||
|
if notary_key != verifying_key {
|
||||||
|
Err("The verifying key does not match the notary key")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the presentation.
|
||||||
|
let provider = CryptoProvider::default();
|
||||||
|
let PresentationOutput {
|
||||||
|
connection_info,
|
||||||
|
transcript,
|
||||||
|
..
|
||||||
|
} = presentation
|
||||||
|
.verify(&provider)
|
||||||
|
.map_err(|x| format!("Presentation verification failed: {}", x))?;
|
||||||
|
|
||||||
|
let (sent, recv) = transcript
|
||||||
|
.map(|mut partial_transcript| {
|
||||||
|
// Set the unauthenticated bytes so they are distinguishable.
|
||||||
|
partial_transcript.set_unauthed(b'X');
|
||||||
|
let sent = String::from_utf8_lossy(partial_transcript.sent_unsafe()).to_string();
|
||||||
|
let recv = String::from_utf8_lossy(partial_transcript.received_unsafe()).to_string();
|
||||||
|
(sent, recv)
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Ok((sent, recv, connection_info.time))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[neon::main]
|
||||||
|
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||||
|
cx.export_function("verify", verify)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Presentation {
|
||||||
|
version: String,
|
||||||
|
data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get example presentation from example.com for testing
|
||||||
|
fn example_presentation() -> String {
|
||||||
|
let example = include_str!("example.json");
|
||||||
|
let presentation: Presentation = serde_json::from_str(&example).unwrap();
|
||||||
|
assert_eq!("0.1.0-alpha.7", presentation.version);
|
||||||
|
presentation.data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify() {
|
||||||
|
let notary_key_cx = String::from(
|
||||||
|
"-----BEGIN PUBLIC KEY-----
|
||||||
|
MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADe0jxnBObaIj7Xjg6TXLCM1GG/VhY5650
|
||||||
|
OrS/jgcbBuc=
|
||||||
|
-----END PUBLIC KEY-----",
|
||||||
|
);
|
||||||
|
|
||||||
|
let (sent, recv, time) =
|
||||||
|
verify_presentation(example_presentation(), notary_key_cx).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(1728416631, time);
|
||||||
|
assert!(sent.contains("host: example.com"));
|
||||||
|
assert!(sent.contains("XXXXXXXXXXXXXXXXXX"));
|
||||||
|
assert!(recv.contains("<title>Example Domain</title>"));
|
||||||
|
assert!(recv.contains("Date: Tue, 08 Oct 2024"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_wrong_key() {
|
||||||
|
let notary_key_cx = String::from(
|
||||||
|
"-----BEGIN PUBLIC KEY-----
|
||||||
|
MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADZT9nJiwhGESLjwQNnZ2MsZ1xwjGzvmhF
|
||||||
|
xFi8Vjzanlg=
|
||||||
|
-----END PUBLIC KEY-----",
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = verify_presentation(example_presentation(), notary_key_cx);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Err("The verifying key does not match the notary key".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,9 @@ import configureAppStore, { AppRootState } from '../web/store';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { verify } from '../rs/verifier/index.node';
|
import { verify } from '../rs/verifier/index.node';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { verify as verifyV6 } from '../rs/0.1.0-alpha.6/index.node';
|
import { verify as verifyV7 } from '../rs/0.1.0-alpha.7/index.node';
|
||||||
import { Attestation } from '../web/utils/types/types';
|
import { Attestation } from '../web/utils/types/types';
|
||||||
|
import { convertNotaryWsToHttp } from '../web/utils';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 3000;
|
const port = 3000;
|
||||||
@@ -97,19 +98,37 @@ app.get('/ipfs/:cid', async (req, res) => {
|
|||||||
* redirect to root if verification fails
|
* redirect to root if verification fails
|
||||||
*/
|
*/
|
||||||
if (!jsonProof.version && jsonProof.notaryUrl) {
|
if (!jsonProof.version && jsonProof.notaryUrl) {
|
||||||
const proof = await verify(
|
const notaryPem = await fetchPublicKeyFromNotary(jsonProof.notaryUrl);
|
||||||
file,
|
const proof = await verify(file, notaryPem);
|
||||||
await fetchPublicKeyFromNotary(jsonProof.notaryUrl),
|
|
||||||
);
|
|
||||||
proof.notaryUrl = jsonProof.notaryUrl;
|
proof.notaryUrl = jsonProof.notaryUrl;
|
||||||
storeConfig.proofs.ipfs[req.params.cid].proof = proof;
|
storeConfig.proofs.ipfs[req.params.cid].proof = {
|
||||||
} else if (jsonProof.version === '1.0') {
|
...proof,
|
||||||
const proof = await verifyV6(
|
version: '0.1.0-alpha.5',
|
||||||
jsonProof.data,
|
notaryUrl: jsonProof.notaryUrl,
|
||||||
await fetchPublicKeyFromNotary(jsonProof.meta.notaryUrl),
|
notaryKey: notaryPem,
|
||||||
);
|
};
|
||||||
|
} else if (jsonProof.version === '0.1.0-alpha.7') {
|
||||||
|
const notaryUrl = convertNotaryWsToHttp(jsonProof.meta.notaryUrl);
|
||||||
|
const notaryPem = await fetchPublicKeyFromNotary(notaryUrl);
|
||||||
|
const proof = await verifyV7(jsonProof.data, notaryPem);
|
||||||
proof.notaryUrl = jsonProof.meta.notaryUrl;
|
proof.notaryUrl = jsonProof.meta.notaryUrl;
|
||||||
storeConfig.proofs.ipfs[req.params.cid].proof = proof;
|
storeConfig.proofs.ipfs[req.params.cid].proof = {
|
||||||
|
version: '0.1.0-alpha.7',
|
||||||
|
time: proof.time,
|
||||||
|
sent: proof.sent,
|
||||||
|
recv: proof.recv,
|
||||||
|
notaryUrl: notaryUrl,
|
||||||
|
websocketProxyUrl: jsonProof.meta.websocketProxyUrl,
|
||||||
|
notaryKey: Buffer.from(
|
||||||
|
notaryPem
|
||||||
|
.replace('-----BEGIN PUBLIC KEY-----', '')
|
||||||
|
.replace('-----END PUBLIC KEY-----', '')
|
||||||
|
.replace(/\n/g, ''),
|
||||||
|
'base64',
|
||||||
|
)
|
||||||
|
.slice(23)
|
||||||
|
.toString('hex'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = configureAppStore(storeConfig);
|
const store = configureAppStore(storeConfig);
|
||||||
@@ -205,11 +224,8 @@ app.listen(port, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function fetchPublicKeyFromNotary(notaryUrl: string) {
|
async function fetchPublicKeyFromNotary(notaryUrl: string) {
|
||||||
const res = await fetch(
|
const res = await fetch(notaryUrl + '/info');
|
||||||
notaryUrl.replace('localhost', '127.0.0.1') + '/info',
|
|
||||||
);
|
|
||||||
const json: any = await res.json();
|
const json: any = await res.json();
|
||||||
|
|
||||||
if (!json.publicKey) throw new Error('invalid response');
|
if (!json.publicKey) throw new Error('invalid response');
|
||||||
return json.publicKey;
|
return json.publicKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import c from 'classnames';
|
import c from 'classnames';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Attestation, Proof as VerifiedProof } from '../../utils/types/types';
|
import {
|
||||||
|
Attestation,
|
||||||
|
AttestedData as VerifiedProof,
|
||||||
|
} from '../../utils/types/types';
|
||||||
import Modal, { ModalContent, ModalFooter, ModalHeader } from '../Modal';
|
import Modal, { ModalContent, ModalFooter, ModalHeader } from '../Modal';
|
||||||
import Icon from '../Icon';
|
import Icon from '../Icon';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
@@ -55,15 +58,15 @@ export default function ProofViewer(props: {
|
|||||||
)}
|
)}
|
||||||
<div className="flex flex-col px-2">
|
<div className="flex flex-col px-2">
|
||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<TabLabel onClick={() => setTab('info')} active={tab === 'info'}>
|
|
||||||
Info
|
|
||||||
</TabLabel>
|
|
||||||
<TabLabel onClick={() => setTab('sent')} active={tab === 'sent'}>
|
<TabLabel onClick={() => setTab('sent')} active={tab === 'sent'}>
|
||||||
Sent
|
Sent
|
||||||
</TabLabel>
|
</TabLabel>
|
||||||
<TabLabel onClick={() => setTab('recv')} active={tab === 'recv'}>
|
<TabLabel onClick={() => setTab('recv')} active={tab === 'recv'}>
|
||||||
Recv
|
Recv
|
||||||
</TabLabel>
|
</TabLabel>
|
||||||
|
<TabLabel onClick={() => setTab('info')} active={tab === 'info'}>
|
||||||
|
Metadata
|
||||||
|
</TabLabel>
|
||||||
<div className="flex flex-row flex-grow items-center justify-end">
|
<div className="flex flex-row flex-grow items-center justify-end">
|
||||||
<button className="button" onClick={onClickShare}>
|
<button className="button" onClick={onClickShare}>
|
||||||
Share
|
Share
|
||||||
@@ -73,15 +76,30 @@ export default function ProofViewer(props: {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col flex-grow px-2">
|
<div className="flex flex-col flex-grow px-2">
|
||||||
{tab === 'info' && (
|
{tab === 'info' && (
|
||||||
<div className="w-full bg-slate-100 text-slate-800 border p-2 text-xs break-all h-full outline-none font-mono">
|
<div className="flex flex-col w-full bg-slate-100 text-slate-800 border p-2 text-xs break-all h-full outline-none font-mono gap-4">
|
||||||
<div>
|
<MetadataRow label="Version" value={props.verifiedProof.version} />
|
||||||
<div>Notary URL:</div>
|
<MetadataRow
|
||||||
<div>
|
label="Notary URL"
|
||||||
{props.proof.version === '1.0'
|
value={props.verifiedProof.notaryUrl}
|
||||||
? props.proof.meta.notaryUrl
|
/>
|
||||||
: props.proof.notaryUrl}
|
{props.verifiedProof.websocketProxyUrl && (
|
||||||
</div>
|
<MetadataRow
|
||||||
</div>
|
label="Websocket Proxy URL"
|
||||||
|
value={props.verifiedProof.websocketProxyUrl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{props.verifiedProof.verifierKey && (
|
||||||
|
<MetadataRow
|
||||||
|
label="Verifying Key"
|
||||||
|
value={props.verifiedProof.verifierKey}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{props.verifiedProof.notaryKey && (
|
||||||
|
<MetadataRow
|
||||||
|
label="Notary Key"
|
||||||
|
value={props.verifiedProof.notaryKey}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{tab === 'sent' && (
|
{tab === 'sent' && (
|
||||||
@@ -103,6 +121,15 @@ export default function ProofViewer(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MetadataRow({ label, value }: { label: string; value: string }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{label}:</div>
|
||||||
|
<div className="text-sm font-semibold whitespace-pre-wrap">{value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function TabLabel(props: {
|
function TabLabel(props: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
onClick: MouseEventHandler;
|
onClick: MouseEventHandler;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
import ProofViewer from '../ProofViewer';
|
import ProofViewer from '../ProofViewer';
|
||||||
import { FileDropdown } from '../FileDropdown';
|
import { FileDropdown } from '../FileDropdown';
|
||||||
import { PubkeyInput } from '../../pages/PubkeyInput';
|
import { PubkeyInput } from '../../pages/PubkeyInput';
|
||||||
import { Proof } from '../../utils/types/types';
|
import { AttestedData } from '../../utils/types/types';
|
||||||
import { File } from '@web-std/file';
|
import { File } from '@web-std/file';
|
||||||
import { verify } from '../../utils';
|
import { verify } from '../../utils';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export default function SharedProof(): ReactElement {
|
|||||||
const file = new File([JSON.stringify(proofData?.raw)], `${cid}.json`, {
|
const file = new File([JSON.stringify(proofData?.raw)], `${cid}.json`, {
|
||||||
type: 'text/plain',
|
type: 'text/plain',
|
||||||
});
|
});
|
||||||
const [verifiedProof, setVerifiedProof] = useState<Proof | null>(
|
const [verifiedProof, setVerifiedProof] = useState<AttestedData | null>(
|
||||||
proofData?.proof || null,
|
proofData?.proof || null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { readFileAsync, safeParseJSON, verify } from '../../utils';
|
import { readFileAsync, safeParseJSON, verify } from '../../utils';
|
||||||
import FileUploadInput from '../../components/FileUploadInput';
|
import FileUploadInput from '../../components/FileUploadInput';
|
||||||
import ProofViewer from '../../components/ProofViewer';
|
import ProofViewer from '../../components/ProofViewer';
|
||||||
import { Attestation, Proof as VerifiedProof } from '../../utils/types/types';
|
import {
|
||||||
|
Attestation,
|
||||||
|
AttestedData as VerifiedProof,
|
||||||
|
} from '../../utils/types/types';
|
||||||
import { FileDropdown } from '../../components/FileDropdown';
|
import { FileDropdown } from '../../components/FileDropdown';
|
||||||
import { PubkeyInput } from '../PubkeyInput';
|
import { PubkeyInput } from '../PubkeyInput';
|
||||||
|
|
||||||
@@ -25,6 +28,7 @@ export default function FileDrop(): ReactElement {
|
|||||||
setVerifiedProof(resp);
|
setVerifiedProof(resp);
|
||||||
setStep('result');
|
setStep('result');
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
if (e?.message !== 'Failed to fetch') {
|
if (e?.message !== 'Failed to fetch') {
|
||||||
setError(
|
setError(
|
||||||
typeof e === 'string'
|
typeof e === 'string'
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function PubkeyInput(props: {
|
|||||||
<div className="font-semibold text-sm cursor-default">
|
<div className="font-semibold text-sm cursor-default">
|
||||||
Please enter the notary key for{' '}
|
Please enter the notary key for{' '}
|
||||||
<span className="text-blue-500 italic font-normal">
|
<span className="text-blue-500 italic font-normal">
|
||||||
{proof.version === '1.0' ? proof.meta.notaryUrl : proof.notaryUrl}
|
{proof.version === '0.1.0-alpha.7' ? proof.meta.notaryUrl : <></>}
|
||||||
</span>
|
</span>
|
||||||
:
|
:
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { Proof } from 'tlsn-js-v5/build/types';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import deepEqual from 'fast-deep-equal';
|
import deepEqual from 'fast-deep-equal';
|
||||||
import { EXPLORER_URL } from '../utils/constants';
|
import { EXPLORER_URL } from '../utils/constants';
|
||||||
import { Attestation } from '../utils/types/types';
|
import { Attestation, AttestedData } from '../utils/types/types';
|
||||||
import { verify } from '../utils';
|
import { verify } from '../utils';
|
||||||
|
|
||||||
enum ActionType {
|
enum ActionType {
|
||||||
@@ -18,19 +18,14 @@ export type Action<payload = any> = {
|
|||||||
meta?: any;
|
meta?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProofData = {
|
type AttestationData = {
|
||||||
raw: Attestation;
|
raw: Attestation;
|
||||||
proof?: {
|
proof?: AttestedData;
|
||||||
time: number;
|
|
||||||
sent: string;
|
|
||||||
recv: string;
|
|
||||||
notaryUrl: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
ipfs: {
|
ipfs: {
|
||||||
[cid: string]: ProofData;
|
[cid: string]: AttestationData;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,7 +61,7 @@ export const fetchProofFromIPFS =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setIPFSProof = (
|
export const setIPFSProof = (
|
||||||
payload: ProofData & {
|
payload: AttestationData & {
|
||||||
cid: string;
|
cid: string;
|
||||||
},
|
},
|
||||||
) => ({
|
) => ({
|
||||||
@@ -76,16 +71,11 @@ export const setIPFSProof = (
|
|||||||
|
|
||||||
export default function proofs(
|
export default function proofs(
|
||||||
state = initState,
|
state = initState,
|
||||||
action: Action<{
|
action: Action<
|
||||||
cid: string;
|
AttestationData & {
|
||||||
raw: Proof;
|
cid: string;
|
||||||
proof: {
|
}
|
||||||
time: number;
|
>,
|
||||||
sent: string;
|
|
||||||
recv: string;
|
|
||||||
notaryUrl: string;
|
|
||||||
};
|
|
||||||
}>,
|
|
||||||
): State {
|
): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionType.SetIPFSProof:
|
case ActionType.SetIPFSProof:
|
||||||
@@ -104,7 +94,7 @@ export default function proofs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useIPFSProof = (cid?: string): ProofData | null => {
|
export const useIPFSProof = (cid?: string): AttestationData | null => {
|
||||||
return useSelector((state: AppRootState) => {
|
return useSelector((state: AppRootState) => {
|
||||||
if (!cid) return null;
|
if (!cid) return null;
|
||||||
return state.proofs.ipfs[cid] || null;
|
return state.proofs.ipfs[cid] || null;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppRootState } from '.';
|
import { AppRootState } from '.';
|
||||||
import type { Proof } from '../utils/types/types';
|
import type { AttestedData } from '../utils/types/types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
export enum ActionType {
|
export enum ActionType {
|
||||||
@@ -8,7 +8,7 @@ export enum ActionType {
|
|||||||
UploadFileSuccess = 'proofupload/uploadFileSuccess',
|
UploadFileSuccess = 'proofupload/uploadFileSuccess',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const uploadFile = (fileName: string, proof: Proof) => ({
|
export const uploadFile = (fileName: string, proof: AttestedData) => ({
|
||||||
type: ActionType.AddFile,
|
type: ActionType.AddFile,
|
||||||
payload: { fileName, proof },
|
payload: { fileName, proof },
|
||||||
});
|
});
|
||||||
@@ -31,8 +31,12 @@ export type Action<payload = any> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
proofs: { fileName: string; proof: Proof }[];
|
proofs: { fileName: string; proof: AttestedData }[];
|
||||||
selectedProof?: { fileName: string; proof: Proof; ipfsCID?: string } | null;
|
selectedProof?: {
|
||||||
|
fileName: string;
|
||||||
|
proof: AttestedData;
|
||||||
|
ipfsCID?: string;
|
||||||
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initState: State = {
|
const initState: State = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement, useRef } from 'react';
|
import React, { ReactElement, useRef } from 'react';
|
||||||
import { Attestation, Proof } from './types/types';
|
import { Attestation, AttestedData } from './types/types';
|
||||||
|
|
||||||
export const readFileAsync = (file: File): Promise<string> => {
|
export const readFileAsync = (file: File): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -98,33 +98,50 @@ async function initTlsnJs() {
|
|||||||
export async function verify(
|
export async function verify(
|
||||||
attestation: Attestation,
|
attestation: Attestation,
|
||||||
pubKey: string,
|
pubKey: string,
|
||||||
): Promise<Proof> {
|
): Promise<AttestedData> {
|
||||||
let key = pubKey;
|
let key = pubKey;
|
||||||
const { NotaryServer } = await import('tlsn-js');
|
const { NotaryServer } = await import('tlsn-js');
|
||||||
|
await initTlsnJs();
|
||||||
|
|
||||||
switch (attestation.version) {
|
switch (attestation.version) {
|
||||||
case undefined: {
|
case undefined: {
|
||||||
const { verify } = await import('tlsn-js-v5');
|
const { verify } = await import('tlsn-js-v5');
|
||||||
key = key || (await NotaryServer.from(attestation.notaryUrl).publicKey());
|
|
||||||
return await verify(attestation, key);
|
|
||||||
}
|
|
||||||
case '1.0': {
|
|
||||||
const { TlsProof } = await import('tlsn-js');
|
|
||||||
console.log(Buffer.from(attestation.data, 'hex').toString('binary'));
|
|
||||||
await initTlsnJs();
|
|
||||||
key =
|
key =
|
||||||
key ||
|
key ||
|
||||||
(await NotaryServer.from(attestation.meta.notaryUrl).publicKey());
|
(await NotaryServer.from(attestation.notaryUrl).publicKey('pem'));
|
||||||
const tlsProof = new TlsProof(attestation.data);
|
const data = await verify(attestation, key);
|
||||||
const data = await tlsProof.verify({
|
|
||||||
typ: 'P256',
|
|
||||||
key: key,
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
sent: data.sent,
|
...data,
|
||||||
recv: data.recv,
|
version: '0.1.0-alpha.5',
|
||||||
time: data.time,
|
notaryUrl: attestation.notaryUrl,
|
||||||
notaryUrl: attestation.meta.notaryUrl,
|
notaryKey: key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case '0.1.0-alpha.7': {
|
||||||
|
const { Presentation, Transcript } = await import('tlsn-js');
|
||||||
|
const tlsProof = new Presentation(attestation.data);
|
||||||
|
const data = await tlsProof.verify();
|
||||||
|
const transcript = new Transcript({
|
||||||
|
sent: data.transcript.sent,
|
||||||
|
recv: data.transcript.recv,
|
||||||
|
});
|
||||||
|
const vk = await tlsProof.verifyingKey();
|
||||||
|
const verifyingKey = Buffer.from(vk.data).toString('hex');
|
||||||
|
const notaryUrl = convertNotaryWsToHttp(attestation.meta.notaryUrl);
|
||||||
|
const publicKey = await new NotaryServer(notaryUrl).publicKey();
|
||||||
|
|
||||||
|
if (verifyingKey !== publicKey)
|
||||||
|
throw new Error(`Notary key doesn't match verifying key`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: '0.1.0-alpha.7',
|
||||||
|
sent: transcript.sent(),
|
||||||
|
recv: transcript.recv(),
|
||||||
|
time: data.connection_info.time,
|
||||||
|
notaryUrl: notaryUrl,
|
||||||
|
notaryKey: publicKey,
|
||||||
|
websocketProxyUrl: attestation.meta.websocketProxyUrl,
|
||||||
|
verifierKey: verifyingKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +149,13 @@ export async function verify(
|
|||||||
throw new Error('Invalid Proof');
|
throw new Error('Invalid Proof');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertNotaryWsToHttp(notaryWs: string) {
|
||||||
|
const { protocol, pathname, hostname } = new URL(notaryWs);
|
||||||
|
const p = protocol === 'wss:' ? 'https:' : 'http';
|
||||||
|
const path = pathname === '/' ? '' : pathname.replace('/notarize', '');
|
||||||
|
return p + '//' + hostname + path;
|
||||||
|
}
|
||||||
|
|
||||||
function defer(): {
|
function defer(): {
|
||||||
promise: Promise<any>;
|
promise: Promise<any>;
|
||||||
resolve: (args?: any) => any;
|
resolve: (args?: any) => any;
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
export interface Proof {
|
export interface AttestedData {
|
||||||
|
version: '0.1.0-alpha.7' | '0.1.0-alpha.5';
|
||||||
time: number;
|
time: number;
|
||||||
sent: string;
|
sent: string;
|
||||||
recv: string;
|
recv: string;
|
||||||
notaryUrl: string;
|
notaryUrl: string;
|
||||||
|
notaryKey: string;
|
||||||
|
websocketProxyUrl?: string;
|
||||||
|
verifierKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttestationV0 = {
|
export type AttestationV0 = {
|
||||||
@@ -13,7 +17,7 @@ export type AttestationV0 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AttestationV1 = {
|
export type AttestationV1 = {
|
||||||
version: '1.0';
|
version: '0.1.0-alpha.7';
|
||||||
data: string;
|
data: string;
|
||||||
meta: {
|
meta: {
|
||||||
notaryUrl: string;
|
notaryUrl: string;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
import init, { Prover, NotarizedSession, TlsProof } from 'tlsn-js';
|
import init, { Prover, Attestation, Presentation } from 'tlsn-js';
|
||||||
|
|
||||||
Comlink.expose({
|
Comlink.expose({
|
||||||
init,
|
init,
|
||||||
Prover,
|
Prover,
|
||||||
NotarizedSession,
|
Attestation,
|
||||||
TlsProof,
|
Presentation,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user