Skip to content

Commit

Permalink
fix: using different generators in private refund (#7414)
Browse files Browse the repository at this point in the history
Fixes #7320
  • Loading branch information
benesjan authored Jul 10, 2024
1 parent f177887 commit 59b92ca
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 48 deletions.
20 changes: 20 additions & 0 deletions noir-projects/aztec-nr/aztec/src/generators.nr
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

0 comments on commit 59b92ca

Please sign in to comment.