Skip to content

Commit

Permalink
feat: add encryption service
Browse files Browse the repository at this point in the history
  • Loading branch information
therustmonk committed Jun 27, 2022
1 parent c2efd5e commit 9b0f19d
Show file tree
Hide file tree
Showing 28 changed files with 1,325 additions and 1,170 deletions.
1,858 changes: 794 additions & 1,064 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion applications/test_faucet/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ fn create_utxo(
if !factories.range_proof.verify(&proof, &commitment) {
panic!("Range proof does not verify");
};
let encrypted_value = EncryptedValue::todo_encrypt_from(value);
let encryption_keys = generate_keys();
let encrypted_value = EncryptedValue::encrypt_value(&encryption_keys.k, &commitment, value).unwrap();
let metadata_sig = TransactionOutput::create_final_metadata_signature(
TransactionOutputVersion::get_current_version(),
value,
Expand Down
1 change: 1 addition & 0 deletions base_layer/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ bincode = "1.1.4"
bitflags = "1.0.4"
blake2 = "^0.9.0"
bytes = "0.5"
chacha20poly1305 = "0.9.0"
chrono = { version = "0.4.19", default-features = false, features = ["serde"] }
criterion = { version = "0.3.5", optional = true }
croaring = { version = "0.5.2", optional = true }
Expand Down
3 changes: 2 additions & 1 deletion base_layer/core/src/test_helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ pub fn create_block(rules: &ConsensusManager, prev_block: &Block, spec: BlockSpe
)
});

