516561dcf9
* separated signing and nyxd-client features * updated cosmrs to the most recent version * using tendermint_rpc directly for the Client trait * fixed rest of the codebase * removed nyxd-client feature * export more types * clippy that seems to have been skipped in makefile
387 lines
14 KiB
Rust
387 lines
14 KiB
Rust
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// for time being assume the bandwidth credential consists of public identity of the requester
|
|
// and private (though known... just go along with it) infinite bandwidth value
|
|
// right now this has no double-spending protection, spender binding, etc
|
|
// it's the simplest possible case
|
|
|
|
use cosmrs::tendermint::hash::Algorithm;
|
|
use cosmrs::tendermint::Hash;
|
|
use nym_coconut_interface::{
|
|
hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters,
|
|
PrivateAttribute, PublicAttribute, Signature, VerificationKey,
|
|
};
|
|
use nym_crypto::asymmetric::{encryption, identity};
|
|
|
|
use super::utils::prepare_credential_for_spending;
|
|
use crate::error::Error;
|
|
|
|
pub const PUBLIC_ATTRIBUTES: u32 = 2;
|
|
pub const PRIVATE_ATTRIBUTES: u32 = 2;
|
|
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
|
|
|
|
pub struct BandwidthVoucher {
|
|
// a random secret value generated by the client used for double-spending detection
|
|
serial_number: PrivateAttribute,
|
|
// a random secret value generated by the client used to bind multiple credentials together
|
|
binding_number: PrivateAttribute,
|
|
// the value (e.g., bandwidth) encoded in this voucher
|
|
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.
|
|
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,
|
|
}
|
|
|
|
impl BandwidthVoucher {
|
|
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,
|
|
}
|
|
}
|
|
|
|
pub fn to_bytes(&self) -> Vec<u8> {
|
|
let serial_number_b = self.serial_number.to_bytes();
|
|
let binding_number_b = self.binding_number.to_bytes();
|
|
let voucher_value_plain_b = self.voucher_value_plain.as_bytes();
|
|
let voucher_info_plain_b = self.voucher_info_plain.as_bytes();
|
|
let tx_hash_b = self.tx_hash.as_bytes();
|
|
let signing_key_b = self.signing_key.to_bytes();
|
|
let encryption_key_b = self.encryption_key.to_bytes();
|
|
let blind_sign_request_b = self.blind_sign_request.to_bytes();
|
|
|
|
let mut ret = Vec::new();
|
|
|
|
ret.extend_from_slice(&serial_number_b);
|
|
ret.extend_from_slice(&binding_number_b);
|
|
ret.extend_from_slice(tx_hash_b);
|
|
ret.extend_from_slice(&signing_key_b);
|
|
ret.extend_from_slice(&encryption_key_b);
|
|
ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes());
|
|
ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes());
|
|
ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes());
|
|
ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes());
|
|
ret.extend_from_slice(voucher_value_plain_b);
|
|
ret.extend_from_slice(voucher_info_plain_b);
|
|
ret.extend_from_slice(&blind_sign_request_b);
|
|
for commitment in self.pedersen_commitments_openings.iter() {
|
|
ret.extend_from_slice(&commitment.to_bytes());
|
|
}
|
|
|
|
ret
|
|
}
|
|
|
|
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
|
if bytes.len() < 32 * 5 + 4 * 8 {
|
|
return Err(Error::BandwidthVoucherDeserializationError(format!(
|
|
"Less then {} bytes needed",
|
|
32 * 5 + 4 * 8
|
|
)));
|
|
}
|
|
let mut buff = [0u8; 32];
|
|
let mut small_buff = [0u8; 8];
|
|
let scalar_err =
|
|
|| Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar"));
|
|
buff.copy_from_slice(&bytes[..32]);
|
|
let serial_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
|
|
.ok_or_else(scalar_err)?;
|
|
buff.copy_from_slice(&bytes[32..2 * 32]);
|
|
let binding_number = Option::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
|
|
.ok_or_else(scalar_err)?;
|
|
buff.copy_from_slice(&bytes[2 * 32..3 * 32]);
|
|
let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| {
|
|
Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash"))
|
|
})?;
|
|
buff.copy_from_slice(&bytes[3 * 32..4 * 32]);
|
|
let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| {
|
|
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
|
|
})?;
|
|
buff.copy_from_slice(&bytes[4 * 32..5 * 32]);
|
|
let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| {
|
|
Error::BandwidthVoucherDeserializationError(String::from("Invalid key"))
|
|
})?;
|
|
small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]);
|
|
let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize;
|
|
small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]);
|
|
let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize;
|
|
small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]);
|
|
let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize;
|
|
small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]);
|
|
let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize;
|
|
|
|
let total_length = 32 * 5
|
|
+ 4 * 8
|
|
+ voucher_value_plain_no
|
|
+ voucher_info_plain_no
|
|
+ blind_sign_request_no
|
|
+ pedersen_commitments_openings_no * 32;
|
|
if bytes.len() != total_length {
|
|
return Err(Error::BandwidthVoucherDeserializationError(format!(
|
|
"Expected {total_length} bytes",
|
|
)));
|
|
}
|
|
|
|
let utf_err = |_| {
|
|
Err(Error::BandwidthVoucherDeserializationError(String::from(
|
|
"Invalid UTF8 string",
|
|
)))
|
|
};
|
|
let mut var_length_pointer = 5 * 32 + 4 * 8;
|
|
let voucher_value_plain = String::from_utf8(
|
|
bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(),
|
|
)
|
|
.or_else(utf_err)?;
|
|
let voucher_value = hash_to_scalar(&voucher_value_plain);
|
|
var_length_pointer += voucher_value_plain_no;
|
|
let voucher_info_plain = String::from_utf8(
|
|
bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(),
|
|
)
|
|
.or_else(utf_err)?;
|
|
let voucher_info = hash_to_scalar(&voucher_info_plain);
|
|
var_length_pointer += voucher_info_plain_no;
|
|
let blind_sign_request = BlindSignRequest::from_bytes(
|
|
&bytes[var_length_pointer..var_length_pointer + blind_sign_request_no],
|
|
)?;
|
|
var_length_pointer += blind_sign_request_no;
|
|
|
|
let mut pedersen_commitments_openings = Vec::new();
|
|
for _ in 0..pedersen_commitments_openings_no {
|
|
buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]);
|
|
let commitment =
|
|
Option::<Attribute>::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?;
|
|
var_length_pointer += 32;
|
|
pedersen_commitments_openings.push(commitment);
|
|
}
|
|
|
|
Ok(Self {
|
|
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,
|
|
})
|
|
}
|
|
|
|
/// 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 get_voucher_value(&self) -> String {
|
|
self.voucher_value_plain.clone()
|
|
}
|
|
|
|
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]
|
|
}
|
|
|
|
pub fn sign(&self, request: &BlindSignRequest) -> identity::Signature {
|
|
let mut message = request.to_bytes();
|
|
message.extend_from_slice(self.tx_hash.to_string().as_bytes());
|
|
self.signing_key.sign(&message)
|
|
}
|
|
}
|
|
|
|
pub fn prepare_for_spending(
|
|
voucher_value: u64,
|
|
voucher_info: String,
|
|
serial_number: PrivateAttribute,
|
|
binding_number: PrivateAttribute,
|
|
epoch_id: u64,
|
|
signature: &Signature,
|
|
verification_key: &VerificationKey,
|
|
) -> Result<Credential, Error> {
|
|
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
|
|
|
|
prepare_credential_for_spending(
|
|
¶ms,
|
|
voucher_value,
|
|
voucher_info,
|
|
serial_number,
|
|
binding_number,
|
|
epoch_id,
|
|
signature,
|
|
verification_key,
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use cosmrs::tendermint::hash::Algorithm;
|
|
use nym_coconut_interface::Base58;
|
|
use rand::rngs::OsRng;
|
|
|
|
fn voucher_fixture() -> BandwidthVoucher {
|
|
let params = Parameters::new(4).unwrap();
|
|
let mut rng = OsRng;
|
|
BandwidthVoucher::new(
|
|
¶ms,
|
|
"1234".to_string(),
|
|
"voucher info".to_string(),
|
|
Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap(),
|
|
identity::PrivateKey::from_base58_string(
|
|
identity::KeyPair::new(&mut rng)
|
|
.private_key()
|
|
.to_base58_string(),
|
|
)
|
|
.unwrap(),
|
|
encryption::PrivateKey::from_bytes(
|
|
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
|
|
)
|
|
.unwrap(),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn serde_voucher() {
|
|
let voucher = voucher_fixture();
|
|
let bytes = voucher.to_bytes();
|
|
let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap();
|
|
assert_eq!(voucher.serial_number, deserialized_voucher.serial_number);
|
|
assert_eq!(voucher.binding_number, deserialized_voucher.binding_number);
|
|
assert_eq!(voucher.voucher_value, deserialized_voucher.voucher_value);
|
|
assert_eq!(
|
|
voucher.voucher_value_plain,
|
|
deserialized_voucher.voucher_value_plain
|
|
);
|
|
assert_eq!(voucher.voucher_info, deserialized_voucher.voucher_info);
|
|
assert_eq!(
|
|
voucher.voucher_info_plain,
|
|
deserialized_voucher.voucher_info_plain
|
|
);
|
|
assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash);
|
|
assert_eq!(
|
|
voucher.signing_key.to_string(),
|
|
deserialized_voucher.signing_key.to_string()
|
|
);
|
|
assert_eq!(
|
|
voucher.encryption_key.to_string(),
|
|
deserialized_voucher.encryption_key.to_string()
|
|
);
|
|
assert_eq!(
|
|
voucher.pedersen_commitments_openings,
|
|
deserialized_voucher.pedersen_commitments_openings
|
|
);
|
|
assert_eq!(
|
|
voucher.blind_sign_request.to_bs58(),
|
|
deserialized_voucher.blind_sign_request.to_bs58()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn voucher_consistency() {
|
|
let voucher = voucher_fixture();
|
|
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()
|
|
));
|
|
}
|
|
}
|