reintroduced handling of old v1 credentials

This commit is contained in:
Jędrzej Stuczyński
2024-02-09 09:59:54 +00:00
parent 5e08135a68
commit c5e9c3fbb9
5 changed files with 247 additions and 30 deletions
@@ -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(),
+154 -1
View File
@@ -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(
&params,
&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() {
+35 -4
View File
@@ -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(())
}
}