@aztec/stdlib 5.0.0-nightly.20260429 → 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.
Files changed (120) hide show
  1. package/dest/avm/avm.d.ts +300 -300
  2. package/dest/block/attestation_info.d.ts +3 -2
  3. package/dest/block/attestation_info.d.ts.map +1 -1
  4. package/dest/block/attestation_info.js +7 -5
  5. package/dest/block/block_data.d.ts +290 -1
  6. package/dest/block/block_data.d.ts.map +1 -1
  7. package/dest/block/block_data.js +14 -0
  8. package/dest/block/block_parameter.d.ts +30 -3
  9. package/dest/block/block_parameter.d.ts.map +1 -1
  10. package/dest/block/block_parameter.js +36 -2
  11. package/dest/block/l2_block_source.d.ts +39 -4
  12. package/dest/block/l2_block_source.d.ts.map +1 -1
  13. package/dest/block/proposal/attestations_and_signers.d.ts +13 -6
  14. package/dest/block/proposal/attestations_and_signers.d.ts.map +1 -1
  15. package/dest/block/proposal/attestations_and_signers.js +26 -18
  16. package/dest/checkpoint/checkpoint_data.d.ts +7 -1
  17. package/dest/checkpoint/checkpoint_data.d.ts.map +1 -1
  18. package/dest/checkpoint/checkpoint_data.js +2 -0
  19. package/dest/config/chain-config.d.ts +2 -2
  20. package/dest/config/chain-config.d.ts.map +1 -1
  21. package/dest/config/chain-config.js +2 -2
  22. package/dest/config/sequencer-config.d.ts +2 -2
  23. package/dest/config/sequencer-config.d.ts.map +1 -1
  24. package/dest/config/sequencer-config.js +6 -6
  25. package/dest/ha-signing/local_config.d.ts +1 -1
  26. package/dest/ha-signing/local_config.d.ts.map +1 -1
  27. package/dest/ha-signing/local_config.js +2 -2
  28. package/dest/interfaces/archiver.d.ts +1 -1
  29. package/dest/interfaces/archiver.d.ts.map +1 -1
  30. package/dest/interfaces/archiver.js +7 -3
  31. package/dest/interfaces/aztec-node-admin.d.ts +11 -1
  32. package/dest/interfaces/aztec-node-admin.d.ts.map +1 -1
  33. package/dest/interfaces/aztec-node-admin.js +2 -1
  34. package/dest/interfaces/aztec-node.d.ts +45 -55
  35. package/dest/interfaces/aztec-node.d.ts.map +1 -1
  36. package/dest/interfaces/aztec-node.js +18 -16
  37. package/dest/interfaces/block_response.d.ts +156 -0
  38. package/dest/interfaces/block_response.d.ts.map +1 -0
  39. package/dest/interfaces/block_response.js +24 -0
  40. package/dest/interfaces/chain_tips.d.ts +304 -0
  41. package/dest/interfaces/chain_tips.d.ts.map +1 -0
  42. package/dest/interfaces/chain_tips.js +11 -0
  43. package/dest/interfaces/checkpoint_parameter.d.ts +27 -0
  44. package/dest/interfaces/checkpoint_parameter.d.ts.map +1 -0
  45. package/dest/interfaces/checkpoint_parameter.js +20 -0
  46. package/dest/interfaces/checkpoint_response.d.ts +312 -0
  47. package/dest/interfaces/checkpoint_response.d.ts.map +1 -0
  48. package/dest/interfaces/checkpoint_response.js +26 -0
  49. package/dest/interfaces/client.d.ts +6 -1
  50. package/dest/interfaces/client.d.ts.map +1 -1
  51. package/dest/interfaces/client.js +5 -0
  52. package/dest/interfaces/configs.d.ts +7 -2
  53. package/dest/interfaces/configs.d.ts.map +1 -1
  54. package/dest/interfaces/configs.js +2 -1
  55. package/dest/interfaces/l1_publish_info.d.ts +43 -0
  56. package/dest/interfaces/l1_publish_info.d.ts.map +1 -0
  57. package/dest/interfaces/l1_publish_info.js +26 -0
  58. package/dest/interfaces/proving-job.d.ts +166 -166
  59. package/dest/interfaces/server.d.ts +6 -1
  60. package/dest/interfaces/server.d.ts.map +1 -1
  61. package/dest/interfaces/server.js +5 -0
  62. package/dest/interfaces/validator.d.ts +10 -1
  63. package/dest/interfaces/validator.d.ts.map +1 -1
  64. package/dest/interfaces/validator.js +1 -0
  65. package/dest/p2p/block_proposal.d.ts +19 -9
  66. package/dest/p2p/block_proposal.d.ts.map +1 -1
  67. package/dest/p2p/block_proposal.js +42 -32
  68. package/dest/p2p/checkpoint_attestation.d.ts +7 -3
  69. package/dest/p2p/checkpoint_attestation.d.ts.map +1 -1
  70. package/dest/p2p/checkpoint_attestation.js +15 -17
  71. package/dest/p2p/checkpoint_proposal.d.ts +15 -7
  72. package/dest/p2p/checkpoint_proposal.d.ts.map +1 -1
  73. package/dest/p2p/checkpoint_proposal.js +31 -29
  74. package/dest/p2p/consensus_payload.d.ts +18 -7
  75. package/dest/p2p/consensus_payload.d.ts.map +1 -1
  76. package/dest/p2p/consensus_payload.js +31 -19
  77. package/dest/p2p/signature_utils.d.ts +28 -19
  78. package/dest/p2p/signature_utils.d.ts.map +1 -1
  79. package/dest/p2p/signature_utils.js +118 -21
  80. package/dest/p2p/signed_txs.d.ts +15 -13
  81. package/dest/p2p/signed_txs.d.ts.map +1 -1
  82. package/dest/p2p/signed_txs.js +26 -24
  83. package/dest/tests/mocks.d.ts +7 -1
  84. package/dest/tests/mocks.d.ts.map +1 -1
  85. package/dest/tests/mocks.js +28 -14
  86. package/dest/timetable/index.d.ts +1 -1
  87. package/dest/timetable/index.d.ts.map +1 -1
  88. package/dest/timetable/index.js +25 -11
  89. package/dest/tx/profiling.js +4 -4
  90. package/package.json +8 -8
  91. package/src/block/attestation_info.ts +11 -11
  92. package/src/block/block_data.ts +17 -0
  93. package/src/block/block_parameter.ts +35 -2
  94. package/src/block/l2_block_source.ts +43 -3
  95. package/src/block/proposal/attestations_and_signers.ts +32 -17
  96. package/src/checkpoint/checkpoint_data.ts +4 -0
  97. package/src/config/chain-config.ts +2 -3
  98. package/src/config/sequencer-config.ts +10 -6
  99. package/src/ha-signing/local_config.ts +2 -2
  100. package/src/interfaces/archiver.ts +13 -3
  101. package/src/interfaces/aztec-node-admin.ts +3 -1
  102. package/src/interfaces/aztec-node.ts +105 -95
  103. package/src/interfaces/block_response.ts +79 -0
  104. package/src/interfaces/chain_tips.ts +24 -0
  105. package/src/interfaces/checkpoint_parameter.ts +22 -0
  106. package/src/interfaces/checkpoint_response.ts +84 -0
  107. package/src/interfaces/client.ts +5 -0
  108. package/src/interfaces/configs.ts +5 -1
  109. package/src/interfaces/l1_publish_info.ts +40 -0
  110. package/src/interfaces/server.ts +5 -0
  111. package/src/interfaces/validator.ts +5 -0
  112. package/src/p2p/block_proposal.ts +84 -28
  113. package/src/p2p/checkpoint_attestation.ts +15 -20
  114. package/src/p2p/checkpoint_proposal.ts +69 -37
  115. package/src/p2p/consensus_payload.ts +50 -28
  116. package/src/p2p/signature_utils.ts +110 -25
  117. package/src/p2p/signed_txs.ts +46 -28
  118. package/src/tests/mocks.ts +46 -26
  119. package/src/timetable/index.ts +26 -11
  120. 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 { type BaseBuffer32, Buffer32 } from '@aztec/foundation/buffer';
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
- SignatureDomainSeparator,
26
- getHashedSignaturePayload,
27
- getHashedSignaturePayloadEthSignedMessage,
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
- private sender: EthAddress | undefined;
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(domainSeparator: SignatureDomainSeparator): Buffer {
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
- payloadSigner: (payload: Buffer32, context: SigningContext) => Promise<Signature>,
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 hashed = getHashedSignaturePayload(tempProposal, SignatureDomainSeparator.blockProposal);
159
- const sig = await payloadSigner(hashed, blockContext);
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
- const txsSigner = (payload: Buffer32) => payloadSigner(payload, txsSigningContext);
166
- signedTxs = await SignedTxs.createFromSigner(txs, txsSigner);
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(blockHeader, indexWithinCheckpoint, inHash, archiveRoot, txHashes, sig, signedTxs);
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 the signedTxs sender matches the block proposal sender.
175
- * @returns The sender address, or undefined if signature recovery fails or senders don't match
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 (!this.sender) {
179
- const hashed = getHashedSignaturePayloadEthSignedMessage(this, SignatureDomainSeparator.blockProposal);
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
- return undefined; // Sender mismatch - fail
217
+ this.cachedSender = null;
218
+ return undefined;
187
219
  }
188
220
  }
