Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: Add Sender struct #223

Merged
merged 1 commit into from
Jun 25, 2024
Merged
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
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]),
ureeves marked this conversation as resolved.
Show resolved Hide resolved
}

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call this recipient_note_pk

Copy link
Member Author

@moCello moCello Jun 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you insist? Because I think it adds more noise. In the end the npk of the stealth-address is used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not!

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