-
Notifications
You must be signed in to change notification settings - Fork 234
/
e2e_keys.test.ts
129 lines (108 loc) · 5.25 KB
/
e2e_keys.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { createAccounts } from '@aztec/accounts/testing';
import {
type AccountWallet,
type AztecAddress,
type AztecNode,
Fr,
type L2Block,
type PXE,
type Wallet,
} from '@aztec/aztec.js';
import {
GeneratorIndex,
INITIAL_L2_BLOCK_NUM,
computeAppNullifierSecretKey,
computeAppSecretKey,
deriveMasterNullifierSecretKey,
deriveMasterOutgoingViewingSecretKey,
derivePublicKeyFromSecretKey,
} from '@aztec/circuits.js';
import { siloNullifier } from '@aztec/circuits.js/hash';
import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto';
import { TestContract } from '@aztec/noir-contracts.js';
import { jest } from '@jest/globals';
import { setup } from './fixtures/utils.js';
const TIMEOUT = 120_000;
describe('Key Registry', () => {
jest.setTimeout(TIMEOUT);
let aztecNode: AztecNode;
let pxe: PXE;
let teardown: () => Promise<void>;
let testContract: TestContract;
const secret = Fr.random();
let account: AccountWallet;
beforeAll(async () => {
let wallet: Wallet;
({ aztecNode, pxe, teardown, wallet } = await setup(2));
testContract = await TestContract.deploy(wallet).send().deployed();
[account] = await createAccounts(pxe, 1, [secret]);
});
afterAll(() => teardown());
describe('using nsk_app to detect nullification', () => {
// This test checks that it possible to detect that a note has been nullified just by using nsk_app. Note that
// this only works for non-transient notes as transient ones never emit a note hash which makes it impossible
// to brute force their nullifier.
// This might seem to make the scheme useless in practice. This could not be the case because if you have
// a note of funds, when you create the transient you are nullifying that note. So even if I cannot see when you
// nullified the transient ones, I can see that you nullified the first.
//
// E.g.: Say you have a note A, which is 10 $, you nullify it (I can see) and create B and C, that you then spend.
// I cannot see B and C, but I saw A, so I knew that you did something with those funds.
//
// There are some examples where the action is fully hidden though. One of those examples is shielding where you
// instantly consume the note after creating it. In this case, the nullifier is never emitted and hence the action
// is impossible to detect with this scheme.
// Another example is a withdraw is withdrawing from DeFi and then immediately spending the funds. In this case,
// we would need nsk_app and the contract address of the DeFi contract to detect the nullification of the initial
// note.
it('nsk_app and contract address are enough to detect note nullification', async () => {
const masterNullifierSecretKey = deriveMasterNullifierSecretKey(secret);
const nskApp = computeAppNullifierSecretKey(masterNullifierSecretKey, testContract.address);
const noteValue = 5;
const noteOwner = account.getAddress();
const outgoingViewer = noteOwner; // Setting the outgoing viewer to owner to not have to bother with setting up another account.
const noteStorageSlot = 12;
await testContract.methods.call_create_note(noteValue, noteOwner, outgoingViewer, noteStorageSlot).send().wait();
expect(await getNumNullifiedNotes(nskApp, testContract.address)).toEqual(0);
await testContract.withWallet(account).methods.call_destroy_note(noteStorageSlot).send().wait();
expect(await getNumNullifiedNotes(nskApp, testContract.address)).toEqual(1);
});
const getNumNullifiedNotes = async (nskApp: Fr, contractAddress: AztecAddress) => {
// 1. Get all the note hashes
const blocks = await aztecNode.getBlocks(INITIAL_L2_BLOCK_NUM, 1000);
const noteHashes = blocks.flatMap((block: L2Block) =>
block.body.txEffects.flatMap(txEffect => txEffect.noteHashes),
);
// 2. Get all the seen nullifiers
const nullifiers = blocks.flatMap((block: L2Block) =>
block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
);
// 3. Derive all the possible nullifiers using nskApp
const derivedNullifiers = noteHashes.map(noteHash => {
const innerNullifier = poseidon2HashWithSeparator([noteHash, nskApp], GeneratorIndex.NOTE_NULLIFIER);
return siloNullifier(contractAddress, innerNullifier);
});
// 4. Count the number of derived nullifiers that are in the nullifiers array
return derivedNullifiers.reduce((count, derived) => {
if (nullifiers.some(nullifier => nullifier.equals(derived))) {
count++;
}
return count;
}, 0);
};
});
describe('ovsk_app', () => {
it('gets ovsk_app', async () => {
// Derive the ovpk_m_hash from the account secret
const ovskM = deriveMasterOutgoingViewingSecretKey(secret);
const ovpkMHash = derivePublicKeyFromSecretKey(ovskM).hash();
// Compute the expected ovsk_app
const expectedOvskApp = computeAppSecretKey(ovskM, testContract.address, 'ov');
// Get the ovsk_app via the test contract
const ovskAppBigInt = await testContract.methods.get_ovsk_app(ovpkMHash).simulate();
const ovskApp = new Fr(ovskAppBigInt);
// Check that the ovsk_app is as expected
expect(ovskApp).toEqual(expectedOvskApp);
});
});
});