189
221
 
190
- // Cache the sender for later use
191
- this.sender = blockSender;
222
+ this.cachedSender = blockSender ?? null;
192
223
  }
193
224
 
194
- return this.sender;
225
+ return this.cachedSender ?? undefined;
195
226
  }
196
227
 
197
228
  getPayload() {
198
- return this.getPayloadToSign(SignatureDomainSeparator.blockProposal);
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(blockHeader, indexWithinCheckpoint, inHash, archiveRoot, txHashes, signature);
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(BlockHeader.empty(), IndexWithinCheckpoint(0), Fr.ZERO, Fr.ZERO, [], Signature.empty());
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 { SignatureDomainSeparator, getHashedSignaturePayloadEthSignedMessage } from './signature_utils.js';
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 sender: EthAddress | undefined;
31
- private proposer: EthAddress | undefined;
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 (!this.sender) {
74
- // Recover the sender from the attestation
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 (!this.proposer) {
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
- // Cache the proposer for later use
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(SignatureDomainSeparator.checkpointAttestation);
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 { type BaseBuffer32, Buffer32 } from '@aztec/foundation/buffer';
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
- SignatureDomainSeparator,
26
- getHashedSignaturePayload,
27
- getHashedSignaturePayloadEthSignedMessage,
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
- private sender: EthAddress | undefined;
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(domainSeparator: SignatureDomainSeparator): Buffer {
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
- payloadSigner: (payload: Buffer32, context: SigningContext) => Promise<Signature>,
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 checkpointSignature = await payloadSigner(checkpointHash, checkpointContext);
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 (!this.sender) {
201
- const hashed = getHashedSignaturePayloadEthSignedMessage(this, SignatureDomainSeparator.checkpointProposal);
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
- return undefined; // Sender mismatch - fail
216
+ this.cachedSender = null;
217
+ return undefined;
210
218
  }
211
219
  }
212
220
 
213
- // Cache the sender for later use
214
- this.sender = checkpointSender;
221
+ this.cachedSender = checkpointSender ?? null;
215
222
  }
216
223
 
217
- return this.sender;
224
+ return this.cachedSender ?? undefined;
218
225
  }
219
226
 
220
227
  getPayload() {
221
- return this.getPayloadToSign(SignatureDomainSeparator.checkpointProposal);
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(CheckpointHeader.empty(), Fr.ZERO, 0n, Signature.empty());
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(CheckpointHeader.random(), Fr.random(), 0n, Signature.random(), {
320
- blockHeader: BlockHeader.random(),
321
- indexWithinCheckpoint: IndexWithinCheckpoint(Math.floor(Math.random() * 5)),
322
- txHashes: [TxHash.random(), TxHash.random()],
323
- signature: Signature.random(),
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(this.checkpointHeader, this.archive, this.feeAssetPriceModifier, this.signature);
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