created nym-id for importing credentials
This commit is contained in:
Generated
+26
@@ -5626,6 +5626,32 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-id"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58 0.5.0",
|
||||
"clap 4.4.7",
|
||||
"nym-bin-common",
|
||||
"nym-credential-storage",
|
||||
"nym-id-lib",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-id-lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-credential-storage",
|
||||
"nym-credentials",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-inclusion-probability"
|
||||
version = "0.1.0"
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ members = [
|
||||
"wasm/client",
|
||||
# "wasm/full-nym-wasm",
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/node-tester", "nym-id", "common/nym-id-lib",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::{QueryClient, SigningClient};
|
||||
use anyhow::{anyhow, bail};
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use futures::StreamExt;
|
||||
use log::{error, info};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_credential_utils::utils::block_until_coconut_is_available;
|
||||
use nym_credentials::coconut::bandwidth::bandwidth_credential_params;
|
||||
use nym_credentials::coconut::bandwidth::freepass::MAX_FREE_PASS_VALIDITY;
|
||||
use nym_credentials::coconut::utils;
|
||||
use nym_credentials::{
|
||||
obtain_aggregate_verification_key, IssuanceBandwidthCredential, IssuedBandwidthCredential,
|
||||
};
|
||||
use nym_credentials_interface::VerificationKey;
|
||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use nym_validator_client::signing::AccountData;
|
||||
use nym_validator_client::CoconutApiClient;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Path to the output directory for generated free passes.
|
||||
#[clap(long)]
|
||||
pub(crate) pass: PathBuf,
|
||||
}
|
||||
|
||||
// async fn get_freepass(
|
||||
// api_clients: Vec<CoconutApiClient>,
|
||||
// aggregate_vk: &VerificationKey,
|
||||
// threshold: u64,
|
||||
// epoch_id: EpochId,
|
||||
// signing_account: &AccountData,
|
||||
// expiration_date: OffsetDateTime,
|
||||
// ) -> anyhow::Result<IssuedBandwidthCredential> {
|
||||
// let issuance_pass = IssuanceBandwidthCredential::new_freepass(Some(expiration_date));
|
||||
// let signing_data = issuance_pass.prepare_for_signing();
|
||||
//
|
||||
// let credential_shares = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
||||
//
|
||||
// futures::stream::iter(api_clients)
|
||||
// .for_each_concurrent(None, |client| async {
|
||||
// // move the client into the block
|
||||
// let client = client;
|
||||
// let api_url = client.api_client.api_url();
|
||||
//
|
||||
// info!("contacting {api_url} for blinded free pass");
|
||||
//
|
||||
// match issuance_pass
|
||||
// .obtain_partial_freepass_credential(
|
||||
// &client.api_client,
|
||||
// signing_account,
|
||||
// &client.verification_key,
|
||||
// signing_data.clone(),
|
||||
// )
|
||||
// .await
|
||||
// {
|
||||
// Ok(partial_credential) => {
|
||||
// credential_shares
|
||||
// .lock()
|
||||
// .await
|
||||
// .push((partial_credential, client.node_id).into());
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("failed to obtain partial free pass from {api_url}: {err}")
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// .await;
|
||||
//
|
||||
// // SAFETY: the futures have completed, so we MUST have the only arc reference
|
||||
// #[allow(clippy::unwrap_used)]
|
||||
// let credential_shares = Arc::into_inner(credential_shares).unwrap().into_inner();
|
||||
//
|
||||
// if credential_shares.len() < threshold as usize {
|
||||
// bail!("we managed to obtain only {} partial credentials while the minimum threshold is {threshold}", credential_shares.len());
|
||||
// }
|
||||
//
|
||||
// let signature = issuance_pass.aggregate_signature_shares(aggregate_vk, &credential_shares)?;
|
||||
// Ok(issuance_pass.into_issued_credential(signature, epoch_id))
|
||||
// }
|
||||
|
||||
pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> {
|
||||
let raw_credential = fs::read(args.pass)?;
|
||||
let credential = IssuedBandwidthCredential::unpack_v1(&raw_credential)?;
|
||||
|
||||
println!("{:?}", credential.get_plain_public_attributes());
|
||||
println!("{}", credential.epoch_id());
|
||||
println!("{:?}", credential.variant_data());
|
||||
|
||||
let api_clients = all_coconut_api_clients(&client, credential.epoch_id()).await?;
|
||||
|
||||
let vk = utils::obtain_aggregate_verification_key(&api_clients)?;
|
||||
|
||||
let foomp = credential.prepare_for_spending(&vk)?;
|
||||
|
||||
let res = foomp.verify(bandwidth_credential_params(), &vk);
|
||||
println!("res: {res}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -315,9 +315,12 @@ impl IssuanceBandwidthCredential {
|
||||
}
|
||||
|
||||
// TODO: is that actually needed?
|
||||
// idea: make it consistent with the issued credential and its vX serde
|
||||
pub fn try_from_recovered_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
use bincode::Options;
|
||||
Ok(make_recovery_bincode_serializer().deserialize(bytes)?)
|
||||
make_recovery_bincode_serializer()
|
||||
.deserialize(bytes)
|
||||
.map_err(|source| Error::RecoveryCredentialDeserializationFailure { source })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,3 +330,18 @@ fn make_recovery_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
fn assert_zeroize<T: Zeroize>() {}
|
||||
|
||||
#[test]
|
||||
fn credential_is_zeroized() {
|
||||
assert_zeroize::<IssuanceBandwidthCredential>();
|
||||
assert_zeroize_on_drop::<IssuanceBandwidthCredential>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,15 @@ impl IssuedBandwidthCredential {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_unpack(bytes: &[u8], revision: impl Into<Option<u8>>) -> Result<Self, Error> {
|
||||
let revision = revision.into().unwrap_or(CURRENT_SERIALIZATION_REVISION);
|
||||
|
||||
match revision {
|
||||
1 => Self::unpack_v1(bytes),
|
||||
_ => Err(Error::UnknownSerializationRevision { revision }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoch_id(&self) -> EpochId {
|
||||
self.epoch_id
|
||||
}
|
||||
@@ -138,7 +147,12 @@ impl IssuedBandwidthCredential {
|
||||
/// Unpack (deserialize) the credential data from the given bytes using v1 serializer.
|
||||
pub fn unpack_v1(bytes: &[u8]) -> Result<Self, Error> {
|
||||
use bincode::Options;
|
||||
Ok(make_storable_bincode_serializer().deserialize(bytes)?)
|
||||
make_storable_bincode_serializer()
|
||||
.deserialize(bytes)
|
||||
.map_err(|source| Error::SerializationFailure {
|
||||
source,
|
||||
revision: 1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn randomise_signature(&mut self) {
|
||||
@@ -191,3 +205,18 @@ fn make_storable_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
fn assert_zeroize<T: Zeroize>() {}
|
||||
|
||||
#[test]
|
||||
fn credential_is_zeroized() {
|
||||
assert_zeroize::<IssuedBandwidthCredential>();
|
||||
assert_zeroize_on_drop::<IssuedBandwidthCredential>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use nym_credentials_interface::CoconutError;
|
||||
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
|
||||
use crate::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -12,8 +13,18 @@ pub enum Error {
|
||||
#[error("IO error")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("failed to (de)serialize credential structure: {0}")]
|
||||
SerializationFailure(#[from] bincode::Error),
|
||||
#[error("failed to deserialize a recovery credential: {source}")]
|
||||
RecoveryCredentialDeserializationFailure { source: bincode::Error },
|
||||
|
||||
#[error("failed to (de)serialize provided credential using revision {revision}: {source}")]
|
||||
SerializationFailure {
|
||||
#[source]
|
||||
source: bincode::Error,
|
||||
revision: u8,
|
||||
},
|
||||
|
||||
#[error("unknown credential serializatio revision {revision}. the current (and max supported) version is {CURRENT_SERIALIZATION_REVISION}")]
|
||||
UnknownSerializationRevision { revision: u8 },
|
||||
|
||||
#[error("The detailed description is yet to be determined")]
|
||||
BandwidthCredentialError,
|
||||
|
||||
@@ -9,3 +9,4 @@ pub use coconut::bandwidth::{
|
||||
IssuedBandwidthCredential,
|
||||
};
|
||||
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||
pub use error::Error;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "nym-id-lib"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tracing.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NymIdError {
|
||||
#[error("failed to deserialize provided credential: {source}")]
|
||||
CredentialDeserializationFailure { source: nym_credentials::Error },
|
||||
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: OffsetDateTime },
|
||||
|
||||
#[error("failed to store credential in the provided store: {source}")]
|
||||
StorageError {
|
||||
source: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::NymIdError;
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use tracing::{debug, warn};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub async fn import_credential<S>(
|
||||
credentials_store: S,
|
||||
raw_credential: Vec<u8>,
|
||||
credential_version: impl Into<Option<u8>>,
|
||||
) -> Result<(), NymIdError>
|
||||
where
|
||||
S: Storage,
|
||||
<S as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let raw_credential = Zeroizing::new(raw_credential);
|
||||
|
||||
// note: the type itself implements ZeroizeOnDrop
|
||||
let credential = IssuedBandwidthCredential::try_unpack(&raw_credential, credential_version)
|
||||
.map_err(|source| NymIdError::CredentialDeserializationFailure { source })?;
|
||||
|
||||
debug!(
|
||||
"attempting to import credential of type {}",
|
||||
credential.typ()
|
||||
);
|
||||
|
||||
match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||
debug!("with value of {}", voucher_info.value())
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
debug!("with expiry at {}", freepass_info.expiry_date());
|
||||
if freepass_info.expired() {
|
||||
warn!("the free pass has already expired!");
|
||||
|
||||
// technically we can import it, but the gateway will just reject it so what's the point
|
||||
return Err(NymIdError::ExpiredCredentialImport {
|
||||
expiration: freepass_info.expiry_date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY:
|
||||
// for the epoch to run over u32::MAX, we'd have to advance it for few centuries every block...
|
||||
// the alternative is a very particularly malformed serialized data, but at that point blowing up is the right call
|
||||
// because we can't rely on it anyway
|
||||
#[allow(clippy::expect_used)]
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: credential.current_serialization_revision(),
|
||||
credential_data: &raw_credential,
|
||||
credential_type: credential.typ().to_string(),
|
||||
epoch_id: credential
|
||||
.epoch_id()
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
};
|
||||
|
||||
credentials_store
|
||||
.insert_issued_credential(storable)
|
||||
.await
|
||||
.map_err(|source| NymIdError::StorageError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod error;
|
||||
pub mod import_credential;
|
||||
|
||||
pub use error::NymIdError;
|
||||
pub use import_credential::import_credential;
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "nym-id"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
bs58.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
tracing.workspace = true
|
||||
|
||||
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] }
|
||||
nym-credential-storage = { path = "../common/credential-storage" }
|
||||
nym-id-lib = { path = "../common/nym-id-lib" }
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: Args) {
|
||||
println!("{}", args.output.format(&bin_info_owned!()))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::ArgGroup;
|
||||
use nym_id_lib::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||
pub(crate) struct Args {
|
||||
/// Explicitly provide the encoded credential data (as base58)
|
||||
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||
pub(crate) credential_data: Option<Vec<u8>>,
|
||||
|
||||
/// Specifies the path to file containing binary credential data
|
||||
#[clap(long, group = "cred_data")]
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
/// Specifies path to the credentials storage
|
||||
#[clap(long)]
|
||||
pub credentials_store_path: PathBuf,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
let credentials_store =
|
||||
nym_credential_storage::initialise_persistent_storage(args.credentials_store_path).await;
|
||||
|
||||
let raw_credential = match args.credential_data {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
// SAFETY: one of those arguments must have been set
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod build_info;
|
||||
mod import_credential;
|
||||
mod setup;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use nym_bin_common::bin_info;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub async fn execute(self) -> anyhow::Result<()> {
|
||||
match self.command {
|
||||
Commands::ImportCredential(args) => import_credential::execute(args).await?,
|
||||
Commands::BuildInfo(args) => build_info::execute(args),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
// TODO: to be determined how it's going to work in nymvpn et al.
|
||||
// ///
|
||||
// Setup,
|
||||
|
||||
/// Attempt to import a bandwidth credential into the provided storage.
|
||||
ImportCredential(import_credential::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::Args),
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use crate::commands::Cli;
|
||||
use clap::Parser;
|
||||
use nym_bin_common::logging::setup_tracing_logger;
|
||||
|
||||
mod commands;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
setup_tracing_logger();
|
||||
let cli = Cli::parse();
|
||||
|
||||
cli.execute().await
|
||||
}
|
||||
Reference in New Issue
Block a user