Skip to content

Commit 60522fa

Browse files
authored
Merge pull request #3201 from ProvableHQ/feat/raw-commitments
[Feat] Introduce `commit.*.raw` instructions
2 parents 0a43230 + 859d227 commit 60522fa

File tree

10 files changed

+491
-15
lines changed

10 files changed

+491
-15
lines changed

console/network/src/consensus_heights.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub enum ConsensusVersion {
5454
/// Introduces `aleo::GENERATOR`, `aleo::GENERATOR_POWERS`, `snark.verify` opcodes,
5555
/// and dynamic dispatch, and identifier literal types.
5656
V14 = 14,
57-
/// V15: Introduces the record-existence check.
57+
/// V15: Introduces the record-existence check and `commit.*.raw` instruction variants.
5858
V15 = 15,
5959
}
6060

synthesizer/process/src/cost.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,24 @@ pub fn cost_per_command<N: Network>(
542542
Command::Instruction(Instruction::CommitPED128(commit)) => {
543543
cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
544544
}
545+
Command::Instruction(Instruction::CommitBHP256Raw(commit)) => {
546+
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
547+
}
548+
Command::Instruction(Instruction::CommitBHP512Raw(commit)) => {
549+
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
550+
}
551+
Command::Instruction(Instruction::CommitBHP768Raw(commit)) => {
552+
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
553+
}
554+
Command::Instruction(Instruction::CommitBHP1024Raw(commit)) => {
555+
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
556+
}
557+
Command::Instruction(Instruction::CommitPED64Raw(commit)) => {
558+
cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
559+
}
560+
Command::Instruction(Instruction::CommitPED128Raw(commit)) => {
561+
cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
562+
}
545563
Command::Instruction(Instruction::DeserializeBits(deserialize)) => {
546564
Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(deserialize.operand_type().clone()))?
547565
.saturating_mul(CAST_PER_BYTE_COST)

synthesizer/process/src/stack/register_types/initialize.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,30 @@ impl<N: Network> RegisterTypes<N> {
686686
matches!(instruction, Instruction::CommitPED128(..)),
687687
"Instruction '{instruction}' is not for opcode '{opcode}'."
688688
),
689+
"commit.bhp256.raw" => ensure!(
690+
matches!(instruction, Instruction::CommitBHP256Raw(..)),
691+
"Instruction '{instruction}' is not for opcode '{opcode}'."
692+
),
693+
"commit.bhp512.raw" => ensure!(
694+
matches!(instruction, Instruction::CommitBHP512Raw(..)),
695+
"Instruction '{instruction}' is not for opcode '{opcode}'."
696+
),
697+
"commit.bhp768.raw" => ensure!(
698+
matches!(instruction, Instruction::CommitBHP768Raw(..)),
699+
"Instruction '{instruction}' is not for opcode '{opcode}'."
700+
),
701+
"commit.bhp1024.raw" => ensure!(
702+
matches!(instruction, Instruction::CommitBHP1024Raw(..)),
703+
"Instruction '{instruction}' is not for opcode '{opcode}'."
704+
),
705+
"commit.ped64.raw" => ensure!(
706+
matches!(instruction, Instruction::CommitPED64Raw(..)),
707+
"Instruction '{instruction}' is not for opcode '{opcode}'."
708+
),
709+
"commit.ped128.raw" => ensure!(
710+
matches!(instruction, Instruction::CommitPED128Raw(..)),
711+
"Instruction '{instruction}' is not for opcode '{opcode}'."
712+
),
689713
_ => bail!("Instruction '{instruction}' is not for opcode '{opcode}'."),
690714
}
691715
Ok(())

synthesizer/program/src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,36 @@ impl<N: Network> ProgramCore<N> {
13231323
Ok(false)
13241324
}
13251325

