Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions crates/discovery-core/src/privacy_pool/decryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ use starknet_types_core::{curve::AffinePoint, felt::Felt};
use thiserror::Error;

use super::hashes::{
compute_enc_amount_hash, compute_enc_channel_key_hash, compute_enc_sender_addr_hash,
compute_enc_token_hash,
compute_enc_amount_hash, compute_enc_channel_key_hash, compute_enc_recipient_addr_hash,
compute_enc_sender_addr_hash, compute_enc_token_hash,
};
use super::types::{
felt_low_u128, ChannelInfo, EncChannelInfo, EncOutgoingChannelInfo, EncSubchannelInfo,
SecretFelt,
};
use super::types::{felt_low_u128, ChannelInfo, EncChannelInfo, EncSubchannelInfo, SecretFelt};

/// Errors that can occur during decryption.
#[derive(Debug, Error)]
Expand Down Expand Up @@ -95,6 +98,19 @@ pub fn decrypt_note_amount(
enc_amount.wrapping_sub(pad)
}

/// Decrypts an outgoing channel's encrypted recipient address.
///
/// `recipient_addr = enc_recipient_addr - hash(ENC_RECIPIENT_ADDR_TAG, sender_addr, viewing_key, index, 0, salt)`
pub fn decrypt_outgoing_recipient_addr(
enc: &EncOutgoingChannelInfo,
sender_addr: Felt,
viewing_key: &SecretFelt,
index: u64,
) -> Felt {
let mask = compute_enc_recipient_addr_hash(sender_addr, viewing_key, index, enc.salt);
enc.enc_recipient_addr - mask
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -163,4 +179,20 @@ mod tests {
);
assert_eq!(amount, f.outputs.dec_note_amount as u128);
}

#[test]
fn test_decrypt_outgoing_recipient_addr_with_cairo_vectors() {
let f = load_cairo_ref_fixture();

let enc = EncOutgoingChannelInfo {
salt: f.outputs.enc_outgoing_salt,
enc_recipient_addr: f.outputs.enc_outgoing_recipient_addr,
};

let key = SecretFelt::new(f.inputs.sender_private_key);
let recipient =
decrypt_outgoing_recipient_addr(&enc, f.inputs.sender, &key, f.inputs.index);

assert_eq!(recipient, f.inputs.recipient);
}
}
103 changes: 103 additions & 0 deletions crates/discovery-core/src/privacy_pool/hashes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! Hash functions and domain separation tags.
//!
//! TODO: rename primitives to match smart contract terminology

use std::sync::LazyLock;

Expand Down Expand Up @@ -29,6 +31,18 @@ static ENC_AMOUNT_TAG: LazyLock<Felt> = LazyLock::new(|| short_string_to_felt("E
/// Domain separation tag for nullifier derivation.
static NULLIFIER_TAG: LazyLock<Felt> = LazyLock::new(|| short_string_to_felt("NULLIFIER_TAG:V1"));

/// Domain separation tag for channel key derivation.
static CHANNEL_KEY_TAG: LazyLock<Felt> =
LazyLock::new(|| short_string_to_felt("CHANNEL_KEY_TAG:V1"));

/// Domain separation tag for outgoing channel id derivation.
static OUTGOING_CHANNEL_ID_TAG: LazyLock<Felt> =
LazyLock::new(|| short_string_to_felt("OUTGOING_CHANNEL_ID_TAG:V1"));

/// Domain separation tag for encrypted recipient address.
static ENC_RECIPIENT_ADDR_TAG: LazyLock<Felt> =
LazyLock::new(|| short_string_to_felt("ENC_RECIPIENT_ADDR_TAG:V1"));

/// Converts a short string (up to 31 ASCII chars) to Felt.
fn short_string_to_felt(s: &str) -> Felt {
assert!(
Expand Down Expand Up @@ -128,6 +142,60 @@ pub fn compute_nullifier(
])
}

/// Computes the channel key from sender credentials and recipient identity.
///
/// `channel_key = hash(CHANNEL_KEY_TAG, sender_addr, viewing_key, recipient_addr, recipient_public_key)`
pub fn compute_channel_key(
sender_addr: Felt,
viewing_key: &super::types::SecretFelt,
recipient_addr: Felt,
recipient_public_key: Felt,
) -> Felt {
hash(&[
*CHANNEL_KEY_TAG,
sender_addr,
**viewing_key,
recipient_addr,
recipient_public_key,
])
}

/// Computes the outgoing channel id for storage lookup.
///
/// `outgoing_channel_id = hash(OUTGOING_CHANNEL_ID_TAG, sender_addr, viewing_key, index, 0)`
pub fn compute_outgoing_channel_id(
sender_addr: Felt,
viewing_key: &super::types::SecretFelt,
index: u64,
) -> Felt {
hash(&[
*OUTGOING_CHANNEL_ID_TAG,
sender_addr,
**viewing_key,
Felt::from(index),
Felt::ZERO,
])
}

/// Computes the encryption mask for the outgoing recipient address.
///
/// `enc_recipient_addr_hash = hash(ENC_RECIPIENT_ADDR_TAG, sender_addr, viewing_key, index, 0, salt)`
pub fn compute_enc_recipient_addr_hash(
sender_addr: Felt,
viewing_key: &super::types::SecretFelt,
index: u64,
salt: Felt,
) -> Felt {
hash(&[
*ENC_RECIPIENT_ADDR_TAG,
sender_addr,
**viewing_key,
Felt::from(index),
Felt::ZERO,
salt,
])
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -207,4 +275,39 @@ mod tests {
f.outputs.enc_amount_hash
);
}

#[test]
fn test_compute_channel_key() {
let f = load_cairo_ref_fixture();
let key = SecretFelt::new(f.inputs.sender_private_key);
assert_eq!(
compute_channel_key(
f.inputs.sender,
&key,
f.inputs.recipient,
f.inputs.recipient_public_key,
),
f.outputs.channel_key
);
}

#[test]
fn test_compute_outgoing_channel_id() {
let f = load_cairo_ref_fixture();
let key = SecretFelt::new(f.inputs.sender_private_key);
assert_eq!(
compute_outgoing_channel_id(f.inputs.sender, &key, f.inputs.index),
f.outputs.outgoing_channel_id
);
}

#[test]
fn test_compute_enc_recipient_addr_hash() {
let f = load_cairo_ref_fixture();
let key = SecretFelt::new(f.inputs.sender_private_key);
assert_eq!(
compute_enc_recipient_addr_hash(f.inputs.sender, &key, f.inputs.index, f.inputs.salt),
f.outputs.enc_recipient_addr_hash
);
}
}
18 changes: 18 additions & 0 deletions crates/discovery-core/src/privacy_pool/storage_slots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ pub struct EncSubchannelInfoSlots {
pub enc_token: Felt,
}

/// Storage slots for encrypted outgoing channel info (2 consecutive slots).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncOutgoingChannelInfoSlots {
pub salt: Felt,
pub enc_recipient_addr: Felt,
}

