Skip to content

Commit

Permalink
Merge pull request #223 from dusk-network/mocello/222_sender
Browse files Browse the repository at this point in the history
core: Add `Sender` struct
  • Loading branch information
moCello authored Jun 25, 2024
2 parents b2bc6b5 + 00a2c40 commit ec8ac0b
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 99 deletions.
2 changes: 2 additions & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `Sender` struct [#222]
- Add `encrypt_sender` function to encrypt the sender with the npk [#214]
- Add `decrypt_sender` method to the `Note` [#214]
- Add `elgamal::encrypt` and `elgamal::decrypt`
Expand Down Expand Up @@ -351,6 +352,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Canonical implementation shielded by feature.

<!-- ISSUES -->
[#222]: https://github.com/dusk-network/phoenix/issues/222
[#214]: https://github.com/dusk-network/phoenix/issues/214
[#208]: https://github.com/dusk-network/phoenix/issues/208
[#201]: https://github.com/dusk-network/phoenix/issues/201
Expand Down
4 changes: 1 addition & 3 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ pub use keys::hash;
pub use keys::public::PublicKey;
pub use keys::secret::SecretKey;
pub use keys::view::ViewKey;
pub use note::{
encrypt_sender, Note, NoteType, VALUE_ENC_SIZE as NOTE_VAL_ENC_SIZE,
};
pub use note::{Note, NoteType, Sender, VALUE_ENC_SIZE as NOTE_VAL_ENC_SIZE};
pub use stealth_address::StealthAddress;

#[cfg(feature = "alloc")]
Expand Down
236 changes: 163 additions & 73 deletions core/src/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ pub struct Note {
pub(crate) stealth_address: StealthAddress,
pub(crate) pos: u64,
pub(crate) value_enc: [u8; VALUE_ENC_SIZE],
pub(crate) sender_enc: [(JubJubAffine, JubJubAffine); 2],
pub(crate) sender: Sender,
}

impl PartialEq for Note {
fn eq(&self, other: &Self) -> bool {
self.hash() == other.hash()
self.value_enc == other.value_enc
&& self.sender == other.sender
&& self.hash() == other.hash()
}
}

Expand Down Expand Up @@ -133,7 +135,7 @@ impl Note {
stealth_address,
pos,
value_enc,
sender_enc: encrypt_sender(
sender: Sender::encrypt(
stealth_address.note_pk(),
sender_pk,
&sender_blinder,
Expand Down Expand Up @@ -172,7 +174,7 @@ impl Note {
pub fn transparent_stealth(
stealth_address: StealthAddress,
value: u64,
sender_enc: [(JubJubAffine, JubJubAffine); 2],
sender: impl Into<Sender>,
) -> Self {
let value_commitment = transparent_value_commitment(value);

Expand All @@ -187,7 +189,7 @@ impl Note {
stealth_address,
pos,
value_enc,
sender_enc,
sender: sender.into(),
}
}

Expand Down Expand Up @@ -224,7 +226,9 @@ impl Note {
stealth_address: StealthAddress::default(),
pos: 0,
value_enc: [0; VALUE_ENC_SIZE],
sender_enc: [(JubJubAffine::default(), JubJubAffine::default()); 2],
sender: Sender::Encryption(
[(JubJubAffine::default(), JubJubAffine::default()); 2],
),
}
}

Expand Down Expand Up @@ -321,8 +325,8 @@ impl Note {
/// Returns elgamal encryption of the sender's [`PublicKey`] encrypted using
/// the [`StealthAddress::note_pk`] so only the receiver of the [`Note`]
/// can decrypt.
pub const fn sender_enc(&self) -> &[(JubJubAffine, JubJubAffine); 2] {
&self.sender_enc
pub const fn sender(&self) -> &Sender {
&self.sender
}

/// Attempt to decrypt the note value provided a [`ViewKey`]. Always
Expand Down Expand Up @@ -359,58 +363,14 @@ impl Note {
_ => Err(Error::MissingViewKey),
}
}

/// Decrypts the [`PublicKey`] of the sender of the [`Note`], using the
/// [`NoteSecretKey`] generated by the receiver's [`SecretKey`] and the
/// [`StealthAddress`] of the [`Note`].
///
/// Note: Decryption with an incorrect [`NoteSecretKey`] will still yield a
/// [`PublicKey`], but it will a random one that has nothing to do with the
/// sender's [`PublicKey`].
pub fn decrypt_sender(&self, note_sk: &NoteSecretKey) -> PublicKey {
let sender_enc_A = self.sender_enc()[0];
let sender_enc_B = self.sender_enc()[1];

let decrypt_A = elgamal::decrypt(
note_sk.as_ref(),
&(sender_enc_A.0.into(), sender_enc_A.1.into()),
);
let decrypt_B = elgamal::decrypt(
note_sk.as_ref(),
&(sender_enc_B.0.into(), sender_enc_B.1.into()),
);

PublicKey::new(decrypt_A, decrypt_B)
}
}

/// Encrypt the sender [`PublicKey`] in a way that only the receiver of the note
/// can decrypt.
pub fn encrypt_sender(
note_pk: &NotePublicKey,
sender_pk: &PublicKey,
blinder: &[JubJubScalar; 2],
) -> [(JubJubAffine, JubJubAffine); 2] {
let sender_enc_A =
elgamal::encrypt(note_pk.as_ref(), sender_pk.A(), &blinder[0]);

let sender_enc_B =
elgamal::encrypt(note_pk.as_ref(), sender_pk.B(), &blinder[1]);

let sender_enc_A: (JubJubAffine, JubJubAffine) =
(sender_enc_A.0.into(), sender_enc_A.1.into());
let sender_enc_B: (JubJubAffine, JubJubAffine) =
(sender_enc_B.0.into(), sender_enc_B.1.into());

[sender_enc_A, sender_enc_B]
}

const SIZE: usize = 1
+ JubJubAffine::SIZE
+ StealthAddress::SIZE
+ u64::SIZE
+ VALUE_ENC_SIZE
+ 4 * JubJubAffine::SIZE;
+ Sender::SIZE;

impl Serializable<SIZE> for Note {
type Error = BytesError;
Expand All @@ -422,27 +382,23 @@ impl Serializable<SIZE> for Note {
buf[0] = self.note_type as u8;

let mut start = 1;

buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&self.value_commitment.to_bytes());
start += JubJubAffine::SIZE;

buf[start..start + StealthAddress::SIZE]
.copy_from_slice(&self.stealth_address.to_bytes());
start += StealthAddress::SIZE;

buf[start..start + u64::SIZE].copy_from_slice(&self.pos.to_le_bytes());
start += u64::SIZE;

buf[start..start + VALUE_ENC_SIZE].copy_from_slice(&self.value_enc);
start += VALUE_ENC_SIZE;
buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[0].0.to_bytes());
start += JubJubAffine::SIZE;
buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[0].1.to_bytes());
start += JubJubAffine::SIZE;
buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[1].0.to_bytes());
start += JubJubAffine::SIZE;
buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[1].1.to_bytes());

buf[start..start + Sender::SIZE]
.copy_from_slice(&self.sender.to_bytes());

buf
}
Expand All @@ -454,30 +410,164 @@ impl Serializable<SIZE> for Note {
bytes[0].try_into().map_err(|_| BytesError::InvalidData)?;

let mut buf = &bytes[1..];

let value_commitment = JubJubAffine::from_reader(&mut buf)?;

let stealth_address = StealthAddress::from_reader(&mut buf)?;

let pos = u64::from_reader(&mut buf)?;

let mut value_enc = [0u8; VALUE_ENC_SIZE];
value_enc.copy_from_slice(&buf[..VALUE_ENC_SIZE]);

buf = &buf[VALUE_ENC_SIZE..];

let sender_enc_A_0 = JubJubAffine::from_reader(&mut buf)?;
let sender_enc_A_1 = JubJubAffine::from_reader(&mut buf)?;
let sender_enc_B_0 = JubJubAffine::from_reader(&mut buf)?;
let sender_enc_B_1 = JubJubAffine::from_reader(&mut buf)?;
let sender = Sender::from_reader(&mut buf)?;

Ok(Note {
note_type,
value_commitment,
stealth_address,
pos,
value_enc,
sender_enc: [
(sender_enc_A_0, sender_enc_A_1),
(sender_enc_B_0, sender_enc_B_1),
],
sender,
})
}
}

/// The sender of the `Note`.
/// This can be either the encrypted sender's [`PublicKey`], if the [`Note`] was
/// created as an output note of a phoenix-transaction, or some contract-data if
/// the [`Note`] was created in another way, e.g. by withdrawing from a
/// contract.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "rkyv-impl",
derive(Archive, Serialize, Deserialize),
archive_attr(derive(bytecheck::CheckBytes))
)]
pub enum Sender {
/// The sender's [`PublicKey`], encrypted using the note_pk of the
/// stealth-address.
Encryption([(JubJubAffine, JubJubAffine); 2]),
/// Information to identify the origin of a `Note`, if it wasn't created as
/// a phoenix-transaction output-note.
ContractInfo([u8; 4 * JubJubAffine::SIZE]),
}

impl Sender {
/// Create a new [`Sender`] enum by encrypting the sender's [`PublicKey`] in
/// a way that only the receiver of the note can decrypt.
pub fn encrypt(
note_pk: &NotePublicKey,
sender_pk: &PublicKey,
blinder: &[JubJubScalar; 2],
) -> Self {
let sender_enc_A =
elgamal::encrypt(note_pk.as_ref(), sender_pk.A(), &blinder[0]);

let sender_enc_B =
elgamal::encrypt(note_pk.as_ref(), sender_pk.B(), &blinder[1]);

let sender_enc_A: (JubJubAffine, JubJubAffine) =
(sender_enc_A.0.into(), sender_enc_A.1.into());
let sender_enc_B: (JubJubAffine, JubJubAffine) =
(sender_enc_B.0.into(), sender_enc_B.1.into());

Self::Encryption([sender_enc_A, sender_enc_B])
}

/// Decrypts the [`PublicKey`] of the sender of the [`Note`], using the
/// [`NoteSecretKey`] generated by the receiver's [`SecretKey`] and the
/// [`StealthAddress`] of the [`Note`].
///
/// Note: Decryption with an *incorrect* [`NoteSecretKey`] will still yield
/// a [`PublicKey`], but in this case, the public-key will be a random one
/// that has nothing to do with the sender's [`PublicKey`].
///
/// Returns an error if the sender is of type [`Sender::ContractInfo`].
pub fn decrypt(&self, note_sk: &NoteSecretKey) -> Result<PublicKey, Error> {
let sender_enc = match self {
Sender::Encryption(enc) => enc,
Sender::ContractInfo(_) => {
return Err(Error::InvalidEncryption);
}
};

let sender_enc_A = sender_enc[0];
let sender_enc_B = sender_enc[1];

let decrypt_A = elgamal::decrypt(
note_sk.as_ref(),
&(sender_enc_A.0.into(), sender_enc_A.1.into()),
);
let decrypt_B = elgamal::decrypt(
note_sk.as_ref(),
&(sender_enc_B.0.into(), sender_enc_B.1.into()),
);

Ok(PublicKey::new(decrypt_A, decrypt_B))
}
}

impl Serializable<{ 1 + 4 * JubJubAffine::SIZE }> for Sender {
type Error = BytesError;

/// Converts a Note into a byte representation
fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut buf = [0u8; Self::SIZE];

match self {
Sender::Encryption(sender_enc) => {
buf[0] = 0;
let mut start = 1;

buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&sender_enc[0].0.to_bytes());
start += JubJubAffine::SIZE;

buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&sender_enc[0].1.to_bytes());
start += JubJubAffine::SIZE;

buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&sender_enc[1].0.to_bytes());
start += JubJubAffine::SIZE;

buf[start..start + JubJubAffine::SIZE]
.copy_from_slice(&sender_enc[1].1.to_bytes());
}
Sender::ContractInfo(contract_data) => {
buf[0] = 1;
buf[1..].copy_from_slice(&contract_data[..]);
}
}

buf
}

