Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
67a908d
fix(aztec-nr): account for AES PKCS#7 padding in message plaintext le…
nchamo Feb 27, 2026
36b95fa
feat: remove epk sign from message payload (#20926)
nventuro Feb 27, 2026
eb9bc44
chore: increase mainnet local ejection threshold to 190k (#20884)
aminsammara Feb 27, 2026
e654e8a
feat: add pinned-build support for protocol contracts in noir-contrac…
AztecBot Feb 28, 2026
a778bd2
chore: pin protocol contracts, allowing aztec-nr to be independent
ludamad Feb 28, 2026
5e3eed8
fix!: undo bad fix (#20987)
LHerskind Mar 1, 2026
6da81b2
chore: backport #20967 to v4 (metric on how many epochs validator has…
AztecBot Mar 2, 2026
096d69b
chore(sequencer): e2e tests for invalid signature recovery in checkpo…
spalladino Mar 2, 2026
44d79c8
chore: pin SponsoredFPC contract to fix testnet compat
ludamad Mar 2, 2026
13c7cae
feat: allow custom addresses to be prefunded with fee juice in local …
AztecBot Mar 2, 2026
fd9b4e7
chore: increase max fee bots use in tests (#20867)
alexghr Mar 2, 2026
2776313
fix(spartan): wire SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT env var (…
spalladino Mar 2, 2026
b7f2353
fix: update MAX_EVENT_SERIALIZATION_LENGTH and re-enable TXE tests in…
AztecBot Mar 3, 2026
939b740
test: update proving-real test to mbps (#20991)
alexghr Mar 3, 2026
70dd988
chore: epoch proving log analyzer (#21033)
alexghr Mar 3, 2026
bccadd7
chore: update pause script to allow resume (#21032)
alexghr Mar 3, 2026
4d883ed
refactor: remove update checker, retain version checks (backport #208…
AztecBot Mar 3, 2026
0dd0a07
chore: backport #20806 (price bump for RPC transaction replacement) t…
AztecBot Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,9 @@ function build_and_test {
if [ "$finished" == "$make_pid" ]; then
make_pid=

if [ -z "${1:-}" ]; then
# TODO: Handle this better to they can be run as part of the Makefile dependency tree.
start_txes
make noir-projects-txe-tests
fi
# TODO: Handle this better so they can be run as part of the Makefile dependency tree.
start_txes
make noir-projects-txe-tests

# Signal tests complete, handled by parallel -E STOP.
echo STOP >> $test_cmds_file
Expand Down
12 changes: 12 additions & 0 deletions docs/docs-operate/operators/reference/changelog/v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ Transaction submission via RPC now returns structured rejection codes when a tra

**Impact**: Improved developer experience — callers can now programmatically handle specific rejection reasons.

### RPC transaction replacement price bump

Transactions submitted via RPC that clash on nullifiers with existing pool transactions must now pay at least X% more in priority fee to replace them. The same bump applies when the pool is full and the incoming tx needs to evict the lowest-priority tx. P2P gossip behavior is unchanged.

**Configuration:**

```bash
P2P_RPC_PRICE_BUMP_PERCENTAGE=10 # default: 10 (percent)
```

Set to `0` to disable the percentage-based bump (still requires strictly higher fee).

## Changed defaults

## Troubleshooting
Expand Down
8 changes: 0 additions & 8 deletions l1-contracts/src/core/slashing/TallySlashingProposer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -903,14 +903,6 @@ contract TallySlashingProposer is EIP712 {
if (escapeHatchEpochs[epochIndex]) {
continue;
}

// Skip validators for epochs without a valid committee (e.g. early epochs
// before the validator set was sampled). Without this check, indexing into
// an empty committee array would revert and block execution of the round.
if (_committees[epochIndex].length != COMMITTEE_SIZE) {
continue;
}

uint256 packedVotes = tallyMatrix[i];

// Skip if no votes for this validator
Expand Down
18 changes: 10 additions & 8 deletions l1-contracts/test/slashing/TallySlashingProposer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -991,9 +991,9 @@ contract TallySlashingProposerTest is TestBase {
// Round FIRST_SLASH_ROUND targets epochs 0 and 1, which have no committees
// because they precede the validator set sampling lag.
//
// Before the fix, casting votes that reach quorum for validator slots in these
// committee-less epochs would cause executeRound (and getTally) to revert with
// an array out-of-bounds access when indexing _committees[epochIndex][validatorIndex].
// Casting votes that reach quorum for validator slots in these committee-less epochs cause
// executeRound (and getTally) to revert with an array out-of-bounds access when
// indexing _committees[epochIndex][validatorIndex].
_jumpToSlashRound(FIRST_SLASH_ROUND);
SlashRound targetRound = slashingProposer.getCurrentRound();

Expand All @@ -1020,16 +1020,18 @@ contract TallySlashingProposerTest is TestBase {
assertEq(committees[0].length, 0, "Epoch 0 should have empty committee");
assertEq(committees[1].length, 0, "Epoch 1 should have empty committee");

// getTally should not revert and should return 0 actions
// getTally should revert because of out of bounds
vm.expectRevert();
TallySlashingProposer.SlashAction[] memory actions = slashingProposer.getTally(targetRound, committees);
assertEq(actions.length, 0, "Should have no slash actions for empty committees");
assertEq(actions.length, 0, "Should have no slash actions since reverted");

// executeRound should also succeed
// Reverts because of out of bounds
vm.expectRevert();
slashingProposer.executeRound(targetRound, committees);

// Verify round is marked as executed
// Verify round is not marked as executed
(bool isExecuted,) = slashingProposer.getRound(targetRound);
assertTrue(isExecuted, "Round should be marked as executed");
assertFalse(isExecuted, "Round should not be marked as executed");
}

function test_revertWhenSlashAmountIsZero() public {
Expand Down
56 changes: 55 additions & 1 deletion noir-projects/aztec-nr/aztec/src/keys/ephemeral.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use std::embedded_curve_ops::{EmbeddedCurveScalar, fixed_base_scalar_mul};

use crate::protocol::{point::Point, scalar::Scalar};

use crate::oracle::random::random;
use crate::{oracle::random::random, utils::point::get_sign_of_point};

/// Generates a random ephemeral key pair.
pub fn generate_ephemeral_key_pair() -> (Scalar, Point) {
// @todo Need to draw randomness from the full domain of Fq not only Fr

Expand All @@ -20,3 +21,56 @@ pub fn generate_ephemeral_key_pair() -> (Scalar, Point) {

(eph_sk, eph_pk)
}

/// Generates a random ephemeral key pair with a positive y-coordinate.
///
/// Unlike [`generate_ephemeral_key_pair`], the y-coordinate of the public key is guaranteed to be a positive value
/// (i.e. [`crate::utils::point::get_sign_of_point`] will return `true`).
///
/// This is useful as it means it is possible to just broadcast the x-coordinate as a single `Field` and then
/// reconstruct the original public key using [`crate::utils::point::point_from_x_coord_and_sign`] with `sign: true`.
pub fn generate_positive_ephemeral_key_pair() -> (Scalar, Point) {
// Safety: we use the randomness to preserve the privacy of both the sender and recipient via encryption, so a
// malicious sender could use non-random values to reveal the plaintext. But they already know it themselves
// anyway, and so the recipient already trusts them to not disclose this information. We can therefore assume that
// the sender will cooperate in the random value generation.
let eph_sk = unsafe { generate_secret_key_for_positive_public_key() };
let eph_pk = fixed_base_scalar_mul(eph_sk);

assert(get_sign_of_point(eph_pk), "Got an ephemeral public key with a negative y coordinate");

(eph_sk, eph_pk)
}

unconstrained fn generate_secret_key_for_positive_public_key() -> EmbeddedCurveScalar {
let mut sk = std::mem::zeroed();

loop {
// We simply produce random secret keys until we find one that has results in a positive public key. About half
// of all public keys fulfill this condition, so this should only take a few iterations at most.

// @todo Need to draw randomness from the full domain of Fq not only Fr
sk = EmbeddedCurveScalar::from_field(random());
let pk = fixed_base_scalar_mul(sk);
if get_sign_of_point(pk) {
break;
}
}

sk
}

mod test {
use crate::utils::point::get_sign_of_point;
use super::generate_positive_ephemeral_key_pair;

#[test]
fn generate_positive_ephemeral_key_pair_produces_positive_keys() {
// About half of random points are negative, so testing just a couple gives us high confidence that
// `generate_positive_ephemeral_key_pair` is indeed producing positive ones.
for _ in 0..10 {
let (_, pk) = generate_positive_ephemeral_key_pair();
assert(get_sign_of_point(pk));
}
}
}
39 changes: 32 additions & 7 deletions noir-projects/aztec-nr/aztec/src/messages/encoding.nr
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ use crate::utils::array;
// fields, so MESSAGE_CIPHERTEXT_LEN is the size of the message in fields.
pub global MESSAGE_CIPHERTEXT_LEN: u32 = PRIVATE_LOG_CIPHERTEXT_LEN;

// TODO(#12750): The global variables below should not be here as they are AES128 specific. ciphertext_length (2) + 14
// bytes pkcs#7 AES padding.
// TODO(#12750): The global variables below should not be here as they are AES128 specific.
// The header plaintext is 2 bytes (ciphertext length), padded to the 16-byte AES block size by PKCS#7.
pub(crate) global HEADER_CIPHERTEXT_SIZE_IN_BYTES: u32 = 16;
// AES PKCS#7 always adds at least one byte of padding. Since each plaintext field is 32 bytes (a multiple of the
// 16-byte AES block size), a full 16-byte padding block is always appended.
pub(crate) global AES128_PKCS7_EXPANSION_IN_BYTES: u32 = 16;

pub global EPH_PK_X_SIZE_IN_FIELDS: u32 = 1;
pub global EPH_PK_SIGN_BYTE_SIZE_IN_BYTES: u32 = 1;

// (17 - 1) * 31 - 16 - 1 = 479 Note: We multiply by 31 because ciphertext bytes are stored in fields using
// (15 - 1) * 31 - 16 - 16 = 402. Note: We multiply by 31 because ciphertext bytes are stored in fields using
// bytes_to_fields, which packs 31 bytes per field (since a Field is ~254 bits and can safely store 31 whole bytes).
global MESSAGE_PLAINTEXT_SIZE_IN_BYTES: u32 = (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31
pub(crate) global MESSAGE_PLAINTEXT_SIZE_IN_BYTES: u32 = (MESSAGE_CIPHERTEXT_LEN - EPH_PK_X_SIZE_IN_FIELDS) * 31
- HEADER_CIPHERTEXT_SIZE_IN_BYTES
- EPH_PK_SIGN_BYTE_SIZE_IN_BYTES;
- AES128_PKCS7_EXPANSION_IN_BYTES;
// The plaintext bytes represent Field values that were originally serialized using fields_to_bytes, which converts
// each Field to 32 bytes. To convert the plaintext bytes back to fields, we divide by 32. 479 / 32 = 14
// each Field to 32 bytes. To convert the plaintext bytes back to fields, we divide by 32. 402 / 32 = 12
pub global MESSAGE_PLAINTEXT_LEN: u32 = MESSAGE_PLAINTEXT_SIZE_IN_BYTES / 32;

pub global MESSAGE_EXPANDED_METADATA_LEN: u32 = 1;
Expand Down Expand Up @@ -244,4 +246,27 @@ mod tests {
assert_eq(original_msg_type, unpacked_msg_type);
assert_eq(original_msg_metadata, unpacked_msg_metadata);
}

#[test]
unconstrained fn encode_decode_max_size_message() {
let msg_type_id: u64 = 42;
let msg_metadata: u64 = 99;
let mut msg_content = [0; MAX_MESSAGE_CONTENT_LEN];
for i in 0..MAX_MESSAGE_CONTENT_LEN {
msg_content[i] = i as Field;
}

let encoded = encode_message(msg_type_id, msg_metadata, msg_content);
let (decoded_type_id, decoded_metadata, decoded_content) = decode_message(BoundedVec::from_array(encoded));

assert_eq(decoded_type_id, msg_type_id);
assert_eq(decoded_metadata, msg_metadata);
assert_eq(decoded_content, BoundedVec::from_array(msg_content));
}

#[test(should_fail_with = "Invalid message content: it must have a length of at most MAX_MESSAGE_CONTENT_LEN")]
fn encode_oversized_message_fails() {
let msg_content = [0; MAX_MESSAGE_CONTENT_LEN + 1];
let _ = encode_message(0, 0, msg_content);
}
}
Loading
Loading