@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.
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
@@ -10,10 +10,21 @@ import { z } from 'zod';
10
10
  import type { Checkpoint } from '../checkpoint/checkpoint.js';
11
11
  import { CheckpointHeader } from '../rollup/checkpoint_header.js';
12
12
  import type { CheckpointProposal, CheckpointProposalCore } from './checkpoint_proposal.js';
13
- import type { Signable, SignatureDomainSeparator } from './signature_utils.js';
13
+ import {
14
+ type CoordinationSignatureContext,
15
+ type CoordinationSignatureType,
16
+ EMPTY_COORDINATION_SIGNATURE_CONTEXT,
17
+ type Signable,
18
+ coordinationSignatureContextEquals,
19
+ coordinationSignatureContextSchema,
20
+ readCoordinationSignatureContext,
21
+ serializeCoordinationSignatureContext,
22
+ } from './signature_utils.js';
14
23
 
15
24
  /** Checkpoint consensus payload as signed by validators and verified on L1. */
16
25
  export class ConsensusPayload implements Signable {
26
+ readonly primaryType: CoordinationSignatureType = 'CheckpointAttestation';
27
+
17
28
  private size: number | undefined;
18
29
 
19
30
  constructor(
@@ -22,7 +33,9 @@ export class ConsensusPayload implements Signable {
22
33
  /** The archive root after the block is added */
23
34
  public readonly archive: Fr,
24
35
  /** The fee asset price modifier in basis points (from oracle) */
25
- public readonly feeAssetPriceModifier: bigint = 0n,
36
+ public readonly feeAssetPriceModifier: bigint,
37
+ /** The signing domain (chainId + rollupAddress) the signature is bound to */
38
+ public readonly signatureContext: CoordinationSignatureContext,
26
39
  ) {}
27
40
 
28
41
  static get schema() {
@@ -31,36 +44,38 @@ export class ConsensusPayload implements Signable {
31
44
  header: CheckpointHeader.schema,
32
45
  archive: schemas.Fr,
33
46
  feeAssetPriceModifier: schemas.BigInt,
47
+ signatureContext: coordinationSignatureContextSchema,
34
48
  })
35
- .transform(obj => new ConsensusPayload(obj.header, obj.archive, obj.feeAssetPriceModifier));
49
+ .transform(obj => new ConsensusPayload(obj.header, obj.archive, obj.feeAssetPriceModifier, obj.signatureContext));
36
50
  }
37
51
 
38
- static getFields(fields: FieldsOf<ConsensusPayload>) {
39
- return [fields.header, fields.archive, fields.feeAssetPriceModifier] as const;
52
+ static getFields(fields: Omit<FieldsOf<ConsensusPayload>, 'primaryType'>) {
53
+ return [fields.header, fields.archive, fields.feeAssetPriceModifier, fields.signatureContext] as const;
40
54
  }
41
55
 
42
- getPayloadToSign(domainSeparator: SignatureDomainSeparator): Buffer {
56
+ getPayloadToSign(): Buffer {
57
+ // Matches the L1 ProposePayload struct in ProposeLib.sol.
43
58
  const abi = parseAbiParameters(
44
- 'uint8, ' + //domainSeperator
45
- '(' +
59
+ '(' +
46
60
  'bytes32, ' + // archive
47
61
  '(int256), ' + // oracleInput
48
62
  'bytes32' + // headerHash
49
63
  ')',
50
64
  );
51
65
  const archiveRoot = this.archive.toString();
52
-
53
66
  const headerHash = this.header.hash().toString();
54
- const encodedData = encodeAbiParameters(abi, [
55
- domainSeparator,
56
- [archiveRoot, [this.feeAssetPriceModifier], headerHash],
57
- ] as const);
67
+ const encodedData = encodeAbiParameters(abi, [[archiveRoot, [this.feeAssetPriceModifier], headerHash]] as const);
58
68
 
59
69
  return hexToBuffer(encodedData);
60
70
  }
61
71
 
62
72
  toBuffer(): Buffer {
63
- return serializeToBuffer([this.header, this.archive, serializeSignedBigInt(this.feeAssetPriceModifier)]);
73
+ return serializeToBuffer([
74
+ this.header,
75
+ this.archive,
76
+ serializeSignedBigInt(this.feeAssetPriceModifier),
77
+ serializeCoordinationSignatureContext(this.signatureContext),
78
+ ]);
64
79
  }
65
80
 
66
81
  public equals(other: ConsensusPayload | CheckpointProposal | CheckpointProposalCore): boolean {
@@ -69,34 +84,39 @@ export class ConsensusPayload implements Signable {
69
84
  return (
70
85
  this.header.equals(otherHeader) &&
71
86
  this.archive.equals(other.archive) &&
72
- this.feeAssetPriceModifier === otherModifier
87
+ this.feeAssetPriceModifier === otherModifier &&
88
+ coordinationSignatureContextEquals(this.signatureContext, other.signatureContext)
73
89
  );
74
90
  }
75
91
 
76
92
  static fromBuffer(buf: Buffer | BufferReader): ConsensusPayload {
77
93
  const reader = BufferReader.asReader(buf);
78
- const payload = new ConsensusPayload(
79
- reader.readObject(CheckpointHeader),
80
- reader.readObject(Fr),
81
- reader.readInt256(),
82
- );
83
- return payload;
94
+ const header = reader.readObject(CheckpointHeader);
95
+ const archive = reader.readObject(Fr);
96
+ const feeAssetPriceModifier = reader.readInt256();
97
+ const signatureContext = readCoordinationSignatureContext(reader);
98
+ return new ConsensusPayload(header, archive, feeAssetPriceModifier, signatureContext);
84
99
  }
85
100
 
86
- static fromFields(fields: FieldsOf<ConsensusPayload>): ConsensusPayload {
87
- return new ConsensusPayload(fields.header, fields.archive, fields.feeAssetPriceModifier);
101
+ static fromFields(fields: Omit<FieldsOf<ConsensusPayload>, 'primaryType'>): ConsensusPayload {
102
+ return new ConsensusPayload(fields.header, fields.archive, fields.feeAssetPriceModifier, fields.signatureContext);
88
103
  }
89
104
 
90
- static fromCheckpoint(checkpoint: Checkpoint): ConsensusPayload {
91
- return new ConsensusPayload(checkpoint.header, checkpoint.archive.root, checkpoint.feeAssetPriceModifier);
105
+ static fromCheckpoint(checkpoint: Checkpoint, signatureContext: CoordinationSignatureContext): ConsensusPayload {
106
+ return new ConsensusPayload(
107
+ checkpoint.header,
108
+ checkpoint.archive.root,
109
+ checkpoint.feeAssetPriceModifier,
110
+ signatureContext,
111
+ );
92
112
  }
93
113
 
94
114
  static empty(): ConsensusPayload {
95
- return new ConsensusPayload(CheckpointHeader.empty(), Fr.ZERO, 0n);
115
+ return new ConsensusPayload(CheckpointHeader.empty(), Fr.ZERO, 0n, EMPTY_COORDINATION_SIGNATURE_CONTEXT);
96
116
  }
97
117
 
98
118
  static random(): ConsensusPayload {
99
- return new ConsensusPayload(CheckpointHeader.random(), Fr.random(), 0n);
119
+ return new ConsensusPayload(CheckpointHeader.random(), Fr.random(), 0n, EMPTY_COORDINATION_SIGNATURE_CONTEXT);
100
120
  }
101
121
 
102
122
  /**
@@ -117,10 +137,12 @@ export class ConsensusPayload implements Signable {
117
137
  header: this.header.toInspect(),
118
138
  archive: this.archive.toString(),
119
139
  feeAssetPriceModifier: this.feeAssetPriceModifier.toString(),
140
+ chainId: this.signatureContext.chainId,
141
+ rollupAddress: this.signatureContext.rollupAddress.toString(),
120
142
  };
121
143
  }
122
144
 
123
145
  toString() {
124
- return `header: ${this.header.toString()}, archive: ${this.archive.toString()}, feeAssetPriceModifier: ${this.feeAssetPriceModifier}}`;
146
+ return `header: ${this.header.toString()}, archive: ${this.archive.toString()}, feeAssetPriceModifier: ${this.feeAssetPriceModifier}, chainId: ${this.signatureContext.chainId}, rollupAddress: ${this.signatureContext.rollupAddress.toString()}`;
125
147
  }
126
148
  }
@@ -1,37 +1,122 @@
1
1
  import { Buffer32 } from '@aztec/foundation/buffer';
2
2
  import { keccak256 } from '@aztec/foundation/crypto/keccak';
3
- import { makeEthSignDigest } from '@aztec/foundation/crypto/secp256k1-signer';
3
+ import { tryRecoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
4
+ import { EthAddress } from '@aztec/foundation/eth-address';
5
+ import type { Signature } from '@aztec/foundation/eth-signature';
6
+ import { type BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
4
7
 
5
- export enum SignatureDomainSeparator {
6
- blockProposal = 0,
7
- checkpointAttestation = 1,
8
- attestationsAndSigners = 2,
9
- checkpointProposal = 3,
10
- signedTxs = 4,
11
- }
8
+ import { type TypedDataDefinition, hashTypedData } from 'viem';
9
+ import { z } from 'zod';
10
+
11
+ import type { ZodFor } from '../schemas/index.js';
12
+
13
+ export type CoordinationSignatureType =
14
+ | 'BlockProposal'
15
+ | 'CheckpointProposal'
16
+ | 'CheckpointAttestation'
17
+ | 'AttestationsAndSigners'
18
+ | 'SignedTxs';
19
+
20
+ export type CoordinationSignatureContext = {
21
+ chainId: number;
22
+ rollupAddress: EthAddress;
23
+ };
24
+
25
+ export const EMPTY_COORDINATION_SIGNATURE_CONTEXT: CoordinationSignatureContext = {
26
+ chainId: 0,
27
+ rollupAddress: EthAddress.ZERO,
28
+ };
29
+
30
+ export const coordinationSignatureContextSchema: ZodFor<CoordinationSignatureContext> = z.object({
31
+ chainId: z.number(),
32
+ rollupAddress: EthAddress.schema,
33
+ });
12
34
 
13
35
  export interface Signable {
14
- getPayloadToSign(domainSeparator: SignatureDomainSeparator): Buffer;
36
+ readonly primaryType: CoordinationSignatureType;
37
+ readonly signatureContext: CoordinationSignatureContext;
38
+ getPayloadToSign(): Buffer;
15
39
  }
16
40
 
17
- /**
18
- * Get the hashed payload for the signature of the `Signable`
19
- * @param s - The `Signable` to sign
20
- * @returns The hashed payload for the signature of the `Signable`
21
- */
22
- export function getHashedSignaturePayload(s: Signable, domainSeparator: SignatureDomainSeparator): Buffer32 {
23
- return Buffer32.fromBuffer(keccak256(s.getPayloadToSign(domainSeparator)));
41
+ export function coordinationSignatureContextEquals(
42
+ a: CoordinationSignatureContext,
43
+ b: CoordinationSignatureContext,
44
+ ): boolean {
45
+ return a.chainId === b.chainId && a.rollupAddress.equals(b.rollupAddress);
46
+ }
47
+
48
+ export function serializeCoordinationSignatureContext(ctx: CoordinationSignatureContext): Buffer {
49
+ return serializeToBuffer([ctx.chainId, ctx.rollupAddress]);
50
+ }
51
+
52
+ export function readCoordinationSignatureContext(reader: BufferReader): CoordinationSignatureContext {
53
+ const chainId = reader.readNumber();
54
+ const rollupAddress = reader.readObject(EthAddress);
55
+ return { chainId, rollupAddress };
24
56
  }
25
57
 
26
58
  /**
27
- * Get the hashed payload for the signature of the `Signable` as an Ethereum signed message EIP-712
28
- * @param s - the `Signable` to sign
29
- * @returns The hashed payload for the signature of the `Signable` as an Ethereum signed message
59
+ * Returns true if the signable carries a context matching the node's expected context.
60
+ * Use this at the P2P ingress boundary to reject foreign-chain messages cheaply before
61
+ * performing any signature recovery.
30
62
  */
31
- export function getHashedSignaturePayloadEthSignedMessage(
32
- s: Signable,
33
- domainSeparator: SignatureDomainSeparator,
34
- ): Buffer32 {
35
- const payload = getHashedSignaturePayload(s, domainSeparator);
36
- return makeEthSignDigest(payload);
63
+ export function hasValidSignatureContext(signable: Signable, expected: CoordinationSignatureContext): boolean {
64
+ return coordinationSignatureContextEquals(signable.signatureContext, expected);
65
+ }
66
+
67
+ const COORDINATION_SIGNATURE_NAME = 'Aztec Rollup';
68
+ const COORDINATION_SIGNATURE_VERSION = '1';
69
+
70
+ const EIP712_DOMAIN_FIELDS = [
71
+ { name: 'name', type: 'string' },
72
+ { name: 'version', type: 'string' },
73
+ { name: 'chainId', type: 'uint256' },
74
+ { name: 'verifyingContract', type: 'address' },
75
+ ] as const;
76
+
77
+ const COORDINATION_SIGNATURE_TYPES = {
78
+ EIP712Domain: EIP712_DOMAIN_FIELDS,
79
+ BlockProposal: [{ name: 'payloadHash', type: 'bytes32' }],
80
+ CheckpointProposal: [{ name: 'payloadHash', type: 'bytes32' }],
81
+ CheckpointAttestation: [{ name: 'payloadHash', type: 'bytes32' }],
82
+ AttestationsAndSigners: [{ name: 'payloadHash', type: 'bytes32' }],
83
+ SignedTxs: [{ name: 'payloadHash', type: 'bytes32' }],
84
+ } as const;
85
+
86
+ export function getCoordinationSignatureTypedDataForPayloadHash(
87
+ payloadHash: Buffer32,
88
+ type: CoordinationSignatureType,
89
+ context: CoordinationSignatureContext,
90
+ ): TypedDataDefinition {
91
+ return {
92
+ domain: {
93
+ name: COORDINATION_SIGNATURE_NAME,
94
+ version: COORDINATION_SIGNATURE_VERSION,
95
+ chainId: context.chainId,
96
+ verifyingContract: context.rollupAddress.toString() as `0x${string}`,
97
+ },
98
+ types: COORDINATION_SIGNATURE_TYPES,
99
+ primaryType: type,
100
+ message: {
101
+ payloadHash: payloadHash.toString() as `0x${string}`,
102
+ },
103
+ };
104
+ }
105
+
106
+ export function getCoordinationSignatureTypedData(signable: Signable): TypedDataDefinition {
107
+ const payloadHash = getHashedSignaturePayload(signable);
108
+ return getCoordinationSignatureTypedDataForPayloadHash(payloadHash, signable.primaryType, signable.signatureContext);
109
+ }
110
+
111
+ export function getHashedSignaturePayloadTypedData(signable: Signable): Buffer32 {
112
+ return Buffer32.fromString(hashTypedData(getCoordinationSignatureTypedData(signable)));
113
+ }
114
+
115
+ export function recoverCoordinationSigner(signable: Signable, signature: Signature): EthAddress | undefined {
116
+ const digest = getHashedSignaturePayloadTypedData(signable);
117
+ return tryRecoverAddress(digest, signature, { allowYParityAsV: true });
118
+ }
119
+
120
+ export function getHashedSignaturePayload(s: Signable): Buffer32 {
121
+ return Buffer32.fromBuffer(keccak256(s.getPayloadToSign()));
37
122
  }
@@ -1,15 +1,20 @@
1
- import { Buffer32 } from '@aztec/foundation/buffer';
2
- import { tryRecoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
3
1
  import type { EthAddress } from '@aztec/foundation/eth-address';
4
2
  import { Signature } from '@aztec/foundation/eth-signature';
5
3
  import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
6
4
 
5
+ import type { TypedDataDefinition } from 'viem';
6
+
7
7
  import { MAX_TXS_PER_BLOCK } from '../deserialization/index.js';
8
8
  import { Tx } from '../tx/tx.js';
9
9
  import {
10
- SignatureDomainSeparator,
11
- getHashedSignaturePayload,
12
- getHashedSignaturePayloadEthSignedMessage,
10
+ type CoordinationSignatureContext,
11
+ type CoordinationSignatureType,
12
+ EMPTY_COORDINATION_SIGNATURE_CONTEXT,
13
+ type Signable,
14
+ getCoordinationSignatureTypedData,
15
+ readCoordinationSignatureContext,
16
+ recoverCoordinationSigner,
17
+ serializeCoordinationSignatureContext,
13
18
  } from './signature_utils.js';
14
19
 
15
20
  /**
@@ -17,50 +22,56 @@ import {
17
22
  * The signature is over the transaction objects themselves, providing
18
23
  * data availability guarantees beyond just the transaction hashes.
19
24
  */
20
- export class SignedTxs {
21
- private sender: EthAddress | undefined;
25
+ export class SignedTxs implements Signable {
26
+ readonly primaryType: CoordinationSignatureType = 'SignedTxs';
27
+
28
+ private cachedSender: EthAddress | undefined | null = undefined;
22
29
 
23
30
  constructor(
24
31
  /** The transactions */
25
32
  public readonly txs: Tx[],
26
33
  /** The proposer's signature over the transactions */
27
34
  public readonly signature: Signature,
35
+ /** The signing domain (chainId + rollupAddress) the signature is bound to */
36
+ public readonly signatureContext: CoordinationSignatureContext,
28
37
  ) {}
29
38
 
30
- /**
31
- * Get the payload to sign for this signed txs.
32
- */
33
- getPayloadToSign(domainSeparator: SignatureDomainSeparator): Buffer {
34
- return serializeToBuffer([domainSeparator, this.txs.length, this.txs]);
39
+ getPayloadToSign(): Buffer {
40
+ return serializeToBuffer([this.txs.length, this.txs]);
35
41
  }
36
42
 
37
43
  /**
38
- * Lazily evaluate the sender of the signed txs; result is cached
44
+ * Lazily evaluate the sender of the signed txs; result is cached.
39
45
  * @returns The sender address, or undefined if signature recovery fails
40
46
  */
41
47
  getSender(): EthAddress | undefined {
42
- if (!this.sender) {
43
- const hashed = getHashedSignaturePayloadEthSignedMessage(this, SignatureDomainSeparator.signedTxs);
44
- this.sender = tryRecoverAddress(hashed, this.signature);
48
+ if (this.cachedSender === undefined) {
49
+ this.cachedSender = recoverCoordinationSigner(this, this.signature) ?? null;
45
50
  }
46
- return this.sender;
51
+ return this.cachedSender ?? undefined;
47
52
  }
48
53
 
49
54
  /**
50
- * Create SignedTxs from a signer function
55
+ * Create SignedTxs from a typed-data signer function
51
56
  */
52
57
  static async createFromSigner(
53
58
  txs: Tx[],
54
- payloadSigner: (payload: Buffer32) => Promise<Signature>,
59
+ signatureContext: CoordinationSignatureContext,
60
+ typedDataSigner: (typedData: TypedDataDefinition) => Promise<Signature>,
55
61
  ): Promise<SignedTxs> {
56
- const tempSignedTxs = new SignedTxs(txs, Signature.empty());
57
- const hashed = getHashedSignaturePayload(tempSignedTxs, SignatureDomainSeparator.signedTxs);
58
- const signature = await payloadSigner(hashed);
59
- return new SignedTxs(txs, signature);
62
+ const tempSignedTxs = new SignedTxs(txs, Signature.empty(), signatureContext);
63
+ const typedData = getCoordinationSignatureTypedData(tempSignedTxs);
64
+ const signature = await typedDataSigner(typedData);
65
+ return new SignedTxs(txs, signature, signatureContext);
60
66
  }
61
67
 
62
68
  toBuffer(): Buffer {
63
- return serializeToBuffer([this.txs.length, this.txs, this.signature]);
69
+ return serializeToBuffer([
70
+ this.txs.length,
71
+ this.txs,
72
+ this.signature,
73
+ serializeCoordinationSignatureContext(this.signatureContext),
74
+ ]);
64
75
  }
65
76
 
66
77
  static fromBuffer(buf: Buffer | BufferReader): SignedTxs {
@@ -71,18 +82,25 @@ export class SignedTxs {
71
82
  }
72
83
  const txs = reader.readArray(txCount, Tx);
73
84
  const signature = reader.readObject(Signature);
74
- return new SignedTxs(txs, signature);
85
+ const signatureContext = readCoordinationSignatureContext(reader);
86
+ return new SignedTxs(txs, signature, signatureContext);
75
87
  }
76
88
 
77
89
  getSize(): number {
78
- return 4 /* txs.length */ + this.txs.reduce((acc, tx) => acc + tx.getSize(), 0) + this.signature.getSize();
90
+ return (
91
+ 4 /* txs.length */ +
92
+ this.txs.reduce((acc, tx) => acc + tx.getSize(), 0) +
93
+ this.signature.getSize() +
94
+ 4 /* chainId */ +
95
+ 20 /* rollupAddress */
96
+ );
79
97
  }
80
98
 
81
99
  static empty(): SignedTxs {
82
- return new SignedTxs([], Signature.empty());
100
+ return new SignedTxs([], Signature.empty(), EMPTY_COORDINATION_SIGNATURE_CONTEXT);
83
101
  }
84
102
 
85
103
  static random(): SignedTxs {
86
- return new SignedTxs([Tx.random(), Tx.random()], Signature.random());
104
+ return new SignedTxs([Tx.random(), Tx.random()], Signature.random(), EMPTY_COORDINATION_SIGNATURE_CONTEXT);
87
105
  }
88
106
  }
@@ -14,8 +14,11 @@ import { padArrayEnd, times } from '@aztec/foundation/collection';
14
14
  import { randomBytes } from '@aztec/foundation/crypto/random';
15
15
  import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer';
16
16
  import { Fr } from '@aztec/foundation/curves/bn254';
17
+ import { EthAddress } from '@aztec/foundation/eth-address';
17
18
  import { Signature } from '@aztec/foundation/eth-signature';
18
19
 
20
+ import { type TypedDataDefinition, hashTypedData } from 'viem';
21
+
19
22
  import type { ContractArtifact } from '../abi/abi.js';
20
23
  import { PublicTxEffect } from '../avm/avm.js';
21
24
  import type { AvmAccumulatedData } from '../avm/avm_accumulated_data.js';
@@ -51,7 +54,7 @@ import { BlockProposal } from '../p2p/block_proposal.js';
51
54
  import { CheckpointAttestation } from '../p2p/checkpoint_attestation.js';
52
55
  import { CheckpointProposal } from '../p2p/checkpoint_proposal.js';
53
56
  import { ConsensusPayload } from '../p2p/consensus_payload.js';
54
- import { SignatureDomainSeparator, getHashedSignaturePayloadEthSignedMessage } from '../p2p/signature_utils.js';
57
+ import { type CoordinationSignatureContext, getHashedSignaturePayloadTypedData } from '../p2p/signature_utils.js';
55
58
  import { ChonkProof } from '../proofs/chonk_proof.js';
56
59
  import { ProvingRequestType } from '../proofs/proving_request_type.js';
57
60
  import { CheckpointHeader } from '../rollup/checkpoint_header.js';
@@ -86,6 +89,15 @@ import {
86
89
  makePublicDataWrite,
87
90
  } from './factories.js';
88
91
 
92
+ export const TEST_COORDINATION_SIGNATURE_CONTEXT: CoordinationSignatureContext = {
93
+ chainId: 31337,
94
+ rollupAddress: EthAddress.fromNumber(1),
95
+ };
96
+
97
+ function signTypedData(signer: Secp256k1Signer, typedData: TypedDataDefinition): Signature {
98
+ return signer.sign(Buffer32.fromString(hashTypedData(typedData)));
99
+ }
100
+
89
101
  export const randomTxHash = (): TxHash => TxHash.random();
90
102
 
91
103
  export const mockTx = async (
@@ -519,6 +531,7 @@ export interface MakeConsensusPayloadOptions {
519
531
  txHashes?: TxHash[];
520
532
  txs?: Tx[];
521
533
  feeAssetPriceModifier?: bigint;
534
+ signatureContext?: CoordinationSignatureContext;
522
535
  }
523
536
 
524
537
  export interface MakeBlockProposalOptions {
@@ -529,6 +542,7 @@ export interface MakeBlockProposalOptions {
529
542
  archiveRoot?: Fr;
530
543
  txHashes?: TxHash[];
531
544
  txs?: Tx[];
545
+ signatureContext?: CoordinationSignatureContext;
532
546
  }
533
547
 
534
548
  export interface MakeCheckpointProposalOptions {
@@ -536,6 +550,7 @@ export interface MakeCheckpointProposalOptions {
536
550
  checkpointHeader?: CheckpointHeader;
537
551
  archiveRoot?: Fr;
538
552
  feeAssetPriceModifier?: bigint;
553
+ signatureContext?: CoordinationSignatureContext;
539
554
  /** Options for the lastBlock - if undefined, no lastBlock is included */
540
555
  lastBlock?: {
541
556
  blockHeader?: BlockHeader;
@@ -546,10 +561,7 @@ export interface MakeCheckpointProposalOptions {
546
561
  }
547
562
 
548
563
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
549
- const makeAndSignConsensusPayload = (
550
- domainSeparator: SignatureDomainSeparator,
551
- options?: MakeConsensusPayloadOptions,
552
- ) => {
564
+ const makeAndSignConsensusPayload = (options?: MakeConsensusPayloadOptions) => {
553
565
  const header = options?.header ?? makeCheckpointHeader(1);
554
566
  const { signer = Secp256k1Signer.random(), archive = Fr.random(), feeAssetPriceModifier = 0n } = options ?? {};
555
567
 
@@ -557,9 +569,10 @@ const makeAndSignConsensusPayload = (
557
569
  header,
558
570
  archive,
559
571
  feeAssetPriceModifier,
572
+ signatureContext: TEST_COORDINATION_SIGNATURE_CONTEXT,
560
573
  });
561
574
 
562
- const hash = getHashedSignaturePayloadEthSignedMessage(payload, domainSeparator);
575
+ const hash = getHashedSignaturePayloadTypedData(payload);
563
576
  const signature = signer.sign(hash);
564
577
 
565
578
  return { blockNumber: header.slotNumber, payload, signature };
@@ -569,10 +582,7 @@ export const makeAndSignCommitteeAttestationsAndSigners = (
569
582
  attestationsAndSigners: CommitteeAttestationsAndSigners,
570
583
  signer: Secp256k1Signer = Secp256k1Signer.random(),
571
584
  ) => {
572
- const hash = getHashedSignaturePayloadEthSignedMessage(
573
- attestationsAndSigners,
574
- SignatureDomainSeparator.attestationsAndSigners,
575
- );
585
+ const hash = getHashedSignaturePayloadTypedData(attestationsAndSigners);
576
586
  return signer.sign(hash);
577
587
  };
578
588
 
@@ -584,6 +594,7 @@ export const makeBlockProposal = (options?: MakeBlockProposalOptions): Promise<B
584
594
  const txHashes = options?.txHashes ?? [0, 1, 2, 3, 4, 5].map(() => TxHash.random());
585
595
  const txs = options?.txs;
586
596
  const signer = options?.signer ?? Secp256k1Signer.random();
597
+ const signatureContext = options?.signatureContext ?? TEST_COORDINATION_SIGNATURE_CONTEXT;
587
598
 
588
599
  return BlockProposal.createProposalFromSigner(
589
600
  blockHeader,
@@ -593,7 +604,9 @@ export const makeBlockProposal = (options?: MakeBlockProposalOptions): Promise<B
593
604
  archiveRoot,
594
605
  txHashes,
595
606
  txs,
596
- (_payload, _context) => Promise.resolve(signer.signMessage(_payload)),
607
+ signatureContext,
608
+ (typedData, _context) => Promise.resolve(signTypedData(signer, typedData)),
609
+ (typedData, _context) => Promise.resolve(signTypedData(signer, typedData)),
597
610
  );
598
611
  };
599
612
 
@@ -602,6 +615,7 @@ export const makeCheckpointProposal = async (options?: MakeCheckpointProposalOpt
602
615
  const archiveRoot = options?.archiveRoot ?? Fr.random();
603
616
  const feeAssetPriceModifier = options?.feeAssetPriceModifier ?? 0n;
604
617
  const signer = options?.signer ?? Secp256k1Signer.random();
618
+ const signatureContext = options?.signatureContext ?? TEST_COORDINATION_SIGNATURE_CONTEXT;
605
619
 
606
620
  // Build a signed block proposal if lastBlock options are provided
607
621
  const lastBlockProposal = options?.lastBlock
@@ -613,6 +627,7 @@ export const makeCheckpointProposal = async (options?: MakeCheckpointProposalOpt
613
627
  txHashes: options.lastBlock.txHashes,
614
628
  txs: options.lastBlock.txs,
615
629
  signer,
630
+ signatureContext,
616
631
  })
617
632
  : undefined;
618
633
 
@@ -622,7 +637,8 @@ export const makeCheckpointProposal = async (options?: MakeCheckpointProposalOpt
622
637
  CheckpointNumber(1),
623
638
  feeAssetPriceModifier,
624
639
  lastBlockProposal,
625
- payload => Promise.resolve(signer.signMessage(payload)),
640
+ signatureContext,
641
+ typedData => Promise.resolve(signTypedData(signer, typedData)),
626
642
  );
627
643
  };
628
644
 
@@ -636,6 +652,7 @@ export type MakeCheckpointAttestationOptions = {
636
652
  attesterSigner?: Secp256k1Signer;
637
653
  proposerSigner?: Secp256k1Signer;
638
654
  signer?: Secp256k1Signer;
655
+ signatureContext?: CoordinationSignatureContext;
639
656
  };
640
657
 
641
658
  /**
@@ -645,26 +662,27 @@ export const makeCheckpointAttestation = (options: MakeCheckpointAttestationOpti
645
662
  const header = options.header ?? makeCheckpointHeader(1);
646
663
  const archive = options.archive ?? Fr.random();
647
664
  const feeAssetPriceModifier = options.feeAssetPriceModifier ?? 0n;
665
+ const signatureContext = options.signatureContext ?? TEST_COORDINATION_SIGNATURE_CONTEXT;
648
666
  const { signer, attesterSigner = signer, proposerSigner = signer } = options;
649
667
 
650
- const payload = new ConsensusPayload(header, archive, feeAssetPriceModifier);
668
+ const payload = new ConsensusPayload(header, archive, feeAssetPriceModifier, signatureContext);
651
669
 
652
670
  // Sign as attester
653
- const attestationHash = getHashedSignaturePayloadEthSignedMessage(
654
- payload,
655
- SignatureDomainSeparator.checkpointAttestation,
656
- );
671
+ const attestationHash = getHashedSignaturePayloadTypedData(payload);
657
672
  const attestationSigner = attesterSigner ?? Secp256k1Signer.random();
658
673
  const attestationSignature = attestationSigner.sign(attestationHash);
659
674
 
660
675
  // Sign as proposer - use CheckpointProposal's payload format (serializeToBuffer)
661
676
  // This is different from ConsensusPayload's format (ABI encoding)
662
677
  const proposalSignerToUse = proposerSigner ?? Secp256k1Signer.random();
663
- const tempProposal = new CheckpointProposal(header, archive, feeAssetPriceModifier, Signature.empty());
664
- const proposalHash = getHashedSignaturePayloadEthSignedMessage(
665
- tempProposal,
666
- SignatureDomainSeparator.checkpointProposal,
678
+ const tempProposal = new CheckpointProposal(
679
+ header,
680
+ archive,
681
+ feeAssetPriceModifier,
682
+ Signature.empty(),
683
+ signatureContext,
667
684
  );
685
+ const proposalHash = getHashedSignaturePayloadTypedData(tempProposal);
668
686
  const proposerSignature = proposalSignerToUse.sign(proposalHash);
669
687
 
670
688
  return new CheckpointAttestation(payload, attestationSignature, proposerSignature);
@@ -677,13 +695,15 @@ export const makeCheckpointAttestationFromProposal = (
677
695
  proposal: CheckpointProposal,
678
696
  attesterSigner?: Secp256k1Signer,
679
697
  ): CheckpointAttestation => {
680
- const payload = new ConsensusPayload(proposal.checkpointHeader, proposal.archive, proposal.feeAssetPriceModifier);
698
+ const payload = new ConsensusPayload(
699
+ proposal.checkpointHeader,
700
+ proposal.archive,
701
+ proposal.feeAssetPriceModifier,
702
+ proposal.signatureContext,
703
+ );
681
704
 
682
705
  // Sign as attester
683
- const attestationHash = getHashedSignaturePayloadEthSignedMessage(
684
- payload,
685
- SignatureDomainSeparator.checkpointAttestation,
686
- );
706
+ const attestationHash = getHashedSignaturePayloadTypedData(payload);
687
707
  const attestationSigner = attesterSigner ?? Secp256k1Signer.random();
688
708
  const attestationSignature = attestationSigner.sign(attestationHash);
689
709