@aztec/stdlib 5.0.0-nightly.20260428 → 5.0.0-nightly.20260430
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/avm/avm.d.ts +300 -300
- package/dest/block/attestation_info.d.ts +3 -2
- package/dest/block/attestation_info.d.ts.map +1 -1
- package/dest/block/attestation_info.js +7 -5
- package/dest/block/block_data.d.ts +290 -1
- package/dest/block/block_data.d.ts.map +1 -1
- package/dest/block/block_data.js +14 -0
- package/dest/block/block_parameter.d.ts +30 -3
- package/dest/block/block_parameter.d.ts.map +1 -1
- package/dest/block/block_parameter.js +36 -2
- package/dest/block/l2_block_source.d.ts +39 -4
- package/dest/block/l2_block_source.d.ts.map +1 -1
- package/dest/block/proposal/attestations_and_signers.d.ts +13 -6
- package/dest/block/proposal/attestations_and_signers.d.ts.map +1 -1
- package/dest/block/proposal/attestations_and_signers.js +26 -18
- package/dest/checkpoint/checkpoint_data.d.ts +7 -1
- package/dest/checkpoint/checkpoint_data.d.ts.map +1 -1
- package/dest/checkpoint/checkpoint_data.js +2 -0
- package/dest/config/chain-config.d.ts +2 -2
- package/dest/config/chain-config.d.ts.map +1 -1
- package/dest/config/chain-config.js +2 -2
- package/dest/config/sequencer-config.d.ts +2 -2
- package/dest/config/sequencer-config.d.ts.map +1 -1
- package/dest/config/sequencer-config.js +6 -6
- package/dest/ha-signing/local_config.d.ts +1 -1
- package/dest/ha-signing/local_config.d.ts.map +1 -1
- package/dest/ha-signing/local_config.js +2 -2
- package/dest/interfaces/archiver.d.ts +1 -1
- package/dest/interfaces/archiver.d.ts.map +1 -1
- package/dest/interfaces/archiver.js +7 -3
- package/dest/interfaces/aztec-node-admin.d.ts +11 -1
- package/dest/interfaces/aztec-node-admin.d.ts.map +1 -1
- package/dest/interfaces/aztec-node-admin.js +2 -1
- package/dest/interfaces/aztec-node.d.ts +45 -55
- package/dest/interfaces/aztec-node.d.ts.map +1 -1
- package/dest/interfaces/aztec-node.js +18 -16
- package/dest/interfaces/block_response.d.ts +156 -0
- package/dest/interfaces/block_response.d.ts.map +1 -0
- package/dest/interfaces/block_response.js +24 -0
- package/dest/interfaces/chain_tips.d.ts +304 -0
- package/dest/interfaces/chain_tips.d.ts.map +1 -0
- package/dest/interfaces/chain_tips.js +11 -0
- package/dest/interfaces/checkpoint_parameter.d.ts +27 -0
- package/dest/interfaces/checkpoint_parameter.d.ts.map +1 -0
- package/dest/interfaces/checkpoint_parameter.js +20 -0
- package/dest/interfaces/checkpoint_response.d.ts +312 -0
- package/dest/interfaces/checkpoint_response.d.ts.map +1 -0
- package/dest/interfaces/checkpoint_response.js +26 -0
- package/dest/interfaces/client.d.ts +6 -1
- package/dest/interfaces/client.d.ts.map +1 -1
- package/dest/interfaces/client.js +5 -0
- package/dest/interfaces/configs.d.ts +7 -2
- package/dest/interfaces/configs.d.ts.map +1 -1
- package/dest/interfaces/configs.js +2 -1
- package/dest/interfaces/l1_publish_info.d.ts +43 -0
- package/dest/interfaces/l1_publish_info.d.ts.map +1 -0
- package/dest/interfaces/l1_publish_info.js +26 -0
- package/dest/interfaces/proving-job.d.ts +166 -166
- package/dest/interfaces/server.d.ts +6 -1
- package/dest/interfaces/server.d.ts.map +1 -1
- package/dest/interfaces/server.js +5 -0
- package/dest/interfaces/validator.d.ts +10 -1
- package/dest/interfaces/validator.d.ts.map +1 -1
- package/dest/interfaces/validator.js +1 -0
- package/dest/p2p/block_proposal.d.ts +19 -9
- package/dest/p2p/block_proposal.d.ts.map +1 -1
- package/dest/p2p/block_proposal.js +42 -32
- package/dest/p2p/checkpoint_attestation.d.ts +7 -3
- package/dest/p2p/checkpoint_attestation.d.ts.map +1 -1
- package/dest/p2p/checkpoint_attestation.js +15 -17
- package/dest/p2p/checkpoint_proposal.d.ts +15 -7
- package/dest/p2p/checkpoint_proposal.d.ts.map +1 -1
- package/dest/p2p/checkpoint_proposal.js +31 -29
- package/dest/p2p/consensus_payload.d.ts +18 -7
- package/dest/p2p/consensus_payload.d.ts.map +1 -1
- package/dest/p2p/consensus_payload.js +31 -19
- package/dest/p2p/signature_utils.d.ts +28 -19
- package/dest/p2p/signature_utils.d.ts.map +1 -1
- package/dest/p2p/signature_utils.js +118 -21
- package/dest/p2p/signed_txs.d.ts +15 -13
- package/dest/p2p/signed_txs.d.ts.map +1 -1
- package/dest/p2p/signed_txs.js +26 -24
- package/dest/tests/mocks.d.ts +7 -1
- package/dest/tests/mocks.d.ts.map +1 -1
- package/dest/tests/mocks.js +28 -14
- package/dest/timetable/index.d.ts +1 -1
- package/dest/timetable/index.d.ts.map +1 -1
- package/dest/timetable/index.js +25 -11
- package/dest/tx/profiling.js +4 -4
- package/package.json +8 -8
- package/src/block/attestation_info.ts +11 -11
- package/src/block/block_data.ts +17 -0
- package/src/block/block_parameter.ts +35 -2
- package/src/block/l2_block_source.ts +43 -3
- package/src/block/proposal/attestations_and_signers.ts +32 -17
- package/src/checkpoint/checkpoint_data.ts +4 -0
- package/src/config/chain-config.ts +2 -3
- package/src/config/sequencer-config.ts +10 -6
- package/src/ha-signing/local_config.ts +2 -2
- package/src/interfaces/archiver.ts +13 -3
- package/src/interfaces/aztec-node-admin.ts +3 -1
- package/src/interfaces/aztec-node.ts +105 -95
- package/src/interfaces/block_response.ts +79 -0
- package/src/interfaces/chain_tips.ts +24 -0
- package/src/interfaces/checkpoint_parameter.ts +22 -0
- package/src/interfaces/checkpoint_response.ts +84 -0
- package/src/interfaces/client.ts +5 -0
- package/src/interfaces/configs.ts +5 -1
- package/src/interfaces/l1_publish_info.ts +40 -0
- package/src/interfaces/server.ts +5 -0
- package/src/interfaces/validator.ts +5 -0
- package/src/p2p/block_proposal.ts +84 -28
- package/src/p2p/checkpoint_attestation.ts +15 -20
- package/src/p2p/checkpoint_proposal.ts +69 -37
- package/src/p2p/consensus_payload.ts +50 -28
- package/src/p2p/signature_utils.ts +110 -25
- package/src/p2p/signed_txs.ts +46 -28
- package/src/tests/mocks.ts +46 -26
- package/src/timetable/index.ts +26 -11
- package/src/tx/profiling.ts +4 -4
|
@@ -5,14 +5,15 @@ import {
|
|
|
5
5
|
IndexWithinCheckpoint,
|
|
6
6
|
SlotNumber,
|
|
7
7
|
} from '@aztec/foundation/branded-types';
|
|
8
|
-
import {
|
|
8
|
+
import type { BaseBuffer32 } from '@aztec/foundation/buffer';
|
|
9
9
|
import { keccak256 } from '@aztec/foundation/crypto/keccak';
|
|
10
|
-
import { tryRecoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
11
10
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
12
11
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
13
12
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
14
13
|
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
15
14
|
|
|
15
|
+
import type { TypedDataDefinition } from 'viem';
|
|
16
|
+
|
|
16
17
|
import type { L2Block } from '../block/l2_block.js';
|
|
17
18
|
import type { L2BlockInfo } from '../block/l2_block_info.js';
|
|
18
19
|
import { MAX_TXS_PER_BLOCK } from '../deserialization/index.js';
|
|
@@ -22,9 +23,15 @@ import { TxHash } from '../tx/index.js';
|
|
|
22
23
|
import type { Tx } from '../tx/tx.js';
|
|
23
24
|
import { Gossipable } from './gossipable.js';
|
|
24
25
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
type CoordinationSignatureContext,
|
|
27
|
+
type CoordinationSignatureType,
|
|
28
|
+
EMPTY_COORDINATION_SIGNATURE_CONTEXT,
|
|
29
|
+
type Signable,
|
|
30
|
+
coordinationSignatureContextEquals,
|
|
31
|
+
getCoordinationSignatureTypedData,
|
|
32
|
+
readCoordinationSignatureContext,
|
|
33
|
+
recoverCoordinationSigner,
|
|
34
|
+
serializeCoordinationSignatureContext,
|
|
28
35
|
} from './signature_utils.js';
|
|
29
36
|
import { SignedTxs } from './signed_txs.js';
|
|
30
37
|
import { TopicType } from './topic_type.js';
|
|
@@ -49,10 +56,12 @@ export type BlockProposalOptions = {
|
|
|
49
56
|
* to be included in a block within a checkpoint. This is used for non-last blocks in a slot.
|
|
50
57
|
* The last block is sent as part of a CheckpointProposal.
|
|
51
58
|
*/
|
|
52
|
-
export class BlockProposal extends Gossipable {
|
|
59
|
+
export class BlockProposal extends Gossipable implements Signable {
|
|
53
60
|
static override p2pTopic = TopicType.block_proposal;
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
readonly primaryType: CoordinationSignatureType = 'BlockProposal';
|
|
63
|
+
|
|
64
|
+
private cachedSender: EthAddress | undefined | null = undefined;
|
|
56
65
|
|
|
57
66
|
constructor(
|
|
58
67
|
/** The per-block header containing block state and global variables */
|
|
@@ -73,6 +82,9 @@ export class BlockProposal extends Gossipable {
|
|
|
73
82
|
/** The proposer's signature over the block data */
|
|
74
83
|
public readonly signature: Signature,
|
|
75
84
|
|
|
85
|
+
/** The signing domain (chainId + rollupAddress) the signature is bound to */
|
|
86
|
+
public readonly signatureContext: CoordinationSignatureContext,
|
|
87
|
+
|
|
76
88
|
/** The signed transactions in the block (optional, for DA guarantees) */
|
|
77
89
|
public readonly signedTxs?: SignedTxs,
|
|
78
90
|
) {
|
|
@@ -114,9 +126,8 @@ export class BlockProposal extends Gossipable {
|
|
|
114
126
|
* Get the payload to sign for this block proposal.
|
|
115
127
|
* The signature is over: blockHeader + indexWithinCheckpoint + inHash + archiveRoot + txHashes
|
|
116
128
|
*/
|
|
117
|
-
getPayloadToSign(
|
|
129
|
+
getPayloadToSign(): Buffer {
|
|
118
130
|
return serializeToBuffer([
|
|
119
|
-
domainSeparator,
|
|
120
131
|
this.blockHeader,
|
|
121
132
|
this.indexWithinCheckpoint,
|
|
122
133
|
this.inHash,
|
|
@@ -134,7 +145,9 @@ export class BlockProposal extends Gossipable {
|
|
|
134
145
|
archiveRoot: Fr,
|
|
135
146
|
txHashes: TxHash[],
|
|
136
147
|
txs: Tx[] | undefined,
|
|
137
|
-
|
|
148
|
+
signatureContext: CoordinationSignatureContext,
|
|
149
|
+
proposalSigner: (typedData: TypedDataDefinition, context: SigningContext) => Promise<Signature>,
|
|
150
|
+
txsSigner?: (typedData: TypedDataDefinition, context: SigningContext) => Promise<Signature>,
|
|
138
151
|
): Promise<BlockProposal> {
|
|
139
152
|
// Create a temporary proposal to get the payload to sign
|
|
140
153
|
const tempProposal = new BlockProposal(
|
|
@@ -144,6 +157,7 @@ export class BlockProposal extends Gossipable {
|
|
|
144
157
|
archiveRoot,
|
|
145
158
|
txHashes,
|
|
146
159
|
Signature.empty(),
|
|
160
|
+
signatureContext,
|
|
147
161
|
);
|
|
148
162
|
|
|
149
163
|
// Create the block signing context
|
|
@@ -155,47 +169,64 @@ export class BlockProposal extends Gossipable {
|
|
|
155
169
|
dutyType: DutyType.BLOCK_PROPOSAL,
|
|
156
170
|
};
|
|
157
171
|
|
|
158
|
-
const
|
|
159
|
-
const sig = await
|
|
172
|
+
const typedData = getCoordinationSignatureTypedData(tempProposal);
|
|
173
|
+
const sig = await proposalSigner(typedData, blockContext);
|
|
160
174
|
|
|
161
175
|
// If txs are provided, sign them as well
|
|
162
176
|
let signedTxs: SignedTxs | undefined;
|
|
163
177
|
if (txs) {
|
|
164
178
|
const txsSigningContext: SigningContext = { dutyType: DutyType.TXS };
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
if (!txsSigner) {
|
|
180
|
+
throw new Error('signed_txs requires a typed-data signer');
|
|
181
|
+
}
|
|
182
|
+
signedTxs = await SignedTxs.createFromSigner(txs, signatureContext, typedData =>
|
|
183
|
+
txsSigner(typedData, txsSigningContext),
|
|
184
|
+
);
|
|
167
185
|
}
|
|
168
186
|
|
|
169
|
-
return new BlockProposal(
|
|
187
|
+
return new BlockProposal(
|
|
188
|
+
blockHeader,
|
|
189
|
+
indexWithinCheckpoint,
|
|
190
|
+
inHash,
|
|
191
|
+
archiveRoot,
|
|
192
|
+
txHashes,
|
|
193
|
+
sig,
|
|
194
|
+
signatureContext,
|
|
195
|
+
signedTxs,
|
|
196
|
+
);
|
|
170
197
|
}
|
|
171
198
|
|
|
172
199
|
/**
|
|
173
200
|
* Lazily evaluate the sender of the proposal; result is cached.
|
|
174
|
-
* If there's signedTxs, also verifies
|
|
175
|
-
*
|
|
201
|
+
* If there's signedTxs, also verifies that its signing domain matches this proposal's and
|
|
202
|
+
* that the signedTxs sender matches the block proposal sender. This prevents a proposer
|
|
203
|
+
* from wrapping a foreign-chain SignedTxs bundle inside a local-chain proposal.
|
|
204
|
+
* @returns The sender address, or undefined if signature recovery fails or inner/outer mismatch
|
|
176
205
|
*/
|
|
177
206
|
getSender(): EthAddress | undefined {
|
|
178
|
-
if (
|
|
179
|
-
const
|
|
180
|
-
const blockSender = tryRecoverAddress(hashed, this.signature);
|
|
207
|
+
if (this.cachedSender === undefined) {
|
|
208
|
+
const blockSender = recoverCoordinationSigner(this, this.signature);
|
|
181
209
|
|
|
182
|
-
// If there's signedTxs, verify the sender matches
|
|
183
210
|
if (blockSender && this.signedTxs) {
|
|
211
|
+
if (!coordinationSignatureContextEquals(this.signedTxs.signatureContext, this.signatureContext)) {
|
|
212
|
+
this.cachedSender = null;
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
184
215
|
const txsSender = this.signedTxs.getSender();
|
|
185
216
|
if (!txsSender || !txsSender.equals(blockSender)) {
|
|
186
|
-
|
|
217
|
+
this.cachedSender = null;
|
|
218
|
+
return undefined;
|
|
187
219
|
}
|
|
188
220
|
}
|
|
189
221
|
|
|
190
|
-
|
|
191
|
-
this.sender = blockSender;
|
|
222
|
+
this.cachedSender = blockSender ?? null;
|
|
192
223
|
}
|
|
193
224
|
|
|
194
|
-
return this.
|
|
225
|
+
return this.cachedSender ?? undefined;
|
|
195
226
|
}
|
|
196
227
|
|
|
197
228
|
getPayload() {
|
|
198
|
-
return this.getPayloadToSign(
|
|
229
|
+
return this.getPayloadToSign();
|
|
199
230
|
}
|
|
200
231
|
|
|
201
232
|
toBuffer(): Buffer {
|
|
@@ -205,6 +236,7 @@ export class BlockProposal extends Gossipable {
|
|
|
205
236
|
this.inHash,
|
|
206
237
|
this.archiveRoot,
|
|
207
238
|
this.signature,
|
|
239
|
+
serializeCoordinationSignatureContext(this.signatureContext),
|
|
208
240
|
this.txHashes.length,
|
|
209
241
|
this.txHashes,
|
|
210
242
|
];
|
|
@@ -225,6 +257,7 @@ export class BlockProposal extends Gossipable {
|
|
|
225
257
|
const inHash = reader.readObject(Fr);
|
|
226
258
|
const archiveRoot = reader.readObject(Fr);
|
|
227
259
|
const signature = reader.readObject(Signature);
|
|
260
|
+
const signatureContext = readCoordinationSignatureContext(reader);
|
|
228
261
|
const txHashCount = reader.readNumber();
|
|
229
262
|
if (txHashCount > MAX_TXS_PER_BLOCK) {
|
|
230
263
|
throw new Error(`txHashes count ${txHashCount} exceeds maximum ${MAX_TXS_PER_BLOCK}`);
|
|
@@ -242,12 +275,21 @@ export class BlockProposal extends Gossipable {
|
|
|
242
275
|
archiveRoot,
|
|
243
276
|
txHashes,
|
|
244
277
|
signature,
|
|
278
|
+
signatureContext,
|
|
245
279
|
signedTxs,
|
|
246
280
|
);
|
|
247
281
|
}
|
|
248
282
|
}
|
|
249
283
|
|
|
250
|
-
return new BlockProposal(
|
|
284
|
+
return new BlockProposal(
|
|
285
|
+
blockHeader,
|
|
286
|
+
indexWithinCheckpoint,
|
|
287
|
+
inHash,
|
|
288
|
+
archiveRoot,
|
|
289
|
+
txHashes,
|
|
290
|
+
signature,
|
|
291
|
+
signatureContext,
|
|
292
|
+
);
|
|
251
293
|
}
|
|
252
294
|
|
|
253
295
|
getSize(): number {
|
|
@@ -257,6 +299,8 @@ export class BlockProposal extends Gossipable {
|
|
|
257
299
|
this.inHash.size +
|
|
258
300
|
this.archiveRoot.size +
|
|
259
301
|
this.signature.getSize() +
|
|
302
|
+
4 /* chainId */ +
|
|
303
|
+
20 /* rollupAddress */ +
|
|
260
304
|
4 /* txHashes.length */ +
|
|
261
305
|
this.txHashes.length * TxHash.SIZE +
|
|
262
306
|
4 /* hasSignedTxs flag */ +
|
|
@@ -265,7 +309,15 @@ export class BlockProposal extends Gossipable {
|
|
|
265
309
|
}
|
|
266
310
|
|
|
267
311
|
static empty(): BlockProposal {
|
|
268
|
-
return new BlockProposal(
|
|
312
|
+
return new BlockProposal(
|
|
313
|
+
BlockHeader.empty(),
|
|
314
|
+
IndexWithinCheckpoint(0),
|
|
315
|
+
Fr.ZERO,
|
|
316
|
+
Fr.ZERO,
|
|
317
|
+
[],
|
|
318
|
+
Signature.empty(),
|
|
319
|
+
EMPTY_COORDINATION_SIGNATURE_CONTEXT,
|
|
320
|
+
);
|
|
269
321
|
}
|
|
270
322
|
|
|
271
323
|
static random(): BlockProposal {
|
|
@@ -276,6 +328,7 @@ export class BlockProposal extends Gossipable {
|
|
|
276
328
|
Fr.random(),
|
|
277
329
|
[TxHash.random(), TxHash.random()],
|
|
278
330
|
Signature.random(),
|
|
331
|
+
EMPTY_COORDINATION_SIGNATURE_CONTEXT,
|
|
279
332
|
);
|
|
280
333
|
}
|
|
281
334
|
|
|
@@ -287,6 +340,8 @@ export class BlockProposal extends Gossipable {
|
|
|
287
340
|
archiveRoot: this.archiveRoot.toString(),
|
|
288
341
|
signature: this.signature.toString(),
|
|
289
342
|
txHashes: this.txHashes.map(h => h.toString()),
|
|
343
|
+
chainId: this.signatureContext.chainId,
|
|
344
|
+
rollupAddress: this.signatureContext.rollupAddress.toString(),
|
|
290
345
|
};
|
|
291
346
|
}
|
|
292
347
|
|
|
@@ -312,6 +367,7 @@ export class BlockProposal extends Gossipable {
|
|
|
312
367
|
this.archiveRoot,
|
|
313
368
|
this.txHashes,
|
|
314
369
|
this.signature,
|
|
370
|
+
this.signatureContext,
|
|
315
371
|
);
|
|
316
372
|
}
|
|
317
373
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { CheckpointAttestationHash, SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import type { BaseBuffer32 } from '@aztec/foundation/buffer';
|
|
3
3
|
import { keccak256 } from '@aztec/foundation/crypto/keccak';
|
|
4
|
-
import { tryRecoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
5
4
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
6
5
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
7
6
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
@@ -13,7 +12,7 @@ import type { ZodFor } from '../schemas/index.js';
|
|
|
13
12
|
import { CheckpointProposal } from './checkpoint_proposal.js';
|
|
14
13
|
import { ConsensusPayload } from './consensus_payload.js';
|
|
15
14
|
import { Gossipable } from './gossipable.js';
|
|
16
|
-
import {
|
|
15
|
+
import { type CoordinationSignatureContext, recoverCoordinationSigner } from './signature_utils.js';
|
|
17
16
|
import { TopicType } from './topic_type.js';
|
|
18
17
|
|
|
19
18
|
export type { CheckpointAttestationHash } from '@aztec/foundation/branded-types';
|
|
@@ -27,8 +26,8 @@ export type { CheckpointAttestationHash } from '@aztec/foundation/branded-types'
|
|
|
27
26
|
export class CheckpointAttestation extends Gossipable {
|
|
28
27
|
static override p2pTopic = TopicType.checkpoint_attestation;
|
|
29
28
|
|
|
30
|
-
private
|
|
31
|
-
private
|
|
29
|
+
private cachedSender: EthAddress | undefined | null = undefined;
|
|
30
|
+
private cachedProposer: EthAddress | undefined | null = undefined;
|
|
32
31
|
|
|
33
32
|
constructor(
|
|
34
33
|
/** The payload of the message, and what the signature is over */
|
|
@@ -65,22 +64,19 @@ export class CheckpointAttestation extends Gossipable {
|
|
|
65
64
|
return this.payload.header.slotNumber;
|
|
66
65
|
}
|
|
67
66
|
|
|
67
|
+
get signatureContext(): CoordinationSignatureContext {
|
|
68
|
+
return this.payload.signatureContext;
|
|
69
|
+
}
|
|
70
|
+
|
|
68
71
|
/**
|
|
69
72
|
* Lazily evaluate and cache the signer of the attestation
|
|
70
73
|
* @returns The signer of the attestation, or undefined if signature recovery fails
|
|
71
74
|
*/
|
|
72
75
|
getSender(): EthAddress | undefined {
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
const hashed = getHashedSignaturePayloadEthSignedMessage(
|
|
76
|
-
this.payload,
|
|
77
|
-
SignatureDomainSeparator.checkpointAttestation,
|
|
78
|
-
);
|
|
79
|
-
// Cache the sender for later use
|
|
80
|
-
this.sender = tryRecoverAddress(hashed, this.signature);
|
|
76
|
+
if (this.cachedSender === undefined) {
|
|
77
|
+
this.cachedSender = recoverCoordinationSigner(this.payload, this.signature) ?? null;
|
|
81
78
|
}
|
|
82
|
-
|
|
83
|
-
return this.sender;
|
|
79
|
+
return this.cachedSender ?? undefined;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
82
|
/**
|
|
@@ -88,7 +84,7 @@ export class CheckpointAttestation extends Gossipable {
|
|
|
88
84
|
* @returns The proposer of the checkpoint
|
|
89
85
|
*/
|
|
90
86
|
getProposer(): EthAddress | undefined {
|
|
91
|
-
if (
|
|
87
|
+
if (this.cachedProposer === undefined) {
|
|
92
88
|
// Create a temporary CheckpointProposal to recover the proposer address.
|
|
93
89
|
// We need to use CheckpointProposal because it has a different getPayloadToSign()
|
|
94
90
|
// implementation than ConsensusPayload (uses serializeToBuffer vs ABI encoding).
|
|
@@ -97,16 +93,15 @@ export class CheckpointAttestation extends Gossipable {
|
|
|
97
93
|
this.payload.archive,
|
|
98
94
|
this.payload.feeAssetPriceModifier,
|
|
99
95
|
this.proposerSignature,
|
|
96
|
+
this.payload.signatureContext,
|
|
100
97
|
);
|
|
101
|
-
|
|
102
|
-
this.proposer = proposal.getSender();
|
|
98
|
+
this.cachedProposer = proposal.getSender() ?? null;
|
|
103
99
|
}
|
|
104
|
-
|
|
105
|
-
return this.proposer;
|
|
100
|
+
return this.cachedProposer ?? undefined;
|
|
106
101
|
}
|
|
107
102
|
|
|
108
103
|
getPayload(): Buffer {
|
|
109
|
-
return this.payload.getPayloadToSign(
|
|
104
|
+
return this.payload.getPayloadToSign();
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
toBuffer(): Buffer {
|
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
IndexWithinCheckpoint,
|
|
5
5
|
SlotNumber,
|
|
6
6
|
} from '@aztec/foundation/branded-types';
|
|
7
|
-
import {
|
|
7
|
+
import type { BaseBuffer32 } from '@aztec/foundation/buffer';
|
|
8
8
|
import { keccak256 } from '@aztec/foundation/crypto/keccak';
|
|
9
|
-
import { tryRecoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
10
9
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
11
10
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
12
11
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
13
12
|
import { BufferReader, serializeSignedBigInt, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
14
13
|
|
|
14
|
+
import type { TypedDataDefinition } from 'viem';
|
|
15
|
+
|
|
15
16
|
import type { L2BlockInfo } from '../block/l2_block_info.js';
|
|
16
17
|
import { MAX_TXS_PER_BLOCK } from '../deserialization/index.js';
|
|
17
18
|
import { DutyType, type SigningContext } from '../ha-signing/index.js';
|
|
@@ -22,9 +23,14 @@ import type { Tx } from '../tx/tx.js';
|
|
|
22
23
|
import { BlockProposal } from './block_proposal.js';
|
|
23
24
|
import { Gossipable } from './gossipable.js';
|
|
24
25
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
type CoordinationSignatureContext,
|
|
27
|
+
type CoordinationSignatureType,
|
|
28
|
+
EMPTY_COORDINATION_SIGNATURE_CONTEXT,
|
|
29
|
+
type Signable,
|
|
30
|
+
getCoordinationSignatureTypedData,
|
|
31
|
+
readCoordinationSignatureContext,
|
|
32
|
+
recoverCoordinationSigner,
|
|
33
|
+
serializeCoordinationSignatureContext,
|
|
28
34
|
} from './signature_utils.js';
|
|
29
35
|
import { SignedTxs } from './signed_txs.js';
|
|
30
36
|
import { TopicType } from './topic_type.js';
|
|
@@ -69,10 +75,12 @@ export type CheckpointLastBlock = Omit<CheckpointLastBlockData, 'txs'> & {
|
|
|
69
75
|
* It includes the aggregated checkpoint header that validators will attest to, plus optionally
|
|
70
76
|
* the last block's info for nodes to re-execute. This marks the completion of a slot's worth of blocks.
|
|
71
77
|
*/
|
|
72
|
-
export class CheckpointProposal extends Gossipable {
|
|
78
|
+
export class CheckpointProposal extends Gossipable implements Signable {
|
|
73
79
|
static override p2pTopic = TopicType.checkpoint_proposal;
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
readonly primaryType: CoordinationSignatureType = 'CheckpointProposal';
|
|
82
|
+
|
|
83
|
+
private cachedSender: EthAddress | undefined | null = undefined;
|
|
76
84
|
|
|
77
85
|
constructor(
|
|
78
86
|
/** The aggregated checkpoint header for consensus */
|
|
@@ -87,6 +95,9 @@ export class CheckpointProposal extends Gossipable {
|
|
|
87
95
|
/** The proposer's signature over the checkpoint payload (checkpointHeader + archive + feeAssetPriceModifier) */
|
|
88
96
|
public readonly signature: Signature,
|
|
89
97
|
|
|
98
|
+
/** The signing domain (chainId + rollupAddress) the signature is bound to */
|
|
99
|
+
public readonly signatureContext: CoordinationSignatureContext,
|
|
100
|
+
|
|
90
101
|
/** Optional last block info, including its own signature for BlockProposal extraction */
|
|
91
102
|
public readonly lastBlock?: CheckpointLastBlock,
|
|
92
103
|
) {
|
|
@@ -117,6 +128,7 @@ export class CheckpointProposal extends Gossipable {
|
|
|
117
128
|
this.archive,
|
|
118
129
|
this.lastBlock.txHashes,
|
|
119
130
|
this.lastBlock.signature,
|
|
131
|
+
this.signatureContext,
|
|
120
132
|
this.lastBlock.signedTxs,
|
|
121
133
|
);
|
|
122
134
|
}
|
|
@@ -148,13 +160,8 @@ export class CheckpointProposal extends Gossipable {
|
|
|
148
160
|
* Get the payload to sign for this checkpoint proposal.
|
|
149
161
|
* The signature is over the checkpoint header + archive root + feeAssetPriceModifier (for consensus).
|
|
150
162
|
*/
|
|
151
|
-
getPayloadToSign(
|
|
152
|
-
return serializeToBuffer([
|
|
153
|
-
domainSeparator,
|
|
154
|
-
this.checkpointHeader,
|
|
155
|
-
this.archive,
|
|
156
|
-
serializeSignedBigInt(this.feeAssetPriceModifier),
|
|
157
|
-
]);
|
|
163
|
+
getPayloadToSign(): Buffer {
|
|
164
|
+
return serializeToBuffer([this.checkpointHeader, this.archive, serializeSignedBigInt(this.feeAssetPriceModifier)]);
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
static async createProposalFromSigner(
|
|
@@ -163,7 +170,8 @@ export class CheckpointProposal extends Gossipable {
|
|
|
163
170
|
checkpointNumber: CheckpointNumber,
|
|
164
171
|
feeAssetPriceModifier: bigint,
|
|
165
172
|
lastBlockProposal: BlockProposal | undefined,
|
|
166
|
-
|
|
173
|
+
signatureContext: CoordinationSignatureContext,
|
|
174
|
+
payloadSigner: (typedData: TypedDataDefinition, context: SigningContext) => Promise<Signature>,
|
|
167
175
|
): Promise<CheckpointProposal> {
|
|
168
176
|
// Sign the checkpoint payload with CHECKPOINT_PROPOSAL duty type
|
|
169
177
|
const tempProposal = new CheckpointProposal(
|
|
@@ -171,22 +179,23 @@ export class CheckpointProposal extends Gossipable {
|
|
|
171
179
|
archiveRoot,
|
|
172
180
|
feeAssetPriceModifier,
|
|
173
181
|
Signature.empty(),
|
|
182
|
+
signatureContext,
|
|
174
183
|
);
|
|
175
|
-
const checkpointHash = getHashedSignaturePayload(tempProposal, SignatureDomainSeparator.checkpointProposal);
|
|
176
|
-
|
|
177
184
|
const checkpointContext: SigningContext = {
|
|
178
185
|
slot: checkpointHeader.slotNumber,
|
|
179
186
|
checkpointNumber,
|
|
180
187
|
dutyType: DutyType.CHECKPOINT_PROPOSAL,
|
|
181
188
|
};
|
|
182
189
|
|
|
183
|
-
const
|
|
190
|
+
const typedData = getCoordinationSignatureTypedData(tempProposal);
|
|
191
|
+
const checkpointSignature = await payloadSigner(typedData, checkpointContext);
|
|
184
192
|
|
|
185
193
|
return new CheckpointProposal(
|
|
186
194
|
checkpointHeader,
|
|
187
195
|
archiveRoot,
|
|
188
196
|
feeAssetPriceModifier,
|
|
189
197
|
checkpointSignature,
|
|
198
|
+
signatureContext,
|
|
190
199
|
lastBlockProposal,
|
|
191
200
|
);
|
|
192
201
|
}
|
|
@@ -197,28 +206,26 @@ export class CheckpointProposal extends Gossipable {
|
|
|
197
206
|
* @returns The sender address, or undefined if signature recovery fails or senders don't match
|
|
198
207
|
*/
|
|
199
208
|
getSender(): EthAddress | undefined {
|
|
200
|
-
if (
|
|
201
|
-
const
|
|
202
|
-
const checkpointSender = tryRecoverAddress(hashed, this.signature);
|
|
209
|
+
if (this.cachedSender === undefined) {
|
|
210
|
+
const checkpointSender = recoverCoordinationSigner(this, this.signature);
|
|
203
211
|
|
|
204
|
-
// If there's a lastBlock, verify the block proposal sender matches
|
|
205
212
|
if (checkpointSender && this.lastBlock) {
|
|
206
213
|
const blockProposal = this.getBlockProposal();
|
|
207
214
|
const blockSender = blockProposal?.getSender();
|
|
208
215
|
if (!blockSender || !blockSender.equals(checkpointSender)) {
|
|
209
|
-
|
|
216
|
+
this.cachedSender = null;
|
|
217
|
+
return undefined;
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
this.sender = checkpointSender;
|
|
221
|
+
this.cachedSender = checkpointSender ?? null;
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
return this.
|
|
224
|
+
return this.cachedSender ?? undefined;
|
|
218
225
|
}
|
|
219
226
|
|
|
220
227
|
getPayload() {
|
|
221
|
-
return this.getPayloadToSign(
|
|
228
|
+
return this.getPayloadToSign();
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
toBuffer(): Buffer {
|
|
@@ -227,6 +234,7 @@ export class CheckpointProposal extends Gossipable {
|
|
|
227
234
|
this.archive,
|
|
228
235
|
serializeSignedBigInt(this.feeAssetPriceModifier),
|
|
229
236
|
this.signature,
|
|
237
|
+
serializeCoordinationSignatureContext(this.signatureContext),
|
|
230
238
|
];
|
|
231
239
|
|
|
232
240
|
if (this.lastBlock) {
|
|
@@ -256,6 +264,7 @@ export class CheckpointProposal extends Gossipable {
|
|
|
256
264
|
const archive = reader.readObject(Fr);
|
|
257
265
|
const feeAssetPriceModifier = reader.readInt256();
|
|
258
266
|
const signature = reader.readObject(Signature);
|
|
267
|
+
const signatureContext = readCoordinationSignatureContext(reader);
|
|
259
268
|
|
|
260
269
|
const hasLastBlock = reader.readNumber();
|
|
261
270
|
|
|
@@ -277,7 +286,7 @@ export class CheckpointProposal extends Gossipable {
|
|
|
277
286
|
}
|
|
278
287
|
}
|
|
279
288
|
|
|
280
|
-
return new CheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, signature, {
|
|
289
|
+
return new CheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, signature, signatureContext, {
|
|
281
290
|
blockHeader,
|
|
282
291
|
indexWithinCheckpoint,
|
|
283
292
|
txHashes,
|
|
@@ -286,7 +295,7 @@ export class CheckpointProposal extends Gossipable {
|
|
|
286
295
|
});
|
|
287
296
|
}
|
|
288
297
|
|
|
289
|
-
return new CheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, signature);
|
|
298
|
+
return new CheckpointProposal(checkpointHeader, archive, feeAssetPriceModifier, signature, signatureContext);
|
|
290
299
|
}
|
|
291
300
|
|
|
292
301
|
getSize(): number {
|
|
@@ -295,6 +304,8 @@ export class CheckpointProposal extends Gossipable {
|
|
|
295
304
|
this.archive.size +
|
|
296
305
|
this.signature.getSize() +
|
|
297
306
|
8 /* feeAssetPriceModifier */ +
|
|
307
|
+
4 /* chainId */ +
|
|
308
|
+
20 /* rollupAddress */ +
|
|
298
309
|
4; /* hasLastBlock flag */
|
|
299
310
|
|
|
300
311
|
if (this.lastBlock) {
|
|
@@ -312,16 +323,29 @@ export class CheckpointProposal extends Gossipable {
|
|
|
312
323
|
}
|
|
313
324
|
|
|
314
325
|
static empty(): CheckpointProposal {
|
|
315
|
-
return new CheckpointProposal(
|
|
326
|
+
return new CheckpointProposal(
|
|
327
|
+
CheckpointHeader.empty(),
|
|
328
|
+
Fr.ZERO,
|
|
329
|
+
0n,
|
|
330
|
+
Signature.empty(),
|
|
331
|
+
EMPTY_COORDINATION_SIGNATURE_CONTEXT,
|
|
332
|
+
);
|
|
316
333
|
}
|
|
317
334
|
|
|
318
335
|
static random(): CheckpointProposal {
|
|
319
|
-
return new CheckpointProposal(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
336
|
+
return new CheckpointProposal(
|
|
337
|
+
CheckpointHeader.random(),
|
|
338
|
+
Fr.random(),
|
|
339
|
+
0n,
|
|
340
|
+
Signature.random(),
|
|
341
|
+
EMPTY_COORDINATION_SIGNATURE_CONTEXT,
|
|
342
|
+
{
|
|
343
|
+
blockHeader: BlockHeader.random(),
|
|
344
|
+
indexWithinCheckpoint: IndexWithinCheckpoint(Math.floor(Math.random() * 5)),
|
|
345
|
+
txHashes: [TxHash.random(), TxHash.random()],
|
|
346
|
+
signature: Signature.random(),
|
|
347
|
+
},
|
|
348
|
+
);
|
|
325
349
|
}
|
|
326
350
|
|
|
327
351
|
toInspect() {
|
|
@@ -330,6 +354,8 @@ export class CheckpointProposal extends Gossipable {
|
|
|
330
354
|
archive: this.archive.toString(),
|
|
331
355
|
signature: this.signature.toString(),
|
|
332
356
|
feeAssetPriceModifier: this.feeAssetPriceModifier.toString(),
|
|
357
|
+
chainId: this.signatureContext.chainId,
|
|
358
|
+
rollupAddress: this.signatureContext.rollupAddress.toString(),
|
|
333
359
|
lastBlock: this.lastBlock
|
|
334
360
|
? {
|
|
335
361
|
blockHeader: this.lastBlock.blockHeader.toInspect(),
|
|
@@ -346,7 +372,13 @@ export class CheckpointProposal extends Gossipable {
|
|
|
346
372
|
* Used when the lastBlock has been extracted and stored separately.
|
|
347
373
|
*/
|
|
348
374
|
toCore(): CheckpointProposalCore {
|
|
349
|
-
return new CheckpointProposal(
|
|
375
|
+
return new CheckpointProposal(
|
|
376
|
+
this.checkpointHeader,
|
|
377
|
+
this.archive,
|
|
378
|
+
this.feeAssetPriceModifier,
|
|
379
|
+
this.signature,
|
|
380
|
+
this.signatureContext,
|
|
381
|
+
);
|
|
350
382
|
}
|
|
351
383
|
}
|
|
352
384
|
|