Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ZK BBS+-based selectively disclosable credentials (JPT) #1355

Merged
merged 35 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6003cf0
Support BBS+ and JWP (#1285)
AlbertoSvg Mar 4, 2024
bdf6b54
merge main
UMR1352 Mar 19, 2024
37d1bd5
Wasm bindings for Jpt credentials
UMR1352 Mar 20, 2024
a02f6fe
JPT presentation bindings
UMR1352 Mar 20, 2024
7b45d70
docs
UMR1352 Mar 20, 2024
592bd9c
jsonprooftoken payloads
UMR1352 Mar 21, 2024
c102864
Refactor `RevocationTimeframeStatus` to align with other setups (#1340)
wulfraem Mar 21, 2024
fd2070c
binding coverage for jsonprooftoken
UMR1352 Mar 22, 2024
711d4ae
Use latest releases of zkryptium/json-proof-token and add new BLS key…
AlbertoSvg Mar 25, 2024
be35e7b
Use zkryptium for cryptographic operations inside Memstore (#1351)
AlbertoSvg Apr 17, 2024
e835a71
Feat/jpt bbs+ sd stronghold impl (#1354)
UMR1352 Apr 24, 2024
ae8e022
rename JwkStorageExt to JwkStorageBbsPlusExt
UMR1352 Apr 24, 2024
4a152d0
JwkStorageBbsPlusExt impl refactor for Stronghold, MemStore, WasmStore
UMR1352 Apr 25, 2024
be022b0
Squashed commit of the following:
UMR1352 Apr 25, 2024
3a6f951
Merge branch 'main' into feat/jpt-bbs+-sd
UMR1352 Apr 25, 2024
94565c9
clippy
UMR1352 Apr 25, 2024
4738605
fmt
UMR1352 Apr 25, 2024
6a32429
add stronghold bbs+ tests
UMR1352 Apr 25, 2024
23a2f46
review comments
UMR1352 Apr 25, 2024
e014728
add license header
UMR1352 Apr 25, 2024
614719b
fix wasm bindings
UMR1352 Apr 26, 2024
096bb30
Persist Stronghold's changes only when its handle is dropped
UMR1352 Apr 26, 2024
b347a1e
Fix StrongholdStorage::get_public_key
UMR1352 Apr 29, 2024
453103c
rename stronghold_jwk_storage_ext
UMR1352 Apr 29, 2024
9632e8d
Add inx-faucet profile in CI
UMR1352 Apr 29, 2024
479f4bc
change stronghold crate's structure, revert persist changes on drop
UMR1352 Apr 30, 2024
87a744c
review comments
UMR1352 Apr 30, 2024
ef85f1e
Update identity_credential/src/presentation/jwp_presentation_builder.rs
UMR1352 Apr 30, 2024
041fbab
fix wasm bindings
UMR1352 Apr 30, 2024
5135f0b
expose stronghold's key types
UMR1352 Apr 30, 2024
63df8d4
revert last commit
UMR1352 May 8, 2024
b2f6277
Add "Fondazione Links" to license header
UMR1352 May 13, 2024
4cf455d
Squashed commit of the following:
UMR1352 May 21, 2024
5cc7f3b
update stronghold and sdk
UMR1352 May 22, 2024
dc0dc96
fix conflicts with main
UMR1352 May 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/iota-sandbox/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ runs:

# Start Tangle
sudo ./bootstrap.sh
docker compose up -d
docker compose --profile inx-faucet up -d
- name: Wait for tangle to start
shell: bash
run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/$WAIT_FOR_VERSION/wait-for | sh -s -- -t 60 http://localhost/health -- echo "Tangle is up"
Expand Down
19 changes: 17 additions & 2 deletions .github/actions/rust/rust-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@ runs:
shell: bash
run: |

if ! rustup self update; then
# self update is currently broken on Windows runners:
# /~https://github.com/rust-lang/rustup/issues/3709
# so we'll skip self update for windows
OS=${{ inputs.os }}
IS_WINDOWS=false; [[ $OS =~ ^[wW]indows ]] && IS_WINDOWS=true

if [[ $IS_WINDOWS = true ]] ;
then
echo "skipping self update on windows runner due to /~https://github.com/rust-lang/rustup/issues/3709"
elif ! rustup self update; then
echo "rustup self update failed"
fi

Expand All @@ -57,7 +66,13 @@ runs:
rustup target add $TARGET
fi

rustup update
if [[ $IS_WINDOWS = true ]] ;
then
echo "skipping self update on windows runner due to /~https://github.com/rust-lang/rustup/issues/3709"
rustup update --no-self-update
else
rustup update
fi

TOOLCHAIN=${{ inputs.toolchain }}
if [[ $TOOLCHAIN != 'stable' ]]; then
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ jobs:
steps:
- uses: actions/checkout@v3

- name: Ensure, OpenSSL is available in Windows
if: matrix.os == 'windows-latest'
run: |
echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
vcpkg install openssl:x64-windows-static-md

- name: Setup Rust and cache
uses: './.github/actions/rust/rust-setup'
with:
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/grpc-publish-to-dockerhub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ jobs:
context: .
file: bindings/grpc/Dockerfile
push: ${{ !inputs.dry-run }}
labels: iotaledger/identity-grpc:${{ inputs.tag }}
tags: iotaledger/identity-grpc:${{ inputs.tag }}

- name: Docker Hub Description
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae
if: ${{ !inputs.dry-run }}
with:
username: ${{ secrets.IOTALEDGER_DOCKER_USERNAME }}
password: ${{ secrets.IOTALEDGER_DOCKER_PASSWORD }}
repository: iotaledger/identity-grpc
readme-filepath: ./bindigns/grpc/README.md
readme-filepath: ./bindings/grpc/README.md
short-description: ${{ github.event.repository.description }}

2 changes: 1 addition & 1 deletion .license_template
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// Copyright {20\d{2}(-20\d{2})?} IOTA Stiftung
// Copyright {20\d{2}(-20\d{2})?} IOTA Stiftung{(?:, .+)?}
// SPDX-License-Identifier: Apache-2.0
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"identity_verification",
"identity_stronghold",
"identity_jose",
"identity_ecdsa_verifier",
"identity_eddsa_verifier",
"examples",
]
Expand All @@ -23,6 +24,8 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv
thiserror = { version = "1.0", default-features = false }
strum = { version = "0.25", default-features = false, features = ["std", "derive"] }
serde_json = { version = "1.0", default-features = false }
json-proof-token = { version = "0.3.5" }
zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] }

[workspace.package]
authors = ["IOTA Stiftung"]
Expand Down
2 changes: 1 addition & 1 deletion bindings/grpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ futures = { version = "0.3" }
identity_eddsa_verifier = { path = "../../identity_eddsa_verifier" }
identity_iota = { path = "../../identity_iota", features = ["resolver", "sd-jwt", "domain-linkage", "domain-linkage-fetch", "status-list-2021"] }
identity_stronghold = { path = "../../identity_stronghold", features = ["send-sync-storage"] }
iota-sdk = { version = "1.1.2", features = ["stronghold"] }
iota-sdk = { version = "1.1.5", features = ["stronghold"] }
openssl = { version = "0.10", features = ["vendored"] }
prost = "0.12"
rand = "0.8.5"
Expand Down
26 changes: 13 additions & 13 deletions bindings/grpc/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Identity.rs gRPC Bindings
This project provides the functionalities of [Identity.rs](/~https://github.com/iotaledger/identity.rs) in a language-agnostic way through a [gRPC](https://grpc.io) server.

The server can easily be run with docker using [this dockerfile](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/Dockerfile).
The server can easily be run with docker using [this dockerfile](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/Dockerfile).

## Build
Run `docker build -f bindings/grpc/Dockerfile -t iotaleger/identity-grpc .` from the project root.
Expand All @@ -17,17 +17,17 @@ Make sure to provide a valid stronghold snapshot at the provided `SNAPSHOT_PATH`
### Available services
| Service description | Service Id | Proto File |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------|
| Credential Revocation Checking | `credentials/CredentialRevocation.check` | [credentials.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/credentials.proto) |
| SD-JWT Validation | `sd_jwt/Verification.verify` | [sd_jwt.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/sd_jwt.proto) |
| Credential JWT creation | `credentials/Jwt.create` | [credentials.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/credentials.proto) |
| Credential JWT validation | `credentials/VcValidation.validate` | [credentials.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/credentials.proto) |
| DID Document Creation | `document/DocumentService.create` | [document.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/document.proto) |
| Domain Linkage - validate domain, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_domain` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) |
| Domain Linkage - validate domain, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_domain_against_did_configuration` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) |
| Domain Linkage - validate endpoints in DID, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_did` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) |
| Domain Linkage - validate endpoints in DID, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_did_against_did_configurations` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) |
| `StatusList2021Credential` creation | `status_list_2021/StatusList2021Svc.create` | [status_list_2021.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/status_list_2021.proto) |
| `StatusList2021Credential` update | `status_list_2021/StatusList2021Svc.update` | [status_list_2021.proto](/~https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/status_list_2021.proto) |
| Credential Revocation Checking | `credentials/CredentialRevocation.check` | [credentials.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/credentials.proto) |
| SD-JWT Validation | `sd_jwt/Verification.verify` | [sd_jwt.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/sd_jwt.proto) |
| Credential JWT creation | `credentials/Jwt.create` | [credentials.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/credentials.proto) |
| Credential JWT validation | `credentials/VcValidation.validate` | [credentials.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/credentials.proto) |
| DID Document Creation | `document/DocumentService.create` | [document.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/document.proto) |
| Domain Linkage - validate domain, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_domain` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) |
| Domain Linkage - validate domain, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_domain_against_did_configuration` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) |
| Domain Linkage - validate endpoints in DID, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_did` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) |
| Domain Linkage - validate endpoints in DID, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_did_against_did_configurations` | [domain_linkage.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) |
| `StatusList2021Credential` creation | `status_list_2021/StatusList2021Svc.create` | [status_list_2021.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/status_list_2021.proto) |
| `StatusList2021Credential` update | `status_list_2021/StatusList2021Svc.update` | [status_list_2021.proto](/~https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/status_list_2021.proto) |