/// Attempts to convert a byte representation of a note into a `Note`,
/// failing if the input is invalid
fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result<Self, Self::Error> {
let sender = match bytes[0] {
0 => {
let mut buf = &bytes[1..];
let sender_enc_A_0 = JubJubAffine::from_reader(&mut buf)?;
let sender_enc_A_1 = JubJubAffine::from_reader(&mut buf)?;
let sender_enc_B_0 = JubJubAffine::from_reader(&mut buf)?;
let sender_enc_B_1 = JubJubAffine::from_reader(&mut buf)?;
Sender::Encryption([
(sender_enc_A_0, sender_enc_A_1),
(sender_enc_B_0, sender_enc_B_1),
])
}
1 => {
let mut contract_data = [0u8; 4 * JubJubAffine::SIZE];
contract_data.copy_from_slice(&bytes[1..Self::SIZE]);
Sender::ContractInfo(contract_data)
}
_ => return Err(BytesError::InvalidData),
};

Ok(sender)
}
}
3 changes: 1 addition & 2 deletions core/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ impl TxSkeleton {

let num_nullifiers = self.nullifiers.len() as u64;
bytes.extend(num_nullifiers.to_bytes());

self.nullifiers.iter().for_each(|nullifier| {
bytes.extend(nullifier.to_bytes());
});
Expand All @@ -88,9 +87,9 @@ impl TxSkeleton {
pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
let mut buffer = buf;
let root = BlsScalar::from_reader(&mut buffer)?;

let num_nullifiers = u64::from_reader(&mut buffer)?;
let mut nullifiers = Vec::with_capacity(num_nullifiers as usize);

for _ in 0..num_nullifiers {
nullifiers.push(BlsScalar::from_reader(&mut buffer)?);
}
Expand Down
Loading

0 comments on commit ec8ac0b

Please sign in to comment.