reintroduced handling of old v1 credentials
This commit is contained in:
@@ -384,7 +384,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
Ok(())
|
||||
}
|
||||
Some(v) if v != PROTOCOL_VERSION => {
|
||||
Some(v) if v > PROTOCOL_VERSION => {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: Some(v),
|
||||
current: PROTOCOL_VERSION,
|
||||
@@ -394,7 +394,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
Some(_) => {
|
||||
info!("the gateway is using exactly the same protocol version as we are. We're good to continue!");
|
||||
info!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -522,7 +522,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
let mut rng = OsRng;
|
||||
let iv = IV::new_random(&mut rng);
|
||||
|
||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential_v2(
|
||||
credential,
|
||||
epoch_id,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
|
||||
@@ -6,6 +6,106 @@ use nym_credentials::coconut::bandwidth::CredentialSpendingData;
|
||||
use nym_credentials_interface::{CoconutError, VerifyCredentialRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// reimplements old coconut-interface::Credential for backwards compatibility sake
|
||||
// (so that 'new' gateways could still understand those requests)
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct OldV1Credential {
|
||||
pub n_params: u32,
|
||||
|
||||
pub theta: VerifyCredentialRequest,
|
||||
|
||||
pub voucher_value: u64,
|
||||
|
||||
pub voucher_info: String,
|
||||
|
||||
pub epoch_id: u64,
|
||||
}
|
||||
|
||||
// attempt to convert the old request type into the new variant
|
||||
impl TryFrom<OldV1Credential> for CredentialSpendingWithEpoch {
|
||||
type Error = GatewayRequestsError;
|
||||
|
||||
fn try_from(value: OldV1Credential) -> Result<Self, Self::Error> {
|
||||
if value.n_params <= 2 {
|
||||
return Err(GatewayRequestsError::InvalidNumberOfEmbededParameters(
|
||||
value.n_params,
|
||||
));
|
||||
}
|
||||
let embedded_private_attributes = value.n_params as usize - 2;
|
||||
let typ = value.voucher_info.parse()?;
|
||||
let public_attributes_plain = vec![value.voucher_value.to_string(), value.voucher_info];
|
||||
|
||||
Ok(CredentialSpendingWithEpoch {
|
||||
data: CredentialSpendingData {
|
||||
embedded_private_attributes,
|
||||
verify_credential_request: value.theta,
|
||||
public_attributes_plain,
|
||||
typ,
|
||||
},
|
||||
epoch_id: value.epoch_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl OldV1Credential {
|
||||
#[cfg(test)]
|
||||
pub fn as_bytes(&self) -> Vec<u8> {
|
||||
let n_params_bytes = self.n_params.to_be_bytes();
|
||||
let theta_bytes = self.theta.to_bytes();
|
||||
let theta_bytes_len = theta_bytes.len();
|
||||
let voucher_value_bytes = self.voucher_value.to_be_bytes();
|
||||
let epoch_id_bytes = self.epoch_id.to_be_bytes();
|
||||
let voucher_info_bytes = self.voucher_info.as_bytes();
|
||||
let voucher_info_len = voucher_info_bytes.len();
|
||||
|
||||
let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len);
|
||||
bytes.extend_from_slice(&n_params_bytes);
|
||||
bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes());
|
||||
bytes.extend_from_slice(&theta_bytes);
|
||||
bytes.extend_from_slice(&voucher_value_bytes);
|
||||
bytes.extend_from_slice(&epoch_id_bytes);
|
||||
bytes.extend_from_slice(voucher_info_bytes);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoconutError> {
|
||||
if bytes.len() < 28 {
|
||||
return Err(CoconutError::Deserialization(String::from(
|
||||
"To few bytes in credential",
|
||||
)));
|
||||
}
|
||||
let mut four_byte = [0u8; 4];
|
||||
let mut eight_byte = [0u8; 8];
|
||||
|
||||
four_byte.copy_from_slice(&bytes[..4]);
|
||||
let n_params = u32::from_be_bytes(four_byte);
|
||||
eight_byte.copy_from_slice(&bytes[4..12]);
|
||||
let theta_len = u64::from_be_bytes(eight_byte);
|
||||
if bytes.len() < 28 + theta_len as usize {
|
||||
return Err(CoconutError::Deserialization(String::from(
|
||||
"To few bytes in credential",
|
||||
)));
|
||||
}
|
||||
let theta = VerifyCredentialRequest::from_bytes(&bytes[12..12 + theta_len as usize])
|
||||
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
|
||||
eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]);
|
||||
let voucher_value = u64::from_be_bytes(eight_byte);
|
||||
eight_byte.copy_from_slice(&bytes[20 + theta_len as usize..28 + theta_len as usize]);
|
||||
let epoch_id = u64::from_be_bytes(eight_byte);
|
||||
let voucher_info = String::from_utf8(bytes[28 + theta_len as usize..].to_vec())
|
||||
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
|
||||
|
||||
Ok(OldV1Credential {
|
||||
n_params,
|
||||
theta,
|
||||
voucher_value,
|
||||
voucher_info,
|
||||
epoch_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CredentialSpendingWithEpoch {
|
||||
/// The cryptographic material required for spending the underlying credential.
|
||||
@@ -145,7 +245,60 @@ mod tests {
|
||||
use super::*;
|
||||
use nym_credentials::coconut::bandwidth::bandwidth_credential_params;
|
||||
use nym_credentials::IssuanceBandwidthCredential;
|
||||
use nym_credentials_interface::{blind_sign, hash_to_scalar};
|
||||
use nym_credentials_interface::{
|
||||
blind_sign, hash_to_scalar, prove_bandwidth_credential, Attribute, Base58, Parameters,
|
||||
Signature, VerificationKey,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn old_v1_coconut_credential_roundtrip() {
|
||||
let voucher_value = 1000000u64;
|
||||
let voucher_info = String::from("BandwidthVoucher");
|
||||
let serial_number =
|
||||
Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap();
|
||||
let binding_number =
|
||||
Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap();
|
||||
let signature = Signature::try_from_bs58(
|
||||
"ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\
|
||||
7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\
|
||||
ZR9j",
|
||||
)
|
||||
.unwrap();
|
||||
let params = Parameters::new(4).unwrap();
|
||||
let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\
|
||||
woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\
|
||||
FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\
|
||||
JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\
|
||||
UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\
|
||||
wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\
|
||||
1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\
|
||||
nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\
|
||||
ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\
|
||||
6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\
|
||||
Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y")
|
||||
.unwrap();
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&signature,
|
||||
&serial_number,
|
||||
&binding_number,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let credential = OldV1Credential {
|
||||
n_params: 4,
|
||||
theta,
|
||||
voucher_value,
|
||||
voucher_info,
|
||||
epoch_id: 42,
|
||||
};
|
||||
|
||||
let serialized_credential = credential.as_bytes();
|
||||
let deserialized_credential = OldV1Credential::from_bytes(&serialized_credential).unwrap();
|
||||
|
||||
assert_eq!(credential, deserialized_credential);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn credential_roundtrip() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
use crate::iv::IV;
|
||||
use crate::models::CredentialSpendingWithEpoch;
|
||||
use crate::models::{CredentialSpendingWithEpoch, OldV1Credential};
|
||||
use crate::registration::handshake::SharedKeys;
|
||||
use crate::{GatewayMacSize, PROTOCOL_VERSION};
|
||||
use log::error;
|
||||
@@ -116,6 +116,9 @@ pub enum GatewayRequestsError {
|
||||
|
||||
#[error("failed to deserialize provided credential: malformed verify request: {0}")]
|
||||
CredentialDeserializationFailureMalformedTheta(CoconutError),
|
||||
|
||||
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
|
||||
InvalidNumberOfEmbededParameters(u32),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -140,6 +143,10 @@ pub enum ClientControlRequest {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
BandwidthCredentialV2 {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
ClaimFreeTestnetBandwidth,
|
||||
}
|
||||
|
||||
@@ -157,7 +164,31 @@ impl ClientControlRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_enc_coconut_bandwidth_credential(
|
||||
pub fn new_enc_coconut_bandwidth_credential_v1(
|
||||
credential: &OldV1Credential,
|
||||
shared_key: &SharedKeys,
|
||||
iv: IV,
|
||||
) -> Self {
|
||||
let serialized_credential = credential.as_bytes();
|
||||
let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner()));
|
||||
|
||||
ClientControlRequest::BandwidthCredential {
|
||||
enc_credential,
|
||||
iv: iv.to_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_enc_coconut_bandwidth_credential_v1(
|
||||
enc_credential: Vec<u8>,
|
||||
shared_key: &SharedKeys,
|
||||
iv: IV,
|
||||
) -> Result<OldV1Credential, GatewayRequestsError> {
|
||||
let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?;
|
||||
OldV1Credential::from_bytes(&credential_bytes)
|
||||
.map_err(|_| GatewayRequestsError::MalformedEncryption)
|
||||
}
|
||||
|
||||
pub fn new_enc_coconut_bandwidth_credential_v2(
|
||||
credential: CredentialSpendingData,
|
||||
epoch_id: u64,
|
||||
shared_key: &SharedKeys,
|
||||
@@ -167,13 +198,13 @@ impl ClientControlRequest {
|
||||
let serialized_credential = cred.to_bytes();
|
||||
let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner()));
|
||||
|
||||
ClientControlRequest::BandwidthCredential {
|
||||
ClientControlRequest::BandwidthCredentialV2 {
|
||||
enc_credential,
|
||||
iv: iv.to_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_enc_coconut_bandwidth_credential(
|
||||
pub fn try_from_enc_coconut_bandwidth_credential_v2(
|
||||
enc_credential: Vec<u8>,
|
||||
shared_key: &SharedKeys,
|
||||
iv: IV,
|
||||
|
||||
@@ -22,6 +22,7 @@ use futures::{
|
||||
use log::*;
|
||||
use nym_credentials::coconut::bandwidth::{bandwidth_credential_params, CredentialType};
|
||||
use nym_credentials_interface::CoconutError;
|
||||
use nym_gateway_requests::models::CredentialSpendingWithEpoch;
|
||||
use nym_gateway_requests::{
|
||||
iv::{IVConversionError, IV},
|
||||
types::{BinaryRequest, ServerResponse},
|
||||
@@ -82,6 +83,12 @@ pub(crate) enum RequestHandlingError {
|
||||
|
||||
#[error("failed to recover bandwidth value: {0}")]
|
||||
BandwidthRecoveryFailure(#[from] BandwidthError),
|
||||
|
||||
#[error("the provided credential did not contain a valid type attribute")]
|
||||
InvalidTypeAttribute,
|
||||
|
||||
#[error("the provided credential did not have a bandwidth attribute")]
|
||||
MissingBandwidthAttribute,
|
||||
}
|
||||
|
||||
impl RequestHandlingError {
|
||||
@@ -211,25 +218,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to handle the received bandwidth request by checking correctness of the received data
|
||||
/// and if successful, increases client's bandwidth by an appropriate amount.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `enc_credential`: raw encrypted bandwidth credential to verify.
|
||||
/// * `iv`: fresh iv used for the credential.
|
||||
async fn handle_bandwidth(
|
||||
async fn handle_bandwidth_request(
|
||||
&mut self,
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
credential: CredentialSpendingWithEpoch,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
let iv = IV::try_from_bytes(&iv)?;
|
||||
let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential(
|
||||
enc_credential,
|
||||
&self.client.shared_keys,
|
||||
iv,
|
||||
)?;
|
||||
|
||||
let aggregated_verification_key = self
|
||||
.inner
|
||||
.coconut_verifier
|
||||
@@ -237,11 +229,11 @@ where
|
||||
.await?;
|
||||
|
||||
if !credential.data.validate_type_attribute() {
|
||||
unimplemented!()
|
||||
return Err(RequestHandlingError::InvalidTypeAttribute);
|
||||
}
|
||||
|
||||
let Some(bandwidth_attribute) = credential.data.get_bandwidth_attribute() else {
|
||||
unimplemented!()
|
||||
return Err(RequestHandlingError::MissingBandwidthAttribute);
|
||||
};
|
||||
|
||||
// this will extract token amounts out of bandwidth vouchers and validate expiry of free passes
|
||||
@@ -279,6 +271,43 @@ where
|
||||
Ok(ServerResponse::Bandwidth { available_total })
|
||||
}
|
||||
|
||||
async fn handle_bandwidth_v1(
|
||||
&mut self,
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
let iv = IV::try_from_bytes(&iv)?;
|
||||
let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential_v1(
|
||||
enc_credential,
|
||||
&self.client.shared_keys,
|
||||
iv,
|
||||
)?;
|
||||
|
||||
self.handle_bandwidth_request(credential.try_into()?).await
|
||||
}
|
||||
|
||||
/// Tries to handle the received bandwidth request by checking correctness of the received data
|
||||
/// and if successful, increases client's bandwidth by an appropriate amount.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `enc_credential`: raw encrypted bandwidth credential to verify.
|
||||
/// * `iv`: fresh iv used for the credential.
|
||||
async fn handle_bandwidth_v2(
|
||||
&mut self,
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
let iv = IV::try_from_bytes(&iv)?;
|
||||
let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential_v2(
|
||||
enc_credential,
|
||||
&self.client.shared_keys,
|
||||
iv,
|
||||
)?;
|
||||
|
||||
self.handle_bandwidth_request(credential).await
|
||||
}
|
||||
|
||||
async fn handle_claim_testnet_bandwidth(
|
||||
&mut self,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
@@ -357,7 +386,11 @@ where
|
||||
Err(e) => RequestHandlingError::InvalidTextRequest(e).into_error_message(),
|
||||
Ok(request) => match request {
|
||||
ClientControlRequest::BandwidthCredential { enc_credential, iv } => self
|
||||
.handle_bandwidth(enc_credential, iv)
|
||||
.handle_bandwidth_v1(enc_credential, iv)
|
||||
.await
|
||||
.into_ws_message(),
|
||||
ClientControlRequest::BandwidthCredentialV2 { enc_credential, iv } => self
|
||||
.handle_bandwidth_v2(enc_credential, iv)
|
||||
.await
|
||||
.into_ws_message(),
|
||||
ClientControlRequest::ClaimFreeTestnetBandwidth => self
|
||||
|
||||
@@ -366,7 +366,7 @@ where
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
Ok(())
|
||||
}
|
||||
Some(v) if v != PROTOCOL_VERSION => {
|
||||
Some(v) if v > PROTOCOL_VERSION => {
|
||||
let err = InitialAuthenticationError::IncompatibleProtocol {
|
||||
client: Some(v),
|
||||
current: PROTOCOL_VERSION,
|
||||
@@ -376,7 +376,7 @@ where
|
||||
}
|
||||
|
||||
Some(_) => {
|
||||
info!("the client is using exactly the same protocol version as we are. We're good to continue!");
|
||||
info!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user