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

fix: using different generators in private refund #7414

Merged
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
20 changes: 20 additions & 0 deletions noir-projects/aztec-nr/aztec/src/generators.nr
Copy link
Collaborator

Choose a reason for hiding this comment

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

Awesome! I had no idea derive_generators was a thing. This is perfect!!

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use dep::protocol_types::point::Point;

// A set of generators generated with `derive_generators(...)` function from noir::std
global Ga1 = Point { x: 0x30426e64aee30e998c13c8ceecda3a77807dbead52bc2f3bf0eae851b4b710c1, y: 0x113156a068f603023240c96b4da5474667db3b8711c521c748212a15bc034ea6, is_infinite: false };
global Ga2 = Point { x: 0x2825c79cc6a5cbbeef7d6a8f1b6a12b312aa338440aefeb4396148c89147c049, y: 0x129bfd1da54b7062d6b544e7e36b90736350f6fba01228c41c72099509f5701e, is_infinite: false };
global Ga3 = Point { x: 0x0edb1e293c3ce91bfc04e3ceaa50d2c541fa9d091c72eb403efb1cfa2cb3357f, y: 0x1341d675fa030ece3113ad53ca34fd13b19b6e9762046734f414824c4d6ade35, is_infinite: false };

mod test {
use crate::generators::{Ga1, Ga2, Ga3};
use dep::protocol_types::point::Point;
use std::hash::derive_generators;

#[test]
fn test_generators() {
let generators: [Point; 3] = derive_generators("aztec_nr_generators".as_bytes(), 0);
assert_eq(generators[0], Ga1);
assert_eq(generators[1], Ga2);
assert_eq(generators[2], Ga3);
}
}
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod context;
mod deploy;
mod generators;
mod hash;
mod history;
mod initializer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use dep::aztec::{
prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext},
protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::Point, scalar::Scalar, hash::poseidon2_hash},
note::utils::compute_note_hash_for_consumption, oracle::unsafe_rand::unsafe_rand,
keys::getters::get_nsk_app, note::note_getter_options::PropertySelector
keys::getters::get_nsk_app, note::note_getter_options::PropertySelector,
generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd}
};
use dep::std::field::bn254::decompose;
use dep::std::embedded_curve_ops::multi_scalar_mul;
Expand Down Expand Up @@ -32,8 +33,6 @@ trait PrivatelyRefundable {

global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header.
global TOKEN_NOTE_BYTES_LEN: Field = 3 * 32 + 64;
// Grumpkin generator point.
global G1 = Point { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false };

#[aztec(note)]
struct TokenNote {
Expand Down Expand Up @@ -75,11 +74,11 @@ impl NoteInterface<TOKEN_NOTE_LEN, TOKEN_NOTE_BYTES_LEN> for TokenNote {
fn compute_note_content_hash(self) -> Field {
let (npk_lo, npk_hi) = decompose(self.npk_m_hash);
let (random_lo, random_hi) = decompose(self.randomness);
// We compute the note content hash as an x-coordinate of `G ^ (amount + npk_m_hash + randomness)` instead
// We compute the note content hash as an x-coordinate of `G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness` instead
// of using pedersen or poseidon2 because it allows us to privately add and subtract from amount in public
// by leveraging homomorphism.
multi_scalar_mul(
[G1, G1, G1],
[G_amt, G_npk, G_rnd],
[Scalar {
lo: self.amount.to_integer(),
hi: 0
Expand Down Expand Up @@ -126,56 +125,58 @@ impl OwnedNote for TokenNote {
* these are going to be eventually turned into notes:
* one for the user, and one for the fee payer.
*
* So you can think of these (x,y) points as "partial notes": they encode part of the internals of the notes.
* So you can think of these (x, y) points as "partial notes": they encode part of the internals of the notes.
*
* This is because the compute_note_content_hash function above defines the content hash to be
* the x-coordinate of a point defined as:
*
* amount * G + npk * G + randomness * G
* = (amount + npk + randomness) * G
* G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness
*
* where G is a generator point. Interesting point here is that we actually need to convert
* where G_amt, G_npk and G_rnd are generator points. Interesting point here is that we actually need to convert
* - amount
* - npk
* - npk_m_hash
* - randomness
* from grumpkin Field elements
* (which have a modulus of 21888242871839275222246405745257275088548364400416034343698204186575808495617)
* into a grumpkin scalar
* (which have a modulus of 21888242871839275222246405745257275088696311157297823662689037894645226208583)
*
* The intuition for this is that the Field elements define the domain of the x,y coordinates for points on the curves,
* but the number of points on the curve is actually greater than the size of that domain.
* The intuition for this is that the Field elements define the domain of the x, y coordinates for points on
* the curves, but the number of points on the curve is actually greater than the size of that domain.
*
* (Consider, e.g. if the curve were defined over a field of 10 elements, and each x coord had two corresponding y for +/-)
* (Consider, e.g. if the curve were defined over a field of 10 elements, and each x coord had two corresponding
* y for +/-)
*
* For a bit more info, see
* https://hackmd.io/@aztec-network/ByzgNxBfd#2-Grumpkin---A-curve-on-top-of-BN-254-for-SNARK-efficient-group-operations
*
*
* Anyway, if we have a secret scalar n := amount + npk + randomness, and then we reveal a point n * G, there is no efficient way to
* deduce what n is. This is the discrete log problem.
* Anyway, if we have a secret scalar s, and then we reveal a point s * G (G being a generator), there is no efficient
* way to deduce what s is. This is the discrete log problem.
*
* However we can still perform addition/subtraction on points! That is why we generate those two points, which are:
* incomplete_fee_payer_point := (fee_payer_npk + fee_payer_randomness) * G
* incomplete_user_point := (user_npk + funded_amount + user_randomness) * G
* incomplete_fee_payer_point := G_npk * fee_payer_npk + G_amt * fee_payer_randomness
* incomplete_user_point := G_npk * user_npk + G_amt * funded_amount + G_rnd * user_randomness
*
* where `funded_amount` is the total amount in tokens that the sponsored user initially supplied, from which the transaction fee will be subtracted.
* where `funded_amount` is the total amount in tokens that the sponsored user initially supplied, from which
* the transaction fee will be subtracted.
*
* So we pass those points into the teardown function (here) and compute a third point corresponding to the transaction fee as just
* So we pass those points into the teardown function (here) and compute a third point corresponding to the transaction
* fee as just:
*
* fee_point := transaction_fee * G
* fee_point := G_amt * transaction_fee
*
* Then we arrive at the final points via addition/subtraction of that transaction fee point:
*
* fee_payer_point := incomplete_fee_payer_point + fee_point
* = (fee_payer_npk + fee_payer_randomness) * G + transaction_fee * G
* = (fee_payer_npk + fee_payer_randomness + transaction_fee) * G
* fee_payer_point := incomplete_fee_payer_point + fee_point =
* = (G_npk * fee_payer_npk + G_rnd * fee_payer_randomness) + G_amt * transaction_fee =
* = G_amt * transaction_fee + G_npk * fee_payer_npk + G_rnd * fee_payer_randomness
*
* user_point := incomplete_user_point - fee_point
* = (user_npk + funded_amount + user_randomness) * G - transaction_fee * G
* = (user_npk + user_randomness + (funded_amount - transaction_fee)) * G
* user_point := incomplete_user_point - fee_point =
* = (G_amt * funded_amount + G_npk * user_npk + G_rnd + user_randomness) - G_amt * transaction_fee =
* = G_amt * (funded_amount - transaction_fee) + G_npk * user_npk + G_rnd + user_randomness
*
* When we return the x-coordinate of those points, it identically matches the note_content_hash of (and therefore *is*) notes like:
* The x-coordinate of points above identically matches the note_content_hash of (and therefore *is*) notes like:
* {
* amount: (funded_amount - transaction_fee),
* npk_m_hash: user_npk,
Expand All @@ -184,26 +185,29 @@ impl OwnedNote for TokenNote {
*
* Why do we need different randomness for the user and the fee payer notes?
* --> This is because if the randomness values were the same we could fingerprint the user by doing the following:
* 1) randomness_influence = incomplete_fee_payer_point - G * fee_payer_npk =
* = (fee_payer_npk + randomness) * G - G * fee_payer_npk = randomness * G
* 2) user_fingerprint = incomplete_user_point - G * funded_amount - randomness_influence =
* = (user_npk + funded_amount + randomness) * G - funded_amount * G - randomness * G =
* = user_npk * G
* 1) randomness_influence = incomplete_fee_payer_point - G_npk * fee_payer_npk =
* = (G_npk * fee_payer_npk + G_rnd * randomness) - G_npk * fee_payer_npk =
* = G_rnd * randomness
* 2) user_fingerprint = incomplete_user_point - G_amt * funded_amount - randomness_influence =
* = (G_npk * user_npk + G_amt * funded_amount + G_rnd * randomness) - G_amt * funded_amount
* - G_rnd * randomness =
* = G_npk * user_npk
* 3) Then the second time the user would use this fee paying contract we would recover the same fingerprint and
* link that the 2 transactions were made by the same user. Given that it's expected that only a limited set
* of fee paying contracts will be used and they will be known searching for fingerprints by trying different
* of fee paying contracts will be used and they will be known, searching for fingerprints by trying different
* fee payer npk values of these known contracts is a feasible attack.
*/
impl PrivatelyRefundable for TokenNote {
fn generate_refund_points(fee_payer_npk_m_hash: Field, user_npk_m_hash: Field, funded_amount: Field, user_randomness: Field, fee_payer_randomness: Field) -> (Point, Point) {
// 1. To be able to multiply generators with randomness and npk_m_hash using barretneberg's (BB) blackbox function we
// first need to convert the fields to high and low limbs.
// 1. To be able to multiply generators with randomness and npk_m_hash using barretneberg's (BB) blackbox
// function we first need to convert the fields to high and low limbs.
let (fee_payer_randomness_lo, fee_payer_randomness_hi) = decompose(fee_payer_randomness);
let (fee_payer_npk_m_hash_lo, fee_payer_npk_m_hash_hi) = decompose(fee_payer_npk_m_hash);

// 2. Now that we have correct representationsn of fee payer and randomness we can compute `G ^ (fee_payer_npk + randomness)`
// 2. Now that we have correct representationsn of fee payer and randomness we can compute
// `G_npk * fee_payer_npk + G_rnd * randomness`.
let incomplete_fee_payer_point = multi_scalar_mul(
[G1, G1],
[G_npk, G_rnd],
[Scalar {
lo: fee_payer_npk_m_hash_lo,
hi: fee_payer_npk_m_hash_hi
Expand All @@ -216,38 +220,38 @@ impl PrivatelyRefundable for TokenNote {

// 3. We do the necessary conversion for values relevant for the sponsored user point.
let (user_randomness_lo, user_randomness_hi) = decompose(user_randomness);
// TODO(#7324): representing user with their npk_m_hash here does not work with key rotation
let (user_lo, user_hi) = decompose(user_npk_m_hash);
// TODO(#7324), TODO(#7323): using npk_m_hash here is vulnerable in 2 ways described in the linked issues.
let (user_npk_lo, user_npk_hi) = decompose(user_npk_m_hash);
let (funded_amount_lo, funded_amount_hi) = decompose(funded_amount);

// 4. We compute `G ^ (user_npk_m_hash + funded_amount + randomness)`
// 4. We compute `G_amt * funded_amount + G_npk * user_npk_m_hash + G_rnd * randomness`.
let incomplete_user_point = multi_scalar_mul(
[G1, G1, G1],
[G_amt, G_npk, G_rnd],
[Scalar {
lo: user_lo,
hi: user_hi
},
Scalar {
lo: funded_amount_lo,
hi: funded_amount_hi
},
Scalar {
lo: user_npk_lo,
hi: user_npk_hi
},
Scalar {
lo: user_randomness_lo,
hi: user_randomness_hi
}]
);

// 5. At last we represent the points as Points and return them.
// 5. At last we return the points.
(incomplete_fee_payer_point, incomplete_user_point)
}

fn complete_refund(incomplete_fee_payer_point: Point, incomplete_user_point: Point, transaction_fee: Field) -> (Field, Field) {
// 1. We convert the transaction fee to high and low limbs to be able to use BB API.
let (transaction_fee_lo, transaction_fee_hi) = decompose(transaction_fee);

// 2. We compute the fee point as `G ^ transaction_fee`
// 2. We compute the fee point as `G_amt * transaction_fee`
let fee_point = multi_scalar_mul(
[G1],
[G_amt],
[Scalar {
lo: transaction_fee_lo,
hi: transaction_fee_hi,
Expand Down
Loading