let (coinbase, coinbase_output) = CoinbaseBuilder::new(CryptoFactories::default())
let factories = CryptoFactories::default();
let (coinbase, coinbase_output) = CoinbaseBuilder::new(factories)
.with_block_height(header.height)
.with_fees(0.into())
.with_nonce(0.into())
Expand Down
26 changes: 21 additions & 5 deletions base_layer/core/src/transactions/coinbase_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub enum CoinbaseBuildError {
MissingSpendKey,
#[error("The script key for this coinbase transaction wasn't provided")]
MissingScriptKey,
#[error("The value encryption was not succeed")]
ValueEncryptionFailed,
#[error("An error occurred building the final transaction: `{0}`")]
BuildError(String),
#[error("Some inconsistent data was given to the builder. This transaction is not valid")]
Expand Down Expand Up @@ -206,7 +208,14 @@ impl CoinbaseBuilder {
let sender_offset_public_key = PublicKey::from_secret_key(&sender_offset_private_key);
let covenant = self.covenant;

let encrypted_value = EncryptedValue::todo_encrypt_from(total_reward);
let encrypted_value = self
.rewind_data
.as_ref()
.map(|rd| EncryptedValue::encrypt_value(&rd.encryption_key, &commitment, total_reward))
.transpose()
.map_err(|_| CoinbaseBuildError::ValueEncryptionFailed)?
.unwrap_or_default();

let metadata_sig = TransactionOutput::create_final_metadata_signature(
TransactionOutputVersion::get_current_version(),
total_reward,
Expand Down Expand Up @@ -276,9 +285,9 @@ mod test {
transactions::{
coinbase_builder::CoinbaseBuildError,
crypto_factories::CryptoFactories,
tari_amount::{uT, MicroTari},
tari_amount::uT,
test_helpers::TestParams,
transaction_components::{KernelFeatures, OutputFeatures, OutputType, TransactionError},
transaction_components::{EncryptedValue, KernelFeatures, OutputFeatures, OutputType, TransactionError},
transaction_protocol::RewindData,
CoinbaseBuilder,
},
Expand Down Expand Up @@ -366,6 +375,7 @@ mod test {
let rewind_data = RewindData {
rewind_blinding_key: rewind_blinding_key.clone(),
recovery_byte_key: PrivateKey::random(&mut OsRng),
encryption_key: PrivateKey::random(&mut OsRng),
};

let p = TestParams::new();
Expand All @@ -381,9 +391,15 @@ mod test {
.unwrap();
let block_reward = rules.emission_schedule().block_reward(42) + 145 * uT;

let committed_value = MicroTari::from(tx.body.outputs()[0].encrypted_value.todo_decrypt());
let output = &tx.body.outputs()[0];
let committed_value = EncryptedValue::decrypt_value(
&p.rewind_data.encryption_key,
&output.commitment,
&output.encrypted_value,
)
.unwrap();
assert_eq!(committed_value, block_reward);
let blinding_factor = tx.body.outputs()[0]
let blinding_factor = output
.recover_mask(&factories.range_proof, &rewind_blinding_key)
.unwrap();
assert_eq!(blinding_factor, p.spend_key);
Expand Down
31 changes: 26 additions & 5 deletions base_layer/core/src/transactions/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use tari_crypto::{
};
use tari_script::{inputs, script, ExecutionStack, TariScript};

use super::transaction_components::{EncryptedValue, TransactionInputVersion, TransactionOutputVersion};
use super::transaction_components::{TransactionInputVersion, TransactionOutputVersion};
use crate::{
consensus::{ConsensusEncodingSized, ConsensusManager},
covenants::Covenant,
Expand All @@ -42,6 +42,7 @@ use crate::{
fee::Fee,
tari_amount::MicroTari,
transaction_components::{
EncryptedValue,
KernelBuilder,
KernelFeatures,
OutputFeatures,
Expand Down Expand Up @@ -90,6 +91,7 @@ pub struct TestParams {
pub sender_private_commitment_nonce: PrivateKey,
pub sender_public_commitment_nonce: PublicKey,
pub commitment_factory: CommitmentFactory,
pub encryption_key: PrivateKey,
pub transaction_weight: TransactionWeight,
pub rewind_data: RewindData,
}
Expand Down Expand Up @@ -149,10 +151,12 @@ impl TestParams {
sender_private_commitment_nonce: sender_sig_pvt_nonce.clone(),
sender_public_commitment_nonce: PublicKey::from_secret_key(&sender_sig_pvt_nonce),
commitment_factory: CommitmentFactory::default(),
encryption_key: PrivateKey::random(&mut OsRng),
transaction_weight: TransactionWeight::v2(),
rewind_data: RewindData {
rewind_blinding_key: PrivateKey::random(&mut OsRng),
recovery_byte_key: PrivateKey::random(&mut OsRng),
encryption_key: PrivateKey::random(&mut OsRng),
},
}
}
Expand All @@ -176,7 +180,7 @@ impl TestParams {
let updated_features =
OutputFeatures::features_with_updated_recovery_byte(&commitment, rewind_data, &params.features);

let encrypted_value = EncryptedValue::todo_encrypt_from(params.value);
let encrypted_value = EncryptedValue::encrypt_value(&self.encryption_key, &commitment, params.value).unwrap();
let metadata_signature = TransactionOutput::create_final_metadata_signature(
TransactionOutputVersion::get_current_version(),
params.value,
Expand Down Expand Up @@ -344,6 +348,20 @@ pub fn create_unblinded_output(
})
}

pub fn create_unblinded_output_with_rewind_data(
script: TariScript,
output_features: OutputFeatures,
test_params: &TestParams,
value: MicroTari,
) -> UnblindedOutput {
test_params.create_unblinded_output_with_rewind_data(UtxoTestParams {
value,
script,
features: output_features,
..Default::default()
})
}

pub fn update_unblinded_output_with_updated_output_features(
test_params: &TestParams,
uo: UnblindedOutput,
Expand Down Expand Up @@ -738,8 +756,9 @@ pub fn create_stx_protocol(schema: TransactionSchema) -> (SenderTransactionProto
..Default::default()
};

// TODO: Get it using `something.encrypt_value(change)`
let encrypted_value = EncryptedValue::todo_encrypt_from(change);
let encrypted_value =
EncryptedValue::encrypt_value(&test_params_change_and_txn.encryption_key, &commitment, change).unwrap();

let change_metadata_sig = TransactionOutput::create_final_metadata_signature(
output_version,
change,
Expand Down Expand Up @@ -809,7 +828,9 @@ pub fn create_utxo(

let updated_features = OutputFeatures::features_with_updated_recovery_byte(&commitment, None, features);

let encrypted_value = EncryptedValue::todo_encrypt_from(value);
let encryption_keys = generate_keys();
let encrypted_value = EncryptedValue::encrypt_value(&encryption_keys.k, &commitment, value).unwrap();

let metadata_sig = TransactionOutput::create_final_metadata_signature(
TransactionOutputVersion::get_current_version(),
value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,23 @@

use std::io::{self, Read, Write};

use blake2::Digest;
use chacha20poly1305::{
aead::{Aead, Error, NewAead, Payload},
ChaCha20Poly1305,
Key,
Nonce,
};
use serde::{Deserialize, Serialize};
use tari_common_types::types::{Commitment, PrivateKey};
use tari_crypto::common::Blake256;
use tari_utilities::{ByteArray, ByteArrayError};
use thiserror::Error;

use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized};
use crate::{
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized},
transactions::tari_amount::MicroTari,
};

const SIZE: usize = 24;

Expand All @@ -52,25 +65,66 @@ impl ByteArray for EncryptedValue {
}
}

#[derive(Debug, Error)]
pub enum EncryptionError {
#[error("Encryption failed: {0}")]
EncryptionFailed(Error),
}

// chacha error is not StdError compatible
impl From<Error> for EncryptionError {
fn from(err: Error) -> Self {
Self::EncryptionFailed(err)
}
}

impl EncryptedValue {
/// TODO: Replace this method with a real call of encryption service
/// that will produce an encrypted value from the given `amount`.
pub fn todo_encrypt_from(amount: impl Into<u64>) -> Self {
pub fn encrypt_value(
encryption_key: &PrivateKey,
commitment: &Commitment,
value: MicroTari,
) -> Result<EncryptedValue, EncryptionError> {
let aead_key = kdf_aead(encryption_key, commitment);
// Encrypt the value (with fixed length) using ChaCha20-Poly1305 with a fixed zero nonce
let aead_payload = Payload {
msg: &value.as_u64().to_le_bytes(),
aad: b"TARI_AAD_SCAN".as_ref(),
};
// Included in the public transaction
let buffer = ChaCha20Poly1305::new(&aead_key).encrypt(&Nonce::default(), aead_payload)?;
let mut data: [u8; SIZE] = [0; SIZE];
let value = amount.into().to_le_bytes();
data[0..8].copy_from_slice(&value);
Self(data)
data.copy_from_slice(&buffer);
Ok(EncryptedValue(data))
}

/// TODO: Replace this method with a real call of decryption service
/// that will produce a decrypted value from self.
pub fn todo_decrypt(&self) -> u64 {
let mut buffer = [0u8; 8];
(&mut buffer[0..8]).copy_from_slice(&self.0.as_slice()[0..8]);
u64::from_le_bytes(buffer)
pub fn decrypt_value(
encryption_key: &PrivateKey,
commitment: &Commitment,
value: &EncryptedValue,
) -> Result<MicroTari, EncryptionError> {
let aead_key = kdf_aead(encryption_key, commitment);
// Authenticate and decrypt the value
let aead_payload = Payload {
msg: value.as_bytes(),
aad: b"TARI_AAD_SCAN".as_ref(),
};
let mut value_bytes = [0u8; 8];
let decrypted_bytes = ChaCha20Poly1305::new(&aead_key).decrypt(&Nonce::default(), aead_payload)?;
value_bytes.clone_from_slice(&decrypted_bytes[..8]);
Ok(u64::from_le_bytes(value_bytes).into())
}
}

// Generate a ChaCha20-Poly1305 key from an ECDH shared secret and commitment using Blake2b
fn kdf_aead(shared_secret: &PrivateKey, commitment: &Commitment) -> Key {
const AEAD_KEY_LENGTH: usize = 32; // The length in bytes of a ChaCha20-Poly1305 AEAD key
let mut hasher = Blake256::with_params(&[], b"SCAN_AEAD".as_ref(), b"TARI_KDF".as_ref());
hasher.update(shared_secret.as_bytes());
hasher.update(commitment.as_bytes());
let output = hasher.finalize();
*Key::from_slice(&output[..AEAD_KEY_LENGTH])
}

impl ConsensusEncoding for EncryptedValue {
fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
self.0.consensus_encode(writer)?;
Expand All @@ -86,27 +140,49 @@ impl ConsensusEncodingSized for EncryptedValue {

impl ConsensusDecoding for EncryptedValue {
fn consensus_decode<R: Read>(reader: &mut R) -> Result<Self, io::Error> {
let data = <[u8; 24]>::consensus_decode(reader)?;
let data = <[u8; SIZE]>::consensus_decode(reader)?;
Ok(Self(data))
}
}

#[cfg(test)]
mod test {
use rand::rngs::OsRng;
use tari_common_types::types::{CommitmentFactory, PrivateKey};
use tari_crypto::{commitment::HomomorphicCommitmentFactory, keys::{SecretKey, PublicKey}};

use super::*;
use crate::consensus::ToConsensusBytes;

#[test]
fn it_encodes_to_bytes() {
let bytes = EncryptedValue::todo_encrypt_from(123u64).to_consensus_bytes();
assert_eq!(&bytes[0..8], &123u64.to_le_bytes());
let commitment_factory = CommitmentFactory::default();
let spending_key = PrivateKey::random(&mut OsRng);
let encryption_key = PrivateKey::random(&mut OsRng);
let value = 123u64;
let commitment = commitment_factory.commit(&spending_key, &PrivateKey::from(value));
let bytes = EncryptedValue::encrypt_value(&encryption_key, &commitment, value.into())
.unwrap()
.to_consensus_bytes();
assert_eq!(bytes.len(), SIZE);
}

#[test]
fn it_decodes_from_bytes() {
let value = &[0; 24];
let value = &[0; SIZE];
let encrypted_value = EncryptedValue::consensus_decode(&mut &value[..]).unwrap();
assert_eq!(encrypted_value, EncryptedValue::default());
}

#[test]
fn it_encrypts_and_decrypts_correctly() {
for value in [0, 123456, 654321, u64::MAX] {
let commitment = Commitment::from_public_key(&PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)));
let encryption_key = PrivateKey::random(&mut OsRng);
let amount = MicroTari::from(value);
let encrypted_value = EncryptedValue::encrypt_value(&encryption_key, &commitment, amount).unwrap();
let decrypted_value = EncryptedValue::decrypt_value(&encryption_key, &commitment, &encrypted_value).unwrap();
assert_eq!(amount, decrypted_value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

pub use asset_output_features::AssetOutputFeatures;
pub use committee_definition_features::CommitteeDefinitionFeatures;
pub use encrypted_value::EncryptedValue;
pub use encrypted_value::{EncryptedValue, EncryptionError};
pub use error::TransactionError;
pub use kernel_builder::KernelBuilder;
pub use kernel_features::KernelFeatures;
Expand Down
3 changes: 3 additions & 0 deletions base_layer/core/src/transactions/transaction_protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ pub enum TransactionProtocolError {
ConversionError(String),
#[error("The script offset private key could not be found")]
ScriptOffsetPrivateKeyNotFound,
#[error("Value encryption failed")]
EncryptionError,
}

/// Transaction metadata, including the fee and lock height
Expand All @@ -146,6 +148,7 @@ pub struct RewindData {
#[derivative(Debug = "ignore")]
pub rewind_blinding_key: PrivateKey,
pub recovery_byte_key: PrivateKey,
pub encryption_key: PrivateKey,
}

/// Convenience function that calculates the challenge for the Schnorr signatures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ mod test {
crypto_factories::CryptoFactories,
tari_amount::*,
test_helpers::TestParams,
transaction_components::OutputFeatures,
transaction_components::{EncryptedValue, OutputFeatures},
transaction_protocol::{
build_challenge,
sender::{SingleRoundSenderData, TransactionSenderMessage},
Expand Down Expand Up @@ -284,6 +284,7 @@ mod test {
let rewind_data = RewindData {
rewind_blinding_key: rewind_blinding_key.clone(),
recovery_byte_key,
encryption_key: PrivateKey::random(&mut OsRng),
};
let amount = MicroTari(500);
let m = TransactionMetadata {
Expand Down Expand Up @@ -324,10 +325,12 @@ mod test {
let data = receiver.get_signed_data().unwrap();
assert_eq!(data.output.features.recovery_byte, recovery_byte);

let committed_value = data.output.encrypted_value.todo_decrypt();
assert_eq!(committed_value, amount.as_u64());
let blinding_factor = data
.output
let output = &data.output;
let committed_value =
EncryptedValue::decrypt_value(&rewind_data.encryption_key, &output.commitment, &output.encrypted_value)
.unwrap();
assert_eq!(committed_value, amount);
let blinding_factor = output
.recover_mask(&factories.range_proof, &rewind_blinding_key)
.unwrap();
assert_eq!(blinding_factor, p.spend_key);
Expand Down
Loading

0 comments on commit 9b0f19d

Please sign in to comment.