Compare commits
225 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc5e12e2b5 | |||
| b31587e051 | |||
| 9b1574ff08 | |||
| d3d5e327c7 | |||
| 433604dec7 | |||
| c429e67d68 | |||
| 96f2718b94 | |||
| 632612eca0 | |||
| c250492f50 | |||
| 52fdc3e0cf | |||
| 5a91240992 | |||
| c9638097a0 | |||
| 383b197e5b | |||
| 787a55a4ba | |||
| bbdd53d2aa | |||
| 67f6394a26 | |||
| dbfeaff661 | |||
| 8318002b0a | |||
| 9fb980dc5e | |||
| ce77b17534 | |||
| e6372d3b02 | |||
| 836ef9d4c8 | |||
| dd66697884 | |||
| 7c4bb68399 | |||
| 58f3a96cde | |||
| 76a61cb3ae | |||
| 94be4c71a4 | |||
| 046dd4cbba | |||
| 485257d29b | |||
| 37de4bf2f7 | |||
| d3372bfc85 | |||
| 25e1bfa345 | |||
| dc0b9c271c | |||
| 9ef29037bc | |||
| 1e13d41245 | |||
| 46a4991c12 | |||
| 9d2d670990 | |||
| baba5ed212 | |||
| ceb5f090cf | |||
| b6ffe8664c | |||
| be4bc2bdcc | |||
| bc049cb954 | |||
| 9aa5b98465 | |||
| d6c9d1d08d | |||
| 49fc51853a | |||
| c177f14073 | |||
| 026932dc16 | |||
| 515d4b73f7 | |||
| fda3636783 | |||
| c056269f0e | |||
| ca49fe2293 | |||
| 1b37ff2242 | |||
| 3712b38230 | |||
| 467bda8ddd | |||
| b083335f56 | |||
| 5cc08211b7 | |||
| a63a94623f | |||
| 5deafaa27b | |||
| 021b542a4a | |||
| 64ee03112e | |||
| bed709b155 | |||
| 1f6d4153a7 | |||
| de45ab8995 | |||
| 15b552fa62 | |||
| 0d343eb82d | |||
| b5eddb6919 | |||
| 7ddde50ffa | |||
| d81967189a | |||
| 11b22ce2c1 | |||
| 58fd40fb8e | |||
| 5bb516471e | |||
| 2b9ea90e16 | |||
| 2779b5d28a | |||
| 9ea2ce5c70 | |||
| 11e1e728e1 | |||
| 4116aa18a8 | |||
| bdebe00c25 | |||
| e106390e1d | |||
| 7a53821af9 | |||
| efe6df12c9 | |||
| 23fb34f564 | |||
| 09155fbf12 | |||
| 53292ceca9 | |||
| 45b41d9e20 | |||
| 95b6ac50be | |||
| 5f2247ab83 | |||
| 842bcfa782 | |||
| c85b0ad07d | |||
| adf4537183 | |||
| 1cf101d50f | |||
| e91e6943c6 | |||
| 700f6a4e98 | |||
| b759e5e7f2 | |||
| deefa09066 | |||
| 3f6cb919ac | |||
| d08bf61905 | |||
| da18a60a91 | |||
| cec7496794 | |||
| dd82b24d61 | |||
| df827b6b09 | |||
| cb25cc2eb9 | |||
| abf7e1f6ad | |||
| 0f5137ea24 | |||
| c2938e0672 | |||
| 34903bfae6 | |||
| 9e8f550e6d | |||
| 278516ad11 | |||
| 8ad3565f2c | |||
| ce241339bf | |||
| 8d7428923a | |||
| 4a1a4b6415 | |||
| 47bdf38776 | |||
| cdd883c174 | |||
| 2d82a51905 | |||
| 38c2ce9837 | |||
| a867921fdd | |||
| 423cdb1e1b | |||
| 7aeac58fd9 | |||
| 30fafa509c | |||
| c950556506 | |||
| 9a49213973 | |||
| 65a45bc0a8 | |||
| 5932974108 | |||
| ce34e45afc | |||
| afcefa9432 | |||
| d346fdcff9 | |||
| a05a7e261a | |||
| ab9c892f8a | |||
| ca7bfef865 | |||
| f8c9397afd | |||
| 0c215efdb2 | |||
| e1efcda3dd | |||
| 59056a22c5 | |||
| 13841e813b | |||
| da775af00a | |||
| bd99474f26 | |||
| 9c70244157 | |||
| 736edcbe00 | |||
| 40e215473e | |||
| 6746514317 | |||
| 50a332eb2b | |||
| 08f1b176db | |||
| 00e2319be3 | |||
| ecd566432b | |||
| b6dc81304c | |||
| 6337cd9966 | |||
| b41b8f94a4 | |||
| 52ced9d3ad | |||
| 5eb2249ad5 | |||
| 008fc045b7 | |||
| 38cc3b9e38 | |||
| 989285e009 | |||
| c6e438600f | |||
| 1dab01ca33 | |||
| 53e0432934 | |||
| a1f2d33397 | |||
| 49aa79b5df | |||
| 6f3b5fad15 | |||
| 0efa909374 | |||
| 43b102782e | |||
| d6e6bab6a6 | |||
| 1a3cdc3128 | |||
| 39f75907e5 | |||
| 029c2698d7 | |||
| 60548770ba | |||
| 39cfc2057b | |||
| 4fbae2d5bd | |||
| 9feee4c174 | |||
| bca6b45273 | |||
| d9733a97ae | |||
| e66dc01966 | |||
| 2b4aca0194 | |||
| 06aa09afcd | |||
| ddc09bbb2e | |||
| 3003be5e68 | |||
| cb2ce87ab7 | |||
| 2ad65fc455 | |||
| cb292b38c0 | |||
| b710f796f9 | |||
| fb5a9a7a3e | |||
| 071720c1cc | |||
| 6679944689 | |||
| bec7814756 | |||
| 592b3b3bb1 | |||
| c2ff786f41 | |||
| 07b6114adf | |||
| 07155b4fe5 | |||
| 8b635a44b8 | |||
| 568862a571 | |||
| 7868725fed | |||
| d30667d8e4 | |||
| 3483a9e7f9 | |||
| 893b28c51c | |||
| 47ffdfe496 | |||
| 51bced8766 | |||
| d4286aeb76 | |||
| ce93e5f677 | |||
| 4b691d19a0 | |||
| 0e86761a13 | |||
| 55463da0da | |||
| 518daeaf20 | |||
| f6ec12db94 | |||
| 1e921186ad | |||
| d653ecde1e | |||
| 5b9b4743dc | |||
| 5f9b384efa | |||
| d69526fad9 | |||
| e10a4e8fca | |||
| 9016878868 | |||
| 1ca6e4a002 | |||
| f9c73183db | |||
| d296100ffa | |||
| 06669fe114 | |||
| 8022fc7e31 | |||
| 2b915fd0aa | |||
| 4e45c54d17 | |||
| 0113141efd | |||
| 614d096929 | |||
| fe7df3a909 | |||
| b14f6ab159 | |||
| e6040b0ecc | |||
| 60e6d23fd8 | |||
| d30038c570 | |||
| e3813b4868 | |||
| 61a0e5bbfb |
@@ -75,7 +75,6 @@ jobs:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -89,6 +88,12 @@ jobs:
|
||||
command: test
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Publish Nym binaries
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Check the release tag starts with `nym-binaries-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Release tag did not start with nym-binaries-...')
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
@@ -64,12 +64,16 @@ jobs:
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_IDENTITY_ID }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
run: yarn && yarn build
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: nym-wallet/target/release/bundle/dmg/*.dmg
|
||||
files: |
|
||||
nym-wallet/target/release/bundle/dmg/*.dmg
|
||||
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
|
||||
|
||||
- name: Clean up keychain
|
||||
if: ${{ always() }}
|
||||
|
||||
@@ -37,10 +37,16 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install app dependencies and build it
|
||||
run: yarn && yarn build
|
||||
|
||||
- name: Install app dependencies
|
||||
run: yarn
|
||||
- name: Build app
|
||||
run: yarn build
|
||||
env:
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: nym-wallet/target/release/bundle/appimage/*.AppImage
|
||||
files: |
|
||||
nym-wallet/target/release/bundle/appimage/*.AppImage
|
||||
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz*
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
name: Publish Nym Wallet (Windows 10)
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nym-wallet
|
||||
|
||||
jobs:
|
||||
publish-tauri:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [windows10]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Clean up first
|
||||
continue-on-error: true
|
||||
working-directory: .
|
||||
run: |
|
||||
cd ..
|
||||
del /s /q /A:H nym
|
||||
rmdir /s /q nym
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Check the release tag starts with `nym-wallet-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Release tag did not start with nym-wallet-...')
|
||||
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
New-Item -ItemType directory -Path certificate
|
||||
Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE
|
||||
certutil -decode certificate/tempCert.txt certificate/certificate.pfx
|
||||
Remove-Item -path certificate -include tempCert.txt
|
||||
Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
|
||||
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install app dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Build and sign it
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ENABLE_CODE_SIGNING: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
run: yarn build
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
nym-wallet/target/release/bundle/msi/*.msi
|
||||
nym-wallet/target/release/bundle/msi/*.msi.zip*
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Nym Wallet Storybook
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'nym-wallet/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Build dependencies
|
||||
run: yarn && yarn build
|
||||
- name: Build storybook
|
||||
run: yarn storybook:build
|
||||
working-directory: ./nym-wallet
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "nym-wallet/storybook-static/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nym-wallet
|
||||
NYM_PROJECT_NAME: "nym-wallet"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-nym-wallet"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -3,7 +3,7 @@ require('dotenv').config();
|
||||
const Bot = require('keybase-bot');
|
||||
|
||||
let context = {
|
||||
kinds: ['ts-packages', 'network-explorer', 'nightly'],
|
||||
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly'],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function addToContextAndValidate(context) {
|
||||
if (!context.env.NYM_CI_WWW_LOCATION) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
|
||||
}
|
||||
if (!context.env.NYM_CI_WWW_BASE) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessageBody(context) {
|
||||
const source = fs
|
||||
.readFileSync(
|
||||
context.env.IS_SUCCESS === 'true'
|
||||
? path.resolve(__dirname, 'templates', 'success')
|
||||
: path.resolve(__dirname, 'templates', 'failure'),
|
||||
)
|
||||
.toString();
|
||||
const template = Handlebars.compile(source);
|
||||
return template(context);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addToContextAndValidate,
|
||||
getMessageBody,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> 🔴 **FAILURE** :cry:
|
||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> ✅ **SUCCESS**
|
||||
|
||||
> ➡️➡️➡️➡️➡️ **View output:**
|
||||
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
|
||||
|
||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -14,18 +14,21 @@ panic = "abort"
|
||||
resolver = "2"
|
||||
members = [
|
||||
"clients/client-core",
|
||||
"clients/credential",
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
# "clients/tauri-client/src-tauri",
|
||||
"common/client-libs/gateway-client",
|
||||
"common/client-libs/mixnet-client",
|
||||
"common/client-libs/validator-client",
|
||||
"common/credential-storage",
|
||||
"common/coconut-interface",
|
||||
"common/config",
|
||||
"common/credentials",
|
||||
"common/crypto",
|
||||
"common/crypto/dkg",
|
||||
"common/bandwidth-claim-contract",
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
"common/cosmwasm-smart-contracts/vesting-contract",
|
||||
|
||||
@@ -26,7 +26,7 @@ clippy-all-wallet:
|
||||
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test --all-features --workspace
|
||||
cargo test --all-features --workspace --release
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
|
||||
@@ -40,36 +40,40 @@ Node, node operator and delegator rewards are determined according to the princi
|
||||
|
||||
|Symbol|Definition|
|
||||
|---|---|
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=R">|global share of rewards available, starts at 2% of the reward pool.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}">|node reward for mixnode `i`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}">|ratio of total node stake (node bond + all delegations) to the token circulating supply.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}">|ratio of stake operator has pledged to their node to the token circulating supply.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}">|fraction of total effort undertaken by node `i`, set to `1/k`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=k">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\alpha">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}">|declared profit margin of operator `i`, defaults to 10% in.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}">|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 NYMT.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=R#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}R#gh-dark-mode-only">|global share of rewards available, starts at 2% of the reward pool.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}R_{i}#gh-dark-mode-only">|node reward for mixnode `i`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\sigma_{i}#gh-dark-mode-only">|ratio of total node stake (node bond + all delegations) to the token circulating supply.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
|
||||
|
||||
Node reward for node `i` is determined as:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)">
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)#gh-dark-mode-only">
|
||||
where:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}\sigma^'_{i} = min\{\sigma_{i}, 1/k\}#gh-dark-mode-only">
|
||||
|
||||
and
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda^'_{i} = min\{\lambda_{i}, 1/k\}#gh-dark-mode-only">
|
||||
|
||||
Operator of node `i` is credited with the following amount:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
Delegate with stake `s` recieves:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
where `s'` is stake `s` scaled over total token circulating supply.
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Critical bug or security issue 💥
|
||||
|
||||
If you're here because you're trying to figure out how to notify us of a security issue, go to Discord, and alert the core engineers:
|
||||
|
||||
Dave Hrycyszyn futurechimp#5430
|
||||
Drazen Urch drazen#4873
|
||||
Jedrzej Stuczynski "Jedrzej | Nym#5666"
|
||||
|
||||
|
||||
Please avoid opening public issues on GitHub that contain information about a potential security vulnerability as this makes it difficult to reduce the impact and harm of valid security issues.
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1011 B |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "0.12.0"
|
||||
version = "1.0.0-rc.2"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -32,4 +32,4 @@ validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
coconut = []
|
||||
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
|
||||
@@ -103,15 +103,8 @@ impl<T: NymConfig> Config<T> {
|
||||
self::Client::<T>::default_reply_encryption_key_store_path(&id);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if self
|
||||
.client
|
||||
.backup_bandwidth_token_keys_dir
|
||||
.as_os_str()
|
||||
.is_empty()
|
||||
{
|
||||
self.client.backup_bandwidth_token_keys_dir =
|
||||
self::Client::<T>::default_backup_bandwidth_token_keys_dir(&id);
|
||||
if self.client.database_path.as_os_str().is_empty() {
|
||||
self.client.database_path = self::Client::<T>::default_database_path(&id);
|
||||
}
|
||||
|
||||
self.client.id = id;
|
||||
@@ -213,9 +206,8 @@ impl<T: NymConfig> Config<T> {
|
||||
self.client.gateway_endpoint.gateway_listener.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn get_backup_bandwidth_token_keys_dir(&self) -> PathBuf {
|
||||
self.client.backup_bandwidth_token_keys_dir.clone()
|
||||
pub fn get_database_path(&self) -> PathBuf {
|
||||
self.client.database_path.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -337,11 +329,8 @@ pub struct Client<T> {
|
||||
/// Information regarding how the client should send data to gateway.
|
||||
gateway_endpoint: GatewayEndpoint,
|
||||
|
||||
/// Path to directory containing public/private keys used for bandwidth token purchase.
|
||||
/// Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
|
||||
/// The public key is the name of the file, while the private key is the content.
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
backup_bandwidth_token_keys_dir: PathBuf,
|
||||
/// Path to the database containing bandwidth credentials of this client.
|
||||
database_path: PathBuf,
|
||||
|
||||
/// Ethereum private key.
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -375,8 +364,7 @@ impl<T: NymConfig> Default for Client<T> {
|
||||
ack_key_file: Default::default(),
|
||||
reply_encryption_key_store_path: Default::default(),
|
||||
gateway_endpoint: Default::default(),
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
backup_bandwidth_token_keys_dir: Default::default(),
|
||||
database_path: Default::default(),
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
eth_private_key: "".to_string(),
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -415,10 +403,8 @@ impl<T: NymConfig> Client<T> {
|
||||
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("reply_key_store")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
fn default_backup_bandwidth_token_keys_dir(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("backup_bandwidth_token_keys")
|
||||
fn default_database_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("db.sqlite")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "credential"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.52"
|
||||
bip39 = "1.0.1"
|
||||
cfg-if = "0.1"
|
||||
clap = { version = "3.0.10", features = ["cargo", "derive"] }
|
||||
pickledb = "0.4.1"
|
||||
rand = "0.7.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
|
||||
|
||||
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
|
||||
[features]
|
||||
coconut = ["credentials/coconut"]
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bip39::Mnemonic;
|
||||
use coconut_bandwidth_contract_common::deposit::DepositData;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::{CONTRACT_ADDRESS, MNEMONIC, NYMD_URL};
|
||||
|
||||
use coconut_bandwidth_contract_common::msg::ExecuteMsg;
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use validator_client::nymd::{
|
||||
AccountId, CosmosCoin, Decimal, Denom, NymdClient, SigningNymdClient,
|
||||
};
|
||||
|
||||
pub(crate) struct Client {
|
||||
nymd_client: NymdClient<SigningNymdClient>,
|
||||
denom: Denom,
|
||||
contract_address: AccountId,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
let nymd_url = Url::from_str(NYMD_URL).unwrap();
|
||||
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
|
||||
let nymd_client = NymdClient::connect_with_mnemonic(
|
||||
DEFAULT_NETWORK,
|
||||
nymd_url.as_ref(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
mnemonic,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let denom = Denom::from_str(network_defaults::DENOM).unwrap();
|
||||
let contract_address = AccountId::from_str(CONTRACT_ADDRESS).unwrap();
|
||||
|
||||
Client {
|
||||
nymd_client,
|
||||
denom,
|
||||
contract_address,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn deposit(
|
||||
&self,
|
||||
amount: u64,
|
||||
info: &str,
|
||||
verification_key: String,
|
||||
encryption_key: String,
|
||||
) -> Result<String> {
|
||||
let req = ExecuteMsg::DepositFunds {
|
||||
data: DepositData::new(info.to_string(), verification_key, encryption_key),
|
||||
};
|
||||
let funds = vec![CosmosCoin {
|
||||
denom: self.denom.clone(),
|
||||
amount: Decimal::from(amount),
|
||||
}];
|
||||
Ok(self
|
||||
.nymd_client
|
||||
.execute(&self.contract_address, &req, Default::default(), "", funds)
|
||||
.await?
|
||||
.transaction_hash
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use clap::{Args, Subcommand};
|
||||
use pickledb::PickleDb;
|
||||
use rand::rngs::OsRng;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
use coconut_interface::{Attribute, Base58, BlindSignRequest, Bytable, Parameters};
|
||||
use credential_storage::storage::Storage;
|
||||
use credential_storage::PersistentStorage;
|
||||
use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
|
||||
use credentials::coconut::utils::obtain_aggregate_signature;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use network_defaults::VOUCHER_INFO;
|
||||
use validator_client::nymd::tx::Hash;
|
||||
|
||||
use crate::client::Client;
|
||||
use crate::error::{CredentialClientError, Result};
|
||||
use crate::state::{KeyPair, RequestData, State};
|
||||
use crate::SIGNER_AUTHORITIES;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Deposit funds for buying coconut credential
|
||||
Deposit(Deposit),
|
||||
/// Lists the tx hashes of previous deposits
|
||||
ListDeposits(ListDeposits),
|
||||
/// Get a credential for a given deposit
|
||||
GetCredential(GetCredential),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub(crate) trait Execute {
|
||||
async fn execute(&self, db: &mut PickleDb, shared_storage: PersistentStorage) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Deposit {
|
||||
/// The amount that needs to be deposited
|
||||
#[clap(long)]
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for Deposit {
|
||||
async fn execute(&self, db: &mut PickleDb, _shared_storage: PersistentStorage) -> Result<()> {
|
||||
let mut rng = OsRng;
|
||||
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
|
||||
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
|
||||
|
||||
let client = Client::new();
|
||||
let tx_hash = client
|
||||
.deposit(
|
||||
self.amount,
|
||||
VOUCHER_INFO,
|
||||
signing_keypair.public_key.clone(),
|
||||
encryption_keypair.public_key.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let state = State {
|
||||
amount: self.amount,
|
||||
tx_hash: tx_hash.clone(),
|
||||
signing_keypair,
|
||||
encryption_keypair,
|
||||
blind_request_data: None,
|
||||
signature: None,
|
||||
};
|
||||
db.set(&tx_hash, &state).unwrap();
|
||||
|
||||
println!("{:?}", state);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct ListDeposits {}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for ListDeposits {
|
||||
async fn execute(&self, db: &mut PickleDb, _shared_storage: PersistentStorage) -> Result<()> {
|
||||
for kv in db.iter() {
|
||||
println!("{:?}", kv.get_value::<State>());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct GetCredential {
|
||||
/// The hash of a successful deposit transaction
|
||||
#[clap(long)]
|
||||
tx_hash: String,
|
||||
/// If we want to get the signature without attaching a blind sign request; it is expected that
|
||||
/// there is already a signature stored on the signer
|
||||
#[clap(long, parse(from_flag))]
|
||||
__no_request: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for GetCredential {
|
||||
async fn execute(&self, db: &mut PickleDb, shared_storage: PersistentStorage) -> Result<()> {
|
||||
let mut state = db
|
||||
.get::<State>(&self.tx_hash)
|
||||
.ok_or(CredentialClientError::NoDeposit)?;
|
||||
let urls = SIGNER_AUTHORITIES.map(|addr| Url::from_str(addr).unwrap());
|
||||
|
||||
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
let bandwidth_credential_attributes = if self.__no_request {
|
||||
if let Some(blind_request_data) = state.blind_request_data {
|
||||
let serial_number =
|
||||
Attribute::try_from_byte_slice(&blind_request_data.serial_number)
|
||||
.map_err(|_| CredentialClientError::CorruptedBlindSignRequest)?;
|
||||
let binding_number =
|
||||
Attribute::try_from_byte_slice(&blind_request_data.binding_number)
|
||||
.map_err(|_| CredentialClientError::CorruptedBlindSignRequest)?;
|
||||
let pedersen_commitments_openings = vec![
|
||||
Attribute::try_from_byte_slice(&blind_request_data.first_attribute)
|
||||
.map_err(|_| CredentialClientError::CorruptedBlindSignRequest)?,
|
||||
Attribute::try_from_byte_slice(&blind_request_data.second_attribute)
|
||||
.map_err(|_| CredentialClientError::CorruptedBlindSignRequest)?,
|
||||
];
|
||||
let blind_sign_request =
|
||||
BlindSignRequest::from_bytes(blind_request_data.blind_sign_req.as_slice())
|
||||
.map_err(|_| CredentialClientError::CorruptedBlindSignRequest)?;
|
||||
BandwidthVoucher::new_with_blind_sign_req(
|
||||
[serial_number, binding_number],
|
||||
[&state.amount.to_string(), VOUCHER_INFO],
|
||||
Hash::from_str(&self.tx_hash)
|
||||
.map_err(|_| CredentialClientError::InvalidTxHash)?,
|
||||
identity::PrivateKey::from_base58_string(&state.signing_keypair.private_key)?,
|
||||
encryption::PrivateKey::from_base58_string(
|
||||
&state.encryption_keypair.private_key,
|
||||
)?,
|
||||
pedersen_commitments_openings,
|
||||
blind_sign_request,
|
||||
)
|
||||
} else {
|
||||
return Err(CredentialClientError::NoLocalBlindSignRequest);
|
||||
}
|
||||
} else {
|
||||
BandwidthVoucher::new(
|
||||
¶ms,
|
||||
state.amount.to_string(),
|
||||
VOUCHER_INFO.to_string(),
|
||||
Hash::from_str(&self.tx_hash).map_err(|_| CredentialClientError::InvalidTxHash)?,
|
||||
identity::PrivateKey::from_base58_string(&state.signing_keypair.private_key)?,
|
||||
encryption::PrivateKey::from_base58_string(&state.encryption_keypair.private_key)?,
|
||||
)
|
||||
};
|
||||
|
||||
// Back up the blind sign req data, in case of sporadic failures
|
||||
state.blind_request_data = Some(RequestData::new(
|
||||
bandwidth_credential_attributes.get_private_attributes(),
|
||||
bandwidth_credential_attributes.pedersen_commitments_openings(),
|
||||
bandwidth_credential_attributes.blind_sign_request(),
|
||||
)?);
|
||||
db.set(&self.tx_hash, &state).unwrap();
|
||||
|
||||
let signature =
|
||||
obtain_aggregate_signature(¶ms, &bandwidth_credential_attributes, &urls).await?;
|
||||
shared_storage
|
||||
.insert_coconut_credential(
|
||||
state.amount.to_string(),
|
||||
VOUCHER_INFO.to_string(),
|
||||
bandwidth_credential_attributes.get_private_attributes()[0].to_bs58(),
|
||||
bandwidth_credential_attributes.get_private_attributes()[1].to_bs58(),
|
||||
signature.to_bs58(),
|
||||
)
|
||||
.await?;
|
||||
state.signature = Some(signature.to_bs58());
|
||||
db.set(&self.tx_hash, &state).unwrap();
|
||||
|
||||
println!("Signature: {:?}", state.signature);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use credential_storage::error::StorageError;
|
||||
use credentials::error::Error as CredentialError;
|
||||
use crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CredentialClientError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CredentialClientError {
|
||||
#[error("Nymd error: {0}")]
|
||||
Nymd(#[from] NymdError),
|
||||
|
||||
#[error("Credential error: {0}")]
|
||||
Credential(#[from] CredentialError),
|
||||
|
||||
#[error("No previous deposit with that tx hash")]
|
||||
NoDeposit,
|
||||
|
||||
#[error("Wrong number of attributes")]
|
||||
WrongAttributeNumber,
|
||||
|
||||
#[error("Could not find any backed up blind sign request data")]
|
||||
NoLocalBlindSignRequest,
|
||||
|
||||
#[error("The local blind sign request data is corrupted")]
|
||||
CorruptedBlindSignRequest,
|
||||
|
||||
#[error("The tx hash provided is not valid")]
|
||||
InvalidTxHash,
|
||||
|
||||
#[error("Could not parse Ed25519 data")]
|
||||
Ed25519ParseError(#[from] Ed25519RecoveryError),
|
||||
|
||||
#[error("Could not parse X25519 data")]
|
||||
X25519ParseError(#[from] KeyRecoveryError),
|
||||
|
||||
#[error("Could not use shared storage")]
|
||||
SharedStorageError(#[from] StorageError),
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "coconut")] {
|
||||
|
||||
mod client;
|
||||
mod commands;
|
||||
mod error;
|
||||
mod state;
|
||||
|
||||
use commands::{Commands, Execute};
|
||||
use error::Result;
|
||||
|
||||
use clap::Parser;
|
||||
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
|
||||
|
||||
pub const MNEMONIC: &str = "jazz fatigue diagram account outer wrist slide cherry mother grid network pause wolf pig round answer mail junior better hair dismiss toward access end";
|
||||
pub const NYMD_URL: &str = "http://127.0.0.1:26657";
|
||||
pub const CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
|
||||
pub const SIGNER_AUTHORITIES: [&str; 1] = [
|
||||
"http://127.0.0.1:8080",
|
||||
];
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about)]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
let shared_storage = credential_storage::initialise_storage(std::path::PathBuf::from("/tmp/credential.db")).await;
|
||||
let mut db = match PickleDb::load(
|
||||
"credential.db",
|
||||
PickleDbDumpPolicy::AutoDump,
|
||||
SerializationMethod::Json,
|
||||
) {
|
||||
Ok(db) => db,
|
||||
Err(_) => PickleDb::new(
|
||||
"credential.db",
|
||||
PickleDbDumpPolicy::AutoDump,
|
||||
SerializationMethod::Json,
|
||||
),
|
||||
};
|
||||
|
||||
match &args.command {
|
||||
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
|
||||
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
|
||||
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
fn main() {
|
||||
println!("Crate only designed for coconut feature");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_interface::{Attribute, BlindSignRequest, Bytable, PrivateAttribute};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
|
||||
use crate::error::{CredentialClientError, Result};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct KeyPair {
|
||||
pub public_key: String,
|
||||
pub private_key: String,
|
||||
}
|
||||
|
||||
impl From<identity::KeyPair> for KeyPair {
|
||||
fn from(kp: identity::KeyPair) -> Self {
|
||||
Self {
|
||||
public_key: kp.public_key().to_base58_string(),
|
||||
private_key: kp.private_key().to_base58_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<encryption::KeyPair> for KeyPair {
|
||||
fn from(kp: encryption::KeyPair) -> Self {
|
||||
Self {
|
||||
public_key: kp.public_key().to_base58_string(),
|
||||
private_key: kp.private_key().to_base58_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct State {
|
||||
pub amount: u64,
|
||||
pub tx_hash: String,
|
||||
pub signing_keypair: KeyPair,
|
||||
pub encryption_keypair: KeyPair,
|
||||
pub blind_request_data: Option<RequestData>,
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct RequestData {
|
||||
pub serial_number: Vec<u8>,
|
||||
pub binding_number: Vec<u8>,
|
||||
pub first_attribute: Vec<u8>,
|
||||
pub second_attribute: Vec<u8>,
|
||||
pub blind_sign_req: Vec<u8>,
|
||||
}
|
||||
|
||||
impl RequestData {
|
||||
pub fn new(
|
||||
private_attributes: Vec<PrivateAttribute>,
|
||||
attributes: &[Attribute],
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
) -> Result<Self> {
|
||||
if private_attributes.len() != 2 || attributes.len() != 2 {
|
||||
Err(CredentialClientError::WrongAttributeNumber)
|
||||
} else {
|
||||
Ok(RequestData {
|
||||
serial_number: private_attributes[0].to_byte_vec(),
|
||||
binding_number: private_attributes[1].to_byte_vec(),
|
||||
first_attribute: attributes[0].to_byte_vec(),
|
||||
second_attribute: attributes[1].to_byte_vec(),
|
||||
blind_sign_req: blind_sign_request.to_bytes(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "0.12.1"
|
||||
version = "1.0.0-rc.2"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
@@ -34,6 +34,7 @@ tokio-tungstenite = "0.14" # websocket
|
||||
client-core = { path = "../client-core" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
config = { path = "../../common/config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
@@ -42,12 +43,12 @@ nymsphinx = { path = "../../common/nymsphinx" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
topology = { path = "../../common/topology" }
|
||||
websocket-requests = { path = "websocket-requests" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
|
||||
eth = []
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -18,14 +18,12 @@
|
||||
"author": "Dave Hrycyszyn",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.2.6",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"html-webpack-plugin": "^4.2.0"
|
||||
"html-webpack-plugin": "^5.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
})
|
||||
],
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
static: path.join(__dirname, 'dist'),
|
||||
compress: true,
|
||||
port: 8888
|
||||
}
|
||||
|
||||
@@ -46,10 +46,8 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
# sent but not received back.
|
||||
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
|
||||
|
||||
# Path to directory containing public/private keys used for bandwidth token purchase.
|
||||
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
|
||||
# The public key is the name of the file, while the private key is the content.
|
||||
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
|
||||
# Path to the database containing bandwidth credentials
|
||||
database_path = '{{ client.database_path }}'
|
||||
|
||||
# Ethereum private key.
|
||||
eth_private_key = '{{ client.eth_private_key }}'
|
||||
|
||||
@@ -174,14 +174,16 @@ impl NymClient {
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
credential_storage::initialise_storage(self.config.get_base().get_database_path())
|
||||
.await,
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
credential_storage::initialise_storage(self.config.get_base().get_database_path())
|
||||
.await,
|
||||
self.config.get_base().get_eth_endpoint(),
|
||||
self.config.get_base().get_eth_private_key(),
|
||||
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
|
||||
)
|
||||
.expect("Could not create bandwidth controller");
|
||||
|
||||
|
||||
@@ -4,20 +4,10 @@
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
use config::NymConfig;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
#[cfg(feature = "coconut")]
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -98,36 +88,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
app
|
||||
}
|
||||
|
||||
// this behaviour should definitely be changed, we shouldn't
|
||||
// need to get bandwidth credential for registration
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
|
||||
serial_number: params.random_scalar(),
|
||||
binding_number: params.random_scalar(),
|
||||
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
|
||||
voucher_info: hash_to_scalar(String::from("BandwidthVoucher").as_bytes()),
|
||||
};
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(
|
||||
raw_identity,
|
||||
&bandwidth_credential,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "0.12.1"
|
||||
version = "1.0.0-rc.2"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
@@ -27,6 +27,7 @@ url = "2.2"
|
||||
client-core = { path = "../client-core" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
config = { path = "../../common/config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
@@ -37,12 +38,12 @@ socks5-requests = { path = "../../common/socks5/requests" }
|
||||
topology = { path = "../../common/topology" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
|
||||
eth = []
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -46,10 +46,8 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
# sent but not received back.
|
||||
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
|
||||
|
||||
# Path to directory containing public/private keys used for bandwidth token purchase.
|
||||
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
|
||||
# The public key is the name of the file, while the private key is the content.
|
||||
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
|
||||
# Path to the database containing bandwidth credentials
|
||||
database_path = '{{ client.database_path }}'
|
||||
|
||||
# Ethereum private key.
|
||||
eth_private_key = '{{ client.eth_private_key }}'
|
||||
|
||||
@@ -162,14 +162,16 @@ impl NymClient {
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
credential_storage::initialise_storage(self.config.get_base().get_database_path())
|
||||
.await,
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
);
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
credential_storage::initialise_storage(self.config.get_base().get_database_path())
|
||||
.await,
|
||||
self.config.get_base().get_eth_endpoint(),
|
||||
self.config.get_base().get_eth_private_key(),
|
||||
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
|
||||
)
|
||||
.expect("Could not create bandwidth controller");
|
||||
|
||||
|
||||
@@ -4,20 +4,10 @@
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
use config::NymConfig;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
#[cfg(feature = "coconut")]
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
|
||||
@@ -98,36 +88,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
app
|
||||
}
|
||||
|
||||
// this behaviour should definitely be changed, we shouldn't
|
||||
// need to get bandwidth credential for registration
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
|
||||
serial_number: params.random_scalar(),
|
||||
binding_number: params.random_scalar(),
|
||||
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
|
||||
voucher_info: hash_to_scalar("BandwidthVoucher"),
|
||||
};
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(
|
||||
raw_identity,
|
||||
&bandwidth_credential,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.DS_Store
|
||||
/node_modules/
|
||||
/src/node_modules/@sapper/
|
||||
yarn-error.log
|
||||
/__sapper__/
|
||||
@@ -1,33 +0,0 @@
|
||||
## Prerequisites
|
||||
|
||||
On Ubuntu-ish systems (not tested on Debian but may work):
|
||||
|
||||
```
|
||||
sudo apt update && sudo apt install libwebkit2gtk-4.0-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
libssl-dev \
|
||||
libgtk-3-dev \
|
||||
libappindicator3-dev \
|
||||
patchelf \
|
||||
librsvg2-dev```
|
||||
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Start Sapper([docs](https://sapper.svelte.dev/docs/)) -> `yarn run dev`
|
||||
2. Start Tauri([docs](https://tauri.studio/en/)) in another terminal -> `yarn tauri dev`
|
||||
3. Start validator-api locally, or override `validator-urls` in `index.svelte`
|
||||
|
||||
## Getting around
|
||||
|
||||
+ Frontend -> `tauri-client/`
|
||||
+ logic -> `tauri-client/src/routes/index.svelte`
|
||||
+ assets -> `tauri-client/static`
|
||||
+ Backend -> `tauri-client/src/src-tauri`
|
||||
|
||||
## Build standalone app
|
||||
|
||||
+ yarn tauri build [--debug]
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"name": "TODO",
|
||||
"description": "TODO",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "sapper dev",
|
||||
"build": "sapper build --legacy",
|
||||
"export": "sapper export --legacy",
|
||||
"start": "node __sapper__/build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.0.0-beta.4",
|
||||
"compression": "^1.7.1",
|
||||
"polka": "next",
|
||||
"qrious": "^4.0.2",
|
||||
"sirv": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@rollup/plugin-babel": "^5.0.0",
|
||||
"@rollup/plugin-commonjs": "^14.0.0",
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"@rollup/plugin-replace": "^2.4.0",
|
||||
"@rollup/plugin-url": "^5.0.0",
|
||||
"@tauri-apps/cli": "^1.0.0-rc.5",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"sapper": "^0.28.0",
|
||||
"sass": "^1.35.2",
|
||||
"svelte": "^3.17.3",
|
||||
"svelte-preprocess": "^4.7.4"
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import path from "path";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import url from "@rollup/plugin-url";
|
||||
import svelte from "rollup-plugin-svelte";
|
||||
import babel from "@rollup/plugin-babel";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import config from "sapper/config/rollup.js";
|
||||
import pkg from "./package.json";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
|
||||
const mode = process.env.NODE_ENV;
|
||||
const dev = mode === "development";
|
||||
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
|
||||
|
||||
const onwarn = (warning, onwarn) =>
|
||||
(warning.code === "MISSING_EXPORT" && /'preload'/.test(warning.message)) ||
|
||||
(warning.code === "CIRCULAR_DEPENDENCY" &&
|
||||
/[/\\]@sapper[/\\]/.test(warning.message)) ||
|
||||
onwarn(warning);
|
||||
|
||||
export default {
|
||||
client: {
|
||||
input: config.client.input(),
|
||||
output: config.client.output(),
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
values: {
|
||||
"process.browser": true,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
},
|
||||
}),
|
||||
svelte({
|
||||
compilerOptions: {
|
||||
dev,
|
||||
hydratable: true,
|
||||
},
|
||||
preprocess: sveltePreprocess(),
|
||||
}),
|
||||
url({
|
||||
sourceDir: path.resolve(__dirname, "src/node_modules/images"),
|
||||
publicPath: "/client/",
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
|
||||
legacy &&
|
||||
babel({
|
||||
extensions: [".js", ".mjs", ".html", ".svelte"],
|
||||
babelHelpers: "runtime",
|
||||
exclude: ["node_modules/@babel/**"],
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
targets: "> 0.25%, not dead",
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
[
|
||||
"@babel/plugin-transform-runtime",
|
||||
{
|
||||
useESModules: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
|
||||
!dev &&
|
||||
terser({
|
||||
module: true,
|
||||
}),
|
||||
],
|
||||
|
||||
preserveEntrySignatures: false,
|
||||
onwarn,
|
||||
},
|
||||
|
||||
server: {
|
||||
input: config.server.input(),
|
||||
output: config.server.output(),
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
values: {
|
||||
"process.browser": false,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
},
|
||||
}),
|
||||
svelte({
|
||||
compilerOptions: {
|
||||
dev,
|
||||
generate: "ssr",
|
||||
hydratable: true,
|
||||
},
|
||||
emitCss: false,
|
||||
}),
|
||||
url({
|
||||
sourceDir: path.resolve(__dirname, "src/node_modules/images"),
|
||||
publicPath: "/client/",
|
||||
emitFiles: false, // already emitted by client build
|
||||
}),
|
||||
resolve({
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
external: Object.keys(pkg.dependencies).concat(
|
||||
require("module").builtinModules
|
||||
),
|
||||
preserveEntrySignatures: "strict",
|
||||
onwarn,
|
||||
},
|
||||
|
||||
serviceworker: {
|
||||
input: config.serviceworker.input(),
|
||||
output: config.serviceworker.output(),
|
||||
plugins: [
|
||||
resolve(),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
values: {
|
||||
"process.browser": true,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
},
|
||||
}),
|
||||
commonjs(),
|
||||
!dev && terser(),
|
||||
],
|
||||
preserveEntrySignatures: false,
|
||||
onwarn,
|
||||
},
|
||||
};
|
||||
@@ -1,304 +0,0 @@
|
||||
/**
|
||||
* Run this script to convert the project to TypeScript. This is only guaranteed to work
|
||||
* on the unmodified default template; if you have done code changes you are likely need
|
||||
* to touch up the generated project manually.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { argv } = require('process');
|
||||
|
||||
const projectRoot = argv[2] || path.join(__dirname, '..');
|
||||
|
||||
const isRollup = fs.existsSync(path.join(projectRoot, "rollup.config.js"));
|
||||
|
||||
function warn(message) {
|
||||
console.warn('Warning: ' + message);
|
||||
}
|
||||
|
||||
function replaceInFile(fileName, replacements) {
|
||||
if (fs.existsSync(fileName)) {
|
||||
let contents = fs.readFileSync(fileName, 'utf8');
|
||||
let hadUpdates = false;
|
||||
|
||||
replacements.forEach(([from, to]) => {
|
||||
const newContents = contents.replace(from, to);
|
||||
|
||||
const isAlreadyApplied = typeof to !== 'string' || contents.includes(to);
|
||||
|
||||
if (newContents !== contents) {
|
||||
contents = newContents;
|
||||
hadUpdates = true;
|
||||
} else if (!isAlreadyApplied) {
|
||||
warn(`Wanted to update "${from}" in ${fileName}, but did not find it.`);
|
||||
}
|
||||
});
|
||||
|
||||
if (hadUpdates) {
|
||||
fs.writeFileSync(fileName, contents);
|
||||
} else {
|
||||
console.log(`${fileName} had already been updated.`);
|
||||
}
|
||||
} else {
|
||||
warn(`Wanted to update ${fileName} but the file did not exist.`);
|
||||
}
|
||||
}
|
||||
|
||||
function createFile(fileName, contents) {
|
||||
if (fs.existsSync(fileName)) {
|
||||
warn(`Wanted to create ${fileName}, but it already existed. Leaving existing file.`);
|
||||
} else {
|
||||
fs.writeFileSync(fileName, contents);
|
||||
}
|
||||
}
|
||||
|
||||
function addDepsToPackageJson() {
|
||||
const pkgJSONPath = path.join(projectRoot, 'package.json');
|
||||
const packageJSON = JSON.parse(fs.readFileSync(pkgJSONPath, 'utf8'));
|
||||
packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
|
||||
...(isRollup ? { '@rollup/plugin-typescript': '^6.0.0' } : { 'ts-loader': '^8.0.4' }),
|
||||
'@tsconfig/svelte': '^1.0.10',
|
||||
'@types/compression': '^1.7.0',
|
||||
'@types/node': '^14.11.1',
|
||||
'@types/polka': '^0.5.1',
|
||||
'svelte-check': '^1.0.46',
|
||||
'svelte-preprocess': '^4.3.0',
|
||||
tslib: '^2.0.1',
|
||||
typescript: '^4.0.3'
|
||||
});
|
||||
|
||||
// Add script for checking
|
||||
packageJSON.scripts = Object.assign(packageJSON.scripts, {
|
||||
validate: 'svelte-check --ignore src/node_modules/@sapper'
|
||||
});
|
||||
|
||||
// Write the package JSON
|
||||
fs.writeFileSync(pkgJSONPath, JSON.stringify(packageJSON, null, ' '));
|
||||
}
|
||||
|
||||
function changeJsExtensionToTs(dir) {
|
||||
const elements = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (elements[i].isDirectory()) {
|
||||
changeJsExtensionToTs(path.join(dir, elements[i].name));
|
||||
} else if (elements[i].name.match(/^[^_]((?!json).)*js$/)) {
|
||||
fs.renameSync(path.join(dir, elements[i].name), path.join(dir, elements[i].name).replace('.js', '.ts'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSingleSvelteFile({ view, vars, contextModule }) {
|
||||
replaceInFile(path.join(projectRoot, 'src', `${view}.svelte`), [
|
||||
[/(?:<script)(( .*?)*?)>/gm, (m, attrs) => `<script${attrs}${!attrs.includes('lang="ts"') ? ' lang="ts"' : ''}>`],
|
||||
...(vars ? vars.map(({ name, type }) => [`export let ${name};`, `export let ${name}: ${type};`]) : []),
|
||||
...(contextModule ? contextModule.map(({ js, ts }) => [js, ts]) : [])
|
||||
]);
|
||||
}
|
||||
|
||||
// Switch the *.svelte file to use TS
|
||||
function updateSvelteFiles() {
|
||||
[
|
||||
{
|
||||
view: 'components/Nav',
|
||||
vars: [{ name: 'segment', type: 'string' }]
|
||||
},
|
||||
{
|
||||
view: 'routes/_layout',
|
||||
vars: [{ name: 'segment', type: 'string' }]
|
||||
},
|
||||
{
|
||||
view: 'routes/_error',
|
||||
vars: [
|
||||
{ name: 'status', type: 'number' },
|
||||
{ name: 'error', type: 'Error' }
|
||||
]
|
||||
},
|
||||
{
|
||||
view: 'routes/blog/index',
|
||||
vars: [{ name: 'posts', type: '{ slug: string; title: string, html: any }[]' }],
|
||||
contextModule: [
|
||||
{
|
||||
js: '.then(r => r.json())',
|
||||
ts: '.then((r: { json: () => any; }) => r.json())'
|
||||
},
|
||||
{
|
||||
js: '.then(posts => {',
|
||||
ts: '.then((posts: { slug: string; title: string, html: any }[]) => {'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
view: 'routes/blog/[slug]',
|
||||
vars: [{ name: 'post', type: '{ slug: string; title: string, html: any }' }]
|
||||
}
|
||||
].forEach(updateSingleSvelteFile);
|
||||
}
|
||||
|
||||
function updateRollupConfig() {
|
||||
// Edit rollup config
|
||||
replaceInFile(path.join(projectRoot, 'rollup.config.js'), [
|
||||
// Edit imports
|
||||
[
|
||||
/'rollup-plugin-terser';\n(?!import sveltePreprocess)/,
|
||||
`'rollup-plugin-terser';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
`
|
||||
],
|
||||
// Edit inputs
|
||||
[
|
||||
/(?<!THIS_IS_UNDEFINED[^\n]*\n\s*)onwarn\(warning\);/,
|
||||
`(warning.code === 'THIS_IS_UNDEFINED') ||\n\tonwarn(warning);`
|
||||
],
|
||||
[/input: config.client.input\(\)(?!\.replace)/, `input: config.client.input().replace(/\\.js$/, '.ts')`],
|
||||
[
|
||||
/input: config.server.input\(\)(?!\.replace)/,
|
||||
`input: { server: config.server.input().server.replace(/\\.js$/, ".ts") }`
|
||||
],
|
||||
[
|
||||
/input: config.serviceworker.input\(\)(?!\.replace)/,
|
||||
`input: config.serviceworker.input().replace(/\\.js$/, '.ts')`
|
||||
],
|
||||
// Add preprocess
|
||||
[/compilerOptions/g, 'preprocess: sveltePreprocess({ sourceMap: dev }),\n\t\t\t\tcompilerOptions'],
|
||||
// Add TypeScript
|
||||
[/commonjs\(\)(?!,\n\s*typescript)/g, 'commonjs(),\n\t\t\ttypescript({ sourceMap: dev })']
|
||||
]);
|
||||
}
|
||||
|
||||
function updateWebpackConfig() {
|
||||
// Edit webpack config
|
||||
replaceInFile(path.join(projectRoot, 'webpack.config.js'), [
|
||||
// Edit imports
|
||||
[
|
||||
/require\('webpack-modules'\);\n(?!const sveltePreprocess)/,
|
||||
`require('webpack-modules');\nconst sveltePreprocess = require('svelte-preprocess');\n`
|
||||
],
|
||||
// Edit extensions
|
||||
[
|
||||
/\['\.mjs', '\.js', '\.json', '\.svelte', '\.html'\]/,
|
||||
`['.mjs', '.js', '.ts', '.json', '.svelte', '.html']`
|
||||
],
|
||||
// Edit entries
|
||||
[
|
||||
/entry: config\.client\.entry\(\)/,
|
||||
`entry: { main: config.client.entry().main.replace(/\\.js$/, '.ts') }`
|
||||
],
|
||||
[
|
||||
/entry: config\.server\.entry\(\)/,
|
||||
`entry: { server: config.server.entry().server.replace(/\\.js$/, '.ts') }`
|
||||
],
|
||||
[
|
||||
/entry: config\.serviceworker\.entry\(\)/,
|
||||
`entry: { 'service-worker': config.serviceworker.entry()['service-worker'].replace(/\\.js$/, '.ts') }`
|
||||
],
|
||||
[
|
||||
/loader: 'svelte-loader',\n\t\t\t\t\t\toptions: {/g,
|
||||
'loader: \'svelte-loader\',\n\t\t\t\t\t\toptions: {\n\t\t\t\t\t\t\tpreprocess: sveltePreprocess({ sourceMap: dev }),'
|
||||
],
|
||||
// Add TypeScript rules for client and server
|
||||
[
|
||||
/module: {\n\s*rules: \[\n\s*(?!{\n\s*test: \/\\\.ts\$\/)/g,
|
||||
`module: {\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttest: /\\.ts$/,\n\t\t\t\t\tloader: 'ts-loader'\n\t\t\t\t},\n\t\t\t\t`
|
||||
],
|
||||
// Add TypeScript rules for serviceworker
|
||||
[
|
||||
/output: config\.serviceworker\.output\(\),\n\s*(?!module)/,
|
||||
`output: config.serviceworker.output(),\n\t\tmodule: {\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttest: /\\.ts$/,\n\t\t\t\t\tloader: 'ts-loader'\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t`
|
||||
],
|
||||
// Edit outputs
|
||||
[
|
||||
/output: config\.serviceworker\.output\(\),\n\s*(?!resolve)/,
|
||||
`output: config.serviceworker.output(),\n\t\tresolve: { extensions: ['.mjs', '.js', '.ts', '.json'] },\n\t\t`
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
function updateServiceWorker() {
|
||||
replaceInFile(path.join(projectRoot, 'src', 'service-worker.ts'), [
|
||||
[`shell.concat(files);`, `(shell as string[]).concat(files as string[]);`],
|
||||
[`self.skipWaiting();`, `((self as any) as ServiceWorkerGlobalScope).skipWaiting();`],
|
||||
[`self.clients.claim();`, `((self as any) as ServiceWorkerGlobalScope).clients.claim();`],
|
||||
[`fetchAndCache(request)`, `fetchAndCache(request: Request)`],
|
||||
[`self.addEventListener('activate', event =>`, `self.addEventListener('activate', (event: ExtendableEvent) =>`],
|
||||
[`self.addEventListener('install', event =>`, `self.addEventListener('install', (event: ExtendableEvent) =>`],
|
||||
[`addEventListener('fetch', event =>`, `addEventListener('fetch', (event: FetchEvent) =>`],
|
||||
]);
|
||||
}
|
||||
|
||||
function createTsConfig() {
|
||||
const tsconfig = `{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "ES2017", "WebWorker"]
|
||||
},
|
||||
"include": ["src/**/*", "src/node_modules/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "static/*"]
|
||||
}`;
|
||||
|
||||
createFile(path.join(projectRoot, 'tsconfig.json'), tsconfig);
|
||||
}
|
||||
|
||||
// Adds the extension recommendation
|
||||
function configureVsCode() {
|
||||
const dir = path.join(projectRoot, '.vscode');
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
|
||||
createFile(path.join(projectRoot, '.vscode', 'extensions.json'), `{"recommendations": ["svelte.svelte-vscode"]}`);
|
||||
}
|
||||
|
||||
function deleteThisScript() {
|
||||
fs.unlinkSync(path.join(__filename));
|
||||
|
||||
// Check for Mac's DS_store file, and if it's the only one left remove it
|
||||
const remainingFiles = fs.readdirSync(path.join(__dirname));
|
||||
if (remainingFiles.length === 1 && remainingFiles[0] === '.DS_store') {
|
||||
fs.unlinkSync(path.join(__dirname, '.DS_store'));
|
||||
}
|
||||
|
||||
// Check if the scripts folder is empty
|
||||
if (fs.readdirSync(path.join(__dirname)).length === 0) {
|
||||
// Remove the scripts folder
|
||||
fs.rmdirSync(path.join(__dirname));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Adding TypeScript with ${isRollup ? "Rollup" : "webpack" }...`);
|
||||
|
||||
addDepsToPackageJson();
|
||||
|
||||
changeJsExtensionToTs(path.join(projectRoot, 'src'));
|
||||
|
||||
updateSvelteFiles();
|
||||
|
||||
if (isRollup) {
|
||||
updateRollupConfig();
|
||||
} else {
|
||||
updateWebpackConfig();
|
||||
}
|
||||
|
||||
updateServiceWorker();
|
||||
|
||||
createTsConfig();
|
||||
|
||||
configureVsCode();
|
||||
|
||||
// Delete this script, but not during testing
|
||||
if (!argv[2]) {
|
||||
deleteThisScript();
|
||||
}
|
||||
|
||||
console.log('Converted to TypeScript.');
|
||||
|
||||
if (fs.existsSync(path.join(projectRoot, 'node_modules'))) {
|
||||
console.log(`
|
||||
Next:
|
||||
1. run 'npm install' again to install TypeScript dependencies
|
||||
2. run 'npm run build' for the @sapper imports in your project to work
|
||||
`);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
WixTools
|
||||
@@ -1,34 +0,0 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
build = "src/build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "=1.0.0-rc.2" }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "=1.0.0-rc.2", features = [] }
|
||||
tokio = "1.4"
|
||||
url = "2.2"
|
||||
|
||||
tauri-codegen = "=1.0.0-rc.1"
|
||||
tauri-macros = "=1.0.0-rc.1"
|
||||
core-graphics = "=0.22.2"
|
||||
|
||||
coconut-interface = { path = "../../../common/coconut-interface" }
|
||||
credentials = { path = "../../../common/credentials" }
|
||||
validator-client = {path = "../../../common/client-libs/validator-client"}
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 88 KiB |
@@ -1,13 +0,0 @@
|
||||
max_width = 100
|
||||
hard_tabs = false
|
||||
tab_spaces = 2
|
||||
newline_style = "Auto"
|
||||
use_small_heuristics = "Default"
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
remove_nested_parens = true
|
||||
edition = "2018"
|
||||
merge_derives = true
|
||||
use_try_shorthand = false
|
||||
use_field_init_shorthand = false
|
||||
force_explicit_abi = true
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
use url::Url;
|
||||
|
||||
use coconut_interface::{
|
||||
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
|
||||
};
|
||||
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||
|
||||
struct State {
|
||||
signatures: Vec<Signature>,
|
||||
n_attributes: u32,
|
||||
params: Parameters,
|
||||
serial_number: Attribute,
|
||||
binding_number: Attribute,
|
||||
voucher_value: Attribute,
|
||||
voucher_info: Attribute,
|
||||
aggregated_verification_key: Option<VerificationKey>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn init(public_attributes_bytes: Vec<Vec<u8>>, private_attributes_bytes: Vec<Vec<u8>>) -> State {
|
||||
let n_attributes = (public_attributes_bytes.len() + private_attributes_bytes.len()) as u32;
|
||||
let params = Parameters::new(n_attributes).unwrap();
|
||||
let public_attributes = public_attributes_bytes
|
||||
.iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<Attribute>>();
|
||||
let private_attributes = private_attributes_bytes
|
||||
.iter()
|
||||
.map(hash_to_scalar)
|
||||
.collect::<Vec<Attribute>>();
|
||||
State {
|
||||
signatures: Vec::new(),
|
||||
n_attributes,
|
||||
params,
|
||||
serial_number: private_attributes[0],
|
||||
binding_number: private_attributes[1],
|
||||
voucher_value: public_attributes[0],
|
||||
voucher_info: public_attributes[1],
|
||||
aggregated_verification_key: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_url_validators(raw: &[String]) -> Result<Vec<Url>, String> {
|
||||
let mut parsed_urls = Vec::with_capacity(raw.len());
|
||||
for url in raw {
|
||||
let parsed_url: Url = url
|
||||
.parse()
|
||||
.map_err(|err| format!("one of validator urls is malformed - {}", err))?;
|
||||
parsed_urls.push(parsed_url)
|
||||
}
|
||||
Ok(parsed_urls)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn randomise_credential(
|
||||
idx: usize,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<Signature>, String> {
|
||||
let mut state = state.write().await;
|
||||
let signature = state.signatures.remove(idx);
|
||||
let (new_signature, _) = signature.randomise(&state.params);
|
||||
state.signatures.insert(idx, new_signature);
|
||||
Ok(state.signatures.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn delete_credential(
|
||||
idx: usize,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<Signature>, String> {
|
||||
let mut state = state.write().await;
|
||||
state.signatures.remove(idx);
|
||||
Ok(state.signatures.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn list_credentials(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<Signature>, String> {
|
||||
let state = state.read().await;
|
||||
Ok(state.signatures.clone())
|
||||
}
|
||||
|
||||
async fn get_aggregated_verification_key(
|
||||
validator_urls: Vec<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<VerificationKey, String> {
|
||||
if let Some(verification_key) = &state.read().await.aggregated_verification_key {
|
||||
return Ok(verification_key.clone());
|
||||
}
|
||||
|
||||
let parsed_urls = parse_url_validators(&validator_urls)?;
|
||||
let key = obtain_aggregate_verification_key(&parsed_urls)
|
||||
.await
|
||||
.map_err(|err| format!("failed to obtain aggregate verification key - {:?}", err))?;
|
||||
|
||||
state
|
||||
.write()
|
||||
.await
|
||||
.aggregated_verification_key
|
||||
.replace(key.clone());
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
async fn prove_credential(
|
||||
idx: usize,
|
||||
validator_urls: Vec<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Theta, String> {
|
||||
let verification_key = get_aggregated_verification_key(validator_urls, state.clone()).await?;
|
||||
let state = state.read().await;
|
||||
|
||||
if let Some(signature) = state.signatures.get(idx) {
|
||||
match coconut_interface::prove_bandwidth_credential(
|
||||
&state.params,
|
||||
&verification_key,
|
||||
signature,
|
||||
state.serial_number,
|
||||
state.binding_number,
|
||||
) {
|
||||
Ok(theta) => Ok(theta),
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
}
|
||||
} else {
|
||||
Err("Got invalid Signature idx".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn verify_credential(
|
||||
idx: usize,
|
||||
validator_urls: Vec<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<bool, String> {
|
||||
// the API needs to be improved but at least it should compile (in theory)
|
||||
let verification_key =
|
||||
get_aggregated_verification_key(validator_urls.clone(), state.clone()).await?;
|
||||
let theta = prove_credential(idx, validator_urls, state.clone()).await?;
|
||||
|
||||
let state = state.read().await;
|
||||
|
||||
let public_attributes_bytes = vec![
|
||||
state.voucher_value.to_bytes().to_vec(),
|
||||
state.voucher_info.to_bytes().to_vec(),
|
||||
];
|
||||
|
||||
let credential = Credential::new(
|
||||
state.n_attributes,
|
||||
theta,
|
||||
public_attributes_bytes,
|
||||
state
|
||||
.signatures
|
||||
.get(idx)
|
||||
.ok_or("Got invalid signature idx")?,
|
||||
);
|
||||
|
||||
Ok(credential.verify(&verification_key))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_credential(
|
||||
validator_urls: Vec<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<Signature>, String> {
|
||||
let guard = state.read().await;
|
||||
let parsed_urls = parse_url_validators(&validator_urls)?;
|
||||
let public_attributes = vec![guard.voucher_value, guard.voucher_info];
|
||||
let private_attributes = vec![guard.serial_number, guard.binding_number];
|
||||
|
||||
let signature = obtain_aggregate_signature(
|
||||
&guard.params,
|
||||
&public_attributes,
|
||||
&private_attributes,
|
||||
&parsed_urls,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("failed to obtain aggregate signature - {:?}", err))?;
|
||||
|
||||
let mut state = state.write().await;
|
||||
state.signatures.push(signature);
|
||||
Ok(state.signatures.clone())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let public_attributes = vec![b"public_key".to_vec()];
|
||||
let private_attributes = vec![b"private_key".to_vec()];
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::init(
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
))))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_credential,
|
||||
randomise_credential,
|
||||
delete_credential,
|
||||
list_credentials,
|
||||
verify_credential
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-tauri-client",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../__sapper__/export",
|
||||
"devPath": "http://localhost:3000",
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": "yarn run export"
|
||||
},
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.tauri.dev",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "",
|
||||
"category": "DeveloperTool",
|
||||
"shortDescription": "",
|
||||
"longDescription": "",
|
||||
"deb": {
|
||||
"depends": [],
|
||||
"useBootstrapper": false
|
||||
},
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "",
|
||||
"useBootstrapper": false,
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": null,
|
||||
"entitlements": null
|
||||
},
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"allowlist": {},
|
||||
"windows": [
|
||||
{
|
||||
"title": "Nym Client",
|
||||
"width": 800,
|
||||
"height": 1000,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* These declarations tell TypeScript that we allow import of images, e.g.
|
||||
* ```
|
||||
<script lang='ts'>
|
||||
import successkid from 'images/successkid.jpg';
|
||||
</script>
|
||||
|
||||
<img src="{successkid}">
|
||||
```
|
||||
*/
|
||||
declare module "*.gif" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.jpg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.jpeg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.svg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.webp" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import * as sapper from '@sapper/app';
|
||||
|
||||
sapper.start({
|
||||
target: document.querySelector('#sapper')
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
<nav>
|
||||
<img src="coconut-120.png" style="width: 120px" alt=""/>
|
||||
</nav>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script>
|
||||
export let status;
|
||||
export let error;
|
||||
|
||||
const dev = process.env.NODE_ENV === 'development';
|
||||
</script>
|
||||
|
||||
<style>
|
||||
h1, p {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.8em;
|
||||
font-weight: 700;
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em auto;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
h1 {
|
||||
font-size: 4em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<title>{status}</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1>{status}</h1>
|
||||
|
||||
<p>{error.message}</p>
|
||||
|
||||
{#if dev && error.stack}
|
||||
<pre>{error.stack}</pre>
|
||||
{/if}
|
||||
@@ -1,22 +0,0 @@
|
||||
<script>
|
||||
import Nav from '../components/Nav.svelte';
|
||||
|
||||
// export let segment;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
main {
|
||||
position: relative;
|
||||
max-width: 56em;
|
||||
/* background-color: white; */
|
||||
padding: 2em;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Nav />
|
||||
|
||||
<main>
|
||||
<slot></slot>
|
||||
</main>
|
||||
@@ -1,119 +0,0 @@
|
||||
<script>
|
||||
import {invoke} from "@tauri-apps/api/tauri";
|
||||
import {onMount} from "svelte";
|
||||
import QRious from "qrious";
|
||||
|
||||
const validator_urls = ["http://localhost:8080"];
|
||||
let signatures = [];
|
||||
let qrVisible = false;
|
||||
|
||||
async function getCredential() {
|
||||
signatures = await invoke("get_credential", {
|
||||
validatorUrls: validator_urls,
|
||||
});
|
||||
}
|
||||
|
||||
async function randomiseCredential(idx) {
|
||||
signatures = await invoke("randomise_credential", {
|
||||
idx: idx,
|
||||
});
|
||||
}
|
||||
|
||||
async function verifyCredential(idx) {
|
||||
const response = await invoke("verify_credential", {
|
||||
idx: idx,
|
||||
validatorUrls: validator_urls,
|
||||
});
|
||||
alert(response);
|
||||
}
|
||||
|
||||
async function deleteCredential(idx) {
|
||||
signatures = await invoke("delete_credential", {
|
||||
idx: idx,
|
||||
});
|
||||
}
|
||||
|
||||
async function listCredentials() {
|
||||
signatures = await invoke("list_credentials");
|
||||
}
|
||||
|
||||
function signatureQR(idx) {
|
||||
qrVisible = true;
|
||||
const signature = signatures[idx];
|
||||
new QRious({
|
||||
element: document.getElementById("qr"),
|
||||
value: signature,
|
||||
foreground: "white",
|
||||
background: "black",
|
||||
size: 148,
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
listCredentials();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Coconut</title>
|
||||
</svelte:head>
|
||||
|
||||
<button class="btn btn-success" on:click={getCredential}>Get Credential</button>
|
||||
<hr />
|
||||
<table class="table table-dark">
|
||||
{#each signatures as signature, idx}
|
||||
<tr>
|
||||
<td><p>{signature.slice(0, 12)}</p></td>
|
||||
<td>
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={() => {
|
||||
randomiseCredential(idx);
|
||||
}}>Randomize</button
|
||||
><button
|
||||
class="btn btn-danger"
|
||||
on:click={() => {
|
||||
deleteCredential(idx);
|
||||
}}>Delete</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-info"
|
||||
on:click={() => {
|
||||
signatureQR(idx);
|
||||
}}>QR</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={() => {
|
||||
verifyCredential(idx);
|
||||
}}>Verify</button
|
||||
>
|
||||
</div></td
|
||||
>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
|
||||
<div
|
||||
class="modal"
|
||||
tabindex="-1"
|
||||
style={qrVisible ? "display: block" : "display: none"}
|
||||
>
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<canvas id="qr"></canvas>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
on:click={() => (qrVisible = false)}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div><br /></div> -->
|
||||
@@ -1,17 +0,0 @@
|
||||
import sirv from 'sirv';
|
||||
import polka from 'polka';
|
||||
import compression from 'compression';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT, NODE_ENV } = process.env;
|
||||
const dev = NODE_ENV === 'development';
|
||||
|
||||
polka() // You can also use Express
|
||||
.use(
|
||||
compression({ threshold: 0 }),
|
||||
sirv('static', { dev }),
|
||||
sapper.middleware()
|
||||
)
|
||||
.listen(PORT, err => {
|
||||
if (err) console.log('error', err);
|
||||
});
|
||||
@@ -1,86 +0,0 @@
|
||||
import { timestamp, files, shell } from '@sapper/service-worker';
|
||||
|
||||
const ASSETS = `cache${timestamp}`;
|
||||
|
||||
// `shell` is an array of all the files generated by the bundler,
|
||||
// `files` is an array of everything in the `static` directory
|
||||
const to_cache = shell.concat(files);
|
||||
const staticAssets = new Set(to_cache);
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
event.waitUntil(
|
||||
caches
|
||||
.open(ASSETS)
|
||||
.then(cache => cache.addAll(to_cache))
|
||||
.then(() => {
|
||||
self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(
|
||||
caches.keys().then(async keys => {
|
||||
// delete old caches
|
||||
for (const key of keys) {
|
||||
if (key !== ASSETS) await caches.delete(key);
|
||||
}
|
||||
|
||||
self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Fetch the asset from the network and store it in the cache.
|
||||
* Fall back to the cache if the user is offline.
|
||||
*/
|
||||
async function fetchAndCache(request) {
|
||||
const cache = await caches.open(`offline${timestamp}`)
|
||||
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
cache.put(request, response.clone());
|
||||
return response;
|
||||
} catch (err) {
|
||||
const response = await cache.match(request);
|
||||
if (response) return response;
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
|
||||
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// don't try to handle e.g. data: URIs
|
||||
const isHttp = url.protocol.startsWith('http');
|
||||
const isDevServerRequest = url.hostname === self.location.hostname && url.port !== self.location.port;
|
||||
const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname);
|
||||
const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset;
|
||||
|
||||
if (isHttp && !isDevServerRequest && !skipBecauseUncached) {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
// always serve static files and bundler-generated assets from cache.
|
||||
// if your application has other URLs with data that will never change,
|
||||
// set this variable to true for them and they will only be fetched once.
|
||||
const cachedAsset = isStaticAsset && await caches.match(event.request);
|
||||
|
||||
// for pages, you might want to serve a shell `service-worker-index.html` file,
|
||||
// which Sapper has generated for you. It's not right for every
|
||||
// app, but if it's right for yours then uncomment this section
|
||||
/*
|
||||
if (!cachedAsset && url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
|
||||
return caches.match('/service-worker-index.html');
|
||||
}
|
||||
*/
|
||||
|
||||
return cachedAsset || fetchAndCache(event.request);
|
||||
})()
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
|
||||
%sapper.base%
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="stylesheets/bootstrap-grid.min.css">
|
||||
<link rel="stylesheet" href="stylesheets/bootstrap-reboot.min.css">
|
||||
<link rel="stylesheet" href="stylesheets/bundle.min.css">
|
||||
<link rel="manifest" href="manifest.json" crossorigin="use-credentials">
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="apple-touch-icon" href="logo-192.png">
|
||||
|
||||
<!-- Sapper creates a <script> tag containing `src/client.js`
|
||||
and anything else it needs to hydrate the app and
|
||||
initialise the router -->
|
||||
%sapper.scripts%
|
||||
|
||||
<!-- Sapper generates a <style> tag containing critical CSS
|
||||
for the current page. CSS for the rest of the app is
|
||||
lazily loaded when it precaches secondary pages -->
|
||||
%sapper.styles%
|
||||
|
||||
<!-- This contains the contents of the <svelte:head> component, if
|
||||
the current page has one -->
|
||||
%sapper.head%
|
||||
</head>
|
||||
<body>
|
||||
<!-- The application will be rendered inside this element,
|
||||
because `src/client.js` references it -->
|
||||
<div id="sapper">%sapper.html%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |