Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d08db67
Increase max array size to 2048
raychu86 Nov 7, 2025
9319273
Implement snark.verify opcode
raychu86 Nov 7, 2025
ff65327
Cleanup ordering
raychu86 Nov 7, 2025
045775f
Add snark.verify to instructions
raychu86 Nov 7, 2025
c3fab05
Implement tests for snark.verify
raychu86 Nov 7, 2025
8ec4b19
Update num expected opcodes
raychu86 Nov 7, 2025
528640e
Add snark.verify.batch to instructions
raychu86 Nov 7, 2025
24de8bc
Integrate snark.verify.batch and add tests
raychu86 Nov 7, 2025
0a0a274
Rewrite expectations and fix tests
raychu86 Nov 7, 2025
6f14b8f
Merge branch 'staging' into feat/snark-verify
raychu86 Nov 10, 2025
10ec834
Add checks for num circuits
raychu86 Jan 7, 2026
8291d31
Add snark.verify mapping test
raychu86 Jan 7, 2026
8ee686a
Add VarunaVersion as an operand for snark.verify
raychu86 Jan 8, 2026
dce2aec
Bound total number of snark.verify assignment instances
raychu86 Jan 8, 2026
4da0200
Add support for padding inputs
raychu86 Jan 9, 2026
b6335bd
nit
raychu86 Jan 9, 2026
6b20106
Update to V14
raychu86 Jan 9, 2026
53066d9
Merge staging
raychu86 Jan 9, 2026
435bec8
Use ConsensusVersion::V14
raychu86 Jan 9, 2026
3e061d4
Merge staging
raychu86 Jan 14, 2026
5d7c92b
Rewrite expectations
raychu86 Jan 14, 2026
8efc127
Fix num operands
raychu86 Jan 15, 2026
2a713e6
Increase future argument size restriction
raychu86 Jan 21, 2026
5ba49c7
Unify versioned MAX_ARRAY_ELEMENTS into a single variable
raychu86 Jan 22, 2026
c3f26a3
Update documentation
raychu86 Jan 22, 2026
7f70852
Merge staging
raychu86 Jan 28, 2026
e7272b2
Adjust snark verification pricing
raychu86 Jan 28, 2026
9273488
nits
raychu86 Feb 2, 2026
d402b1b
Merge staging
raychu86 Feb 2, 2026
0563439
Address comments
raychu86 Feb 2, 2026
44727d3
Fix size check for FinalizeType
raychu86 Feb 2, 2026
4046c49
Add documentation
raychu86 Feb 2, 2026
d095b17
Merge staging
raychu86 Feb 3, 2026
1bff6b6
Update documentation
raychu86 Feb 3, 2026
fb5fec6
Fix CI indents
raychu86 Feb 4, 2026
44c09de
Fix CI name
raychu86 Feb 4, 2026
1e437ee
Fix indent
raychu86 Feb 4, 2026
8332ee3
Merge branch 'staging' into feat/snark-verify
raychu86 Feb 5, 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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ commands:

# Put cargo/rustup first in PATH for this step and subsequent steps
$Env:Path = "$Env:USERPROFILE\.cargo\bin;$Env:Path"

# Select the toolchain
rustup default 1.88.0-x86_64-pc-windows-msvc

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions algorithms/src/snark/varuna/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.

use core::fmt::Debug;
use snarkvm_utilities::{FromBytes, ToBytes, io_error};
use std::io;

/// A trait to specify the SNARK mode.
pub trait SNARKMode: 'static + Copy + Clone + Debug + PartialEq + Eq + Sync + Send {
Expand All @@ -37,8 +39,26 @@ impl SNARKMode for VarunaNonHidingMode {
}

/// The different Varuna Versions.
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VarunaVersion {
V1 = 1,
V2 = 2,
}

impl ToBytes for VarunaVersion {
fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
(*self as u8).write_le(writer)
}
}

impl FromBytes for VarunaVersion {
fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
match u8::read_le(reader)? {
0 => Err(io_error("Zero is not a valid Varuna version")),
1 => Ok(Self::V1),
2 => Ok(Self::V2),
_ => Err(io_error("Invalid Varuna version")),
}
}
}
14 changes: 11 additions & 3 deletions circuit/program/src/data/future/to_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.

use super::*;
use snarkvm_circuit_types::U8;
use snarkvm_circuit_types::{U8, U32};

impl<A: Aleo> ToBits for Future<A> {
type Boolean = Boolean<A>;
Expand All @@ -39,7 +39,11 @@ impl<A: Aleo> ToBits for Future<A> {
for argument in &self.arguments {
let argument_bits = argument.to_bits_le();
// Write the size of the argument.
U16::constant(console::U16::new(argument_bits.len() as u16)).write_bits_le(vec);
let argument_length = argument_bits.len();
match argument_length <= u16::MAX as usize {
true => U16::constant(console::U16::new(argument_length as u16)).write_bits_le(vec),
false => U32::constant(console::U32::new(argument_length as u32)).write_bits_le(vec),
}
Comment on lines +42 to +46
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicsn @d0cd This section in particular warrants a review.

I only updated the bit serialization rather than byte serialization because we do not have version variants in serialization that could be used (particularly in circuit).

The bit serialization is used in generating the field repr preimage of the hash. Had to do some custom muxxing, but should be safe in theory because these argument sizes need to be fixed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do we need to increase the limit for the Plaintext::toBits value sizes (that are currently bound to u16::MAX)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems safe indeed because argument sizes are fixed.

Not sure I have context to answer whether the size of other types need to increase. Can you follow the requirements of a motivating unit test?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Increasing Plaintext::ToBits is an option, if you want expand scope.
By increasing the bound here we are supporting much larger nested Futures and slightly larger Plaintext arguments.

On a separate note, if we care about recovery of ToBits (which we haven't paid much attention to in the past), we can consider introducing a "version" byte with a zero in front of an identifier. Similar to dynamic disptach.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be leaving Plaintext::ToBits for future work, as it is quite involved and requires updating the from_bits impls.

Currently a Plaintext::Struct with a byte array element can only have ~1300 elements in that array due to the element bit size limit of u16::MAX.

// Write the argument.
vec.extend_from_slice(&argument_bits);
}
Expand All @@ -65,7 +69,11 @@ impl<A: Aleo> ToBits for Future<A> {
for argument in &self.arguments {
let argument_bits = argument.to_bits_be();
// Write the size of the argument.
U16::constant(console::U16::new(argument_bits.len() as u16)).write_bits_be(vec);
let argument_length = argument_bits.len();
match argument_length <= u16::MAX as usize {
true => U16::constant(console::U16::new(argument_length as u16)).write_bits_be(vec),
false => U32::constant(console::U32::new(argument_length as u32)).write_bits_be(vec),
}
// Write the argument.
vec.extend_from_slice(&argument_bits);
}
Expand Down
32 changes: 30 additions & 2 deletions console/network/src/consensus_heights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ pub enum ConsensusVersion {
V12 = 12,
/// V13: Introduces external structs.
V13 = 13,
/// V14: Increase the program size limit to 512 kB and transaction size limit to 540 kB.
/// Introduces `aleo::GENERATOR` and `aleo::GENERATOR_POWERS` opcodes.
/// V14: Increase the program size limit to 512 kB, the transaction size limit to 540 kB,
/// the array size limit to 2048, and the `Future` argument bit size to 32 bits.
/// Introduces `aleo::GENERATOR`, `aleo::GENERATOR_POWERS`, and `snark.verify` opcodes.
V14 = 14,
}

Expand Down Expand Up @@ -318,6 +319,11 @@ mod tests {
assert!(*version > previous_version);
previous_version = *version;
}
let mut previous_version = N::MAX_ARRAY_ELEMENTS.first().unwrap().0;
for (version, _) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
assert!(*version > previous_version);
previous_version = *version;
}
let mut previous_version = N::MAX_PROGRAM_SIZE.first().unwrap().0;
for (version, _) in N::MAX_PROGRAM_SIZE.iter().skip(1) {
assert!(*version > previous_version);
Expand Down Expand Up @@ -362,6 +368,12 @@ mod tests {
// Double-check that consensus_config_value returns the correct value.
assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
}
for (version, value) in N::MAX_ARRAY_ELEMENTS.iter() {
// Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
// Double-check that consensus_config_value returns the correct value.
assert_eq!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, height).unwrap(), *value);
}
for (version, value) in N::MAX_PROGRAM_SIZE.iter() {
// Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
Expand All @@ -387,6 +399,7 @@ mod tests {
for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
assert!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, *height).is_some());
assert!(consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).is_some());
assert!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).is_some());
assert!(consensus_config_value!(N, MAX_WRITES, *height).is_some());
Expand All @@ -403,6 +416,16 @@ mod tests {
}
}