1326+
/// Returns `true` if a program contains any V15 syntax.
1327+
/// This includes:
1328+
/// 1. `commit.*.raw` opcodes (raw commit variants).
1329+
///
1330+
/// This is enforced to be `false` for programs before `ConsensusVersion::V15`.
1331+
#[inline]
1332+
pub fn contains_v15_syntax(&self) -> bool {
1333+
// Helper to check if an opcode is a raw commit variant.
1334+
let has_op = |opcode: &str| opcode.starts_with("commit.") && opcode.ends_with(".raw");
1335+
1336+
// Determine if any function instructions contain the new syntax.
1337+
let function_contains = cfg_iter!(self.functions())
1338+
.flat_map(|(_, function)| function.instructions())
1339+
.any(|instruction| has_op(*instruction.opcode()));
1340+
1341+
// Determine if any closure instructions contain the new syntax.
1342+
let closure_contains = cfg_iter!(self.closures())
1343+
.flat_map(|(_, closure)| closure.instructions())
1344+
.any(|instruction| has_op(*instruction.opcode()));
1345+
1346+
// Determine if any finalize commands or constructor commands contain the new syntax.
1347+
let command_contains = cfg_iter!(self.functions())
1348+
.flat_map(|(_, function)| function.finalize_logic().map(|finalize| finalize.commands()))
1349+
.flatten()
1350+
.chain(cfg_iter!(self.constructor).flat_map(|constructor| constructor.commands()))
1351+
.any(|command| matches!(command, Command::Instruction(instruction) if has_op(*instruction.opcode())));
1352+
1353+
function_contains || closure_contains || command_contains
1354+
}
1355+
13261356
/// Returns `true` if a program contains any string type.
13271357
/// Before ConsensusVersion::V12, variable-length string sampling when using them as inputs caused deployment synthesis to be inconsistent and abort with probability 63/64.
13281358
/// After ConsensusVersion::V12, string types are disallowed.

synthesizer/program/src/logic/instruction/mod.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ pub enum Instruction<N: Network> {
9393
CommitPED64(CommitPED64<N>),
9494
/// Performs a Pedersen commitment on up to a 128-bit input.
9595
CommitPED128(CommitPED128<N>),
96+
/// Performs a BHP commitment on the input's raw bits in 256-bit chunks.
97+
CommitBHP256Raw(CommitBHP256Raw<N>),
98+
/// Performs a BHP commitment on the input's raw bits in 512-bit chunks.
99+
CommitBHP512Raw(CommitBHP512Raw<N>),
100+
/// Performs a BHP commitment on the input's raw bits in 768-bit chunks.
101+
CommitBHP768Raw(CommitBHP768Raw<N>),
102+
/// Performs a BHP commitment on the input's raw bits in 1024-bit chunks.
103+
CommitBHP1024Raw(CommitBHP1024Raw<N>),
104+
/// Performs a Pedersen commitment on the input's raw bits up to a 64-bit input.
105+
CommitPED64Raw(CommitPED64Raw<N>),
106+
/// Performs a Pedersen commitment on the input's raw bits up to a 128-bit input.
107+
CommitPED128Raw(CommitPED128Raw<N>),
96108
/// Deserializes the bits into a value.
97109
DeserializeBits(DeserializeBits<N>),
98110
/// Deserializes the raw bits into a value.
@@ -462,6 +474,14 @@ macro_rules! instruction {
462474
SnarkVerify,
463475
SnarkVerifyBatch,
464476

477+
// New opcodes added in `ConsensusVersion::V15`
478+
CommitBHP256Raw,
479+
CommitBHP512Raw,
480+
CommitBHP768Raw,
481+
CommitBHP1024Raw,
482+
CommitPED64Raw,
483+
CommitPED128Raw,
484+
465485
// New opcodes should be added here, with a comment on which consensus version they were added in.
466486
}}
467487
};
@@ -685,7 +705,7 @@ mod tests {
685705
// Sanity check the number of instructions is unchanged.
686706
// Note that the number of opcodes **MUST NOT** exceed u16::MAX.
687707
assert_eq!(
688-
123,
708+
129,
689709
Instruction::<CurrentNetwork>::OPCODES.len(),
690710
"Update me if the number of instructions changes."
691711
);

synthesizer/program/src/logic/instruction/operation/commit.rs

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,35 @@ pub type CommitPED64<N> = CommitInstruction<N, { CommitVariant::CommitPED64 as u
3333
/// Pedersen128 is a collision-resistant function that processes inputs in 128-bit chunks.
3434
pub type CommitPED128<N> = CommitInstruction<N, { CommitVariant::CommitPED128 as u8 }>;
3535

36+
/// BHP256 commit over raw bits (no type-tagged serialization).
37+
pub type CommitBHP256Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP256Raw as u8 }>;
38+
/// BHP512 commit over raw bits.
39+
pub type CommitBHP512Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP512Raw as u8 }>;
40+
/// BHP768 commit over raw bits.
41+
pub type CommitBHP768Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP768Raw as u8 }>;
42+
/// BHP1024 commit over raw bits.
43+
pub type CommitBHP1024Raw<N> = CommitInstruction<N, { CommitVariant::CommitBHP1024Raw as u8 }>;
44+
/// Pedersen64 commit over raw bits.
45+
pub type CommitPED64Raw<N> = CommitInstruction<N, { CommitVariant::CommitPED64Raw as u8 }>;
46+
/// Pedersen128 commit over raw bits.
47+
pub type CommitPED128Raw<N> = CommitInstruction<N, { CommitVariant::CommitPED128Raw as u8 }>;
48+
3649
/// Which commit function to use.
37-
#[derive(Debug, Clone, Eq, PartialEq)]
50+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
3851
pub enum CommitVariant {
3952
CommitBHP256,
4053
CommitBHP512,
4154
CommitBHP768,
4255
CommitBHP1024,
4356
CommitPED64,
4457
CommitPED128,
58+
// The variants that commit over raw inputs.
59+
CommitBHP256Raw,
60+
CommitBHP512Raw,
61+
CommitBHP768Raw,
62+
CommitBHP1024Raw,
63+
CommitPED64Raw,
64+
CommitPED128Raw,
4565
}
4666

