diff --git a/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr index 4c06546d7a98..c08c4cd24083 100644 --- a/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr @@ -24,8 +24,8 @@ contract PrivateFPC { // convince the FPC we are not cheating context.push_nullifier(user_randomness); - // We use different randomness for fee payer to prevent a potential privay leak (see impl - // of PrivatelyRefundable for TokenNote for details). + // We use different randomness for fee payer to prevent a potential privay leak (see description + // of `setup_refund(...)` function in TokenWithRefunds for details. let fee_payer_randomness = poseidon2_hash([user_randomness]); // We emit fee payer randomness to ensure FPC admin can reconstruct their fee note emit_randomness_as_unencrypted_log(&mut context, fee_payer_randomness); diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr index d9bca24ecd96..2c452fd389e1 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/main.nr @@ -430,6 +430,21 @@ contract TokenWithRefunds { }; use crate::types::token_note::TokenNoteHidingPoint; + /// We need to use different randomness for the user and for the fee payer notes because if the randomness values + /// were the same we could fingerprint the user by doing the following: + /// 1) randomness_influence = 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 = user_point - randomness_influence = + /// = (G_npk * user_npk + G_rnd * randomness) - 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 fee payer npk values of these known contracts is a feasible attack. + /// + /// fee_payer_point and user_point above are public information since there are args to the public + /// `complete_refund(...)` function. #[aztec(private)] fn setup_refund( fee_payer: AztecAddress, // Address of the entity which will receive the fee note. diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr index 23d74d027556..47efdfc84e96 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr @@ -140,82 +140,3 @@ impl OwnedNote for TokenNote { self.amount } } - -/** - * What is happening below? - * - * First in generate_refund_points, we create two points on the grumpkin curve; - * 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. - * - * This is because the compute_note_hiding_point function above defines the hiding point as: - * - * G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness - * - * where G_amt, G_npk and G_rnd are generator points. Interesting point here is that we actually need to convert - * - amount - * - 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. - * - * (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 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 := G_npk * fee_payer_npk + G_rnd * fee_payer_randomness - * incomplete_user_point := G_npk * user_npk + G_rnd * user_randomness - * - * So we pass those points into the teardown function (here) and compute a third point corresponding to the transaction - * fee as just: - * - * fee_point := G_amt * transaction_fee - * refund_point := G_amt * (funded_amount - transaction_fee) - * - * where `funded_amount` is the total amount in tokens that the sponsored user initially supplied and the transaction - * fee is the final transaction fee whose value is made available in the public teardown function. - * - * Then we arrive at the final points via addition of the fee and refund points: - * - * 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 + refund_point = - * = (G_npk * user_npk + G_rnd + user_randomness) + G_amt * (funded_amount - transaction_fee) = - * = G_amt * (funded_amount - transaction_fee) + G_npk * user_npk + G_rnd * user_randomness - * - * The point above matches the note_hiding_point of (and therefore *is*) notes like: - * { - * amount: (funded_amount - transaction_fee), - * npk_m_hash: user_npk, - * randomness: user_randomness - * } - * - * 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_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 - randomness_influence = - * = (G_npk * user_npk + G_rnd * randomness) - 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 - * fee payer npk values of these known contracts is a feasible attack. - */