/// Ensure that `MAX_ARRAY_ELEMENTS` increases and is correctly defined.
/// See the constant declaration for an explanation why.
fn max_array_elements_increasing<N: Network>() {
let mut previous_value = N::MAX_ARRAY_ELEMENTS.first().unwrap().1;
for (_, value) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
assert!(*value >= previous_value);
previous_value = *value;
}
}

/// Ensure that `MAX_TRANSACTION_SIZE` is at least 28KB greater than `MAX_PROGRAM_SIZE` for all consensus versions.
/// This overhead accounts for proofs, signatures, and other transaction metadata.
fn transaction_size_exceeds_program_size<N: Network>() {
Expand All @@ -425,6 +448,7 @@ mod tests {
let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
let _ = [N1::MAX_ARRAY_ELEMENTS, N2::MAX_ARRAY_ELEMENTS, N3::MAX_ARRAY_ELEMENTS];
let _ = [N1::MAX_PROGRAM_SIZE, N2::MAX_PROGRAM_SIZE, N3::MAX_PROGRAM_SIZE];
let _ = [N1::MAX_TRANSACTION_SIZE, N2::MAX_TRANSACTION_SIZE, N3::MAX_TRANSACTION_SIZE];
let _ = [N1::MAX_WRITES, N2::MAX_WRITES, N3::MAX_WRITES];
Expand Down Expand Up @@ -470,6 +494,10 @@ mod tests {
max_certificates_increasing::<TestnetV0>();
max_certificates_increasing::<CanaryV0>();

max_array_elements_increasing::<MainnetV0>();
max_array_elements_increasing::<TestnetV0>();
max_array_elements_increasing::<CanaryV0>();

transaction_size_exceeds_program_size::<MainnetV0>();
transaction_size_exceeds_program_size::<TestnetV0>();
transaction_size_exceeds_program_size::<CanaryV0>();
Expand Down
10 changes: 8 additions & 2 deletions console/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,9 @@ pub trait Network:

/// The minimum number of elements in an array.
const MIN_ARRAY_ELEMENTS: usize = 1; // This ensures the array is not empty.
/// The maximum number of elements in an array.
const MAX_ARRAY_ELEMENTS: usize = 512;
/// A list of (consensus_version, size) pairs indicating the maximum number of elements in an array.
const MAX_ARRAY_ELEMENTS: [(ConsensusVersion, usize); 3] =
[(ConsensusVersion::V1, 32), (ConsensusVersion::V11, 512), (ConsensusVersion::V14, 2048)];

/// The minimum number of entries in a record.
const MIN_RECORD_ENTRIES: usize = 1; // This accounts for 'record.owner'.
Expand Down Expand Up @@ -315,6 +316,11 @@ pub trait Network:
fn CONSENSUS_HEIGHT(version: ConsensusVersion) -> Result<u32> {
Ok(Self::CONSENSUS_VERSION_HEIGHTS().get(version as usize - 1).ok_or(anyhow!("Invalid consensus version"))?.1)
}
/// Returns the last `MAX_ARRAY_ELEMENTS` value.
#[allow(non_snake_case)]
fn LATEST_MAX_ARRAY_ELEMENTS() -> usize {
Self::MAX_ARRAY_ELEMENTS.last().expect("MAX_ARRAY_ELEMENTS must have at least one entry").1
}
/// Returns the last `MAX_CERTIFICATES` value.
#[allow(non_snake_case)]
fn LATEST_MAX_CERTIFICATES() -> u16 {
Expand Down
22 changes: 20 additions & 2 deletions console/program/src/data/future/to_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use super::*;

impl<N: Network> ToBits for Future<N> {
/// Returns the future as a list of **little-endian** bits.
// Note: Any updates to bit serialization will require updating `FinalizeType::future_size_in_bits`.
#[inline]
fn write_bits_le(&self, vec: &mut Vec<bool>) {
// Write the bits for the program ID.
Expand All @@ -39,14 +40,23 @@ impl<N: Network> ToBits for Future<N> {
let argument_bits = argument.to_bits_le();

// Write the size of the argument.
u16::try_from(argument_bits.len()).or_halt_with::<N>("argument exceeds u16::MAX bits").write_bits_le(vec);
let argument_length = argument_bits.len();
match argument_length <= u16::MAX as usize {
true => u16::try_from(argument_length)
.or_halt_with::<N>("argument exceeds u16::MAX bits")
.write_bits_le(vec),
false => u32::try_from(argument_length)
.or_halt_with::<N>("argument exceeds u32::MAX bits")
.write_bits_le(vec),
}

// Write the argument.
vec.extend_from_slice(&argument_bits);
}
}

/// Returns the future as a list of **big-endian** bits.
// Note: Any updates to bit serialization will require updating `FinalizeType::future_size_in_bits`.
#[inline]
fn write_bits_be(&self, vec: &mut Vec<bool>) {
// Write the bits for the program ID.
Expand All @@ -69,7 +79,15 @@ impl<N: Network> ToBits for Future<N> {
let argument_bits = argument.to_bits_be();

// Write the size of the argument.
u16::try_from(argument_bits.len()).or_halt_with::<N>("argument exceeds u16::MAX bits").write_bits_be(vec);
let argument_length = argument_bits.len();
match argument_length <= u16::MAX as usize {
true => u16::try_from(argument_length)
.or_halt_with::<N>("argument exceeds u16::MAX bits")
.write_bits_be(vec),
false => u32::try_from(argument_length)
.or_halt_with::<N>("argument exceeds u32::MAX bits")
.write_bits_be(vec),
}

// Write the argument.
vec.extend_from_slice(&argument_bits);
Expand Down
2 changes: 1 addition & 1 deletion console/program/src/data/plaintext/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl<N: Network> Plaintext<N> {
2 => {
// Read the length of the array.
let num_elements = u32::read_le(&mut reader)?;
if num_elements as usize > N::MAX_ARRAY_ELEMENTS {
if num_elements as usize > N::LATEST_MAX_ARRAY_ELEMENTS() {
return Err(error("Failed to deserialize plaintext: Array exceeds maximum length"));
}
// Read the elements.
Expand Down
4 changes: 2 additions & 2 deletions console/program/src/data/plaintext/from_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl<N: Network> Plaintext<N> {
// Array
else if variant == PlaintextType::<N>::ARRAY_PREFIX_BITS {
let num_elements = u32::from_bits_le(next_bits(32)?)?;
if num_elements as usize > N::MAX_ARRAY_ELEMENTS {
if num_elements as usize > N::LATEST_MAX_ARRAY_ELEMENTS() {
bail!("Array exceeds maximum of elements.");
}

Expand Down Expand Up @@ -176,7 +176,7 @@ impl<N: Network> Plaintext<N> {
// Array
else if variant == PlaintextType::<N>::ARRAY_PREFIX_BITS {
let num_elements = u32::from_bits_be(next_bits(32)?)?;
if num_elements as usize > N::MAX_ARRAY_ELEMENTS {
if num_elements as usize > N::LATEST_MAX_ARRAY_ELEMENTS() {
bail!("Array exceeds maximum of elements.");
}

Expand Down
34 changes: 34 additions & 0 deletions console/program/src/data/plaintext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,40 @@ impl<N: Network> Plaintext<N> {
_ => bail!("Expected a bit array, found a non-array plaintext."),
}
}

/// Returns the `Plaintext` as a `Vec<u8>`, if it is a u8 array.
pub fn as_byte_array(&self) -> Result<Vec<u8>> {
match self {
Self::Array(elements, _) => {
let mut bytes = Vec::with_capacity(elements.len());
for element in elements {
match element {
Self::Literal(Literal::U8(byte), _) => bytes.push(**byte),
_ => bail!("Expected a u8 array, found a non-u8 element."),
}
}
Ok(bytes)
}
_ => bail!("Expected a u8 array, found a non-array plaintext."),
}
}

/// Returns the `Plaintext` as a `Vec<N::Field>`, if it is a field array.
pub fn as_field_array(&self) -> Result<Vec<Field<N>>> {
match self {
Self::Array(elements, _) => {
let mut fields = Vec::with_capacity(elements.len());
for element in elements {
match element {
Self::Literal(Literal::Field(field), _) => fields.push(*field),
_ => bail!("Expected an array of fields, found a non-field element."),
}
}
Ok(fields)
}
_ => bail!("Expected an array of fields, found a non-array plaintext."),
}
}
}

impl<N: Network> From<Literal<N>> for Plaintext<N> {
Expand Down
2 changes: 1 addition & 1 deletion console/program/src/data/record/entry/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ impl<N: Network> Parser for Entry<N, Plaintext<N>> {
false => return Err(error("Members of an array have different visibilities")),
};
// Ensure the number of array elements is within the maximum limit.
match members.len() <= N::MAX_ARRAY_ELEMENTS {
match members.len() <= N::LATEST_MAX_ARRAY_ELEMENTS() {
// Return the members and the visibility.
true => Ok((members.into_iter().map(|(p, _)| p).collect::<Vec<_>>(), mode)),
false => Err(error(format!("Found an array that exceeds size ({})", members.len()))),
Expand Down
4 changes: 2 additions & 2 deletions console/program/src/data_types/array_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ impl<N: Network> ArrayType<N> {
for length in &dimensions {
ensure!(**length as usize >= N::MIN_ARRAY_ELEMENTS, "An array must have {} element", N::MIN_ARRAY_ELEMENTS);
ensure!(
**length as usize <= N::MAX_ARRAY_ELEMENTS,
**length as usize <= N::LATEST_MAX_ARRAY_ELEMENTS(),
"An array can contain {} elements",
N::MAX_ARRAY_ELEMENTS
N::LATEST_MAX_ARRAY_ELEMENTS()
);
}
// Construct the array type.
Expand Down
30 changes: 18 additions & 12 deletions console/program/src/data_types/finalize_type/size_in_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<N: Network> FinalizeType<N> {

/// A helper function to determine the number of bits of a plaintext type, while tracking the depth of the data.
/// Note. The plaintext variant is assumed to be an argument of a `Future` and thus does not have a "raw" serialization.
fn size_in_bits_internal<F0, F1, F2>(
pub fn size_in_bits_internal<F0, F1, F2>(
&self,
get_struct: &F0,
get_external_struct: &F1,
Expand Down Expand Up @@ -89,26 +89,32 @@ impl<N: Network> FinalizeType<N> {
// Account for the argument variant bit.
size = size.checked_add(1).ok_or(anyhow!("`size_in_bits` overflowed"))?;

// Account for the size of the argument bits.
size = size.checked_add(16).ok_or(anyhow!("`size_in_bits` overflowed"))?;
// Calculate argument bits size.
let argument_size_in_bits =
argument.size_in_bits_internal(get_struct, get_external_struct, get_future, depth + 1)?;

// Account for the size of the argument bits
match argument_size_in_bits <= u16::MAX as usize {
true => {
// Account for the size of the argument bits (u16).
size = size.checked_add(16).ok_or(anyhow!("`size_in_bits` overflowed"))?;
}
false => {
// Account for the size of the argument bits (u32).
size = size.checked_add(32).ok_or(anyhow!("`size_in_bits` overflowed"))?;
}
}

// Account for the argument bits.
size = size
.checked_add(argument.size_in_bits_internal(
get_struct,
get_external_struct,
get_future,
depth + 1,
)?)
.ok_or(anyhow!("`size_in_bits` overflowed"))?;
size = size.checked_add(argument_size_in_bits).ok_or(anyhow!("`size_in_bits` overflowed"))?;
}

Ok(size)
}
}
}

/// Returns the number of raw bits of a finlaize type.
/// Returns the number of raw bits of a finalize type.
pub fn future_size_in_bits_raw<F0, F1, F2>(
locator: &Locator<N>,
get_struct: &F0,
Expand Down
6 changes: 0 additions & 6 deletions parameters/src/mainnet/resources/genesis.metadata

This file was deleted.

Loading