4767
impl CommitVariant {
@@ -54,9 +74,21 @@ impl CommitVariant {
5474
3 => "commit.bhp1024",
5575
4 => "commit.ped64",
5676
5 => "commit.ped128",
57-
6.. => panic!("Invalid 'commit' instruction opcode"),
77+
// The variants that commit over raw inputs.
78+
6 => "commit.bhp256.raw",
79+
7 => "commit.bhp512.raw",
80+
8 => "commit.bhp768.raw",
81+
9 => "commit.bhp1024.raw",
82+
10 => "commit.ped64.raw",
83+
11 => "commit.ped128.raw",
84+
12.. => panic!("Invalid 'commit' instruction opcode"),
5885
}
5986
}
87+
88+
// Returns `true` if the variant commits over raw bits.
89+
pub const fn is_raw(variant: u8) -> bool {
90+
matches!(variant, 6..=11)
91+
}
6092
}
6193

6294
/// Returns 'true' if the destination type is valid.
@@ -129,16 +161,21 @@ impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
129161
macro_rules! do_commit {
130162
($N: ident, $variant: expr, $destination_type: expr, $input: expr, $randomizer: expr, $ty: ty, $q: expr) => {{
131163
let func = match $variant {
132-
0 => $N::commit_to_group_bhp256,
133-
1 => $N::commit_to_group_bhp512,
134-
2 => $N::commit_to_group_bhp768,
135-
3 => $N::commit_to_group_bhp1024,
136-
4 => $N::commit_to_group_ped64,
137-
5 => $N::commit_to_group_ped128,
138-
6.. => bail!("Invalid 'commit' variant: {}", $variant),
164+
0 | 6 => $N::commit_to_group_bhp256,
165+
1 | 7 => $N::commit_to_group_bhp512,
166+
2 | 8 => $N::commit_to_group_bhp768,
167+
3 | 9 => $N::commit_to_group_bhp1024,
168+
4 | 10 => $N::commit_to_group_ped64,
169+
5 | 11 => $N::commit_to_group_ped128,
170+
12.. => bail!("Invalid 'commit' variant: {}", $variant),
139171
};
140172

141-
let literal_output: $ty = $q(func(&$input.to_bits_le(), $randomizer))?.into();
173+
let bits = match CommitVariant::is_raw($variant) {
174+
true => $input.to_bits_raw_le(),
175+
false => $input.to_bits_le(),
176+
};
177+
178+
let literal_output: $ty = $q(func(&bits, $randomizer))?.into();
142179
literal_output.cast_lossy($destination_type)?
143180
}};
144181
}
@@ -196,7 +233,7 @@ impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
196233
stack: &impl StackTrait<N>,
197234
registers: &mut impl RegistersCircuit<N, A>,
198235
) -> Result<()> {
199-
use circuit::traits::ToBits;
236+
use circuit::traits::{ToBits, ToBitsRaw};
200237

201238
// Ensure the number of operands is correct.
202239
if self.operands.len() != 2 {
@@ -251,8 +288,8 @@ impl<N: Network, const VARIANT: u8> CommitInstruction<N, VARIANT> {
251288
// TODO (howardwu): If the operation is Pedersen, check that it is within the number of bits.
252289

253290
match VARIANT {
254-
0..=5 => Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(self.destination_type))]),
255-
6.. => bail!("Invalid 'commit' variant: {VARIANT}"),
291+
0..=11 => Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(self.destination_type))]),
292+
12.. => bail!("Invalid 'commit' variant: {VARIANT}"),
256293
}
257294
}
258295
}
@@ -397,4 +434,46 @@ mod tests {
397434
assert_eq!(commit.destination_type, *destination_type, "The destination type is incorrect");
398435
}
399436
}
437+
438+
#[test]
439+
fn test_parse_raw() {
440+
for destination_type in valid_destination_types() {
441+
let instruction = format!("commit.bhp256.raw r0 r1 into r2 as {destination_type}");
442+
let (string, commit) = CommitBHP256Raw::<CurrentNetwork>::parse(&instruction).unwrap();
443+
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
444+
assert_eq!(commit.operands.len(), 2, "The number of operands is incorrect");
445+
assert_eq!(commit.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
446+
assert_eq!(commit.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
447+
assert_eq!(commit.destination, Register::Locator(2), "The destination register is incorrect");
448+
assert_eq!(commit.destination_type, *destination_type, "The destination type is incorrect");
449+
}
450+
}
451+
452+
#[test]
453+
fn test_raw_differs_from_standard() {
454+
use console::{
455+
program::{Literal, Plaintext, Value},
456+
types::{Field, Scalar},
457+
};
458+
459+
type N = CurrentNetwork;
460+
461+
// Use a non-trivial field literal (not zero) so the bits actually differ between
462+
// to_bits_le (type-tagged) and to_bits_raw_le (untagged).
463+
let input_field = Field::<N>::one();
464+
let randomizer = Scalar::<N>::one();
465+
let value = Value::Plaintext(Plaintext::from(Literal::Field(input_field)));
466+
467+
let standard = evaluate_commit(CommitVariant::CommitBHP256, &value, &randomizer, LiteralType::Field).unwrap();
468+
let raw = evaluate_commit(CommitVariant::CommitBHP256Raw, &value, &randomizer, LiteralType::Field).unwrap();
469+
470+
assert_ne!(
471+
standard, raw,
472+
"commit.bhp256 and commit.bhp256.raw must produce different outputs for the same input"
473+
);
474+
475+
// Check that committing on the field element is equivalent to the raw commit via the instruction.
476+
let expected_commitment = N::commit_bhp256(&input_field.to_bits_le(), &randomizer).unwrap();
477+
assert_eq!(Literal::Field(expected_commitment), raw);
478+
}
400479
}

0 commit comments

Comments
 (0)