## Testing

Expand Down Expand Up @@ -62,7 +62,7 @@ In order to test domain linkage, you need access to a server that is reachable v
1. for convenience, you can find a script to start the HTTP server, that you can adjust in `tooling/start-http-server.sh`, don't forget to insert your static domain or to remove the `--domain` parameter

#### Domain linkage credential
1. copy the public url and insert it into [6_domain_linkage.rs](../../examples/1_advanced/6_domain_linkage.rs) as domain 1, e.g. `let domain_1: Url = Url::parse("https://0d40-2003-d3-2710-e200-485f-e8bb-7431-79a7.ngrok-free.app")?;`
1. copy the public url and insert it into [6_domain_linkage.rs](/~https://github.com/iotaledger/identity.rs/blob/main/examples/1_advanced/6_domain_linkage.rs) as domain 1, e.g. `let domain_1: Url = Url::parse("https://0d40-2003-d3-2710-e200-485f-e8bb-7431-79a7.ngrok-free.app")?;`
.1 run the example with `cargo run --release --example 6_domain_linkage`

#### GRPC server
Expand Down
23 changes: 23 additions & 0 deletions bindings/grpc/proto/utils.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

syntax = "proto3";
package utils;

message DataSigningRequest {
// Raw data that will be signed.
bytes data = 1;
// Signing key's ID.
string key_id = 2;
}

message DataSigningResponse {
// Raw data signature.
bytes signature = 1;
}

// Service that handles signing operations on raw data.
service Signing {
rpc sign(DataSigningRequest) returns (DataSigningResponse);
}

22 changes: 19 additions & 3 deletions bindings/grpc/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use anyhow::Context;
use identity_grpc::server::GRpcServer;
use identity_stronghold::StrongholdStorage;
use iota_sdk::client::stronghold::StrongholdAdapter;
Expand Down Expand Up @@ -29,11 +30,18 @@ async fn main() -> anyhow::Result<()> {

#[tracing::instrument]
fn init_stronghold() -> anyhow::Result<StrongholdStorage> {
let stronghold_password = std::env::var("STRONGHOLD_PWD")?;
let snapshot_path = std::env::var("SNAPSHOT_PATH")?;
use std::env;
use std::fs;
let stronghold_password = env::var("STRONGHOLD_PWD_FILE")
.context("Unset \"STRONGHOLD_PWD_FILE\" env variable")
.and_then(|path| fs::read_to_string(&path).context(format!("{path} does not exists")))
.map(sanitize_pwd)
.or(env::var("STRONGHOLD_PWD"))
.context("No password for stronghold was provided")?;
let snapshot_path = env::var("SNAPSHOT_PATH")?;

// Check for snapshot file at specified path
let metadata = std::fs::metadata(&snapshot_path)?;
let metadata = fs::metadata(&snapshot_path)?;
if !metadata.is_file() {
return Err(anyhow::anyhow!("No snapshot at provided path \"{}\"", &snapshot_path));
}
Expand All @@ -45,3 +53,11 @@ fn init_stronghold() -> anyhow::Result<StrongholdStorage> {
.map(StrongholdStorage::new)?,
)
}

/// Remove any trailing whitespace in-place.
fn sanitize_pwd(mut pwd: String) -> String {
let trimmed = pwd.trim_end();
pwd.truncate(trimmed.len());
pwd.shrink_to_fit();
pwd
}
2 changes: 2 additions & 0 deletions bindings/grpc/src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod domain_linkage;
pub mod health_check;
pub mod sd_jwt;
pub mod status_list_2021;
pub mod utils;

use identity_stronghold::StrongholdStorage;
use iota_sdk::client::Client;
Expand All @@ -21,6 +22,7 @@ pub fn routes(client: &Client, stronghold: &StrongholdStorage) -> Routes {
routes.add_service(domain_linkage::service(client));
routes.add_service(document::service(client, stronghold));
routes.add_service(status_list_2021::service());
routes.add_service(utils::service(stronghold));

routes.routes()
}
67 changes: 67 additions & 0 deletions bindings/grpc/src/services/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use _utils::signing_server::Signing as SigningSvc;
use _utils::signing_server::SigningServer;
use _utils::DataSigningRequest;
use _utils::DataSigningResponse;
use identity_iota::storage::JwkStorage;
use identity_iota::storage::KeyId;
use identity_iota::storage::KeyStorageError;
use identity_stronghold::StrongholdStorage;
use tonic::Request;
use tonic::Response;
use tonic::Status;

mod _utils {
tonic::include_proto!("utils");
}

#[derive(Debug, thiserror::Error)]
#[error("Key storage error: {0}")]
pub struct Error(#[from] KeyStorageError);

impl From<Error> for Status {
fn from(value: Error) -> Self {
Status::internal(value.to_string())
}
}

pub struct SigningService {
storage: StrongholdStorage,
}

impl SigningService {
pub fn new(stronghold: &StrongholdStorage) -> Self {
Self {
storage: stronghold.clone(),
}
}
}

#[tonic::async_trait]
impl SigningSvc for SigningService {
#[tracing::instrument(
name = "utils/sign",
skip_all,
fields(request = ?req.get_ref())
ret,
err,
)]
async fn sign(&self, req: Request<DataSigningRequest>) -> Result<Response<DataSigningResponse>, Status> {
let DataSigningRequest { data, key_id } = req.into_inner();
let key_id = KeyId::new(key_id);
let public_key_jwk = self.storage.get_public_key(&key_id).await.map_err(Error)?;
let signature = self
.storage
.sign(&key_id, &data, &public_key_jwk)
.await
.map_err(Error)?;

Ok(Response::new(DataSigningResponse { signature }))
}
}

pub fn service(stronghold: &StrongholdStorage) -> SigningServer<SigningService> {
SigningServer::new(SigningService::new(stronghold))
}
1 change: 1 addition & 0 deletions bindings/grpc/tests/api/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ mod helpers;
mod jwt;
mod sd_jwt_validation;
mod status_list_2021;
mod utils;
48 changes: 48 additions & 0 deletions bindings/grpc/tests/api/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use _utils::signing_client::SigningClient;
use _utils::DataSigningRequest;
use identity_iota::verification::jws::JwsAlgorithm;
use identity_storage::JwkStorage;
use identity_storage::KeyType;
use identity_stronghold::StrongholdStorage;

use crate::helpers::make_stronghold;
use crate::helpers::TestServer;

mod _utils {
tonic::include_proto!("utils");
}

const SAMPLE_SIGNING_DATA: &'static [u8] = b"I'm just some random data to be signed :)";

