mirror of
https://github.com/blyssprivacy/sdk.git
synced 2026-01-09 15:18:01 -05:00
Initial version of homomorphic encryption SDK
This commit is contained in:
20
.editorconfig
Normal file
20
.editorconfig
Normal file
@@ -0,0 +1,20 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{js,json,ts}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
20
.eslintrc.json
Normal file
20
.eslintrc.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"prettier"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": 0,
|
||||
"prettier/prettier": 1,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"no-empty-function": 0,
|
||||
"@typescript-eslint/no-empty-function": 0
|
||||
}
|
||||
}
|
||||
49
.github/workflows/build-js.yml
vendored
Normal file
49
.github/workflows/build-js.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Build the SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
client-build:
|
||||
name: Build SDK
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '19.x'
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@f98777369a49686b132a9e8f0fdd59837bf3c3fd
|
||||
with:
|
||||
version: v0.10.3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
npm run build
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
npm test
|
||||
|
||||
- name: Upload single-file bundle
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sdk-bundle
|
||||
path: dist/blyss-bundle.min.js
|
||||
|
||||
- name: Upload complete bundle
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sdk
|
||||
path: dist/
|
||||
31
.github/workflows/publish-to-npm.yml
vendored
Normal file
31
.github/workflows/publish-to-npm.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Publish to NPM
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '19.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@f98777369a49686b132a9e8f0fdd59837bf3c3fd
|
||||
with:
|
||||
version: v0.10.3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
npm run build
|
||||
|
||||
- name: Publish to NPM
|
||||
run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
target/
|
||||
*.gz
|
||||
*.dat
|
||||
node_modules
|
||||
/dist
|
||||
/target
|
||||
/pkg
|
||||
/wasm-pack.log
|
||||
|
||||
.env/
|
||||
*.pyc
|
||||
|
||||
17
.npmignore
Normal file
17
.npmignore
Normal file
@@ -0,0 +1,17 @@
|
||||
.editorconfig
|
||||
.github/
|
||||
.vscode/
|
||||
examples/
|
||||
target/
|
||||
python/
|
||||
js/tests/
|
||||
js/bridge/
|
||||
lib/
|
||||
docs/
|
||||
|
||||
.eslintrc.json
|
||||
.prettierrc.json
|
||||
.prettierignore
|
||||
jest.config.js
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
*.md
|
||||
15
.prettierrc.json
Normal file
15
.prettierrc.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"semi": true,
|
||||
"arrowParens": "avoid",
|
||||
"plugins": [
|
||||
"prettier-plugin-jsdoc"
|
||||
],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true,
|
||||
"jsdocPrintWidth": 80,
|
||||
"parser": "typescript"
|
||||
}
|
||||
40
LICENSE
40
LICENSE
@@ -1,25 +1,21 @@
|
||||
Copyright (c) 2022 Samir Menon <menon.samir@gmail.com>
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
Copyright (c) 2023, Blyss Inc.
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
102
README.md
102
README.md
@@ -1,18 +1,94 @@
|
||||
# Spiral: Fast, High-Rate Single-Server PIR via FHE Composition
|
||||
# Blyss SDK
|
||||
|
||||
A demo of this work that enables access to Wikipedia over PIR is available at https://spiralwiki.com.
|
||||
This is an implementation of our paper "Spiral: Fast, High-Rate Single-Server PIR via FHE Composition", available [here](https://eprint.iacr.org/2022/368.pdf).
|
||||
The [Blyss SDK](https://blyss.dev) lets you use homomorphic encryption to make private retrievals from Blyss buckets.
|
||||
|
||||
> **WARNING**: This is research-quality code; it has not been checked for side-channel leakage or basic logical or memory safety issues. Do not use this in production.
|
||||
Get an API key by [signing up](https://blyss.dev/auth/sign-up). Detailed documentation is at [docs.blyss.dev](https://docs.blyss.dev).
|
||||
|
||||
> **Warning**
|
||||
> The SDK has not yet been audited or reviewed, and the public Blyss service is still in beta. Data stored in Blyss should not be considered durable. [Contact us](mailto:founders@blyss.dev) for access to a production-ready service.
|
||||
|
||||
> _Looking for Spiral?_
|
||||
> Our new name is Blyss. The core Rust cryptographic library that this repo started as is now in `lib/spiral-rs`.
|
||||
|
||||
## Quick start
|
||||
|
||||
You can quickly try using the SDK without downloading anything. The example code shows how to use Blyss buckets to perform private contact intersection.
|
||||
|
||||
1. Get an API key by [signing up](https://blyss.dev/auth/sign-up).
|
||||
2. Open [this CodeSandbox](https://codesandbox.io/s/blyss-contact-intersection-example-7qr6r5) and enter your API key where it says `<YOUR API KEY HERE>`. This lets you try using the SDK in your browser.
|
||||
3. Try adding users to the service using the "Add a user" button. As you add more users, the service will _privately_ intersect each new users's contacts and the already existing users.
|
||||
Every user's list of contacts stays completely private using homomorphic encryption: it never leaves their device unencrypted.
|
||||
|
||||
If you prefer a simpler example using vanilla JS, check out [this CodePen](https://codepen.io/blyssprivacy/pen/qByMJwr?editors=0010&layout=left).
|
||||
|
||||
## Examples
|
||||
|
||||
The `examples/` directory has several examples of how to use the Blyss SDK. Running the examples requires [an API key](https://blyss.dev/auth/sign-up).
|
||||
|
||||
### Browser
|
||||
|
||||
The browser example shows how to quickly start using the Blyss SDK from vanilla JavaScript. The `blyss-bundle.min.js` build output is a single-file JS bundle that binds the library to `window.blyss`. Including this is a fast way to get started, especially if you prefer to use vanilla JS.
|
||||
|
||||
1. Edit `examples/browser-simple/main.js` to use your API key.
|
||||
2. Run a local HTTP server (we suggest [serve](https://github.com/vercel/serve)) in the repo root.
|
||||
3. Go to `http://localhost:3000/examples/browser-simple/` in a browser.
|
||||
|
||||
### React
|
||||
|
||||
The React example shows how to use the Blyss SDK in modern client-side JS. It also implements a more complicated application: private contact intersection.
|
||||
|
||||
1. Enter `examples/react-complex`, and run `npm install`.
|
||||
2. Edit `src/App.tsx` to use your API key.
|
||||
3. Run `npm run start`.
|
||||
|
||||
### Node
|
||||
|
||||
The Node.js example shows how to use the Blyss SDK in server-side JS. Node 19+ and ESM support is required.
|
||||
|
||||
1. Enter `examples/node`, and run `npm install`.
|
||||
2. Edit `main.ts` to use your API key.
|
||||
3. Run `ts-node main.ts`.
|
||||
|
||||
### Python
|
||||
|
||||
The Blyss SDK for Python is still in development. To build the Python library:
|
||||
|
||||
1. Enter `python/` and run `python -m venv .env` (on macOS, use `python3 -m venv .env`)
|
||||
2. Run `source .env/bin/activate`
|
||||
3. Run `pip install maturin`
|
||||
4. Run `maturin develop`
|
||||
|
||||
This will install the SDK locally as `blyss`. You can now import `blyss` in scripts you run from this virtual environment. To run the Python example:
|
||||
|
||||
1. Enter `examples/python`
|
||||
2. Run `python main.py`
|
||||
|
||||
## Documentation
|
||||
|
||||
All documentation is available at [docs.blyss.dev](https://docs.blyss.dev). You can generate the docs by running `npm start` in `docs/`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please feel free to open issues and pull requests! For bugs, try to provide as much context as possible. We are also always open to documentation contributions.
|
||||
|
||||
## Building
|
||||
|
||||
- In `spiral-rs/spiral-rs`:
|
||||
- To run an end-to-end test for a database with 2^20 elements of size 256 bytes, run `cargo run --release --bin e2e 20 256`.
|
||||
- To build the library `spiral-rs`, run `cargo build --release`.
|
||||
- To run the library tests, run `cargo test`.
|
||||
- To build the server, run `cargo build --release --bin server --features server`.
|
||||
- To preprocess a database, run `cargo run --release --bin preprocess_db dbfile.db dbfile.dbp`.
|
||||
- To run the server, run `target/release/server dbfile.dbp` with the preprocessed database file `dbfile.dbp`
|
||||
- In `spiral-rs/client`:
|
||||
- To build the client for our Wikipedia demo, run `wasm-pack build --target web --out-dir static/pkg`
|
||||
Steps to building the SDK:
|
||||
|
||||
1. Install Node with [nvm](https://github.com/nvm-sh/nvm#installing-and-updating), Rust with [rustup](https://rustup.rs/), and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
|
||||
2. Run `npm install`.
|
||||
3. Run `npm run build`.
|
||||
|
||||
This will completely build the SDK, including the core Rust library in `lib/spiral-rs`.
|
||||
|
||||
The SDK is structured as:
|
||||
|
||||
1. `lib/spiral-rs/`, a Rust crate containing the core cryptographic implementation of the [Spiral PIR scheme](https://eprint.iacr.org/2022/368.pdf).
|
||||
2. `js/`, the TypeScript code that implements the user-facing Blyss SDK.
|
||||
- `js/bridge/`, a Rust "bridge" crate that exposes key functionality from `spiral-rs` to the TypeScript code.
|
||||
3. `python/`, the Python version of the SDK (still in development)
|
||||
- `python/src/lib.rs`, another Rust "bridge" crate that exposes key functionality from `spiral-rs` to the Python code.
|
||||
|
||||
## License
|
||||
|
||||
MIT (see LICENSE.md)
|
||||
|
||||
7
client/.gitignore
vendored
7
client/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
static/data
|
||||
@@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "client"
|
||||
version = "0.1.0"
|
||||
authors = ["Samir Menon <menon.samir@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
spiral-rs = { path = "../spiral-rs" }
|
||||
rand = { version = "0.8.5" }
|
||||
rand_chacha = "0.3.1"
|
||||
wasm-bindgen = "0.2.74"
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||
|
||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||
# compared to the default allocator's ~10K. It is slower than the default
|
||||
# allocator, however.
|
||||
#
|
||||
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||
wee_alloc = { version = "0.4.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.13"
|
||||
|
||||
[profile.release]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
@@ -1,30 +0,0 @@
|
||||
The attached notices are provided for information only.
|
||||
|
||||
License notice for wasm-bindgen
|
||||
-------------------------------
|
||||
|
||||
Copyright (c) 2014 Alex Crichton
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,115 +0,0 @@
|
||||
use std::convert::TryInto;
|
||||
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use spiral_rs::{client::*, discrete_gaussian::*, util::*};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
const UUID_V4_LEN: usize = 36;
|
||||
|
||||
// console_log! macro
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
// Container class for a static lifetime Client
|
||||
// Avoids a lifetime in the return signature of bound Rust functions
|
||||
#[wasm_bindgen]
|
||||
pub struct WrappedClient {
|
||||
client: &'static mut Client<'static>,
|
||||
}
|
||||
|
||||
// Very simply test to ensure random generation is not obviously biased.
|
||||
fn _dg_seems_okay() {
|
||||
let params = get_test_params();
|
||||
let mut rng = ChaCha20Rng::from_entropy();
|
||||
let dg = DiscreteGaussian::init(¶ms);
|
||||
let mut v = Vec::new();
|
||||
let trials = 10000;
|
||||
let mut sum = 0;
|
||||
for _ in 0..trials {
|
||||
let val = dg.sample(&mut rng);
|
||||
v.push(val);
|
||||
sum += val;
|
||||
}
|
||||
let mean = sum as f64 / trials as f64;
|
||||
let std_dev = params.noise_width / f64::sqrt(2f64 * std::f64::consts::PI);
|
||||
let std_dev_of_mean = std_dev / f64::sqrt(trials as f64);
|
||||
assert!(f64::abs(mean) < std_dev_of_mean * 5f64);
|
||||
}
|
||||
|
||||
// Initializes a client; can optionally take in a set of parameters
|
||||
#[wasm_bindgen]
|
||||
pub fn initialize(json_params: Option<String>) -> WrappedClient {
|
||||
// spiral_rs::ntt::test::ntt_correct();
|
||||
let cfg = r#"
|
||||
{'n': 2,
|
||||
'nu_1': 10,
|
||||
'nu_2': 6,
|
||||
'p': 512,
|
||||
'q2_bits': 21,
|
||||
's_e': 85.83255142749422,
|
||||
't_gsw': 10,
|
||||
't_conv': 4,
|
||||
't_exp_left': 16,
|
||||
't_exp_right': 56,
|
||||
'instances': 11,
|
||||
'db_item_size': 100000 }
|
||||
"#;
|
||||
let mut cfg = cfg.replace("'", "\"");
|
||||
if json_params.is_some() {
|
||||
cfg = json_params.unwrap();
|
||||
}
|
||||
|
||||
let params = Box::leak(Box::new(params_from_json(&cfg)));
|
||||
let client = Box::leak(Box::new(Client::init(params)));
|
||||
|
||||
WrappedClient { client }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_keys(c: &mut WrappedClient, seed: Box<[u8]>, generate_pub_params: bool) -> Option<Box<[u8]>> {
|
||||
if generate_pub_params {
|
||||
Some(c.client.generate_keys_from_seed((*seed).try_into().unwrap()).serialize().into_boxed_slice())
|
||||
} else {
|
||||
c.client.generate_secret_keys_from_seed((*seed).try_into().unwrap());
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_query(c: &mut WrappedClient, id: &str, idx_target: usize) -> Box<[u8]> {
|
||||
assert_eq!(id.len(), UUID_V4_LEN);
|
||||
let query = c.client.generate_query(idx_target);
|
||||
let mut query_buf = query.serialize();
|
||||
let mut full_query_buf = id.as_bytes().to_vec();
|
||||
full_query_buf.append(&mut query_buf);
|
||||
full_query_buf.into_boxed_slice()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn decode_response(c: &mut WrappedClient, data: Box<[u8]>) -> Box<[u8]> {
|
||||
c.client.decode_response(&*data).into_boxed_slice()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{distributions::Standard, prelude::Distribution};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn chacha_is_correct() {
|
||||
let mut rng1 = ChaCha20Rng::from_seed([1u8; 32]);
|
||||
let mut rng2 = ChaCha20Rng::from_seed([1u8; 32]);
|
||||
let val1: u64 = Standard.sample(&mut rng1);
|
||||
let val2: u64 = Standard.sample(&mut rng2);
|
||||
assert_eq!(val1, val2);
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/*!
|
||||
* Load Awesome v1.1.0 (http://github.danielcardoso.net/load-awesome/)
|
||||
* Copyright 2015 Daniel Cardoso <@DanielCardoso>
|
||||
* Licensed under MIT
|
||||
*/
|
||||
.la-ball-clip-rotate,
|
||||
.la-ball-clip-rotate>div {
|
||||
position: relative;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate {
|
||||
display: block;
|
||||
font-size: 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate.la-dark {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate>div {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
background-color: currentColor;
|
||||
border: 0 solid currentColor;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate>div {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border-width: 2px;
|
||||
border-bottom-color: transparent;
|
||||
border-radius: 100%;
|
||||
-webkit-animation: ball-clip-rotate 1.0s linear infinite;
|
||||
-moz-animation: ball-clip-rotate 1.0s linear infinite;
|
||||
-o-animation: ball-clip-rotate 1.0s linear infinite;
|
||||
animation: ball-clip-rotate 1.0s linear infinite;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate.la-sm {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate.la-sm>div {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate.la-2x {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate.la-2x>div {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate.la-3x {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.la-ball-clip-rotate.la-3x>div {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-width: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Animation
|
||||
*/
|
||||
@-webkit-keyframes ball-clip-rotate {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes ball-clip-rotate {
|
||||
0% {
|
||||
-moz-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
-moz-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes ball-clip-rotate {
|
||||
0% {
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
-o-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ball-clip-rotate {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
-o-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--delay: 0;
|
||||
}
|
||||
|
||||
.progress {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: conic-gradient(#333 80%, #fff 80%);
|
||||
border-radius: 50%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.progress>div {
|
||||
position: absolute;
|
||||
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background-color: #fff;
|
||||
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.off {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -1,530 +0,0 @@
|
||||
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2020 Tobias Ahlin
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.articles article a.broken {
|
||||
color: #9c3535;
|
||||
cursor: not-allowed;
|
||||
text-decoration: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fixedpanel {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
position: fixed;
|
||||
background-color: #FFF;
|
||||
padding: 16px;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.searchbox {
|
||||
width: 250px;
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
color: #202020;
|
||||
height: 30px;
|
||||
-webkit-appearance: none;
|
||||
border: 1px solid #EEE;
|
||||
}
|
||||
|
||||
.query {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
color: #909090;
|
||||
background-color: #EEE;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
top: 40px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.articles {
|
||||
min-width: 400px;
|
||||
max-width: 800px;
|
||||
margin-left: 20px;
|
||||
flex-grow: 1;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
/* .articles:empty {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
.sidebar {
|
||||
max-width: 400px;
|
||||
padding: 8px;
|
||||
font-family: Arial;
|
||||
background-color: #EEE;
|
||||
position: relative;
|
||||
min-height: 48px;
|
||||
flex-grow: 1;
|
||||
line-height: 1.25em;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
height: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
line-height: 36px;
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
padding: 0 16px 16px 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.sidebar-collapse-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sidebar-collapse-btn::before {
|
||||
border-style: solid;
|
||||
border-width: 3px 3px 0 0;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 8px;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
transform: rotate(45deg) scaleY(-1);
|
||||
vertical-align: top;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .sidebar-collapse-btn {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.sidebar {
|
||||
order: -1;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* @media (max-width: 768px) {
|
||||
.fixedpanel {
|
||||
padding: 4px;
|
||||
}
|
||||
.content {
|
||||
padding: 4px;
|
||||
}
|
||||
body {
|
||||
margin: 4px;
|
||||
}
|
||||
}
|
||||
*/
|
||||
.articles article {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
color: rgb(32, 33, 34);
|
||||
line-height: 22.4px;
|
||||
}
|
||||
|
||||
.articles article a {
|
||||
color:rgb(6, 69, 173);
|
||||
cursor: pointer;
|
||||
direction: ltr;
|
||||
display: inline;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 22.4px;
|
||||
text-decoration-color: rgb(6, 69, 173);
|
||||
text-decoration-line: none;
|
||||
text-decoration-style: solid;
|
||||
}
|
||||
|
||||
.articles article h1, .articles article h2, .articles article h3, .articles article h4, .articles article h5 {
|
||||
margin-bottom: 0.25em;
|
||||
padding: 0;
|
||||
font-family: 'Linux Libertine','Georgia','Times',serif;
|
||||
border-bottom-color: rgb(162, 169, 177);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
font-weight: 400;
|
||||
color: rgb(0,0,0);
|
||||
}
|
||||
|
||||
.articles article h2.title {
|
||||
line-height: 37.44px;
|
||||
font-size: 28.8px;
|
||||
}
|
||||
|
||||
.articles article h1 {
|
||||
line-height: 27.3px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.articles article h2:not(.title) {
|
||||
line-height: 26.88px;
|
||||
font-size: 16.8px;
|
||||
border: none;
|
||||
font-weight: 700;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.articles article h3 {
|
||||
line-height: 22.4px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
font-weight: 700;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.articles article a:hover, .articles article a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.sidebar li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.sidebar h2:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
|
||||
.sidebar p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.sidebar p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
.sidebar figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar table {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sidebar table thead {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
padding: 4px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.suggestions > div {
|
||||
height: 19px;
|
||||
padding: 0.01em 0.25em;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suggestions > div:hover {
|
||||
background-color: #666;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.suggestions .highlight {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nolistmarker {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.diagram {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes clientProcessingBottom {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(0) scaleX(1);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(0) scaleX(1);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(32px) scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes clientProcessingMiddle {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(0) scaleX(1);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(0) scaleX(1);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(32px) scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes clientProcessingTop {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(0) scaleX(1);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(32px) scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.client {
|
||||
border: 4px solid #666;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.client .box {
|
||||
height: 8px;
|
||||
width: 32px;
|
||||
margin: 4px;
|
||||
background-color: #666;
|
||||
transform-origin: 0% 50%;
|
||||
}
|
||||
|
||||
.client .box.top {
|
||||
animation: clientProcessingBottom 3s ease 0s infinite normal none;
|
||||
}
|
||||
|
||||
.client .box.middle {
|
||||
animation: clientProcessingMiddle 3s ease 0s infinite normal none;
|
||||
}
|
||||
|
||||
.client .box.bottom {
|
||||
animation: clientProcessingTop 3s ease 0s infinite normal none;
|
||||
}
|
||||
|
||||
.box.complete {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
@keyframes expandRightLine {
|
||||
0% {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes expandRightArrowhead {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(200px);
|
||||
}
|
||||
}
|
||||
|
||||
.upload .line {
|
||||
transform-origin: 0% 50%;
|
||||
/* animation: expandRightLine 3s ease 0s infinite normal none; */
|
||||
|
||||
margin: 14px 4px 0 0;
|
||||
width: 200px;
|
||||
height: 4px;
|
||||
background-color: #666;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.arrowhead {
|
||||
/* animation: expandRightArrowhead 3s ease 0s infinite normal none; */
|
||||
content:"";
|
||||
position:absolute;
|
||||
height:0;
|
||||
width:0;
|
||||
top:-8px;
|
||||
border:10px solid transparent;
|
||||
border-left: 10px solid #666;
|
||||
}
|
||||
|
||||
.server {
|
||||
border: 4px solid #666;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
margin-left: 200px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.hidden {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.loadingbar {
|
||||
width: 100px;
|
||||
height: 14px;
|
||||
background-color: #EEE;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.complete {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #CDC;
|
||||
transform-origin: left;
|
||||
/* transition: transform ease-in 0.3s; */
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
width: 200px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.bar .label {
|
||||
font-family: sans-serif;
|
||||
margin-left: 4px;
|
||||
font-size: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.searchsuperbox {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.loadingbars {
|
||||
margin-left: 10px;
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
i, em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-right: 32px;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-family: sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.searchbutton {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#make_query {
|
||||
appearance: none;
|
||||
border: none;
|
||||
height: 30px;
|
||||
background-color: #ddd;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading {
|
||||
margin-left: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading .message {
|
||||
margin-left: 8px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.loading .inprogress {
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||
<link href="css/style.css" rel="stylesheet" />
|
||||
<link href="css/loading.css" rel="stylesheet" />
|
||||
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self' 'unsafe-eval' 'wasm-eval';">
|
||||
</head>
|
||||
<body>
|
||||
<div class="fixedpanel">
|
||||
<div class="topbar">
|
||||
<div class="title">
|
||||
<h1>Spiral Demo</h1>
|
||||
</div>
|
||||
<div class="searchsuperbox">
|
||||
<div class="searchandsuggestions">
|
||||
<div class="searchbutton">
|
||||
<input type="text" class="searchbox" placeholder="Article title" />
|
||||
<input id="make_query" type="button" value=">" />
|
||||
<div class="loading">
|
||||
<div class="progress off"><div></div></div>
|
||||
<div class="loading-icon hidden la-ball-clip-rotate la-dark la-sm"><div></div></div>
|
||||
<div class="message inprogress"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div id="output" class="articles"></div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="sidebar-title">FAQ</div>
|
||||
<div class="sidebar-collapse-btn"></div>
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<h2>What is this?</h2>
|
||||
<p>
|
||||
This is a page is a demo of the work featured in our paper, <a href="https://eprint.iacr.org/2022/368.pdf">"Spiral: Fast, High-Rate Single-Server PIR via FHE Composition"</a>.
|
||||
The code for our system is available <a href="https://github.com/menonsamir/spiral-rs">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
This demo allows private access to 6 GB (~30%) of English Wikipedia.
|
||||
In theory, even if the server is malicious, it will be unable to learn which articles you request.
|
||||
All article title searches are performed locally, and no images are available.
|
||||
</p>
|
||||
|
||||
<h2>What costs are associated with running the demo?</h2>
|
||||
<p>
|
||||
When making your first query, this demo will upload 18 MB of data; each later query requires only 28 KB of upload.
|
||||
The server response to each query is 250 KB.
|
||||
</p>
|
||||
|
||||
<h2>Should I use this in production?</h2>
|
||||
<p>
|
||||
No! This is research-quality software built for demonstration purposes; it is not intended to be
|
||||
side-channel resistant and has not undergone any kind fo security review. Don't use this code in production.
|
||||
</p>
|
||||
|
||||
<h2>Who made this demo?</h2>
|
||||
<p>
|
||||
Samir Menon, a (currently) independent researcher in PIR and lattice-based cryptography.
|
||||
Please contact me with any questions, comments, and sugggestions at <a href="menon.samir@gmail.com">menon.samir@gmail.com</a>.
|
||||
The scheme that this demo uses is "Spiral", which is a joint work with Prof. David Wu at UT Austin.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <script type="module" src="js/bz2.js"></script>
|
||||
<script type="module" src="js/wtf_wikipedia-client.min.js"></script> -->
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,343 +0,0 @@
|
||||
/*! bz2 (C) 2019-present SheetJS LLC */
|
||||
|
||||
'use strict';
|
||||
|
||||
(function bz2() {
|
||||
// https://www.ncbi.nlm.nih.gov/IEB/ToolBox/CPP_DOC/lxr/source/src/util/compress/bzip2/crctable.c
|
||||
const crc32Table = [
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
|
||||
];
|
||||
|
||||
// generated from 1 << i, except for 32
|
||||
const masks = [
|
||||
0x00000000, 0x00000001, 0x00000003, 0x00000007,
|
||||
0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f,
|
||||
0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff,
|
||||
0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff,
|
||||
0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff,
|
||||
0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff,
|
||||
0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff,
|
||||
0x0fffffff, 0x1fffffff, 0x3fffffff, -0x80000000,
|
||||
];
|
||||
|
||||
function createOrderedHuffmanTable(lengths) {
|
||||
const z = [];
|
||||
for (let i = 0; i < lengths.length; i += 1) {
|
||||
z.push([i, lengths[i]]);
|
||||
}
|
||||
z.push([lengths.length, -1]);
|
||||
const table = [];
|
||||
let start = z[0][0];
|
||||
let bits = z[0][1];
|
||||
for (let i = 0; i < z.length; i += 1) {
|
||||
const finish = z[i][0];
|
||||
const endbits = z[i][1];
|
||||
if (bits) {
|
||||
for (let code = start; code < finish; code += 1) {
|
||||
table.push({ code, bits, symbol: undefined });
|
||||
}
|
||||
}
|
||||
start = finish;
|
||||
bits = endbits;
|
||||
if (endbits === -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
table.sort((a, b) => ((a.bits - b.bits) || (a.code - b.code)));
|
||||
let tempBits = 0;
|
||||
let symbol = -1;
|
||||
const fastAccess = [];
|
||||
let current;
|
||||
for (let i = 0; i < table.length; i += 1) {
|
||||
const t = table[i];
|
||||
symbol += 1;
|
||||
if (t.bits !== tempBits) {
|
||||
symbol <<= t.bits - tempBits;
|
||||
tempBits = t.bits;
|
||||
current = fastAccess[tempBits] = {};
|
||||
}
|
||||
t.symbol = symbol;
|
||||
current[symbol] = t;
|
||||
}
|
||||
return {
|
||||
table,
|
||||
fastAccess,
|
||||
};
|
||||
}
|
||||
|
||||
function bwtReverse(src, primary) {
|
||||
if (primary < 0 || primary >= src.length) {
|
||||
throw RangeError('Out of bound');
|
||||
}
|
||||
const unsorted = src.slice();
|
||||
src.sort((a, b) => a - b);
|
||||
const start = {};
|
||||
for (let i = src.length - 1; i >= 0; i -= 1) {
|
||||
start[src[i]] = i;
|
||||
}
|
||||
const links = [];
|
||||
for (let i = 0; i < src.length; i += 1) {
|
||||
links.push(start[unsorted[i]]++); // eslint-disable-line no-plusplus
|
||||
}
|
||||
let i;
|
||||
const first = src[i = primary];
|
||||
const ret = [];
|
||||
for (let j = 1; j < src.length; j += 1) {
|
||||
const x = src[i = links[i]];
|
||||
if (x === undefined) {
|
||||
ret.push(255);
|
||||
} else {
|
||||
ret.push(x);
|
||||
}
|
||||
}
|
||||
ret.push(first);
|
||||
ret.reverse();
|
||||
return ret;
|
||||
}
|
||||
|
||||
function decompress(bytes, checkCRC = false) {
|
||||
let index = 0;
|
||||
let bitfield = 0;
|
||||
let bits = 0;
|
||||
const read = (n) => {
|
||||
if (n >= 32) {
|
||||
const nd = n >> 1;
|
||||
return read(nd) * (1 << nd) + read(n - nd);
|
||||
}
|
||||
while (bits < n) {
|
||||
bitfield = (bitfield << 8) + bytes[index];
|
||||
index += 1;
|
||||
bits += 8;
|
||||
}
|
||||
const m = masks[n];
|
||||
const r = (bitfield >> (bits - n)) & m;
|
||||
bits -= n;
|
||||
bitfield &= ~(m << bits);
|
||||
return r;
|
||||
};
|
||||
|
||||
const magic = read(16);
|
||||
if (magic !== 0x425A) { // 'BZ'
|
||||
throw new Error('Invalid magic');
|
||||
}
|
||||
const method = read(8);
|
||||
if (method !== 0x68) { // h for huffman
|
||||
throw new Error('Invalid method');
|
||||
}
|
||||
|
||||
let blocksize = read(8);
|
||||
if (blocksize >= 49 && blocksize <= 57) { // 1..9
|
||||
blocksize -= 48;
|
||||
} else {
|
||||
throw new Error('Invalid blocksize');
|
||||
}
|
||||
|
||||
let out = new Uint8Array(bytes.length * 1.5);
|
||||
let outIndex = 0;
|
||||
let newCRC = -1;
|
||||
while (true) {
|
||||
const blocktype = read(48);
|
||||
const crc = read(32) | 0;
|
||||
if (blocktype === 0x314159265359) {
|
||||
if (read(1)) {
|
||||
throw new Error('do not support randomised');
|
||||
}
|
||||
const pointer = read(24);
|
||||
const used = [];
|
||||
const usedGroups = read(16);
|
||||
for (let i = 1 << 15; i > 0; i >>= 1) {
|
||||
if (!(usedGroups & i)) {
|
||||
for (let j = 0; j < 16; j += 1) {
|
||||
used.push(false);
|
||||
}
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
const usedChars = read(16);
|
||||
for (let j = 1 << 15; j > 0; j >>= 1) {
|
||||
used.push(!!(usedChars & j));
|
||||
}
|
||||
}
|
||||
const groups = read(3);
|
||||
if (groups < 2 || groups > 6) {
|
||||
throw new Error('Invalid number of huffman groups');
|
||||
}
|
||||
const selectorsUsed = read(15);
|
||||
const selectors = [];
|
||||
const mtf = Array.from({ length: groups }, (_, i) => i);
|
||||
for (let i = 0; i < selectorsUsed; i += 1) {
|
||||
let c = 0;
|
||||
while (read(1)) {
|
||||
c += 1;
|
||||
if (c >= groups) {
|
||||
throw new Error('MTF table out of range');
|
||||
}
|
||||
}
|
||||
const v = mtf[c];
|
||||
for (let j = c; j > 0; mtf[j] = mtf[--j]) { // eslint-disable-line no-plusplus
|
||||
// nothing
|
||||
}
|
||||
selectors.push(v);
|
||||
mtf[0] = v;
|
||||
}
|
||||
const symbolsInUse = used.reduce((a, b) => a + b, 0) + 2;
|
||||
const tables = [];
|
||||
for (let i = 0; i < groups; i += 1) {
|
||||
let length = read(5);
|
||||
const lengths = [];
|
||||
for (let j = 0; j < symbolsInUse; j += 1) {
|
||||
if (length < 0 || length > 20) {
|
||||
throw new Error('Huffman group length outside range');
|
||||
}
|
||||
while (read(1)) {
|
||||
length -= (read(1) * 2) - 1;
|
||||
}
|
||||
lengths.push(length);
|
||||
}
|
||||
tables.push(createOrderedHuffmanTable(lengths));
|
||||
}
|
||||
const favourites = [];
|
||||
for (let i = 0; i < used.length - 1; i += 1) {
|
||||
if (used[i]) {
|
||||
favourites.push(i);
|
||||
}
|
||||
}
|
||||
let decoded = 0;
|
||||
let selectorPointer = 0;
|
||||
let t;
|
||||
let r;
|
||||
let repeat = 0;
|
||||
let repeatPower = 0;
|
||||
const buffer = [];
|
||||
while (true) {
|
||||
decoded -= 1;
|
||||
if (decoded <= 0) {
|
||||
decoded = 50;
|
||||
if (selectorPointer <= selectors.length) {
|
||||
t = tables[selectors[selectorPointer]];
|
||||
selectorPointer += 1;
|
||||
}
|
||||
}
|
||||
for (const b in t.fastAccess) {
|
||||
if (!Object.prototype.hasOwnProperty.call(t.fastAccess, b)) {
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
if (bits < b) {
|
||||
bitfield = (bitfield << 8) + bytes[index];
|
||||
index += 1;
|
||||
bits += 8;
|
||||
}
|
||||
r = t.fastAccess[b][bitfield >> (bits - b)];
|
||||
if (r) {
|
||||
bitfield &= masks[bits -= b];
|
||||
r = r.code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r >= 0 && r <= 1) {
|
||||
if (repeat === 0) {
|
||||
repeatPower = 1;
|
||||
}
|
||||
repeat += repeatPower << r;
|
||||
repeatPower <<= 1;
|
||||
continue; // eslint-disable-line no-continue
|
||||
} else {
|
||||
const v = favourites[0];
|
||||
for (; repeat > 0; repeat -= 1) {
|
||||
buffer.push(v);
|
||||
}
|
||||
}
|
||||
if (r === symbolsInUse - 1) {
|
||||
break;
|
||||
} else {
|
||||
const v = favourites[r - 1];
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let j = r - 1; j > 0; favourites[j] = favourites[--j]) {
|
||||
// nothing
|
||||
}
|
||||
favourites[0] = v;
|
||||
buffer.push(v);
|
||||
}
|
||||
}
|
||||
const nt = bwtReverse(buffer, pointer);
|
||||
let i = 0;
|
||||
while (i < nt.length) {
|
||||
const c = nt[i];
|
||||
let count = 1;
|
||||
if ((i < nt.length - 4)
|
||||
&& nt[i + 1] === c
|
||||
&& nt[i + 2] === c
|
||||
&& nt[i + 3] === c) {
|
||||
count = nt[i + 4] + 4;
|
||||
i += 5;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
if (outIndex + count >= out.length) {
|
||||
const old = out;
|
||||
out = new Uint8Array(old.length * 2);
|
||||
out.set(old);
|
||||
}
|
||||
for (let j = 0; j < count; j += 1) {
|
||||
if (checkCRC) {
|
||||
newCRC = (newCRC << 8) ^ crc32Table[((newCRC >> 24) ^ c) & 0xff];
|
||||
}
|
||||
out[outIndex] = c;
|
||||
outIndex += 1;
|
||||
}
|
||||
}
|
||||
if (checkCRC) {
|
||||
const calculatedCRC = newCRC ^ -1;
|
||||
if (calculatedCRC !== crc) {
|
||||
throw new Error(`CRC mismatch: ${calculatedCRC} !== ${crc}`);
|
||||
}
|
||||
newCRC = -1;
|
||||
}
|
||||
} else if (blocktype === 0x177245385090) {
|
||||
read(bits & 0x07); // pad align
|
||||
break;
|
||||
} else {
|
||||
throw new Error('Invalid bz2 blocktype');
|
||||
}
|
||||
}
|
||||
return out.subarray(0, outIndex);
|
||||
}
|
||||
|
||||
const exports = { decompress };
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.bz2 = exports; // eslint-disable-line no-undef
|
||||
} else {
|
||||
module.exports = exports;
|
||||
}
|
||||
}());
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* Bundled by jsDelivr using Rollup v2.67.2 and Terser v5.10.0.
|
||||
* Original file: /npm/idb@7.0.1/build/index.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
let e,t;const n=new WeakMap,r=new WeakMap,o=new WeakMap,s=new WeakMap,a=new WeakMap;let i={get(e,t,n){if(e instanceof IDBTransaction){if("done"===t)return r.get(e);if("objectStoreNames"===t)return e.objectStoreNames||o.get(e);if("store"===t)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return d(e[t])},set:(e,t,n)=>(e[t]=n,!0),has:(e,t)=>e instanceof IDBTransaction&&("done"===t||"store"===t)||t in e};function c(e){return e!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(t||(t=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(e)?function(...t){return e.apply(l(this),t),d(n.get(this))}:function(...t){return d(e.apply(l(this),t))}:function(t,...n){const r=e.call(l(this),t,...n);return o.set(r,t.sort?t.sort():[t]),d(r)}}function u(t){return"function"==typeof t?c(t):(t instanceof IDBTransaction&&function(e){if(r.has(e))return;const t=new Promise(((t,n)=>{const r=()=>{e.removeEventListener("complete",o),e.removeEventListener("error",s),e.removeEventListener("abort",s)},o=()=>{t(),r()},s=()=>{n(e.error||new DOMException("AbortError","AbortError")),r()};e.addEventListener("complete",o),e.addEventListener("error",s),e.addEventListener("abort",s)}));r.set(e,t)}(t),n=t,(e||(e=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])).some((e=>n instanceof e))?new Proxy(t,i):t);var n}function d(e){if(e instanceof IDBRequest)return function(e){const t=new Promise(((t,n)=>{const r=()=>{e.removeEventListener("success",o),e.removeEventListener("error",s)},o=()=>{t(d(e.result)),r()},s=()=>{n(e.error),r()};e.addEventListener("success",o),e.addEventListener("error",s)}));return t.then((t=>{t instanceof IDBCursor&&n.set(t,e)})).catch((()=>{})),a.set(t,e),t}(e);if(s.has(e))return s.get(e);const t=u(e);return t!==e&&(s.set(e,t),a.set(t,e)),t}const l=e=>a.get(e);function f(e,t,{blocked:n,upgrade:r,blocking:o,terminated:s}={}){const a=indexedDB.open(e,t),i=d(a);return r&&a.addEventListener("upgradeneeded",(e=>{r(d(a.result),e.oldVersion,e.newVersion,d(a.transaction))})),n&&a.addEventListener("blocked",(()=>n())),i.then((e=>{s&&e.addEventListener("close",(()=>s())),o&&e.addEventListener("versionchange",(()=>o()))})).catch((()=>{})),i}function p(e,{blocked:t}={}){const n=indexedDB.deleteDatabase(e);return t&&n.addEventListener("blocked",(()=>t())),d(n).then((()=>{}))}const D=["get","getKey","getAll","getAllKeys","count"],v=["put","add","delete","clear"],b=new Map;function I(e,t){if(!(e instanceof IDBDatabase)||t in e||"string"!=typeof t)return;if(b.get(t))return b.get(t);const n=t.replace(/FromIndex$/,""),r=t!==n,o=v.includes(n);if(!(n in(r?IDBIndex:IDBObjectStore).prototype)||!o&&!D.includes(n))return;const s=async function(e,...t){const s=this.transaction(e,o?"readwrite":"readonly");let a=s.store;return r&&(a=a.index(t.shift())),(await Promise.all([a[n](...t),o&&s.done]))[0]};return b.set(t,s),s}i=(e=>({...e,get:(t,n,r)=>I(t,n)||e.get(t,n,r),has:(t,n)=>!!I(t,n)||e.has(t,n)}))(i);export{p as deleteDB,f as openDB,l as unwrap,d as wrap};export default null;
|
||||
//# sourceMappingURL=/sm/8b3d9dc6242aa179b44ac56c276ea9b7bf63a66eaddfa7ca77337528485c7dd7.map
|
||||
@@ -1,448 +0,0 @@
|
||||
import init, {
|
||||
initialize,
|
||||
generate_keys,
|
||||
generate_query,
|
||||
decode_response
|
||||
} from '../pkg/client.js';
|
||||
|
||||
import './bz2.js';
|
||||
import './wtf_wikipedia.js';
|
||||
import './wtf-plugin-html.js';
|
||||
wtf.extend(wtfHtml);
|
||||
|
||||
const API_URL = "/api";
|
||||
const CHECK_URL = "/check";
|
||||
const SETUP_URL = "/setup";
|
||||
const QUERY_URL = "/query";
|
||||
|
||||
async function postData(url = '', data = {}, json = false) {
|
||||
// const response = await fetch(url, {
|
||||
// method: 'POST',
|
||||
// mode: 'cors',
|
||||
// cache: 'no-store',
|
||||
// credentials: 'omit',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/octet-stream',
|
||||
// 'Content-Length': data.length
|
||||
// },
|
||||
// redirect: 'follow',
|
||||
// referrerPolicy: 'no-referrer',
|
||||
// body: data
|
||||
// });
|
||||
// if (json) {
|
||||
// return response.json();
|
||||
// } else {
|
||||
// let data = await response.arrayBuffer();
|
||||
// return new Uint8Array(data);
|
||||
// }
|
||||
|
||||
// Can't use Fetch API here since it lacks progress indication
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.responseType = json ? 'json' : 'arraybuffer';
|
||||
return await new Promise((resolve, reject) => {
|
||||
xhr.upload.addEventListener("progress", (event) => {
|
||||
if (event.lengthComputable) {
|
||||
setProgress(Math.round(event.loaded / event.total * 100))
|
||||
}
|
||||
});
|
||||
xhr.addEventListener("loadend", () => {
|
||||
resolve(xhr.readyState === 4 && xhr.status === 200);
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject({
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
reject({
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
};
|
||||
xhr.open("POST", url, true);
|
||||
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||
xhr.send(new Blob([data.buffer]));
|
||||
});
|
||||
}
|
||||
|
||||
async function getData(url = '', json = false) {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'default',
|
||||
credentials: 'omit',
|
||||
redirect: 'follow',
|
||||
referrerPolicy: 'no-referrer'
|
||||
});
|
||||
if (json) {
|
||||
return response.json();
|
||||
} else {
|
||||
let data = await response.arrayBuffer();
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
}
|
||||
|
||||
const api = {
|
||||
check: async (uuid) => getData(API_URL + CHECK_URL + "?uuid="+uuid, true),
|
||||
setup: async (data) => postData(API_URL + SETUP_URL, data, true),
|
||||
query: async (data) => postData(API_URL + QUERY_URL, data, false)
|
||||
}
|
||||
|
||||
function extractTitle(article) {
|
||||
var title = "";
|
||||
var endTitleTagIdx = article.indexOf("</title>");
|
||||
if (endTitleTagIdx != -1) {
|
||||
title = article.slice(0, endTitleTagIdx);
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
function preprocessWikiText(wikiText, targetTitle) {
|
||||
targetTitle = targetTitle.toLowerCase();
|
||||
|
||||
let articles = wikiText.split("<title>")
|
||||
.filter(d => d.length > 10)
|
||||
.filter(d => {
|
||||
return extractTitle(d).toLowerCase() == targetTitle;
|
||||
});
|
||||
|
||||
if (articles.length === 0) {
|
||||
console.log("error decoding...");
|
||||
return "";
|
||||
}
|
||||
|
||||
let d = articles[0];
|
||||
let title = extractTitle(d);
|
||||
let articlePageMatch = d.match(/<text>/);
|
||||
if (!articlePageMatch) {
|
||||
console.log("error decoding...");
|
||||
return "";
|
||||
}
|
||||
let startPageContentIdx = articlePageMatch.index + articlePageMatch[0].length;
|
||||
let endPageContentIdx = d.slice(startPageContentIdx).indexOf("</text>")
|
||||
d = d.slice(startPageContentIdx, endPageContentIdx);
|
||||
|
||||
d = d
|
||||
.replace(/<ref[\s\S]{0,500}?<\/ref>/gi, "")
|
||||
.replace(/<ref[\s\S]{0,500}?\/>/gi, "")
|
||||
.replace(/<ref>[\s\S]{0,500}?<\/ref>/gi, "")
|
||||
.replace(/<![\s\S]{0,500}?-->/gi, "");
|
||||
|
||||
return {
|
||||
"wikiText": d,
|
||||
"title": title
|
||||
};
|
||||
}
|
||||
function postProcessWikiHTML(wikiHTML, title) {
|
||||
wikiHTML = wikiHTML.replace(/<img.*?\/>/g, "");
|
||||
wikiHTML = "<h2 class=\"title\">"+title+"</h2>" + wikiHTML
|
||||
return wikiHTML;
|
||||
}
|
||||
|
||||
function resultToHtml(result, title) {
|
||||
let decompressedData = bz2.decompress(result);
|
||||
let wikiText = new TextDecoder("utf-8").decode(decompressedData);
|
||||
let processedData = preprocessWikiText(wikiText, title);
|
||||
let wikiHTML = wtf(processedData.wikiText).html();
|
||||
wikiHTML = postProcessWikiHTML(wikiHTML, processedData.title);
|
||||
return "<article>" + wikiHTML + "</article>";
|
||||
}
|
||||
window.resultToHtml = resultToHtml;
|
||||
|
||||
function addBold(suggestion, query) {
|
||||
return '<span class="highlight">'
|
||||
+ suggestion.slice(0,query.length)
|
||||
+ "</span>"
|
||||
+ suggestion.slice(query.length);
|
||||
}
|
||||
|
||||
function showSuggestionsBox(suggestions, query) {
|
||||
var htmlSuggestions = '<div class="suggestions">'
|
||||
+ suggestions.map(m => "<div>"+addBold(m, query)+"</div>").join('')
|
||||
+ "</div>";
|
||||
document.querySelector('.searchbutton').insertAdjacentHTML('afterend', htmlSuggestions);
|
||||
document.querySelectorAll('.suggestions > div').forEach((el) => {
|
||||
el.onclick = (e) => {
|
||||
document.querySelector(".searchbox").value = el.innerHTML
|
||||
.replace('<span class="highlight">', '')
|
||||
.replace('</span>', '');
|
||||
clearExistingSuggestionsBox();
|
||||
document.querySelector('#make_query').click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearExistingSuggestionsBox() {
|
||||
var existing = document.querySelector('.suggestions');
|
||||
if (existing) {
|
||||
existing.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function hasTitle(title) {
|
||||
return title && window.title_index.hasOwnProperty(title) && window.title_index[title] < window.numArticles;
|
||||
}
|
||||
|
||||
function followRedirects(title) {
|
||||
if (hasTitle(title)) {
|
||||
return title;
|
||||
} else if (window.redirects.hasOwnProperty(title) && hasTitle(window.redirects[title])) {
|
||||
return window.redirects[title];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function startLoading(message, hasProgress) {
|
||||
window.loading = true;
|
||||
window.started_loading = Date.now();
|
||||
if (hasProgress) {
|
||||
document.querySelector(".progress").classList.remove("off");
|
||||
document.querySelector(".loading-icon").classList.add("off");
|
||||
} else {
|
||||
document.querySelector(".progress").classList.add("off");
|
||||
document.querySelector(".loading-icon").classList.remove("off");
|
||||
document.querySelector(".loading-icon").classList.remove("hidden");
|
||||
}
|
||||
document.querySelector(".loading .message").innerHTML = message+"...";
|
||||
document.querySelector(".loading .message").classList.add("inprogress");
|
||||
}
|
||||
|
||||
function stopLoading(message) {
|
||||
window.loading = false;
|
||||
document.querySelector(".loading-icon").classList.add("hidden");
|
||||
let seconds = (Date.now() - window.started_loading) / 1000
|
||||
let secondsRounded = Math.round(seconds * 100) / 100;
|
||||
let timingMessage = secondsRounded > 0.01 ? (" Took "+secondsRounded+"s.") : "";
|
||||
document.querySelector(".loading .message").innerHTML = "Done " + message.toLowerCase() + "." + timingMessage;
|
||||
document.querySelector(".loading .message").classList.remove("inprogress");
|
||||
}
|
||||
|
||||
function queryTitleOnClick(title, displayTitle) {
|
||||
return async (e) => {
|
||||
e.preventDefault();
|
||||
document.querySelector(".searchbox").value = displayTitle;
|
||||
window.scrollTo(0, 0);
|
||||
queryTitle(title);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function enableLinks(element) {
|
||||
element.querySelectorAll('a').forEach((el) => {
|
||||
let displayTitle = el.getAttribute("href").slice(2).replace(/_/g, " ");
|
||||
let linkTitle = displayTitle.toLowerCase();
|
||||
if (hasTitle(linkTitle)) {
|
||||
el.onclick = queryTitleOnClick(linkTitle, displayTitle);
|
||||
} else {
|
||||
var redirected = followRedirects(linkTitle);
|
||||
if (redirected !== null && hasTitle(redirected)) {
|
||||
el.onclick = queryTitleOnClick(redirected, displayTitle);
|
||||
} else {
|
||||
el.classList.add("broken")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const DB_NAME = 'spiralKey';
|
||||
const KEY_SIZE = 32;
|
||||
const MAX_VALID_TIME = 604800000; // 1 week
|
||||
|
||||
async function arrayBufferToBase64(data) {
|
||||
const base64url = await new Promise((r) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => r(reader.result)
|
||||
reader.readAsDataURL(new Blob([data]))
|
||||
})
|
||||
return base64url.split(",", 2)[1]
|
||||
}
|
||||
|
||||
function base64ToArrayBuffer(str) {
|
||||
return Uint8Array.from(atob(str), c => c.charCodeAt(0));
|
||||
}
|
||||
|
||||
async function storeState(key, uuid) {
|
||||
console.log(key);
|
||||
let dataToStore = {
|
||||
"key": await arrayBufferToBase64(key),
|
||||
"uuid": uuid,
|
||||
"createdAt": Date.now()
|
||||
}
|
||||
window.localStorage[DB_NAME] = JSON.stringify(dataToStore);
|
||||
}
|
||||
|
||||
function retrieveState() {
|
||||
if (!window.localStorage || !window.localStorage[DB_NAME]) return false;
|
||||
let state = JSON.parse(window.localStorage[DB_NAME]);
|
||||
state["key"] = base64ToArrayBuffer(state["key"]);
|
||||
return state;
|
||||
}
|
||||
|
||||
function setStateFromKey(key, shouldGeneratePubParams) {
|
||||
console.log("Initializing...");
|
||||
window.key = key;
|
||||
window.client = initialize();
|
||||
console.log("done");
|
||||
console.log("Generating public parameters...");
|
||||
window.publicParameters = generate_keys(window.client, key, shouldGeneratePubParams);
|
||||
console.log(`done (${publicParameters.length} bytes)`);
|
||||
}
|
||||
|
||||
async function isStateValid(state) {
|
||||
console.log("Checking if cached state is still valid")
|
||||
if (Date.now() - state.createdAt > MAX_VALID_TIME) return false;
|
||||
|
||||
let isValidResponse = await api.check(state.uuid);
|
||||
let isValid = isValidResponse.is_valid;
|
||||
if (!isValid) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function setUpClient() {
|
||||
let state = retrieveState();
|
||||
if (state && await isStateValid(state)) {
|
||||
console.log("Loading previous client state")
|
||||
setStateFromKey(state.key, false);
|
||||
window.id = state.uuid;
|
||||
return true;
|
||||
} else {
|
||||
console.log("No state stored, generating new client state")
|
||||
let key = new Uint8Array(KEY_SIZE);
|
||||
self.crypto.getRandomValues(key);
|
||||
setStateFromKey(key, true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadState() {
|
||||
startLoading("Uploading setup data", true);
|
||||
console.log("Sending public parameters...");
|
||||
let setup_resp = await api.setup(window.publicParameters);
|
||||
console.log("sent.");
|
||||
let id = setup_resp["id"];
|
||||
stopLoading("Uploading setup data");
|
||||
await storeState(window.key, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
async function query(targetIdx, title) {
|
||||
if (!window.hasSetUp) {
|
||||
let id = await uploadState();
|
||||
if (!id) return false;
|
||||
window.hasSetUp = true;
|
||||
window.id = id;
|
||||
}
|
||||
|
||||
startLoading("Loading article");
|
||||
console.log("Generating query... ("+targetIdx+")");
|
||||
let query = generate_query(window.client, window.id, targetIdx);
|
||||
console.log(`done (${query.length} bytes)`);
|
||||
|
||||
console.log("Sending query...");
|
||||
let response = new Uint8Array(await api.query(query));
|
||||
console.log("sent.");
|
||||
|
||||
console.log(`done, got (${response.length} bytes)`);
|
||||
|
||||
console.log("Decoding result...");
|
||||
let result = decode_response(window.client, response)
|
||||
console.log("done.")
|
||||
console.log("Final result:")
|
||||
console.log(result);
|
||||
|
||||
let resultHtml = resultToHtml(result, title);
|
||||
|
||||
let outputArea = document.getElementById("output");
|
||||
outputArea.innerHTML = resultHtml;
|
||||
|
||||
enableLinks(outputArea);
|
||||
stopLoading("Loading article");
|
||||
}
|
||||
|
||||
async function queryTitle(targetTitle) {
|
||||
let redirectedTitle = followRedirects(targetTitle);
|
||||
let articleIndex = window.title_index[redirectedTitle];
|
||||
return await query(articleIndex, targetTitle);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
startLoading("Initializing");
|
||||
await init();
|
||||
stopLoading("Initializing");
|
||||
|
||||
window.numArticles = 65536;
|
||||
window.articleSize = 100000;
|
||||
|
||||
let makeQueryBtn = document.querySelector('#make_query');
|
||||
let searchBox = document.querySelector(".searchbox");
|
||||
document.querySelector(".sidebar-collapse-btn").onclick = () => {
|
||||
document.querySelector(".sidebar").classList.toggle("collapsed");
|
||||
}
|
||||
|
||||
startLoading("Loading article titles");
|
||||
let title_index_p = getData("data/enwiki-20220320-index.json", true);
|
||||
let redirect_backlinks_p = getData("data/redirects-old.json", true);
|
||||
let setupClientResult = setUpClient();
|
||||
|
||||
window.title_index = await title_index_p;
|
||||
let keys = Object.keys(window.title_index);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
window.title_index[key] /= window.articleSize;
|
||||
window.title_index[key.toLowerCase()] = window.title_index[key];
|
||||
}
|
||||
let redirect_backlinks = await redirect_backlinks_p;
|
||||
keys = Object.keys(redirect_backlinks);
|
||||
window.redirects = {}
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
let redirect_dest = keys[i];
|
||||
let redirect_srcs = redirect_backlinks[redirect_dest];
|
||||
for (var j = 0; j < redirect_srcs.length; j++) {
|
||||
window.redirects[redirect_srcs[j].toLowerCase()] = redirect_dest;
|
||||
}
|
||||
}
|
||||
|
||||
window.hasSetUp = await setupClientResult;
|
||||
stopLoading("Loading article titles");
|
||||
|
||||
searchBox.addEventListener('input', (e) => {
|
||||
clearExistingSuggestionsBox();
|
||||
|
||||
let search = e.target.value;
|
||||
if (search.length < 1) return;
|
||||
|
||||
var matching = Object.keys(window.title_index).filter((v) => v.startsWith(search));
|
||||
if (matching.length == 0) return;
|
||||
|
||||
matching.sort();
|
||||
if (matching.length > 10) matching = matching.slice(0, 10);
|
||||
|
||||
showSuggestionsBox(matching, search);
|
||||
})
|
||||
|
||||
makeQueryBtn.onclick = async () => {
|
||||
makeQueryBtn.disabled = true;
|
||||
await queryTitle(searchBox.value);
|
||||
makeQueryBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
run();
|
||||
|
||||
function setProgress(progress) {
|
||||
document.querySelector(".progress").style.background =
|
||||
"conic-gradient(#333 " +
|
||||
progress +
|
||||
"%,#fff " +
|
||||
progress +
|
||||
"%)";
|
||||
|
||||
// document.getElementById("middle-circle").innerHTML =
|
||||
// progress.toString() + "%";
|
||||
}
|
||||
window.setProgress = setProgress;
|
||||
@@ -1 +0,0 @@
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wtfHtml=e()}(this,(function(){"use strict";const t={title:!0,infoboxes:!0,headers:!0,sections:!0,links:!0},e=function(e){let n="";return!0===(e=Object.assign({},t,e)).title&&this._title&&(n+="<title>"+this._title+"</title>\n"),!0===this.isRedirect()?(n+=function(t){let e=t.redirectTo(),n=e.page;return n="./"+n.replace(/ /g,"_"),e.anchor&&(n+="#"+e.anchor),` <div class="redirect">\n ↳ <a class="link" href="./${n}">${e.text}</a>\n </div>`}(this),n):(!0===e.infoboxes&&(n+=this.infoboxes().map((t=>t.html(e))).join("\n")),!0!==e.sections&&!0!==e.paragraphs&&!0!==e.sentences||(n+=this.sections().map((t=>t.html(e))).join("\n")),!0===e.references&&(n+="<h2>References</h2>",n+=this.references().map((t=>t.html(e))).join("\n")),n)},n={headers:!0,images:!0,tables:!0,lists:!0,paragraphs:!0},i=function(t){t=Object.assign({},n,t);let e="";if(!0===t.headers&&this.title()){let t=1+this.depth();e+=" <h"+t+">"+this.title()+"</h"+t+">",e+="\n"}if(!0===t.images){let n=this.images();n.length>0&&(e+=n.map((e=>e.html(t))).join("\n"))}return!0===t.tables&&(e+=this.tables().map((e=>e.html(t))).join("\n")),!0===t.lists&&(e+=this.lists().map((e=>e.html(t))).join("\n")),!0===t.paragraphs&&this.paragraphs().length>0?(e+=' <div class="text">\n',this.paragraphs().forEach((n=>{e+=' <p class="paragraph">\n',e+=" "+n.sentences().map((e=>e.html(t))).join(" "),e+="\n </p>\n"})),e+=" </div>\n"):!0===t.sentences&&(e+=" "+this.sentences().map((e=>e.html(t))).join(" ")),'<div class="section">\n'+e+"</div>\n"},s={sentences:!0},a=function(t){let e="";return!0===(t=Object.assign({},s,t)).sentences&&(e+=this.sentences().map((e=>e.html(t))).join("\n")),e};const l=function(t,e,n){if(!e||!t)return t;"number"==typeof t&&(t=String(t)),e=e.replace(/[\-[\]/{}()*+?.\\^$|]/g,"\\$&");let i=new RegExp("\\b"+e+"\\b");return t=!0===i.test(t)?t.replace(i,n):t.replace(e,n)},r={links:!0,formatting:!0},h=function(t){t=Object.assign({},r,t);let e=this.text();return!0===t.links&&this.links().forEach((t=>{let n=t.text()||t.page(),i=t.html();e=l(e,n,i)})),!0===t.formatting&&(this.bolds().forEach((t=>{e=l(e,t,"<b>"+t+"</b>")})),this.italics().forEach((t=>{e=l(e,t,"<i>"+t+"</i>")}))),'<span class="sentence">'+e+"</span>"},o=function(){let t=this.href();return t=t.replace(/ /g,"_"),`<a class="link" href="${t}">${this.text()||this.page()}</a>`},c={images:!0},d={image:!0,caption:!0,alt:!0,signature:!0,"signature alt":!0},p=function(t){t=Object.assign({},c,t);let e='<table class="infobox">\n';if(e+=" <thead>\n",e+=" </thead>\n",e+=" <tbody>\n",!0===t.images&&this.data.image){if(e+=" <tr>\n",e+=' <td colspan="2" style="text-align:center">\n',e+=" "+this.image().html()+"\n",e+=" </td>\n",this.data.caption||this.data.alt){let n=this.data.caption?this.data.caption.html(t):this.data.alt.html(t);e+=' <td colspan="2" style="text-align:center">\n',e+=" "+n+"\n",e+=" </td>\n"}e+=" </tr>\n"}return Object.keys(this.data).forEach((n=>{if(!0===d[n])return;let i=this.data[n],s=n.replace(/_/g," ");s=s.charAt(0).toUpperCase()+s.substring(1);let a=i.html(t);e+=" <tr>\n",e+=" <td>"+s+"</td>\n",e+=" <td>"+a+"</td>\n",e+=" </tr>\n"})),e+=" </tbody>\n",e+="</table>\n",e},f=function(){return' <img src="'+this.thumbnail()+'" alt="'+this.alt()+'"/>'},m=function(t){let e=' <ul class="list">\n';return this.lines().forEach((n=>{e+=" <li>"+n.html(t)+"</li>\n"})),e+=" </ul>\n",e},u=function(t){if(this.data&&this.data.url&&this.data.title){let e=this.data.title;return!0===t.links&&(e=`<a href="${this.data.url}">${e}</a>`),`<div class="reference">⌃ ${e} </div>`}if(this.data.encyclopedia)return`<div class="reference">⌃ ${this.data.encyclopedia}</div>`;if(this.data.title){let t=this.data.title;return this.data.author&&(t+=this.data.author),this.data.first&&this.data.last&&(t+=this.data.first+" "+this.data.last),`<div class="reference">⌃ ${t}</div>`}return this.inline?`<div class="reference">⌃ ${this.inline.html()}</div>`:""},g=function(t){let e=this.data,n='<table class="table">\n';return n+=" <thead>\n",n+=" <tr>\n",Object.keys(e[0]).forEach((t=>{!0!==/^col[0-9]/.test(t)&&(n+=" <td>"+t+"</td>\n")})),n+=" </tr>\n",n+=" </thead>\n",n+=" <tbody>\n",e.forEach((e=>{n+=" <tr>\n",Object.keys(e).forEach((i=>{let s=e[i].html(t);n+=" <td>"+s+"</td>\n"})),n+=" </tr>\n"})),n+=" </tbody>\n",n+="</table>\n",n};return function(t){t.Doc.prototype.html=e,t.Section.prototype.html=i,t.Paragraph.prototype.html=a,t.Sentence.prototype.html=h,t.Image.prototype.html=f,t.Infobox.prototype.html=p,t.Link.prototype.html=o,t.List.prototype.html=m,t.Reference.prototype.html=u,t.Table.prototype.html=g}}));
|
||||
File diff suppressed because one or more lines are too long
@@ -1,13 +0,0 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
||||
3
docs/.gitignore
vendored
Normal file
3
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.docusaurus
|
||||
docs/api/
|
||||
build/
|
||||
3
docs/babel.config.js
Normal file
3
docs/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')]
|
||||
};
|
||||
70
docs/docs/buckets.mdx
Normal file
70
docs/docs/buckets.mdx
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Buckets
|
||||
|
||||
A Blyss bucket is a key-value store: it allows you to write data in key-value pairs, and then later retrieve values using keys.
|
||||
|
||||
## Operations
|
||||
|
||||
Clients can **create** and **destroy** buckets. Bucket destruction is permanent, and all data inside the bucket is deleted.
|
||||
|
||||
Clients can modify the data inside a bucket with:
|
||||
|
||||
- **Write**: Sets the given key-value pairs in the bucket, overriding any previous values.
|
||||
- **Delete Key**: Removes a key and its corresponding value from the bucket.
|
||||
|
||||
Clients can read the data inside a bucket with:
|
||||
|
||||
- **Private Read**: Gets the value in the bucket corresponding to the given key, privately.
|
||||
- **List Keys**: Gets a list of all the keys in the bucket.
|
||||
|
||||
## Consistency
|
||||
|
||||
All writes are idempotent. Once a write completes successfully, subsequent reads will reflect the updated value. While a write is in progress, reads may return the new or old value.
|
||||
|
||||
## Names
|
||||
|
||||
Bucket names must be 1-128 characters long, composed of only lowercase letters (`[a-z]`), digits (`[0-9]`), and hyphens (`-`).
|
||||
|
||||
Blyss buckets exist in account-local namespaces by default. You can name buckets whatever you want (subject to the basic format rules above), and they will only be visible to API keys owned by your Blyss account.
|
||||
|
||||
### Global Buckets
|
||||
|
||||
If you want to share Blyss buckets across accounts, you can opt-in to the global Blyss namespace by prefixing your bucket name with `global.`. This is the only way in which the `.` character is allowed in bucket names. The prefix counts towards the bucket name length limit. Global buckets must be uniquely named.
|
||||
|
||||
## Permissions
|
||||
|
||||
Blyss buckets are private by default. Permissions get granted to API keys, and can be limited in scope to three levels: read, write, and admin. One API key is generated when you sign up, and an additional read-only API key is generated for each bucket you create. When you create a bucket using an API key, that API key gets Admin permissions for that bucket.
|
||||
|
||||
### Read
|
||||
Permits idempotent, read-only operations, including listing keys and reading values.
|
||||
|
||||
### Write
|
||||
Permits write operations, such as adding, modifying, or deleting key-value pairs.
|
||||
(includes all permissions in Read)
|
||||
|
||||
### Admin
|
||||
Permits modification of permissions related to this bucket, and bucket destruction.
|
||||
(includes all permissions in Write)
|
||||
|
||||
### Open Access
|
||||
Buckets intended for widespread consumption can enable open-access mode. An open-access bucket automatically grants Read permissions to every Blyss API key.
|
||||
|
||||
|
||||
## Limits
|
||||
|
||||
These are the service-imposed size and format limits on buckets and the data they contain.
|
||||
|
||||
### Keys
|
||||
|
||||
Keys can be any UTF-8 serializable string of length 1-1024, inclusive.
|
||||
|
||||
### Values
|
||||
|
||||
Values can be arbitrary bytes, up to a length limit dependent on bucket configuration.
|
||||
|
||||
### Configuration
|
||||
|
||||
Blyss buckets are configured upon creation for a maximum total size, maximum value size, and maximum number of entries. In the Free Tier, buckets are configured with a 1 GB maximum total size, 1 KB maximum value size, and 1 million items maximum. These limits are subject to change. For larger configurations, please [contact us](#TODO).
|
||||
114
docs/docs/how-it-works.mdx
Normal file
114
docs/docs/how-it-works.mdx
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
import Center from "@site/src/components/Center";
|
||||
import SecurityModel from "/img/security-model.svg";
|
||||
import SepiaExample from "/img/sepia-example.svg";
|
||||
import PIRFromFHE from "/img/pir-from-fhe.svg";
|
||||
|
||||
# How it works
|
||||
|
||||
The Blyss bucket service is a key-value store with private retrievals. Clients can use the Blyss SDK to create buckets, write data to them, and make private retrievals from them.
|
||||
|
||||
In this document, we'll explain what private retrieval is, and how the Blyss service makes it possible at a high level. For a more complete explanation, including the underlying mathematics, [check out this blog post](https://blintzbase.com/posts/pir-and-fhe-from-scratch/).
|
||||
|
||||
## Goal: private retrieval
|
||||
|
||||
The security guarantee of a _private retrieval_ is that no entity, not even the Blyss bucket service itself, can determine the key being retrieved. The security model assumes from the start that the Blyss bucket service, and all other entities outside of the client's device, are _completely untrusted_.
|
||||
|
||||
<Center space={10}>
|
||||
<SecurityModel />
|
||||
</Center>
|
||||
|
||||
<!-- This is an unusually strong threat model for a key-value store. Typically, when using services like [Amazon S3](https://aws.amazon.com/s3/) or a managed [Redis](https://redis.io/), the server learns information about every retrieval, keeping logs of what was retrieved when. The extent of this logging is opaque and can change unilaterally. -->
|
||||
|
||||
Private retrievals were essentially **impossible** until recently. Data stores like [Amazon S3](https://aws.amazon.com/s3/), [Firebase](https://firebase.google.com/), or [Redis](https://redis.io/) cannot offer private retrievals - they must know the item being retrieved so that they can fetch it from memory or disk. The only way to perform private retrievals from these services is to download the _entire_ database.
|
||||
|
||||
Blyss uses advancements in state-of-the-art cryptography to build the first generally available key-value store with private retrievals.
|
||||
|
||||
<!-- The Blyss security model is unique: we _cannot_ learn information about retrievals. -->
|
||||
|
||||
<!-- This is a powerful security property, and can be used to build new kinds of privacy-preserving apps. -->
|
||||
|
||||
<!-- relies on the secure implementation of cryptographic primitives in the Blyss SDK, which is open source. -->
|
||||
|
||||
## Tool: homomorphic encryption
|
||||
|
||||
The cryptography that powers Blyss is called "homomorphic encryption". First, some history: homomorphic encryption was a 'holy grail' for academic cryptographers for over 20 years, until it was finally constructed for the first time by Craig Gentry in 2009. Early schemes were comically slow: it could take hours to encrypt small amounts of data. Thanks to hard work from a large set of academic cryptographers, homomorphic encryption has _finally_ become fast enough in the last two years.
|
||||
|
||||
What kind of encryption could so fascinate people that they spend decades working to build it? Well, basically, homomorphic encryption lets you **perform computation on encrypted data**. This is really unique: normally, when you encrypt something, it gets totally garbled. If you tried to perform computation on normally encrypted data, you would get meaningless garbage.
|
||||
|
||||
<Center space={10}>
|
||||
<SepiaExample />
|
||||
</Center>
|
||||
|
||||
With homomorphic encryption, everything still gets garbled, but you can do strange and wonderful new things. For example, if you _homomorphically_ encrypt an image, and send the encrypted image to an untrusted server, the server can apply filters (e.g. 'sepia' or 'background blur') _directly to the encrypted image_. Everything stays encrypted the whole time - the server never gets to see the original or filtered image - but you get back an encrypted version of the filtered image. It's almost like the sever has a blindfold on, and is able to 'do its job' without learning any inputs or outputs. This is totally impossible with normal encryption, and opens up a whole new world of services and applications that can serve end users without learning their sensitve data.
|
||||
|
||||
<!-- For example, you can now [read Wikipedia using homomorphic encryption](https://spiralwiki.com), something completely unthinkable just 10 years ago. -->
|
||||
|
||||
<!-- So, what exactly is homomorphic encrpytion? The basic property of homomorphic encryption is that it allows someone to compute on encrypted data, without learning the true values of the underlying data. For example, a client could homomorphically encrypt an image, and send the encrypted image to an untrusted server. The sever could apply a filter to the encrypted image and get an encrypted result. Finally, the server could send this encrypted result back to the client, who could decrypt it and see the filtered image. The key feature of homomorphic encryption is that all of this can happen without the server ever seeing the image you uploaded. -->
|
||||
|
||||
:::info
|
||||
|
||||
Homomorphic encryption is an emerging technology, and is still in the process of [being standardized](https://homomorphicencryption.org/standard/). The underlying cryptographic assumptions are [well-founded](https://en.wikipedia.org/wiki/Lattice-based_cryptography) and have been extensively analyzed as a byproduct of [efforts to standardize post-quantum cryptography](https://csrc.nist.gov/Projects/post-quantum-cryptography/post-quantum-cryptography-standardization), but it is not yet available in libraries like OpenSSL and OS's.
|
||||
|
||||
:::
|
||||
|
||||
## Private retrievals using homomorphic encryption
|
||||
|
||||
So, how does Blyss actually use homomorphic encryption to enable private retrievals? Here's a high-level summary:
|
||||
|
||||
<Center space={10}>
|
||||
<PIRFromFHE />
|
||||
</Center>
|
||||
|
||||
1. The client homomorphically encrypts a large vector of 0's and 1's. The vector has encrypted 0's in almost every entry, except that it has '1' in the entry corresponding to the item we are trying to retrieve. The client sends this large vector of encrypted 0's and 1's as the query.
|
||||
:::info
|
||||
|
||||
The client uploads a series of encrypted 0's and 1's, but the server cannot tell which are 0, and which are 1; each encryption is unique random-looking.
|
||||
|
||||
:::
|
||||
|
||||
2. The server computes a **dot-product** between the data in the bucket and the query. Specifically, it multiplies each item in the bucket by the corresponding encrypted 0 or 1 in the query, and then adds up all the results. All the non-desired items will get multiplied by zero, so we're left with only the client's desired item. Thanks to the special properties of homomorphic encryption, all of this happens _without the server learning anything_ about which item the client wanted!
|
||||
|
||||
3. The server sends back this final encryption, and the client decrypts it, getting the item that it wanted.
|
||||
|
||||
Basically, we can "select" an item from the bucket using 0's and 1's, and homomorphic encryption lets the server do that while "blindfolded" - without learning which value was 1. This is an abridged version of what is happening under the hood: Blyss uses cutting-edge schemes that compress the query significantly and structure the database as many-dimensional. The [underlying cryptographic schemes](https://eprint.iacr.org/2022/368.pdf) are peer-reviewed and were [presented at IEEE S&P 2022](https://www.ieee-security.org/TC/SP2022/program-papers.html#:~:text=Spiral%3A%20Fast%2C%20High%2DRate%20Single%2DServer%20PIR%20via%20FHE%20Composition).
|
||||
|
||||
## SDK
|
||||
|
||||
To perform a private retrieval, you need to homomorphically encrypt 0's and 1's. Since homomorphic encryption is not (yet) a part of standard cryptography toolkits like OpenSSL, we distribute an open-source SDK that provides this functionality. Our implementation of all cryptography is written in Rust, with basic side-channel mitigations. As this space matures, we hope to encourage multiple open-source implementations of a common private retrieval protocol. The SDK is versioned, open source, and has reproducible builds, mitigating the potential for a malicious actor to inject compromised SDK code into the software supply chain.
|
||||
|
||||
<!-- Bugs in this SDK do have the potential to compromise query privacy - that's why the SDK is open source, and w -->
|
||||
|
||||
<!-- To fulfill this goal, clients use **homomorphic encryption** to hide the retrievals they make from a bucket. Homomorphic encryption is a new kind of encryption that enables computation on encrypted data - you can read more about -->
|
||||
|
||||
<!-- relies on the secure implementation of cryptographic primitives in the Blyss SDK, which is open source. -->
|
||||
|
||||
<!-- Our security goal for client queries is a complete, formal statement: absolutely _no information_ about which key the client is retrieving should be possible for the Blyss service to obtain. -->
|
||||
|
||||
<!-- The system boundary is the set of functions that the Blyss SDK exposes. Client operation outside these functions could always in theory leak information (e.g. malware). You can read more tips on how to architect privacy-preserving apps in [this article](../making-apps). -->
|
||||
|
||||
<!-- - Secure implementation of the Blyss SDK
|
||||
- Cryptographic security of the underlying homomorphic encryption scheme -->
|
||||
|
||||
<!-- Notably, the Blyss system’s primary security goal does **_not_** rely on a secure implementation of the Blyss service. The clients retrievals remain private even if the service is compromised. -->
|
||||
|
||||
<!-- ## Architecture
|
||||
|
||||
### Client
|
||||
|
||||
The client uses the **Blyss SDK** to access the Blyss service. The SDK is available in multiple languages and is open source. All core cryptographic operations are written in Rust, and then compiled for many runtimes and architectures.
|
||||
|
||||
### Service
|
||||
|
||||
T
|
||||
|
||||
### API Keys
|
||||
|
||||
When you sign up for the Blyss service, you get an initial **API key**. This key allows you to create buckets, write items to buckets, and privately retrieve items from buckets. This API key serves to enforce permissions and rate-limiting on data in Blyss buckets; it is \***\*not\*\*** used to encrypt private retrievals, and if leaked, it does _not_ compromise the security of past queries.
|
||||
|
||||
From the **Blyss admin console**, you can create, list, and revoke API keys.
|
||||
|
||||
### -->
|
||||
37
docs/docs/index.mdx
Normal file
37
docs/docs/index.mdx
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
import Center from "@site/src/components/Center";
|
||||
import BasicExample from "/img/basicexample.png";
|
||||
|
||||
# Getting started
|
||||
|
||||
The Blyss bucket service is a key-value store (similar to Amazon S3 or Redis) that allows private retrievals using homomorphic encryption.
|
||||
|
||||
The special privacy property of Blyss is that retrieval from a bucket is _completely_ private. No entity, _not even the Blyss service itself_, can determine what key a client retrieves. This property is cryptographic; it's not based on the security of special hardware enclaves or trusted third parties. You can read more about the security properties in ["How it works"](./how-it-works).
|
||||
|
||||
You can use the Blyss bucket service to build fundamentally new kinds of privacy-preserving applications that collect less information from users.
|
||||
|
||||
:::caution
|
||||
|
||||
The Blyss bucket service is in beta. Data stored in Blyss should not be considered durable. [Contact us](mailto:founders@blyss.dev) if you need access to a higher level of service.
|
||||
|
||||
:::
|
||||
|
||||
### Get your API key
|
||||
|
||||
To get an API key, [sign up here](https://blyss.dev/auth/sign-up). You will see an API key in the [Blyss Console](https://blyss.dev/console/apikeys) once you log in.
|
||||
|
||||
An API key allows you to create buckets and read and write to them. Your API key does not encrypt anything - it is only used for rate-limiting and enforcing bucket ownership.
|
||||
|
||||
### Use the SDK to make private retrievals
|
||||
|
||||
To make queries against a Blyss bucket, you will need the **Blyss SDK** for your preferred programming language. Currently, only JavaScript/TypeScript is supported.
|
||||
|
||||
You can try the SDK directly from your browser using [our CodeSandbox](#).
|
||||
|
||||
To use the SDK in an existing project, check out [our repository](#).
|
||||
29
docs/docs/performance.mdx
Normal file
29
docs/docs/performance.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Performance
|
||||
|
||||
The Blyss bucket service has unique performance characteristics, detailed here.
|
||||
|
||||
## First-Retrieval Overhead
|
||||
|
||||
The first private retrieval that a client makes against a bucket requires more upload and/or download communication than baseline. Later queries from the same client do not incur this first-retrieval cost.
|
||||
|
||||
If your application allows it, it is highly recommended that you:
|
||||
|
||||
1. Serialize the client state to a secret seed using `toSecretSeed()`.
|
||||
2. Store this secret seed somewhere durable and secure. For example, on the web, you could store this in `localStorage`.
|
||||
3. When starting fresh later, first attempt to restore state from the secret seed. If this fails, you can always just construct a new client from scratch.
|
||||
|
||||
This pattern allows clients to skip the time-consuming first-retrieval overhead when possible.
|
||||
|
||||
## Writes
|
||||
|
||||
Writes to a bucket are generally similar in speed to writes to a standard key-value store.
|
||||
|
||||
Writes are significantly faster when batched - whenever possible, group multiple writes into a single API call.
|
||||
|
||||
## Private Retrievals
|
||||
|
||||
Private retrievals from a bucket are generally slower and take more communication than normal retrievals from a key-value store. Depending on the application, this overhead can be significant or negligible.
|
||||
33
docs/docs/privacy-model.mdx
Normal file
33
docs/docs/privacy-model.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Privacy model
|
||||
|
||||
## Client privacy
|
||||
|
||||
The primary goal of the Blyss service is to minimize what the server learns about clients.
|
||||
There are some limitations to our guarantees:
|
||||
1. Client writes are not private.
|
||||
2. The amount of data in a bucket is not private.
|
||||
3. The timing and quantities of retrievals made by clients are not private.
|
||||
|
||||
See the best practices below for guidance on how to work around these limitations.
|
||||
|
||||
## Server privacy
|
||||
|
||||
Some applications benefit from minimizing what clients learn about the server's contents.
|
||||
|
||||
A standard Blyss bucket allows any client with read access to retrieve all values. To restrict clients ability to dump bucket contents, query caps can be used: for example, you could issue each client an API key with a fixed quota of allowed queries. However, Blyss buckets respond to private queries with a payload that could contain more values than just that of the queried key. If it is crucial to limit the ability of clients to dumpt the database, there are alternative solutions; contact us for more details.
|
||||
|
||||
## Data privacy
|
||||
|
||||
By default, the Blyss service sees the contents of items that you put into a Blyss bucket. To enable end-to-end encryption for a bucket, you need to manage an encryption key known only to your users or service, and encrypt every value written to the bucket. An automated way to enable this is in development; contact us for more details.
|
||||
|
||||
## Best practices
|
||||
|
||||
There are some basic best practices for delivering privacy to end users:
|
||||
|
||||
- Since writes are not private, writes and private retrievals should generally be decoupled. The best way to achieve this is through a "producer/consumer" model: a "producer" server writes new data to the bucket, while several end-user "consumer" clients periodically perform private retrievals on the bucket.
|
||||
- When creating a bucket, choose a max item size that will accomodate the largest item you expect to store. If you must store larger items later, create a new bucket with a larger max item size. It's generally a bad practice to store larger items by sharding them across multiple keys, since clients who make bursts of reads to retrieve large items may leak size metadata.
|
||||
- In the privacy model for your app, make sure to account for the fact that the timing of client reads is *not* private.
|
||||
93
docs/docusaurus.config.js
Normal file
93
docs/docusaurus.config.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
||||
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'Blyss Docs',
|
||||
tagline: 'Retrieve data privately using homomorphic encryption.',
|
||||
url: 'https://docs.blyss.dev',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en']
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'@docusaurus/preset-classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
{
|
||||
docs: {
|
||||
sidebarPath: require.resolve('./sidebars.js'),
|
||||
routeBasePath: 'docs'
|
||||
},
|
||||
blog: false,
|
||||
theme: {
|
||||
customCss: require.resolve('./src/css/custom.css')
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
plugins: [
|
||||
[
|
||||
'docusaurus-plugin-typedoc-api',
|
||||
|
||||
// Plugin / TypeDoc options
|
||||
{
|
||||
projectRoot: path.join(__dirname, '../'),
|
||||
packages: [
|
||||
{
|
||||
path: '.',
|
||||
entry: 'js/index.ts'
|
||||
}
|
||||
],
|
||||
gitRefName: 'main',
|
||||
typedocOptions: {
|
||||
excludePrivate: true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
{
|
||||
navbar: {
|
||||
title: 'blyss',
|
||||
items: [
|
||||
{
|
||||
to: 'docs',
|
||||
label: 'Docs',
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
to: 'api',
|
||||
label: 'API',
|
||||
position: 'left'
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
copyright: `Copyright © ${new Date().getFullYear()}, Blyss`
|
||||
},
|
||||
prism: {
|
||||
theme: lightCodeTheme,
|
||||
darkTheme: darkCodeTheme
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
12775
docs/package-lock.json
generated
Normal file
12775
docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
docs/package.json
Normal file
33
docs/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@docusaurus/types": "^2.3.0",
|
||||
"@tsconfig/docusaurus": "^1.0.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"docusaurus-plugin-typedoc-api": "^2.5.1",
|
||||
"typedoc": "^0.23.24"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.3.0",
|
||||
"@docusaurus/preset-classic": "^2.3.0",
|
||||
"prism-react-renderer": "^1.3.5"
|
||||
}
|
||||
}
|
||||
36
docs/sidebars.js
Normal file
36
docs/sidebars.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/* eslint-env node */
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
*
|
||||
* - Create an ordered group of docs
|
||||
* - Render a sidebar for each doc of that group
|
||||
* - Provide next/previous navigation
|
||||
*
|
||||
* The sidebars can be generated from the filesystem, or explicitly defined
|
||||
* here.
|
||||
*
|
||||
* Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }]
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
tutorialSidebar: [
|
||||
'intro',
|
||||
'hello',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['tutorial-basics/create-a-document'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
||||
|
||||
module.exports = sidebars;
|
||||
20
docs/src/components/Center.tsx
Normal file
20
docs/src/components/Center.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Center({ children, space }) {
|
||||
space = space || 0;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
paddingTop: space,
|
||||
paddingBottom: space,
|
||||
marginBottom: 'var(--ifm-paragraph-margin-bottom)'
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
298
docs/src/css/custom.css
Normal file
298
docs/src/css/custom.css
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url(https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600');
|
||||
|
||||
|
||||
:root {
|
||||
--ifm-color-primary: #e4637c;
|
||||
--ifm-color-primary-dark: #df4765;
|
||||
--ifm-color-primary-darker: #dd3959;
|
||||
--ifm-color-primary-darkest: #c32241;
|
||||
--ifm-color-primary-light: #e97f93;
|
||||
--ifm-color-primary-lighter: #eb8d9f;
|
||||
--ifm-color-primary-lightest: #f2b7c2;
|
||||
--ifm-navbar-link-hover-color: var(--ifm-color-primary);
|
||||
--ifm-menu-color-background-active: transparent;
|
||||
--ifm-menu-color-background-hover: transparent;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
|
||||
--ifm-font-family-base: "Inter", sans-serif;
|
||||
|
||||
--ifm-breadcrumb-separator: "none";
|
||||
--ifm-breadcrumb-item-background-active: "none";
|
||||
--ifm-breadcrumb-padding-horizontal: 4px;
|
||||
--ifm-breadcrumb-spacing: 2px;
|
||||
}
|
||||
|
||||
.menu__link:hover {
|
||||
color: var(--ifm-menu-color-active);
|
||||
}
|
||||
|
||||
.menu__link--sublist.menu__link--active {
|
||||
color: var(--ifm-menu-color);
|
||||
}
|
||||
|
||||
.menu__link--sublist.menu__link--active:hover {
|
||||
color: var(--ifm-menu-color-active);
|
||||
}
|
||||
|
||||
.menu__list-item-collapsible--active > .menu__link--sublist.menu__link--active {
|
||||
color: var(--ifm-menu-color-active);
|
||||
}
|
||||
|
||||
.tabs__item {
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
.tabs__item:hover {
|
||||
background: none;
|
||||
color: var(--ifm-tabs-color-active);
|
||||
}
|
||||
|
||||
.table-of-contents {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.table-of-contents:before {
|
||||
content: "On This Page";
|
||||
text-transform: uppercase;
|
||||
color: #000;
|
||||
font-weight: 800;
|
||||
margin: 0 var(--ifm-toc-padding-horizontal) 0.5rem var(--ifm-toc-padding-horizontal);
|
||||
display: inline-block;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.toc-wrapper {
|
||||
position: sticky;
|
||||
overflow-y: auto;
|
||||
top: calc(var(--ifm-navbar-height) + 50px);
|
||||
max-height: calc(100vh - (var(--ifm-navbar-height) - 2rem));
|
||||
}
|
||||
|
||||
.toc-wrapper > div {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.theme-admonition svg {
|
||||
display: none !important;
|
||||
}
|
||||
.theme-admonition span {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.theme-admonition > div:first-child {
|
||||
display: none;
|
||||
/* margin-bottom: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 0.9em; */
|
||||
}
|
||||
|
||||
.navbar__link--active {
|
||||
color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
nav.menu {
|
||||
padding-top: 48px;
|
||||
}
|
||||
|
||||
article h1 {
|
||||
font-size: 2.25rem;
|
||||
line-height: 110%;
|
||||
}
|
||||
|
||||
article h2 {
|
||||
font-size: 1.75rem;
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
article h3 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
article {
|
||||
padding: 0 24px 0 24px;
|
||||
}
|
||||
|
||||
main > .container {
|
||||
padding-top: 48px !important;
|
||||
}
|
||||
|
||||
.breadcrumbs__item:not(:last-child):after {
|
||||
content: "/";
|
||||
}
|
||||
.breadcrumbs__item:first-child a svg {
|
||||
display: none;
|
||||
}
|
||||
.breadcrumbs__item:first-child a:before {
|
||||
content: "Docs";
|
||||
display: inline-block;
|
||||
}
|
||||
nav.theme-doc-breadcrumbs {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #eb8d9f;
|
||||
--ifm-color-primary-dark: #e56d84;
|
||||
--ifm-color-primary-darker: #e35d77;
|
||||
--ifm-color-primary-darkest: #da2d4e;
|
||||
--ifm-color-primary-light: #f1adba;
|
||||
--ifm-color-primary-lighter: #f3bdc7;
|
||||
--ifm-color-primary-lightest: #fcedf0;
|
||||
}
|
||||
|
||||
.navbar__brand {
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
height: 42px;
|
||||
font-family: Source Sans Pro;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-size: 24px;
|
||||
letter-spacing: normal;
|
||||
margin-right: 1rem;
|
||||
width: 80px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.navbar__title {
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
height: 42px;
|
||||
font-family: Source Sans Pro;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
letter-spacing: -0.04em;
|
||||
color: #F68E9D;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0px 40px 40px -40px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
|
||||
.menu__caret::before {
|
||||
transition: transform var(--ifm-transition-fast) var(--ifm-transition-timing-default);
|
||||
background: var(--ifm-menu-link-sublist-icon) 50% / 1.75rem 1.75rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
main > .container {
|
||||
padding-top: 16px !important;
|
||||
}
|
||||
article {
|
||||
padding: 0 8px;
|
||||
}
|
||||
nav.theme-doc-breadcrumbs {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-doc-toc-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-back-to-top-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: calc(100vh - var(--ifm-navbar-height)) !important;
|
||||
}
|
||||
|
||||
.footer__copyright {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #dee2e6;
|
||||
min-height: 250px;
|
||||
--ifm-footer-color: #5c5f66;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-logo-tagline {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.footer .navbar__brand {
|
||||
margin: 0;
|
||||
width: unset;
|
||||
height: 54px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.footer .navbar__brand::before {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
footer.footer {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
footer.footer > .container {
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.footer-logo .navbar__title {
|
||||
font-size: 36px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
color: #A6A7AB;
|
||||
}
|
||||
|
||||
.navbar__toggle {
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
|
||||
.navbar-sidebar__back {
|
||||
padding: 0.6rem 1.3rem;
|
||||
background: var(--ifm-hover-overlay);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
24
docs/src/pages/index.module.css
Normal file
24
docs/src/pages/index.module.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
*/
|
||||
|
||||
.heroBanner {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #F68E9D;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.heroBanner {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
6
docs/src/pages/index.tsx
Normal file
6
docs/src/pages/index.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Redirect } from '@docusaurus/router';
|
||||
import React from 'react';
|
||||
|
||||
export default function Home() {
|
||||
return <Redirect to="/docs" />;
|
||||
}
|
||||
7
docs/src/pages/markdown-page.md
Normal file
7
docs/src/pages/markdown-page.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Markdown page example
|
||||
---
|
||||
|
||||
# Markdown page example
|
||||
|
||||
You don't need React to write simple standalone pages.
|
||||
43
docs/src/theme/Footer/index.tsx
Normal file
43
docs/src/theme/Footer/index.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useThemeConfig } from '@docusaurus/theme-common';
|
||||
import FooterCopyright from '@theme/Footer/Copyright';
|
||||
import FooterLayout from '@theme/Footer/Layout';
|
||||
import React from 'react';
|
||||
|
||||
function Footer() {
|
||||
const { footer } = useThemeConfig();
|
||||
if (!footer) {
|
||||
return null;
|
||||
}
|
||||
let { copyright, style } = footer;
|
||||
|
||||
const elem = (
|
||||
<div className="footer-wrapper">
|
||||
<div className="footer-logo-tagline">
|
||||
<div className="footer-logo">
|
||||
<div className="navbar__title">blyss</div>
|
||||
</div>
|
||||
<div className="footer-tagline">
|
||||
The next generation of privacy. Today. <br />
|
||||
|
||||
</div>
|
||||
<div className="footer-copyright">© 2023 Blyss, Inc.</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// links={links && links.length > 0 && <FooterLinks links={links} />}
|
||||
return (
|
||||
<FooterLayout
|
||||
style={style}
|
||||
logo={elem}
|
||||
links={null}
|
||||
copyright={
|
||||
copyright && (
|
||||
<div style={{ fontSize: 8 }}>
|
||||
<FooterCopyright copyright={copyright} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export default React.memo(Footer);
|
||||
10
docs/src/theme/Icon/ExternalLink/index.tsx
Normal file
10
docs/src/theme/Icon/ExternalLink/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import ExternalLink from '@theme-original/Icon/ExternalLink';
|
||||
import React from 'react';
|
||||
|
||||
export default function ExternalLinkWrapper(props) {
|
||||
return (
|
||||
<div style={{ display: 'none' }}>
|
||||
<ExternalLink {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
docs/src/theme/Navbar/ColorModeToggle/index.tsx
Normal file
10
docs/src/theme/Navbar/ColorModeToggle/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import ColorModeToggle from '@theme-original/Navbar/ColorModeToggle';
|
||||
import React from 'react';
|
||||
|
||||
export default function ColorModeToggleWrapper(props) {
|
||||
return (
|
||||
<div style={{ display: 'none' }}>
|
||||
<ColorModeToggle {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
docs/src/theme/TOC/index.tsx
Normal file
10
docs/src/theme/TOC/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import TOC from '@theme-original/TOC';
|
||||
import React from 'react';
|
||||
|
||||
export default function TOCWrapper(props) {
|
||||
return (
|
||||
<div className="toc-wrapper">
|
||||
<TOC {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
0
docs/static/.nojekyll
vendored
Normal file
0
docs/static/.nojekyll
vendored
Normal file
BIN
docs/static/img/basicexample.png
vendored
Normal file
BIN
docs/static/img/basicexample.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/static/img/favicon.ico
vendored
Normal file
BIN
docs/static/img/favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
1
docs/static/img/logo.svg
vendored
Normal file
1
docs/static/img/logo.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.3 KiB |
139
docs/static/img/pir-from-fhe.svg
vendored
Normal file
139
docs/static/img/pir-from-fhe.svg
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
<svg width="374" height="278" viewBox="0 0 374 278" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="374" height="278" fill="white"/>
|
||||
<path d="M169.061 93.0607C169.646 92.4749 169.646 91.5251 169.061 90.9393L159.515 81.3934C158.929 80.8076 157.979 80.8076 157.393 81.3934C156.808 81.9792 156.808 82.9289 157.393 83.5147L165.879 92L157.393 100.485C156.808 101.071 156.808 102.021 157.393 102.607C157.979 103.192 158.929 103.192 159.515 102.607L169.061 93.0607ZM93 93.5H168V90.5H93V93.5Z" fill="#C7C7C7"/>
|
||||
<path d="M91.9393 174.939C91.3536 175.525 91.3536 176.475 91.9393 177.061L101.485 186.607C102.071 187.192 103.021 187.192 103.607 186.607C104.192 186.021 104.192 185.071 103.607 184.485L95.1213 176L103.607 167.515C104.192 166.929 104.192 165.979 103.607 165.393C103.021 164.808 102.071 164.808 101.485 165.393L91.9393 174.939ZM235 174.5L93 174.5V177.5L235 177.5V174.5Z" fill="#C7C7C7"/>
|
||||
<path d="M176.5 148L359 148" stroke="#C7C7C7" stroke-width="3"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="38.7344" y="18.3182">Client homomorphically </tspan><tspan x="52.3438" y="37.3182">encrypts 0’s and 1’s.</tspan></text>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="232.281" y="214.318">Server multiplies </tspan><tspan x="245.609" y="233.318">encryptions by </tspan><tspan x="221.031" y="252.318">the database, and </tspan><tspan x="208.562" y="271.318">adds up the results.</tspan></text>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="6" y="214.318">Client decrypts </tspan><tspan x="6" y="233.318">and gets the </tspan><tspan x="6" y="252.318">desired item.</tspan></text>
|
||||
<rect x="207" y="76" width="24" height="24" transform="rotate(180 207 76)" fill="#DBDBDB"/>
|
||||
<rect x="203" y="76" width="4" height="4" transform="rotate(180 203 76)" fill="#C7C7C7"/>
|
||||
<rect x="199" y="72" width="4" height="4" transform="rotate(180 199 72)" fill="#C7C7C7"/>
|
||||
<rect x="199" y="64" width="4" height="4" transform="rotate(180 199 64)" fill="#C7C7C7"/>
|
||||
<rect x="199" y="56" width="4" height="4" transform="rotate(180 199 56)" fill="#C7C7C7"/>
|
||||
<rect x="191" y="76" width="4" height="4" transform="rotate(180 191 76)" fill="#C7C7C7"/>
|
||||
<rect x="199" y="68" width="4" height="4" transform="rotate(180 199 68)" fill="#C7C7C7"/>
|
||||
<rect x="187" y="60" width="4" height="4" transform="rotate(180 187 60)" fill="#C7C7C7"/>
|
||||
<rect x="207" y="64" width="4" height="4" transform="rotate(180 207 64)" fill="#C7C7C7"/>
|
||||
<rect x="207" y="60" width="4" height="4" transform="rotate(180 207 60)" fill="#C7C7C7"/>
|
||||
<rect x="195" y="76" width="4" height="4" transform="rotate(180 195 76)" fill="#C7C7C7"/>
|
||||
<rect x="191" y="72" width="4" height="4" transform="rotate(180 191 72)" fill="#C7C7C7"/>
|
||||
<rect x="195" y="60" width="4" height="4" transform="rotate(180 195 60)" fill="#C7C7C7"/>
|
||||
<rect x="195" y="68" width="4" height="4" transform="rotate(180 195 68)" fill="#C7C7C7"/>
|
||||
<rect x="191" y="60" width="4" height="4" transform="rotate(180 191 60)" fill="#C7C7C7"/>
|
||||
<rect x="191" y="56" width="4" height="4" transform="rotate(180 191 56)" fill="#C7C7C7"/>
|
||||
<rect x="187" y="68" width="4" height="4" transform="rotate(180 187 68)" fill="#C7C7C7"/>
|
||||
<rect x="203" y="68" width="4" height="4" transform="rotate(180 203 68)" fill="#C7C7C7"/>
|
||||
<rect x="183" y="132" width="24" height="24" transform="rotate(-90 183 132)" fill="#DBDBDB"/>
|
||||
<rect x="183" y="128" width="4" height="4" transform="rotate(-90 183 128)" fill="#C7C7C7"/>
|
||||
<rect x="203" y="112" width="4" height="4" transform="rotate(-90 203 112)" fill="#C7C7C7"/>
|
||||
<rect x="195" y="128" width="4" height="4" transform="rotate(-90 195 128)" fill="#C7C7C7"/>
|
||||
<rect x="203" y="124" width="4" height="4" transform="rotate(-90 203 124)" fill="#C7C7C7"/>
|
||||
<rect x="191" y="124" width="4" height="4" transform="rotate(-90 191 124)" fill="#C7C7C7"/>
|
||||
<rect x="187" y="132" width="4" height="4" transform="rotate(-90 187 132)" fill="#C7C7C7"/>
|
||||
<rect x="199" y="112" width="4" height="4" transform="rotate(-90 199 112)" fill="#C7C7C7"/>
|
||||
<rect x="195" y="132" width="4" height="4" transform="rotate(-90 195 132)" fill="#C7C7C7"/>
|
||||
<rect x="183" y="124" width="4" height="4" transform="rotate(-90 183 124)" fill="#C7C7C7"/>
|
||||
<rect x="183" y="120" width="4" height="4" transform="rotate(-90 183 120)" fill="#C7C7C7"/>
|
||||
<rect x="187" y="112" width="4" height="4" transform="rotate(-90 187 112)" fill="#C7C7C7"/>
|
||||
<rect x="203" y="128" width="4" height="4" transform="rotate(-90 203 128)" fill="#C7C7C7"/>
|
||||
<rect x="191" y="120" width="4" height="4" transform="rotate(-90 191 120)" fill="#C7C7C7"/>
|
||||
<rect x="195" y="120" width="4" height="4" transform="rotate(-90 195 120)" fill="#C7C7C7"/>
|
||||
<rect x="203" y="116" width="4" height="4" transform="rotate(-90 203 116)" fill="#C7C7C7"/>
|
||||
<rect x="191" y="112" width="4" height="4" transform="rotate(-90 191 112)" fill="#C7C7C7"/>
|
||||
<rect x="199" y="128" width="4" height="4" transform="rotate(-90 199 128)" fill="#C7C7C7"/>
|
||||
<rect x="183" y="80" width="24" height="24" fill="#DBDBDB"/>
|
||||
<rect x="187" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="191" y="92" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="187" y="88" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="195" y="96" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="191" y="88" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="183" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="203" y="96" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="183" y="92" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="191" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="195" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="203" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="183" y="100" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="195" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="195" y="92" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="199" y="100" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="203" y="88" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="187" y="96" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="298" y="164" width="24" height="24" fill="#DBDBDB"/>
|
||||
<rect x="302" y="164" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="306" y="168" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="306" y="176" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="306" y="184" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="314" y="164" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="306" y="172" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="318" y="176" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="298" y="176" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="298" y="180" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="310" y="164" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="298" y="172" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="310" y="180" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="310" y="172" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="314" y="180" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="318" y="184" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="318" y="172" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="302" y="180" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect width="24" height="24" transform="matrix(1 0 0 -1 250 188)" fill="#DBDBDB"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 254 188)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 270 168)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 254 176)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 258 168)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 258 180)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 250 184)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 262 172)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 250 176)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 258 188)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 262 188)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 270 184)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 254 168)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 266 184)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 262 176)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 266 168)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 270 180)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(1 0 0 -1 254 172)" fill="#C7C7C7"/>
|
||||
<rect width="24" height="24" transform="matrix(-4.37114e-08 1 1 4.37114e-08 274 164)" fill="#DBDBDB"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 290 180)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 286 172)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 282 168)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 286 164)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 274 180)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 278 164)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 290 184)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 282 172)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 290 172)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 278 172)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 278 184)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 294 180)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 278 176)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 286 176)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 294 172)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 282 184)" fill="#C7C7C7"/>
|
||||
<rect width="4" height="4" transform="matrix(-4.37114e-08 1 1 4.37114e-08 290 168)" fill="#C7C7C7"/>
|
||||
<rect x="54" y="52" width="24" height="24" fill="#D9E4EE"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="61" y="69.8182">0</tspan></text>
|
||||
<rect x="250" y="52" width="72" height="24" fill="#D9E4EE"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" font-style="italic" letter-spacing="0em"><tspan x="259.383" y="69.8182">“alpha”</tspan></text>
|
||||
<rect x="54" y="108" width="24" height="24" fill="#D9E4EE"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="61" y="125.818">0</tspan></text>
|
||||
<rect x="250" y="108" width="72" height="24" fill="#D9E4EE"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" font-style="italic" letter-spacing="0em"><tspan x="254.125" y="125.818">“omega”</tspan></text>
|
||||
<rect x="54" y="80" width="24" height="24" fill="#94B9DA"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="62.2812" y="97.8182">1</tspan></text>
|
||||
<rect x="250" y="80" width="72" height="24" fill="#D9E4EE"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" font-style="italic" letter-spacing="0em"><tspan x="262.852" y="97.8182">“beta”</tspan></text>
|
||||
<rect x="6" y="164" width="72" height="24" fill="#D9E4EE"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" font-style="italic" letter-spacing="0em"><tspan x="18.8516" y="181.818">“beta”</tspan></text>
|
||||
<path d="M223 68L232 59" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
<path d="M223 59L232 68" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
<path d="M223 96L232 87" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
<path d="M223 87L232 96" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
<path d="M223 124L232 115" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
<path d="M223 115L232 124" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
<path d="M336 132.364H348.728" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
<path d="M342.364 126L342.364 138.728" stroke="#C7C7C7" stroke-width="3" stroke-linecap="square"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
23
docs/static/img/security-model.svg
vendored
Normal file
23
docs/static/img/security-model.svg
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg width="390" height="205" viewBox="0 0 390 205" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="390" height="205" fill="white"/>
|
||||
<path d="M229.289 132.421C228.89 130.503 228.682 128.524 228.682 126.5C228.682 109.103 244.089 95 263.096 95C274.725 95 285.007 100.28 291.236 108.364C297.216 103.768 304.93 101 313.349 101C332.173 101 347.467 114.833 347.759 132H347.763C368.881 132 386 147.67 386 167C386 186.33 368.881 202 347.763 202H235.237C214.119 202 197 186.33 197 167C197 149.521 210.996 135.036 229.289 132.421Z" fill="#F7E2D3"/>
|
||||
<rect opacity="0.28" x="4" y="29" width="137" height="173" rx="8" fill="#6663E4"/>
|
||||
<text fill="#E49963" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" font-weight="bold" letter-spacing="0em"><tspan x="197" y="80.3182">Cloud, untrusted</tspan></text>
|
||||
<text fill="#6663E4" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" font-weight="bold" letter-spacing="0em"><tspan x="12" y="21.3182">Local, trusted</tspan></text>
|
||||
<rect x="85" y="41" width="36" height="60" rx="6" fill="#636363"/>
|
||||
<rect x="87" y="43" width="32" height="56" rx="4" fill="#858585"/>
|
||||
<text fill="#636363" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="34.5156" y="67.3182">User </tspan><tspan x="20.0625" y="86.3182">device</tspan></text>
|
||||
<text fill="#636363" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="316" y="159.318">Bucket </tspan><tspan x="316" y="178.318">service</tspan></text>
|
||||
<text fill="#636363" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="29.3906" y="144.818">Open </tspan><tspan x="18.8594" y="163.818">source </tspan><tspan x="37.8594" y="182.818">SDK</tspan></text>
|
||||
<rect x="85" y="140" width="36" height="36" rx="6" fill="#636363"/>
|
||||
<text fill="white" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="12" font-weight="bold" letter-spacing="0em"><tspan x="90.5664" y="162.364">SDK</tspan></text>
|
||||
<path d="M125.293 157.293C124.902 157.683 124.902 158.317 125.293 158.707L131.657 165.071C132.047 165.462 132.681 165.462 133.071 165.071C133.462 164.681 133.462 164.047 133.071 163.657L127.414 158L133.071 152.343C133.462 151.953 133.462 151.319 133.071 150.929C132.681 150.538 132.047 150.538 131.657 150.929L125.293 157.293ZM241.707 158.707C242.098 158.317 242.098 157.683 241.707 157.293L235.343 150.929C234.953 150.538 234.319 150.538 233.929 150.929C233.538 151.319 233.538 151.953 233.929 152.343L239.586 158L233.929 163.657C233.538 164.047 233.538 164.681 233.929 165.071C234.319 165.462 234.953 165.462 235.343 165.071L241.707 158.707ZM126 159H241V157H126V159Z" fill="#E4637C"/>
|
||||
<path d="M102.293 135.707C102.683 136.098 103.317 136.098 103.707 135.707L110.071 129.343C110.462 128.953 110.462 128.319 110.071 127.929C109.681 127.538 109.047 127.538 108.657 127.929L103 133.586L97.3431 127.929C96.9526 127.538 96.3195 127.538 95.9289 127.929C95.5384 128.319 95.5384 128.953 95.9289 129.343L102.293 135.707ZM103.707 105.293C103.317 104.902 102.683 104.902 102.293 105.293L95.9289 111.657C95.5384 112.047 95.5384 112.681 95.9289 113.071C96.3195 113.462 96.9526 113.462 97.3431 113.071L103 107.414L108.657 113.071C109.047 113.462 109.681 113.462 110.071 113.071C110.462 112.681 110.462 112.047 110.071 111.657L103.707 105.293ZM104 135L104 106L102 106L102 135L104 135Z" fill="#636363"/>
|
||||
<mask id="path-14-outside-1_5_49" maskUnits="userSpaceOnUse" x="245.5" y="133" width="64" height="61" fill="black">
|
||||
<rect fill="white" x="245.5" y="133" width="64" height="61"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M306.5 135H248.5L256.434 184.586C256.43 184.64 256.429 184.693 256.429 184.747C256.429 188.753 265.863 192 277.5 192C289.138 192 298.572 188.753 298.572 184.747C298.572 184.693 298.57 184.639 298.566 184.585L306.5 135Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M306.5 135H248.5L256.434 184.586C256.43 184.64 256.429 184.693 256.429 184.747C256.429 188.753 265.863 192 277.5 192C289.138 192 298.572 188.753 298.572 184.747C298.572 184.693 298.57 184.639 298.566 184.585L306.5 135Z" fill="#F68E9D"/>
|
||||
<path d="M248.5 135V133H246.155L246.525 135.316L248.5 135ZM306.5 135L308.475 135.316L308.845 133H306.5V135ZM256.434 184.586L258.43 184.712L258.444 184.49L258.409 184.27L256.434 184.586ZM298.566 184.585L296.591 184.269L296.556 184.49L296.57 184.712L298.566 184.585ZM248.5 137H306.5V133H248.5V137ZM258.409 184.27L250.475 134.684L246.525 135.316L254.459 184.902L258.409 184.27ZM258.429 184.747C258.429 184.736 258.429 184.724 258.43 184.712L254.438 184.459C254.432 184.555 254.429 184.651 254.429 184.747H258.429ZM277.5 190C271.842 190 266.804 189.208 263.251 187.985C261.465 187.37 260.163 186.683 259.347 186.019C258.515 185.342 258.429 184.901 258.429 184.747H254.429C254.429 186.597 255.521 188.063 256.822 189.122C258.139 190.193 259.922 191.069 261.949 191.767C266.023 193.169 271.521 194 277.5 194V190ZM296.572 184.747C296.572 184.901 296.485 185.342 295.653 186.019C294.837 186.683 293.535 187.37 291.749 187.985C288.196 189.208 283.158 190 277.5 190V194C283.479 194 288.977 193.169 293.051 191.767C295.078 191.069 296.861 190.193 298.178 189.122C299.479 188.063 300.572 186.597 300.572 184.747H296.572ZM296.57 184.712C296.571 184.724 296.572 184.736 296.572 184.747H300.572C300.572 184.651 300.568 184.554 300.562 184.458L296.57 184.712ZM304.525 134.684L296.591 184.269L300.541 184.901L308.475 135.316L304.525 134.684Z" fill="#E4637C" mask="url(#path-14-outside-1_5_49)"/>
|
||||
<path d="M277.5 141C285.704 141 293.166 140.156 298.606 138.772C301.318 138.083 303.585 137.245 305.197 136.276C306.747 135.345 308 134.099 308 132.5C308 130.901 306.747 129.655 305.197 128.724C303.585 127.755 301.318 126.917 298.606 126.228C293.166 124.844 285.704 124 277.5 124C269.296 124 261.834 124.844 256.394 126.228C253.682 126.917 251.415 127.755 249.803 128.724C248.253 129.655 247 130.901 247 132.5C247 134.099 248.253 135.345 249.803 136.276C251.415 137.245 253.682 138.083 256.394 138.772C261.834 140.156 269.296 141 277.5 141Z" fill="#EEB5BE" stroke="#E4637C" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
110
docs/static/img/sepia-example.svg
vendored
Normal file
110
docs/static/img/sepia-example.svg
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
<svg width="374" height="217" viewBox="0 0 374 217" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_25_2)">
|
||||
<rect width="374" height="217" fill="white"/>
|
||||
<rect x="15" y="81" width="36" height="7" fill="#AECFAD"/>
|
||||
<rect x="15" y="52" width="36" height="29" fill="#D9E4EE"/>
|
||||
<path d="M21.1765 64L15 81H25.5H36L21.1765 64Z" fill="#C7C7C7"/>
|
||||
<path d="M41.1639 60L26 81H51L41.1639 60Z" fill="#DBDBDB"/>
|
||||
<circle cx="28.5" cy="60.5" r="3.5" fill="#D8C49C"/>
|
||||
<rect x="15" y="156" width="36" height="7" fill="#AECFAD"/>
|
||||
<rect x="15" y="127" width="36" height="29" fill="#D9E4EE"/>
|
||||
<path d="M21.1765 139L15 156H25.5H36L21.1765 139Z" fill="#C7C7C7"/>
|
||||
<path d="M41.1639 135L26 156H51L41.1639 135Z" fill="#DBDBDB"/>
|
||||
<circle cx="28.5" cy="135.5" r="3.5" fill="#D8C49C"/>
|
||||
<path d="M120.061 71.0607C120.646 70.4749 120.646 69.5251 120.061 68.9393L110.515 59.3934C109.929 58.8076 108.979 58.8076 108.393 59.3934C107.808 59.9792 107.808 60.9289 108.393 61.5147L116.879 70L108.393 78.4853C107.808 79.0711 107.808 80.0208 108.393 80.6066C108.979 81.1924 109.929 81.1924 110.515 80.6066L120.061 71.0607ZM64 71.5H119V68.5H64V71.5Z" fill="#C7C7C7"/>
|
||||
<path d="M62.9393 146.061C62.3536 145.475 62.3536 144.525 62.9393 143.939L72.4853 134.393C73.0711 133.808 74.0208 133.808 74.6066 134.393C75.1924 134.979 75.1924 135.929 74.6066 136.515L66.1213 145L74.6066 153.485C75.1924 154.071 75.1924 155.021 74.6066 155.607C74.0208 156.192 73.0711 156.192 72.4853 155.607L62.9393 146.061ZM119 146.5H64V143.5H119V146.5Z" fill="#C7C7C7"/>
|
||||
<path d="M226 70L227.5 70L227.5 68.5H226V70ZM226 145L226 146.5L227.5 146.5L227.5 145L226 145ZM179.939 143.939C179.354 144.525 179.354 145.475 179.939 146.061L189.485 155.607C190.071 156.192 191.021 156.192 191.607 155.607C192.192 155.021 192.192 154.071 191.607 153.485L183.121 145L191.607 136.515C192.192 135.929 192.192 134.979 191.607 134.393C191.021 133.808 190.071 133.808 189.485 134.393L179.939 143.939ZM181 71.5H226V68.5H181V71.5ZM224.5 70L224.5 145L227.5 145L227.5 70L224.5 70ZM226 143.5L181 143.5L181 146.5L226 146.5L226 143.5Z" fill="#C7C7C7"/>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="2.23438" y="16.3182">Client homomorphically </tspan><tspan x="19.25" y="35.3182">encrypts its image.</tspan></text>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" font-style="italic" letter-spacing="0em"><tspan x="257" y="128.318">without seeing </tspan><tspan x="257" y="147.318">the image!</tspan></text>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="257" y="90.3182">Server applies </tspan><tspan x="257" y="109.318">the sepia filter, </tspan></text>
|
||||
<text fill="#1C1E21" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="16" letter-spacing="0em"><tspan x="16.4453" y="191.318">Client decrypts and </tspan><tspan x="3.07031" y="210.318">gets the filtered image.</tspan></text>
|
||||
<rect x="132" y="52" width="36" height="36" fill="#DBDBDB"/>
|
||||
<rect x="132" y="52" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="136" y="52" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="136" y="64" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="136" y="68" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="136" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="132" y="64" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="132" y="68" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="132" y="76" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="132" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="148" y="52" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="152" y="56" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="152" y="64" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="152" y="72" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="152" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="148" y="56" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="148" y="72" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="148" y="76" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="152" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="140" y="52" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="144" y="56" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="144" y="64" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="144" y="72" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="144" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="144" y="60" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="140" y="68" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="136" y="76" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="136" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="160" y="56" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="160" y="60" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="160" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="156" y="60" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="160" y="68" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="156" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="156" y="84" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="164" y="52" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="164" y="60" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="164" y="64" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="164" y="76" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="164" y="80" width="4" height="4" fill="#C7C7C7"/>
|
||||
<rect x="168" y="127" width="36" height="36" transform="rotate(90 168 127)" fill="#DBDBDB"/>
|
||||
<rect x="168" y="127" width="4" height="4" transform="rotate(90 168 127)" fill="#C7C7C7"/>
|
||||
<rect x="160" y="131" width="4" height="4" transform="rotate(90 160 131)" fill="#C7C7C7"/>
|
||||
<rect x="156" y="131" width="4" height="4" transform="rotate(90 156 131)" fill="#C7C7C7"/>
|
||||
<rect x="144" y="151" width="4" height="4" transform="rotate(90 144 151)" fill="#C7C7C7"/>
|
||||
<rect x="140" y="155" width="4" height="4" transform="rotate(90 140 155)" fill="#C7C7C7"/>
|
||||
<rect x="160" y="127" width="4" height="4" transform="rotate(90 160 127)" fill="#C7C7C7"/>
|
||||
<rect x="152" y="127" width="4" height="4" transform="rotate(90 152 127)" fill="#C7C7C7"/>
|
||||
<rect x="136" y="159" width="4" height="4" transform="rotate(90 136 159)" fill="#C7C7C7"/>
|
||||
<rect x="144" y="127" width="4" height="4" transform="rotate(90 144 127)" fill="#C7C7C7"/>
|
||||
<rect x="160" y="143" width="4" height="4" transform="rotate(90 160 143)" fill="#C7C7C7"/>
|
||||
<rect x="164" y="159" width="4" height="4" transform="rotate(90 164 159)" fill="#C7C7C7"/>
|
||||
<rect x="156" y="147" width="4" height="4" transform="rotate(90 156 147)" fill="#C7C7C7"/>
|
||||
<rect x="148" y="147" width="4" height="4" transform="rotate(90 148 147)" fill="#C7C7C7"/>
|
||||
<rect x="140" y="147" width="4" height="4" transform="rotate(90 140 147)" fill="#C7C7C7"/>
|
||||
<rect x="152" y="143" width="4" height="4" transform="rotate(90 152 143)" fill="#C7C7C7"/>
|
||||
<rect x="148" y="143" width="4" height="4" transform="rotate(90 148 143)" fill="#C7C7C7"/>
|
||||
<rect x="148" y="139" width="4" height="4" transform="rotate(90 148 139)" fill="#C7C7C7"/>
|
||||
<rect x="140" y="135" width="4" height="4" transform="rotate(90 140 135)" fill="#C7C7C7"/>
|
||||
<rect x="168" y="135" width="4" height="4" transform="rotate(90 168 135)" fill="#C7C7C7"/>
|
||||
<rect x="164" y="139" width="4" height="4" transform="rotate(90 164 139)" fill="#C7C7C7"/>
|
||||
<rect x="164" y="147" width="4" height="4" transform="rotate(90 164 147)" fill="#C7C7C7"/>
|
||||
<rect x="144" y="139" width="4" height="4" transform="rotate(90 144 139)" fill="#C7C7C7"/>
|
||||
<rect x="136" y="139" width="4" height="4" transform="rotate(90 136 139)" fill="#C7C7C7"/>
|
||||
<rect x="160" y="135" width="4" height="4" transform="rotate(90 160 135)" fill="#C7C7C7"/>
|
||||
<rect x="152" y="135" width="4" height="4" transform="rotate(90 152 135)" fill="#C7C7C7"/>
|
||||
<rect x="144" y="131" width="4" height="4" transform="rotate(90 144 131)" fill="#C7C7C7"/>
|
||||
<rect x="136" y="131" width="4" height="4" transform="rotate(90 136 131)" fill="#C7C7C7"/>
|
||||
<rect x="168" y="151" width="4" height="4" transform="rotate(90 168 151)" fill="#C7C7C7"/>
|
||||
<rect x="160" y="155" width="4" height="4" transform="rotate(90 160 155)" fill="#C7C7C7"/>
|
||||
<rect x="152" y="159" width="4" height="4" transform="rotate(90 152 159)" fill="#C7C7C7"/>
|
||||
<rect x="160" y="151" width="4" height="4" transform="rotate(90 160 151)" fill="#C7C7C7"/>
|
||||
<rect x="152" y="155" width="4" height="4" transform="rotate(90 152 155)" fill="#C7C7C7"/>
|
||||
<rect x="140" y="151" width="4" height="4" transform="rotate(90 140 151)" fill="#C7C7C7"/>
|
||||
<rect x="136" y="151" width="4" height="4" transform="rotate(90 136 151)" fill="#C7C7C7"/>
|
||||
<rect x="168" y="147" width="4" height="4" transform="rotate(90 168 147)" fill="#C7C7C7"/>
|
||||
<rect x="156" y="151" width="4" height="4" transform="rotate(90 156 151)" fill="#C7C7C7"/>
|
||||
<rect x="156" y="159" width="4" height="4" transform="rotate(90 156 159)" fill="#C7C7C7"/>
|
||||
<rect x="144" y="159" width="4" height="4" transform="rotate(90 144 159)" fill="#C7C7C7"/>
|
||||
<rect x="148" y="131" width="4" height="4" transform="rotate(90 148 131)" fill="#C7C7C7"/>
|
||||
<rect x="244" y="91" width="36" height="36" transform="rotate(90 244 91)" fill="#D8C49C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M244 123.464L244 118.536L216.464 91L211.536 91L244 123.464ZM208 94.5355L208 99.4645L235.536 127L240.464 127L208 94.5355ZM208 112.464L208 106.536L228.464 127L222.536 127L208 112.464ZM208 119.536L208 127L215.464 127L208 119.536ZM244 91L244 98.4645L236.536 91L244 91ZM223.536 91L229.464 91L244 105.536L244 111.464L223.536 91Z" fill="#EAD5AB"/>
|
||||
<rect x="15" y="127" width="36" height="36" fill="#EAD5AB" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_25_2">
|
||||
<rect width="374" height="217" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.5 KiB |
8
docs/tsconfig.json
Normal file
8
docs/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
// This file is not used in compilation. It is here just for a nice editor experience.
|
||||
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
79
examples/browser-simple/index.html
Normal file
79
examples/browser-simple/index.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>Blyss Demo</title>
|
||||
<style>
|
||||
html {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
*:not(pre) {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Blyss Demo</h1>
|
||||
<div>
|
||||
<p>
|
||||
<strong>Bucket contents</strong>
|
||||
</p>
|
||||
<div>
|
||||
<pre>
|
||||
{
|
||||
"California": "Sacramento",
|
||||
"Ohio": "Columbus",
|
||||
"New York": "Albany"
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<strong>Perform a private retrieval</strong>
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
Private retrieval:
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
<label>
|
||||
<select id="key-input">
|
||||
<option value="California">California</option>
|
||||
<option value="Ohio">Ohio</option>
|
||||
<option value="New York">New York</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="submit">
|
||||
>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
Result:
|
||||
</p>
|
||||
<div>
|
||||
<pre id="result-output">...</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../dist/blyss-bundle.min.js"></script>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
48
examples/browser-simple/main.js
Normal file
48
examples/browser-simple/main.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const keyInput = document.getElementById('key-input');
|
||||
const resultOutput = document.getElementById('result-output');
|
||||
const submitButton = document.getElementById('submit');
|
||||
|
||||
// This will be our client to the bucket
|
||||
let bucket;
|
||||
|
||||
// This function gets called only on the first query
|
||||
async function setup() {
|
||||
const client = new window.blyss.Client('<YOUR API KEY HERE>');
|
||||
|
||||
// Create the bucket
|
||||
const bucketName = 'state-capitals';
|
||||
if (!(await client.exists(bucketName))) {
|
||||
await client.create(bucketName);
|
||||
}
|
||||
|
||||
// Connect to your bucket
|
||||
bucket = await client.connect(bucketName);
|
||||
|
||||
// Write some data to it
|
||||
bucket.write({
|
||||
California: 'Sacramento',
|
||||
Ohio: 'Columbus',
|
||||
'New York': 'Albany'
|
||||
});
|
||||
}
|
||||
|
||||
// This function performs the query
|
||||
async function privateRetrieve() {
|
||||
submitButton.innerText = '...';
|
||||
submitButton.disabled = true;
|
||||
|
||||
if (!bucket) await setup();
|
||||
|
||||
// This is a completely *private* query:
|
||||
// the server *cannot* learn anything about 'keyToRetrieve'!
|
||||
const keyToRetrieve = keyInput.value;
|
||||
const result = await bucket.privateRead(keyToRetrieve);
|
||||
|
||||
resultOutput.innerText = result;
|
||||
|
||||
submitButton.innerText = '>';
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
|
||||
// Perform the query when we click the button
|
||||
submitButton.onclick = privateRetrieve;
|
||||
40
examples/node/main.ts
Normal file
40
examples/node/main.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Client } from '@blyss/client-sdk';
|
||||
|
||||
async function main() {
|
||||
const client = new Client('<YOUR API KEY HERE>');
|
||||
|
||||
// Create the bucket
|
||||
const bucketName = 'state-capitals';
|
||||
if (!(await client.exists(bucketName))) {
|
||||
console.log('creating...');
|
||||
await client.create(bucketName);
|
||||
}
|
||||
|
||||
// Connect to your bucket
|
||||
const bucket = await client.connect(bucketName);
|
||||
|
||||
// Write some data to it
|
||||
bucket.write({
|
||||
California: 'Sacramento',
|
||||
Ohio: 'Columbus',
|
||||
'New York': 'Albany'
|
||||
});
|
||||
|
||||
// This is a completely *private* query:
|
||||
// the server *cannot* learn that you looked up "California"!
|
||||
const capital = await bucket.privateRead('California');
|
||||
console.log(`Got capital: ${capital}`);
|
||||
|
||||
// This is a completely *private* intersection operation:
|
||||
// the server *cannot* learn that the set was ['Wyoming', 'California', 'Ohio']!
|
||||
const setToTest = ['Wyoming', 'California', 'Ohio'];
|
||||
const intersection = await bucket.privateIntersect(setToTest);
|
||||
console.log(
|
||||
'Intersection of',
|
||||
setToTest,
|
||||
'and bucket yielded:',
|
||||
intersection
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
10
examples/node/package.json
Normal file
10
examples/node/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@blyss/client-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
10
examples/node/tsconfig.json
Normal file
10
examples/node/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ES6",
|
||||
"target": "ES6",
|
||||
"moduleResolution": "node",
|
||||
},
|
||||
"ts-node": {
|
||||
"esm": true,
|
||||
}
|
||||
}
|
||||
37
examples/python/main.py
Normal file
37
examples/python/main.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import blyss
|
||||
import logging
|
||||
import requests
|
||||
import json
|
||||
|
||||
api_key = "<YOUR API KEY HERE>"
|
||||
client = blyss.Client(api_key)
|
||||
|
||||
# Create the bucket and fill it with some data
|
||||
bucket_name = "state-capitals"
|
||||
bucket = None
|
||||
if not client.exists(bucket_name):
|
||||
client.create(bucket_name)
|
||||
|
||||
# Connect to your bucket
|
||||
bucket = client.connect(bucket_name)
|
||||
|
||||
# Write some data to it
|
||||
bucket.write(
|
||||
{
|
||||
"California": "Sacramento",
|
||||
"Ohio": "Columbus",
|
||||
"New York": "Albany",
|
||||
}
|
||||
)
|
||||
|
||||
# This is a completely *private* query:
|
||||
# the server *cannot* learn that you looked up "California"!
|
||||
print("Privately reading the capital of California...")
|
||||
capital = bucket.private_read("California")
|
||||
print(f"Got '{capital}'!")
|
||||
|
||||
# This is a completely *private* intersection operation:
|
||||
# the server *cannot* learn that the set was ['Wyoming', 'California', 'Ohio']!
|
||||
set_to_test = ["Wyoming", "California", "Ohio"]
|
||||
intersection = bucket.private_key_intersect(set_to_test)
|
||||
print(f"Intersection of {set_to_test} and bucket yielded: {intersection}")
|
||||
23
examples/react-complex/.gitignore
vendored
Normal file
23
examples/react-complex/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
3
examples/react-complex/README.md
Normal file
3
examples/react-complex/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Private contact lookup
|
||||
|
||||
This is a small application to demonstrate how to use Blyss buckets to perform private contact lookup.
|
||||
16860
examples/react-complex/package-lock.json
generated
Normal file
16860
examples/react-complex/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
examples/react-complex/package.json
Normal file
39
examples/react-complex/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "react-complex",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^16.18.11",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.4",
|
||||
"@blyss/client-sdk": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
20
examples/react-complex/public/index.html
Normal file
20
examples/react-complex/public/index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Web site created using create-react-app" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
|
||||
<title>Private Contact Lookup</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
22
examples/react-complex/src/App.css
Normal file
22
examples/react-complex/src/App.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.App-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
background-color: #282c34;
|
||||
height: 85px;
|
||||
padding: 0 120px;
|
||||
font-size: 36px;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.App-main {
|
||||
padding: 120px;
|
||||
padding-top: 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
max-width: 800px;
|
||||
}
|
||||
208
examples/react-complex/src/App.tsx
Normal file
208
examples/react-complex/src/App.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
import './App.css';
|
||||
import { Bucket, Client } from '@blyss/client-sdk';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
// This function gets called only on the first query
|
||||
async function setup(): Promise<Bucket> {
|
||||
const client = new Client('<YOUR API KEY HERE>');
|
||||
|
||||
// Create the bucket
|
||||
const bucketName = 'contact-demo';
|
||||
if (!(await client.exists(bucketName))) {
|
||||
await client.create(bucketName);
|
||||
}
|
||||
|
||||
// Connect to your bucket
|
||||
const bucket = await client.connect(bucketName);
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
function pickRandom(arr: string[]): string {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
function pickRandomSubset(arr: string[]): string[] {
|
||||
let n = Math.floor(Math.random() * Math.min(arr.length, 5));
|
||||
if (n === 0) return [];
|
||||
|
||||
let res = [];
|
||||
for (let i = 0; i < n; i++) res.push(pickRandom(arr));
|
||||
return Array.from(new Set(res));
|
||||
}
|
||||
|
||||
function randomPhone(): string {
|
||||
const digit = (s: string) =>
|
||||
s === '-' ? '-' : Math.floor(Math.random() * 10);
|
||||
return 'XXX-XXX-XXXX'.split('').map(digit).join('');
|
||||
}
|
||||
|
||||
function generateRandomUser(allPhones: string[]): User {
|
||||
const first = 'Joe,Ali,Alisa,Belen,Jakob,Cade,Brett,Trent,Silas'.split(',');
|
||||
const last = 'Brown,Jones,Miller,Davis,Garcia,Rodriguez'.split(',');
|
||||
const name = pickRandom(first) + ' ' + pickRandom(last);
|
||||
const handle =
|
||||
'@' + name.toLowerCase().replace(' ', '') + Math.floor(Math.random() * 100);
|
||||
const phone = randomPhone();
|
||||
const contactPhones = pickRandomSubset(allPhones);
|
||||
return { name, handle, phone, contactPhones };
|
||||
}
|
||||
|
||||
interface User {
|
||||
name: string;
|
||||
phone: string;
|
||||
handle: string;
|
||||
contactPhones: string[];
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [bucketHandle, setBucketHandle] = React.useState<Bucket | undefined>();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [user, setUser] = React.useState<User>(generateRandomUser([]));
|
||||
const [resolveAll, setResolveAll] = React.useState(false);
|
||||
const [allPhones, setAllPhones] = React.useState<string[]>([]);
|
||||
|
||||
const [trace, setTrace] = React.useState<ReactNode[]>([]);
|
||||
let logMessage = (t: ReactNode) => setTrace([t, ...trace]);
|
||||
|
||||
async function performSignup(user: User): Promise<any[]> {
|
||||
// 1. Get the bucket
|
||||
let bucket = bucketHandle;
|
||||
if (!bucket) {
|
||||
bucket = await setup();
|
||||
setBucketHandle(bucket);
|
||||
}
|
||||
|
||||
// 2. Add the user to the bucket, using their phone number as the key
|
||||
await bucket.write({
|
||||
[user.phone]: { name: user.name, phone: user.phone, handle: user.handle }
|
||||
});
|
||||
|
||||
// 3. Check if any of the user's contacts are on the service
|
||||
let intersection: any[];
|
||||
if (resolveAll) {
|
||||
intersection = await bucket.privateIntersect(user.contactPhones);
|
||||
} else {
|
||||
intersection = await bucket.privateKeyIntersect(user.contactPhones);
|
||||
}
|
||||
|
||||
return intersection;
|
||||
}
|
||||
|
||||
async function animateSignup(user: User): Promise<void> {
|
||||
setLoading(true);
|
||||
|
||||
// 1. Sign up the user
|
||||
let start = performance.now();
|
||||
let intersection = await performSignup(user);
|
||||
let took = (performance.now() - start) / 1000;
|
||||
let tookMsg = (
|
||||
<em style={{ color: '#666', paddingLeft: 20 }}>
|
||||
({Math.round(took * 100) / 100} s)
|
||||
</em>
|
||||
);
|
||||
|
||||
// 2. Log the result
|
||||
logMessage(
|
||||
<>
|
||||
<div>
|
||||
Wrote new user "{user.handle}".{' '}
|
||||
{intersection.length > 0 ? (
|
||||
<>
|
||||
Privately found {intersection.length} of their contacts using the
|
||||
service. {tookMsg}
|
||||
{resolveAll ? (
|
||||
<div>
|
||||
Resolved the details on these contacts:
|
||||
<pre>{JSON.stringify(intersection)}</pre>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>{tookMsg}</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// 3. Add phone number to a local store (for demo only)
|
||||
setAllPhones([...allPhones, user.phone]);
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
let addOneRandomUser = () => {
|
||||
let user = generateRandomUser(allPhones);
|
||||
setUser(user);
|
||||
animateSignup(user);
|
||||
};
|
||||
|
||||
let colGap: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 10
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">Private Contact Lookup</header>
|
||||
<div className="App-main">
|
||||
<div>
|
||||
<div style={{ ...colGap, gap: 40 }}>
|
||||
<div>Number of users: {allPhones.length}</div>
|
||||
<div style={{ maxWidth: 300 }}>
|
||||
<label
|
||||
style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={resolveAll}
|
||||
onChange={() => setResolveAll(!resolveAll)}
|
||||
style={{ margin: 4 }}
|
||||
/>
|
||||
<div>
|
||||
Checking this box will resolve the details (name, handle,
|
||||
phone) of every contact that we discover.
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<strong>Add a user</strong>
|
||||
</p>
|
||||
<div style={colGap}>
|
||||
<div>
|
||||
<input type="text" value={user.name} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" value={user.phone} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" value={user.handle} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<button disabled={loading} onClick={addOneRandomUser}>
|
||||
Add 1 random user
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<strong>Trace</strong>
|
||||
</p>
|
||||
<div style={colGap}>
|
||||
{trace.length > 0
|
||||
? trace.map((t, i) => <div key={i}>{t}</div>)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
13
examples/react-complex/src/index.css
Normal file
13
examples/react-complex/src/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
13
examples/react-complex/src/index.tsx
Normal file
13
examples/react-complex/src/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
1
examples/react-complex/src/react-app-env.d.ts
vendored
Normal file
1
examples/react-complex/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
26
examples/react-complex/tsconfig.json
Normal file
26
examples/react-complex/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
5
jest.config.js
Normal file
5
jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
export default {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
};
|
||||
5
js/bridge/.gitignore
vendored
Normal file
5
js/bridge/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
/dist
|
||||
/target
|
||||
/pkg
|
||||
/wasm-pack.log
|
||||
417
js/bridge/Cargo.lock
generated
Normal file
417
js/bridge/Cargo.lock
generated
Normal file
@@ -0,0 +1,417 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.47",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spiral-rs"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spiral-rs-js-bridge"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"spiral-rs",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83420b37346c311b9ed822af41ec2e82839bfe99867ec6c54e2da43b7538771c"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"futures",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote 1.0.21",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.2.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d9693b63a742d481c7f80587e057920e568317b2806988c59cd71618bc26c1"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
"js-sys",
|
||||
"scoped-tls",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.2.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0789dac148a8840bbcf9efe13905463b733fa96543bfbf263790535c11af7ba5"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
"quote 0.6.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
27
js/bridge/Cargo.toml
Normal file
27
js/bridge/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "spiral-rs-js-bridge"
|
||||
description = "Bridge crate between the spiral-rs-client library and the JS client"
|
||||
version = "0.1.0"
|
||||
authors = ["Samir Menon <samir@blyss.dev>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/blyssprivacy/client-sdk"
|
||||
categories = ["wasm"]
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.83"
|
||||
spiral-rs = { path = "../../lib/spiral-rs" }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.2.45"
|
||||
futures = "0.1.27"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
# lto = "fat"
|
||||
# codegen-units = 1
|
||||
# panic = "abort"
|
||||
21
js/bridge/LICENSE
Normal file
21
js/bridge/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023, Samir Menon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
js/bridge/README.md
Normal file
5
js/bridge/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Bridge crate
|
||||
|
||||
This is a simple Rust crate to bridge the core `spiral-rs` library (located in `../lib/spiral-rs`) to the JS Blyss SDK.
|
||||
|
||||
You generally do not need to build this crate directly. Instead, invoke `npm run build` in the repository root to build the entire SDK.
|
||||
84
js/bridge/src/lib.rs
Normal file
84
js/bridge/src/lib.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use spiral_rs::client::*;
|
||||
use spiral_rs::key_value::*;
|
||||
use spiral_rs::params::Params;
|
||||
use spiral_rs::util::*;
|
||||
|
||||
use std::convert::TryInto;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
pub struct ApiClientObj<'a> {
|
||||
pub params: &'a Params,
|
||||
pub c: Client<'a>,
|
||||
}
|
||||
|
||||
// Container class for a static lifetime ApiClientObj
|
||||
// Avoids a lifetime in the return signature of bound Rust functions
|
||||
#[wasm_bindgen]
|
||||
pub struct ApiClient {
|
||||
client: &'static mut ApiClientObj<'static>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn initialize_client(json_params: Option<String>) -> ApiClient {
|
||||
let mut cfg = DEFAULT_PARAMS.to_owned();
|
||||
if json_params.is_some() {
|
||||
cfg = json_params.unwrap();
|
||||
}
|
||||
|
||||
let params = Box::leak(Box::new(params_from_json(&cfg)));
|
||||
let client = Box::leak(Box::new(ApiClientObj {
|
||||
params,
|
||||
c: Client::init(params),
|
||||
}));
|
||||
|
||||
ApiClient { client }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_keys(
|
||||
c: &mut ApiClient,
|
||||
seed: Box<[u8]>,
|
||||
generate_pub_params: bool,
|
||||
) -> Option<Box<[u8]>> {
|
||||
let seed_val = (*seed).try_into().unwrap();
|
||||
let result = c
|
||||
.client
|
||||
.c
|
||||
.generate_keys_optional(seed_val, generate_pub_params)?
|
||||
.into_boxed_slice();
|
||||
Some(result)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_query(c: &mut ApiClient, id: &str, idx_target: usize) -> Box<[u8]> {
|
||||
c.client
|
||||
.c
|
||||
.generate_full_query(id, idx_target)
|
||||
.into_boxed_slice()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn decode_response(c: &mut ApiClient, data: Box<[u8]>) -> Box<[u8]> {
|
||||
c.client.c.decode_response(&*data).into_boxed_slice()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_row(c: &mut ApiClient, key: &str) -> u32 {
|
||||
row_from_key(c.client.params, key) as u32
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn extract_result(_c: &mut ApiClient, key: &str, result: &[u8]) -> Option<Vec<u8>> {
|
||||
extract_result_impl(key, result).ok()
|
||||
}
|
||||
339
js/bucket/bucket.ts
Normal file
339
js/bucket/bucket.ts
Normal file
@@ -0,0 +1,339 @@
|
||||
import { Api, ApiError, BucketMetadata } from '../client/api';
|
||||
import { base64ToBytes, getRandomSeed } from '../client/seed';
|
||||
import { decompress } from '../compression/bz2_decompress';
|
||||
import { bloomLookup } from '../data/bloom';
|
||||
import {
|
||||
DataWithMetadata,
|
||||
concatBytes,
|
||||
deserialize,
|
||||
deserializeChunks,
|
||||
serialize,
|
||||
serializeChunks,
|
||||
wrapKeyValue
|
||||
} from '../data/serializer';
|
||||
import { BlyssLib } from '../lib/blyss_lib';
|
||||
|
||||
/**
|
||||
* Maximum number of private reads to perform before using the Bloom filter
|
||||
* optimization.
|
||||
*/
|
||||
export const BLOOM_CUTOFF = 0;
|
||||
|
||||
/** Information about a key in a bucket. */
|
||||
export interface KeyInfo {
|
||||
/** The name of the key, as a string. */
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A client to a single Blyss bucket.
|
||||
*
|
||||
* You should not need to construct this object directly. Instead, call
|
||||
* `client.connect()` to connect to an existing bucket, or `client.create()` to
|
||||
* create a new one.
|
||||
*
|
||||
* You can serialize and deserialize this object using `toSecretSeed()` and
|
||||
* `client.connect(bucketName, secretSeed)`.
|
||||
*/
|
||||
export class Bucket {
|
||||
/** The target API to send all underlying API calls to. */
|
||||
readonly api: Api;
|
||||
|
||||
/** The name of this bucket. */
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* The secret seed for this instance of the client, which can be saved and
|
||||
* then later used to restore state.
|
||||
*/
|
||||
readonly secretSeed?: string;
|
||||
|
||||
/** The metadata of this bucket. */
|
||||
metadata: BucketMetadata;
|
||||
|
||||
/** The inner WASM client for this instance of the client. */
|
||||
lib: BlyssLib;
|
||||
|
||||
/** The public UUID of this client's public parameters. */
|
||||
uuid?: string;
|
||||
|
||||
/**
|
||||
* The maximum size of query batches sent to the service. Must be greater than
|
||||
* 0.
|
||||
*/
|
||||
batchSize = 5;
|
||||
|
||||
private constructor(api: Api, name: string, secretSeed?: string) {
|
||||
this.api = api;
|
||||
this.name = name;
|
||||
this.secretSeed = getRandomSeed();
|
||||
if (secretSeed) {
|
||||
this.secretSeed = secretSeed;
|
||||
}
|
||||
}
|
||||
|
||||
private async check(uuid: string): Promise<boolean> {
|
||||
try {
|
||||
await this.api.check(uuid);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 404) {
|
||||
return false;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getEndResult(key: string, queryResult: Uint8Array) {
|
||||
const decryptedResult = this.lib.decodeResponse(queryResult);
|
||||
const decompressedResult = decompress(decryptedResult);
|
||||
const extractedResult = this.lib.extractResult(key, decompressedResult);
|
||||
const result = deserialize(extractedResult);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getRawResponse(queryData: Uint8Array): Promise<Uint8Array> {
|
||||
const queryResult = base64ToBytes(
|
||||
new TextDecoder().decode(await this.api.privateRead(this.name, queryData))
|
||||
);
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
private async performPrivateReads(
|
||||
keys: string[]
|
||||
): Promise<DataWithMetadata[]> {
|
||||
if (!this.uuid || !this.check(this.uuid)) {
|
||||
await this.setup();
|
||||
}
|
||||
|
||||
const queries: { key: string; queryData: Uint8Array }[] = [];
|
||||
for (const key of keys) {
|
||||
const rowIdx = this.lib.getRow(key);
|
||||
const queryData = this.lib.generateQuery(this.uuid, rowIdx);
|
||||
queries.push({ key, queryData });
|
||||
}
|
||||
|
||||
const endResults = [];
|
||||
const batches = Math.ceil(queries.length / this.batchSize);
|
||||
for (let i = 0; i < batches; i++) {
|
||||
const queriesForBatch = queries.slice(
|
||||
i * this.batchSize,
|
||||
(i + 1) * this.batchSize
|
||||
);
|
||||
|
||||
const queryBatch = serializeChunks(queriesForBatch.map(x => x.queryData));
|
||||
const rawResultChunks = await this.getRawResponse(queryBatch);
|
||||
const rawResults = deserializeChunks(rawResultChunks);
|
||||
|
||||
const batchEndResults = await Promise.all(
|
||||
rawResults.map((r, i) => this.getEndResult(queriesForBatch[i].key, r))
|
||||
);
|
||||
|
||||
endResults.push(...batchEndResults);
|
||||
}
|
||||
|
||||
return endResults;
|
||||
}
|
||||
|
||||
private async performPrivateRead(key: string): Promise<DataWithMetadata> {
|
||||
return (await this.performPrivateReads([key]))[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a client for a single existing Blyss bucket. You should not need
|
||||
* to call this method directly. Instead, call `client.connect()` to connect
|
||||
* to an existing bucket, or `client.create()` to create a new one.
|
||||
*
|
||||
* @param {Api} api - A target API to send all underlying API calls to.
|
||||
* @param {string} name - The name of the bucket.
|
||||
* @param {string} [secretSeed] - An optional secret seed to initialize the
|
||||
* client with. A random one will be generated if not supplied.
|
||||
*/
|
||||
static async initialize(
|
||||
api: Api,
|
||||
name: string,
|
||||
secretSeed?: string
|
||||
): Promise<Bucket> {
|
||||
const b = new this(api, name, secretSeed);
|
||||
b.metadata = await b.api.meta(b.name);
|
||||
b.lib = new BlyssLib(JSON.stringify(b.metadata.pir_scheme), b.secretSeed);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares this bucket client for private reads.
|
||||
*
|
||||
* This method will be called automatically by
|
||||
* {@link privateRead(key: string)}, but clients may call it explicitly prior
|
||||
* to make subsequent {@link privateRead(key: string)} calls faster.
|
||||
*
|
||||
* Can upload significant amounts of data (1-10 MB).
|
||||
*
|
||||
* @param {string} [uuid] - Optional previous UUID that the client should
|
||||
* attempt to reuse, to avoid generating and uploading larger amounts of
|
||||
* data.
|
||||
*/
|
||||
async setup(uuid?: string) {
|
||||
if (uuid && this.check(uuid)) {
|
||||
this.lib.generateKeys(false);
|
||||
this.uuid = uuid;
|
||||
} else {
|
||||
const publicParams = this.lib.generateKeys(true);
|
||||
const setupResp = await this.api.setup(this.name, publicParams);
|
||||
this.uuid = setupResp.uuid;
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets information about this bucket from the service. */
|
||||
async info(): Promise<BucketMetadata> {
|
||||
return await this.api.meta(this.name);
|
||||
}
|
||||
|
||||
/** Gets info on all keys in this bucket. */
|
||||
async listKeys(): Promise<KeyInfo[]> {
|
||||
return await this.api.listKeys(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a write to this bucket.
|
||||
*
|
||||
* @param {{ [key: string]: any }} keyValuePairs - An object containing the
|
||||
* key-value pairs to write. Keys must be strings, and values may be any
|
||||
* JSON-serializable value or a Uint8Array. The maximum size of a key is
|
||||
* 1024 UTF-8 bytes.
|
||||
* @param {{ [key: string]: any }} [metadata] - An optional object containing
|
||||
* metadata. Each key of this object should also be a key of
|
||||
* `keyValuePairs`, and the value should be some metadata object to store
|
||||
* with the values being written.
|
||||
*/
|
||||
async write(
|
||||
keyValuePairs: { [key: string]: any },
|
||||
metadata?: { [key: string]: any }
|
||||
) {
|
||||
const data = [];
|
||||
for (const key in keyValuePairs) {
|
||||
if (Object.prototype.hasOwnProperty.call(keyValuePairs, key)) {
|
||||
const value = keyValuePairs[key];
|
||||
let valueMetadata = undefined;
|
||||
if (metadata && Object.prototype.hasOwnProperty.call(metadata, key)) {
|
||||
valueMetadata = metadata[key];
|
||||
}
|
||||
const valueBytes = serialize(value, valueMetadata);
|
||||
const keyBytes = new TextEncoder().encode(key);
|
||||
const serializedKeyValue = wrapKeyValue(keyBytes, valueBytes);
|
||||
data.push(serializedKeyValue);
|
||||
}
|
||||
}
|
||||
const concatenatedData = concatBytes(data);
|
||||
await this.api.write(this.name, concatenatedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the supplied key from the bucket.
|
||||
*
|
||||
* Note that this does not remove the key from the Bloom filter, so subsequent
|
||||
* calls to `privateIntersect` or `privateKeyIntersect` could still return
|
||||
* this key.
|
||||
*
|
||||
* @param {string} key - The key to delete.
|
||||
*/
|
||||
async deleteKey(key: string) {
|
||||
await this.api.deleteKey(this.name, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the entire bucket, and all data inside of it. This action is
|
||||
* permanent and irreversible.
|
||||
*/
|
||||
async destroyEntireBucket() {
|
||||
await this.api.destroy(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Privately reads the supplied key from the bucket, returning the value
|
||||
* corresponding to the key.
|
||||
*
|
||||
* No entity, including the Blyss service, should be able to determine which
|
||||
* key this method was called for.
|
||||
*
|
||||
* @param {string} key - The key to _privately_ retrieve the value of.
|
||||
*/
|
||||
async privateRead(key: string): Promise<any> {
|
||||
return (await this.performPrivateRead(key)).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Privately reads the supplied key from the bucket, returning the value and
|
||||
* metadata corresponding to the key.
|
||||
*
|
||||
* No entity, including the Blyss service, should be able to determine which
|
||||
* key this method was called for.
|
||||
*
|
||||
* @param {string} key - The key to _privately_ retrieve the value of.
|
||||
*/
|
||||
async privateReadWithMetadata(key: string): Promise<DataWithMetadata> {
|
||||
return await this.performPrivateRead(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Privately intersects the given set of keys with the keys in this bucket,
|
||||
* returning the keys that intersected and their values. This is generally
|
||||
* slower than a single private read.
|
||||
*
|
||||
* No entity, including the Blyss service, should be able to determine which
|
||||
* keys this method was called for.
|
||||
*
|
||||
* The number of intersections could be determined by the Blyss service or a
|
||||
* network observer.
|
||||
*
|
||||
* @param keys - The keys to _privately_ intersect the value of.
|
||||
*/
|
||||
async privateIntersect(keys: string[]): Promise<any> {
|
||||
if (keys.length < BLOOM_CUTOFF) {
|
||||
return (await this.performPrivateReads(keys)).map(x => x.data);
|
||||
}
|
||||
|
||||
const bloomFilter = await this.api.bloom(this.name);
|
||||
const matches = [];
|
||||
for (const key of keys) {
|
||||
if (await bloomLookup(bloomFilter, key)) {
|
||||
matches.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return (await this.performPrivateReads(matches)).map(x => x.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Privately intersects the given set of keys with the keys in this bucket,
|
||||
* returning the keys that intersected. This is generally slower than a single
|
||||
* private read.
|
||||
*
|
||||
* No entity, including the Blyss service, should be able to determine which
|
||||
* keys this method was called for.
|
||||
*
|
||||
* @param keys - The keys to _privately_ intersect the value of.
|
||||
*/
|
||||
async privateKeyIntersect(keys: string[]): Promise<string[]> {
|
||||
const bloomFilter = await this.api.bloom(this.name);
|
||||
const matches = [];
|
||||
for (const key of keys) {
|
||||
if (await bloomLookup(bloomFilter, key)) {
|
||||
matches.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the state of the bucket client to a secret seed.
|
||||
*
|
||||
* This secret seed is sensitive! It must stay local to the client to preserve
|
||||
* query privacy.
|
||||
*/
|
||||
toSecretSeed(): string {
|
||||
return this.secretSeed;
|
||||
}
|
||||
}
|
||||
123
js/bucket/bucket_service.ts
Normal file
123
js/bucket/bucket_service.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Api, ApiError } from '../client/api';
|
||||
import { getRandomSeed } from '../client/seed';
|
||||
import { Bucket } from './bucket';
|
||||
|
||||
export interface BucketParameters {
|
||||
/** The maximum item size this bucket supports */
|
||||
maxItemSize: MaxItemSizeIdentifier;
|
||||
}
|
||||
|
||||
type MaxItemSizeIdentifier = '100B' | '1KB' | '10KB';
|
||||
|
||||
const DEFAULT_BUCKET_PARAMETERS: BucketParameters = {
|
||||
maxItemSize: '1KB'
|
||||
};
|
||||
|
||||
const BLYSS_BUCKET_URL = 'https://beta.api.blyss.dev';
|
||||
|
||||
/** Information specifying how to connect to a Blyss bucket service API endpoint. */
|
||||
export interface ApiConfig {
|
||||
/** A fully qualified endpoint URL for the bucket service. */
|
||||
endpoint?: string;
|
||||
/** An API key to supply with every request. */
|
||||
apiKey?: string;
|
||||
}
|
||||
|
||||
/** A class representing a client to the Blyss bucket service. */
|
||||
export class BucketService {
|
||||
apiConfig: ApiConfig;
|
||||
serviceEndpoint: string;
|
||||
api: Api;
|
||||
|
||||
/**
|
||||
* Initialize a client of the Blyss bucket service.
|
||||
*
|
||||
* @param apiConfig - An API key, or an object containing an API
|
||||
* configuration.
|
||||
*/
|
||||
constructor(apiConfig: string | ApiConfig) {
|
||||
if (apiConfig === '<YOUR API KEY HERE>')
|
||||
throw new ApiError(500, '', '', 'You must supply an API key.');
|
||||
|
||||
if (typeof apiConfig === 'string') {
|
||||
this.apiConfig = { apiKey: apiConfig };
|
||||
} else {
|
||||
this.apiConfig = apiConfig;
|
||||
}
|
||||
|
||||
this.serviceEndpoint = BLYSS_BUCKET_URL;
|
||||
if (this.apiConfig.endpoint) {
|
||||
this.serviceEndpoint = this.apiConfig.endpoint;
|
||||
}
|
||||
|
||||
this.api = new Api(this.apiConfig.apiKey, this.serviceEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to an existing Blyss bucket.
|
||||
*
|
||||
* @param {string} bucketName - The name of the bucket to connect to.
|
||||
* @param {string} [secretSeed] - An optional secret seed to initialize the
|
||||
* client using. The secret seed is used to encrypt client queries. If not
|
||||
* supplied, a random one is generated.
|
||||
*/
|
||||
async connect(bucketName: string, secretSeed?: string): Promise<Bucket> {
|
||||
let seed = getRandomSeed();
|
||||
if (secretSeed) {
|
||||
seed = secretSeed;
|
||||
}
|
||||
return await Bucket.initialize(this.api, bucketName, seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Blyss bucket.
|
||||
*
|
||||
* @param {string} bucketName - The bucket name. Bucket names must be 1-128
|
||||
* characters long, composed of only lowercase letters (`[a-z]`), digits
|
||||
* (`[0-9]`), and hyphens (`-`). If you want to share Blyss buckets across
|
||||
* accounts, you can opt-in to the global Blyss namespace by prefixing your
|
||||
* bucket name with `global.`. This is the only way in which the `.`
|
||||
* character is allowed in bucket names.
|
||||
* @param {boolean} [openAccess] - If set to true, this bucket will be
|
||||
* publicly readable by anyone. You should generally also make the bucket
|
||||
* part of the global namespace when setting this option to true. Defaults
|
||||
* to false.
|
||||
* @param {Partial<BucketParameters>} [params] - An optional object of
|
||||
* requested parameters for the bucket. Defaults to `{}`. This is currently
|
||||
* unused.
|
||||
*/
|
||||
async create(
|
||||
bucketName: string,
|
||||
openAccess?: boolean,
|
||||
params?: Partial<BucketParameters>
|
||||
) {
|
||||
openAccess = openAccess || false;
|
||||
const parameters = { ...DEFAULT_BUCKET_PARAMETERS, params };
|
||||
const bucketCreateReq = {
|
||||
name: bucketName,
|
||||
parameters: JSON.stringify(parameters),
|
||||
open_access: openAccess
|
||||
};
|
||||
await this.api.create(JSON.stringify(bucketCreateReq));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Blyss bucket exists.
|
||||
*
|
||||
* @param {string} bucketName - The bucket name.
|
||||
* @returns Whether a bucket with the given name exists.
|
||||
*/
|
||||
async exists(bucketName: string): Promise<boolean> {
|
||||
try {
|
||||
await this.connect(bucketName);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && (e.status === 403 || e.status === 404)) {
|
||||
return false;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
290
js/client/api.ts
Normal file
290
js/client/api.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { KeyInfo } from '../bucket/bucket';
|
||||
import { gzip } from '../compression/pako';
|
||||
import { BloomFilter, bloomFilterFromBytes } from '../data/bloom';
|
||||
|
||||
const CREATE_PATH = '/create';
|
||||
const DESTROY_PATH = '/destroy';
|
||||
const CHECK_PATH = '/check';
|
||||
const DELETE_PATH = '/delete';
|
||||
const META_PATH = '/meta';
|
||||
const BLOOM_PATH = '/bloom';
|
||||
const LIST_KEYS_PATH = '/list-keys';
|
||||
const SETUP_PATH = '/setup';
|
||||
const WRITE_PATH = '/write';
|
||||
const READ_PATH = '/private-read';
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public path: string,
|
||||
public body: string,
|
||||
public msg: string
|
||||
) {
|
||||
super(msg);
|
||||
Object.setPrototypeOf(this, ApiError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export type BucketMetadata = any;
|
||||
|
||||
// HTTP utilities
|
||||
|
||||
async function getData(
|
||||
apiKey: string | null,
|
||||
url: string,
|
||||
getJson: boolean
|
||||
): Promise<any | Uint8Array> {
|
||||
const headers = new Headers();
|
||||
if (apiKey) headers.append('X-API-Key', apiKey);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.status < 200 || response.status > 299) {
|
||||
throw new ApiError(
|
||||
response.status,
|
||||
url,
|
||||
await response.text(),
|
||||
response.statusText
|
||||
);
|
||||
}
|
||||
|
||||
if (getJson) {
|
||||
return response.json();
|
||||
} else {
|
||||
const data = await response.arrayBuffer();
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function postData(
|
||||
apiKey: string | null,
|
||||
url: string,
|
||||
data: Uint8Array | string,
|
||||
getJson: boolean
|
||||
): Promise<Uint8Array | any> {
|
||||
const headers = new Headers();
|
||||
if (apiKey) headers.append('X-API-Key', apiKey);
|
||||
|
||||
if (typeof data === 'string' || data instanceof String) {
|
||||
headers.append('Content-Type', 'application/json');
|
||||
} else {
|
||||
headers.append('Accept-Encoding', 'gzip');
|
||||
headers.append('Content-Encoding', 'gzip');
|
||||
headers.append('Content-Type', 'application/octet-stream');
|
||||
data = gzip(data);
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.status < 200 || response.status > 299) {
|
||||
throw new ApiError(
|
||||
response.status,
|
||||
url,
|
||||
await response.text(),
|
||||
response.statusText
|
||||
);
|
||||
}
|
||||
|
||||
if (getJson) {
|
||||
return response.json();
|
||||
} else {
|
||||
const data = await response.arrayBuffer();
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function postFormData(
|
||||
url: string,
|
||||
fields: any,
|
||||
data: Uint8Array
|
||||
): Promise<Uint8Array | any> {
|
||||
const formData = new FormData();
|
||||
for (const field in fields) {
|
||||
formData.append(field, fields[field]);
|
||||
}
|
||||
formData.append('file', new Blob([data]));
|
||||
|
||||
const req = new Request(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const contentLength = (await req.clone().arrayBuffer()).byteLength;
|
||||
req.headers.append('Content-Length', contentLength + '');
|
||||
|
||||
const response = await fetch(req);
|
||||
|
||||
if (response.status < 200 || response.status > 299) {
|
||||
throw new ApiError(
|
||||
response.status,
|
||||
url,
|
||||
await response.text(),
|
||||
response.statusText
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// API client
|
||||
|
||||
class Api {
|
||||
apiKey: string;
|
||||
serviceEndpoint: string;
|
||||
|
||||
constructor(apiKey: string, serviceEndpoint: string) {
|
||||
this.apiKey = apiKey;
|
||||
this.serviceEndpoint = serviceEndpoint;
|
||||
}
|
||||
|
||||
private serviceUrlFor(path: string): string {
|
||||
return this.serviceEndpoint + path;
|
||||
}
|
||||
|
||||
private urlFor(bucketName: string, path: string): string {
|
||||
return this.serviceEndpoint + '/' + bucketName + path;
|
||||
}
|
||||
|
||||
// Service methods
|
||||
|
||||
/**
|
||||
* Create a new bucket, given the supplied data.
|
||||
*
|
||||
* @param dataJson A JSON-encoded string of the new bucket request.
|
||||
*/
|
||||
async create(dataJson: string) {
|
||||
await postData(
|
||||
this.apiKey,
|
||||
this.serviceUrlFor(CREATE_PATH),
|
||||
dataJson,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a UUID is still valid on the server.
|
||||
*
|
||||
* @param uuid The UUID to check.
|
||||
*/
|
||||
async check(uuid: string): Promise<any> {
|
||||
return await getData(
|
||||
this.apiKey,
|
||||
this.serviceUrlFor('/' + uuid + CHECK_PATH),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Bucket-specific methods
|
||||
|
||||
/**
|
||||
* Get metadata about a bucket.
|
||||
*
|
||||
* @param bucketName The name of the bucket.
|
||||
* @returns Metadata about the bucket.
|
||||
*/
|
||||
async meta(bucketName: string): Promise<BucketMetadata> {
|
||||
return await getData(this.apiKey, this.urlFor(bucketName, META_PATH), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Bloom filter for keys in this bucket. The Bloom filter contains all
|
||||
* keys ever inserted into this bucket; it does not remove deleted keys.
|
||||
*
|
||||
* The false positive rate is determined by parameters chosen by the server.
|
||||
*
|
||||
* @param bucketName The name of the bucket.
|
||||
* @returns The Bloom filter for the keys of this bucket.
|
||||
*/
|
||||
async bloom(bucketName: string): Promise<BloomFilter> {
|
||||
const presignedResp = await getData(
|
||||
this.apiKey,
|
||||
this.urlFor(bucketName, BLOOM_PATH),
|
||||
true
|
||||
);
|
||||
const data = await getData(null, presignedResp['url'], false);
|
||||
const filter = bloomFilterFromBytes(data);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all keys in a bucket.
|
||||
*
|
||||
* @param bucketName The name of the bucket.
|
||||
* @returns A list of information on every key in the bucket.
|
||||
*/
|
||||
async listKeys(bucketName: string): Promise<KeyInfo[]> {
|
||||
return await getData(
|
||||
this.apiKey,
|
||||
this.urlFor(bucketName, LIST_KEYS_PATH),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload new setup data.
|
||||
*
|
||||
* @param bucketName The name of the bucket associated with this setup data.
|
||||
* @param data The setup data.
|
||||
* @returns The setup data upload response, containing a UUID.
|
||||
*/
|
||||
async setup(bucketName: string, data: Uint8Array): Promise<any> {
|
||||
const prelim_result = await postData(
|
||||
this.apiKey,
|
||||
this.urlFor(bucketName, SETUP_PATH),
|
||||
JSON.stringify({ length: data.length }),
|
||||
true
|
||||
);
|
||||
|
||||
// perform the long upload
|
||||
await postFormData(prelim_result['url'], prelim_result['fields'], data);
|
||||
|
||||
return prelim_result;
|
||||
}
|
||||
|
||||
/** Destroy this bucket. */
|
||||
async destroy(bucketName: string) {
|
||||
await postData(
|
||||
this.apiKey,
|
||||
this.urlFor(bucketName, DESTROY_PATH),
|
||||
'',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/** Write to this bucket. */
|
||||
async write(bucketName: string, data: Uint8Array) {
|
||||
await postData(
|
||||
this.apiKey,
|
||||
this.urlFor(bucketName, WRITE_PATH),
|
||||
data,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/** Delete a key in this bucket. */
|
||||
async deleteKey(bucketName: string, key: string) {
|
||||
await postData(
|
||||
this.apiKey,
|
||||
this.urlFor(bucketName, DELETE_PATH),
|
||||
new TextEncoder().encode(key),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/** Privately read data from this bucket. */
|
||||
async privateRead(bucketName: string, data: Uint8Array): Promise<Uint8Array> {
|
||||
return await postData(
|
||||
this.apiKey,
|
||||
this.urlFor(bucketName, READ_PATH),
|
||||
data,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Api };
|
||||
53
js/client/seed.ts
Normal file
53
js/client/seed.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { isNode } from '../lib/util';
|
||||
|
||||
const SEED_BYTES = 32;
|
||||
const SEED_STR_LEN = 44;
|
||||
|
||||
export function bytesToBase64(arr: Uint8Array): string {
|
||||
if (isNode()) {
|
||||
return Buffer.from(arr).toString('base64');
|
||||
} else {
|
||||
const output = [];
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
output.push(String.fromCharCode(arr[i]));
|
||||
}
|
||||
|
||||
return btoa(output.join(''));
|
||||
}
|
||||
}
|
||||
|
||||
export function base64ToBytes(inp: string): Uint8Array {
|
||||
if (isNode()) {
|
||||
return Buffer.from(inp, 'base64');
|
||||
} else {
|
||||
return Uint8Array.from(atob(inp), c => c.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
|
||||
export function seedFromString(seedStr: string): Uint8Array {
|
||||
if (seedStr.length !== SEED_STR_LEN)
|
||||
throw new Error('Incorrect seed length.');
|
||||
const seed = base64ToBytes(seedStr);
|
||||
if (seed.length !== SEED_BYTES) throw new Error('Incorrect seed length.');
|
||||
return seed;
|
||||
}
|
||||
|
||||
export function stringFromSeed(seed: Uint8Array): string {
|
||||
if (seed.length !== SEED_BYTES) throw new Error('Incorrect seed length.');
|
||||
const seedStr = bytesToBase64(seed);
|
||||
if (seedStr.length !== SEED_STR_LEN)
|
||||
throw new Error('Incorrect seed length.');
|
||||
return seedStr;
|
||||
}
|
||||
|
||||
export function getRandomSeed(): string {
|
||||
const seed = new Uint8Array(SEED_BYTES);
|
||||
crypto.getRandomValues(seed);
|
||||
return stringFromSeed(seed);
|
||||
}
|
||||
|
||||
export function getInsecureFixedSeed(): string {
|
||||
const seed = new Uint8Array(SEED_BYTES);
|
||||
return stringFromSeed(seed);
|
||||
}
|
||||
352
js/compression/bz2_decompress.js
Normal file
352
js/compression/bz2_decompress.js
Normal file
@@ -0,0 +1,352 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
|
||||
/*! bz2 (C) 2019-present SheetJS LLC */
|
||||
|
||||
'use strict';
|
||||
|
||||
// https://www.ncbi.nlm.nih.gov/IEB/ToolBox/CPP_DOC/lxr/source/src/util/compress/bzip2/crctable.c
|
||||
const crc32Table = [
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
|
||||
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
||||
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
|
||||
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
|
||||
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
|
||||
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
|
||||
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
|
||||
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
|
||||
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
|
||||
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
|
||||
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
|
||||
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
|
||||
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
|
||||
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
|
||||
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
|
||||
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
|
||||
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
|
||||
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
|
||||
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
||||
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
|
||||
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
|
||||
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
|
||||
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
|
||||
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
|
||||
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
|
||||
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
|
||||
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
|
||||
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
|
||||
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
|
||||
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
|
||||
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
|
||||
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
];
|
||||
|
||||
// generated from 1 << i, except for 32
|
||||
const masks = [
|
||||
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f,
|
||||
0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff,
|
||||
0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, 0x0001ffff,
|
||||
0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff,
|
||||
0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, 0x1fffffff,
|
||||
0x3fffffff, -0x80000000
|
||||
];
|
||||
|
||||
function createOrderedHuffmanTable(lengths) {
|
||||
const z = [];
|
||||
for (let i = 0; i < lengths.length; i += 1) {
|
||||
z.push([i, lengths[i]]);
|
||||
}
|
||||
z.push([lengths.length, -1]);
|
||||
const table = [];
|
||||
let start = z[0][0];
|
||||
let bits = z[0][1];
|
||||
for (let i = 0; i < z.length; i += 1) {
|
||||
const finish = z[i][0];
|
||||
const endbits = z[i][1];
|
||||
if (bits) {
|
||||
for (let code = start; code < finish; code += 1) {
|
||||
table.push({ code, bits, symbol: undefined });
|
||||
}
|
||||
}
|
||||
start = finish;
|
||||
bits = endbits;
|
||||
if (endbits === -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
table.sort((a, b) => a.bits - b.bits || a.code - b.code);
|
||||
let tempBits = 0;
|
||||
let symbol = -1;
|
||||
const fastAccess = [];
|
||||
let current;
|
||||
for (let i = 0; i < table.length; i += 1) {
|
||||
const t = table[i];
|
||||
symbol += 1;
|
||||
if (t.bits !== tempBits) {
|
||||
symbol <<= t.bits - tempBits;
|
||||
tempBits = t.bits;
|
||||
current = fastAccess[tempBits] = {};
|
||||
}
|
||||
t.symbol = symbol;
|
||||
current[symbol] = t;
|
||||
}
|
||||
return {
|
||||
table,
|
||||
fastAccess
|
||||
};
|
||||
}
|
||||
|
||||
function bwtReverse(src, primary) {
|
||||
if (primary < 0 || primary >= src.length) {
|
||||
throw RangeError('Out of bound');
|
||||
}
|
||||
const unsorted = src.slice();
|
||||
src.sort((a, b) => a - b);
|
||||
const start = {};
|
||||
for (let i = src.length - 1; i >= 0; i -= 1) {
|
||||
start[src[i]] = i;
|
||||
}
|
||||
const links = [];
|
||||
for (let i = 0; i < src.length; i += 1) {
|
||||
links.push(start[unsorted[i]]++); // eslint-disable-line no-plusplus
|
||||
}
|
||||
let i;
|
||||
const first = src[(i = primary)];
|
||||
const ret = [];
|
||||
for (let j = 1; j < src.length; j += 1) {
|
||||
const x = src[(i = links[i])];
|
||||
if (x === undefined) {
|
||||
ret.push(255);
|
||||
} else {
|
||||
ret.push(x);
|
||||
}
|
||||
}
|
||||
ret.push(first);
|
||||
ret.reverse();
|
||||
return ret;
|
||||
}
|
||||
|
||||
function decompress(bytes, checkCRC = false) {
|
||||
let index = 0;
|
||||
let bitfield = 0;
|
||||
let bits = 0;
|
||||
const read = n => {
|
||||
if (n >= 32) {
|
||||
const nd = n >> 1;
|
||||
return read(nd) * (1 << nd) + read(n - nd);
|
||||
}
|
||||
while (bits < n) {
|
||||
bitfield = (bitfield << 8) + bytes[index];
|
||||
index += 1;
|
||||
bits += 8;
|
||||
}
|
||||
const m = masks[n];
|
||||
const r = (bitfield >> (bits - n)) & m;
|
||||
bits -= n;
|
||||
bitfield &= ~(m << bits);
|
||||
return r;
|
||||
};
|
||||
|
||||
const magic = read(16);
|
||||
if (magic !== 0x425a) {
|
||||
// 'BZ'
|
||||
throw new Error('Invalid magic');
|
||||
}
|
||||
const method = read(8);
|
||||
if (method !== 0x68) {
|
||||
// h for huffman
|
||||
throw new Error('Invalid method');
|
||||
}
|
||||
|
||||
let blocksize = read(8);
|
||||
if (blocksize >= 49 && blocksize <= 57) {
|
||||
// 1..9
|
||||
blocksize -= 48;
|
||||
} else {
|
||||
throw new Error('Invalid blocksize');
|
||||
}
|
||||
|
||||
let out = new Uint8Array(bytes.length * 1.5);
|
||||
let outIndex = 0;
|
||||
let newCRC = -1;
|
||||
while (true) {
|
||||
const blocktype = read(48);
|
||||
const crc = read(32) | 0;
|
||||
if (blocktype === 0x314159265359) {
|
||||
if (read(1)) {
|
||||
throw new Error('do not support randomised');
|
||||
}
|
||||
const pointer = read(24);
|
||||
const used = [];
|
||||
const usedGroups = read(16);
|
||||
for (let i = 1 << 15; i > 0; i >>= 1) {
|
||||
if (!(usedGroups & i)) {
|
||||
for (let j = 0; j < 16; j += 1) {
|
||||
used.push(false);
|
||||
}
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
const usedChars = read(16);
|
||||
for (let j = 1 << 15; j > 0; j >>= 1) {
|
||||
used.push(!!(usedChars & j));
|
||||
}
|
||||
}
|
||||
const groups = read(3);
|
||||
if (groups < 2 || groups > 6) {
|
||||
throw new Error('Invalid number of huffman groups');
|
||||
}
|
||||
const selectorsUsed = read(15);
|
||||
const selectors = [];
|
||||
const mtf = Array.from({ length: groups }, (_, i) => i);
|
||||
for (let i = 0; i < selectorsUsed; i += 1) {
|
||||
let c = 0;
|
||||
while (read(1)) {
|
||||
c += 1;
|
||||
if (c >= groups) {
|
||||
throw new Error('MTF table out of range');
|
||||
}
|
||||
}
|
||||
const v = mtf[c];
|
||||
for (let j = c; j > 0; mtf[j] = mtf[--j]) {
|
||||
// eslint-disable-line no-plusplus
|
||||
// nothing
|
||||
}
|
||||
selectors.push(v);
|
||||
mtf[0] = v;
|
||||
}
|
||||
const symbolsInUse = used.reduce((a, b) => a + b, 0) + 2;
|
||||
const tables = [];
|
||||
for (let i = 0; i < groups; i += 1) {
|
||||
let length = read(5);
|
||||
const lengths = [];
|
||||
for (let j = 0; j < symbolsInUse; j += 1) {
|
||||
if (length < 0 || length > 20) {
|
||||
throw new Error('Huffman group length outside range');
|
||||
}
|
||||
while (read(1)) {
|
||||
length -= read(1) * 2 - 1;
|
||||
}
|
||||
lengths.push(length);
|
||||
}
|
||||
tables.push(createOrderedHuffmanTable(lengths));
|
||||
}
|
||||
const favourites = [];
|
||||
for (let i = 0; i < used.length - 1; i += 1) {
|
||||
if (used[i]) {
|
||||
favourites.push(i);
|
||||
}
|
||||
}
|
||||
let decoded = 0;
|
||||
let selectorPointer = 0;
|
||||
let t;
|
||||
let r;
|
||||
let repeat = 0;
|
||||
let repeatPower = 0;
|
||||
const buffer = [];
|
||||
while (true) {
|
||||
decoded -= 1;
|
||||
if (decoded <= 0) {
|
||||
decoded = 50;
|
||||
if (selectorPointer <= selectors.length) {
|
||||
t = tables[selectors[selectorPointer]];
|
||||
selectorPointer += 1;
|
||||
}
|
||||
}
|
||||
for (const b in t.fastAccess) {
|
||||
if (!Object.prototype.hasOwnProperty.call(t.fastAccess, b)) {
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
if (bits < b) {
|
||||
bitfield = (bitfield << 8) + bytes[index];
|
||||
index += 1;
|
||||
bits += 8;
|
||||
}
|
||||
r = t.fastAccess[b][bitfield >> (bits - b)];
|
||||
if (r) {
|
||||
bitfield &= masks[(bits -= b)];
|
||||
r = r.code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r >= 0 && r <= 1) {
|
||||
if (repeat === 0) {
|
||||
repeatPower = 1;
|
||||
}
|
||||
repeat += repeatPower << r;
|
||||
repeatPower <<= 1;
|
||||
continue; // eslint-disable-line no-continue
|
||||
} else {
|
||||
const v = favourites[0];
|
||||
for (; repeat > 0; repeat -= 1) {
|
||||
buffer.push(v);
|
||||
}
|
||||
}
|
||||
if (r === symbolsInUse - 1) {
|
||||
break;
|
||||
} else {
|
||||
const v = favourites[r - 1];
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let j = r - 1; j > 0; favourites[j] = favourites[--j]) {
|
||||
// nothing
|
||||
}
|
||||
favourites[0] = v;
|
||||
buffer.push(v);
|
||||
}
|
||||
}
|
||||
const nt = bwtReverse(buffer, pointer);
|
||||
let i = 0;
|
||||
while (i < nt.length) {
|
||||
const c = nt[i];
|
||||
let count = 1;
|
||||
if (
|
||||
i < nt.length - 4 &&
|
||||
nt[i + 1] === c &&
|
||||
nt[i + 2] === c &&
|
||||
nt[i + 3] === c
|
||||
) {
|
||||
count = nt[i + 4] + 4;
|
||||
i += 5;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
if (outIndex + count >= out.length) {
|
||||
const old = out;
|
||||
out = new Uint8Array(old.length * 2);
|
||||
out.set(old);
|
||||
}
|
||||
for (let j = 0; j < count; j += 1) {
|
||||
if (checkCRC) {
|
||||
newCRC = (newCRC << 8) ^ crc32Table[((newCRC >> 24) ^ c) & 0xff];
|
||||
}
|
||||
out[outIndex] = c;
|
||||
outIndex += 1;
|
||||
}
|
||||
}
|
||||
if (checkCRC) {
|
||||
const calculatedCRC = newCRC ^ -1;
|
||||
if (calculatedCRC !== crc) {
|
||||
throw new Error(`CRC mismatch: ${calculatedCRC} !== ${crc}`);
|
||||
}
|
||||
newCRC = -1;
|
||||
}
|
||||
} else if (blocktype === 0x177245385090) {
|
||||
read(bits & 0x07); // pad align
|
||||
break;
|
||||
} else {
|
||||
throw new Error('Invalid bz2 blocktype');
|
||||
}
|
||||
}
|
||||
return out.subarray(0, outIndex);
|
||||
}
|
||||
|
||||
export { decompress };
|
||||
7262
js/compression/pako.js
Normal file
7262
js/compression/pako.js
Normal file
File diff suppressed because it is too large
Load Diff
88
js/data/bloom.ts
Normal file
88
js/data/bloom.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { mergeUint8Arrays } from './serializer';
|
||||
|
||||
export type BloomFilter = {
|
||||
k: number;
|
||||
bits: number;
|
||||
data: Uint8Array;
|
||||
};
|
||||
|
||||
export function bloomFilterFromBytes(rawData: Uint8Array): BloomFilter {
|
||||
const dv = new DataView(rawData.buffer);
|
||||
const k = dv.getUint32(0, true);
|
||||
const bits = dv.getUint32(4, true);
|
||||
const data = rawData.slice(8);
|
||||
return { k, bits, data };
|
||||
}
|
||||
|
||||
function topBEBits(data: Uint8Array, bits: number): number {
|
||||
let num = 0;
|
||||
for (let i = 0; i < bits; i++) {
|
||||
const bit = data[Math.floor(i / 8)] & (1 << (7 - (i % 8)));
|
||||
if (bit != 0) {
|
||||
num += 1 << (bits - 1 - i);
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
function toLEBytesUint32(num: number): Uint8Array {
|
||||
const ab = new ArrayBuffer(4);
|
||||
const dv = new DataView(ab);
|
||||
dv.setUint32(0, num, true);
|
||||
return new Uint8Array(ab);
|
||||
}
|
||||
|
||||
async function bloomHash(
|
||||
bloomFilter: BloomFilter,
|
||||
key: string,
|
||||
hashIdx: number
|
||||
): Promise<number> {
|
||||
const dataToHash = mergeUint8Arrays(
|
||||
toLEBytesUint32(hashIdx),
|
||||
new TextEncoder().encode(key)
|
||||
);
|
||||
const hashVal = new Uint8Array(
|
||||
await crypto.subtle.digest('SHA-1', dataToHash)
|
||||
);
|
||||
const num = topBEBits(hashVal, bloomFilter.bits);
|
||||
return num;
|
||||
}
|
||||
|
||||
function checkBit(data: Uint8Array, idx: number): boolean {
|
||||
return (data[Math.floor(idx / 8)] & (1 << (7 - (idx % 8)))) != 0;
|
||||
}
|
||||
|
||||
function setBit(data: Uint8Array, idx: number) {
|
||||
data[Math.floor(idx / 8)] |= 1 << (7 - (idx % 8));
|
||||
}
|
||||
|
||||
export async function bloomLookup(
|
||||
bloomFilter: BloomFilter,
|
||||
key: string
|
||||
): Promise<boolean> {
|
||||
for (let i = 0; i < bloomFilter.k; i++) {
|
||||
const idx = await bloomHash(bloomFilter, key, i);
|
||||
const check = checkBit(bloomFilter.data, idx);
|
||||
if (!check) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function bloomWrite(
|
||||
bloomFilter: BloomFilter,
|
||||
key: string
|
||||
): Promise<void> {
|
||||
for (let i = 0; i < bloomFilter.k; i++) {
|
||||
const idx = await bloomHash(bloomFilter, key, i);
|
||||
setBit(bloomFilter.data, idx);
|
||||
}
|
||||
}
|
||||
|
||||
export function bloomInit(k: number, bits: number) {
|
||||
return {
|
||||
k,
|
||||
bits,
|
||||
data: new Uint8Array(1 << (bits - 3))
|
||||
};
|
||||
}
|
||||
147
js/data/serializer.ts
Normal file
147
js/data/serializer.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import * as varint from './varint';
|
||||
|
||||
export function serializeChunks(chunks: Uint8Array[]): Uint8Array {
|
||||
let totalLen = 0;
|
||||
for (const chunk of chunks) {
|
||||
totalLen += chunk.length;
|
||||
}
|
||||
|
||||
const out = new Uint8Array(totalLen + chunks.length * 8 + 8);
|
||||
const dv = new DataView(out.buffer);
|
||||
dv.setBigUint64(0, BigInt(chunks.length), true);
|
||||
let offs = 8;
|
||||
for (const chunk of chunks) {
|
||||
dv.setBigUint64(offs, BigInt(chunk.length), true);
|
||||
offs += 8;
|
||||
out.set(chunk, offs);
|
||||
offs += chunk.length;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function deserializeChunks(serializedChunks: Uint8Array): Uint8Array[] {
|
||||
const dv = new DataView(serializedChunks.buffer);
|
||||
let offs = 0;
|
||||
const numChunks = Number(dv.getBigUint64(offs, true));
|
||||
offs += 8;
|
||||
|
||||
const chunks = [];
|
||||
for (let i = 0; i < numChunks; i++) {
|
||||
const chunkLen = Number(dv.getBigUint64(offs, true));
|
||||
offs += 8;
|
||||
const chunk = serializedChunks.slice(offs, offs + chunkLen);
|
||||
offs += chunkLen;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
export function mergeUint8Arrays(
|
||||
arr1: Uint8Array,
|
||||
arr2: Uint8Array
|
||||
): Uint8Array {
|
||||
const mergedArray = new Uint8Array(arr1.length + arr2.length);
|
||||
mergedArray.set(arr1);
|
||||
mergedArray.set(arr2, arr1.length);
|
||||
return mergedArray;
|
||||
}
|
||||
|
||||
function getObjectAsBytes(obj: any): Uint8Array {
|
||||
if (obj instanceof ArrayBuffer || obj instanceof Uint8Array) {
|
||||
return obj instanceof ArrayBuffer ? new Uint8Array(obj) : obj;
|
||||
}
|
||||
const encoder = new TextEncoder();
|
||||
const objJson = JSON.stringify(obj);
|
||||
if (objJson === undefined)
|
||||
throw new Error('Object does not properly serialize');
|
||||
return encoder.encode(objJson);
|
||||
}
|
||||
|
||||
function getHeaderBytes(obj: any, metadata?: any): Uint8Array {
|
||||
if (!metadata && (obj instanceof ArrayBuffer || obj instanceof Uint8Array)) {
|
||||
return varint.encode(0);
|
||||
} else {
|
||||
const encoder = new TextEncoder();
|
||||
const headerData = { contentType: 'application/json', ...metadata };
|
||||
const header = JSON.stringify(headerData);
|
||||
const headerVarInt = varint.encode(header.length);
|
||||
return mergeUint8Arrays(headerVarInt, encoder.encode(header));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely serializes an object (and optional metadta) into bytes.
|
||||
*
|
||||
* @param obj - Object to serialize.
|
||||
*/
|
||||
export function serialize(obj: any, metadata?: any): Uint8Array {
|
||||
const headerBytes = getHeaderBytes(obj, metadata);
|
||||
const objAsBytes = getObjectAsBytes(obj);
|
||||
|
||||
return mergeUint8Arrays(headerBytes, objAsBytes);
|
||||
}
|
||||
|
||||
export interface DataWithMetadata {
|
||||
data: any;
|
||||
metadata?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely deserializes an object, and possibly any associated metadata, from the
|
||||
* input bytes.
|
||||
*
|
||||
* @param data - Bytes to deserialize.
|
||||
*/
|
||||
export function deserialize(data: Uint8Array): DataWithMetadata {
|
||||
const { value, bytesProcessed } = varint.decode(data);
|
||||
const headerLength = value;
|
||||
let i = bytesProcessed;
|
||||
if (headerLength === 0) {
|
||||
return { data: data.slice(i) };
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const headerBytes = data.slice(i, i + headerLength);
|
||||
i += headerLength;
|
||||
const header = JSON.parse(decoder.decode(headerBytes));
|
||||
|
||||
const dataBytes = data.slice(i);
|
||||
|
||||
let obj;
|
||||
if (header.contentType === 'application/json') {
|
||||
obj = JSON.parse(decoder.decode(dataBytes));
|
||||
} else {
|
||||
obj = dataBytes;
|
||||
}
|
||||
|
||||
return {
|
||||
data: obj,
|
||||
metadata: header
|
||||
};
|
||||
}
|
||||
|
||||
/** Concatenate the input Uint8Arrays. */
|
||||
export function concatBytes(arrays: Uint8Array[]): Uint8Array {
|
||||
let totalLen = 0;
|
||||
for (const arr of arrays) {
|
||||
totalLen += arr.length;
|
||||
}
|
||||
|
||||
const output = new Uint8Array(totalLen);
|
||||
let idx = 0;
|
||||
for (const arr of arrays) {
|
||||
output.set(arr, idx);
|
||||
idx += arr.length;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/** Wraps a key and value into a single bytes sequence. */
|
||||
export function wrapKeyValue(key: Uint8Array, value: Uint8Array): Uint8Array {
|
||||
const keyLenVarint = varint.encode(key.length);
|
||||
const valueLenVarint = varint.encode(value.length);
|
||||
return concatBytes([keyLenVarint, key, valueLenVarint, value]);
|
||||
}
|
||||
62
js/data/varint.ts
Normal file
62
js/data/varint.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// adapted from github.com/chrisdickinson/varint
|
||||
|
||||
const MSB = 0x80,
|
||||
REST = 0x7f,
|
||||
MSBALL = ~REST,
|
||||
INT = Math.pow(2, 31);
|
||||
|
||||
/**
|
||||
* Encodes a number as a varint.
|
||||
* @param num - the number to encode
|
||||
* @returns a `Uint8Array` encoding the number as a varint
|
||||
*/
|
||||
export function encode(num: number): Uint8Array {
|
||||
if (Number.MAX_SAFE_INTEGER && num > Number.MAX_SAFE_INTEGER) {
|
||||
throw new RangeError("Could not encode varint");
|
||||
}
|
||||
const out = [];
|
||||
let offset = 0;
|
||||
|
||||
while (num >= INT) {
|
||||
out[offset++] = (num & 0xff) | MSB;
|
||||
num /= 128;
|
||||
}
|
||||
while (num & MSBALL) {
|
||||
out[offset++] = (num & 0xff) | MSB;
|
||||
num >>>= 7;
|
||||
}
|
||||
out[offset] = num | 0;
|
||||
|
||||
return new Uint8Array(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a varint.
|
||||
* @param buf - the buffer to decode
|
||||
* @returns `value`, the value of the varint,
|
||||
* and `bytesProcessed`, the number of bytes consumed
|
||||
*/
|
||||
export function decode(buf: Uint8Array): {
|
||||
value: number;
|
||||
bytesProcessed: number;
|
||||
} {
|
||||
let res = 0,
|
||||
shift = 0,
|
||||
counter = 0,
|
||||
b;
|
||||
const l = buf.length;
|
||||
|
||||
do {
|
||||
if (counter >= l || shift > 49) {
|
||||
throw new RangeError("Could not decode varint");
|
||||
}
|
||||
b = buf[counter++];
|
||||
res += shift < 28 ? (b & REST) << shift : (b & REST) * Math.pow(2, shift);
|
||||
shift += 7;
|
||||
} while (b >= MSB);
|
||||
|
||||
return {
|
||||
value: res,
|
||||
bytesProcessed: counter,
|
||||
};
|
||||
}
|
||||
20
js/index.ts
Normal file
20
js/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Bucket, KeyInfo } from './bucket/bucket';
|
||||
import type { ApiConfig } from './bucket/bucket_service';
|
||||
import { BucketService } from './bucket/bucket_service';
|
||||
import type { ApiError } from './client/api';
|
||||
import { DataWithMetadata } from './data/serializer';
|
||||
|
||||
export { BucketService as Client };
|
||||
|
||||
export type {
|
||||
Bucket,
|
||||
KeyInfo,
|
||||
BucketService,
|
||||
ApiError,
|
||||
ApiConfig,
|
||||
DataWithMetadata
|
||||
};
|
||||
|
||||
// External copyright notices:
|
||||
/*! pako (C) 1995-2013 Jean-loup Gailly and Mark Adler */
|
||||
/*! pako (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin */
|
||||
51
js/lib/blyss_lib.ts
Normal file
51
js/lib/blyss_lib.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { seedFromString } from '../client/seed';
|
||||
|
||||
import {
|
||||
ApiClient,
|
||||
decode_response,
|
||||
extract_result,
|
||||
generate_keys,
|
||||
generate_query,
|
||||
get_row,
|
||||
initialize_client
|
||||
} from './helper';
|
||||
|
||||
export class BlyssLib {
|
||||
private innerClient: ApiClient;
|
||||
private secretSeed: string;
|
||||
|
||||
generateKeys(generatePublicParameters: boolean) {
|
||||
return generate_keys(
|
||||
this.innerClient,
|
||||
seedFromString(this.secretSeed),
|
||||
generatePublicParameters
|
||||
);
|
||||
}
|
||||
|
||||
getRow(key: string): number {
|
||||
return get_row(this.innerClient, key);
|
||||
}
|
||||
|
||||
generateQuery(uuid: string, rowIdx: number) {
|
||||
return generate_query(this.innerClient, uuid, rowIdx);
|
||||
}
|
||||
|
||||
decodeResponse(response: Uint8Array): Uint8Array {
|
||||
return decode_response(this.innerClient, response);
|
||||
}
|
||||
|
||||
extractResult(key: string, data: Uint8Array): Uint8Array {
|
||||
return extract_result(this.innerClient, key, data);
|
||||
}
|
||||
|
||||
free() {
|
||||
this.innerClient.free();
|
||||
this.innerClient = null;
|
||||
this.secretSeed = '';
|
||||
}
|
||||
|
||||
constructor(params: string, secretSeed: string) {
|
||||
this.innerClient = initialize_client(params);
|
||||
this.secretSeed = secretSeed;
|
||||
}
|
||||
}
|
||||
23
js/lib/helper.ts
Normal file
23
js/lib/helper.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Not the nicest way to import WASM, but allows Webpack
|
||||
// to bundle everything into a single JS file
|
||||
import initWasm, {
|
||||
ApiClient,
|
||||
decode_response,
|
||||
extract_result,
|
||||
generate_keys,
|
||||
generate_query,
|
||||
get_row,
|
||||
initialize_client
|
||||
} from '../../dist/lib/lib';
|
||||
import wasmData from '../../dist/lib/lib_bg.wasm';
|
||||
initWasm(wasmData);
|
||||
|
||||
export {
|
||||
ApiClient,
|
||||
decode_response,
|
||||
extract_result,
|
||||
generate_keys,
|
||||
generate_query,
|
||||
get_row,
|
||||
initialize_client
|
||||
};
|
||||
7
js/lib/util.ts
Normal file
7
js/lib/util.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function isNode(): boolean {
|
||||
return !!(
|
||||
typeof process !== "undefined" &&
|
||||
process.versions &&
|
||||
process.versions.node
|
||||
);
|
||||
}
|
||||
21
js/tests/bloom.test.ts
Normal file
21
js/tests/bloom.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { bloomLookup, bloomWrite, bloomInit } from '../data/bloom';
|
||||
|
||||
describe('bloom filter write + lookup', () => {
|
||||
it.each([
|
||||
[10, 24, ['a', 'b', 'c']],
|
||||
[8, 18, ['x', 'y', 'z']]
|
||||
])(`should work`, async (k, bits, vals) => {
|
||||
const filter = bloomInit(k, bits);
|
||||
for (const val of vals) {
|
||||
bloomWrite(filter, val);
|
||||
}
|
||||
for (const val of vals) {
|
||||
const got = await bloomLookup(filter, val);
|
||||
expect(got).toEqual(true);
|
||||
}
|
||||
for (const val of vals) {
|
||||
const got = await bloomLookup(filter, val + '############');
|
||||
expect(got).toEqual(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
10
js/tests/seed.test.ts
Normal file
10
js/tests/seed.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { seedFromString, stringFromSeed } from '../client/seed';
|
||||
|
||||
describe('seedFromString/stringFromSeed routines', () => {
|
||||
it.each([
|
||||
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
|
||||
'11opbOgHEQUCgBcXSeOv7wnYCEJFmycNY+HhuypZQJY='
|
||||
])(`should be inverses for: %s`, val => {
|
||||
expect(stringFromSeed(seedFromString(val))).toEqual(val);
|
||||
});
|
||||
});
|
||||
47
js/tests/serializer.test.ts
Normal file
47
js/tests/serializer.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
serialize,
|
||||
deserialize,
|
||||
deserializeChunks,
|
||||
serializeChunks
|
||||
} from '../data/serializer';
|
||||
|
||||
describe('serialization', () => {
|
||||
it.each([undefined, () => 'foo' /*{ a: "foo", b: { c: () => "foo" } }*/])(
|
||||
`should fail for: %s`,
|
||||
val => {
|
||||
expect(() => serialize(val)).toThrow();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('serialization/deserialization routines', () => {
|
||||
it.each([
|
||||
[0, 0, 0],
|
||||
'foo',
|
||||
{ a: 'foo' },
|
||||
{ a: 'foo', b: { c: 'bar', d: ['baz'] } },
|
||||
0,
|
||||
null
|
||||
])(`should be inverses for: %s`, val => {
|
||||
expect(deserialize(serialize(val)).data).toEqual(val);
|
||||
});
|
||||
});
|
||||
|
||||
describe('chunk serialization/deserialization routines', () => {
|
||||
it.each([
|
||||
[
|
||||
[
|
||||
new Uint8Array([0, 1, 0]),
|
||||
new Uint8Array([77, 12, 10]),
|
||||
new Uint8Array([0]),
|
||||
new Uint8Array([190, 1, 4, 6, 1])
|
||||
]
|
||||
],
|
||||
[[new Uint8Array([0, 1, 0])]],
|
||||
[[new Uint8Array([1])]],
|
||||
[[new Uint8Array([])]],
|
||||
[[]]
|
||||
])(`should be inverses for: %s`, val => {
|
||||
expect(deserializeChunks(serializeChunks(val))).toEqual(val);
|
||||
});
|
||||
});
|
||||
18
js/tests/varint.test.ts
Normal file
18
js/tests/varint.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { encode, decode } from '../data/varint';
|
||||
|
||||
describe('encode/decode routines', () => {
|
||||
it.each([
|
||||
0,
|
||||
1,
|
||||
127,
|
||||
128,
|
||||
12345678,
|
||||
100,
|
||||
1000,
|
||||
1 << 32,
|
||||
1 << 50,
|
||||
(1 << 32) - 1
|
||||
])(`should be inverses for: %i`, val => {
|
||||
expect(decode(encode(val)).value).toEqual(val);
|
||||
});
|
||||
});
|
||||
6
lib/.gitignore
vendored
Normal file
6
lib/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
target/
|
||||
*.gz
|
||||
*.dat
|
||||
*.DS_Store
|
||||
.cache/
|
||||
dist/
|
||||
2
lib/spiral-rs/.cargo/config.toml
Normal file
2
lib/spiral-rs/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[target.'cfg(target_feature = "avx2")']
|
||||
rustflags = ["-C", "target-feature=+avx2"]
|
||||
419
lib/spiral-rs/Cargo.lock
generated
Normal file
419
lib/spiral-rs/Cargo.lock
generated
Normal file
@@ -0,0 +1,419 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spiral-rs"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rayon",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
16
lib/spiral-rs/Cargo.toml
Normal file
16
lib/spiral-rs/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "spiral-rs"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
server = ["rayon"]
|
||||
|
||||
[dependencies]
|
||||
rayon = { version = "1.6.1", optional = true }
|
||||
getrandom = { features = ["js"], version = "0.2.6" }
|
||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
serde_json = "1.0"
|
||||
rand_chacha = "0.3.1"
|
||||
sha2 = "0.10"
|
||||
subtle = "2.4"
|
||||
@@ -4,10 +4,29 @@ use crate::{
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use std::{iter::once, mem::size_of};
|
||||
use subtle::ConditionallySelectable;
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
pub type Seed = <ChaCha20Rng as SeedableRng>::Seed;
|
||||
pub const SEED_LENGTH: usize = 32;
|
||||
|
||||
pub const DEFAULT_PARAMS: &'static str = r#"
|
||||
{"n": 2,
|
||||
"nu_1": 10,
|
||||
"nu_2": 6,
|
||||
"p": 512,
|
||||
"q2_bits": 21,
|
||||
"s_e": 85.83255142749422,
|
||||
"t_gsw": 10,
|
||||
"t_conv": 4,
|
||||
"t_exp_left": 16,
|
||||
"t_exp_right": 56,
|
||||
"instances": 11,
|
||||
"db_item_size": 100000 }
|
||||
"#;
|
||||
|
||||
const UUID_V4_LEN: usize = 36;
|
||||
|
||||
fn new_vec_raw<'a>(
|
||||
params: &'a Params,
|
||||
num: usize,
|
||||
@@ -328,7 +347,7 @@ impl<'a> Client<'a> {
|
||||
let sk_gsw_full = matrix_with_identity(&sk_gsw);
|
||||
let sk_reg_full = matrix_with_identity(&sk_reg);
|
||||
|
||||
let dg = DiscreteGaussian::init(params);
|
||||
let dg = DiscreteGaussian::init(params.noise_width);
|
||||
|
||||
Self {
|
||||
params,
|
||||
@@ -464,6 +483,19 @@ impl<'a> Client<'a> {
|
||||
self.generate_secret_keys_impl(&mut ChaCha20Rng::from_entropy())
|
||||
}
|
||||
|
||||
pub fn generate_keys_optional(
|
||||
&mut self,
|
||||
seed: Seed,
|
||||
generate_pub_params: bool,
|
||||
) -> Option<Vec<u8>> {
|
||||
if generate_pub_params {
|
||||
Some(self.generate_keys_from_seed(seed).serialize())
|
||||
} else {
|
||||
self.generate_secret_keys_from_seed(seed);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_secret_keys_impl(&mut self, rng: &mut ChaCha20Rng) {
|
||||
self.dg.sample_matrix(&mut self.sk_gsw, rng);
|
||||
self.dg.sample_matrix(&mut self.sk_reg, rng);
|
||||
@@ -557,17 +589,24 @@ impl<'a> Client<'a> {
|
||||
invert_uint_mod(1 << (params.stop_round() + 1), params.modulus).unwrap();
|
||||
|
||||
if params.db_dim_2 == 0 {
|
||||
sigma.data[idx_dim0] = scale_k;
|
||||
for i in 0..(1 << params.db_dim_1) {
|
||||
sigma.data[i].conditional_assign(&scale_k, (i as u64).ct_eq(&(idx_dim0 as u64)))
|
||||
}
|
||||
|
||||
for i in 0..params.poly_len {
|
||||
sigma.data[i] = multiply_uint_mod(sigma.data[i], inv_2_g_first, params.modulus);
|
||||
}
|
||||
} else {
|
||||
sigma.data[2 * idx_dim0] = scale_k;
|
||||
for i in 0..(1 << params.db_dim_1) {
|
||||
sigma.data[2 * i]
|
||||
.conditional_assign(&scale_k, (i as u64).ct_eq(&(idx_dim0 as u64)))
|
||||
}
|
||||
|
||||
for i in 0..further_dims as u64 {
|
||||
let bit: u64 = ((idx_further as u64) & (1 << i)) >> i;
|
||||
let mask = 1 << i;
|
||||
let bit = ((idx_further as u64) & mask).ct_eq(&mask);
|
||||
for j in 0..params.t_gsw {
|
||||
let val = (1u64 << (bits_per * j)) * bit;
|
||||
let val = u64::conditional_select(&0, &(1u64 << (bits_per * j)), bit);
|
||||
let idx = (i as usize) * params.t_gsw + (j as usize);
|
||||
sigma.data[2 * idx + 1] = val;
|
||||
}
|
||||
@@ -633,6 +672,15 @@ impl<'a> Client<'a> {
|
||||
query
|
||||
}
|
||||
|
||||
pub fn generate_full_query(&self, id: &str, idx_target: usize) -> Vec<u8> {
|
||||
assert_eq!(id.len(), UUID_V4_LEN);
|
||||
let query = self.generate_query(idx_target);
|
||||
let mut query_buf = query.serialize();
|
||||
let mut full_query_buf = id.as_bytes().to_vec();
|
||||
full_query_buf.append(&mut query_buf);
|
||||
full_query_buf
|
||||
}
|
||||
|
||||
pub fn decode_response(&self, data: &[u8]) -> Vec<u8> {
|
||||
/*
|
||||
0. NTT over q2 the secret key
|
||||
@@ -762,23 +810,26 @@ mod test {
|
||||
get_vec(&pub_params.v_packing),
|
||||
get_vec(&deserialized1.v_packing)
|
||||
);
|
||||
|
||||
println!(
|
||||
"packing mats (bytes) {}",
|
||||
get_vec(&pub_params.v_packing).len() * 8
|
||||
);
|
||||
println!("total size (bytes) {}", serialized1.len());
|
||||
if pub_params.v_conversion.is_some() {
|
||||
assert_eq!(
|
||||
get_vec(&pub_params.v_conversion.unwrap()),
|
||||
get_vec(&deserialized1.v_conversion.unwrap())
|
||||
);
|
||||
let l1 = get_vec(&pub_params.v_conversion.unwrap());
|
||||
assert_eq!(l1, get_vec(&deserialized1.v_conversion.unwrap()));
|
||||
println!("conv mats (bytes) {}", l1.len() * 8);
|
||||
}
|
||||
if pub_params.v_expansion_left.is_some() {
|
||||
assert_eq!(
|
||||
get_vec(&pub_params.v_expansion_left.unwrap()),
|
||||
get_vec(&deserialized1.v_expansion_left.unwrap())
|
||||
);
|
||||
let l1 = get_vec(&pub_params.v_expansion_left.unwrap());
|
||||
assert_eq!(l1, get_vec(&deserialized1.v_expansion_left.unwrap()));
|
||||
println!("exp left (bytes) {}", l1.len() * 8);
|
||||
}
|
||||
if pub_params.v_expansion_right.is_some() {
|
||||
assert_eq!(
|
||||
get_vec(&pub_params.v_expansion_right.unwrap()),
|
||||
get_vec(&deserialized1.v_expansion_right.unwrap())
|
||||
);
|
||||
let l1 = get_vec(&pub_params.v_expansion_right.unwrap());
|
||||
assert_eq!(l1, get_vec(&deserialized1.v_expansion_right.unwrap()));
|
||||
println!("exp right (bytes) {}", l1.len() * 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,6 +859,25 @@ mod test {
|
||||
public_parameters_serialization_is_correct_for_params(params)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_public_parameters_2_serialization_is_correct() {
|
||||
let cfg = r#"
|
||||
{ "n": 4,
|
||||
"nu_1": 9,
|
||||
"nu_2": 5,
|
||||
"p": 256,
|
||||
"q2_bits": 20,
|
||||
"t_gsw": 8,
|
||||
"t_conv": 4,
|
||||
"t_exp_left": 8,
|
||||
"t_exp_right": 56,
|
||||
"instances": 2,
|
||||
"db_item_size": 65536 }
|
||||
"#;
|
||||
let params = params_from_json(&cfg);
|
||||
public_parameters_serialization_is_correct_for_params(params)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_expansion_public_parameters_serialization_is_correct() {
|
||||
public_parameters_serialization_is_correct_for_params(get_no_expansion_testing_params())
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user