@aztec/stdlib 5.0.0-nightly.20260704 → 5.0.0-nightly.20260706
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/block/proposal/attestations_and_signers.d.ts +1 -1
- package/dest/block/proposal/attestations_and_signers.d.ts.map +1 -1
- package/dest/block/proposal/attestations_and_signers.js +10 -6
- package/dest/p2p/attestation_utils.d.ts +1 -1
- package/dest/p2p/attestation_utils.d.ts.map +1 -1
- package/dest/p2p/attestation_utils.js +8 -1
- package/dest/p2p/checkpoint_attestation.d.ts +9 -1
- package/dest/p2p/checkpoint_attestation.d.ts.map +1 -1
- package/dest/p2p/checkpoint_attestation.js +10 -0
- package/package.json +8 -8
- package/src/block/proposal/attestations_and_signers.ts +10 -6
- package/src/p2p/attestation_utils.ts +8 -1
- package/src/p2p/checkpoint_attestation.ts +12 -0
|
@@ -45,4 +45,4 @@ export declare class MaliciousCommitteeAttestationsAndSigners extends CommitteeA
|
|
|
45
45
|
constructor(attestations: CommitteeAttestation[], signers: EthAddress[], signatureContext: CoordinationSignatureContext);
|
|
46
46
|
getSigners(): EthAddress[];
|
|
47
47
|
}
|
|
48
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
48
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0ZXN0YXRpb25zX2FuZF9zaWduZXJzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmxvY2svcHJvcG9zYWwvYXR0ZXN0YXRpb25zX2FuZF9zaWduZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFJM0UsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUV4QixPQUFPLEVBQ0wsS0FBSyw0QkFBNEIsRUFDakMsS0FBSyx5QkFBeUIsRUFDOUIsS0FBSyxRQUFRLEVBRWQsTUFBTSw4QkFBOEIsQ0FBQztBQUN0QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsVUFBVSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFOUUscUJBQWEsK0JBQWdDLFlBQVcsUUFBUTtJQUlyRCxZQUFZLEVBQUUsb0JBQW9CLEVBQUU7YUFDM0IsZ0JBQWdCLEVBQUUsNEJBQTRCO0lBSmhFLFFBQVEsQ0FBQyxXQUFXLEVBQUUseUJBQXlCLENBQTRCO0lBRTNFLFlBQ1MsWUFBWSxFQUFFLG9CQUFvQixFQUFFLEVBQzNCLGdCQUFnQixFQUFFLDRCQUE0QixFQUM1RDtJQUVKLE1BQU0sS0FBSyxNQUFNOzs7Ozs7Ozs7Ozs7UUFPaEI7SUFFRCxnQkFBZ0IsSUFBSSxNQUFNLENBV3pCO0lBRUQsTUFBTSxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFBRSw0QkFBNEIsR0FBRywrQkFBK0IsQ0FFNUY7SUFFRCxRQUFRLFNBRVA7SUFFRCxVQUFVLGlCQUVUO0lBRUQscUJBQXFCLDJCQUVwQjtJQUVEOzs7OztPQUtHO0lBQ0gsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFlBQVksRUFBRSxvQkFBb0IsRUFBRSxHQUFHLHlCQUF5QixDQW1FdkY7SUFFRCxxQkFBcUIsSUFBSSx5QkFBeUIsQ0FFakQ7Q0FDRjtBQUVEOzs7OztHQUtHO0FBQ0gscUJBQWEsd0NBQXlDLFNBQVEsK0JBQStCO0lBR3pGLE9BQU8sQ0FBQyxPQUFPO0lBRmpCLFlBQ0UsWUFBWSxFQUFFLG9CQUFvQixFQUFFLEVBQzVCLE9BQU8sRUFBRSxVQUFVLEVBQUUsRUFDN0IsZ0JBQWdCLEVBQUUsNEJBQTRCLEVBRy9DO0lBRVEsVUFBVSxJQUFJLFVBQVUsRUFBRSxDQUVsQztDQUNGIn0=
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestations_and_signers.d.ts","sourceRoot":"","sources":["../../../src/block/proposal/attestations_and_signers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAI3E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,QAAQ,EAEd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE9E,qBAAa,+BAAgC,YAAW,QAAQ;IAIrD,YAAY,EAAE,oBAAoB,EAAE;aAC3B,gBAAgB,EAAE,4BAA4B;IAJhE,QAAQ,CAAC,WAAW,EAAE,yBAAyB,CAA4B;IAE3E,YACS,YAAY,EAAE,oBAAoB,EAAE,EAC3B,gBAAgB,EAAE,4BAA4B,EAC5D;IAEJ,MAAM,KAAK,MAAM;;;;;;;;;;;;QAOhB;IAED,gBAAgB,IAAI,MAAM,CAWzB;IAED,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,4BAA4B,GAAG,+BAA+B,CAE5F;IAED,QAAQ,SAEP;IAED,UAAU,iBAET;IAED,qBAAqB,2BAEpB;IAED;;;;;OAKG;IACH,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,oBAAoB,EAAE,GAAG,yBAAyB,
|
|
1
|
+
{"version":3,"file":"attestations_and_signers.d.ts","sourceRoot":"","sources":["../../../src/block/proposal/attestations_and_signers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAI3E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,QAAQ,EAEd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE9E,qBAAa,+BAAgC,YAAW,QAAQ;IAIrD,YAAY,EAAE,oBAAoB,EAAE;aAC3B,gBAAgB,EAAE,4BAA4B;IAJhE,QAAQ,CAAC,WAAW,EAAE,yBAAyB,CAA4B;IAE3E,YACS,YAAY,EAAE,oBAAoB,EAAE,EAC3B,gBAAgB,EAAE,4BAA4B,EAC5D;IAEJ,MAAM,KAAK,MAAM;;;;;;;;;;;;QAOhB;IAED,gBAAgB,IAAI,MAAM,CAWzB;IAED,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,4BAA4B,GAAG,+BAA+B,CAE5F;IAED,QAAQ,SAEP;IAED,UAAU,iBAET;IAED,qBAAqB,2BAEpB;IAED;;;;;OAKG;IACH,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,oBAAoB,EAAE,GAAG,yBAAyB,CAmEvF;IAED,qBAAqB,IAAI,yBAAyB,CAEjD;CACF;AAED;;;;;GAKG;AACH,qBAAa,wCAAyC,SAAQ,+BAA+B;IAGzF,OAAO,CAAC,OAAO;IAFjB,YACE,YAAY,EAAE,oBAAoB,EAAE,EAC5B,OAAO,EAAE,UAAU,EAAE,EAC7B,gBAAgB,EAAE,4BAA4B,EAG/C;IAEQ,UAAU,IAAI,UAAU,EAAE,CAElC;CACF"}
|
|
@@ -58,8 +58,10 @@ export class CommitteeAttestationsAndSigners {
|
|
|
58
58
|
let totalDataSize = 0;
|
|
59
59
|
for (const attestation of viemAttestations){
|
|
60
60
|
const signature = attestation.signature;
|
|
61
|
-
//
|
|
62
|
-
|
|
61
|
+
// A slot is empty (a non-signing member, packed as its address) only when r, s and v are all zero
|
|
62
|
+
// — matching Signature.isEmpty() and getSigners(), so the bitmap popcount and the signers list can
|
|
63
|
+
// never disagree (which would revert propose() with SignersSizeMismatch).
|
|
64
|
+
const isEmpty = signature.v === 0 && BigInt(signature.r) === 0n && BigInt(signature.s) === 0n;
|
|
63
65
|
if (!isEmpty) {
|
|
64
66
|
totalDataSize += 65; // v (1) + r (32) + s (32)
|
|
65
67
|
} else {
|
|
@@ -71,15 +73,17 @@ export class CommitteeAttestationsAndSigners {
|
|
|
71
73
|
// Pack the data
|
|
72
74
|
for (const [i, attestation] of viemAttestations.entries()){
|
|
73
75
|
const signature = attestation.signature;
|
|
74
|
-
//
|
|
75
|
-
const isEmpty = signature.v === 0;
|
|
76
|
+
// Empty iff r, s and v are all zero (see the size-tally loop above).
|
|
77
|
+
const isEmpty = signature.v === 0 && BigInt(signature.r) === 0n && BigInt(signature.s) === 0n;
|
|
76
78
|
if (!isEmpty) {
|
|
77
79
|
// Set bit in bitmap (bit 7-0 in each byte, left to right)
|
|
78
80
|
const byteIndex = Math.floor(i / 8);
|
|
79
81
|
const bitIndex = 7 - i % 8;
|
|
80
82
|
signatureIndices[byteIndex] = (signatureIndices[byteIndex] ?? 0) | 1 << bitIndex;
|
|
81
|
-
// Pack signature: v + r + s
|
|
82
|
-
|
|
83
|
+
// Pack signature: v + r + s. Canonicalize a yParity recovery byte (v = 0/1) to 27/28 — it
|
|
84
|
+
// recovers to the same signer, but L1 ECDSA.recover only accepts 27/28. Any other value is left
|
|
85
|
+
// as-is so a genuinely malformed signature still fails on L1 rather than being silently rewritten.
|
|
86
|
+
signaturesOrAddresses[dataIndex] = signature.v === 0 || signature.v === 1 ? signature.v + 27 : signature.v;
|
|
83
87
|
dataIndex++;
|
|
84
88
|
// Pack r (32 bytes)
|
|
85
89
|
const rBytes = Buffer.from(signature.r.slice(2), 'hex');
|
|
@@ -17,4 +17,4 @@ export declare function orderAttestations(attestations: CheckpointAttestation[],
|
|
|
17
17
|
* 3. Remaining attestations filled to reach the required count
|
|
18
18
|
*/
|
|
19
19
|
export declare function trimAttestations(attestations: CheckpointAttestation[], required: number, proposerAddress: EthAddress, localAddresses: EthAddress[]): CheckpointAttestation[];
|
|
20
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
20
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0ZXN0YXRpb25fdXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wMnAvYXR0ZXN0YXRpb25fdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFaEUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDekQsT0FBTyxLQUFLLEVBQUUscUJBQXFCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUV6RTs7OztHQUlHO0FBQ0gsd0JBQWdCLGlCQUFpQixDQUMvQixZQUFZLEVBQUUscUJBQXFCLEVBQUUsRUFDckMsY0FBYyxFQUFFLFVBQVUsRUFBRSxHQUMzQixvQkFBb0IsRUFBRSxDQTJCeEI7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILHdCQUFnQixnQkFBZ0IsQ0FDOUIsWUFBWSxFQUFFLHFCQUFxQixFQUFFLEVBQ3JDLFFBQVEsRUFBRSxNQUFNLEVBQ2hCLGVBQWUsRUFBRSxVQUFVLEVBQzNCLGNBQWMsRUFBRSxVQUFVLEVBQUUsR0FDM0IscUJBQXFCLEVBQUUsQ0F3Q3pCIn0=
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation_utils.d.ts","sourceRoot":"","sources":["../../src/p2p/attestation_utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attestation_utils.d.ts","sourceRoot":"","sources":["../../src/p2p/attestation_utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEzE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,qBAAqB,EAAE,EACrC,cAAc,EAAE,UAAU,EAAE,GAC3B,oBAAoB,EAAE,CA2BxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,qBAAqB,EAAE,EACrC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,UAAU,EAC3B,cAAc,EAAE,UAAU,EAAE,GAC3B,qBAAqB,EAAE,CAwCzB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
1
2
|
import { CommitteeAttestation } from '../block/index.js';
|
|
2
3
|
/**
|
|
3
4
|
* Returns attestation signatures in the order of a series of provided ethereum addresses
|
|
@@ -9,7 +10,13 @@ import { CommitteeAttestation } from '../block/index.js';
|
|
|
9
10
|
for (const attestation of attestations){
|
|
10
11
|
const sender = attestation.getSender();
|
|
11
12
|
if (sender) {
|
|
12
|
-
|
|
13
|
+
// Canonicalize the recovery byte to v ∈ {27, 28} before it reaches the L1 bundle. A committee
|
|
14
|
+
// member (or a peer mutating the byte in flight) can emit an equivalent signature in yParity form
|
|
15
|
+
// (v = 0/1); left as-is it makes `packAttestations` (empty iff v === 0) and `getSigners` (empty iff
|
|
16
|
+
// r,s,v all zero) disagree, reverting `propose()` with SignersSizeMismatch, and a v = 1 byte would
|
|
17
|
+
// later revert epoch proving in ECDSA.recover. `sender` recovered, so the signature is non-empty
|
|
18
|
+
// and low-s, and normalizeSignature preserves the recovered address.
|
|
19
|
+
attestationMap.set(sender.toString(), CommitteeAttestation.fromAddressAndSignature(sender, normalizeSignature(attestation.signature)));
|
|
13
20
|
}
|
|
14
21
|
}
|
|
15
22
|
// Create the ordered array based on the orderAddresses, else return an empty attestation
|
|
@@ -47,6 +47,14 @@ export declare class CheckpointAttestation extends Gossipable {
|
|
|
47
47
|
* @returns The proposer of the checkpoint
|
|
48
48
|
*/
|
|
49
49
|
getProposer(): EthAddress | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Returns a copy with the attester signature canonicalized to v ∈ {27, 28}. Callers store attestations
|
|
52
|
+
* received from gossip verbatim; normalizing on ingress keeps a signature emitted in yParity form
|
|
53
|
+
* (v = 0/1) — whether by a malicious committee member or a peer mutating the byte in flight — from
|
|
54
|
+
* reaching the L1 bundle in a non-canonical form. Must only be called once the signature has recovered
|
|
55
|
+
* a sender (so it is non-empty and low-s); `normalizeSignature` throws on an all-zero signature.
|
|
56
|
+
*/
|
|
57
|
+
withNormalizedSignature(): CheckpointAttestation;
|
|
50
58
|
getPayload(): Buffer;
|
|
51
59
|
/**
|
|
52
60
|
* Returns a keccak256 hash of the signed consensus payload.
|
|
@@ -86,4 +94,4 @@ export declare class CheckpointAttestation extends Gossipable {
|
|
|
86
94
|
proposerSignature: `0x${string}`;
|
|
87
95
|
};
|
|
88
96
|
}
|
|
89
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
97
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9hdHRlc3RhdGlvbi5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3AycC9jaGVja3BvaW50X2F0dGVzdGF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxLQUFLLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzFGLE9BQU8sRUFBRSxLQUFLLFlBQVksRUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBRXZFLE9BQU8sS0FBSyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ2hFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUM1RCxPQUFPLEVBQUUsWUFBWSxFQUFxQixNQUFNLDZCQUE2QixDQUFDO0FBSTlFLE9BQU8sS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRWxELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzFELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM3QyxPQUFPLEVBQUUsS0FBSyw0QkFBNEIsRUFBNkIsTUFBTSxzQkFBc0IsQ0FBQztBQUNwRyxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFNUM7Ozs7O0dBS0c7QUFDSCxxQkFBYSxxQkFBc0IsU0FBUSxVQUFVO0lBT2pELGlFQUFpRTthQUNqRCxPQUFPLEVBQUUsZ0JBQWdCO0lBRXpDLCtDQUErQzthQUMvQixTQUFTLEVBQUUsU0FBUztJQUVwQyxpREFBaUQ7YUFDakMsaUJBQWlCLEVBQUUsU0FBUztJQWI5QyxPQUFnQixRQUFRLFlBQW9DO0lBRTVELE9BQU8sQ0FBQyxZQUFZLENBQTRDO0lBQ2hFLE9BQU8sQ0FBQyxjQUFjLENBQTRDO0lBRWxFO0lBQ0UsaUVBQWlFO0lBQ2pELE9BQU8sRUFBRSxnQkFBZ0I7SUFFekMsK0NBQStDO0lBQy9CLFNBQVMsRUFBRSxTQUFTO0lBRXBDLGlEQUFpRDtJQUNqQyxpQkFBaUIsRUFBRSxTQUFTLEVBRzdDO0lBRUQsTUFBTSxLQUFLLE1BQU0sSUFBSSxNQUFNLENBQUMscUJBQXFCLENBQUMsQ0FRakQ7SUFFUSw0QkFBNEIsSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFDLENBRTdEO0lBRUQsSUFBSSxPQUFPLElBQUksRUFBRSxDQUVoQjtJQUVELElBQUksVUFBVSxJQUFJLFVBQVUsQ0FFM0I7SUFFRCxJQUFJLGdCQUFnQixJQUFJLDRCQUE0QixDQUVuRDtJQUVEOzs7T0FHRztJQUNILFNBQVMsSUFBSSxVQUFVLEdBQUcsU0FBUyxDQUtsQztJQUVEOzs7T0FHRztJQUNILFdBQVcsSUFBSSxVQUFVLEdBQUcsU0FBUyxDQWVwQztJQUVEOzs7Ozs7T0FNRztJQUNILHVCQUF1QixJQUFJLHFCQUFxQixDQUUvQztJQUVELFVBQVUsSUFBSSxNQUFNLENBRW5CO0lBRUQ7OztPQUdHO0lBQ0gsY0FBYyxJQUFJLHNCQUFzQixDQUV2QztJQUVELFFBQVEsSUFBSSxNQUFNLENBRWpCO0lBRUQsTUFBTSxDQUFDLFVBQVUsQ0FBQyxHQUFHLEVBQUUsTUFBTSxHQUFHLFlBQVksR0FBRyxxQkFBcUIsQ0FPbkU7SUFFRCxNQUFNLENBQUMsS0FBSyxJQUFJLHFCQUFxQixDQUVwQztJQUVELE1BQU0sQ0FBQyxNQUFNLElBQUkscUJBQXFCLENBRXJDO0lBRUQsT0FBTyxJQUFJLE1BQU0sQ0FFaEI7SUFFRCxTQUFTOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztNQU1SO0NBQ0YifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint_attestation.d.ts","sourceRoot":"","sources":["../../src/p2p/checkpoint_attestation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,KAAK,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"checkpoint_attestation.d.ts","sourceRoot":"","sources":["../../src/p2p/checkpoint_attestation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,KAAK,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAE,KAAK,YAAY,EAAY,MAAM,0BAA0B,CAAC;AAEvE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAqB,MAAM,6BAA6B,CAAC;AAI9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,4BAA4B,EAA6B,MAAM,sBAAsB,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,UAAU;IAOjD,iEAAiE;aACjD,OAAO,EAAE,gBAAgB;IAEzC,+CAA+C;aAC/B,SAAS,EAAE,SAAS;IAEpC,iDAAiD;aACjC,iBAAiB,EAAE,SAAS;IAb9C,OAAgB,QAAQ,YAAoC;IAE5D,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,cAAc,CAA4C;IAElE;IACE,iEAAiE;IACjD,OAAO,EAAE,gBAAgB;IAEzC,+CAA+C;IAC/B,SAAS,EAAE,SAAS;IAEpC,iDAAiD;IACjC,iBAAiB,EAAE,SAAS,EAG7C;IAED,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,qBAAqB,CAAC,CAQjD;IAEQ,4BAA4B,IAAI,OAAO,CAAC,YAAY,CAAC,CAE7D;IAED,IAAI,OAAO,IAAI,EAAE,CAEhB;IAED,IAAI,UAAU,IAAI,UAAU,CAE3B;IAED,IAAI,gBAAgB,IAAI,4BAA4B,CAEnD;IAED;;;OAGG;IACH,SAAS,IAAI,UAAU,GAAG,SAAS,CAKlC;IAED;;;OAGG;IACH,WAAW,IAAI,UAAU,GAAG,SAAS,CAepC;IAED;;;;;;OAMG;IACH,uBAAuB,IAAI,qBAAqB,CAE/C;IAED,UAAU,IAAI,MAAM,CAEnB;IAED;;;OAGG;IACH,cAAc,IAAI,sBAAsB,CAEvC;IAED,QAAQ,IAAI,MAAM,CAEjB;IAED,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,qBAAqB,CAOnE;IAED,MAAM,CAAC,KAAK,IAAI,qBAAqB,CAEpC;IAED,MAAM,CAAC,MAAM,IAAI,qBAAqB,CAErC;IAED,OAAO,IAAI,MAAM,CAEhB;IAED,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;MAMR;CACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CheckpointProposalHash } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
3
|
+
import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
3
4
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
4
5
|
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
5
6
|
import { z } from 'zod';
|
|
@@ -64,6 +65,15 @@ import { TopicType } from './topic_type.js';
|
|
|
64
65
|
}
|
|
65
66
|
return this.cachedProposer ?? undefined;
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns a copy with the attester signature canonicalized to v ∈ {27, 28}. Callers store attestations
|
|
70
|
+
* received from gossip verbatim; normalizing on ingress keeps a signature emitted in yParity form
|
|
71
|
+
* (v = 0/1) — whether by a malicious committee member or a peer mutating the byte in flight — from
|
|
72
|
+
* reaching the L1 bundle in a non-canonical form. Must only be called once the signature has recovered
|
|
73
|
+
* a sender (so it is non-empty and low-s); `normalizeSignature` throws on an all-zero signature.
|
|
74
|
+
*/ withNormalizedSignature() {
|
|
75
|
+
return new CheckpointAttestation(this.payload, normalizeSignature(this.signature), this.proposerSignature);
|
|
76
|
+
}
|
|
67
77
|
getPayload() {
|
|
68
78
|
return this.payload.getPayloadToSign();
|
|
69
79
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/stdlib",
|
|
3
|
-
"version": "5.0.0-nightly.
|
|
3
|
+
"version": "5.0.0-nightly.20260706",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"inherits": [
|
|
6
6
|
"../package.common.json",
|
|
@@ -92,13 +92,13 @@
|
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
94
|
"@aws-sdk/client-s3": "^3.892.0",
|
|
95
|
-
"@aztec/bb.js": "5.0.0-nightly.
|
|
96
|
-
"@aztec/blob-lib": "5.0.0-nightly.
|
|
97
|
-
"@aztec/constants": "5.0.0-nightly.
|
|
98
|
-
"@aztec/ethereum": "5.0.0-nightly.
|
|
99
|
-
"@aztec/foundation": "5.0.0-nightly.
|
|
100
|
-
"@aztec/l1-artifacts": "5.0.0-nightly.
|
|
101
|
-
"@aztec/noir-noirc_abi": "5.0.0-nightly.
|
|
95
|
+
"@aztec/bb.js": "5.0.0-nightly.20260706",
|
|
96
|
+
"@aztec/blob-lib": "5.0.0-nightly.20260706",
|
|
97
|
+
"@aztec/constants": "5.0.0-nightly.20260706",
|
|
98
|
+
"@aztec/ethereum": "5.0.0-nightly.20260706",
|
|
99
|
+
"@aztec/foundation": "5.0.0-nightly.20260706",
|
|
100
|
+
"@aztec/l1-artifacts": "5.0.0-nightly.20260706",
|
|
101
|
+
"@aztec/noir-noirc_abi": "5.0.0-nightly.20260706",
|
|
102
102
|
"@google-cloud/storage": "^7.15.0",
|
|
103
103
|
"axios": "^1.15.1",
|
|
104
104
|
"json-stringify-deterministic": "1.0.12",
|
|
@@ -76,8 +76,10 @@ export class CommitteeAttestationsAndSigners implements Signable {
|
|
|
76
76
|
let totalDataSize = 0;
|
|
77
77
|
for (const attestation of viemAttestations) {
|
|
78
78
|
const signature = attestation.signature;
|
|
79
|
-
//
|
|
80
|
-
|
|
79
|
+
// A slot is empty (a non-signing member, packed as its address) only when r, s and v are all zero
|
|
80
|
+
// — matching Signature.isEmpty() and getSigners(), so the bitmap popcount and the signers list can
|
|
81
|
+
// never disagree (which would revert propose() with SignersSizeMismatch).
|
|
82
|
+
const isEmpty = signature.v === 0 && BigInt(signature.r) === 0n && BigInt(signature.s) === 0n;
|
|
81
83
|
|
|
82
84
|
if (!isEmpty) {
|
|
83
85
|
totalDataSize += 65; // v (1) + r (32) + s (32)
|
|
@@ -93,8 +95,8 @@ export class CommitteeAttestationsAndSigners implements Signable {
|
|
|
93
95
|
for (const [i, attestation] of viemAttestations.entries()) {
|
|
94
96
|
const signature = attestation.signature;
|
|
95
97
|
|
|
96
|
-
//
|
|
97
|
-
const isEmpty = signature.v === 0;
|
|
98
|
+
// Empty iff r, s and v are all zero (see the size-tally loop above).
|
|
99
|
+
const isEmpty = signature.v === 0 && BigInt(signature.r) === 0n && BigInt(signature.s) === 0n;
|
|
98
100
|
|
|
99
101
|
if (!isEmpty) {
|
|
100
102
|
// Set bit in bitmap (bit 7-0 in each byte, left to right)
|
|
@@ -102,8 +104,10 @@ export class CommitteeAttestationsAndSigners implements Signable {
|
|
|
102
104
|
const bitIndex = 7 - (i % 8);
|
|
103
105
|
signatureIndices[byteIndex] = (signatureIndices[byteIndex] ?? 0) | (1 << bitIndex);
|
|
104
106
|
|
|
105
|
-
// Pack signature: v + r + s
|
|
106
|
-
|
|
107
|
+
// Pack signature: v + r + s. Canonicalize a yParity recovery byte (v = 0/1) to 27/28 — it
|
|
108
|
+
// recovers to the same signer, but L1 ECDSA.recover only accepts 27/28. Any other value is left
|
|
109
|
+
// as-is so a genuinely malformed signature still fails on L1 rather than being silently rewritten.
|
|
110
|
+
signaturesOrAddresses[dataIndex] = signature.v === 0 || signature.v === 1 ? signature.v + 27 : signature.v;
|
|
107
111
|
dataIndex++;
|
|
108
112
|
|
|
109
113
|
// Pack r (32 bytes)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
1
2
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
3
|
|
|
3
4
|
import { CommitteeAttestation } from '../block/index.js';
|
|
@@ -18,9 +19,15 @@ export function orderAttestations(
|
|
|
18
19
|
for (const attestation of attestations) {
|
|
19
20
|
const sender = attestation.getSender();
|
|
20
21
|
if (sender) {
|
|
22
|
+
// Canonicalize the recovery byte to v ∈ {27, 28} before it reaches the L1 bundle. A committee
|
|
23
|
+
// member (or a peer mutating the byte in flight) can emit an equivalent signature in yParity form
|
|
24
|
+
// (v = 0/1); left as-is it makes `packAttestations` (empty iff v === 0) and `getSigners` (empty iff
|
|
25
|
+
// r,s,v all zero) disagree, reverting `propose()` with SignersSizeMismatch, and a v = 1 byte would
|
|
26
|
+
// later revert epoch proving in ECDSA.recover. `sender` recovered, so the signature is non-empty
|
|
27
|
+
// and low-s, and normalizeSignature preserves the recovered address.
|
|
21
28
|
attestationMap.set(
|
|
22
29
|
sender.toString(),
|
|
23
|
-
CommitteeAttestation.fromAddressAndSignature(sender, attestation.signature),
|
|
30
|
+
CommitteeAttestation.fromAddressAndSignature(sender, normalizeSignature(attestation.signature)),
|
|
24
31
|
);
|
|
25
32
|
}
|
|
26
33
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CheckpointProposalHash, type SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { type BaseBuffer32, Buffer32 } from '@aztec/foundation/buffer';
|
|
3
|
+
import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
3
4
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
5
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
6
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
@@ -97,6 +98,17 @@ export class CheckpointAttestation extends Gossipable {
|
|
|
97
98
|
return this.cachedProposer ?? undefined;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Returns a copy with the attester signature canonicalized to v ∈ {27, 28}. Callers store attestations
|
|
103
|
+
* received from gossip verbatim; normalizing on ingress keeps a signature emitted in yParity form
|
|
104
|
+
* (v = 0/1) — whether by a malicious committee member or a peer mutating the byte in flight — from
|
|
105
|
+
* reaching the L1 bundle in a non-canonical form. Must only be called once the signature has recovered
|
|
106
|
+
* a sender (so it is non-empty and low-s); `normalizeSignature` throws on an all-zero signature.
|
|
107
|
+
*/
|
|
108
|
+
withNormalizedSignature(): CheckpointAttestation {
|
|
109
|
+
return new CheckpointAttestation(this.payload, normalizeSignature(this.signature), this.proposerSignature);
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
getPayload(): Buffer {
|
|
101
113
|
return this.payload.getPayloadToSign();
|
|
102
114
|
}
|