Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf9ae009ee | |||
| 8bf3efcef7 | |||
| 37de4bf2f7 | |||
| d3372bfc85 | |||
| 25e1bfa345 | |||
| dc0b9c271c | |||
| 9ef29037bc | |||
| 1e13d41245 | |||
| 46a4991c12 | |||
| 9d2d670990 | |||
| baba5ed212 | |||
| ceb5f090cf | |||
| b6ffe8664c | |||
| be4bc2bdcc | |||
| bc049cb954 | |||
| d6c9d1d08d | |||
| 49fc51853a | |||
| 026932dc16 | |||
| fda3636783 | |||
| 5cc08211b7 | |||
| 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 | |||
| c85b0ad07d | |||
| adf4537183 | |||
| 1cf101d50f | |||
| e91e6943c6 | |||
| 700f6a4e98 | |||
| b759e5e7f2 | |||
| deefa09066 | |||
| 3f6cb919ac | |||
| d08bf61905 | |||
| da18a60a91 | |||
| cec7496794 | |||
| dd82b24d61 | |||
| df827b6b09 | |||
| cb25cc2eb9 | |||
| abf7e1f6ad | |||
| 0f5137ea24 | |||
| 34903bfae6 | |||
| 9e8f550e6d | |||
| 8ad3565f2c | |||
| 47bdf38776 | |||
| cdd883c174 | |||
| 2d82a51905 | |||
| 38c2ce9837 | |||
| a867921fdd | |||
| 423cdb1e1b | |||
| 7aeac58fd9 | |||
| 30fafa509c | |||
| c950556506 | |||
| 9a49213973 |
@@ -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,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 }}
|
||||
```
|
||||
Generated
+378
-176
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ panic = "abort"
|
||||
resolver = "2"
|
||||
members = [
|
||||
"clients/client-core",
|
||||
"clients/credential",
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
@@ -25,7 +26,9 @@ members = [
|
||||
"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",
|
||||
|
||||
@@ -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 |
@@ -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"]
|
||||
Generated
+3453
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
[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" }
|
||||
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,186 @@
|
||||
// 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 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) -> 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) -> 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) -> 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) -> 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?;
|
||||
state.signature = Some(signature.to_bs58());
|
||||
db.set(&self.tx_hash, &state).unwrap();
|
||||
|
||||
println!("Signature: {:?}", state.signature);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct SpendCredential {
|
||||
/// Spend one of the acquired credentials
|
||||
#[clap(long)]
|
||||
id: usize,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Execute for SpendCredential {
|
||||
async fn execute(&self, _db: &mut PickleDb) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
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),
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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 = "sun surge soon stomach flavor country gorilla dress oblige stamp attract hip soldier agree steel prize nuclear know enjoy arm bargain always theme matter";
|
||||
pub const NYMD_URL: &str = "http://127.0.0.1:26657";
|
||||
pub const CONTRACT_ADDRESS: &str = "nymt1vhjnzk9ly03dugffvzfcwgry4dgc8x0sscmfl2";
|
||||
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 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).await?,
|
||||
Commands::ListDeposits(m) => m.execute(&mut db).await?,
|
||||
Commands::GetCredential(m) => m.execute(&mut db).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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ 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"]
|
||||
eth = []
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -5,11 +5,12 @@ 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 coconut_interface::{Credential, Parameters};
|
||||
use config::NymConfig;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
use credentials::coconut::{
|
||||
bandwidth::prepare_for_spending, bandwidth::BandwidthVoucher, bandwidth::TOTAL_ATTRIBUTES,
|
||||
utils::obtain_aggregate_signature,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
@@ -17,7 +18,7 @@ use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
#[cfg(feature = "coconut")]
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
use network_defaults::{BANDWIDTH_VALUE, VOUCHER_INFO};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -28,6 +29,8 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
#[cfg(feature = "coconut")]
|
||||
use validator_client::nymd::tx::Hash;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::override_config;
|
||||
@@ -107,15 +110,25 @@ async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8])
|
||||
.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 mut rng = OsRng;
|
||||
let bandwidth_credential_attributes = BandwidthVoucher::new(
|
||||
¶ms,
|
||||
BANDWIDTH_VALUE.to_string(),
|
||||
VOUCHER_INFO.to_string(),
|
||||
Hash::new([0; 32]),
|
||||
// workaround for putting a valid value here, without deriving clone for the private
|
||||
// key, until we have actual useful values
|
||||
identity::PrivateKey::from_base58_string(
|
||||
identity::KeyPair::new(&mut rng)
|
||||
.private_key()
|
||||
.to_base58_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
encryption::KeyPair::new(&mut rng).private_key().clone(),
|
||||
);
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
obtain_aggregate_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ 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"]
|
||||
eth = []
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -5,11 +5,12 @@ 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 coconut_interface::{Credential, Parameters};
|
||||
use config::NymConfig;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
use credentials::coconut::{
|
||||
bandwidth::prepare_for_spending, bandwidth::BandwidthVoucher, bandwidth::TOTAL_ATTRIBUTES,
|
||||
utils::obtain_aggregate_signature,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
@@ -26,6 +27,8 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
#[cfg(feature = "coconut")]
|
||||
use validator_client::nymd::tx::Hash;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::override_config;
|
||||
@@ -107,15 +110,25 @@ async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8])
|
||||
.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 mut rng = OsRng;
|
||||
let bandwidth_credential_attributes = BandwidthVoucher::new(
|
||||
¶ms,
|
||||
BANDWIDTH_VALUE.to_string(),
|
||||
network_defaults::VOUCHER_INFO.to_string(),
|
||||
Hash::new([0; 32]),
|
||||
// workaround for putting a valid value here, without deriving clone for the private
|
||||
// key, until we have actual useful values
|
||||
identity::PrivateKey::from_base58_string(
|
||||
identity::KeyPair::new(&mut rng)
|
||||
.private_key()
|
||||
.to_base58_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
encryption::KeyPair::new(&mut rng).private_key().clone(),
|
||||
);
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
obtain_aggregate_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ secp256k1 = "0.20.3"
|
||||
web3 = { version = "0.17.0", default-features = false }
|
||||
|
||||
# internal
|
||||
cosmrs = { version = "0.4.1", optional = true }
|
||||
credentials = { path = "../../credentials" }
|
||||
crypto = { path = "../../crypto" }
|
||||
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||
@@ -26,6 +27,7 @@ nymsphinx = { path = "../../nymsphinx" }
|
||||
pemstore = { path = "../../pemstore" }
|
||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
validator-client = { path = "../validator-client", optional = true }
|
||||
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
@@ -67,6 +69,6 @@ features = ["js"]
|
||||
#url = "2.1"
|
||||
|
||||
[features]
|
||||
coconut = ["gateway-requests/coconut", "coconut-interface"]
|
||||
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut", "cosmrs"]
|
||||
wasm = ["web3/wasm", "web3/http", "web3/signing"]
|
||||
default = ["web3/default"]
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use cosmrs::tx::Hash;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::{
|
||||
bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
},
|
||||
utils::obtain_aggregate_verification_key,
|
||||
bandwidth::{prepare_for_spending, BandwidthVoucher, TOTAL_ATTRIBUTES},
|
||||
utils::{obtain_aggregate_signature, obtain_aggregate_verification_key},
|
||||
};
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use credentials::token::bandwidth::TokenCredential;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[cfg(feature = "coconut")]
|
||||
use crypto::asymmetric::encryption;
|
||||
use crypto::asymmetric::identity;
|
||||
use crypto::asymmetric::identity::PublicKey;
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use network_defaults::{
|
||||
@@ -22,7 +22,6 @@ use network_defaults::{
|
||||
};
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use pemstore::traits::PemStorableKeyPair;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use rand::rngs::OsRng;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use secp256k1::SecretKey;
|
||||
@@ -73,7 +72,7 @@ pub struct BandwidthController {
|
||||
#[cfg(feature = "coconut")]
|
||||
validator_endpoints: Vec<url::Url>,
|
||||
#[cfg(feature = "coconut")]
|
||||
identity: PublicKey,
|
||||
identity: identity::PublicKey,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
contract: Contract<Http>,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -86,7 +85,7 @@ pub struct BandwidthController {
|
||||
|
||||
impl BandwidthController {
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn new(validator_endpoints: Vec<url::Url>, identity: PublicKey) -> Self {
|
||||
pub fn new(validator_endpoints: Vec<url::Url>, identity: identity::PublicKey) -> Self {
|
||||
BandwidthController {
|
||||
validator_endpoints,
|
||||
identity,
|
||||
@@ -175,17 +174,25 @@ impl BandwidthController {
|
||||
let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?;
|
||||
let params = coconut_interface::Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
|
||||
let mut rng = OsRng;
|
||||
// TODO: Decide what is the value and additional info associated with the bandwidth voucher
|
||||
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
|
||||
serial_number: params.random_scalar(),
|
||||
binding_number: params.random_scalar(),
|
||||
voucher_value: coconut_interface::hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
|
||||
voucher_info: coconut_interface::hash_to_scalar(
|
||||
String::from("BandwidthVoucher").as_bytes(),
|
||||
),
|
||||
};
|
||||
let bandwidth_credential_attributes = BandwidthVoucher::new(
|
||||
¶ms,
|
||||
BANDWIDTH_VALUE.to_string(),
|
||||
network_defaults::VOUCHER_INFO.to_string(),
|
||||
Hash::new([0; 32]),
|
||||
// workaround for putting a valid value here, without deriving clone for the private
|
||||
// key, until we have actual useful values
|
||||
identity::PrivateKey::from_base58_string(
|
||||
identity::KeyPair::new(&mut rng)
|
||||
.private_key()
|
||||
.to_base58_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
encryption::KeyPair::new(&mut rng).private_key().clone(),
|
||||
);
|
||||
|
||||
let bandwidth_credential = obtain_signature(
|
||||
let bandwidth_credential = obtain_aggregate_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
&self.validator_endpoints,
|
||||
@@ -205,7 +212,7 @@ impl BandwidthController {
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub async fn prepare_token_credential(
|
||||
&self,
|
||||
gateway_identity: PublicKey,
|
||||
gateway_identity: identity::PublicKey,
|
||||
gateway_owner: String,
|
||||
) -> Result<TokenCredential, GatewayClientError> {
|
||||
let kp = match self.restore_keypair() {
|
||||
@@ -244,7 +251,7 @@ impl BandwidthController {
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub async fn buy_token_credential(
|
||||
&self,
|
||||
verification_key: PublicKey,
|
||||
verification_key: identity::PublicKey,
|
||||
signed_verification_key: identity::Signature,
|
||||
gateway_owner: String,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
|
||||
@@ -9,6 +9,7 @@ rust-version = "1.56"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13"
|
||||
colored = "2.0"
|
||||
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
|
||||
vesting-contract = { path = "../../../contracts/vesting" }
|
||||
@@ -18,6 +19,8 @@ reqwest = { version = "0.11", features = ["json"] }
|
||||
thiserror = "1"
|
||||
log = "0.4"
|
||||
url = { version = "2.2", features = ["serde"] }
|
||||
tokio = { version = "1.10", features = ["sync", "time"] }
|
||||
futures = "0.3"
|
||||
|
||||
coconut-interface = { path = "../../coconut-interface" }
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
|
||||
@@ -685,6 +685,16 @@ impl ApiClient {
|
||||
Ok(self.validator_api.blind_sign(request_body).await?)
|
||||
}
|
||||
|
||||
pub async fn partial_bandwidth_credential(
|
||||
&self,
|
||||
request_body: &str,
|
||||
) -> Result<BlindedSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.validator_api
|
||||
.partial_bandwidth_credential(request_body)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_coconut_verification_key(
|
||||
&self,
|
||||
) -> Result<VerificationKeyResponse, ValidatorClientError> {
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::{NymdClient, QueryNymdClient};
|
||||
use crate::ApiClient;
|
||||
use network_defaults::all::Network;
|
||||
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasher;
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
use url::Url;
|
||||
|
||||
const MAX_URLS_TESTED: usize = 200;
|
||||
const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
|
||||
|
||||
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
|
||||
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
|
||||
nymd_urls: impl Iterator<Item = (Network, Url)>,
|
||||
api_urls: impl Iterator<Item = (Network, Url)>,
|
||||
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
|
||||
) -> (
|
||||
HashMap<Network, Vec<(Url, bool)>>,
|
||||
HashMap<Network, Vec<(Url, bool)>>,
|
||||
) {
|
||||
// Setup all the clients for the connection tests
|
||||
let connection_test_clients =
|
||||
setup_connection_tests(nymd_urls, api_urls, mixnet_contract_address);
|
||||
|
||||
// Run all tests concurrently
|
||||
let connection_results = futures::future::join_all(
|
||||
connection_test_clients
|
||||
.into_iter()
|
||||
.take(MAX_URLS_TESTED)
|
||||
.map(ClientForConnectionTest::run_connection_check),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Seperate and collect results into HashMaps
|
||||
(
|
||||
extract_and_collect_results_into_map(&connection_results, &UrlType::Nymd),
|
||||
extract_and_collect_results_into_map(&connection_results, &UrlType::Api),
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_connection_tests<H: BuildHasher + 'static>(
|
||||
nymd_urls: impl Iterator<Item = (Network, Url)>,
|
||||
api_urls: impl Iterator<Item = (Network, Url)>,
|
||||
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
|
||||
) -> impl Iterator<Item = ClientForConnectionTest> {
|
||||
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
|
||||
let address = mixnet_contract_address
|
||||
.get(&network)
|
||||
.expect("No configured contract address")
|
||||
.clone();
|
||||
NymdClient::<QueryNymdClient>::connect(url.as_str(), address, None, None)
|
||||
.map(move |client| ClientForConnectionTest::Nymd(network, url, Box::new(client)))
|
||||
.ok()
|
||||
});
|
||||
|
||||
let api_connection_test_clients = api_urls.map(|(network, url)| {
|
||||
ClientForConnectionTest::Api(network, url.clone(), ApiClient::new(url))
|
||||
});
|
||||
|
||||
nymd_connection_test_clients.chain(api_connection_test_clients)
|
||||
}
|
||||
|
||||
fn extract_and_collect_results_into_map(
|
||||
connection_results: &[ConnectionResult],
|
||||
url_type: &UrlType,
|
||||
) -> HashMap<Network, Vec<(Url, bool)>> {
|
||||
connection_results
|
||||
.iter()
|
||||
.filter(|c| &c.url_type() == url_type)
|
||||
.map(|c| {
|
||||
let (network, url, result) = c.result();
|
||||
(*network, (url.clone(), *result))
|
||||
})
|
||||
.into_group_map()
|
||||
}
|
||||
|
||||
async fn test_nymd_connection(
|
||||
network: Network,
|
||||
url: &Url,
|
||||
client: &NymdClient<QueryNymdClient>,
|
||||
) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
client.get_mixnet_contract_version(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Err(NymdError::TendermintError(e))) => {
|
||||
// If we get a tendermint-rpc error, we classify the node as not contactable
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}: {}",
|
||||
"failed".red(),
|
||||
e
|
||||
);
|
||||
false
|
||||
}
|
||||
Ok(Err(NymdError::AbciError(code, log))) => {
|
||||
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
|
||||
// for example on a pre-launch network.
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
|
||||
"success".green()
|
||||
);
|
||||
code == 18
|
||||
}
|
||||
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}: {error}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
// For any other error, we're optimistic and just try anyway.
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
|
||||
"success".green()
|
||||
);
|
||||
true
|
||||
}
|
||||
Ok(Ok(_)) => {
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}",
|
||||
"success".green()
|
||||
);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}: {e}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
ConnectionResult::Nymd(network, url.clone(), result)
|
||||
}
|
||||
|
||||
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
client.get_cached_mixnodes(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => {
|
||||
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
|
||||
true
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
log::debug!(
|
||||
"Checking: api_url: {network}: {url}: {}: {e}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!(
|
||||
"Checking: api_url: {network}: {url}: {}: {e}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
ConnectionResult::Api(network, url.clone(), result)
|
||||
}
|
||||
|
||||
enum ClientForConnectionTest {
|
||||
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
|
||||
Api(Network, Url, ApiClient),
|
||||
}
|
||||
|
||||
impl ClientForConnectionTest {
|
||||
async fn run_connection_check(self) -> ConnectionResult {
|
||||
match self {
|
||||
ClientForConnectionTest::Nymd(network, ref url, ref client) => {
|
||||
test_nymd_connection(network, url, client).await
|
||||
}
|
||||
ClientForConnectionTest::Api(network, ref url, ref client) => {
|
||||
test_api_connection(network, url, client).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum UrlType {
|
||||
Nymd,
|
||||
Api,
|
||||
}
|
||||
|
||||
impl fmt::Display for UrlType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
UrlType::Nymd => write!(f, "nymd"),
|
||||
UrlType::Api => write!(f, "api"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ConnectionResult {
|
||||
Nymd(Network, Url, bool),
|
||||
Api(Network, Url, bool),
|
||||
}
|
||||
|
||||
impl ConnectionResult {
|
||||
fn result(&self) -> (&Network, &Url, &bool) {
|
||||
match self {
|
||||
ConnectionResult::Nymd(network, url, result)
|
||||
| ConnectionResult::Api(network, url, result) => (network, url, result),
|
||||
}
|
||||
}
|
||||
|
||||
fn url_type(&self) -> UrlType {
|
||||
match self {
|
||||
ConnectionResult::Nymd(..) => UrlType::Nymd,
|
||||
ConnectionResult::Api(..) => UrlType::Api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectionResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let (network, url, result) = self.result();
|
||||
let url_type = self.url_type();
|
||||
write!(
|
||||
f,
|
||||
"{network}: {url}: {url_type}: connection is successful: {result}"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub mod connection_tester;
|
||||
mod error;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub mod nymd;
|
||||
|
||||
@@ -29,9 +29,12 @@ pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
pub use crate::nymd::fee::Fee;
|
||||
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
||||
pub use cosmrs::rpc::endpoint::tx::Response as TxResponse;
|
||||
pub use cosmrs::rpc::endpoint::validators::Response as ValidatorResponse;
|
||||
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
|
||||
pub use cosmrs::rpc::Paging;
|
||||
pub use cosmrs::tendermint::abci::responses::{DeliverTx, Event};
|
||||
pub use cosmrs::tendermint::abci::tag::Tag;
|
||||
pub use cosmrs::tendermint::block::Height;
|
||||
pub use cosmrs::tendermint::hash;
|
||||
pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
|
||||
@@ -274,6 +277,13 @@ impl<C> NymdClient<C> {
|
||||
self.client.get_balance(address, denom).await
|
||||
}
|
||||
|
||||
pub async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.get_tx(id).await
|
||||
}
|
||||
|
||||
pub async fn get_total_supply(&self) -> Result<Vec<Coin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
|
||||
@@ -7,4 +7,7 @@ pub enum ValidatorAPIError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("Request failed with error message - {0}")]
|
||||
GenericRequestFailure(String),
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use validator_api_requests::models::{
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
pub(crate) mod routes;
|
||||
pub mod routes;
|
||||
|
||||
type PathSegments<'a> = &'a [&'a str];
|
||||
type Params<'a, K, V> = &'a [(K, V)];
|
||||
@@ -39,6 +39,10 @@ impl Client {
|
||||
self.url = new_url
|
||||
}
|
||||
|
||||
pub fn current_url(&self) -> &Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
async fn query_validator_api<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -66,14 +70,14 @@ impl Client {
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
Ok(self
|
||||
.reqwest_client
|
||||
.post(url)
|
||||
.json(json_body)
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
let response = self.reqwest_client.post(url).json(json_body).send().await?;
|
||||
if response.status().is_success() {
|
||||
Ok(response.json().await?)
|
||||
} else {
|
||||
Err(ValidatorAPIError::GenericRequestFailure(
|
||||
response.text().await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
|
||||
@@ -254,7 +258,29 @@ impl Client {
|
||||
request_body: &BlindSignRequestBody,
|
||||
) -> Result<BlindedSignatureResponse, ValidatorAPIError> {
|
||||
self.post_validator_api(
|
||||
&[routes::API_VERSION, routes::COCONUT_BLIND_SIGN],
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_BLIND_SIGN,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn partial_bandwidth_credential(
|
||||
&self,
|
||||
request_body: &str,
|
||||
) -> Result<BlindedSignatureResponse, ValidatorAPIError> {
|
||||
self.post_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
@@ -265,7 +291,12 @@ impl Client {
|
||||
&self,
|
||||
) -> Result<VerificationKeyResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY],
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_VERIFICATION_KEY,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -10,7 +10,11 @@ pub const GATEWAYS: &str = "gateways";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
|
||||
pub const COCONUT_ROUTES: &str = "coconut";
|
||||
pub const BANDWIDTH: &str = "bandwidth";
|
||||
|
||||
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
|
||||
pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential";
|
||||
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
|
||||
@@ -5,7 +5,9 @@ edition = "2021"
|
||||
description = "Crutch library until there is proper SerDe support for coconut structs"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
bs58 = "0.4.0"
|
||||
getset = "0.1.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
|
||||
nymcoconut = {path = "../nymcoconut" }
|
||||
|
||||
@@ -5,21 +5,9 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CoconutInterfaceError {
|
||||
#[error("could not parse validator URL: {source}")]
|
||||
UrlParsingError {
|
||||
#[from]
|
||||
source: url::ParseError,
|
||||
},
|
||||
#[error("not enough bytes: {0} received, minimum {1} required")]
|
||||
InvalidByteLength(usize, usize),
|
||||
|
||||
#[error("could not aggregate verification key: {0}")]
|
||||
AggregateVerificationKeyError(coconut_rs::CoconutError),
|
||||
|
||||
#[error("could not prove credential: {0}")]
|
||||
ProveCredentialError(coconut_rs::CoconutError),
|
||||
|
||||
#[error("got invalid signature index: {0}")]
|
||||
InvalidSignatureIdx(usize),
|
||||
|
||||
#[error("got too many total attributes(public + private): {0} received, {1} is the maximum")]
|
||||
TooManyTotalAttributes(usize, u32),
|
||||
#[error("Could not decode base 58 string - {0}")]
|
||||
MalformedString(#[from] bs58::decode::Error),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod error;
|
||||
|
||||
use getset::{CopyGetters, Getters};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use error::CoconutInterfaceError;
|
||||
|
||||
pub use nymcoconut::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)]
|
||||
@@ -79,27 +83,39 @@ impl VerifyCredentialBody {
|
||||
}
|
||||
}
|
||||
// All strings are base58 encoded representations of structs
|
||||
#[derive(Serialize, Deserialize, Debug, Getters, CopyGetters)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Getters, CopyGetters)]
|
||||
pub struct BlindSignRequestBody {
|
||||
#[getset(get = "pub")]
|
||||
blind_sign_request: BlindSignRequest,
|
||||
#[getset(get = "pub")]
|
||||
tx_hash: String,
|
||||
#[getset(get = "pub")]
|
||||
signature: String,
|
||||
public_attributes: Vec<String>,
|
||||
#[getset(get = "pub")]
|
||||
public_attributes_plain: Vec<String>,
|
||||
#[getset(get = "pub")]
|
||||
total_params: u32,
|
||||
}
|
||||
|
||||
impl BlindSignRequestBody {
|
||||
pub fn new(
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
tx_hash: String,
|
||||
signature: String,
|
||||
public_attributes: &[Attribute],
|
||||
public_attributes_plain: Vec<String>,
|
||||
total_params: u32,
|
||||
) -> BlindSignRequestBody {
|
||||
BlindSignRequestBody {
|
||||
blind_sign_request: blind_sign_request.clone(),
|
||||
tx_hash,
|
||||
signature,
|
||||
public_attributes: public_attributes
|
||||
.iter()
|
||||
.map(|attr| attr.to_bs58())
|
||||
.collect(),
|
||||
public_attributes_plain,
|
||||
total_params,
|
||||
}
|
||||
}
|
||||
@@ -112,14 +128,46 @@ impl BlindSignRequestBody {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BlindedSignatureResponse {
|
||||
pub blinded_signature: BlindedSignature,
|
||||
pub remote_key: [u8; 32],
|
||||
pub encrypted_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BlindedSignatureResponse {
|
||||
pub fn new(blinded_signature: BlindedSignature) -> BlindedSignatureResponse {
|
||||
BlindedSignatureResponse { blinded_signature }
|
||||
pub fn new(encrypted_signature: Vec<u8>, remote_key: [u8; 32]) -> BlindedSignatureResponse {
|
||||
BlindedSignatureResponse {
|
||||
encrypted_signature,
|
||||
remote_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_base58_string(&self) -> String {
|
||||
bs58::encode(&self.to_bytes()).into_string()
|
||||
}
|
||||
|
||||
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, CoconutInterfaceError> {
|
||||
let bytes = bs58::decode(val).into_vec()?;
|
||||
Self::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = self.remote_key.to_vec();
|
||||
bytes.extend_from_slice(&self.encrypted_signature);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoconutInterfaceError> {
|
||||
if bytes.len() < 32 {
|
||||
return Err(CoconutInterfaceError::InvalidByteLength(bytes.len(), 32));
|
||||
}
|
||||
let mut remote_key = [0u8; 32];
|
||||
remote_key.copy_from_slice(&bytes[..32]);
|
||||
let encrypted_signature = bytes[32..].to_vec();
|
||||
Ok(BlindedSignatureResponse {
|
||||
remote_key,
|
||||
encrypted_signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ edition = "2021"
|
||||
[dependencies]
|
||||
handlebars = "3.0.1"
|
||||
humantime-serde = "1.0"
|
||||
log = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.5.6"
|
||||
url = "2.2"
|
||||
|
||||
network-defaults = { path = "../network-defaults" }
|
||||
network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -13,6 +13,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
fn template() -> &'static str;
|
||||
|
||||
fn config_file_name() -> String {
|
||||
log::trace!("NymdConfig::config_file_name");
|
||||
"config.toml".to_string()
|
||||
}
|
||||
|
||||
@@ -20,6 +21,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
|
||||
// default, most probable, implementations; can be easily overridden where required
|
||||
fn default_config_directory(id: Option<&str>) -> PathBuf {
|
||||
log::trace!("NymdConfig::default_config_directory");
|
||||
if let Some(id) = id {
|
||||
Self::default_root_directory().join(id).join("config")
|
||||
} else {
|
||||
@@ -28,6 +30,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
}
|
||||
|
||||
fn default_data_directory(id: Option<&str>) -> PathBuf {
|
||||
log::trace!("NymdConfig::default_data_path");
|
||||
if let Some(id) = id {
|
||||
Self::default_root_directory().join(id).join("data")
|
||||
} else {
|
||||
@@ -36,6 +39,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
}
|
||||
|
||||
fn default_config_file_path(id: Option<&str>) -> PathBuf {
|
||||
log::trace!("NymdConfig::default_config_file_path");
|
||||
Self::default_config_directory(id).join(Self::config_file_name())
|
||||
}
|
||||
|
||||
@@ -68,7 +72,9 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
}
|
||||
|
||||
fn load_from_file(id: Option<&str>) -> io::Result<Self> {
|
||||
let config_contents = fs::read_to_string(Self::default_config_file_path(id))?;
|
||||
let file = Self::default_config_file_path(id);
|
||||
log::trace!("Loading from file: {:#?}", file);
|
||||
let config_contents = fs::read_to_string(file)?;
|
||||
|
||||
toml::from_str(&config_contents)
|
||||
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "coconut-bandwidth-contract-common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct DepositData {
|
||||
deposit_info: String,
|
||||
identity_key: String,
|
||||
encryption_key: String,
|
||||
}
|
||||
|
||||
impl DepositData {
|
||||
pub fn new(deposit_info: String, identity_key: String, encryption_key: String) -> Self {
|
||||
DepositData {
|
||||
deposit_info,
|
||||
identity_key,
|
||||
encryption_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deposit_info(&self) -> &str {
|
||||
&self.deposit_info
|
||||
}
|
||||
|
||||
pub fn identity_key(&self) -> &str {
|
||||
&self.identity_key
|
||||
}
|
||||
|
||||
pub fn encryption_key(&self) -> &str {
|
||||
&self.encryption_key
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// event types
|
||||
pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds";
|
||||
|
||||
// attributes that are used in multiple places
|
||||
pub const DEPOSIT_VALUE: &str = "deposit-value";
|
||||
pub const DEPOSIT_INFO: &str = "deposit-info";
|
||||
pub const DEPOSIT_IDENTITY_KEY: &str = "deposit-identity-key";
|
||||
pub const DEPOSIT_ENCRYPTION_KEY: &str = "deposit-encryption-key";
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod deposit;
|
||||
pub mod events;
|
||||
pub mod msg;
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::deposit::DepositData;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct InstantiateMsg {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
DepositFunds { data: DepositData },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
@@ -21,9 +21,10 @@ time = { version = "0.3.6", features = ["parsing", "formatting"] }
|
||||
|
||||
contracts-common = { path = "../contracts-common" }
|
||||
|
||||
ts-rs = { version = "6.1.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
time = { version = "0.3.5", features = ["serde", "macros"] }
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
all(feature = "ts-rs", test),
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/gateway.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
|
||||
@@ -14,9 +14,9 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
all(feature = "ts-rs", test),
|
||||
ts(
|
||||
export,
|
||||
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
|
||||
@@ -109,9 +109,9 @@ impl PendingUndelegate {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
all(feature = "ts-rs", test),
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/mixnode.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
@@ -228,7 +228,7 @@ impl DelegatorRewardParams {
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward =
|
||||
(ONE - self.profit_margin) * scaled_delegation_amount / self.sigma * self.node_profit;
|
||||
(ONE - self.profit_margin) * (scaled_delegation_amount / self.sigma) * self.node_profit;
|
||||
|
||||
let reward = delegator_reward.max(U128::ZERO);
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
@@ -426,17 +426,17 @@ impl MixNodeBond {
|
||||
&self,
|
||||
params: &RewardParams,
|
||||
) -> Result<(u64, u64, u64), MixnetContractError> {
|
||||
let total_node_reward = self.reward(params);
|
||||
let total_node_reward = self
|
||||
.reward(params)
|
||||
.reward()
|
||||
.checked_to_num::<u128>()
|
||||
.unwrap_or_default();
|
||||
let operator_reward = self.operator_reward(params);
|
||||
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
|
||||
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
|
||||
// Total reward has to be the sum of operator and delegator rewards
|
||||
let delegators_reward = total_node_reward - operator_reward;
|
||||
|
||||
Ok((
|
||||
total_node_reward
|
||||
.reward()
|
||||
.checked_to_num::<u128>()
|
||||
.unwrap_or_default()
|
||||
.try_into()?,
|
||||
total_node_reward.try_into()?,
|
||||
operator_reward.try_into()?,
|
||||
delegators_reward.try_into()?,
|
||||
))
|
||||
|
||||
@@ -96,10 +96,25 @@ impl NodeEpochRewards {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
all(feature = "ts-rs", test),
|
||||
ts(
|
||||
export,
|
||||
export_to = "../../../nym-wallet/src/types/rust/epochrewardparams.ts"
|
||||
)
|
||||
)]
|
||||
pub struct EpochRewardParams {
|
||||
#[cfg_attr(feature = "ts-rs", ts(type = "string"))]
|
||||
epoch_reward_pool: Uint128,
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", ts(type = "string"))]
|
||||
rewarded_set_size: Uint128,
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", ts(type = "string"))]
|
||||
active_set_size: Uint128,
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", ts(type = "string"))]
|
||||
circulating_supply: Uint128,
|
||||
sybil_resistance_percent: u8,
|
||||
active_set_work_factor: u8,
|
||||
@@ -157,8 +172,17 @@ impl EpochRewardParams {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
all(feature = "ts-rs", test),
|
||||
ts(
|
||||
export,
|
||||
export_to = "../../../nym-wallet/src/types/rust/noderewardparams.ts"
|
||||
)
|
||||
)]
|
||||
pub struct NodeRewardParams {
|
||||
reward_blockstamp: u64,
|
||||
#[cfg_attr(feature = "ts-rs", ts(type = "string"))]
|
||||
uptime: Uint128,
|
||||
in_active_set: bool,
|
||||
}
|
||||
@@ -186,6 +210,14 @@ impl NodeRewardParams {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
all(feature = "ts-rs", test),
|
||||
ts(
|
||||
export,
|
||||
export_to = "../../../nym-wallet/src/types/rust/rewardparams.ts"
|
||||
)
|
||||
)]
|
||||
pub struct RewardParams {
|
||||
pub epoch: EpochRewardParams,
|
||||
pub node: NodeRewardParams,
|
||||
|
||||
@@ -7,11 +7,18 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
cosmrs = { version = "0.4.1", optional = true }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
|
||||
# I guess temporarily until we get serde support in coconut up and running
|
||||
coconut-interface = { path = "../coconut-interface" }
|
||||
crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] }
|
||||
network-defaults = { path = "../network-defaults" }
|
||||
validator-client = { path = "../client-libs/validator-client" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
||||
|
||||
[features]
|
||||
coconut = ["cosmrs"]
|
||||
@@ -7,56 +7,167 @@
|
||||
// it's the simplest possible case
|
||||
|
||||
use coconut_interface::{
|
||||
Credential, Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey,
|
||||
hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters,
|
||||
PrivateAttribute, PublicAttribute, Signature, VerificationKey,
|
||||
};
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
use url::Url;
|
||||
|
||||
use cosmrs::tx::Hash;
|
||||
|
||||
use super::utils::prepare_credential_for_spending;
|
||||
use crate::error::Error;
|
||||
|
||||
use super::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
|
||||
|
||||
pub const PUBLIC_ATTRIBUTES: u32 = 2;
|
||||
pub const PRIVATE_ATTRIBUTES: u32 = 2;
|
||||
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
|
||||
|
||||
pub struct BandwidthVoucherAttributes {
|
||||
pub struct BandwidthVoucher {
|
||||
// a random secret value generated by the client used for double-spending detection
|
||||
pub serial_number: PrivateAttribute,
|
||||
serial_number: PrivateAttribute,
|
||||
// a random secret value generated by the client used to bind multiple credentials together
|
||||
pub binding_number: PrivateAttribute,
|
||||
binding_number: PrivateAttribute,
|
||||
// the value (e.g., bandwidth) encoded in this voucher
|
||||
pub voucher_value: PublicAttribute,
|
||||
voucher_value: PublicAttribute,
|
||||
// the plain text value (e.g., bandwidth) encoded in this voucher
|
||||
voucher_value_plain: String,
|
||||
// a field with public information, e.g., type of voucher, interval etc.
|
||||
pub voucher_info: PublicAttribute,
|
||||
voucher_info: PublicAttribute,
|
||||
// the plain text information
|
||||
voucher_info_plain: String,
|
||||
// the hash of the deposit transaction
|
||||
tx_hash: Hash,
|
||||
// base58 encoded private key ensuring the depositer requested these attributes
|
||||
signing_key: identity::PrivateKey,
|
||||
// base58 encoded private key ensuring only this client receives the signature share
|
||||
encryption_key: encryption::PrivateKey,
|
||||
pedersen_commitments_openings: Vec<Attribute>,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
use_request: bool,
|
||||
}
|
||||
|
||||
impl BandwidthVoucherAttributes {
|
||||
impl BandwidthVoucher {
|
||||
pub fn new_with_blind_sign_req(
|
||||
private_attributes: [PrivateAttribute; PRIVATE_ATTRIBUTES as usize],
|
||||
public_attributes_plain: [&str; PUBLIC_ATTRIBUTES as usize],
|
||||
tx_hash: Hash,
|
||||
signing_key: identity::PrivateKey,
|
||||
encryption_key: encryption::PrivateKey,
|
||||
pedersen_commitments_openings: Vec<Attribute>,
|
||||
blind_sign_request: BlindSignRequest,
|
||||
) -> Self {
|
||||
let voucher_value = public_attributes_plain[0];
|
||||
let voucher_info = public_attributes_plain[1];
|
||||
let voucher_value_plain = voucher_value.to_string();
|
||||
let voucher_info_plain = voucher_info.to_string();
|
||||
let voucher_value = hash_to_scalar(voucher_value.as_bytes());
|
||||
let voucher_info = hash_to_scalar(voucher_info.as_bytes());
|
||||
|
||||
BandwidthVoucher {
|
||||
serial_number: private_attributes[0],
|
||||
binding_number: private_attributes[1],
|
||||
voucher_value,
|
||||
voucher_value_plain,
|
||||
voucher_info,
|
||||
voucher_info_plain,
|
||||
tx_hash,
|
||||
signing_key,
|
||||
encryption_key,
|
||||
pedersen_commitments_openings,
|
||||
blind_sign_request,
|
||||
use_request: false,
|
||||
}
|
||||
}
|
||||
pub fn new(
|
||||
params: &Parameters,
|
||||
voucher_value: String,
|
||||
voucher_info: String,
|
||||
tx_hash: Hash,
|
||||
signing_key: identity::PrivateKey,
|
||||
encryption_key: encryption::PrivateKey,
|
||||
) -> Self {
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let voucher_value_plain = voucher_value.clone();
|
||||
let voucher_info_plain = voucher_info.clone();
|
||||
let voucher_value = hash_to_scalar(voucher_value.as_bytes());
|
||||
let voucher_info = hash_to_scalar(voucher_info.as_bytes());
|
||||
let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign(
|
||||
params,
|
||||
&[serial_number, binding_number],
|
||||
&[voucher_value, voucher_info],
|
||||
)
|
||||
.unwrap();
|
||||
BandwidthVoucher {
|
||||
serial_number,
|
||||
binding_number,
|
||||
voucher_value,
|
||||
voucher_value_plain,
|
||||
voucher_info,
|
||||
voucher_info_plain,
|
||||
tx_hash,
|
||||
signing_key,
|
||||
encryption_key,
|
||||
pedersen_commitments_openings,
|
||||
blind_sign_request,
|
||||
use_request: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the plain values correspond to the PublicAttributes
|
||||
pub fn verify_against_plain(values: &[PublicAttribute], plain_values: &[String]) -> bool {
|
||||
values.len() == 2
|
||||
&& plain_values.len() == 2
|
||||
&& values[0] == hash_to_scalar(&plain_values[0])
|
||||
&& values[1] == hash_to_scalar(&plain_values[1])
|
||||
}
|
||||
|
||||
pub fn tx_hash(&self) -> &Hash {
|
||||
&self.tx_hash
|
||||
}
|
||||
|
||||
pub fn get_public_attributes(&self) -> Vec<PublicAttribute> {
|
||||
vec![self.voucher_value, self.voucher_info]
|
||||
}
|
||||
|
||||
pub fn encryption_key(&self) -> &encryption::PrivateKey {
|
||||
&self.encryption_key
|
||||
}
|
||||
|
||||
pub fn pedersen_commitments_openings(&self) -> &Vec<Attribute> {
|
||||
&self.pedersen_commitments_openings
|
||||
}
|
||||
|
||||
pub fn blind_sign_request(&self) -> &BlindSignRequest {
|
||||
&self.blind_sign_request
|
||||
}
|
||||
|
||||
pub fn use_request(&self) -> bool {
|
||||
self.use_request
|
||||
}
|
||||
|
||||
pub fn get_public_attributes_plain(&self) -> Vec<String> {
|
||||
vec![
|
||||
self.voucher_value_plain.clone(),
|
||||
self.voucher_info_plain.clone(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_private_attributes(&self) -> Vec<PrivateAttribute> {
|
||||
vec![self.serial_number, self.binding_number]
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this definitely has to be moved somewhere else. It's just a temporary solution
|
||||
pub async fn obtain_signature(
|
||||
params: &Parameters,
|
||||
attributes: &BandwidthVoucherAttributes,
|
||||
validators: &[Url],
|
||||
) -> Result<Signature, Error> {
|
||||
let public_attributes = attributes.get_public_attributes();
|
||||
let private_attributes = attributes.get_private_attributes();
|
||||
|
||||
obtain_aggregate_signature(params, &public_attributes, &private_attributes, validators).await
|
||||
pub fn sign(&self, request: &BlindSignRequest) -> identity::Signature {
|
||||
let mut message = request.to_bytes();
|
||||
message.extend_from_slice(self.tx_hash.as_bytes());
|
||||
self.signing_key.sign(&message)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_for_spending(
|
||||
raw_identity: &[u8],
|
||||
signature: &Signature,
|
||||
attributes: &BandwidthVoucherAttributes,
|
||||
attributes: &BandwidthVoucher,
|
||||
verification_key: &VerificationKey,
|
||||
) -> Result<Credential, Error> {
|
||||
let public_attributes = vec![
|
||||
@@ -75,3 +186,62 @@ pub fn prepare_for_spending(
|
||||
verification_key,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
#[test]
|
||||
fn voucher_consistency() {
|
||||
let params = Parameters::new(4).unwrap();
|
||||
let mut rng = OsRng;
|
||||
let voucher = BandwidthVoucher::new(
|
||||
¶ms,
|
||||
"1234".to_string(),
|
||||
"voucher info".to_string(),
|
||||
Hash::new([0; 32]),
|
||||
identity::PrivateKey::from_base58_string(
|
||||
identity::KeyPair::new(&mut rng)
|
||||
.private_key()
|
||||
.to_base58_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
encryption::KeyPair::new(&mut rng).private_key().clone(),
|
||||
);
|
||||
assert!(!BandwidthVoucher::verify_against_plain(
|
||||
&[],
|
||||
&voucher.get_public_attributes_plain()
|
||||
));
|
||||
assert!(!BandwidthVoucher::verify_against_plain(
|
||||
&voucher.get_public_attributes(),
|
||||
&[],
|
||||
));
|
||||
assert!(!BandwidthVoucher::verify_against_plain(
|
||||
&voucher.get_public_attributes(),
|
||||
&[
|
||||
voucher.get_public_attributes_plain()[0].clone(),
|
||||
String::new()
|
||||
]
|
||||
));
|
||||
assert!(!BandwidthVoucher::verify_against_plain(
|
||||
&voucher.get_public_attributes(),
|
||||
&[
|
||||
String::new(),
|
||||
voucher.get_public_attributes_plain()[1].clone()
|
||||
]
|
||||
));
|
||||
assert!(!BandwidthVoucher::verify_against_plain(
|
||||
&[voucher.get_public_attributes()[0], Attribute::one()],
|
||||
&voucher.get_public_attributes_plain()
|
||||
));
|
||||
assert!(!BandwidthVoucher::verify_against_plain(
|
||||
&[Attribute::one(), voucher.get_public_attributes()[1]],
|
||||
&voucher.get_public_attributes_plain()
|
||||
));
|
||||
assert!(BandwidthVoucher::verify_against_plain(
|
||||
&voucher.get_public_attributes(),
|
||||
&voucher.get_public_attributes_plain()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod bandwidth;
|
||||
pub mod params;
|
||||
pub mod utils;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::aes::Aes128;
|
||||
use crypto::blake3;
|
||||
use crypto::ctr;
|
||||
|
||||
type Aes128Ctr = ctr::Ctr64LE<Aes128>;
|
||||
|
||||
/// Hashing algorithm used during hkdf for ephemeral shared key generation per blinded signature
|
||||
/// response encryption.
|
||||
pub type ValidatorApiCredentialHkdfAlgorithm = blake3::Hasher;
|
||||
|
||||
/// Encryption algorithm used for end-to-end encryption of blinded signature response
|
||||
pub type ValidatorApiCredentialEncryptionAlgorithm = Aes128Ctr;
|
||||
@@ -1,15 +1,20 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::Scalar;
|
||||
use coconut_interface::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
|
||||
prove_bandwidth_credential, Attribute, BlindSignRequest, BlindSignRequestBody, Credential,
|
||||
Parameters, Signature, SignatureShare, VerificationKey,
|
||||
aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute,
|
||||
BlindSignRequestBody, BlindedSignature, Credential, Parameters, Signature, SignatureShare,
|
||||
VerificationKey,
|
||||
};
|
||||
use crypto::asymmetric::encryption::PublicKey;
|
||||
use crypto::shared_key::recompute_shared_key;
|
||||
use crypto::symmetric::stream_cipher;
|
||||
use url::Url;
|
||||
|
||||
use crate::coconut::bandwidth::PRIVATE_ATTRIBUTES;
|
||||
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES};
|
||||
use crate::coconut::params::{
|
||||
ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm,
|
||||
};
|
||||
use crate::error::Error;
|
||||
|
||||
/// Contacts all provided validators and then aggregate their verification keys.
|
||||
@@ -63,31 +68,53 @@ pub async fn obtain_aggregate_verification_key(
|
||||
|
||||
async fn obtain_partial_credential(
|
||||
params: &Parameters,
|
||||
public_attributes: &[Attribute],
|
||||
private_attributes: &[Attribute],
|
||||
pedersen_commitments_openings: &[Scalar],
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
attributes: &BandwidthVoucher,
|
||||
client: &validator_client::ApiClient,
|
||||
validator_vk: &VerificationKey,
|
||||
) -> Result<Signature, Error> {
|
||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
||||
blind_sign_request,
|
||||
public_attributes,
|
||||
(public_attributes.len() + private_attributes.len()) as u32,
|
||||
let public_attributes = attributes.get_public_attributes();
|
||||
let public_attributes_plain = attributes.get_public_attributes_plain();
|
||||
let private_attributes = attributes.get_private_attributes();
|
||||
let blind_sign_request = attributes.blind_sign_request();
|
||||
|
||||
let response = if attributes.use_request() {
|
||||
let blind_sign_request_body = BlindSignRequestBody::new(
|
||||
blind_sign_request,
|
||||
attributes.tx_hash().to_string(),
|
||||
attributes.sign(blind_sign_request).to_base58_string(),
|
||||
&public_attributes,
|
||||
public_attributes_plain,
|
||||
(public_attributes.len() + private_attributes.len()) as u32,
|
||||
);
|
||||
client.blind_sign(&blind_sign_request_body).await?
|
||||
} else {
|
||||
client
|
||||
.partial_bandwidth_credential(&attributes.tx_hash().to_string())
|
||||
.await?
|
||||
};
|
||||
let encrypted_signature = response.encrypted_signature;
|
||||
let remote_key = PublicKey::from_bytes(&response.remote_key)?;
|
||||
|
||||
let encryption_key = recompute_shared_key::<
|
||||
ValidatorApiCredentialEncryptionAlgorithm,
|
||||
ValidatorApiCredentialHkdfAlgorithm,
|
||||
>(&remote_key, attributes.encryption_key());
|
||||
let zero_iv = stream_cipher::zero_iv::<ValidatorApiCredentialEncryptionAlgorithm>();
|
||||
let blinded_signature_bytes = stream_cipher::decrypt::<ValidatorApiCredentialEncryptionAlgorithm>(
|
||||
&encryption_key,
|
||||
&zero_iv,
|
||||
&encrypted_signature,
|
||||
);
|
||||
|
||||
let blinded_signature = client
|
||||
.blind_sign(&blind_sign_request_body)
|
||||
.await?
|
||||
.blinded_signature;
|
||||
let blinded_signature = BlindedSignature::from_bytes(&blinded_signature_bytes)?;
|
||||
|
||||
let unblinded_signature = blinded_signature.unblind(
|
||||
params,
|
||||
validator_vk,
|
||||
private_attributes,
|
||||
public_attributes,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&*pedersen_commitments_openings,
|
||||
attributes.pedersen_commitments_openings(),
|
||||
)?;
|
||||
|
||||
Ok(unblinded_signature)
|
||||
@@ -95,13 +122,14 @@ async fn obtain_partial_credential(
|
||||
|
||||
pub async fn obtain_aggregate_signature(
|
||||
params: &Parameters,
|
||||
public_attributes: &[Attribute],
|
||||
private_attributes: &[Attribute],
|
||||
attributes: &BandwidthVoucher,
|
||||
validators: &[Url],
|
||||
) -> Result<Signature, Error> {
|
||||
if validators.is_empty() {
|
||||
return Err(Error::NoValidatorsAvailable);
|
||||
}
|
||||
let public_attributes = attributes.get_public_attributes();
|
||||
let private_attributes = attributes.get_private_attributes();
|
||||
|
||||
let mut shares = Vec::with_capacity(validators.len());
|
||||
let mut validators_partial_vks: Vec<VerificationKey> = Vec::with_capacity(validators.len());
|
||||
@@ -110,42 +138,24 @@ pub async fn obtain_aggregate_signature(
|
||||
let validator_partial_vk = client.get_coconut_verification_key().await?;
|
||||
validators_partial_vks.push(validator_partial_vk.key.clone());
|
||||
|
||||
let (pedersen_commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(params, private_attributes, public_attributes)?;
|
||||
|
||||
let first = obtain_partial_credential(
|
||||
params,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
&pedersen_commitments_openings,
|
||||
&blind_sign_request,
|
||||
&client,
|
||||
&validator_partial_vk.key,
|
||||
)
|
||||
.await?;
|
||||
let first =
|
||||
obtain_partial_credential(params, attributes, &client, &validator_partial_vk.key).await?;
|
||||
shares.push(SignatureShare::new(first, 1));
|
||||
|
||||
for (id, validator_url) in validators.iter().enumerate().skip(1) {
|
||||
client.change_validator_api(validator_url.clone());
|
||||
let validator_partial_vk = client.get_coconut_verification_key().await?;
|
||||
validators_partial_vks.push(validator_partial_vk.key.clone());
|
||||
let signature = obtain_partial_credential(
|
||||
params,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
&pedersen_commitments_openings,
|
||||
&blind_sign_request,
|
||||
&client,
|
||||
&validator_partial_vk.key,
|
||||
)
|
||||
.await?;
|
||||
let signature =
|
||||
obtain_partial_credential(params, attributes, &client, &validator_partial_vk.key)
|
||||
.await?;
|
||||
let share = SignatureShare::new(signature, (id + 1) as u64);
|
||||
shares.push(share)
|
||||
}
|
||||
|
||||
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
|
||||
attributes.extend_from_slice(private_attributes);
|
||||
attributes.extend_from_slice(public_attributes);
|
||||
attributes.extend_from_slice(&private_attributes);
|
||||
attributes.extend_from_slice(&public_attributes);
|
||||
|
||||
let mut indices: Vec<u64> = Vec::with_capacity(validators_partial_vks.len());
|
||||
for i in 0..validators_partial_vks.len() {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::CoconutError;
|
||||
use thiserror::Error;
|
||||
use crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("The detailed description is yet to be determined")]
|
||||
@@ -13,6 +16,7 @@ pub enum Error {
|
||||
#[error("Could not contact any validator")]
|
||||
NoValidatorsAvailable,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Run into a coconut error - {0}")]
|
||||
CoconutError(#[from] CoconutError),
|
||||
|
||||
@@ -30,4 +34,7 @@ pub enum Error {
|
||||
|
||||
#[error("There is not associated bandwidth for the given client")]
|
||||
MissingBandwidth,
|
||||
|
||||
#[error("Could not parse the key - {0}")]
|
||||
ParsePublicKey(#[from] KeyRecoveryError),
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub mod coconut;
|
||||
pub mod error;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub mod token;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||
|
||||
@@ -6,7 +6,6 @@ use crypto::asymmetric::identity::{PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGN
|
||||
use crate::error::Error;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub struct TokenCredential {
|
||||
verification_key: PublicKey,
|
||||
gateway_identity: PublicKey,
|
||||
@@ -14,7 +13,6 @@ pub struct TokenCredential {
|
||||
signature: Signature,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
impl TokenCredential {
|
||||
pub fn new(
|
||||
verification_key: PublicKey,
|
||||
@@ -99,7 +97,6 @@ impl TokenCredential {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[test]
|
||||
fn token_serde() {
|
||||
// pre-generated, valid values
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "dkg"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitvec = "1.0.0"
|
||||
|
||||
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
|
||||
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||
|
||||
bs58 = "0.4"
|
||||
|
||||
lazy_static = "1.4.0"
|
||||
rand = { version = "0.8.5", default-features = false}
|
||||
rand_chacha = "0.3"
|
||||
rand_core = "0.6.3"
|
||||
sha2 = "0.9"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
thiserror = "1.0"
|
||||
zeroize = { version = "1.4", features = ["zeroize_derive"] }
|
||||
|
||||
[dependencies.group]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[dependencies.ff]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
@@ -0,0 +1,541 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::{G1Projective, G2Affine, G2Prepared, Scalar};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use dkg::bte::encryption::BabyStepGiantStepLookup;
|
||||
use dkg::bte::proof_chunking::ProofOfChunking;
|
||||
use dkg::bte::proof_discrete_log::ProofOfDiscreteLog;
|
||||
use dkg::bte::proof_sharing::ProofOfSecretSharing;
|
||||
use dkg::bte::{
|
||||
decrypt_share, encrypt_shares, keygen, proof_chunking, proof_sharing, setup, DecryptionKey,
|
||||
Epoch, PublicKey,
|
||||
};
|
||||
use dkg::interpolation::polynomial::Polynomial;
|
||||
use dkg::{Dealing, NodeIndex, Share};
|
||||
use ff::Field;
|
||||
use rand_core::{RngCore, SeedableRng};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub fn precompute_default_bsgs_table(c: &mut Criterion) {
|
||||
c.bench_function("bsgs default table", |b| {
|
||||
b.iter(|| black_box(BabyStepGiantStepLookup::default()))
|
||||
});
|
||||
}
|
||||
|
||||
pub fn precomputing_g2_generator_for_miller_loop(c: &mut Criterion) {
|
||||
let g2 = G2Affine::generator();
|
||||
c.bench_function("precomputing G2Prepared", |b| {
|
||||
b.iter(|| black_box(G2Prepared::from(g2)))
|
||||
});
|
||||
}
|
||||
|
||||
fn prepare_keys(
|
||||
mut rng: impl RngCore,
|
||||
nodes: usize,
|
||||
) -> (BTreeMap<NodeIndex, PublicKey>, Vec<DecryptionKey>) {
|
||||
let params = setup();
|
||||
let mut node_indices = (0..nodes).map(|_| rng.next_u64()).collect::<Vec<_>>();
|
||||
node_indices.sort_unstable();
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
let mut dks = Vec::new();
|
||||
for index in &node_indices {
|
||||
let (dk, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
dks.push(dk)
|
||||
}
|
||||
|
||||
(receivers, dks)
|
||||
}
|
||||
|
||||
pub fn creating_dealing_for_3_parties(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let threshold = 2;
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 3);
|
||||
|
||||
c.bench_function("creating single dealing for 3 parties (threshold 2)", |b| {
|
||||
b.iter(|| {
|
||||
black_box({
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
receivers.keys().next().copied().unwrap(),
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let threshold = 2;
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, mut dks) = prepare_keys(&mut rng, 3);
|
||||
let (dealing, _) = Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
receivers.keys().next().copied().unwrap(),
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
);
|
||||
|
||||
let first_key = dks.get_mut(0).unwrap();
|
||||
first_key.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
c.bench_function(
|
||||
"verifying single dealing made for 3 parties (threshold 2) and recovering share",
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
assert!(dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers)
|
||||
.is_ok());
|
||||
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn creating_dealing_for_20_parties(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let threshold = 14;
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 20);
|
||||
|
||||
c.bench_function(
|
||||
"creating single dealing for 20 parties (threshold 14)",
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
black_box({
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
receivers.keys().next().copied().unwrap(),
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let threshold = 14;
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, mut dks) = prepare_keys(&mut rng, 20);
|
||||
let (dealing, _) = Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
receivers.keys().next().copied().unwrap(),
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
);
|
||||
|
||||
let first_key = dks.get_mut(0).unwrap();
|
||||
first_key.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
c.bench_function(
|
||||
"verifying single dealing made for 20 parties (threshold 14) and recovering share",
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
assert!(dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers)
|
||||
.is_ok());
|
||||
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn creating_dealing_for_100_parties(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let threshold = 67;
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 100);
|
||||
|
||||
c.bench_function(
|
||||
"creating single dealing for 100 parties (threshold 67)",
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
black_box({
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
receivers.keys().next().copied().unwrap(),
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let threshold = 67;
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, mut dks) = prepare_keys(&mut rng, 100);
|
||||
let (dealing, _) = Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
receivers.keys().next().copied().unwrap(),
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
);
|
||||
|
||||
let first_key = dks.get_mut(0).unwrap();
|
||||
first_key.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
c.bench_function(
|
||||
"verifying single dealing made for 100 parties (threshold 67) and recovering share",
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
assert!(dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers)
|
||||
.is_ok());
|
||||
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn creating_proof_of_key_possession(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let g1 = G1Projective::generator();
|
||||
let x = Scalar::random(&mut rng);
|
||||
let y = g1 * x;
|
||||
|
||||
c.bench_function("creating proof of key possession", |b| {
|
||||
b.iter(|| black_box(ProofOfDiscreteLog::construct(&mut rng, &y, &x)))
|
||||
});
|
||||
}
|
||||
|
||||
pub fn verifying_proof_of_key_possession(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let g1 = G1Projective::generator();
|
||||
let x = Scalar::random(&mut rng);
|
||||
let y = g1 * x;
|
||||
|
||||
let zk_proof = ProofOfDiscreteLog::construct(&mut rng, &y, &x);
|
||||
c.bench_function("verifying proof of key possession", |b| {
|
||||
b.iter(|| black_box(zk_proof.verify(&y)))
|
||||
});
|
||||
}
|
||||
|
||||
pub fn creating_proof_of_chunking_for_100_parties(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 100);
|
||||
|
||||
let polynomial = Polynomial::new_random(&mut rng, 67);
|
||||
let shares = receivers
|
||||
.keys()
|
||||
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let remote_share_key_pairs = shares
|
||||
.iter()
|
||||
.zip(receivers.values())
|
||||
.map(|(share, key)| (share, key))
|
||||
.collect::<Vec<_>>();
|
||||
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
|
||||
|
||||
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng);
|
||||
|
||||
c.bench_function("creating proof of chunking for 100 parties", |b| {
|
||||
b.iter(|| {
|
||||
let chunking_instance =
|
||||
proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
|
||||
black_box(
|
||||
ProofOfChunking::construct(&mut rng, chunking_instance, hazmat.r(), &shares)
|
||||
.expect("failed to construct proof of chunking"),
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn verifying_proof_of_chunking_for_100_parties(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 100);
|
||||
|
||||
let polynomial = Polynomial::new_random(&mut rng, 67);
|
||||
let shares = receivers
|
||||
.keys()
|
||||
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let remote_share_key_pairs = shares
|
||||
.iter()
|
||||
.zip(receivers.values())
|
||||
.map(|(share, key)| (share, key))
|
||||
.collect::<Vec<_>>();
|
||||
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
|
||||
|
||||
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng);
|
||||
|
||||
let chunking_instance = proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
|
||||
let proof_of_chunking =
|
||||
ProofOfChunking::construct(&mut rng, chunking_instance, hazmat.r(), &shares)
|
||||
.expect("failed to construct proof of chunking");
|
||||
|
||||
c.bench_function("verifying proof of chunking for 100 parties", |b| {
|
||||
b.iter(|| {
|
||||
let chunking_instance =
|
||||
proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
|
||||
black_box(proof_of_chunking.verify(chunking_instance))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn creating_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 100);
|
||||
|
||||
let polynomial = Polynomial::new_random(&mut rng, 67);
|
||||
let shares = receivers
|
||||
.keys()
|
||||
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let remote_share_key_pairs = shares
|
||||
.iter()
|
||||
.zip(receivers.values())
|
||||
.map(|(share, key)| (share, key))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng);
|
||||
|
||||
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
|
||||
let combined_r = hazmat.combine_rs();
|
||||
let combined_rr = ciphertexts.combine_rs();
|
||||
let public_coefficients = polynomial.public_coefficients();
|
||||
|
||||
c.bench_function("creating proof of secret sharing for 100 parties", |b| {
|
||||
b.iter(|| {
|
||||
let sharing_instance = proof_sharing::Instance::new(
|
||||
&receivers,
|
||||
&public_coefficients,
|
||||
&combined_rr,
|
||||
&combined_ciphertexts,
|
||||
);
|
||||
black_box(
|
||||
ProofOfSecretSharing::construct(&mut rng, sharing_instance, &combined_r, &shares)
|
||||
.expect("failed to construct proof of secret sharing"),
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn verifying_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 100);
|
||||
|
||||
let polynomial = Polynomial::new_random(&mut rng, 67);
|
||||
let shares = receivers
|
||||
.keys()
|
||||
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let remote_share_key_pairs = shares
|
||||
.iter()
|
||||
.zip(receivers.values())
|
||||
.map(|(share, key)| (share, key))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, ¶ms, &mut rng);
|
||||
|
||||
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
|
||||
let combined_r = hazmat.combine_rs();
|
||||
let combined_rr = ciphertexts.combine_rs();
|
||||
let public_coefficients = polynomial.public_coefficients();
|
||||
let sharing_instance = proof_sharing::Instance::new(
|
||||
&receivers,
|
||||
&public_coefficients,
|
||||
&combined_rr,
|
||||
&combined_ciphertexts,
|
||||
);
|
||||
let proof_of_secret_sharing =
|
||||
ProofOfSecretSharing::construct(&mut rng, sharing_instance, &combined_r, &shares)
|
||||
.expect("failed to construct proof of secret sharing");
|
||||
|
||||
c.bench_function("verifying proof of secret sharing for 100 parties", |b| {
|
||||
b.iter(|| {
|
||||
let sharing_instance = proof_sharing::Instance::new(
|
||||
&receivers,
|
||||
&public_coefficients,
|
||||
&combined_rr,
|
||||
&combined_ciphertexts,
|
||||
);
|
||||
black_box(proof_of_secret_sharing.verify(sharing_instance))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn single_share_encryption(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let epoch = Epoch::new(2);
|
||||
let (_, pk) = keygen(¶ms, &mut rng);
|
||||
|
||||
let polynomial = Polynomial::new_random(&mut rng, 3);
|
||||
let share: Share = polynomial.evaluate_at(&Scalar::from(42)).into();
|
||||
|
||||
c.bench_function("single share encryption", |b| {
|
||||
b.iter(|| {
|
||||
black_box(encrypt_shares(
|
||||
&[(&share, pk.public_key())],
|
||||
epoch,
|
||||
¶ms,
|
||||
&mut rng,
|
||||
))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn share_encryption_100(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let (receivers, _) = prepare_keys(&mut rng, 100);
|
||||
let polynomial = Polynomial::new_random(&mut rng, 3);
|
||||
let shares = receivers
|
||||
.keys()
|
||||
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let remote_share_key_pairs = shares
|
||||
.iter()
|
||||
.zip(receivers.values())
|
||||
.map(|(share, key)| (share, key))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
c.bench_function("100 shares encryption", |b| {
|
||||
b.iter(|| {
|
||||
black_box(encrypt_shares(
|
||||
&remote_share_key_pairs,
|
||||
epoch,
|
||||
¶ms,
|
||||
&mut rng,
|
||||
))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn share_decryption(c: &mut Criterion) {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
let epoch = Epoch::new(2);
|
||||
let (mut dk, pk) = keygen(¶ms, &mut rng);
|
||||
|
||||
let polynomial = Polynomial::new_random(&mut rng, 3);
|
||||
let share: Share = polynomial.evaluate_at(&Scalar::from(42)).into();
|
||||
let (ciphertexts, _) = encrypt_shares(&[(&share, pk.public_key())], epoch, ¶ms, &mut rng);
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
c.bench_function("single share decryption", |b| {
|
||||
b.iter(|| black_box(decrypt_share(&dk, 0, &ciphertexts, epoch, None)))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
utils,
|
||||
precompute_default_bsgs_table,
|
||||
precomputing_g2_generator_for_miller_loop,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
dealings_creation,
|
||||
creating_dealing_for_3_parties,
|
||||
creating_dealing_for_20_parties,
|
||||
creating_dealing_for_100_parties,
|
||||
);
|
||||
|
||||
// note: in our setting each party will have to create at least 4 dealings (one per attribute in credential)
|
||||
// and verify 99 * 4 of them (4 from each other dealer)
|
||||
criterion_group!(
|
||||
dealings_verification,
|
||||
verifying_dealing_made_for_3_parties_and_recovering_share,
|
||||
verifying_dealing_made_for_20_parties_and_recovering_share,
|
||||
verifying_dealing_made_for_100_parties_and_recovering_share,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
proofs_of_knowledge,
|
||||
creating_proof_of_key_possession,
|
||||
verifying_proof_of_key_possession,
|
||||
creating_proof_of_chunking_for_100_parties,
|
||||
verifying_proof_of_chunking_for_100_parties,
|
||||
creating_proof_of_secret_sharing_for_100_parties,
|
||||
verifying_proof_of_secret_sharing_for_100_parties
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
encryption,
|
||||
single_share_encryption,
|
||||
share_encryption_100,
|
||||
share_decryption,
|
||||
);
|
||||
|
||||
criterion_main!(
|
||||
utils,
|
||||
dealings_creation,
|
||||
dealings_verification,
|
||||
proofs_of_knowledge,
|
||||
encryption
|
||||
);
|
||||
|
||||
// TODO: benchmark using affine vs projective representation throughout the crate
|
||||
// (when conversion / serialization / computation is involved)
|
||||
@@ -0,0 +1,772 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bte::keys::{DecryptionKey, PublicKey};
|
||||
use crate::bte::{Epoch, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUNKS, PAIRING_BASE};
|
||||
use crate::error::DkgError;
|
||||
use crate::utils::{combine_g1_chunks, combine_scalar_chunks, deserialize_g1, deserialize_g2};
|
||||
use crate::{Chunk, ChunkedShare, Share};
|
||||
use bls12_381::{G1Affine, G1Projective, G2Prepared, G2Projective, Gt, Scalar};
|
||||
use ff::Field;
|
||||
use group::{Curve, Group, GroupEncoding};
|
||||
use rand_core::RngCore;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Neg;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
pub struct Ciphertexts {
|
||||
pub rr: [G1Projective; NUM_CHUNKS],
|
||||
pub ss: [G1Projective; NUM_CHUNKS],
|
||||
pub zz: [G2Projective; NUM_CHUNKS],
|
||||
pub ciphertext_chunks: Vec<[G1Projective; NUM_CHUNKS]>,
|
||||
}
|
||||
|
||||
impl Ciphertexts {
|
||||
pub fn verify_integrity(&self, params: &Params, epoch: Epoch) -> bool {
|
||||
// if this checks fails it means the ciphertext is undefined as values
|
||||
// in `r`, `s` and `z` are meaningless since technically this ciphertext
|
||||
// has been created for 0 parties
|
||||
if self.ciphertext_chunks.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let g1_neg = G1Affine::generator().neg();
|
||||
let f = epoch
|
||||
.as_extended_tau(&self.rr, &self.ss, &self.ciphertext_chunks)
|
||||
.evaluate_f(params);
|
||||
|
||||
// we have to use `f` in up to `NUM_CHUNKS` pairings (if everything is valid),
|
||||
// so perform some precomputation on it
|
||||
let f_prepared = G2Prepared::from(f.to_affine());
|
||||
|
||||
// for each triple (R_i, S_i, Z_i) check whether e(g1, Z_i) == e(R_j, f) • e(S_i, h),
|
||||
// which is equivalent to checking whether e(R_j, f) • e(S_i, h) • e(g1, Z_i)^-1 == id
|
||||
// and due to bilinear property whether e(R_j, f) • e(S_i, h) • e(g1^-1, Z_i) == id
|
||||
for i in 0..self.rr.len() {
|
||||
let miller = bls12_381::multi_miller_loop(&[
|
||||
(&self.rr[i].to_affine(), &f_prepared),
|
||||
(&self.ss[i].to_affine(), ¶ms._h_prepared),
|
||||
(&g1_neg, &G2Prepared::from(self.zz[i].to_affine())),
|
||||
]);
|
||||
let res = miller.final_exponentiation();
|
||||
if !bool::from(res.is_identity()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn combine_rs(&self) -> G1Projective {
|
||||
combine_g1_chunks(&self.rr)
|
||||
}
|
||||
|
||||
// required for the purposes of the proof of secret sharing
|
||||
pub fn combine_ciphertexts(&self) -> Vec<G1Projective> {
|
||||
self.ciphertext_chunks
|
||||
.iter()
|
||||
.map(|share_ciphertext| combine_g1_chunks(share_ciphertext))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let num_receivers = self.ciphertext_chunks.len();
|
||||
|
||||
let mut bytes = Vec::with_capacity(NUM_CHUNKS * ((num_receivers + 2) * 48 + 96) + 4);
|
||||
for r_i in &self.rr {
|
||||
bytes.extend_from_slice(r_i.to_bytes().as_ref())
|
||||
}
|
||||
for s_i in &self.ss {
|
||||
bytes.extend_from_slice(s_i.to_bytes().as_ref())
|
||||
}
|
||||
for z_i in &self.zz {
|
||||
bytes.extend_from_slice(z_i.to_bytes().as_ref())
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&(num_receivers as u32).to_be_bytes());
|
||||
for c_i in &self.ciphertext_chunks {
|
||||
for c_ij in c_i {
|
||||
bytes.extend_from_slice(c_ij.to_bytes().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
|
||||
// at the very minimum we must have enough bytes for a single receiver
|
||||
if bytes.len() < NUM_CHUNKS * (3 * 48 + 96) + 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Ciphertexts",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let mut rr = Vec::with_capacity(NUM_CHUNKS);
|
||||
let mut ss = Vec::with_capacity(NUM_CHUNKS);
|
||||
let mut zz = Vec::with_capacity(NUM_CHUNKS);
|
||||
|
||||
let mut i = 0;
|
||||
for _ in 0..NUM_CHUNKS {
|
||||
rr.push(deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("Ciphertexts.r", "invalid curve point")
|
||||
})?);
|
||||
i += 48;
|
||||
}
|
||||
for _ in 0..NUM_CHUNKS {
|
||||
ss.push(deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("Ciphertexts.s", "invalid curve point")
|
||||
})?);
|
||||
i += 48;
|
||||
}
|
||||
for _ in 0..NUM_CHUNKS {
|
||||
zz.push(deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("Ciphertexts.z", "invalid curve point")
|
||||
})?);
|
||||
i += 96;
|
||||
}
|
||||
|
||||
let num_receivers = u32::from_be_bytes(bytes[i..i + 4].try_into().unwrap()) as usize;
|
||||
i += 4;
|
||||
|
||||
if bytes[i..].len() != num_receivers * NUM_CHUNKS * 48 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Ciphertexts",
|
||||
"invalid number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let mut ciphertext_chunks = Vec::with_capacity(num_receivers);
|
||||
|
||||
for _ in 0..num_receivers {
|
||||
let mut ci = Vec::with_capacity(NUM_CHUNKS);
|
||||
for _ in 0..NUM_CHUNKS {
|
||||
ci.push(deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"Ciphertexts.ciphertext_chunks",
|
||||
"invalid curve point",
|
||||
)
|
||||
})?);
|
||||
i += 48;
|
||||
}
|
||||
|
||||
// this unwrap is fine as we have exactly NUM_CHUNKS elements in each vector
|
||||
ciphertext_chunks.push(ci.try_into().unwrap())
|
||||
}
|
||||
|
||||
// and the same is true here, the unwraps are fine as we have exactly NUM_CHUNKS elements in each as required
|
||||
Ok(Ciphertexts {
|
||||
rr: rr.try_into().unwrap(),
|
||||
ss: ss.try_into().unwrap(),
|
||||
zz: zz.try_into().unwrap(),
|
||||
ciphertext_chunks,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
/// Randomness generated during ciphertext generation that is required for proofs of knowledge.
|
||||
/// It must be handled with extreme care as its misuse might help malicious parties to recover
|
||||
/// the underlying plaintext.
|
||||
pub struct HazmatRandomness {
|
||||
r: [Scalar; NUM_CHUNKS],
|
||||
s: [Scalar; NUM_CHUNKS],
|
||||
}
|
||||
|
||||
impl HazmatRandomness {
|
||||
pub fn r(&self) -> &[Scalar; NUM_CHUNKS] {
|
||||
&self.r
|
||||
}
|
||||
|
||||
pub fn s(&self) -> &[Scalar; NUM_CHUNKS] {
|
||||
&self.s
|
||||
}
|
||||
|
||||
pub fn combine_rs(&self) -> Scalar {
|
||||
combine_scalar_chunks(&self.r)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt_shares(
|
||||
shares: &[(&Share, &PublicKey)],
|
||||
epoch: Epoch,
|
||||
params: &Params,
|
||||
mut rng: impl RngCore,
|
||||
) -> (Ciphertexts, HazmatRandomness) {
|
||||
let g1 = G1Projective::generator();
|
||||
|
||||
let mut rand_rs = Vec::with_capacity(NUM_CHUNKS);
|
||||
let mut rand_ss = Vec::with_capacity(NUM_CHUNKS);
|
||||
let mut rr = Vec::with_capacity(NUM_CHUNKS);
|
||||
let mut ss = Vec::with_capacity(NUM_CHUNKS);
|
||||
|
||||
// generate relevant re-usable pseudorandom data
|
||||
for _ in 0..NUM_CHUNKS {
|
||||
let rand_r = Scalar::random(&mut rng);
|
||||
let rand_s = Scalar::random(&mut rng);
|
||||
|
||||
// g1^r
|
||||
let rr_i = g1 * rand_r;
|
||||
// g1^s
|
||||
let ss_i = g1 * rand_s;
|
||||
|
||||
rand_rs.push(rand_r);
|
||||
rand_ss.push(rand_s);
|
||||
|
||||
rr.push(rr_i);
|
||||
ss.push(ss_i);
|
||||
}
|
||||
|
||||
// produce per-chunk ciphertexts
|
||||
let mut cc = Vec::with_capacity(shares.len());
|
||||
|
||||
for (share, pk) in shares {
|
||||
let m = share.to_chunks();
|
||||
|
||||
let mut ci = Vec::with_capacity(NUM_CHUNKS);
|
||||
|
||||
for (j, chunk) in m.chunks.iter().enumerate() {
|
||||
// can't really have a more efficient implementation until https://github.com/zkcrypto/bls12_381/pull/70 is merged...
|
||||
let c = pk.0 * rand_rs[j] + g1 * Scalar::from(*chunk as u64);
|
||||
ci.push(c)
|
||||
}
|
||||
|
||||
// the conversion must succeed since we must have EXACTLY `NUM_CHUNKS` elements
|
||||
cc.push(ci.try_into().unwrap())
|
||||
}
|
||||
|
||||
// convert into arrays, note that the unwraps are fine as we have exactly `NUM_CHUNKS` elements in each vector
|
||||
let rr = rr.try_into().unwrap();
|
||||
let ss = ss.try_into().unwrap();
|
||||
|
||||
let f = epoch.as_extended_tau(&rr, &ss, &cc).evaluate_f(params);
|
||||
|
||||
let mut zz = Vec::with_capacity(NUM_CHUNKS);
|
||||
for i in 0..NUM_CHUNKS {
|
||||
zz.push(f * rand_rs[i] + params.h * rand_ss[i]);
|
||||
}
|
||||
|
||||
// the conversions here must also succeed since the other vecs also have `NUM_CHUNKS` elements
|
||||
(
|
||||
Ciphertexts {
|
||||
rr,
|
||||
ss,
|
||||
zz: zz.try_into().unwrap(),
|
||||
ciphertext_chunks: cc,
|
||||
},
|
||||
HazmatRandomness {
|
||||
r: rand_rs.try_into().unwrap(),
|
||||
s: rand_ss.try_into().unwrap(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn decrypt_share(
|
||||
dk: &DecryptionKey,
|
||||
// in the case of multiple receivers, specifies which index of ciphertext chunks should be used
|
||||
i: usize,
|
||||
ciphertext: &Ciphertexts,
|
||||
epoch: Epoch,
|
||||
lookup_table: Option<&BabyStepGiantStepLookup>,
|
||||
) -> Result<Share, DkgError> {
|
||||
let mut plaintext = ChunkedShare::default();
|
||||
|
||||
let decryption_node = dk.try_get_compatible_node(epoch)?;
|
||||
let extended_tau = epoch.as_extended_tau(
|
||||
&ciphertext.rr,
|
||||
&ciphertext.ss,
|
||||
&ciphertext.ciphertext_chunks,
|
||||
);
|
||||
|
||||
if i >= ciphertext.ciphertext_chunks.len() {
|
||||
return Err(DkgError::UnavailableCiphertext(i));
|
||||
}
|
||||
|
||||
let height = decryption_node.tau.height();
|
||||
let b_neg = decryption_node
|
||||
.ds
|
||||
.iter()
|
||||
.chain(decryption_node.dh.iter())
|
||||
.zip(extended_tau.0.iter().by_vals().skip(height))
|
||||
.filter(|(_, i)| *i)
|
||||
.map(|(d_i, _)| d_i)
|
||||
.fold(decryption_node.b, |acc, d_i| acc + d_i)
|
||||
.neg()
|
||||
.to_affine();
|
||||
|
||||
let e_neg = decryption_node.e.neg().to_affine();
|
||||
|
||||
for j in 0..NUM_CHUNKS {
|
||||
let rr_j = &ciphertext.rr[j];
|
||||
let ss_j = &ciphertext.ss[j];
|
||||
let zz_j = ciphertext.zz[j].to_affine();
|
||||
let cc_ij = &ciphertext.ciphertext_chunks[i][j];
|
||||
|
||||
let miller = bls12_381::multi_miller_loop(&[
|
||||
(&cc_ij.to_affine(), &G2_GENERATOR_PREPARED),
|
||||
(&rr_j.to_affine(), &G2Prepared::from(b_neg)),
|
||||
(&decryption_node.a.to_affine(), &G2Prepared::from(zz_j)),
|
||||
(&ss_j.to_affine(), &G2Prepared::from(e_neg)),
|
||||
]);
|
||||
let m = miller.final_exponentiation();
|
||||
|
||||
plaintext.chunks[j] = baby_step_giant_step(&m, &PAIRING_BASE, lookup_table)?;
|
||||
}
|
||||
|
||||
plaintext.try_into()
|
||||
}
|
||||
|
||||
pub struct BabyStepGiantStepLookup {
|
||||
base: Gt,
|
||||
m: Chunk,
|
||||
lookup: HashMap<[u8; 576], Chunk>,
|
||||
}
|
||||
|
||||
impl BabyStepGiantStepLookup {
|
||||
pub fn precompute(base: &Gt) -> Self {
|
||||
let mut lookup = HashMap::new();
|
||||
let mut g = Gt::identity();
|
||||
|
||||
// 1. m ← Ceiling(√n)
|
||||
let m = (CHUNK_SIZE as f32).sqrt().ceil() as Chunk;
|
||||
|
||||
// 2. For all j where 0 ≤ j < m:
|
||||
for j in 0..m {
|
||||
// Compute α^j and store the pair (j, α^j) in a table.
|
||||
lookup.insert(g.to_uncompressed(), j);
|
||||
g += base;
|
||||
}
|
||||
|
||||
BabyStepGiantStepLookup {
|
||||
base: *base,
|
||||
m,
|
||||
lookup,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_solve(&self, target: &Gt) -> Result<Chunk, DkgError> {
|
||||
// 3. Compute α^{−m}
|
||||
let m_neg = Scalar::from(self.m as u64).neg();
|
||||
let alpha_m = self.base * m_neg;
|
||||
|
||||
// 4. γ ← β. (set γ = β)
|
||||
let mut gamma = *target;
|
||||
|
||||
// 5. For all i where 0 ≤ i < m:
|
||||
for i in 0..self.m {
|
||||
// 1. Check to see if γ is the second component (αj) of any pair in the table.
|
||||
if let Some(j) = self.lookup.get(&gamma.to_uncompressed()) {
|
||||
// 2. If so, return im + j.
|
||||
return Ok(i * self.m + j);
|
||||
} else {
|
||||
// 3. If not, γ ← γ • α^{−m}.
|
||||
gamma += alpha_m;
|
||||
}
|
||||
}
|
||||
|
||||
Err(DkgError::UnsolvableDiscreteLog)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BabyStepGiantStepLookup {
|
||||
fn default() -> Self {
|
||||
BabyStepGiantStepLookup::precompute(&PAIRING_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to solve the discrete log problem g^m, where g is in the Gt group and
|
||||
/// m should be within the [0, CHUNK_MAX] range.
|
||||
///
|
||||
/// The implementation follows the following algorithm: https://en.wikipedia.org/wiki/Baby-step_giant-step#The_algorithm
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `target`: the result of the exponentiation, M in M = g^m,
|
||||
/// * `base`: the base used for exponentiation, g in M = g^m
|
||||
/// * `lookup_table`: precomputed table containing (j, α^j) pairs
|
||||
pub fn baby_step_giant_step(
|
||||
target: &Gt,
|
||||
base: &Gt,
|
||||
lookup_table: Option<&BabyStepGiantStepLookup>,
|
||||
) -> Result<Chunk, DkgError> {
|
||||
if let Some(lookup_table) = lookup_table {
|
||||
// compute expected m to make sure the provided lookup is valid
|
||||
let m = (CHUNK_SIZE as f32).sqrt().ceil() as Chunk;
|
||||
|
||||
if &lookup_table.base != base || lookup_table.lookup.len() != m as usize {
|
||||
return Err(DkgError::MismatchedLookupTable);
|
||||
}
|
||||
|
||||
lookup_table.try_solve(target)
|
||||
} else {
|
||||
BabyStepGiantStepLookup::precompute(base).try_solve(target)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bte::{keygen, setup, DEFAULT_BSGS_TABLE};
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
fn verify_hazmat_rand(ciphertext: &Ciphertexts, randomness: &HazmatRandomness) {
|
||||
let g1 = G1Projective::generator();
|
||||
|
||||
for i in 0..ciphertext.rr.len() {
|
||||
assert_eq!(ciphertext.rr[i], g1 * randomness.r[i]);
|
||||
assert_eq!(ciphertext.ss[i], g1 * randomness.s[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn baby_giant_100_without_table() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
for i in 0u64..100 {
|
||||
let base = Gt::random(&mut rng);
|
||||
let x = (rng.next_u64() + i) % CHUNK_SIZE as u64;
|
||||
let target = base * Scalar::from(x);
|
||||
|
||||
assert_eq!(
|
||||
baby_step_giant_step(&target, &base, None).unwrap(),
|
||||
x as Chunk
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn baby_giant_100_with_table() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let base = Gt::random(&mut rng);
|
||||
let lookup_table = BabyStepGiantStepLookup::precompute(&base);
|
||||
let table = Some(&lookup_table);
|
||||
|
||||
for i in 0u64..100 {
|
||||
let x = (rng.next_u64() + i) % CHUNK_SIZE as u64;
|
||||
let target = base * Scalar::from(x);
|
||||
|
||||
assert_eq!(
|
||||
baby_step_giant_step(&target, &base, table).unwrap(),
|
||||
x as Chunk
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn share_decryption_20() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let (decryption_key1, public_key1) = keygen(¶ms, &mut rng);
|
||||
let (decryption_key2, public_key2) = keygen(¶ms, &mut rng);
|
||||
let epoch = Epoch::new(0);
|
||||
|
||||
let lookup_table = &DEFAULT_BSGS_TABLE;
|
||||
|
||||
for _ in 0..10 {
|
||||
let m1 = Share::random(&mut rng);
|
||||
let m2 = Share::random(&mut rng);
|
||||
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
|
||||
|
||||
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext, &hazmat);
|
||||
|
||||
let recovered1 =
|
||||
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
|
||||
let recovered2 =
|
||||
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
|
||||
assert_eq!(m1, recovered1);
|
||||
assert_eq!(m2, recovered2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn share_encryption_under_nonzero_epoch() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let (mut decryption_key1, public_key1) = keygen(¶ms, &mut rng);
|
||||
let (mut decryption_key2, public_key2) = keygen(¶ms, &mut rng);
|
||||
let epoch = Epoch::new(12345);
|
||||
decryption_key1
|
||||
.try_update_to(epoch, ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
decryption_key2
|
||||
.try_update_to(epoch, ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
|
||||
let lookup_table = &DEFAULT_BSGS_TABLE;
|
||||
|
||||
for _ in 0..10 {
|
||||
let m1 = Share::random(&mut rng);
|
||||
let m2 = Share::random(&mut rng);
|
||||
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
|
||||
|
||||
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext, &hazmat);
|
||||
|
||||
let recovered1 =
|
||||
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
|
||||
let recovered2 =
|
||||
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
|
||||
assert_eq!(m1, recovered1);
|
||||
assert_eq!(m2, recovered2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decryption_with_root_key() {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let (root_key, public_key) = keygen(¶ms, &mut rng);
|
||||
|
||||
let share = Share::random(&mut rng);
|
||||
|
||||
let epoch0 = Epoch::new(0);
|
||||
let epoch42 = Epoch::new(42);
|
||||
let epoch_big = Epoch::new(3292547435);
|
||||
|
||||
let (ciphertext1, hazmat1) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch0, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext1, &hazmat1);
|
||||
|
||||
let (ciphertext2, hazmat2) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch42, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext2, &hazmat2);
|
||||
|
||||
let (ciphertext3, hazmat3) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch_big, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext3, &hazmat3);
|
||||
|
||||
let recovered1 = decrypt_share(&root_key, 0, &ciphertext1, epoch0, None).unwrap();
|
||||
let recovered2 = decrypt_share(&root_key, 0, &ciphertext2, epoch42, None).unwrap();
|
||||
let recovered3 = decrypt_share(&root_key, 0, &ciphertext3, epoch_big, None).unwrap();
|
||||
|
||||
assert_eq!(share, recovered1);
|
||||
assert_eq!(share, recovered2);
|
||||
assert_eq!(share, recovered3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_and_decrypt_10() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let (mut decryption_key, public_key) = keygen(¶ms, &mut rng);
|
||||
|
||||
for epoch_value in 0..10 {
|
||||
let epoch = Epoch::new(epoch_value);
|
||||
let share = Share::random(&mut rng);
|
||||
decryption_key
|
||||
.try_update_to(epoch, ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
|
||||
let (ciphertext, hazmat) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext, &hazmat);
|
||||
|
||||
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
|
||||
assert_eq!(share, recovered);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reblinding_node_doesnt_affect_decryption() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let (mut decryption_key, public_key) = keygen(¶ms, &mut rng);
|
||||
|
||||
let epoch = Epoch::new(12345);
|
||||
decryption_key
|
||||
.try_update_to(epoch, ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
for node in decryption_key.nodes.iter_mut() {
|
||||
node.reblind(¶ms, &mut rng);
|
||||
}
|
||||
let share = Share::random(&mut rng);
|
||||
|
||||
let (ciphertext, hazmat) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext, &hazmat);
|
||||
|
||||
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
|
||||
assert_eq!(share, recovered);
|
||||
|
||||
// attempt to update the key again so we have to derive fresh nodes using previous reblinded results
|
||||
let epoch2 = Epoch::new(67890);
|
||||
decryption_key
|
||||
.try_update_to(epoch2, ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
for node in decryption_key.nodes.iter_mut() {
|
||||
node.reblind(¶ms, &mut rng);
|
||||
}
|
||||
let share2 = Share::random(&mut rng);
|
||||
|
||||
let (ciphertext, hazmat) =
|
||||
encrypt_shares(&[(&share2, &public_key.key)], epoch2, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext, &hazmat);
|
||||
|
||||
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch2, None).unwrap();
|
||||
assert_eq!(share2, recovered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertext_integrity_check_passes_for_valid_data() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (mut dk, public_key) = keygen(¶ms, &mut rng);
|
||||
let epoch = Epoch::new(1);
|
||||
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
let share = Share::random(&mut rng);
|
||||
let (ciphertext, _) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng);
|
||||
assert!(ciphertext.verify_integrity(¶ms, epoch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertext_integrity_check_passes_fails_for_malformed_data() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (mut dk, public_key) = keygen(¶ms, &mut rng);
|
||||
let epoch = Epoch::new(1);
|
||||
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
let share = Share::random(&mut rng);
|
||||
let (ciphertext, _) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng);
|
||||
|
||||
let mut bad_cipher1 = ciphertext.clone();
|
||||
bad_cipher1.rr[4] = G1Projective::generator();
|
||||
assert!(!bad_cipher1.verify_integrity(¶ms, epoch));
|
||||
|
||||
let mut bad_cipher2 = ciphertext.clone();
|
||||
bad_cipher2.ss[4] = G1Projective::generator();
|
||||
assert!(!bad_cipher2.verify_integrity(¶ms, epoch));
|
||||
|
||||
let mut bad_cipher3 = ciphertext;
|
||||
bad_cipher3.zz[4] = G2Projective::generator();
|
||||
assert!(!bad_cipher3.verify_integrity(¶ms, epoch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertext_integrity_check_passes_fails_for_wrong_epoch() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (mut dk, public_key) = keygen(¶ms, &mut rng);
|
||||
let epoch = Epoch::new(1);
|
||||
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
let share = Share::random(&mut rng);
|
||||
let (ciphertext, _) =
|
||||
encrypt_shares(&[(&share, &public_key.key)], epoch, ¶ms, &mut rng);
|
||||
|
||||
let another_epoch = Epoch::new(2);
|
||||
assert!(!ciphertext.verify_integrity(¶ms, another_epoch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertext_combining() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let nodes = 3;
|
||||
|
||||
let mut shares = Vec::new();
|
||||
let mut public_keys = Vec::new();
|
||||
for _ in 0..nodes {
|
||||
shares.push(Share::random(&mut rng));
|
||||
let (_, pk) = keygen(¶ms, &mut rng);
|
||||
public_keys.push(*pk.public_key());
|
||||
}
|
||||
|
||||
let refs = shares.iter().zip(public_keys.iter()).collect::<Vec<_>>();
|
||||
let (ciphertext, hazmat) = encrypt_shares(&refs, Epoch::new(42), ¶ms, &mut rng);
|
||||
|
||||
let combined_r = combine_scalar_chunks(hazmat.r());
|
||||
let combined_rr = ciphertext.combine_rs();
|
||||
let combined_ciphertexts = ciphertext.combine_ciphertexts();
|
||||
|
||||
let g1 = G1Projective::generator();
|
||||
for i in 0..nodes {
|
||||
let expected = public_keys[i].0 * combined_r + g1 * shares[i].0;
|
||||
assert_eq!(expected, combined_ciphertexts[i]);
|
||||
assert_eq!(combined_rr, g1 * combined_r);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ciphertexts_roundtrip() {
|
||||
fn random_ciphertexts(mut rng: impl RngCore, num_receivers: usize) -> Ciphertexts {
|
||||
Ciphertexts {
|
||||
rr: (0..NUM_CHUNKS)
|
||||
.map(|_| G1Projective::random(&mut rng))
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
ss: (0..NUM_CHUNKS)
|
||||
.map(|_| G1Projective::random(&mut rng))
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
zz: (0..NUM_CHUNKS)
|
||||
.map(|_| G2Projective::random(&mut rng))
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
ciphertext_chunks: (0..num_receivers)
|
||||
.map(|_| {
|
||||
(0..NUM_CHUNKS)
|
||||
.map(|_| G1Projective::random(&mut rng))
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let good_ciphertexts = vec![
|
||||
random_ciphertexts(&mut rng, 1),
|
||||
random_ciphertexts(&mut rng, 2),
|
||||
random_ciphertexts(&mut rng, 10),
|
||||
];
|
||||
|
||||
for ciphertexts in &good_ciphertexts {
|
||||
let bytes = ciphertexts.to_bytes();
|
||||
let recovered = Ciphertexts::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(ciphertexts, &recovered);
|
||||
}
|
||||
|
||||
// ciphertext for 0 receivers is invalid by default
|
||||
let ciphertexts = random_ciphertexts(&mut rng, 0);
|
||||
let bytes = ciphertexts.to_bytes();
|
||||
assert!(Ciphertexts::try_from_bytes(&bytes).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,875 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bte::proof_discrete_log::ProofOfDiscreteLog;
|
||||
use crate::bte::{Epoch, Params, Tau};
|
||||
use crate::error::DkgError;
|
||||
use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar};
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use ff::Field;
|
||||
use group::GroupEncoding;
|
||||
use rand_core::RngCore;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
pub(crate) struct Node {
|
||||
pub(crate) tau: Tau,
|
||||
|
||||
// g1^rho
|
||||
pub(crate) a: G1Projective,
|
||||
|
||||
// g2^x
|
||||
pub(crate) b: G2Projective,
|
||||
|
||||
// f_i^rho, up to lambda_t elements
|
||||
pub(crate) ds: Vec<G2Projective>,
|
||||
|
||||
// fh_i^rho, always lambda_h elements
|
||||
pub(crate) dh: Vec<G2Projective>,
|
||||
|
||||
// h^rho
|
||||
pub(crate) e: G2Projective,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn new_root(
|
||||
a: G1Projective,
|
||||
b: G2Projective,
|
||||
ds: Vec<G2Projective>,
|
||||
dh: Vec<G2Projective>,
|
||||
e: G2Projective,
|
||||
) -> Self {
|
||||
Node {
|
||||
tau: Tau::new_root(),
|
||||
a,
|
||||
b,
|
||||
ds,
|
||||
dh,
|
||||
e,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_root(&self) -> bool {
|
||||
self.tau.0.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn reblind(&mut self, params: &Params, mut rng: impl RngCore) {
|
||||
let delta = Scalar::random(&mut rng);
|
||||
self.a += G1Projective::generator() * delta;
|
||||
|
||||
// TODO: or do we have to do full tau evaluation here?
|
||||
self.b += self.tau.evaluate_partial_f(params) * delta;
|
||||
self.ds
|
||||
.iter_mut()
|
||||
.zip(params.fs.iter().skip(self.tau.height()))
|
||||
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
|
||||
self.dh
|
||||
.iter_mut()
|
||||
.zip(params.fh.iter())
|
||||
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
|
||||
|
||||
self.e += params.h * delta;
|
||||
}
|
||||
|
||||
// note: it's unsafe to use this method outside `try_update_to` as
|
||||
// we have guaranteed there that `self` is parent of the target
|
||||
// and that `self.tau != target_tau`
|
||||
/// Given `self` with `Tau1` and `target_tau` with `Tau2`, such that `Tau1` prefixes `Tau2`,
|
||||
/// i.e. `Tau2 == Tau1 || SUFFIX`, and `Tau2` is a leaf node, derive all required crypto material
|
||||
/// for its construction.
|
||||
fn derive_target_child_with_partials(
|
||||
&self,
|
||||
params: &Params,
|
||||
target_tau: Tau,
|
||||
partial_b: &G2Projective,
|
||||
partial_f: &G2Projective,
|
||||
mut rng: impl RngCore,
|
||||
) -> Self {
|
||||
debug_assert!(self.tau.is_parent_of(&target_tau));
|
||||
debug_assert_ne!(self.tau, target_tau);
|
||||
|
||||
let delta = Scalar::random(&mut rng);
|
||||
let a = self.a + G1Projective::generator() * delta;
|
||||
let b = partial_b + partial_f * delta;
|
||||
let ds = self
|
||||
.ds
|
||||
.iter()
|
||||
.zip(params.fs.iter())
|
||||
.skip(target_tau.height())
|
||||
.map(|(d_i, f_i)| d_i + f_i * delta)
|
||||
.collect();
|
||||
let dh = self
|
||||
.dh
|
||||
.iter()
|
||||
.zip(params.fh.iter())
|
||||
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
|
||||
.collect();
|
||||
let e = self.e + params.h * delta;
|
||||
|
||||
Node {
|
||||
tau: target_tau,
|
||||
a,
|
||||
b,
|
||||
ds,
|
||||
dh,
|
||||
e,
|
||||
}
|
||||
}
|
||||
|
||||
// note: it's unsafe to use this method outside `try_update_to` as
|
||||
// we have guaranteed there that `self` is parent of the target
|
||||
// and that `self.tau != target_tau`
|
||||
/// Given `self` with `Tau1` and `most_direct_parent` with `Tau2`, such that `Tau1` prefixes `Tau2`,
|
||||
/// i.e. `Tau2 == Tau1 || SUFFIX`, derive node with `Tau3 = Tau2 || 1`
|
||||
fn derive_right_nonfinal_child_of_with_partials(
|
||||
&self,
|
||||
params: &Params,
|
||||
most_direct_parent: Tau,
|
||||
partial_b: &G2Projective,
|
||||
partial_f: &G2Projective,
|
||||
mut rng: impl RngCore,
|
||||
) -> Self {
|
||||
let right_branch = most_direct_parent.right_child();
|
||||
|
||||
debug_assert!(self.tau.is_parent_of(&most_direct_parent));
|
||||
debug_assert!(self.tau.is_parent_of(&right_branch));
|
||||
debug_assert_ne!(self.tau, right_branch);
|
||||
|
||||
// n is height difference between self and the child
|
||||
let n = right_branch.height() - self.tau.height();
|
||||
|
||||
// i is the index of the last bit we just added
|
||||
let i = right_branch.height() - 1;
|
||||
|
||||
let delta = Scalar::random(&mut rng);
|
||||
let a = self.a + G1Projective::generator() * delta;
|
||||
let d0 = self.ds[n - 1];
|
||||
let b = partial_b + d0 + (partial_f + params.fs[i]) * delta;
|
||||
let ds = self
|
||||
.ds
|
||||
.iter()
|
||||
.skip(n)
|
||||
.zip(params.fs.iter().skip(right_branch.height()))
|
||||
.map(|(d_i, f_i)| d_i + f_i * delta)
|
||||
.collect();
|
||||
let dh = self
|
||||
.dh
|
||||
.iter()
|
||||
.zip(params.fh.iter())
|
||||
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
|
||||
.collect();
|
||||
|
||||
let e = self.e + params.h * delta;
|
||||
|
||||
Node {
|
||||
tau: right_branch,
|
||||
a,
|
||||
b,
|
||||
ds,
|
||||
dh,
|
||||
e,
|
||||
}
|
||||
}
|
||||
|
||||
// tau_bytes_len || tau || a || b || len_ds || ds || len_dh || dh || e
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let g1_elements = 1;
|
||||
let g2_elements = self.ds.len() + self.dh.len() + 2;
|
||||
|
||||
let tau_bytes = self.tau.to_bytes();
|
||||
|
||||
// the extra 12 comes from the triple u32 we use for encoding lengths of tau, ds and dh
|
||||
let mut bytes =
|
||||
Vec::with_capacity(tau_bytes.len() + g1_elements * 48 + g2_elements * 96 + 12);
|
||||
|
||||
bytes.extend_from_slice(&((tau_bytes.len() as u32).to_be_bytes()));
|
||||
bytes.extend_from_slice(&tau_bytes);
|
||||
bytes.extend_from_slice(self.a.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.b.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(&((self.ds.len() as u32).to_be_bytes()));
|
||||
for d_i in &self.ds {
|
||||
bytes.extend_from_slice(d_i.to_bytes().as_ref());
|
||||
}
|
||||
bytes.extend_from_slice(&((self.dh.len() as u32).to_be_bytes()));
|
||||
for dh_i in &self.dh {
|
||||
bytes.extend_from_slice(dh_i.to_bytes().as_ref());
|
||||
}
|
||||
bytes.extend_from_slice(self.e.to_bytes().as_ref());
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
|
||||
// at the very least we require bytes for:
|
||||
// - tau_len ( 4 )
|
||||
// - tau ( could be 0 for root node )
|
||||
// - a ( 48 )
|
||||
// - b ( 96 )
|
||||
// - length indication of ds ( 4 )
|
||||
// - length indication of dh ( 4 )
|
||||
// - e ( 96 )
|
||||
if bytes.len() < 4 + 48 + 96 + 4 + 4 + 96 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Node",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let tau_len = u32::from_be_bytes((&bytes[..4]).try_into().unwrap()) as usize;
|
||||
let mut i = 4;
|
||||
|
||||
let tau = Tau::try_from_bytes(&bytes[i..i + tau_len])?;
|
||||
i += tau_len;
|
||||
|
||||
// perform another length check to account for bytes consumed by tau
|
||||
if bytes[i..].len() < 48 + 96 + 4 + 4 + 96 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Node",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let a = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("Node.a", "invalid curve point")
|
||||
})?;
|
||||
i += 48;
|
||||
|
||||
let b = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("Node.b", "invalid curve point")
|
||||
})?;
|
||||
i += 96;
|
||||
|
||||
let ds_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
|
||||
i += 4;
|
||||
|
||||
if bytes[i..].len() < ds_len * 96 + 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Node",
|
||||
"insufficient number of bytes provided (ds)",
|
||||
));
|
||||
}
|
||||
|
||||
let mut ds = Vec::with_capacity(ds_len);
|
||||
for j in 0..ds_len {
|
||||
let d_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
format!("Node.ds_{}", j),
|
||||
"invalid curve point",
|
||||
)
|
||||
})?;
|
||||
|
||||
ds.push(d_i);
|
||||
i += 96;
|
||||
}
|
||||
|
||||
let dh_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
|
||||
i += 4;
|
||||
|
||||
if bytes[i..].len() != (dh_len + 1) * 96 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Node",
|
||||
"insufficient number of bytes provided (dh)",
|
||||
));
|
||||
}
|
||||
|
||||
let mut dh = Vec::with_capacity(dh_len);
|
||||
for j in 0..dh_len {
|
||||
let dh_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
format!("Node.dh_{}", j),
|
||||
"invalid curve point",
|
||||
)
|
||||
})?;
|
||||
|
||||
dh.push(dh_i);
|
||||
i += 96;
|
||||
}
|
||||
|
||||
let e = deserialize_g2(&bytes[i..]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("Node.h", "invalid curve point")
|
||||
})?;
|
||||
|
||||
Ok(Node {
|
||||
tau,
|
||||
a,
|
||||
b,
|
||||
ds,
|
||||
dh,
|
||||
e,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// produces public key and a decryption key for the root of the tree
|
||||
pub fn keygen(params: &Params, mut rng: impl RngCore) -> (DecryptionKey, PublicKeyWithProof) {
|
||||
let g1 = G1Projective::generator();
|
||||
let g2 = G2Projective::generator();
|
||||
|
||||
let mut x = Scalar::random(&mut rng);
|
||||
let y = g1 * x;
|
||||
|
||||
let proof = ProofOfDiscreteLog::construct(&mut rng, &y, &x);
|
||||
|
||||
let mut rho = Scalar::random(&mut rng);
|
||||
|
||||
let a = g1 * rho;
|
||||
let b = g2 * x + params.f0 * rho;
|
||||
|
||||
let ds = params.fs.iter().map(|f_i| f_i * rho).collect();
|
||||
let dh = params.fh.iter().map(|fh_i| fh_i * rho).collect();
|
||||
let e = params.h * rho;
|
||||
|
||||
let dk = DecryptionKey::new_root(Node::new_root(a, b, ds, dh, e));
|
||||
|
||||
let public_key = PublicKey(y);
|
||||
let key_with_proof = PublicKeyWithProof {
|
||||
key: public_key,
|
||||
proof,
|
||||
};
|
||||
|
||||
x.zeroize();
|
||||
rho.zeroize();
|
||||
|
||||
(dk, key_with_proof)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct PublicKey(pub(crate) G1Projective);
|
||||
|
||||
impl PublicKey {
|
||||
pub fn verify(&self, proof: &ProofOfDiscreteLog) -> bool {
|
||||
proof.verify(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct PublicKeyWithProof {
|
||||
pub(crate) key: PublicKey,
|
||||
pub(crate) proof: ProofOfDiscreteLog,
|
||||
}
|
||||
|
||||
impl PublicKeyWithProof {
|
||||
pub fn verify(&self) -> bool {
|
||||
self.key.verify(&self.proof)
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> &PublicKey {
|
||||
&self.key
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
// we have 2 G1 elements and 1 Scalar
|
||||
let mut bytes = Vec::with_capacity(2 * 48 + 32);
|
||||
bytes.extend_from_slice(self.key.0.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.proof.rand_commitment.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.proof.response.to_bytes().as_ref());
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
|
||||
if bytes.len() != 2 * 48 + 32 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"PublicKeyWithProof",
|
||||
"provided bytes had invalid length",
|
||||
));
|
||||
}
|
||||
|
||||
let y_bytes = &bytes[..48];
|
||||
let commitment_bytes = &bytes[48..96];
|
||||
let response_bytes = &bytes[96..];
|
||||
|
||||
let y = deserialize_g1(y_bytes).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("PublicKeyWithProof.key.0", "invalid curve point")
|
||||
})?;
|
||||
|
||||
let rand_commitment = deserialize_g1(commitment_bytes).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"PublicKeyWithProof.proof.rand_commitment",
|
||||
"invalid curve point",
|
||||
)
|
||||
})?;
|
||||
|
||||
let response = deserialize_scalar(response_bytes).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"PublicKeyWithProof.proof.response",
|
||||
"invalid scalar",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(PublicKeyWithProof {
|
||||
key: PublicKey(y),
|
||||
proof: ProofOfDiscreteLog {
|
||||
rand_commitment,
|
||||
response,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct DecryptionKey {
|
||||
// note that the nodes are ordered from "right" to "left"
|
||||
pub(crate) nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
impl DecryptionKey {
|
||||
fn new_root(root_node: Node) -> Self {
|
||||
DecryptionKey {
|
||||
nodes: vec![root_node],
|
||||
}
|
||||
}
|
||||
|
||||
fn current(&self) -> Result<&Node, DkgError> {
|
||||
// we must have at least a single node, otherwise we have a malformed key
|
||||
self.nodes.last().ok_or(DkgError::MalformedDecryptionKey)
|
||||
}
|
||||
|
||||
pub fn current_epoch(&self, params: &Params) -> Result<Option<Epoch>, DkgError> {
|
||||
let current_node = self.current()?;
|
||||
if current_node.is_root() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Epoch::try_from_tau(¤t_node.tau, params).map(Option::Some)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_compatible_node(&self, epoch: Epoch) -> Result<&Node, DkgError> {
|
||||
let tau = epoch.as_tau();
|
||||
self.nodes
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|node| node.tau.is_parent_of(&tau))
|
||||
.ok_or(DkgError::ExpiredKey)
|
||||
}
|
||||
|
||||
pub fn try_update_to_next_epoch(
|
||||
&mut self,
|
||||
params: &Params,
|
||||
mut rng: impl RngCore,
|
||||
) -> Result<(), DkgError> {
|
||||
if self.nodes.is_empty() {
|
||||
return Err(DkgError::MalformedDecryptionKey);
|
||||
}
|
||||
|
||||
let mut target_epoch = Epoch::new(0);
|
||||
if self.nodes.len() == 1 && self.nodes[0].is_root() {
|
||||
return self.try_update_to(target_epoch, params, &mut rng);
|
||||
}
|
||||
|
||||
// unwrap is fine as we have asserted self.nodes is not empty
|
||||
self.nodes.pop().unwrap();
|
||||
|
||||
if let Some(tail) = self.nodes.last() {
|
||||
target_epoch = tail.tau.lowest_valid_epoch_child(params)?;
|
||||
} else {
|
||||
// essentially our key consisted of only a single node and it wasn't a root,
|
||||
// so either it was malformed or we somehow reached the final epoch and wanted to update
|
||||
// beyond that. Either way, update to l + 1 is impossible
|
||||
return Err(DkgError::MalformedDecryptionKey);
|
||||
}
|
||||
|
||||
self.try_update_to(target_epoch, params, &mut rng)
|
||||
}
|
||||
|
||||
/// Attempts to update `self` to the provided `epoch`. If the update is not possible,
|
||||
/// because the target was in the past or the key is malformed, an error is returned.
|
||||
///
|
||||
/// Note that this method mutates the key in place and if the original key was malformed,
|
||||
/// there are no guarantees about its internal state post-call.
|
||||
pub fn try_update_to(
|
||||
&mut self,
|
||||
target_epoch: Epoch,
|
||||
params: &Params,
|
||||
mut rng: impl RngCore,
|
||||
) -> Result<(), DkgError> {
|
||||
if self.nodes.is_empty() {
|
||||
// somehow we have an empty decryption key
|
||||
return Err(DkgError::MalformedDecryptionKey);
|
||||
}
|
||||
|
||||
// makes it easier to work with since we will be generating non-leaf nodes
|
||||
let target_tau = target_epoch.as_tau();
|
||||
let current_tau = &self.current()?.tau;
|
||||
|
||||
if current_tau == &target_tau {
|
||||
// our key is already updated to the target
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if current_tau > &target_tau {
|
||||
// we cannot derive keys for past epochs
|
||||
return Err(DkgError::TargetEpochUpdateInThePast);
|
||||
}
|
||||
|
||||
// drop the nodes that are no longer required and get the most direct parent for the target epoch available
|
||||
let mut parent = loop {
|
||||
// if pop() fails the key is malformed since we checked that the target_epoch > current_epoch,
|
||||
// hence the update should have been possible
|
||||
let tail = self.nodes.pop().ok_or(DkgError::MalformedDecryptionKey)?;
|
||||
if tail.tau.is_parent_of(&target_tau) {
|
||||
break tail;
|
||||
}
|
||||
};
|
||||
|
||||
// essentially the case of updating epoch n to n + 1, where n is even;
|
||||
// in that case the last two nodes are [..., epoch_{n+1}, epoch_n]
|
||||
// so we just have to reblind the n+1 node and we're done
|
||||
if parent.tau == target_tau {
|
||||
parent.reblind(params, &mut rng);
|
||||
self.nodes.push(parent);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// accumulators, note that the previous elements have already been included by the parent,
|
||||
// i.e. for example for parent at height l <= n, b = g2^x * f0^rho * d1^{tau_1} * ... * dl^{tau_l}
|
||||
// new_b_accumulator = b * d1^{tau_1} * d2^{tau_2} * ... * dn^{tau_n}
|
||||
// new_f_accumulator = f0 * f1^{tau_1} * f2^{tau_2} * ... * fn^{tau_n} (up to lambda_t)
|
||||
let mut new_b_accumulator = parent.b;
|
||||
let mut new_f_accumulator = parent.tau.evaluate_partial_f(params);
|
||||
|
||||
let parent_height = parent.tau.height();
|
||||
|
||||
// path from the parent to the child
|
||||
for (n, bit) in target_tau
|
||||
.0
|
||||
.iter()
|
||||
.by_vals()
|
||||
.skip(parent.tau.height())
|
||||
.enumerate()
|
||||
{
|
||||
// ith bit of the [child] epoch
|
||||
// note that n represents height difference between parent and the current bit
|
||||
let i = n + parent_height;
|
||||
|
||||
// if the bit is NOT set, push the right '1' subtree (for future keys)
|
||||
// so for example if given parent with some `PREFIX` tau and target_epoch being `PREFIX || 010`,
|
||||
// in the first loop iteration we're going to look at bit `0` and
|
||||
// derive child node `PREFIX || 1` so that in the future we could derive keys for all other epochs starting with `PREFIX || 1`
|
||||
// in the next loop iteration we're going to look at bit `1` and simply update the accumulators,
|
||||
// as we don't need to generate any "left" nodes as all of them would have constructed epochs that are already in the past
|
||||
// finally, in the last iteration, we look at the bit `0` and derive node `PREFIX || 011`,
|
||||
// i.e. the one that FOLLOWS the target node.
|
||||
if !bit {
|
||||
let direct_parent = target_tau.try_get_parent_at_height(i)?;
|
||||
|
||||
self.nodes
|
||||
.push(parent.derive_right_nonfinal_child_of_with_partials(
|
||||
params,
|
||||
direct_parent,
|
||||
&new_b_accumulator,
|
||||
&new_f_accumulator,
|
||||
&mut rng,
|
||||
));
|
||||
} else {
|
||||
// only update the accumulators when the bit is set, as d^0 == identity, so there's
|
||||
// no point in doing anything else;
|
||||
// note that we don't have to generate any new nodes when going into the right branch
|
||||
// of the tree as everything on the left would have been in the past, so we don't care about them
|
||||
new_b_accumulator += parent.ds[n]; // add d0
|
||||
new_f_accumulator += params.fs[i]; // f_i
|
||||
}
|
||||
}
|
||||
|
||||
self.nodes.push(parent.derive_target_child_with_partials(
|
||||
params,
|
||||
target_epoch.as_tau(),
|
||||
&new_b_accumulator,
|
||||
&new_f_accumulator,
|
||||
&mut rng,
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let num_nodes = self.nodes.len() as u32;
|
||||
|
||||
// unfortunately we're not going to know the expected capacity
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&num_nodes.to_be_bytes());
|
||||
|
||||
for node in &self.nodes {
|
||||
let mut node_bytes = node.to_bytes();
|
||||
bytes.extend_from_slice(&((node_bytes.len() as u32).to_be_bytes()));
|
||||
bytes.append(&mut node_bytes)
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
|
||||
// we have to be able to read the length of nodes
|
||||
if b.len() < 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"DecryptionKey",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
let nodes_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
|
||||
let mut nodes = Vec::with_capacity(nodes_len);
|
||||
|
||||
let mut i = 4;
|
||||
for _ in 0..nodes_len {
|
||||
// check if we can actually read the length...
|
||||
if b[i..].len() < 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"DecryptionKey.Node",
|
||||
"insufficient number of bytes provided for BTE Node recovery",
|
||||
));
|
||||
}
|
||||
|
||||
let node_bytes = u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) as usize;
|
||||
if b[i + 4..].len() < node_bytes {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"DecryptionKey.Node",
|
||||
"insufficient number of bytes provided for BTE Node recovery",
|
||||
));
|
||||
}
|
||||
i += 4;
|
||||
|
||||
let node = Node::try_from_bytes(&b[i..i + node_bytes])?;
|
||||
nodes.push(node);
|
||||
i += node_bytes;
|
||||
}
|
||||
|
||||
Ok(DecryptionKey { nodes })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bte::setup;
|
||||
use bitvec::bitvec;
|
||||
use bitvec::order::Msb0;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn basic_coverage_nodes() {
|
||||
// it's some basic test I've been performing when writing the update function, but figured
|
||||
// might as well put it into a unit test. note that it doesn't check the entire structure,
|
||||
// but just the few last nodes of low height
|
||||
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (mut dk, _) = keygen(¶ms, &mut rng);
|
||||
|
||||
let root_node_copy = dk.nodes.clone();
|
||||
|
||||
// this is a root node
|
||||
assert_eq!(dk.nodes.len(), 1);
|
||||
assert!(dk.nodes[0].is_root());
|
||||
|
||||
// we have to have a node for right branch on each height (1, 01, 001, ... etc)
|
||||
// plus an additional one for the two left-most leaves (epochs "0" and "1")
|
||||
dk.try_update_to(Epoch::new(0), ¶ms, &mut rng).unwrap();
|
||||
assert_eq!(dk.nodes.len(), 33);
|
||||
|
||||
let expected_last = Tau::new(0);
|
||||
// (and yes, I had to look up those names in a thesaurus)
|
||||
let expected_penultimate = Tau::new(1);
|
||||
// note that this value is 31bit long
|
||||
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1
|
||||
]);
|
||||
|
||||
let mut nodes_iter = dk.nodes.iter().rev();
|
||||
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
|
||||
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
|
||||
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
|
||||
|
||||
let mut epoch_zero_nodes = dk.nodes.clone();
|
||||
|
||||
// nodes for epoch1 should be identical for those for epoch0 minus the 00..00 leaf
|
||||
dk.try_update_to(Epoch::new(1), ¶ms, &mut rng).unwrap();
|
||||
assert_eq!(dk.nodes.len(), 32);
|
||||
epoch_zero_nodes.pop().unwrap();
|
||||
assert_eq!(
|
||||
epoch_zero_nodes
|
||||
.iter()
|
||||
.map(|node| node.tau.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
dk.nodes
|
||||
.iter()
|
||||
.map(|node| node.tau.clone())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
dk.try_update_to(Epoch::new(2), ¶ms, &mut rng).unwrap();
|
||||
dk.try_update_to(Epoch::new(3), ¶ms, &mut rng).unwrap();
|
||||
dk.try_update_to(Epoch::new(4), ¶ms, &mut rng).unwrap();
|
||||
|
||||
let expected_last = Tau::new(4);
|
||||
let expected_penultimate = Tau::new(5);
|
||||
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1
|
||||
]);
|
||||
let expected_preantepenultimate = Tau(bitvec![u32, Msb0;
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
|
||||
]);
|
||||
assert_eq!(dk.nodes.len(), 32);
|
||||
let mut nodes_iter = dk.nodes.iter().rev();
|
||||
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
|
||||
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
|
||||
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
|
||||
assert_eq!(expected_preantepenultimate, nodes_iter.next().unwrap().tau);
|
||||
|
||||
// the result should be the same of regardless if we update incrementally or go to the target immediately
|
||||
let mut new_root = DecryptionKey {
|
||||
nodes: root_node_copy,
|
||||
};
|
||||
new_root
|
||||
.try_update_to(Epoch::new(4), ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
dk.nodes
|
||||
.iter()
|
||||
.map(|node| node.tau.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
new_root
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|node| node.tau.clone())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// getting expected nodes for those epochs is non-trivial for test purposes, but the last node
|
||||
// should ALWAYS be equal to the target epoch
|
||||
dk.try_update_to(Epoch::new(42), ¶ms, &mut rng).unwrap();
|
||||
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(42));
|
||||
dk.try_update_to(Epoch::new(123456), ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(123456));
|
||||
dk.try_update_to(Epoch::new(3292547435), ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(3292547435));
|
||||
|
||||
// trying to go to past epochs fails
|
||||
assert!(dk
|
||||
.try_update_to(Epoch::new(531), ¶ms, &mut rng)
|
||||
.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_to_next_epoch() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (mut dk, _) = keygen(¶ms, &mut rng);
|
||||
|
||||
// for root node current epoch is `None`
|
||||
assert_eq!(None, dk.current_epoch(¶ms).unwrap());
|
||||
|
||||
// for root node it should result in epoch 0
|
||||
dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap();
|
||||
assert_eq!(Some(Epoch::new(0)), dk.current_epoch(¶ms).unwrap());
|
||||
|
||||
dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap();
|
||||
assert_eq!(Some(Epoch::new(1)), dk.current_epoch(¶ms).unwrap());
|
||||
|
||||
dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap();
|
||||
assert_eq!(Some(Epoch::new(2)), dk.current_epoch(¶ms).unwrap());
|
||||
|
||||
// if we start from some non-root epoch, it should result in l + 1
|
||||
dk.try_update_to(Epoch::new(42), ¶ms, &mut rng).unwrap();
|
||||
dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap();
|
||||
assert_eq!(Some(Epoch::new(43)), dk.current_epoch(¶ms).unwrap());
|
||||
|
||||
dk.try_update_to(Epoch::new(12345), ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap();
|
||||
assert_eq!(Some(Epoch::new(12346)), dk.current_epoch(¶ms).unwrap());
|
||||
|
||||
dk.try_update_to(Epoch::new(3292547435), ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
dk.try_update_to_next_epoch(¶ms, &mut rng).unwrap();
|
||||
assert_eq!(
|
||||
Some(Epoch::new(3292547436)),
|
||||
dk.current_epoch(¶ms).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_key_with_proof_roundtrip() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (_, pk) = keygen(¶ms, &mut rng);
|
||||
let bytes = pk.to_bytes();
|
||||
let recovered = PublicKeyWithProof::try_from_bytes(&bytes).unwrap();
|
||||
|
||||
assert_eq!(pk, recovered)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bte_node_roundtrip() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (mut dk, _) = keygen(¶ms, &mut rng);
|
||||
|
||||
let root_node = dk.nodes[0].clone();
|
||||
let bytes = root_node.to_bytes();
|
||||
let recovered = Node::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(root_node, recovered);
|
||||
|
||||
dk.try_update_to(Epoch::new(3292547435), ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
for node in &dk.nodes {
|
||||
let bytes = node.to_bytes();
|
||||
let recovered = Node::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(node, &recovered);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decryption_key_node_roundtrip() {
|
||||
let params = setup();
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (mut dk, _) = keygen(¶ms, &mut rng);
|
||||
|
||||
let bytes = dk.to_bytes();
|
||||
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(dk, recovered);
|
||||
|
||||
dk.try_update_to(Epoch::new(0), ¶ms, &mut rng).unwrap();
|
||||
let bytes = dk.to_bytes();
|
||||
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(dk, recovered);
|
||||
|
||||
dk.try_update_to(Epoch::new(1), ¶ms, &mut rng).unwrap();
|
||||
let bytes = dk.to_bytes();
|
||||
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(dk, recovered);
|
||||
|
||||
dk.try_update_to(Epoch::new(42), ¶ms, &mut rng).unwrap();
|
||||
let bytes = dk.to_bytes();
|
||||
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(dk, recovered);
|
||||
|
||||
dk.try_update_to(Epoch::new(3292547435), ¶ms, &mut rng)
|
||||
.unwrap();
|
||||
let bytes = dk.to_bytes();
|
||||
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(dk, recovered);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::DkgError;
|
||||
use crate::utils::{hash_g2, RandomOracleBuilder};
|
||||
use crate::{Chunk, Share};
|
||||
use bitvec::field::BitField;
|
||||
use bitvec::order::Msb0;
|
||||
use bitvec::vec::BitVec;
|
||||
use bitvec::view::BitView;
|
||||
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt};
|
||||
use group::Curve;
|
||||
use lazy_static::lazy_static;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub mod encryption;
|
||||
pub mod keys;
|
||||
pub mod proof_chunking;
|
||||
pub mod proof_discrete_log;
|
||||
pub mod proof_sharing;
|
||||
|
||||
pub use encryption::{decrypt_share, encrypt_shares, Ciphertexts};
|
||||
pub use keys::{keygen, DecryptionKey, PublicKey, PublicKeyWithProof};
|
||||
|
||||
lazy_static! {
|
||||
pub(crate) static ref PAIRING_BASE: Gt =
|
||||
bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator());
|
||||
pub(crate) static ref G2_GENERATOR_PREPARED: G2Prepared =
|
||||
G2Prepared::from(G2Affine::generator());
|
||||
pub(crate) static ref DEFAULT_BSGS_TABLE: encryption::BabyStepGiantStepLookup =
|
||||
encryption::BabyStepGiantStepLookup::default();
|
||||
}
|
||||
|
||||
// Domain tries to follow guidelines specified by:
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
||||
const SETUP_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381G2_XMD:SHA-256_SSWU_RO_SETUP";
|
||||
|
||||
// this particular domain is not for curve hashing, but might as well also follow the same naming pattern
|
||||
const TREE_TAU_EXTENSION_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_SHA-256_TREE_EXTENSION";
|
||||
|
||||
const MAX_EPOCHS_EXP: usize = 32;
|
||||
const HASH_SECURITY_PARAM: usize = 256;
|
||||
|
||||
// note: CHUNK_BYTES * NUM_CHUNKS must equal to SCALAR_SIZE
|
||||
pub const CHUNK_BYTES: usize = 2;
|
||||
pub const NUM_CHUNKS: usize = 16;
|
||||
pub const SCALAR_SIZE: usize = 32;
|
||||
|
||||
/// In paper B; number of distinct chunks
|
||||
pub const CHUNK_SIZE: usize = 1 << (CHUNK_BYTES << 3);
|
||||
|
||||
pub(crate) type EpochStore = u32;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
// None empty bitvec implies this is a root node
|
||||
pub(crate) struct Tau(BitVec<EpochStore, Msb0>);
|
||||
|
||||
impl Tau {
|
||||
pub fn new_root() -> Self {
|
||||
Tau(BitVec::new())
|
||||
}
|
||||
|
||||
// TODO: perhaps this should be explicitly moved to some test module
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(epoch: EpochStore) -> Self {
|
||||
Tau(epoch.view_bits().to_bitvec())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn left_child(&self) -> Self {
|
||||
let mut child = self.0.clone();
|
||||
child.push(false);
|
||||
Tau(child)
|
||||
}
|
||||
|
||||
pub fn right_child(&self) -> Self {
|
||||
let mut child = self.0.clone();
|
||||
child.push(true);
|
||||
Tau(child)
|
||||
}
|
||||
|
||||
pub fn is_leaf(&self, params: &Params) -> bool {
|
||||
self.height() == params.lambda_t
|
||||
}
|
||||
|
||||
pub fn try_get_parent_at_height(&self, height: usize) -> Result<Self, DkgError> {
|
||||
if height > self.0.len() {
|
||||
return Err(DkgError::NotAValidParent);
|
||||
}
|
||||
|
||||
Ok(Tau(self.0[..height].to_bitvec()))
|
||||
}
|
||||
|
||||
// essentially is this tau prefixing the other
|
||||
pub fn is_parent_of(&self, other: &Tau) -> bool {
|
||||
if self.0.len() > other.0.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i, b) in self.0.iter().enumerate() {
|
||||
if b != other.0[i] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn lowest_valid_epoch_child(&self, params: &Params) -> Result<Epoch, DkgError> {
|
||||
if self.0.len() > params.lambda_t {
|
||||
// this node is already BELOW a valid leaf-epoch node. it can only happen
|
||||
// if either some invariant was broken or additional data was pushed to `tau`
|
||||
// in order compute some intermediate results, but in that case this method should have
|
||||
// never been called anyway. tl;dr: if this is called, the underlying key is malformed
|
||||
return Err(DkgError::NotAValidParent);
|
||||
}
|
||||
let mut child = self.0.clone();
|
||||
for _ in 0..(params.lambda_t - self.0.len()) {
|
||||
child.push(false)
|
||||
}
|
||||
|
||||
// the unwrap here is fine as we ensure we have exactly `params.tree_height` bits here
|
||||
// (we could just propagate the error instead of unwraping and putting it behind an `Ok` anyway
|
||||
// but I'd prefer to just blow up since this would be a serious error
|
||||
Ok(Epoch::try_from_tau(&Tau(child), params).unwrap())
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
fn extend(
|
||||
&self,
|
||||
rr: &[G1Projective; NUM_CHUNKS],
|
||||
ss: &[G1Projective; NUM_CHUNKS],
|
||||
cc: &[[G1Projective; NUM_CHUNKS]],
|
||||
) -> Self {
|
||||
let mut random_oracle_builder = RandomOracleBuilder::new(TREE_TAU_EXTENSION_DOMAIN);
|
||||
random_oracle_builder.update_with_g1_elements(rr.iter());
|
||||
random_oracle_builder.update_with_g1_elements(ss.iter());
|
||||
for ciphertext_chunks in cc {
|
||||
random_oracle_builder.update_with_g1_elements(ciphertext_chunks.iter());
|
||||
}
|
||||
|
||||
let tau_mem = self.0.as_raw_slice();
|
||||
assert_eq!(tau_mem.len(), 1, "tau length invariant was broken");
|
||||
random_oracle_builder.update(&tau_mem[0].to_be_bytes());
|
||||
|
||||
let oracle_output = random_oracle_builder.finalize();
|
||||
debug_assert_eq!(oracle_output.len() * 8, HASH_SECURITY_PARAM);
|
||||
|
||||
let mut extended_tau = self.clone();
|
||||
for byte in oracle_output {
|
||||
extended_tau
|
||||
.0
|
||||
.extend_from_bitslice(byte.view_bits::<Msb0>())
|
||||
}
|
||||
|
||||
extended_tau
|
||||
}
|
||||
|
||||
// considers all lambda_t + lambda_h bits
|
||||
fn evaluate_f(&self, params: &Params) -> G2Projective {
|
||||
self.0
|
||||
.iter()
|
||||
.by_vals()
|
||||
.zip(params.fs.iter().chain(params.fh.iter()))
|
||||
.filter(|(i, _)| *i)
|
||||
.map(|(_, f_i)| f_i)
|
||||
.fold(params.f0, |acc, f_i| acc + f_i)
|
||||
}
|
||||
|
||||
// only considers up to lambda_t bits
|
||||
fn evaluate_partial_f(&self, params: &Params) -> G2Projective {
|
||||
self.0
|
||||
.iter()
|
||||
.by_vals()
|
||||
.zip(params.fs.iter())
|
||||
.filter(|(i, _)| *i)
|
||||
.map(|(_, f_i)| f_i)
|
||||
.fold(params.f0, |acc, f_i| acc + f_i)
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let len_bytes = (self.0.len() as u32).to_be_bytes();
|
||||
len_bytes
|
||||
.into_iter()
|
||||
.chain(self.0.chunks(8).map(BitField::load_be))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
|
||||
if b.len() < 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Tau",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
let tau_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
|
||||
|
||||
// maximum theoretical length
|
||||
if tau_len > MAX_EPOCHS_EXP + HASH_SECURITY_PARAM {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Tau",
|
||||
format!(
|
||||
"malformed length {} is greater than maximum {}",
|
||||
tau_len,
|
||||
MAX_EPOCHS_EXP + HASH_SECURITY_PARAM
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if tau_len == 0 {
|
||||
if b.len() != 4 {
|
||||
Err(DkgError::new_deserialization_failure(
|
||||
"Tau",
|
||||
"malformed bytes",
|
||||
))
|
||||
} else {
|
||||
Ok(Tau::new_root())
|
||||
}
|
||||
} else if b.len() == 4 {
|
||||
Err(DkgError::new_deserialization_failure(
|
||||
"Tau",
|
||||
"insufficient number of bytes provided",
|
||||
))
|
||||
} else {
|
||||
let mut inner = BitVec::repeat(false, tau_len);
|
||||
for (slot, &byte) in inner.chunks_mut(8).zip(b[4..].iter()) {
|
||||
slot.store_be(byte);
|
||||
}
|
||||
|
||||
Ok(Tau(inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for Tau {
|
||||
fn zeroize(&mut self) {
|
||||
for v in self.0.as_raw_mut_slice() {
|
||||
v.zeroize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Epoch(EpochStore);
|
||||
|
||||
impl Epoch {
|
||||
pub fn new(value: EpochStore) -> Self {
|
||||
Epoch(value)
|
||||
}
|
||||
|
||||
pub(crate) fn as_tau(&self) -> Tau {
|
||||
(*self).into()
|
||||
}
|
||||
|
||||
pub(crate) fn as_extended_tau(
|
||||
&self,
|
||||
rr: &[G1Projective; NUM_CHUNKS],
|
||||
ss: &[G1Projective; NUM_CHUNKS],
|
||||
cc: &[[G1Projective; NUM_CHUNKS]],
|
||||
) -> Tau {
|
||||
self.as_tau().extend(rr, ss, cc)
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_tau(tau: &Tau, params: &Params) -> Result<Self, DkgError> {
|
||||
if !tau.is_leaf(params) {
|
||||
Err(DkgError::MalformedEpoch)
|
||||
} else {
|
||||
Ok(Epoch(tau.0.load_be()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Epoch> for Tau {
|
||||
fn from(epoch: Epoch) -> Self {
|
||||
Tau(epoch.0.view_bits().to_bitvec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EpochStore> for Epoch {
|
||||
fn from(epoch: EpochStore) -> Self {
|
||||
Epoch(epoch)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Params {
|
||||
/// Maximum size of an epoch, in bits.
|
||||
pub lambda_t: usize,
|
||||
|
||||
/// Security parameter of our $H_{\Lamda_H}$ hash function
|
||||
pub lambda_h: usize,
|
||||
|
||||
// keeping f0 separate from the rest of the curve points makes it easier to work with tau
|
||||
f0: G2Projective,
|
||||
fs: Vec<G2Projective>, // f_1, f_2, .... f_{lambda_t} in the paper
|
||||
fh: Vec<G2Projective>, // f_{lambda_t+1}, f_{lambda_t+1}, .... f_{lambda_t+lambda_h} in the paper
|
||||
h: G2Projective,
|
||||
|
||||
/// Precomputed `h` used for the miller loop
|
||||
_h_prepared: G2Prepared,
|
||||
}
|
||||
|
||||
pub fn setup() -> Params {
|
||||
let f0 = hash_g2(b"f0", SETUP_DOMAIN);
|
||||
|
||||
let fs = (1..=MAX_EPOCHS_EXP)
|
||||
.map(|i| hash_g2(format!("f{}", i), SETUP_DOMAIN))
|
||||
.collect();
|
||||
|
||||
let fh = (0..HASH_SECURITY_PARAM)
|
||||
.map(|i| hash_g2(format!("fh{}", i), SETUP_DOMAIN))
|
||||
.collect();
|
||||
|
||||
let h = hash_g2(b"h", SETUP_DOMAIN);
|
||||
|
||||
Params {
|
||||
lambda_t: MAX_EPOCHS_EXP,
|
||||
lambda_h: HASH_SECURITY_PARAM,
|
||||
f0,
|
||||
fs,
|
||||
fh,
|
||||
h,
|
||||
_h_prepared: G2Prepared::from(h.to_affine()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bitvec::bitvec;
|
||||
use bitvec::order::Msb0;
|
||||
|
||||
#[test]
|
||||
fn creating_tau_from_epoch() {
|
||||
assert!(Tau::new_root().0.is_empty());
|
||||
|
||||
let zero = Tau::new(0);
|
||||
assert!(zero.0.iter().by_vals().all(|b| !b));
|
||||
|
||||
let one = Tau::new(1);
|
||||
let mut iter = one.0.iter().by_vals();
|
||||
// first 31 bits are 0, the last one is 1
|
||||
for _ in 0..31 {
|
||||
assert!(!iter.next().unwrap())
|
||||
}
|
||||
assert!(iter.next().unwrap());
|
||||
|
||||
// 101010 in binary
|
||||
let forty_two = Tau::new(42);
|
||||
// first 26 bits are not set
|
||||
let mut iter = forty_two.0.iter().by_vals();
|
||||
for _ in 0..26 {
|
||||
assert!(!iter.next().unwrap())
|
||||
}
|
||||
assert!(iter.next().unwrap());
|
||||
assert!(!iter.next().unwrap());
|
||||
assert!(iter.next().unwrap());
|
||||
assert!(!iter.next().unwrap());
|
||||
assert!(iter.next().unwrap());
|
||||
assert!(!iter.next().unwrap());
|
||||
|
||||
// value that requires an actual u32 (i.e. takes 4 bytes to represent)
|
||||
// 11000100_01000000_01001001_01101011 in binary
|
||||
let big_val = Tau::new(3292547435);
|
||||
let expected = bitvec![u32, Msb0;
|
||||
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
|
||||
0, 1, 1
|
||||
];
|
||||
assert_eq!(expected, big_val.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn getting_parent_at_height() {
|
||||
let tau = Tau(bitvec![u32, Msb0; 1,0,1,1,0,0,1]);
|
||||
|
||||
let expected_0 = Tau(BitVec::new());
|
||||
let expected_1 = Tau(bitvec![u32, Msb0; 1]);
|
||||
let expected_5 = Tau(bitvec![u32, Msb0; 1,0,1,1,0]);
|
||||
|
||||
assert_eq!(expected_0, tau.try_get_parent_at_height(0).unwrap());
|
||||
assert_eq!(expected_1, tau.try_get_parent_at_height(1).unwrap());
|
||||
assert_eq!(expected_5, tau.try_get_parent_at_height(5).unwrap());
|
||||
assert_eq!(tau, tau.try_get_parent_at_height(7).unwrap());
|
||||
assert!(tau.try_get_parent_at_height(8).is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converting_tau_to_epoch() {
|
||||
let params = setup();
|
||||
|
||||
let tau0: Tau = Epoch::new(0).into();
|
||||
let tau1: Tau = Epoch::new(1).into();
|
||||
let tau42: Tau = Epoch::new(42).into();
|
||||
let tau_big: Tau = Epoch::new(3292547435).into();
|
||||
|
||||
assert_eq!(Epoch::new(0), Epoch::try_from_tau(&tau0, ¶ms).unwrap());
|
||||
assert_eq!(Epoch::new(1), Epoch::try_from_tau(&tau1, ¶ms).unwrap());
|
||||
assert_eq!(
|
||||
Epoch::new(42),
|
||||
Epoch::try_from_tau(&tau42, ¶ms).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Epoch::new(3292547435),
|
||||
Epoch::try_from_tau(&tau_big, ¶ms).unwrap()
|
||||
);
|
||||
|
||||
assert!(Epoch::try_from_tau(&Tau(BitVec::new()), ¶ms).is_err());
|
||||
assert!(Epoch::try_from_tau(&Tau(bitvec![u32, Msb0; 1,0,1,1,0]), ¶ms).is_err());
|
||||
let _31bit_tau = Tau(bitvec![u32, Msb0;
|
||||
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
|
||||
0, 1
|
||||
]);
|
||||
assert!(Epoch::try_from_tau(&_31bit_tau, ¶ms).is_err());
|
||||
|
||||
let _33bit_tau = Tau(bitvec![u32, Msb0;
|
||||
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
|
||||
0, 1, 1, 0
|
||||
]);
|
||||
assert!(Epoch::try_from_tau(&_33bit_tau, ¶ms).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tau_roundtrip() {
|
||||
let good_taus = vec![
|
||||
Tau::new_root(),
|
||||
Tau::new(0),
|
||||
Tau::new(1),
|
||||
Tau::new(2),
|
||||
Tau::new(42),
|
||||
Tau::new(123456),
|
||||
Tau::new(3292547435),
|
||||
Tau::new(u32::MAX),
|
||||
];
|
||||
|
||||
for tau in good_taus {
|
||||
let bytes = tau.to_bytes();
|
||||
let recovered = Tau::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(tau, recovered);
|
||||
}
|
||||
|
||||
// more valid variants
|
||||
let mut another_tau = Tau::new(u32::MAX);
|
||||
another_tau.0.push(true);
|
||||
another_tau.0.push(false);
|
||||
another_tau.0.push(true);
|
||||
|
||||
let bytes = another_tau.to_bytes();
|
||||
let recovered = Tau::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(another_tau, recovered);
|
||||
|
||||
// ensure there are no panics
|
||||
let big_length_bytes = [255, 255, 255, 255, 42];
|
||||
assert!(Tau::try_from_bytes(&big_length_bytes).is_err());
|
||||
|
||||
assert!(Tau::try_from_bytes(&[]).is_err());
|
||||
assert!(Tau::try_from_bytes(&[1, 1, 1, 1]).is_err());
|
||||
assert!(Tau::try_from_bytes(&[0, 0, 0, 1]).is_err());
|
||||
assert!(Tau::try_from_bytes(&[1, 0, 0, 0]).is_err());
|
||||
assert!(Tau::try_from_bytes(&[1, 0, 0]).is_err());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::utils::hash_to_scalar;
|
||||
use bls12_381::{G1Projective, Scalar};
|
||||
use ff::Field;
|
||||
use group::GroupEncoding;
|
||||
use rand_core::RngCore;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// Domain tries to follow guidelines specified by:
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
||||
const DISCRETE_LOG_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct ProofOfDiscreteLog {
|
||||
pub(crate) rand_commitment: G1Projective,
|
||||
pub(crate) response: Scalar,
|
||||
}
|
||||
|
||||
impl ProofOfDiscreteLog {
|
||||
pub fn construct(mut rng: impl RngCore, public: &G1Projective, witness: &Scalar) -> Self {
|
||||
let mut rand_x = Scalar::random(&mut rng);
|
||||
let rand_commitment = G1Projective::generator() * rand_x;
|
||||
let challenge = Self::compute_challenge(public, &rand_commitment);
|
||||
|
||||
let response = rand_x + challenge * witness;
|
||||
rand_x.zeroize();
|
||||
|
||||
ProofOfDiscreteLog {
|
||||
rand_commitment,
|
||||
response,
|
||||
}
|
||||
}
|
||||
|
||||
// note: we don't have to explicitly check whether points are on correct curves / fields
|
||||
// as if they weren't, they'd fail to get deserialized
|
||||
pub fn verify(&self, public: &G1Projective) -> bool {
|
||||
let challenge = Self::compute_challenge(public, &self.rand_commitment);
|
||||
|
||||
// y^c • a == g1^rand_x
|
||||
public * challenge + self.rand_commitment == G1Projective::generator() * self.response
|
||||
}
|
||||
|
||||
pub(crate) fn compute_challenge(public: &G1Projective, rand_commit: &G1Projective) -> Scalar {
|
||||
let public_bytes = public.to_bytes();
|
||||
let rand_commit_bytes = rand_commit.to_bytes();
|
||||
|
||||
let mut bytes = Vec::with_capacity(96);
|
||||
bytes.extend_from_slice(public_bytes.as_ref());
|
||||
bytes.extend_from_slice(rand_commit_bytes.as_ref());
|
||||
|
||||
hash_to_scalar(bytes, DISCRETE_LOG_DOMAIN)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn should_verify_a_valid_proof() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let witness = Scalar::random(&mut rng);
|
||||
let public = G1Projective::generator() * witness;
|
||||
|
||||
let proof = ProofOfDiscreteLog::construct(&mut rng, &public, &witness);
|
||||
|
||||
assert!(proof.verify(&public))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_on_invalid_proof() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let witness = Scalar::random(&mut rng);
|
||||
let public = G1Projective::generator() * witness;
|
||||
|
||||
let other_witness = Scalar::random(&mut rng);
|
||||
let other_public = G1Projective::generator() * other_witness;
|
||||
|
||||
let proof = ProofOfDiscreteLog::construct(&mut rng, &public, &witness);
|
||||
let other_proof = ProofOfDiscreteLog::construct(&mut rng, &other_public, &other_witness);
|
||||
|
||||
assert!(!proof.verify(&other_public));
|
||||
assert!(!other_proof.verify(&public));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,615 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bte::PublicKey;
|
||||
use crate::error::DkgError;
|
||||
use crate::interpolation::polynomial::PublicCoefficients;
|
||||
use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar, hash_to_scalar};
|
||||
use crate::{NodeIndex, Share};
|
||||
use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||
use ff::Field;
|
||||
use group::GroupEncoding;
|
||||
use rand_core::RngCore;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// Domain tries to follow guidelines specified by:
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
||||
const INSTANCE_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_SECRET_SHARING_INSTANCE";
|
||||
|
||||
const CHALLENGE_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_SECRET_SHARING_CHALLENGE";
|
||||
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
pub struct Instance<'a> {
|
||||
public_keys: &'a BTreeMap<NodeIndex, PublicKey>,
|
||||
public_coefficients: &'a PublicCoefficients,
|
||||
combined_randomizer: &'a G1Projective,
|
||||
combined_ciphertexts: &'a [G1Projective],
|
||||
}
|
||||
|
||||
impl<'a> Instance<'a> {
|
||||
pub fn new(
|
||||
public_keys: &'a BTreeMap<NodeIndex, PublicKey>,
|
||||
public_coefficients: &'a PublicCoefficients,
|
||||
combined_randomizer: &'a G1Projective,
|
||||
combined_ciphertexts: &'a [G1Projective],
|
||||
) -> Instance<'a> {
|
||||
Instance {
|
||||
public_keys,
|
||||
public_coefficients,
|
||||
combined_randomizer,
|
||||
combined_ciphertexts,
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_to_scalar(&self) -> Scalar {
|
||||
let g1s = self.public_keys.len() + 1 + self.combined_ciphertexts.len();
|
||||
let g2s = self.public_coefficients.size();
|
||||
let mut bytes = Vec::with_capacity(g1s * 48 + g2s * 96);
|
||||
|
||||
for pk in self.public_keys.values() {
|
||||
bytes.extend_from_slice(pk.0.to_bytes().as_ref())
|
||||
}
|
||||
for coeff in self.public_coefficients.inner() {
|
||||
bytes.extend_from_slice(coeff.to_bytes().as_ref())
|
||||
}
|
||||
bytes.extend_from_slice(self.combined_randomizer.to_bytes().as_ref());
|
||||
|
||||
for ciphertext in self.combined_ciphertexts {
|
||||
bytes.extend_from_slice(ciphertext.to_bytes().as_ref())
|
||||
}
|
||||
|
||||
hash_to_scalar(&bytes, INSTANCE_DOMAIN)
|
||||
}
|
||||
|
||||
fn validate(&self) -> bool {
|
||||
if self.public_keys.is_empty() || self.public_coefficients.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.public_keys.len() != self.combined_ciphertexts.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
pub struct ProofOfSecretSharing {
|
||||
ff: G1Projective,
|
||||
aa: G2Projective,
|
||||
yy: G1Projective,
|
||||
response_r: Scalar,
|
||||
response_alpha: Scalar,
|
||||
}
|
||||
|
||||
impl ProofOfSecretSharing {
|
||||
pub fn construct(
|
||||
mut rng: impl RngCore,
|
||||
instance: Instance,
|
||||
witness_r: &Scalar,
|
||||
witnesses_s: &[Share],
|
||||
) -> Result<Self, DkgError> {
|
||||
if !instance.validate() {
|
||||
return Err(DkgError::MalformedProofOfSharingInstance);
|
||||
}
|
||||
|
||||
let g1 = G1Projective::generator();
|
||||
let g2 = G2Projective::generator();
|
||||
|
||||
let x = instance.hash_to_scalar();
|
||||
|
||||
// alpha, rho ← random_scalars
|
||||
let alpha = Scalar::random(&mut rng);
|
||||
let rho = Scalar::random(&mut rng);
|
||||
|
||||
// F = g1^rho
|
||||
let ff = g1 * rho;
|
||||
// A = g2^alpha
|
||||
let aa = g2 * alpha;
|
||||
|
||||
// Y = (y_1^{x^1} • ... y_n^{x^n})^rho • g1^alpha
|
||||
// produce intermediate product (y_1^{x^1} • ... y_n^{x^n})
|
||||
let product =
|
||||
instance
|
||||
.public_keys
|
||||
.values()
|
||||
.rev()
|
||||
.fold(G1Projective::identity(), |mut acc, pk| {
|
||||
acc += pk.0;
|
||||
acc *= x;
|
||||
acc
|
||||
});
|
||||
let yy = product * rho + g1 * alpha;
|
||||
|
||||
let challenge = Self::compute_challenge(&x, &ff, &aa, &yy);
|
||||
|
||||
// response_r = r • challenge + rho
|
||||
let response_r = witness_r * challenge + rho;
|
||||
|
||||
// response_alpha = (share_1 • x^1 + ... share_n • x^n) • challenge + alpha
|
||||
// produce intermediate sum (share_1 • x^1 + ... share_n • x^n)
|
||||
let sum = witnesses_s
|
||||
.iter()
|
||||
.rev()
|
||||
.fold(Scalar::zero(), |mut acc, witness| {
|
||||
acc += witness.inner();
|
||||
acc *= x;
|
||||
acc
|
||||
});
|
||||
let response_alpha = sum * challenge + alpha;
|
||||
|
||||
Ok(ProofOfSecretSharing {
|
||||
ff,
|
||||
aa,
|
||||
yy,
|
||||
response_r,
|
||||
response_alpha,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify(&self, instance: Instance) -> bool {
|
||||
if !instance.validate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let g1 = G1Projective::generator();
|
||||
let g2 = G2Projective::generator();
|
||||
|
||||
let x = instance.hash_to_scalar();
|
||||
let challenge = Self::compute_challenge(&x, &self.ff, &self.aa, &self.yy);
|
||||
|
||||
// check if R^challenge * F == g1^response_r
|
||||
if instance.combined_randomizer * challenge + self.ff != g1 * self.response_r {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if
|
||||
// (A_0 ^ (id1^0 • x^1 + ... idn^0 • x^n) • ... A_{t-1} ^ (id1^{t-1} • x^{t-1} + ... idn^{t-1} • x^n))^challenge * A
|
||||
// ==
|
||||
// g2^response_alpha
|
||||
let product = instance
|
||||
.public_coefficients
|
||||
.inner()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(G2Projective::identity(), |mut acc, (k, coeff)| {
|
||||
// intermediate (id1^k • x^1 + ... + idn^k • x^n) sum
|
||||
let sum: Scalar = instance
|
||||
.public_keys
|
||||
.keys()
|
||||
.enumerate()
|
||||
.map(|(i, node_id)| {
|
||||
let id_scalar = Scalar::from(*node_id);
|
||||
id_scalar.pow(&[k as u64, 0, 0, 0]) * x.pow(&[(i + 1) as u64, 0, 0, 0])
|
||||
})
|
||||
.sum();
|
||||
|
||||
acc += coeff * sum;
|
||||
acc
|
||||
});
|
||||
|
||||
if product * challenge + self.aa != g2 * self.response_alpha {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if
|
||||
// (ciphertext_1 ^ (x^1) • ... ciphertext_n ^ (x^n)) ^ challenge • Y
|
||||
// ==
|
||||
// (pk_1 ^ (x^1) • ... pk_n ^ (x^n)) ^ response_r • g1^response_alpha
|
||||
|
||||
let product_1 = instance.combined_ciphertexts.iter().rev().fold(
|
||||
G1Projective::identity(),
|
||||
|mut acc, ciphertext| {
|
||||
acc += ciphertext;
|
||||
acc *= x;
|
||||
acc
|
||||
},
|
||||
);
|
||||
|
||||
let product_2 =
|
||||
instance
|
||||
.public_keys
|
||||
.values()
|
||||
.rev()
|
||||
.fold(G1Projective::identity(), |mut acc, pk| {
|
||||
acc += pk.0;
|
||||
acc *= x;
|
||||
acc
|
||||
});
|
||||
|
||||
if product_1 * challenge + self.yy != product_2 * self.response_r + g1 * self.response_alpha
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn compute_challenge(
|
||||
commitment: &Scalar,
|
||||
blinder_g1: &G1Projective,
|
||||
blinder_g2: &G2Projective,
|
||||
blinded_instance: &G1Projective,
|
||||
) -> Scalar {
|
||||
let mut bytes = Vec::with_capacity(224);
|
||||
|
||||
bytes.extend_from_slice(commitment.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(blinder_g1.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(blinder_g2.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(blinded_instance.to_bytes().as_ref());
|
||||
|
||||
hash_to_scalar(&bytes, CHALLENGE_DOMAIN)
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
// we have 2 G1 elements, single G2 element and 2 scalars
|
||||
let mut bytes = Vec::with_capacity(2 * 48 + 96 + 2 * 32);
|
||||
bytes.extend_from_slice(self.ff.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.aa.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.yy.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.response_r.to_bytes().as_ref());
|
||||
bytes.extend_from_slice(self.response_alpha.to_bytes().as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
|
||||
if bytes.len() != 2 * 48 + 96 + 2 * 32 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"ProofOfSecretSharing",
|
||||
"invalid number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let f = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("ProofOfSecretSharing.f", "invalid curve point")
|
||||
})?;
|
||||
i += 48;
|
||||
|
||||
let a = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("ProofOfSecretSharing.a", "invalid curve point")
|
||||
})?;
|
||||
i += 96;
|
||||
|
||||
let y = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure("ProofOfSecretSharing.y", "invalid curve point")
|
||||
})?;
|
||||
i += 48;
|
||||
|
||||
let response_r = deserialize_scalar(&bytes[i..i + 32]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"ProofOfSecretSharing.response_r",
|
||||
"invalid scalar",
|
||||
)
|
||||
})?;
|
||||
i += 32;
|
||||
|
||||
let response_alpha = deserialize_scalar(&bytes[i..]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"ProofOfSecretSharing.response_alpha",
|
||||
"invalid scalar",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(ProofOfSecretSharing {
|
||||
ff: f,
|
||||
aa: a,
|
||||
yy: y,
|
||||
response_r,
|
||||
response_alpha,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::interpolation::polynomial::Polynomial;
|
||||
use group::Group;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
const NODES: u64 = 50;
|
||||
const THRESHOLD: u64 = 40;
|
||||
|
||||
fn setup(
|
||||
mut rng: impl RngCore,
|
||||
) -> (
|
||||
BTreeMap<NodeIndex, PublicKey>,
|
||||
PublicCoefficients,
|
||||
G1Projective,
|
||||
Vec<G1Projective>,
|
||||
Scalar,
|
||||
Vec<Share>,
|
||||
) {
|
||||
let g1 = G1Projective::generator();
|
||||
|
||||
let mut pks = BTreeMap::new();
|
||||
let polynomial = Polynomial::new_random(&mut rng, THRESHOLD - 1);
|
||||
let public_coefficients = polynomial.public_coefficients();
|
||||
|
||||
let mut shares: Vec<Share> = Vec::new();
|
||||
let mut node_indices = (0..NODES).map(|_| rng.next_u64()).collect::<Vec<_>>();
|
||||
node_indices.sort_unstable();
|
||||
|
||||
for node_index in node_indices {
|
||||
let share = polynomial.evaluate_at(&Scalar::from(node_index));
|
||||
shares.push(share.into());
|
||||
pks.insert(node_index, PublicKey(g1 * Scalar::random(&mut rng)));
|
||||
}
|
||||
|
||||
let r = Scalar::random(&mut rng);
|
||||
let rr = g1 * r;
|
||||
|
||||
let ciphertexts = pks
|
||||
.values()
|
||||
.zip(&shares)
|
||||
.map(|(pk, share)| pk.0 * r + g1 * share.inner())
|
||||
.collect();
|
||||
(pks, public_coefficients, rr, ciphertexts, r, shares)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_to_create_proof_with_invalid_instance() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let g1 = G1Projective::generator();
|
||||
|
||||
let mut pks = BTreeMap::new();
|
||||
let polynomial = Polynomial::new_random(&mut rng, THRESHOLD - 1);
|
||||
let public_coefficients = polynomial.public_coefficients();
|
||||
|
||||
let mut shares: Vec<Share> = Vec::new();
|
||||
for _ in 0..NODES {
|
||||
let node_index = rng.next_u64();
|
||||
let share = polynomial.evaluate_at(&Scalar::from(node_index));
|
||||
shares.push(share.into());
|
||||
pks.insert(node_index, PublicKey(g1 * Scalar::random(&mut rng)));
|
||||
}
|
||||
|
||||
let r = Scalar::random(&mut rng);
|
||||
let rr = g1 * r;
|
||||
|
||||
let mut shares = Vec::new();
|
||||
for node_id in 1..NODES + 1 {
|
||||
let share = polynomial.evaluate_at(&Scalar::from(node_id));
|
||||
shares.push(share);
|
||||
}
|
||||
|
||||
let ciphertexts = pks
|
||||
.values()
|
||||
.zip(&shares)
|
||||
.map(|(pk, share)| pk.0 * r + g1 * share)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// no public keys
|
||||
let bad_instance1 = Instance {
|
||||
public_keys: &BTreeMap::new(),
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
assert!(!bad_instance1.validate());
|
||||
|
||||
// no public coefficients
|
||||
let bad_instance2 = Instance {
|
||||
public_keys: &pks,
|
||||
public_coefficients: &PublicCoefficients {
|
||||
coefficients: Vec::new(),
|
||||
},
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
assert!(!bad_instance2.validate());
|
||||
|
||||
// no ciphertexts
|
||||
let bad_instance3 = Instance {
|
||||
public_keys: &pks,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &[],
|
||||
};
|
||||
assert!(!bad_instance3.validate());
|
||||
|
||||
// public_keys.len() != combined_ciphertexts.len()
|
||||
let bad_ciphertexts = ciphertexts.iter().skip(1).cloned().collect::<Vec<_>>();
|
||||
|
||||
let bad_instance4 = Instance {
|
||||
public_keys: &pks,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &bad_ciphertexts,
|
||||
};
|
||||
assert!(!bad_instance4.validate());
|
||||
|
||||
// changed index of one of the keys
|
||||
let mut bad_pks = pks.clone();
|
||||
let first_id = bad_pks.keys().copied().take(1).collect::<Vec<_>>();
|
||||
let first_val = bad_pks.remove(&first_id[0]).unwrap();
|
||||
bad_pks.insert(rng.next_u64(), first_val);
|
||||
|
||||
let bad_instance5 = Instance {
|
||||
public_keys: &bad_pks,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &bad_ciphertexts,
|
||||
};
|
||||
assert!(!bad_instance5.validate());
|
||||
|
||||
let good_instance = Instance {
|
||||
public_keys: &pks,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
assert!(good_instance.validate())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_verify_a_valid_proof() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (public_keys, public_coefficients, rr, ciphertexts, r, shares) = setup(&mut rng);
|
||||
|
||||
let instance = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
|
||||
let sharing_proof =
|
||||
ProofOfSecretSharing::construct(&mut rng, instance.clone(), &r, &shares).unwrap();
|
||||
|
||||
assert!(sharing_proof.verify(instance))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_to_verify_proof_with_invalid_instance() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (public_keys, public_coefficients, rr, ciphertexts, r, shares) = setup(&mut rng);
|
||||
|
||||
let instance = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
|
||||
let sharing_proof =
|
||||
ProofOfSecretSharing::construct(&mut rng, instance.clone(), &r, &shares).unwrap();
|
||||
|
||||
// no public keys
|
||||
let bad_instance1 = Instance {
|
||||
public_keys: &BTreeMap::new(),
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
assert!(!sharing_proof.verify(bad_instance1));
|
||||
|
||||
// no public coefficients
|
||||
let bad_instance2 = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &PublicCoefficients {
|
||||
coefficients: Vec::new(),
|
||||
},
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
assert!(!sharing_proof.verify(bad_instance2));
|
||||
|
||||
// no ciphertexts
|
||||
let bad_instance3 = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &[],
|
||||
};
|
||||
assert!(!sharing_proof.verify(bad_instance3));
|
||||
|
||||
// public_keys.len() != combined_ciphertexts.len()
|
||||
let bad_ciphertexts = ciphertexts.iter().skip(1).cloned().collect::<Vec<_>>();
|
||||
|
||||
let bad_instance4 = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &bad_ciphertexts,
|
||||
};
|
||||
assert!(!sharing_proof.verify(bad_instance4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_to_verify_proof_with_wrong_instance() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (public_keys, public_coefficients, rr, ciphertexts, r, shares) = setup(&mut rng);
|
||||
|
||||
let instance = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
|
||||
let sharing_proof =
|
||||
ProofOfSecretSharing::construct(&mut rng, instance.clone(), &r, &shares).unwrap();
|
||||
|
||||
let (public_keys, public_coefficients, rr, ciphertexts, _, _) = setup(&mut rng);
|
||||
let bad_instance = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
|
||||
assert!(!sharing_proof.verify(bad_instance));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_to_verify_invalid_proof() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let (public_keys, public_coefficients, rr, ciphertexts, r, shares) = setup(&mut rng);
|
||||
|
||||
let instance = Instance {
|
||||
public_keys: &public_keys,
|
||||
public_coefficients: &public_coefficients,
|
||||
combined_randomizer: &rr,
|
||||
combined_ciphertexts: &ciphertexts,
|
||||
};
|
||||
|
||||
let good_proof =
|
||||
ProofOfSecretSharing::construct(&mut rng, instance.clone(), &r, &shares).unwrap();
|
||||
|
||||
let mut bad_proof = good_proof.clone();
|
||||
bad_proof.ff = G1Projective::generator();
|
||||
assert!(!bad_proof.verify(instance.clone()));
|
||||
|
||||
let mut bad_proof = good_proof.clone();
|
||||
bad_proof.aa = G2Projective::generator();
|
||||
assert!(!bad_proof.verify(instance.clone()));
|
||||
|
||||
let mut bad_proof = good_proof.clone();
|
||||
bad_proof.yy = G1Projective::generator();
|
||||
assert!(!bad_proof.verify(instance.clone()));
|
||||
|
||||
let mut bad_proof = good_proof.clone();
|
||||
bad_proof.response_r = Scalar::from(42);
|
||||
assert!(!bad_proof.verify(instance.clone()));
|
||||
|
||||
let mut bad_proof = good_proof;
|
||||
bad_proof.response_alpha = Scalar::from(42);
|
||||
assert!(!bad_proof.verify(instance));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_of_secret_sharing_roundtrip() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let proof_fixture = ProofOfSecretSharing {
|
||||
ff: G1Projective::random(&mut rng),
|
||||
aa: G2Projective::random(&mut rng),
|
||||
yy: G1Projective::random(&mut rng),
|
||||
response_r: Scalar::random(&mut rng),
|
||||
response_alpha: Scalar::random(&mut rng),
|
||||
};
|
||||
|
||||
let bytes = proof_fixture.to_bytes();
|
||||
let recovered = ProofOfSecretSharing::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(proof_fixture, recovered);
|
||||
|
||||
assert!(ProofOfSecretSharing::try_from_bytes(&bytes[1..]).is_err())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bte::proof_chunking::ProofOfChunking;
|
||||
use crate::bte::proof_sharing::ProofOfSecretSharing;
|
||||
use crate::bte::{
|
||||
encrypt_shares, proof_chunking, proof_sharing, Ciphertexts, Epoch, Params, PublicKey,
|
||||
};
|
||||
use crate::error::DkgError;
|
||||
use crate::interpolation::polynomial::{Polynomial, PublicCoefficients};
|
||||
use crate::interpolation::{
|
||||
perform_lagrangian_interpolation_at_origin, perform_lagrangian_interpolation_at_x,
|
||||
};
|
||||
use crate::{NodeIndex, Share, Threshold};
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use rand_core::RngCore;
|
||||
use std::collections::BTreeMap;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Dealing {
|
||||
pub public_coefficients: PublicCoefficients,
|
||||
pub ciphertexts: Ciphertexts,
|
||||
pub proof_of_chunking: ProofOfChunking,
|
||||
pub proof_of_sharing: ProofOfSecretSharing,
|
||||
}
|
||||
|
||||
impl Dealing {
|
||||
// I'm not a big fan of this function signature, but I'm not clear on how to improve it while
|
||||
// allowing the dealer to skip decryption of its own share if it was also one of the receivers
|
||||
pub fn create(
|
||||
mut rng: impl RngCore,
|
||||
params: &Params,
|
||||
dealer_index: NodeIndex,
|
||||
threshold: Threshold,
|
||||
epoch: Epoch,
|
||||
// BTreeMap ensures the keys are sorted by their indices
|
||||
receivers: &BTreeMap<NodeIndex, PublicKey>,
|
||||
prior_resharing_secret: Option<Scalar>,
|
||||
) -> (Self, Option<Share>) {
|
||||
assert!(threshold > 0);
|
||||
|
||||
let mut polynomial = Polynomial::new_random(&mut rng, threshold - 1);
|
||||
if let Some(prior_secret) = prior_resharing_secret {
|
||||
polynomial.set_constant_coefficient(prior_secret)
|
||||
}
|
||||
|
||||
let mut shares = receivers
|
||||
.keys()
|
||||
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let remote_share_key_pairs = shares
|
||||
.iter()
|
||||
.zip(receivers.values())
|
||||
.map(|(share, key)| (share, key))
|
||||
.collect::<Vec<_>>();
|
||||
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
|
||||
|
||||
let (ciphertexts, hazmat) =
|
||||
encrypt_shares(&remote_share_key_pairs, epoch, params, &mut rng);
|
||||
|
||||
// create proofs of knowledge
|
||||
let chunking_instance = proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
|
||||
let proof_of_chunking =
|
||||
ProofOfChunking::construct(&mut rng, chunking_instance, hazmat.r(), &shares)
|
||||
.expect("failed to construct proof of chunking");
|
||||
|
||||
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
|
||||
let mut combined_r = hazmat.combine_rs();
|
||||
let combined_rr = ciphertexts.combine_rs();
|
||||
|
||||
let public_coefficients = polynomial.public_coefficients();
|
||||
let sharing_instance = proof_sharing::Instance::new(
|
||||
receivers,
|
||||
&public_coefficients,
|
||||
&combined_rr,
|
||||
&combined_ciphertexts,
|
||||
);
|
||||
let proof_of_sharing =
|
||||
ProofOfSecretSharing::construct(&mut rng, sharing_instance, &combined_r, &shares)
|
||||
.expect("failed to construct proof of secret sharing");
|
||||
|
||||
combined_r.zeroize();
|
||||
|
||||
let dealing = Dealing {
|
||||
public_coefficients,
|
||||
ciphertexts,
|
||||
proof_of_chunking,
|
||||
proof_of_sharing,
|
||||
};
|
||||
|
||||
let dealers_key_index = receivers
|
||||
.keys()
|
||||
.position(|node_index| node_index == &dealer_index);
|
||||
if let Some(dealer_key_index) = dealers_key_index {
|
||||
let dealers_share = shares.remove(dealer_key_index);
|
||||
shares.zeroize();
|
||||
(dealing, Some(dealers_share))
|
||||
} else {
|
||||
(dealing, None)
|
||||
}
|
||||
}
|
||||
|
||||
// rather than returning a bool for whether the dealing is valid or not, a Result is returned
|
||||
// instead so that we would have more information regarding a possible failure cause
|
||||
pub fn verify(
|
||||
&self,
|
||||
params: &Params,
|
||||
epoch: Epoch,
|
||||
threshold: Threshold,
|
||||
receivers: &BTreeMap<NodeIndex, PublicKey>,
|
||||
prior_resharing_public: Option<G2Projective>,
|
||||
) -> Result<(), DkgError> {
|
||||
if threshold == 0 || threshold as usize > receivers.len() {
|
||||
return Err(DkgError::InvalidThreshold {
|
||||
actual: threshold as usize,
|
||||
participating: receivers.len(),
|
||||
});
|
||||
}
|
||||
|
||||
if self.ciphertexts.ciphertext_chunks.len() != receivers.len() {
|
||||
return Err(DkgError::WrongCiphertextSize {
|
||||
actual: self.ciphertexts.ciphertext_chunks.len(),
|
||||
expected: receivers.len(),
|
||||
});
|
||||
}
|
||||
|
||||
if self.public_coefficients.size() != threshold as usize {
|
||||
return Err(DkgError::WrongPublicCoefficientsSize {
|
||||
actual: self.public_coefficients.size(),
|
||||
expected: threshold as usize,
|
||||
});
|
||||
}
|
||||
|
||||
if !self.ciphertexts.verify_integrity(params, epoch) {
|
||||
return Err(DkgError::FailedCiphertextIntegrityCheck);
|
||||
}
|
||||
|
||||
// TODO: perhaps change the underlying arguments in proofs of knowledge to avoid this allocation?
|
||||
let sorted_receivers = receivers.values().copied().collect::<Vec<_>>();
|
||||
|
||||
let chunking_instance = proof_chunking::Instance::new(&sorted_receivers, &self.ciphertexts);
|
||||
if !self.proof_of_chunking.verify(chunking_instance) {
|
||||
return Err(DkgError::InvalidProofOfChunking);
|
||||
}
|
||||
|
||||
let combined_randomizer = &self.ciphertexts.combine_rs();
|
||||
let combined_ciphertexts = &self.ciphertexts.combine_ciphertexts();
|
||||
|
||||
let sharing_instance = proof_sharing::Instance::new(
|
||||
receivers,
|
||||
&self.public_coefficients,
|
||||
combined_randomizer,
|
||||
combined_ciphertexts,
|
||||
);
|
||||
|
||||
if !self.proof_of_sharing.verify(sharing_instance) {
|
||||
return Err(DkgError::InvalidProofOfSharing);
|
||||
}
|
||||
|
||||
if let Some(prior_public) = prior_resharing_public {
|
||||
let dealt_public = &self.public_coefficients[0];
|
||||
if dealt_public != &prior_public {
|
||||
return Err(DkgError::InvalidResharing);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// coeff_len || coeff || cc_len || cc || pi_c_len || pi_c || pi_s_len || pi_s
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
let mut coefficients_bytes = self.public_coefficients.to_bytes();
|
||||
bytes.extend_from_slice(&(coefficients_bytes.len() as u32).to_be_bytes());
|
||||
bytes.append(&mut coefficients_bytes);
|
||||
|
||||
let mut ciphertexts_bytes = self.ciphertexts.to_bytes();
|
||||
bytes.extend_from_slice(&(ciphertexts_bytes.len() as u32).to_be_bytes());
|
||||
bytes.append(&mut ciphertexts_bytes);
|
||||
|
||||
let mut proof_sharing_bytes = self.proof_of_sharing.to_bytes();
|
||||
bytes.extend_from_slice(&(proof_sharing_bytes.len() as u32).to_be_bytes());
|
||||
bytes.append(&mut proof_sharing_bytes);
|
||||
|
||||
let mut proof_chunking_bytes = self.proof_of_chunking.to_bytes();
|
||||
bytes.extend_from_slice(&(proof_chunking_bytes.len() as u32).to_be_bytes());
|
||||
bytes.append(&mut proof_chunking_bytes);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
|
||||
// can we read the length of serialized public coefficients?
|
||||
if bytes.len() < 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Dealing",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let coefficients_bytes_len =
|
||||
u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
|
||||
i += 4;
|
||||
let public_coefficients =
|
||||
PublicCoefficients::try_from_bytes(&bytes[i..i + coefficients_bytes_len])?;
|
||||
i += coefficients_bytes_len;
|
||||
|
||||
let ciphertexts_bytes_len =
|
||||
u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
|
||||
i += 4;
|
||||
let ciphertexts = Ciphertexts::try_from_bytes(&bytes[i..i + ciphertexts_bytes_len])?;
|
||||
i += ciphertexts_bytes_len;
|
||||
|
||||
let proof_of_sharing_bytes_len =
|
||||
u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
|
||||
i += 4;
|
||||
let proof_of_sharing =
|
||||
ProofOfSecretSharing::try_from_bytes(&bytes[i..i + proof_of_sharing_bytes_len])?;
|
||||
i += proof_of_sharing_bytes_len;
|
||||
|
||||
let proof_of_chunking_bytes_len =
|
||||
u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
|
||||
i += 4;
|
||||
|
||||
if bytes[i..].len() != proof_of_chunking_bytes_len {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"Dealing",
|
||||
"invalid number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let proof_of_chunking = ProofOfChunking::try_from_bytes(&bytes[i..])?;
|
||||
|
||||
Ok(Dealing {
|
||||
public_coefficients,
|
||||
ciphertexts,
|
||||
proof_of_chunking,
|
||||
proof_of_sharing,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// this assumes all dealings have been verified
|
||||
pub fn try_recover_verification_keys(
|
||||
dealings: &[Dealing],
|
||||
threshold: Threshold,
|
||||
receivers: &BTreeMap<NodeIndex, PublicKey>,
|
||||
) -> Result<(G2Projective, Vec<G2Projective>), DkgError> {
|
||||
if dealings.is_empty() {
|
||||
return Err(DkgError::NoDealingsAvailable);
|
||||
}
|
||||
|
||||
let threshold_usize = threshold as usize;
|
||||
|
||||
if !dealings
|
||||
.iter()
|
||||
.all(|dealing| dealing.public_coefficients.size() == threshold_usize)
|
||||
{
|
||||
return Err(DkgError::MismatchedDealings);
|
||||
}
|
||||
|
||||
// currently we expect every dealer to also be a receiver. This restriction might be relaxed in the future
|
||||
if dealings.len() != receivers.len() {
|
||||
return Err(DkgError::MismatchedDealings);
|
||||
}
|
||||
|
||||
let indices = receivers.keys().collect::<Vec<_>>();
|
||||
|
||||
// Compute A0, ..., A_{t-1}
|
||||
let mut interpolated_coefficients = Vec::with_capacity(threshold_usize);
|
||||
for k in 0..threshold_usize {
|
||||
let mut samples = Vec::with_capacity(indices.len());
|
||||
for (j, dealing) in dealings.iter().enumerate() {
|
||||
samples.push((
|
||||
Scalar::from(*indices[j]),
|
||||
*dealing.public_coefficients.nth(k),
|
||||
))
|
||||
}
|
||||
let interpolated = perform_lagrangian_interpolation_at_origin(&samples)?;
|
||||
interpolated_coefficients.push(interpolated);
|
||||
}
|
||||
|
||||
let master_verification_key = interpolated_coefficients[0];
|
||||
|
||||
let interpolated_coefficients = PublicCoefficients {
|
||||
coefficients: interpolated_coefficients,
|
||||
};
|
||||
|
||||
// shvk_j = A0^{j^0} * A1^{j^1} * ... * A_{t-1}^{j^{t-1}}
|
||||
let verification_key_shares = receivers
|
||||
.keys()
|
||||
.map(|index| interpolated_coefficients.evaluate_at(&Scalar::from(*index)))
|
||||
.collect();
|
||||
|
||||
Ok((master_verification_key, verification_key_shares))
|
||||
}
|
||||
|
||||
pub fn verify_verification_keys(
|
||||
master_key: &G2Projective,
|
||||
shares: &[G2Projective],
|
||||
receivers: &BTreeMap<NodeIndex, PublicKey>,
|
||||
threshold: Threshold,
|
||||
) -> Result<(), DkgError> {
|
||||
if shares.len() != receivers.len() {
|
||||
return Err(DkgError::NotEnoughReceiversProvided);
|
||||
}
|
||||
|
||||
if threshold as usize > receivers.len() {
|
||||
return Err(DkgError::InvalidThreshold {
|
||||
actual: threshold as usize,
|
||||
participating: receivers.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let indices = receivers.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
let indices_with_origin = std::iter::once(&0)
|
||||
.chain(receivers.keys())
|
||||
.collect::<Vec<_>>();
|
||||
let all_shares = std::iter::once(master_key)
|
||||
.chain(shares.iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (i, share) in shares.iter().enumerate() {
|
||||
let samples = indices_with_origin
|
||||
.iter()
|
||||
.zip(all_shares.iter())
|
||||
.map(|(&node_index, &share)| (Scalar::from(*node_index), *share))
|
||||
.take(threshold as usize)
|
||||
.collect::<Vec<_>>();
|
||||
let interpolated =
|
||||
perform_lagrangian_interpolation_at_x(&Scalar::from(indices[i]), &samples)?;
|
||||
if share != &interpolated {
|
||||
return Err(DkgError::MismatchedVerificationKey);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bte::{decrypt_share, keygen, setup};
|
||||
use crate::combine_shares;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn recovering_partial_verification_keys() {
|
||||
// START OF SETUP
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let threshold = 2;
|
||||
let node_indices = vec![1, 4, 7];
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
let mut full_keys = Vec::new();
|
||||
for index in &node_indices {
|
||||
let (dk, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
full_keys.push((dk, pk))
|
||||
}
|
||||
|
||||
// start off in a defined epoch (i.e. not root);
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
let shares = dealings
|
||||
.iter()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
|
||||
.collect();
|
||||
derived_secrets.push(
|
||||
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
let master_secret = perform_lagrangian_interpolation_at_origin(&[
|
||||
(Scalar::from(node_indices[2]), derived_secrets[2]),
|
||||
(Scalar::from(node_indices[1]), derived_secrets[1]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
// END OF SETUP
|
||||
let (recovered_master, recovered_partials) =
|
||||
try_recover_verification_keys(&dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let g2 = G2Projective::generator();
|
||||
assert_eq!(g2 * master_secret, recovered_master);
|
||||
|
||||
assert_eq!(g2 * derived_secrets[0], recovered_partials[0]);
|
||||
assert_eq!(g2 * derived_secrets[1], recovered_partials[1]);
|
||||
assert_eq!(g2 * derived_secrets[2], recovered_partials[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifying_partial_verification_keys() {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let threshold = 2;
|
||||
let node_indices = vec![1, 4, 7];
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
let mut full_keys = Vec::new();
|
||||
for index in &node_indices {
|
||||
let (dk, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
full_keys.push((dk, pk))
|
||||
}
|
||||
|
||||
// start off in a defined epoch (i.e. not root);
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (recovered_master, recovered_partials) =
|
||||
try_recover_verification_keys(&dealings, threshold, &receivers).unwrap();
|
||||
|
||||
assert!(verify_verification_keys(
|
||||
&recovered_master,
|
||||
&recovered_partials,
|
||||
&receivers,
|
||||
threshold
|
||||
)
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dealing_roundtrip() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let parties = 5;
|
||||
let threshold = ((parties as f32 * 2.) / 3. + 1.) as Threshold;
|
||||
let node_indices = (1..=parties).collect::<Vec<_>>();
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
for index in &node_indices {
|
||||
let (_, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
}
|
||||
|
||||
let (dealing, _) = Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
node_indices[0],
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
);
|
||||
|
||||
let bytes = dealing.to_bytes();
|
||||
let recovered = Dealing::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(dealing, recovered);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DkgError {
|
||||
#[error("Provided set of values contained duplicate coordinate")]
|
||||
DuplicateCoordinate,
|
||||
|
||||
#[error("The public key is malformed")]
|
||||
MalformedPublicKey,
|
||||
|
||||
#[error("The decryption key is malformed")]
|
||||
MalformedDecryptionKey,
|
||||
|
||||
#[error("Could not solve the discrete log")]
|
||||
UnsolvableDiscreteLog,
|
||||
|
||||
#[error("Received share is malformed")]
|
||||
MalformedShare,
|
||||
|
||||
#[error("The share encrypted under index {0} doesn't exist")]
|
||||
UnavailableCiphertext(usize),
|
||||
|
||||
#[error("The provided lookup table is mismatched")]
|
||||
MismatchedLookupTable,
|
||||
|
||||
#[error("Failed to verify proof of discrete logarithm")]
|
||||
InvalidProofOfDiscreteLog,
|
||||
|
||||
#[error("Tried to construct proof of sharing with an invalid instance")]
|
||||
MalformedProofOfSharingInstance,
|
||||
|
||||
#[error("Tried to construct proof of chunking with an invalid instance")]
|
||||
MalformedProofOfChunkingInstance,
|
||||
|
||||
#[error("Aborted construction of proof of chunking - could not complete it within specified number of attempts")]
|
||||
AbortedProofOfChunking,
|
||||
|
||||
#[error("Tried to update the decryption key to an epoch in the past")]
|
||||
TargetEpochUpdateInThePast,
|
||||
|
||||
#[error("Provided epoch is malformed")]
|
||||
MalformedEpoch,
|
||||
|
||||
#[error("Provided node is not a valid parent")]
|
||||
NotAValidParent,
|
||||
|
||||
#[error("Provided decryption key has expired")]
|
||||
ExpiredKey,
|
||||
|
||||
#[error("Provided threshold value ({actual}) is either 0 or larger than the total number of the participating parties ({participating})")]
|
||||
InvalidThreshold { actual: usize, participating: usize },
|
||||
|
||||
#[error(
|
||||
"Provided ciphertext has been generated for a different number of participating parties (expected: {expected}, actual: {actual})"
|
||||
)]
|
||||
WrongCiphertextSize { actual: usize, expected: usize },
|
||||
|
||||
#[error(
|
||||
"Provided public coefficients have been generated for a different number of participating parties (expected: {expected}, actual: {actual})"
|
||||
)]
|
||||
WrongPublicCoefficientsSize { actual: usize, expected: usize },
|
||||
|
||||
#[error("The provided ciphertexts failed integrity check")]
|
||||
FailedCiphertextIntegrityCheck,
|
||||
|
||||
#[error("The provided proof of secret sharing was invalid")]
|
||||
InvalidProofOfSharing,
|
||||
|
||||
#[error("The provided proof of chunking was invalid")]
|
||||
InvalidProofOfChunking,
|
||||
|
||||
#[error("Failed to deserialize {name} - {reason}")]
|
||||
DeserializationFailure { name: String, reason: String },
|
||||
|
||||
#[error("No dealings were provided")]
|
||||
NoDealingsAvailable,
|
||||
|
||||
#[error("Provided dealings were created under different parameters")]
|
||||
MismatchedDealings,
|
||||
|
||||
#[error(
|
||||
"Not enough dealings are available. We have {available} while require at least {required}"
|
||||
)]
|
||||
NotEnoughDealingsAvailable { available: usize, required: usize },
|
||||
|
||||
#[error("Received different number of x and y coordinates for lagrangian interpolation (xs: {x}, ys: {y})")]
|
||||
MismatchedLagrangianSamplesLengths { x: usize, y: usize },
|
||||
|
||||
#[error("Derived partial verification key is mismatched")]
|
||||
MismatchedVerificationKey,
|
||||
|
||||
#[error("Insufficient number of receivers was provided")]
|
||||
NotEnoughReceiversProvided,
|
||||
|
||||
#[error(
|
||||
"The reshared dealing has different public constant coefficient than its prior variant"
|
||||
)]
|
||||
InvalidResharing,
|
||||
}
|
||||
|
||||
impl DkgError {
|
||||
pub fn new_deserialization_failure<S: Into<String>, T: Into<String>>(
|
||||
name: S,
|
||||
reason: T,
|
||||
) -> DkgError {
|
||||
DkgError::DeserializationFailure {
|
||||
name: name.into(),
|
||||
reason: reason.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::DkgError;
|
||||
use bls12_381::Scalar;
|
||||
use core::iter::Sum;
|
||||
use core::ops::Mul;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub mod polynomial;
|
||||
|
||||
fn contains_duplicates(vals: &[Scalar]) -> bool {
|
||||
let mut set = HashSet::new();
|
||||
|
||||
for x in vals {
|
||||
if !set.insert(x.to_bytes()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn generate_lagrangian_coefficients_at_x(
|
||||
x: &Scalar,
|
||||
points: &[Scalar],
|
||||
) -> Result<Vec<Scalar>, DkgError> {
|
||||
let num_points = points.len();
|
||||
if num_points == 0 {
|
||||
return Ok(Vec::new());
|
||||
} else if num_points == 1 {
|
||||
return Ok(vec![Scalar::one()]);
|
||||
}
|
||||
|
||||
if contains_duplicates(points) {
|
||||
return Err(DkgError::DuplicateCoordinate);
|
||||
}
|
||||
|
||||
let mut res = Vec::with_capacity(points.len());
|
||||
|
||||
for (i, xi) in points.iter().enumerate() {
|
||||
let mut numerator = Scalar::one();
|
||||
let mut denominator = Scalar::one();
|
||||
|
||||
for (j, xj) in points.iter().enumerate() {
|
||||
if j != i {
|
||||
// numerator = (x - xs[0]) * ... * (x - xs[j]), j != i
|
||||
numerator *= x - xj;
|
||||
|
||||
// denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i
|
||||
denominator *= xi - xj;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 / denominator
|
||||
let inv: Scalar =
|
||||
Option::from(denominator.invert()).ok_or(DkgError::DuplicateCoordinate)?;
|
||||
|
||||
// numerator / denominator
|
||||
res.push(numerator * inv)
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Performs a Lagrange interpolation at specified x for a polynomial defined by set of coordinates
|
||||
/// (x, f(x)), where x is a `Scalar` and f(x) is a generic type that can be obtained by evaluating `f` at `x`.
|
||||
/// It can be used for Scalars, G1 and G2 points.
|
||||
pub fn perform_lagrangian_interpolation_at_x<T>(
|
||||
x: &Scalar,
|
||||
points: &[(Scalar, T)],
|
||||
) -> Result<T, DkgError>
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> &'a T: Mul<Scalar, Output = T>,
|
||||
{
|
||||
let xs = points.iter().map(|p| p.0).collect::<Vec<_>>();
|
||||
let coefficients = generate_lagrangian_coefficients_at_x(x, &xs)?;
|
||||
|
||||
Ok(coefficients
|
||||
.into_iter()
|
||||
.zip(points.iter().map(|p| &p.1))
|
||||
.map(|(coeff, y)| y * coeff)
|
||||
.sum())
|
||||
}
|
||||
|
||||
/// Performs a Lagrange interpolation at the origin for a polynomial defined by set of coordinates
|
||||
/// (x, f(x)), where x is a `Scalar` and f(x) is a generic type that can be obtained by evaluating `f` at `x`.
|
||||
/// It can be used for Scalars, G1 and G2 points.
|
||||
pub fn perform_lagrangian_interpolation_at_origin<T>(points: &[(Scalar, T)]) -> Result<T, DkgError>
|
||||
where
|
||||
T: Sum,
|
||||
for<'a> &'a T: Mul<Scalar, Output = T>,
|
||||
{
|
||||
perform_lagrangian_interpolation_at_x(&Scalar::zero(), points)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn performing_lagrangian_scalar_interpolation_at_origin() {
|
||||
// x^2 + 3
|
||||
// x, f(x):
|
||||
// 1, 4,
|
||||
// 2, 7,
|
||||
// 3, 12,
|
||||
let points = vec![
|
||||
(Scalar::from(1), Scalar::from(4)),
|
||||
(Scalar::from(2), Scalar::from(7)),
|
||||
(Scalar::from(3), Scalar::from(12)),
|
||||
];
|
||||
assert_eq!(
|
||||
Scalar::from(3),
|
||||
perform_lagrangian_interpolation_at_origin(&points).unwrap()
|
||||
);
|
||||
|
||||
// x^3 + 3x^2 - 5x + 11
|
||||
// x, f(x):
|
||||
// 1, 10
|
||||
// 2, 21
|
||||
// 3, 50
|
||||
// 4, 103
|
||||
let points = vec![
|
||||
(Scalar::from(1), Scalar::from(10)),
|
||||
(Scalar::from(2), Scalar::from(21)),
|
||||
(Scalar::from(3), Scalar::from(50)),
|
||||
(Scalar::from(4), Scalar::from(103)),
|
||||
];
|
||||
assert_eq!(
|
||||
Scalar::from(11),
|
||||
perform_lagrangian_interpolation_at_origin(&points).unwrap()
|
||||
);
|
||||
|
||||
// more points than it is required
|
||||
// x^2 + x + 10
|
||||
// x, f(x)
|
||||
// 1, 12
|
||||
// 2, 16
|
||||
// 3, 22
|
||||
// 4, 30
|
||||
// 5, 40
|
||||
let points = vec![
|
||||
(Scalar::from(1), Scalar::from(12)),
|
||||
(Scalar::from(2), Scalar::from(16)),
|
||||
(Scalar::from(3), Scalar::from(22)),
|
||||
(Scalar::from(4), Scalar::from(30)),
|
||||
(Scalar::from(5), Scalar::from(40)),
|
||||
];
|
||||
assert_eq!(
|
||||
Scalar::from(10),
|
||||
perform_lagrangian_interpolation_at_origin(&points).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::DkgError;
|
||||
use crate::utils::deserialize_g2;
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use ff::Field;
|
||||
use group::GroupEncoding;
|
||||
use rand_core::RngCore;
|
||||
use std::ops::{Add, Index, IndexMut};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PublicCoefficients {
|
||||
pub(crate) coefficients: Vec<G2Projective>,
|
||||
}
|
||||
|
||||
impl PublicCoefficients {
|
||||
pub(crate) fn size(&self) -> usize {
|
||||
self.coefficients.len()
|
||||
}
|
||||
|
||||
pub(crate) fn nth(&self, n: usize) -> &G2Projective {
|
||||
&self.coefficients[n]
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.coefficients.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &[G2Projective] {
|
||||
&self.coefficients
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate_at(&self, x: &Scalar) -> G2Projective {
|
||||
if self.coefficients.is_empty() {
|
||||
G2Projective::identity()
|
||||
// if x is zero then we can ignore most of the expensive computation and
|
||||
// just return the last term of the polynomial
|
||||
} else if x.is_zero().into() {
|
||||
// we checked that coefficients are not empty so unwrap here is fine
|
||||
*self.coefficients.first().unwrap()
|
||||
} else {
|
||||
self.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
// coefficient[n] * x ^ n
|
||||
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let coeffs = self.coefficients.len();
|
||||
let mut bytes = Vec::with_capacity(4 + 96 * coeffs);
|
||||
bytes.extend_from_slice(&((coeffs as u32).to_be_bytes()));
|
||||
for coeff in &self.coefficients {
|
||||
bytes.extend_from_slice(coeff.to_bytes().as_ref())
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
|
||||
if b.len() < 4 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"PublicCoefficients",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let coeffs = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
|
||||
let mut coefficients = Vec::with_capacity(coeffs);
|
||||
|
||||
if b.len() != 4 + coeffs * 96 {
|
||||
return Err(DkgError::new_deserialization_failure(
|
||||
"PublicCoefficients",
|
||||
"insufficient number of bytes provided",
|
||||
));
|
||||
}
|
||||
|
||||
let mut i = 4;
|
||||
for _ in 0..coeffs {
|
||||
let coefficient = deserialize_g2(&b[i..i + 96]).ok_or_else(|| {
|
||||
DkgError::new_deserialization_failure(
|
||||
"PublicCoefficients.coefficient",
|
||||
"invalid curve point",
|
||||
)
|
||||
})?;
|
||||
|
||||
coefficients.push(coefficient);
|
||||
i += 96;
|
||||
}
|
||||
|
||||
Ok(PublicCoefficients { coefficients })
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for PublicCoefficients {
|
||||
type Output = G2Projective;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
self.coefficients.index(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for PublicCoefficients {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
self.coefficients.index_mut(index)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub struct Polynomial {
|
||||
coefficients: Vec<Scalar>,
|
||||
}
|
||||
|
||||
impl Polynomial {
|
||||
// for polynomial of degree n, we generate n+1 values
|
||||
// (for example for degree 1, like y = x + 2, we need [2,1])
|
||||
/// Creates new pseudorandom polynomial of specified degree.
|
||||
pub fn new_random(mut rng: impl RngCore, degree: u64) -> Self {
|
||||
Polynomial {
|
||||
coefficients: (0..=degree).map(|_| Scalar::random(&mut rng)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new polynomial with provided coefficients.
|
||||
pub fn new(coefficients: Vec<Scalar>) -> Self {
|
||||
Polynomial { coefficients }
|
||||
}
|
||||
|
||||
pub fn set_constant_coefficient(&mut self, value: Scalar) {
|
||||
if self.coefficients.is_empty() {
|
||||
self.coefficients = vec![value]
|
||||
} else {
|
||||
self.coefficients[0] = value
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a zero-polynomial, i.e. p(x) = 0
|
||||
pub const fn zero() -> Self {
|
||||
Polynomial {
|
||||
coefficients: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns public coefficients associated with this polynomial.
|
||||
pub fn public_coefficients(&self) -> PublicCoefficients {
|
||||
let g2 = G2Projective::generator();
|
||||
let coefficients = self.coefficients.iter().map(|a_i| g2 * a_i).collect();
|
||||
|
||||
PublicCoefficients { coefficients }
|
||||
}
|
||||
|
||||
/// Evaluates the polynomial at point x.
|
||||
pub fn evaluate_at(&self, x: &Scalar) -> Scalar {
|
||||
if self.coefficients.is_empty() {
|
||||
Scalar::zero()
|
||||
// if x is zero then we can ignore most of the expensive computation and
|
||||
// just return the last term of the polynomial
|
||||
} else if x.is_zero().into() {
|
||||
// we checked that coefficients are not empty so unwrap here is fine
|
||||
*self.coefficients.first().unwrap()
|
||||
} else {
|
||||
self.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
// coefficient[n] * x ^ n
|
||||
.map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0]))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Polynomial {
|
||||
type Output = Scalar;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
self.coefficients.index(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for Polynomial {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
self.coefficients.index_mut(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Add<&'b Polynomial> for Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: &'b Polynomial) -> Polynomial {
|
||||
&self + rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add<Polynomial> for &'a Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: Polynomial) -> Polynomial {
|
||||
self + &rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Polynomial> for Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: Polynomial) -> Polynomial {
|
||||
&self + &rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<&'b Polynomial> for &'a Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: &'b Polynomial) -> Self::Output {
|
||||
let len = self.coefficients.len();
|
||||
let rhs_len = rhs.coefficients.len();
|
||||
|
||||
// to have easier bound checks
|
||||
if rhs_len > len {
|
||||
return rhs + self;
|
||||
}
|
||||
|
||||
// we know len >= rhs_len and hence the output will also be of size len
|
||||
let mut res = Vec::with_capacity(len);
|
||||
|
||||
for i in 0..len {
|
||||
if let Some(rhs_coeff) = rhs.coefficients.get(i) {
|
||||
res.push(self[i] + rhs_coeff)
|
||||
} else {
|
||||
res.push(self[i])
|
||||
}
|
||||
}
|
||||
|
||||
Polynomial { coefficients: res }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn polynomial_evaluation() {
|
||||
// y = 42 (it should be 42 regardless of x)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(42)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(42), poly.evaluate_at(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate_at(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(42), poly.evaluate_at(&Scalar::from(10)));
|
||||
|
||||
// y = x + 10, at x = 2 (exp: 12)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![Scalar::from(10), Scalar::from(1)],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(12), poly.evaluate_at(&Scalar::from(2)));
|
||||
|
||||
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
|
||||
let poly = Polynomial {
|
||||
coefficients: vec![
|
||||
(-Scalar::from(3)),
|
||||
Scalar::from(2),
|
||||
(-Scalar::from(5)),
|
||||
Scalar::zero(),
|
||||
Scalar::from(1),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(Scalar::from(39), poly.evaluate_at(&Scalar::from(3)));
|
||||
|
||||
// empty polynomial
|
||||
let poly = Polynomial::zero();
|
||||
|
||||
// should always be 0
|
||||
assert_eq!(Scalar::from(0), poly.evaluate_at(&Scalar::from(1)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate_at(&Scalar::from(0)));
|
||||
assert_eq!(Scalar::from(0), poly.evaluate_at(&Scalar::from(10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polynomial_addition() {
|
||||
let empty = Polynomial::zero();
|
||||
let p1 = Polynomial {
|
||||
coefficients: vec![Scalar::from(1), Scalar::from(2), Scalar::from(3)],
|
||||
};
|
||||
let p2 = Polynomial {
|
||||
coefficients: vec![Scalar::from(4), Scalar::from(5)],
|
||||
};
|
||||
let expected_sum = Polynomial {
|
||||
coefficients: vec![Scalar::from(5), Scalar::from(7), Scalar::from(3)],
|
||||
};
|
||||
|
||||
assert_eq!(p1, &p1 + &empty);
|
||||
assert_eq!(p1, empty + &p1);
|
||||
assert_eq!(expected_sum, &p1 + &p2);
|
||||
assert_eq!(expected_sum, &p2 + &p1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_coefficients_evaluation() {
|
||||
// we use the same values as in polynomial evaluation test
|
||||
|
||||
let g2 = G2Projective::generator();
|
||||
|
||||
// y = 42 (it should be 42 regardless of x)
|
||||
let coeffs = PublicCoefficients {
|
||||
coefficients: vec![g2 * Scalar::from(42)],
|
||||
};
|
||||
|
||||
assert_eq!(g2 * Scalar::from(42), coeffs.evaluate_at(&Scalar::from(1)));
|
||||
assert_eq!(g2 * Scalar::from(42), coeffs.evaluate_at(&Scalar::from(0)));
|
||||
assert_eq!(g2 * Scalar::from(42), coeffs.evaluate_at(&Scalar::from(10)));
|
||||
|
||||
// y = x + 10, at x = 2 (exp: 12)
|
||||
let poly = PublicCoefficients {
|
||||
coefficients: vec![g2 * Scalar::from(10), g2 * Scalar::from(1)],
|
||||
};
|
||||
|
||||
assert_eq!(g2 * Scalar::from(12), poly.evaluate_at(&Scalar::from(2)));
|
||||
|
||||
// y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39)
|
||||
let coeffs = PublicCoefficients {
|
||||
coefficients: vec![
|
||||
(-g2 * Scalar::from(3)),
|
||||
g2 * Scalar::from(2),
|
||||
(-g2 * Scalar::from(5)),
|
||||
G2Projective::identity(),
|
||||
g2 * Scalar::from(1),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(g2 * Scalar::from(39), coeffs.evaluate_at(&Scalar::from(3)));
|
||||
|
||||
// empty coefficients
|
||||
let coeffs = PublicCoefficients {
|
||||
coefficients: Vec::new(),
|
||||
};
|
||||
|
||||
// should always be 0
|
||||
assert_eq!(
|
||||
G2Projective::identity(),
|
||||
coeffs.evaluate_at(&Scalar::from(1))
|
||||
);
|
||||
assert_eq!(
|
||||
G2Projective::identity(),
|
||||
coeffs.evaluate_at(&Scalar::from(0))
|
||||
);
|
||||
assert_eq!(
|
||||
G2Projective::identity(),
|
||||
coeffs.evaluate_at(&Scalar::from(10))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_coefficients_roundtrip() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let good = vec![
|
||||
Polynomial::zero().public_coefficients(),
|
||||
Polynomial::new_random(&mut rng, 0).public_coefficients(),
|
||||
Polynomial::new_random(&mut rng, 1).public_coefficients(),
|
||||
Polynomial::new_random(&mut rng, 4).public_coefficients(),
|
||||
Polynomial::new_random(&mut rng, 15).public_coefficients(),
|
||||
];
|
||||
|
||||
for coefficient in good {
|
||||
let bytes = coefficient.to_bytes();
|
||||
let recovered = PublicCoefficients::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(coefficient, recovered);
|
||||
}
|
||||
|
||||
assert!(PublicCoefficients::try_from_bytes(&[]).is_err());
|
||||
assert!(PublicCoefficients::try_from_bytes(&[1]).is_err());
|
||||
assert!(PublicCoefficients::try_from_bytes(&[1, 2, 3, 4]).is_err());
|
||||
|
||||
let g2 = G2Projective::generator().to_bytes();
|
||||
let mut bad_length = Vec::new();
|
||||
bad_length.extend_from_slice(&2u32.to_be_bytes());
|
||||
bad_length.extend_from_slice(g2.as_ref());
|
||||
assert!(PublicCoefficients::try_from_bytes(&bad_length).is_err());
|
||||
|
||||
let mut incomplete = Vec::new();
|
||||
incomplete.extend_from_slice(&1u32.to_be_bytes());
|
||||
incomplete.extend_from_slice(&g2.as_ref()[..95]);
|
||||
assert!(PublicCoefficients::try_from_bytes(&incomplete).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// forward-secure public key encryption scheme
|
||||
pub mod bte;
|
||||
pub mod error;
|
||||
pub mod interpolation;
|
||||
|
||||
// this entire module is a big placeholder for whatever scheme we decide to use for the
|
||||
// secure channel encryption scheme, but I would assume that the top-level API would
|
||||
// remain more or less the same
|
||||
pub mod dealing;
|
||||
pub(crate) mod share;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub use dealing::*;
|
||||
pub use share::*;
|
||||
|
||||
// TODO: presumably this should live in a some different, common, crate?
|
||||
pub type Threshold = u64;
|
||||
pub type NodeIndex = u64;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::interpolation::perform_lagrangian_interpolation_at_origin;
|
||||
use crate::interpolation::polynomial::Polynomial;
|
||||
use bls12_381::Scalar;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn basic_dummy_secret_sharing() {
|
||||
let degree = 2;
|
||||
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let p1 = Polynomial::new_random(&mut rng, degree);
|
||||
let p2 = Polynomial::new_random(&mut rng, degree);
|
||||
let p3 = Polynomial::new_random(&mut rng, degree);
|
||||
let p4 = Polynomial::new_random(&mut rng, degree);
|
||||
|
||||
let zero = Scalar::zero();
|
||||
let one = Scalar::one();
|
||||
let two = Scalar::from(2);
|
||||
let three = Scalar::from(3);
|
||||
let four = Scalar::from(4);
|
||||
|
||||
// i.e. given:
|
||||
// p1 = a1 + x * b1 + ...
|
||||
// p2 = a2 + x * b2 + ...
|
||||
// ...
|
||||
// expected = (a1 + a2 + ...) + x * (b1 + b2 + ...) + ...
|
||||
// note: master polynomial is NEVER explicitly computed
|
||||
let expected_master = &p1 + &p2 + &p3 + &p4;
|
||||
|
||||
let v1_secret = p1.evaluate_at(&one)
|
||||
+ p2.evaluate_at(&one)
|
||||
+ p3.evaluate_at(&one)
|
||||
+ p4.evaluate_at(&one);
|
||||
let v2_secret = p1.evaluate_at(&two)
|
||||
+ p2.evaluate_at(&two)
|
||||
+ p3.evaluate_at(&two)
|
||||
+ p4.evaluate_at(&two);
|
||||
let v3_secret = p1.evaluate_at(&three)
|
||||
+ p2.evaluate_at(&three)
|
||||
+ p3.evaluate_at(&three)
|
||||
+ p4.evaluate_at(&three);
|
||||
let v4_secret = p1.evaluate_at(&four)
|
||||
+ p2.evaluate_at(&four)
|
||||
+ p3.evaluate_at(&four)
|
||||
+ p4.evaluate_at(&four);
|
||||
|
||||
// note that the following would have never happened in actual dkg setting, but it's
|
||||
// used here mostly for a sanity check on the maths used
|
||||
let samples = vec![
|
||||
(one, v1_secret),
|
||||
(two, v2_secret),
|
||||
(three, v3_secret),
|
||||
(four, v4_secret),
|
||||
];
|
||||
let master_secret = perform_lagrangian_interpolation_at_origin(&samples).unwrap();
|
||||
|
||||
assert_eq!(expected_master.evaluate_at(&zero), master_secret);
|
||||
assert_eq!(expected_master.evaluate_at(&one), v1_secret);
|
||||
assert_eq!(expected_master.evaluate_at(&two), v2_secret);
|
||||
assert_eq!(expected_master.evaluate_at(&three), v3_secret);
|
||||
assert_eq!(expected_master.evaluate_at(&four), v4_secret);
|
||||
|
||||
// since we have 4 parties, but polynomials used are of degree 2, we only need at least 3
|
||||
// issuers to contribute
|
||||
let samples2 = vec![(one, v1_secret), (three, v3_secret), (four, v4_secret)];
|
||||
let master_secret2 = perform_lagrangian_interpolation_at_origin(&samples2).unwrap();
|
||||
assert_eq!(master_secret, master_secret2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bte::{CHUNK_BYTES, NUM_CHUNKS, SCALAR_SIZE};
|
||||
use crate::error::DkgError;
|
||||
use crate::interpolation::perform_lagrangian_interpolation_at_origin;
|
||||
use crate::NodeIndex;
|
||||
use bls12_381::Scalar;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// if this type is changed, one must ensure all values can fit in it
|
||||
pub type Chunk = u16;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Zeroize)]
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
#[zeroize(drop)]
|
||||
pub struct Share(pub(crate) Scalar);
|
||||
|
||||
pub fn combine_shares(shares: Vec<Share>, node_indices: &[NodeIndex]) -> Result<Scalar, DkgError> {
|
||||
if shares.len() != node_indices.len() {
|
||||
return Err(DkgError::MismatchedLagrangianSamplesLengths {
|
||||
x: node_indices.len(),
|
||||
y: shares.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let samples = shares
|
||||
.into_iter()
|
||||
.zip(node_indices.iter())
|
||||
.map(|(share, index)| (Scalar::from(*index), share.0))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
perform_lagrangian_interpolation_at_origin(&samples)
|
||||
}
|
||||
|
||||
impl Share {
|
||||
// not really used outside tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn random(mut rng: impl rand_core::RngCore) -> Self {
|
||||
use ff::Field;
|
||||
Share(Scalar::random(&mut rng))
|
||||
}
|
||||
|
||||
pub(crate) fn to_chunks(&self) -> ChunkedShare {
|
||||
let mut chunks = [0; NUM_CHUNKS];
|
||||
let mut bytes = self.0.to_bytes();
|
||||
|
||||
for (chunk, chunk_bytes) in chunks.iter_mut().zip(bytes[..].chunks_exact(CHUNK_BYTES)) {
|
||||
let mut tmp = [0u8; CHUNK_BYTES];
|
||||
tmp.copy_from_slice(chunk_bytes);
|
||||
*chunk = Chunk::from_le_bytes(tmp)
|
||||
}
|
||||
|
||||
bytes.zeroize();
|
||||
ChunkedShare { chunks }
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &Scalar {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Scalar> for Share {
|
||||
fn from(s: Scalar) -> Self {
|
||||
Share(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Zeroize)]
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
#[zeroize(drop)]
|
||||
pub(crate) struct ChunkedShare {
|
||||
pub(crate) chunks: [Chunk; NUM_CHUNKS],
|
||||
}
|
||||
|
||||
impl From<Share> for ChunkedShare {
|
||||
fn from(share: Share) -> ChunkedShare {
|
||||
share.to_chunks()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ChunkedShare> for Share {
|
||||
type Error = DkgError;
|
||||
|
||||
fn try_from(chunked: ChunkedShare) -> Result<Share, Self::Error> {
|
||||
let mut bytes = [0u8; SCALAR_SIZE];
|
||||
for (chunk, chunk_bytes) in chunked
|
||||
.chunks
|
||||
.iter()
|
||||
.zip(bytes[..].chunks_exact_mut(CHUNK_BYTES))
|
||||
{
|
||||
let tmp = chunk.to_le_bytes();
|
||||
chunk_bytes.copy_from_slice(&tmp[..]);
|
||||
}
|
||||
|
||||
let recovered = Option::from(Scalar::from_bytes(&bytes))
|
||||
.map(Share)
|
||||
.ok_or(DkgError::MalformedShare)?;
|
||||
|
||||
bytes.zeroize();
|
||||
Ok(recovered)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::combine_scalar_chunks;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn chunking_share() {
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let share = Share::random(&mut rng);
|
||||
let chunks: ChunkedShare = share.clone().into();
|
||||
|
||||
let scalar_chunks = chunks
|
||||
.chunks
|
||||
.iter()
|
||||
.map(|c| Scalar::from(*c as u64))
|
||||
.collect::<Vec<_>>();
|
||||
let expected = combine_scalar_chunks(&scalar_chunks);
|
||||
assert_eq!(expected, share.0);
|
||||
|
||||
let recombined: Share = chunks.try_into().unwrap();
|
||||
assert_eq!(expected, recombined.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Just a todo file to keep track of what should be changed
|
||||
|
||||
- Deriving challenges and oracles should be more streamlined; perhaps some trait for hashing
|
||||
- perhaps there could be additional intermediate types in proofs of knowledge (for moves / responses / etc)
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bte::CHUNK_SIZE;
|
||||
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField};
|
||||
use bls12_381::G1Projective;
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use group::GroupEncoding;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_len {
|
||||
($a:expr, $b:expr) => {
|
||||
if $a.len() != $b {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) struct RandomOracleBuilder {
|
||||
inner_state: Sha256,
|
||||
}
|
||||
|
||||
impl RandomOracleBuilder {
|
||||
pub(crate) fn new(domain: &[u8]) -> Self {
|
||||
let mut inner_state = Sha256::new();
|
||||
inner_state.update(domain);
|
||||
|
||||
RandomOracleBuilder { inner_state }
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self, data: impl AsRef<[u8]>) {
|
||||
self.inner_state.update(data)
|
||||
}
|
||||
|
||||
pub(crate) fn update_with_g1_elements<'a, I>(&mut self, items: I)
|
||||
where
|
||||
I: Iterator<Item = &'a G1Projective>,
|
||||
{
|
||||
items.for_each(|item| self.update(item.to_bytes()))
|
||||
}
|
||||
|
||||
pub(crate) fn finalize(self) -> [u8; 32] {
|
||||
self.inner_state.finalize().into()
|
||||
}
|
||||
}
|
||||
|
||||
// those will most likely need to somehow get re-combined with coconut (or maybe extracted to a completely different module)
|
||||
pub(crate) fn hash_to_scalar<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> Scalar {
|
||||
// the unwrap here is fine as the result vector will have 1 element (as specified) and will not be empty
|
||||
hash_to_scalars(msg, domain, 1).pop().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
|
||||
let mut output = vec![Scalar::zero(); n];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>>(msg.as_ref(), domain, &mut output);
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(msg, domain)
|
||||
}
|
||||
|
||||
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
|
||||
let chunk_size_scalar = Scalar::from(CHUNK_SIZE as u64);
|
||||
chunks.iter().rev().fold(Scalar::zero(), |mut acc, chunk| {
|
||||
acc *= chunk_size_scalar;
|
||||
acc += chunk;
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn combine_g1_chunks(chunks: &[G1Projective]) -> G1Projective {
|
||||
let chunk_size_scalar = Scalar::from(CHUNK_SIZE as u64);
|
||||
chunks
|
||||
.iter()
|
||||
.rev()
|
||||
.fold(G1Projective::identity(), |mut acc, chunk| {
|
||||
acc *= chunk_size_scalar;
|
||||
acc += chunk;
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_scalar(b: &[u8]) -> Option<Scalar> {
|
||||
if b.len() != 32 {
|
||||
None
|
||||
} else {
|
||||
let mut repr: [u8; 32] = Default::default();
|
||||
repr.as_mut().copy_from_slice(b);
|
||||
Scalar::from_bytes(&repr).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_g1(b: &[u8]) -> Option<G1Projective> {
|
||||
if b.len() != 48 {
|
||||
None
|
||||
} else {
|
||||
let mut encoding = <G1Projective as GroupEncoding>::Repr::default();
|
||||
encoding.as_mut().copy_from_slice(b);
|
||||
G1Projective::from_bytes(&encoding).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_g2(b: &[u8]) -> Option<G2Projective> {
|
||||
if b.len() != 96 {
|
||||
None
|
||||
} else {
|
||||
let mut encoding = <G2Projective as GroupEncoding>::Repr::default();
|
||||
encoding.as_mut().copy_from_slice(b);
|
||||
G2Projective::from_bytes(&encoding).into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::{G2Projective, Scalar};
|
||||
use dkg::bte::{decrypt_share, keygen, setup, Epoch};
|
||||
use dkg::interpolation::perform_lagrangian_interpolation_at_origin;
|
||||
use dkg::{combine_shares, try_recover_verification_keys, Dealing};
|
||||
use rand_core::SeedableRng;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn single_sender() {
|
||||
// makes it easier to understand than `full_threshold_secret_sharing`
|
||||
// and is a good stepping stone, because its everything each node will have to perform (from one point of view)
|
||||
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
// the simplest possible case
|
||||
let threshold = 2;
|
||||
|
||||
// the indices are going to get assigned externally, so for test sake, use non-consecutive ones
|
||||
let node_indices = vec![15u64, 248, 33521];
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
let mut full_keys = Vec::new();
|
||||
for index in &node_indices {
|
||||
let (dk, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
full_keys.push((dk, pk))
|
||||
}
|
||||
|
||||
// start off in a defined epoch (i.e. not root);
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
|
||||
// verify remote proofs of key possession
|
||||
for key in full_keys.iter() {
|
||||
assert!(key.1.verify());
|
||||
}
|
||||
|
||||
let (dealing, dealer_share) = Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
node_indices[0],
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
);
|
||||
dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers, None)
|
||||
.unwrap();
|
||||
|
||||
// make sure each share is actually decryptable (even though proofs say they must be, perform this sanity check)
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap();
|
||||
}
|
||||
|
||||
// and for good measure, check that the dealer's share matches decryption result
|
||||
let recovered_dealer =
|
||||
decrypt_share(&full_keys[0].0, 0, &dealing.ciphertexts, epoch, None).unwrap();
|
||||
assert_eq!(recovered_dealer, dealer_share.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_threshold_secret_sharing() {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
// the simplest possible case
|
||||
let threshold = 2;
|
||||
|
||||
// the indices are going to get assigned externally, so for test sake, use non-consecutive ones
|
||||
let node_indices = vec![15u64, 248, 33521];
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
let mut full_keys = Vec::new();
|
||||
for index in &node_indices {
|
||||
let (dk, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
full_keys.push((dk, pk))
|
||||
}
|
||||
|
||||
// start off in a defined epoch (i.e. not root);
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
// TODO: HERE BE SERIALIZATION / DESERIALIZATION THAT'S NOT IMPLEMENTED YET
|
||||
// verify remote proofs of key possession
|
||||
for key in full_keys.iter() {
|
||||
assert!(key.1.verify());
|
||||
}
|
||||
|
||||
let dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for dealing in dealings.iter() {
|
||||
dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers, None)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// recover verification keys
|
||||
let (recovered_master, recovered_partials) =
|
||||
try_recover_verification_keys(&dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let g2 = G2Projective::generator();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
let shares = dealings
|
||||
.iter()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
|
||||
.collect();
|
||||
|
||||
// we know dealer_share matches, but it would be inconvenient to try to put them in here,
|
||||
// so for ease of use (IN A TEST SETTING), just decrypt one's own share
|
||||
let recovered_secret =
|
||||
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap();
|
||||
|
||||
// make sure it matches the associated vk
|
||||
assert_eq!(recovered_partials[i], g2 * recovered_secret);
|
||||
|
||||
derived_secrets.push(recovered_secret)
|
||||
}
|
||||
|
||||
// sanity check that the shares were combined correctly and if we take threshold number of them,
|
||||
// we end up with the same master secret, note: those are NEVER explicitly recovered in actual system
|
||||
// (remember threshold was 2)
|
||||
let master1 = perform_lagrangian_interpolation_at_origin(&[
|
||||
(Scalar::from(node_indices[0]), derived_secrets[0]),
|
||||
(Scalar::from(node_indices[1]), derived_secrets[1]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let master2 = perform_lagrangian_interpolation_at_origin(&[
|
||||
(Scalar::from(node_indices[1]), derived_secrets[1]),
|
||||
(Scalar::from(node_indices[2]), derived_secrets[2]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(master1, master2);
|
||||
assert_eq!(recovered_master, g2 * master1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_threshold_secret_resharing() {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
// the simplest possible case
|
||||
let threshold = 2;
|
||||
|
||||
// the indices are going to get assigned externally, so for test sake, use non-consecutive ones
|
||||
let node_indices = vec![15u64, 248, 33521];
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
let mut full_keys = Vec::new();
|
||||
for index in &node_indices {
|
||||
let (dk, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
full_keys.push((dk, pk))
|
||||
}
|
||||
|
||||
// start off in a defined epoch (i.e. not root);
|
||||
let epoch = Epoch::new(2);
|
||||
|
||||
let first_dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// recover verification keys
|
||||
let (public_original_master, recovered_partials) =
|
||||
try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
dk.try_update_to(epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
let shares = first_dealings
|
||||
.iter()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, epoch, None).unwrap())
|
||||
.collect();
|
||||
|
||||
let recovered_secret =
|
||||
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap();
|
||||
|
||||
derived_secrets.push(recovered_secret)
|
||||
}
|
||||
|
||||
let original_master = perform_lagrangian_interpolation_at_origin(&[
|
||||
(Scalar::from(node_indices[0]), derived_secrets[0]),
|
||||
(Scalar::from(node_indices[1]), derived_secrets[1]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let next_epoch = Epoch::new(3);
|
||||
|
||||
// attempt to create resharing dealings!
|
||||
let resharing_dealings = node_indices
|
||||
.iter()
|
||||
.zip(derived_secrets.iter())
|
||||
.map(|(&dealer_index, prior_secret)| {
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
next_epoch,
|
||||
&receivers,
|
||||
Some(*prior_secret),
|
||||
)
|
||||
.0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (reshared_dealing, prior_vk) in resharing_dealings.iter().zip(recovered_partials.iter()) {
|
||||
reshared_dealing
|
||||
.verify(¶ms, next_epoch, threshold, &receivers, Some(*prior_vk))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// recover verification keys
|
||||
let (public_reshared_master, reshared_partials) =
|
||||
try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let mut reshared_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
dk.try_update_to(next_epoch, ¶ms, &mut rng).unwrap();
|
||||
|
||||
let shares = resharing_dealings
|
||||
.iter()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, next_epoch, None).unwrap())
|
||||
.collect();
|
||||
|
||||
let recovered_secret =
|
||||
combine_shares(shares, &receivers.keys().copied().collect::<Vec<_>>()).unwrap();
|
||||
|
||||
reshared_secrets.push(recovered_secret)
|
||||
}
|
||||
|
||||
let reshared_master = perform_lagrangian_interpolation_at_origin(&[
|
||||
(Scalar::from(node_indices[0]), reshared_secrets[0]),
|
||||
(Scalar::from(node_indices[1]), reshared_secrets[1]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
// the master secret and public values didn't change
|
||||
assert_eq!(original_master, reshared_master);
|
||||
assert_eq!(public_original_master, public_reshared_master);
|
||||
|
||||
// but partials did
|
||||
assert_ne!(derived_secrets, reshared_secrets);
|
||||
assert_ne!(recovered_partials, reshared_partials);
|
||||
}
|
||||
@@ -147,6 +147,8 @@ pub const UTOKENS_TO_BURN: u64 = TOKENS_TO_BURN * 1000000;
|
||||
/// Default bandwidth (in bytes) that we try to buy
|
||||
pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN;
|
||||
|
||||
pub const VOUCHER_INFO: &str = "BandwidthVoucher";
|
||||
|
||||
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
|
||||
|
||||
/// Defaults Cosmos Hub/ATOM path
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::convert::TryInto;
|
||||
|
||||
use bls12_381::Scalar;
|
||||
|
||||
pub use crate::traits::Bytable;
|
||||
pub use elgamal::elgamal_keygen;
|
||||
pub use elgamal::ElGamalKeyPair;
|
||||
pub use elgamal::PublicKey;
|
||||
@@ -28,8 +29,6 @@ pub use scheme::SignatureShare;
|
||||
pub use traits::Base58;
|
||||
pub use utils::hash_to_scalar;
|
||||
|
||||
use crate::traits::Bytable;
|
||||
|
||||
pub mod elgamal;
|
||||
mod error;
|
||||
mod impls;
|
||||
|
||||
Generated
+25
-2
@@ -191,12 +191,35 @@ dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coconut-bandwidth"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"coconut-bandwidth-contract-common",
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coconut-bandwidth-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"serde",
|
||||
"toml",
|
||||
@@ -810,7 +833,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-contract"
|
||||
version = "1.0.0-rc.1"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bs58",
|
||||
@@ -1518,7 +1541,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "1.0.0-rc.1"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["bandwidth-claim", "mixnet", "vesting"]
|
||||
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
{
|
||||
"rinkeby":{
|
||||
"NYM_ERC20":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B",
|
||||
"BANDWIDTH_GENERATOR":"0x5FbDB2315678afecb367f032d93F642f64180aa3",
|
||||
"GRAVITY":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B"
|
||||
},
|
||||
"mainnet":{
|
||||
"NYM_ERC20":"0x525A8F6F3Ba4752868cde25164382BfbaE3990e1",
|
||||
"NYMT": "0xE8883BAeF3869e14E4823F46662e81D4F7d2A81F",
|
||||
"BANDWIDTH_GENERATOR":"",
|
||||
"rinkeby":
|
||||
{"NYM_ERC20":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B",
|
||||
"BANDWIDTH_GENERATOR":"0xfa2714Bf14EB5Bb887e4A54984C6F7A7e3E6c84b",
|
||||
"GRAVITY":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B"},
|
||||
"mainnet":
|
||||
{"NYM_ERC20":"0x525A8F6F3Ba4752868cde25164382BfbaE3990e1",
|
||||
"NYMT":"0xE8883BAeF3869e14E4823F46662e81D4F7d2A81F",
|
||||
"BANDWIDTH_GENERATOR":"0x3FfEb99acca159A182f35F9944dAf3BF41Ae8165",
|
||||
"BANDWIDTH_GENERATOR_NYMT":"0xB3BF30DD53044c9050B7309031Bbf26b2cecF3be",
|
||||
"GRAVITY":"0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906"
|
||||
}
|
||||
"GRAVITY":"0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906"}
|
||||
}
|
||||
Generated
+867
@@ -0,0 +1,867 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
dependencies = [
|
||||
"byte-tools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"network-defaults",
|
||||
"serde",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df"
|
||||
|
||||
[[package]]
|
||||
name = "cosmos_contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"erc20-bridge-contract",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
"k256",
|
||||
"rand_core 0.5.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
"cosmwasm-derive",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d12477e115c0d570c12a2dfd859f80b55b60ddb5075df210d3af06d133a69f45"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
|
||||
dependencies = [
|
||||
"der",
|
||||
"elliptic-curve",
|
||||
"hmac",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-zebra"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"hex",
|
||||
"rand_core 0.5.1",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"ff",
|
||||
"generic-array 0.14.4",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erc20-bridge-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "3.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"quick-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "humantime-serde"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||
dependencies = [
|
||||
"maplit",
|
||||
"pest",
|
||||
"sha-1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7a48d098c2a7fdf5740b19deb1181b4fb8a9e68e03ae517c14cde04b5725409"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-json-wasm"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
"block-buffer 0.7.3",
|
||||
"digest 0.8.1",
|
||||
"fake-simd",
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
|
||||
dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99beeb0daeac2bd1e86ac2c21caddecb244b39a093594da1a661ec2060c7aedd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "uint"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crunchy",
|
||||
"hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "coconut-bandwidth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
|
||||
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
config = { path = "../../common/config"}
|
||||
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
cosmwasm-storage = "1.0.0-beta3"
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::StdError;
|
||||
use thiserror::Error;
|
||||
|
||||
use config::defaults::DENOM;
|
||||
|
||||
/// Custom errors for contract failure conditions.
|
||||
///
|
||||
/// Add any other custom errors you like here.
|
||||
/// Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details.
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("Received multiple coin types")]
|
||||
MultipleDenoms,
|
||||
|
||||
#[error("No coin was sent for voucher")]
|
||||
NoCoin,
|
||||
|
||||
#[error("Wrong coin denomination, you must send {}", DENOM)]
|
||||
WrongDenom,
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod error;
|
||||
mod support;
|
||||
mod transactions;
|
||||
|
||||
use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use coconut_bandwidth_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg};
|
||||
|
||||
/// Instantiate the contract.
|
||||
///
|
||||
/// `deps` contains Storage, API and Querier
|
||||
/// `env` contains block, message and contract info
|
||||
/// `msg` is the contract initialization message, sort of like a constructor call.
|
||||
#[entry_point]
|
||||
pub fn instantiate(
|
||||
_deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
_msg: InstantiateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
/// Handle an incoming message
|
||||
#[entry_point]
|
||||
pub fn execute(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::DepositFunds { data } => transactions::deposit_funds(deps, env, info, data),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::coins;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
|
||||
#[test]
|
||||
fn initialize_contract() {
|
||||
let mut deps = mock_dependencies();
|
||||
let env = mock_env();
|
||||
let msg = InstantiateMsg {};
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
assert_eq!(0, res.messages.len());
|
||||
|
||||
// Contract balance should be 0
|
||||
assert_eq!(
|
||||
coins(0, DENOM),
|
||||
vec![deps
|
||||
.as_ref()
|
||||
.querier
|
||||
.query_balance(env.contract.address, DENOM)
|
||||
.unwrap()]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pub mod tests;
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod helpers {
|
||||
use crate::instantiate;
|
||||
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
|
||||
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
|
||||
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
return deps;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{DepsMut, Env, Event, MessageInfo, Response};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use coconut_bandwidth_contract_common::deposit::DepositData;
|
||||
use coconut_bandwidth_contract_common::events::{
|
||||
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO,
|
||||
DEPOSIT_VALUE,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
|
||||
pub(crate) fn deposit_funds(
|
||||
_deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
info: MessageInfo,
|
||||
data: DepositData,
|
||||
) -> Result<Response, ContractError> {
|
||||
if info.funds.is_empty() {
|
||||
return Err(ContractError::NoCoin);
|
||||
}
|
||||
if info.funds.len() > 1 {
|
||||
return Err(ContractError::MultipleDenoms);
|
||||
}
|
||||
if info.funds[0].denom != DENOM {
|
||||
return Err(ContractError::WrongDenom);
|
||||
}
|
||||
|
||||
let voucher_value = info.funds.last().unwrap();
|
||||
let event = Event::new(DEPOSITED_FUNDS_EVENT_TYPE)
|
||||
.add_attribute(DEPOSIT_VALUE, voucher_value.amount)
|
||||
.add_attribute(DEPOSIT_INFO, data.deposit_info())
|
||||
.add_attribute(DEPOSIT_IDENTITY_KEY, data.identity_key())
|
||||
.add_attribute(DEPOSIT_ENCRYPTION_KEY, data.encryption_key());
|
||||
|
||||
Ok(Response::new().add_event(event))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::Coin;
|
||||
|
||||
#[test]
|
||||
fn invalid_deposit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("requester", &[]);
|
||||
|
||||
let deposit_info = String::from("Deposit info");
|
||||
let verification_key = String::from("Verification key");
|
||||
let encryption_key = String::from("Encryption key");
|
||||
let data = DepositData::new(deposit_info, verification_key, encryption_key);
|
||||
|
||||
assert_eq!(
|
||||
deposit_funds(deps.as_mut(), env.clone(), info, data.clone()),
|
||||
Err(ContractError::NoCoin)
|
||||
);
|
||||
|
||||
let coin = Coin::new(1000000, DENOM);
|
||||
let second_coin = Coin::new(1000000, "some_denom");
|
||||
|
||||
let info = mock_info("requester", &[coin, second_coin.clone()]);
|
||||
assert_eq!(
|
||||
deposit_funds(deps.as_mut(), env.clone(), info, data.clone()),
|
||||
Err(ContractError::MultipleDenoms)
|
||||
);
|
||||
|
||||
let info = mock_info("requester", &[second_coin]);
|
||||
assert_eq!(
|
||||
deposit_funds(deps.as_mut(), env, info, data),
|
||||
Err(ContractError::WrongDenom)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_deposit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let deposit_info = String::from("Deposit info");
|
||||
let verification_key = String::from("Verification key");
|
||||
let encryption_key = String::from("Encryption key");
|
||||
let deposit_value = 424242;
|
||||
let data = DepositData::new(
|
||||
deposit_info.clone(),
|
||||
verification_key.clone(),
|
||||
encryption_key.clone(),
|
||||
);
|
||||
let coin = Coin::new(deposit_value, DENOM);
|
||||
let info = mock_info("requester", &[coin]);
|
||||
|
||||
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap();
|
||||
|
||||
let events: Vec<_> = tx
|
||||
.events
|
||||
.iter()
|
||||
.filter(|event| event.ty == DEPOSITED_FUNDS_EVENT_TYPE)
|
||||
.collect();
|
||||
assert_eq!(events.len(), 1);
|
||||
|
||||
let event = events[0];
|
||||
assert_eq!(event.attributes.len(), 4);
|
||||
|
||||
let deposit_attr = event
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == DEPOSIT_VALUE)
|
||||
.unwrap();
|
||||
assert_eq!(deposit_attr.value, deposit_value.to_string());
|
||||
|
||||
let info_attr = event
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == DEPOSIT_INFO)
|
||||
.unwrap();
|
||||
assert_eq!(info_attr.value, deposit_info);
|
||||
|
||||
let verification_key_attr = event
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == DEPOSIT_IDENTITY_KEY)
|
||||
.unwrap();
|
||||
assert_eq!(verification_key_attr.value, verification_key);
|
||||
|
||||
let encryption_key_attr = event
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == DEPOSIT_ENCRYPTION_KEY)
|
||||
.unwrap();
|
||||
assert_eq!(encryption_key_attr.value, encryption_key);
|
||||
}
|
||||
}
|
||||
@@ -1025,6 +1025,8 @@ pub mod tests {
|
||||
);
|
||||
assert_eq!(mix_1_reward_result.reward().int(), 259114u128);
|
||||
|
||||
assert_eq!(mix_1.node_profit(¶ms).int(), 203558u128);
|
||||
|
||||
let mix1_operator_reward = mix_1.operator_reward(¶ms);
|
||||
|
||||
let mix1_delegator1_reward = mix_1.reward_delegation(Uint128::new(8000_000000), ¶ms);
|
||||
@@ -1035,6 +1037,11 @@ pub mod tests {
|
||||
assert_eq!(mix1_delegator1_reward, 73280);
|
||||
assert_eq!(mix1_delegator2_reward, 18320);
|
||||
|
||||
assert_eq!(
|
||||
mix_1_reward_result.reward().int(),
|
||||
mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward + 1
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward + 1,
|
||||
mix_1_reward_result.reward().int()
|
||||
@@ -1076,13 +1083,6 @@ pub mod tests {
|
||||
+ mix1_delegator2_reward
|
||||
+ 1 // There is a rounding error here it seems
|
||||
);
|
||||
// assert_eq!(
|
||||
// mixnodes_storage::TOTAL_DELEGATION
|
||||
// .load(&deps.storage, &node_identity)
|
||||
// .unwrap()
|
||||
// .u128(),
|
||||
// pre_reward_delegation + mix1_delegator1_reward + mix1_delegator2_reward
|
||||
// );
|
||||
|
||||
assert_eq!(
|
||||
storage::REWARD_POOL.load(&deps.storage).unwrap().u128(),
|
||||
|
||||
+12
-12
@@ -358,9 +358,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-align/node_modules/ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -403,9 +403,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -2309,9 +2309,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
@@ -2344,9 +2344,9 @@
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
|
||||
Vendored
+41
-2
@@ -1,18 +1,30 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const DEFAULT_CACHE_VALIDITY: Duration = Duration::from_secs(60 * 30);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Cache<T: Clone> {
|
||||
inner: HashMap<String, CacheItem<T>>,
|
||||
cache_validity_duration: Duration,
|
||||
}
|
||||
|
||||
impl<T: Clone> Cache<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Cache {
|
||||
inner: HashMap::new(),
|
||||
cache_validity_duration: DEFAULT_CACHE_VALIDITY,
|
||||
}
|
||||
}
|
||||
|
||||
// it felt like this might be an useful addition if we want to keep our caches with different
|
||||
// validity durations
|
||||
#[allow(unused)]
|
||||
pub(crate) fn with_validity_duration(mut self, new_cache_validity: Duration) -> Self {
|
||||
self.cache_validity_duration = new_cache_validity;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
@@ -27,7 +39,7 @@ impl<T: Clone> Cache<T> {
|
||||
pub(crate) fn get(&self, key: &str) -> Option<T> {
|
||||
self.inner
|
||||
.get(key)
|
||||
.filter(|cache_item| cache_item.valid_until > SystemTime::now())
|
||||
.filter(|cache_item| cache_item.valid_until >= SystemTime::now())
|
||||
.map(|cache_item| cache_item.value.clone())
|
||||
}
|
||||
|
||||
@@ -35,11 +47,31 @@ impl<T: Clone> Cache<T> {
|
||||
self.inner.insert(
|
||||
key.to_string(),
|
||||
CacheItem {
|
||||
valid_until: SystemTime::now() + Duration::from_secs(60 * 30),
|
||||
valid_until: SystemTime::now() + self.cache_validity_duration,
|
||||
value,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn remove(&mut self, key: &str) -> Option<T> {
|
||||
self.inner.remove(key).map(|item| item.value)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn remove_if_expired(&mut self, key: &str) -> Option<T> {
|
||||
if self.inner.get(key)?.has_expired() {
|
||||
self.remove(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// it seems like something should be running on timer calling this method on all of our caches
|
||||
#[allow(unused)]
|
||||
pub(crate) fn remove_all_expired(&mut self) {
|
||||
self.inner.retain(|_, v| !v.has_expired())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -47,3 +79,10 @@ pub(crate) struct CacheItem<T> {
|
||||
pub(crate) value: T,
|
||||
pub(crate) valid_until: std::time::SystemTime,
|
||||
}
|
||||
|
||||
impl<T> CacheItem<T> {
|
||||
fn has_expired(&self) -> bool {
|
||||
let now = SystemTime::now();
|
||||
self.valid_until < now
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
use network_defaults::{default_api_endpoints, default_nymd_endpoints, DEFAULT_NETWORK};
|
||||
use reqwest::Url;
|
||||
use std::sync::Arc;
|
||||
use validator_client::nymd::QueryNymdClient;
|
||||
|
||||
pub(crate) fn new_nymd_client() -> validator_client::Client<QueryNymdClient> {
|
||||
// since this is just a query client, we don't need any locking mechanism to keep sequence numbers in check
|
||||
// nor we need to access any of its methods taking mutable reference (like updating api URL)
|
||||
// when that becomes a requirement, we would simply put an extra RwLock (or Mutex) in here
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadsafeValidatorClient(
|
||||
pub(crate) Arc<validator_client::Client<QueryNymdClient>>,
|
||||
);
|
||||
|
||||
impl ThreadsafeValidatorClient {
|
||||
pub(crate) fn new() -> Self {
|
||||
new_validator_client()
|
||||
}
|
||||
|
||||
pub(crate) fn api_endpoint(&self) -> &Url {
|
||||
self.0.validator_api.current_url()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_validator_client() -> ThreadsafeValidatorClient {
|
||||
let network = DEFAULT_NETWORK;
|
||||
let mixnet_contract = network.mixnet_contract_address().to_string();
|
||||
let nymd_url = default_nymd_endpoints()[0].clone();
|
||||
@@ -16,5 +37,7 @@ pub(crate) fn new_nymd_client() -> validator_client::Client<QueryNymdClient> {
|
||||
None,
|
||||
);
|
||||
|
||||
validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!")
|
||||
ThreadsafeValidatorClient(Arc::new(
|
||||
validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ impl ExplorerApi {
|
||||
async fn run(&mut self) {
|
||||
info!("Explorer API starting up...");
|
||||
|
||||
let validator_api_url = network_defaults::default_api_endpoints()[0].clone();
|
||||
let validator_api_url = self.state.inner.validator_client.api_endpoint();
|
||||
info!("Using validator API - {}", validator_api_url);
|
||||
|
||||
// spawn concurrent tasks
|
||||
|
||||
+6
-2
@@ -1,11 +1,15 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::ThreadsafeValidatorClient;
|
||||
use mixnet_contract_common::Delegation;
|
||||
|
||||
pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec<Delegation> {
|
||||
let client = crate::client::new_nymd_client();
|
||||
pub(crate) async fn get_single_mixnode_delegations(
|
||||
client: &ThreadsafeValidatorClient,
|
||||
pubkey: &str,
|
||||
) -> Vec<Delegation> {
|
||||
let delegates = match client
|
||||
.0
|
||||
.get_all_nymd_single_mixnode_delegations(pubkey.to_string())
|
||||
.await
|
||||
{
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::ThreadsafeValidatorClient;
|
||||
use crate::mix_node::models::EconomicDynamicsStats;
|
||||
|
||||
pub(crate) async fn retrieve_mixnode_econ_stats(
|
||||
client: &ThreadsafeValidatorClient,
|
||||
identity: &str,
|
||||
) -> Option<EconomicDynamicsStats> {
|
||||
let stake_saturation = client
|
||||
.0
|
||||
.validator_api
|
||||
.get_mixnode_stake_saturation(identity)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let inclusion_probability = client
|
||||
.0
|
||||
.validator_api
|
||||
.get_mixnode_inclusion_probability(identity)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let reward_estimation = client
|
||||
.0
|
||||
.validator_api
|
||||
.get_mixnode_reward_estimation(identity)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
Some(EconomicDynamicsStats {
|
||||
stake_saturation: stake_saturation.saturation,
|
||||
active_set_inclusion_probability: inclusion_probability.in_active,
|
||||
reserve_set_inclusion_probability: inclusion_probability.in_reserve,
|
||||
estimated_total_node_reward: reward_estimation.estimated_total_node_reward,
|
||||
estimated_operator_reward: reward_estimation.estimated_operator_reward,
|
||||
estimated_delegators_reward: reward_estimation.estimated_delegators_reward,
|
||||
current_interval_uptime: reward_estimation.reward_params.node.uptime() as u8,
|
||||
})
|
||||
}
|
||||
@@ -11,8 +11,11 @@ use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
use mixnet_contract_common::Delegation;
|
||||
|
||||
use crate::mix_node::models::{NodeDescription, NodeStats, PrettyDetailedMixNodeBond};
|
||||
use crate::mix_nodes::delegations::get_single_mixnode_delegations;
|
||||
use crate::mix_node::delegations::get_single_mixnode_delegations;
|
||||
use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats;
|
||||
use crate::mix_node::models::{
|
||||
EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond,
|
||||
};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
@@ -21,6 +24,7 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>,
|
||||
get_by_id,
|
||||
get_description,
|
||||
get_stats,
|
||||
get_economic_dynamics_stats,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -43,8 +47,11 @@ pub(crate) async fn get_by_id(
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/<pubkey>/delegations")]
|
||||
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<Delegation>> {
|
||||
Json(get_single_mixnode_delegations(pubkey).await)
|
||||
pub(crate) async fn get_delegations(
|
||||
pubkey: &str,
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<Delegation>> {
|
||||
Json(get_single_mixnode_delegations(&state.inner.validator_client, pubkey).await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
@@ -134,6 +141,31 @@ pub(crate) async fn get_stats(
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/<pubkey>/economic-dynamics-stats")]
|
||||
pub(crate) async fn get_economic_dynamics_stats(
|
||||
pubkey: &str,
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Option<Json<EconomicDynamicsStats>> {
|
||||
match state.inner.mixnode.get_econ_stats(pubkey).await {
|
||||
Some(cache_value) => {
|
||||
trace!("Returning cached value for {}", pubkey);
|
||||
Some(Json(cache_value))
|
||||
}
|
||||
None => {
|
||||
trace!("No valid cache value for {}", pubkey);
|
||||
|
||||
// get fresh value from the validator API
|
||||
let econ_stats =
|
||||
retrieve_mixnode_econ_stats(&state.inner.validator_client, pubkey).await?;
|
||||
|
||||
// update cache
|
||||
state.inner.mixnode.set_econ_stats(pubkey, econ_stats).await;
|
||||
Some(Json(econ_stats))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_mix_node_description(host: &str, port: &u16) -> Result<NodeDescription, ReqwestError> {
|
||||
reqwest::get(format!("http://{}:{}/description", host, port))
|
||||
.await?
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
pub(crate) mod delegations;
|
||||
pub(crate) mod econ_stats;
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod models;
|
||||
|
||||
@@ -32,6 +32,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
|
||||
pub(crate) struct MixNodeCache {
|
||||
pub(crate) descriptions: Cache<NodeDescription>,
|
||||
pub(crate) node_stats: Cache<NodeStats>,
|
||||
pub(crate) econ_stats: Cache<EconomicDynamicsStats>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -45,6 +46,7 @@ impl ThreadsafeMixNodeCache {
|
||||
inner: Arc::new(RwLock::new(MixNodeCache {
|
||||
descriptions: Cache::new(),
|
||||
node_stats: Cache::new(),
|
||||
econ_stats: Cache::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -57,6 +59,10 @@ impl ThreadsafeMixNodeCache {
|
||||
self.inner.read().await.node_stats.get(identity_key)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_econ_stats(&self, identity_key: &str) -> Option<EconomicDynamicsStats> {
|
||||
self.inner.read().await.econ_stats.get(identity_key)
|
||||
}
|
||||
|
||||
pub(crate) async fn set_description(&self, identity_key: &str, description: NodeDescription) {
|
||||
self.inner
|
||||
.write()
|
||||
@@ -72,6 +78,18 @@ impl ThreadsafeMixNodeCache {
|
||||
.node_stats
|
||||
.set(identity_key, node_stats);
|
||||
}
|
||||
|
||||
pub(crate) async fn set_econ_stats(
|
||||
&self,
|
||||
identity_key: &str,
|
||||
econ_stats: EconomicDynamicsStats,
|
||||
) {
|
||||
self.inner
|
||||
.write()
|
||||
.await
|
||||
.econ_stats
|
||||
.set(identity_key, econ_stats);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
@@ -103,3 +121,17 @@ pub(crate) struct NodeStats {
|
||||
packets_sent_since_last_update: u64,
|
||||
packets_explicitly_dropped_since_last_update: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Copy, Deserialize, JsonSchema)]
|
||||
pub(crate) struct EconomicDynamicsStats {
|
||||
pub(crate) stake_saturation: f32,
|
||||
|
||||
pub(crate) active_set_inclusion_probability: f32,
|
||||
pub(crate) reserve_set_inclusion_probability: f32,
|
||||
|
||||
pub(crate) estimated_total_node_reward: u64,
|
||||
pub(crate) estimated_operator_reward: u64,
|
||||
pub(crate) estimated_delegators_reward: u64,
|
||||
|
||||
pub(crate) current_interval_uptime: u8,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) mod delegations;
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod location;
|
||||
pub(crate) mod models;
|
||||
|
||||
@@ -5,6 +5,7 @@ use chrono::{DateTime, Utc};
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::client::ThreadsafeValidatorClient;
|
||||
use mixnet_contract_common::MixNodeBond;
|
||||
|
||||
use crate::country_statistics::country_nodes_distribution::{
|
||||
@@ -28,6 +29,9 @@ pub struct ExplorerApiState {
|
||||
pub(crate) mixnodes: ThreadsafeMixNodesCache,
|
||||
pub(crate) ping: ThreadsafePingCache,
|
||||
pub(crate) validators: ThreadsafeValidatorCache,
|
||||
|
||||
// TODO: discuss with @MS whether this is an appropriate spot for it
|
||||
pub(crate) validator_client: ThreadsafeValidatorClient,
|
||||
}
|
||||
|
||||
impl ExplorerApiState {
|
||||
@@ -75,6 +79,7 @@ impl ExplorerApiStateContext {
|
||||
),
|
||||
ping: ThreadsafePingCache::new(),
|
||||
validators: ThreadsafeValidatorCache::new(),
|
||||
validator_client: ThreadsafeValidatorClient::new(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -90,6 +95,7 @@ impl ExplorerApiStateContext {
|
||||
mixnodes: ThreadsafeMixNodesCache::new(),
|
||||
ping: ThreadsafePingCache::new(),
|
||||
validators: ThreadsafeValidatorCache::new(),
|
||||
validator_client: ThreadsafeValidatorClient::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,16 @@ use validator_client::nymd::error::NymdError;
|
||||
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
use crate::client::new_nymd_client;
|
||||
use crate::mix_nodes::CACHE_REFRESH_RATE;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub(crate) struct ExplorerApiTasks {
|
||||
state: ExplorerApiStateContext,
|
||||
validator_client: validator_client::Client<QueryNymdClient>,
|
||||
}
|
||||
|
||||
impl ExplorerApiTasks {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
|
||||
ExplorerApiTasks {
|
||||
state,
|
||||
validator_client: new_nymd_client(),
|
||||
}
|
||||
ExplorerApiTasks { state }
|
||||
}
|
||||
|
||||
// a helper to remove duplicate code when grabbing active/rewarded/all mixnodes
|
||||
@@ -31,7 +26,7 @@ impl ExplorerApiTasks {
|
||||
F: FnOnce(&'a validator_client::Client<QueryNymdClient>) -> Fut,
|
||||
Fut: Future<Output = Result<Vec<MixNodeBond>, ValidatorClientError>>,
|
||||
{
|
||||
let bonds = match f(&self.validator_client).await {
|
||||
let bonds = match f(&self.state.inner.validator_client.0).await {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
error!("Unable to retrieve mixnode bonds: {:?}", e);
|
||||
@@ -51,18 +46,29 @@ impl ExplorerApiTasks {
|
||||
|
||||
async fn retrieve_all_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
info!("About to retrieve all gateways...");
|
||||
self.validator_client.get_cached_gateways().await
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.get_cached_gateways()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn retrieve_all_validators(&self) -> Result<ValidatorResponse, NymdError> {
|
||||
info!("About to retrieve all validators...");
|
||||
let height = self
|
||||
.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.nymd
|
||||
.get_current_block_height()
|
||||
.await?;
|
||||
let response: ValidatorResponse = self
|
||||
.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.nymd
|
||||
.get_validators(height.value(), Paging::All)
|
||||
.await?;
|
||||
|
||||
@@ -18,6 +18,8 @@ We use the following:
|
||||
|
||||
## Development mode
|
||||
|
||||
Copy the `.env.prod` file to `.env` to configure your environment. Using the live sandbox Explorer API is the best way to do development, so the prod settings are good.
|
||||
|
||||
Run the following:
|
||||
|
||||
```
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DiscordIcon } from '../icons/socials/DiscordIcon';
|
||||
export const TELEGRAM_LINK = 'https://t.me/nymchan';
|
||||
export const TWITTER_LINK = 'https://twitter.com/nymproject';
|
||||
export const GITHUB_LINK = 'https://github.com/nymtech';
|
||||
export const DISCORD_LINK = 'https://discord.gg/jUqJYGB5';
|
||||
export const DISCORD_LINK = 'https://discord.gg/ggxrUpbNnn';
|
||||
|
||||
export const Socials: React.FC<{ isFooter?: boolean }> = ({ isFooter }) => {
|
||||
const theme = useTheme();
|
||||
@@ -22,11 +22,9 @@ export const Socials: React.FC<{ isFooter?: boolean }> = ({ isFooter }) => {
|
||||
<IconButton component="a" href={TELEGRAM_LINK} target="_blank" data-testid="telegram">
|
||||
<TelegramIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
{false && (
|
||||
<IconButton component="a" href={DISCORD_LINK} target="_blank" data-testid="discord">
|
||||
<DiscordIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton component="a" href={DISCORD_LINK} target="_blank" data-testid="discord">
|
||||
<DiscordIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
<IconButton component="a" href={TWITTER_LINK} target="_blank" data-testid="twitter">
|
||||
<TwitterIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ validator-client = { path = "../common/client-libs/validator-client", features =
|
||||
version-checker = { path = "../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
coconut = ["coconut-interface", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut"]
|
||||
eth = []
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -27,7 +27,7 @@ coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface"]
|
||||
coconut = ["coconut-interface", "credentials/coconut"]
|
||||
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13.0"
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: '@storybook/react',
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
},
|
||||
// webpackFinal: async (config, { configType }) => {
|
||||
// // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
||||
// // You can change the configuration based on that.
|
||||
// // 'PRODUCTION' is used when building the static version of storybook.
|
||||
webpackFinal: async (config) => {
|
||||
config.module.rules.forEach((rule) => {
|
||||
// look for SVG import rule and replace
|
||||
// NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
|
||||
if (rule.test?.toString().includes('svg')) {
|
||||
rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
|
||||
}
|
||||
});
|
||||
|
||||
// handle asset loading with this
|
||||
config.module.rules.unshift({
|
||||
test: /\.svg(\?.*)?$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
|
||||
config.resolve.extensions = ['.tsx', '.ts', '.js'];
|
||||
config.resolve.plugins = [new TsconfigPathsPlugin()];
|
||||
|
||||
config.plugins.push(new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
mode: 'write-references',
|
||||
diagnosticOptions: {
|
||||
semantic: true,
|
||||
syntactic: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
if (!config.resolve.alias) {
|
||||
config.resolve.alias = {};
|
||||
}
|
||||
|
||||
config.resolve.alias['@tauri-apps/api'] = `${__dirname}/mocks/tauri`;
|
||||
|
||||
// Return the altered config
|
||||
return config;
|
||||
},
|
||||
features: {
|
||||
emotionAlias: false,
|
||||
},
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user