/// Computes a storage variable address.
///
/// Wraps `get_storage_var_address`, which only fails if the variable name
Expand Down Expand Up @@ -99,6 +106,17 @@ pub fn subchannel_tokens(subchannel_id: Felt) -> EncSubchannelInfoSlots {
}
}

/// Storage slots for encrypted outgoing channel info.
/// Cairo: `outgoing_channels: Map<felt252, EncOutgoingChannelInfo>`
/// EncOutgoingChannelInfo has 2 fields: salt and enc_recipient_addr.
pub fn outgoing_channels(outgoing_channel_id: Felt) -> EncOutgoingChannelInfoSlots {
let base = slot("outgoing_channels", &[outgoing_channel_id]);
EncOutgoingChannelInfoSlots {
salt: base,
enc_recipient_addr: base + Felt::ONE,
}
}

/// Storage slot for a note's existence.
/// Cairo: `notes: LegacyMap<NoteId, bool>`
pub fn notes(note_id: Felt) -> Felt {
Expand Down
9 changes: 9 additions & 0 deletions crates/discovery-core/src/privacy_pool/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,12 @@ pub struct ChannelInfo {
/// The sender's address.
pub sender_addr: Felt,
}

/// Encrypted outgoing channel info stored in the contract.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncOutgoingChannelInfo {
/// Salt generated by the sender for one-time key usage.
pub salt: Felt,
/// Encrypted recipient address.
pub enc_recipient_addr: Felt,
}
24 changes: 23 additions & 1 deletion crates/discovery-core/src/privacy_pool/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use num_traits::ToPrimitive;
use starknet_types_core::felt::Felt;

use super::storage_slots;
use super::types::{EncChannelInfo, EncPrivateKey, EncSubchannelInfo};
use super::types::{EncChannelInfo, EncOutgoingChannelInfo, EncPrivateKey, EncSubchannelInfo};
use crate::storage_backend::{RawStorageAccess, StorageError};

/// Privacy contract view methods.
Expand Down Expand Up @@ -33,6 +33,12 @@ pub trait IViews: Send + Sync {
subchannel_id: Felt,
) -> Result<EncSubchannelInfo, StorageError>;

/// Returns encrypted outgoing channel info for the given outgoing channel id.
async fn get_outgoing_channel_info(
&self,
outgoing_channel_id: Felt,
) -> Result<EncOutgoingChannelInfo, StorageError>;

/// Returns the note value for the given note ID.
async fn get_note(&self, note_id: Felt) -> Result<Felt, StorageError>;

Expand Down Expand Up @@ -123,6 +129,22 @@ impl<T: RawStorageAccess> IViews for T {
})
}

#[tracing::instrument(name = "get_outgoing_channel_info", level = "debug", skip(self))]
async fn get_outgoing_channel_info(
&self,
outgoing_channel_id: Felt,
) -> Result<EncOutgoingChannelInfo, StorageError> {
let slots = storage_slots::outgoing_channels(outgoing_channel_id);
let values = self
.read_slots(vec![slots.salt, slots.enc_recipient_addr])
.await?;
check_slots_len(&values, 2)?;
Ok(EncOutgoingChannelInfo {
salt: values[0],
enc_recipient_addr: values[1],
})
}

#[tracing::instrument(name = "get_note", level = "debug", skip(self))]
async fn get_note(&self, note_id: Felt) -> Result<Felt, StorageError> {
let slot = storage_slots::notes(note_id);
Expand Down