#[tokio::test]
async fn raw_data_signing_works() -> anyhow::Result<()> {
let stronghold = StrongholdStorage::new(make_stronghold());
let server = TestServer::new_with_stronghold(stronghold.clone()).await;

let key_id = stronghold
.generate(KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA)
.await?
.key_id;

let expected_signature = {
let public_key_jwk = stronghold.get_public_key(&key_id).await?;
stronghold.sign(&key_id, SAMPLE_SIGNING_DATA, &public_key_jwk).await?
};

let mut grpc_client = SigningClient::connect(server.endpoint()).await?;
let signature = grpc_client
.sign(DataSigningRequest {
data: SAMPLE_SIGNING_DATA.to_owned(),
key_id: key_id.to_string(),
})
.await?
.into_inner()
.signature;

assert_eq!(signature, expected_signature);

Ok(())
}
9 changes: 8 additions & 1 deletion bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ console_error_panic_hook = { version = "0.1" }
futures = { version = "0.3" }
identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] }
js-sys = { version = "0.3.61" }
json-proof-token = "0.3.4"
proc_typescript = { version = "0.1.0", path = "./proc_typescript" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false }
Expand All @@ -29,11 +30,12 @@ serde_repr = { version = "0.1", default-features = false }
tokio = { version = "1.29", default-features = false, features = ["sync"] }
wasm-bindgen = { version = "0.2.85", features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "0.4", default-features = false }
zkryptium = "0.2.2"

[dependencies.identity_iota]
path = "../../identity_iota"
default-features = false
features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021"]
features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus"]

[dev-dependencies]
rand = "0.8.5"
Expand All @@ -45,3 +47,8 @@ instant = { version = "0.1", default-features = false, features = ["wasm-bindgen
[profile.release]
opt-level = 's'
lto = true

[lints.clippy]
# can be removed as soon as fix has been added to clippy
# see /~https://github.com/rust-lang/rust-clippy/issues/12377
empty_docs = "allow"
Loading