Skip to content

Commit f6e5a6b

Browse files
author
Mohammad Fawaz
committed
ternarys on arrays and structs
1 parent 6a2b98b commit f6e5a6b

18 files changed

Lines changed: 2600 additions & 29 deletions

File tree

circuit/program/src/data/literal/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod cast_lossy;
2121
mod equal;
2222
mod from_bits;
2323
mod size_in_bits;
24+
mod ternary;
2425
mod to_bits;
2526
mod to_fields;
2627
mod to_type;
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright (c) 2019-2026 Provable Inc.
2+
// This file is part of the snarkVM library.
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at:
7+
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
use super::*;
17+
18+
impl<A: Aleo> Ternary for Literal<A> {
19+
type Boolean = Boolean<A>;
20+
type Output = Self;
21+
22+
/// Returns `first` if `condition` is `true`, otherwise returns `second`.
23+
/// The `first` and `second` literals must be the same variant, and must be a variant for which
24+
/// ternary selection is supported. Callers are expected to enforce this via type-checking
25+
/// before invocation; mismatched or unsupported variants are treated as unreachable.
26+
fn ternary(condition: &<Self as Ternary>::Boolean, first: &Self, second: &Self) -> <Self as Ternary>::Output {
27+
match (first, second) {
28+
(Self::Address(a), Self::Address(b)) => Self::Address(Address::ternary(condition, a, b)),
29+
(Self::Boolean(a), Self::Boolean(b)) => Self::Boolean(Boolean::ternary(condition, a, b)),
30+
(Self::Field(a), Self::Field(b)) => Self::Field(Field::ternary(condition, a, b)),
31+
(Self::Group(a), Self::Group(b)) => Self::Group(Group::ternary(condition, a, b)),
32+
(Self::I8(a), Self::I8(b)) => Self::I8(I8::ternary(condition, a, b)),
33+
(Self::I16(a), Self::I16(b)) => Self::I16(I16::ternary(condition, a, b)),
34+
(Self::I32(a), Self::I32(b)) => Self::I32(I32::ternary(condition, a, b)),
35+
(Self::I64(a), Self::I64(b)) => Self::I64(I64::ternary(condition, a, b)),
36+
(Self::I128(a), Self::I128(b)) => Self::I128(I128::ternary(condition, a, b)),
37+
(Self::U8(a), Self::U8(b)) => Self::U8(U8::ternary(condition, a, b)),
38+
(Self::U16(a), Self::U16(b)) => Self::U16(U16::ternary(condition, a, b)),
39+
(Self::U32(a), Self::U32(b)) => Self::U32(U32::ternary(condition, a, b)),
40+
(Self::U64(a), Self::U64(b)) => Self::U64(U64::ternary(condition, a, b)),
41+
(Self::U128(a), Self::U128(b)) => Self::U128(U128::ternary(condition, a, b)),
42+
(Self::Scalar(a), Self::Scalar(b)) => Self::Scalar(Scalar::ternary(condition, a, b)),
43+
(Self::Signature(a), Self::Signature(b)) => Self::Signature(Ternary::ternary(condition, a, b)),
44+
(Self::Identifier(a), Self::Identifier(b)) => Self::Identifier(Ternary::ternary(condition, a, b)),
45+
_ => unreachable!("ternary operands must be the same literal variant after type-checking"),
46+
}
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use super::*;
53+
use crate::Circuit;
54+
use snarkvm_utilities::{TestRng, Uniform};
55+
56+
fn check_dispatch(first: Literal<Circuit>, second: Literal<Circuit>) {
57+
let true_ = Boolean::<Circuit>::new(Mode::Private, true);
58+
let false_ = Boolean::<Circuit>::new(Mode::Private, false);
59+
assert_eq!(first.eject_value(), Literal::ternary(&true_, &first, &second).eject_value());
60+
assert_eq!(second.eject_value(), Literal::ternary(&false_, &first, &second).eject_value());
61+
}
62+
63+
#[test]
64+
fn test_literal_ternary_dispatches_all_supported_variants() {
65+
let mut rng = TestRng::default();
66+
67+
check_dispatch(
68+
Literal::Address(Address::new(Mode::Private, Uniform::rand(&mut rng))),
69+
Literal::Address(Address::new(Mode::Private, Uniform::rand(&mut rng))),
70+
);
71+
check_dispatch(
72+
Literal::Boolean(Boolean::new(Mode::Private, true)),
73+
Literal::Boolean(Boolean::new(Mode::Private, false)),
74+
);
75+
check_dispatch(
76+
Literal::Field(Field::new(Mode::Private, Uniform::rand(&mut rng))),
77+
Literal::Field(Field::new(Mode::Private, Uniform::rand(&mut rng))),
78+
);
79+
check_dispatch(
80+
Literal::Group(Group::new(Mode::Private, Uniform::rand(&mut rng))),
81+
Literal::Group(Group::new(Mode::Private, Uniform::rand(&mut rng))),
82+
);
83+
check_dispatch(
84+
Literal::I8(I8::new(Mode::Private, Uniform::rand(&mut rng))),
85+
Literal::I8(I8::new(Mode::Private, Uniform::rand(&mut rng))),
86+
);
87+
check_dispatch(
88+
Literal::I16(I16::new(Mode::Private, Uniform::rand(&mut rng))),
89+
Literal::I16(I16::new(Mode::Private, Uniform::rand(&mut rng))),
90+
);
91+
check_dispatch(
92+
Literal::I32(I32::new(Mode::Private, Uniform::rand(&mut rng))),
93+
Literal::I32(I32::new(Mode::Private, Uniform::rand(&mut rng))),
94+
);
95+
check_dispatch(
96+
Literal::I64(I64::new(Mode::Private, Uniform::rand(&mut rng))),
97+
Literal::I64(I64::new(Mode::Private, Uniform::rand(&mut rng))),
98+
);
99+
check_dispatch(
100+
Literal::I128(I128::new(Mode::Private, Uniform::rand(&mut rng))),
101+
Literal::I128(I128::new(Mode::Private, Uniform::rand(&mut rng))),
102+
);
103+
check_dispatch(
104+
Literal::U8(U8::new(Mode::Private, Uniform::rand(&mut rng))),
105+
Literal::U8(U8::new(Mode::Private, Uniform::rand(&mut rng))),
106+
);
107+
check_dispatch(
108+
Literal::U16(U16::new(Mode::Private, Uniform::rand(&mut rng))),
109+
Literal::U16(U16::new(Mode::Private, Uniform::rand(&mut rng))),
110+
);
111+
check_dispatch(
112+
Literal::U32(U32::new(Mode::Private, Uniform::rand(&mut rng))),
113+
Literal::U32(U32::new(Mode::Private, Uniform::rand(&mut rng))),
114+
);
115+
check_dispatch(
116+
Literal::U64(U64::new(Mode::Private, Uniform::rand(&mut rng))),
117+
Literal::U64(U64::new(Mode::Private, Uniform::rand(&mut rng))),
118+
);
119+
check_dispatch(
120+
Literal::U128(U128::new(Mode::Private, Uniform::rand(&mut rng))),
121+
Literal::U128(U128::new(Mode::Private, Uniform::rand(&mut rng))),
122+
);
123+
check_dispatch(
124+
Literal::Scalar(Scalar::new(Mode::Private, Uniform::rand(&mut rng))),
125+
Literal::Scalar(Scalar::new(Mode::Private, Uniform::rand(&mut rng))),
126+
);
127+
check_dispatch(
128+
Literal::Identifier(Box::new(IdentifierLiteral::new(
129+
Mode::Private,
130+
console::IdentifierLiteral::rand(&mut rng),
131+
))),
132+
Literal::Identifier(Box::new(IdentifierLiteral::new(
133+
Mode::Private,
134+
console::IdentifierLiteral::rand(&mut rng),
135+
))),
136+
);
137+
}
138+
139+
#[test]
140+
#[should_panic(expected = "ternary operands must be the same literal variant")]
141+
fn test_literal_ternary_variant_mismatch_panics() {
142+
let mut rng = TestRng::default();
143+
let first = Literal::<Circuit>::Field(Field::new(Mode::Private, Uniform::rand(&mut rng)));
144+
let second = Literal::<Circuit>::Boolean(Boolean::new(Mode::Private, true));
145+
let cond = Boolean::<Circuit>::new(Mode::Private, false);
146+
let _ = Literal::ternary(&cond, &first, &second);
147+
}
148+
}

circuit/program/src/data/plaintext/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod from_bits;
2525
mod from_fields;
2626
mod num_randomizers;
2727
mod size_in_fields;
28+
mod ternary;
2829
mod to_bits;
2930
mod to_bits_raw;
3031
mod to_fields;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright (c) 2019-2026 Provable Inc.
2+
// This file is part of the snarkVM library.
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at:
7+
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
use super::*;
17+
18+
impl<A: Aleo> Ternary for Plaintext<A> {
19+
type Boolean = Boolean<A>;
20+
type Output = Self;
21+
22+
/// Returns `first` if `condition` is `true`, otherwise returns `second`.
23+
/// The `first` and `second` plaintexts must have the same shape: same variant, arrays of equal
24+
/// length with matching element shapes, or structs with matching keys in the same order.
25+
/// Callers are expected to enforce this via type-checking before invocation; mismatched shapes
26+
/// are treated as unreachable.
27+
fn ternary(condition: &<Self as Ternary>::Boolean, first: &Self, second: &Self) -> <Self as Ternary>::Output {
28+
match (first, second) {
29+
(Self::Literal(a, _), Self::Literal(b, _)) => {
30+
Self::Literal(Literal::ternary(condition, a, b), OnceCell::new())
31+
}
32+
(Self::Array(a, _), Self::Array(b, _)) if a.len() == b.len() => {
33+
let elements = a.iter().zip_eq(b.iter()).map(|(x, y)| Plaintext::ternary(condition, x, y)).collect();
34+
Self::Array(elements, OnceCell::new())
35+
}
36+
(Self::Struct(a, _), Self::Struct(b, _))
37+
if a.len() == b.len() && a.keys().zip(b.keys()).all(|(ka, kb)| ka == kb) =>
38+
{
39+
let fields = a
40+
.iter()
41+
.zip_eq(b.iter())
42+
.map(|((key, x), (_, y))| (key.clone(), Plaintext::ternary(condition, x, y)))
43+
.collect();
44+
Self::Struct(fields, OnceCell::new())
45+
}
46+
_ => unreachable!("ternary operands must have equivalent shape after type-checking"),
47+
}
48+
}
49+
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
use super::*;
54+
use crate::Circuit;
55+
56+
fn sample(mode: Mode, literal: &str) -> Plaintext<Circuit> {
57+
let primitive = console::Plaintext::<<Circuit as Environment>::Network>::from_str(literal).unwrap();
58+
Plaintext::new(mode, primitive)
59+
}
60+
61+
fn check_both_branches(first: &Plaintext<Circuit>, second: &Plaintext<Circuit>) {
62+
let true_ = Boolean::<Circuit>::new(Mode::Private, true);
63+
let false_ = Boolean::<Circuit>::new(Mode::Private, false);
64+
assert_eq!(first.eject_value(), Plaintext::ternary(&true_, first, second).eject_value());
65+
assert_eq!(second.eject_value(), Plaintext::ternary(&false_, first, second).eject_value());
66+
}
67+
68+
#[test]
69+
fn test_plaintext_ternary_literal() {
70+
let first = sample(Mode::Private, "1field");
71+
let second = sample(Mode::Private, "2field");
72+
check_both_branches(&first, &second);
73+
}
74+
75+
#[test]
76+
fn test_plaintext_ternary_flat_array() {
77+
let first = sample(Mode::Private, "[ 1field, 2field, 3field ]");
78+
let second = sample(Mode::Private, "[ 4field, 5field, 6field ]");
79+
check_both_branches(&first, &second);
80+
}
81+
82+
#[test]
83+
fn test_plaintext_ternary_nested_array() {
84+
let first = sample(Mode::Private, "[ [ 1u8, 2u8 ], [ 3u8, 4u8 ] ]");
85+
let second = sample(Mode::Private, "[ [ 5u8, 6u8 ], [ 7u8, 8u8 ] ]");
86+
check_both_branches(&first, &second);
87+
}
88+
89+
#[test]
90+
fn test_plaintext_ternary_struct() {
91+
let first = sample(Mode::Private, "{ x: 1field, y: 2field }");
92+
let second = sample(Mode::Private, "{ x: 3field, y: 4field }");
93+
check_both_branches(&first, &second);
94+
}
95+
96+
#[test]
97+
fn test_plaintext_ternary_struct_of_arrays() {
98+
let first = sample(Mode::Private, "{ a: [ 1u8, 2u8 ], b: 3field }");
99+
let second = sample(Mode::Private, "{ a: [ 4u8, 5u8 ], b: 6field }");
100+
check_both_branches(&first, &second);
101+
}
102+
103+
#[test]
104+
fn test_plaintext_ternary_array_of_structs() {
105+
let first = sample(Mode::Private, "[ { x: 1field, y: 2field }, { x: 3field, y: 4field } ]");
106+
let second = sample(Mode::Private, "[ { x: 5field, y: 6field }, { x: 7field, y: 8field } ]");
107+
check_both_branches(&first, &second);
108+
}
109+
110+
#[test]
111+
#[should_panic(expected = "ternary operands must have equivalent shape")]
112+
fn test_plaintext_ternary_array_length_mismatch_panics() {
113+
let first = sample(Mode::Private, "[ 1field, 2field ]");
114+
let second = sample(Mode::Private, "[ 3field, 4field, 5field ]");
115+
let cond = Boolean::<Circuit>::new(Mode::Private, false);
116+
let _ = Plaintext::ternary(&cond, &first, &second);
117+
}
118+
119+
#[test]
120+
#[should_panic(expected = "ternary operands must have equivalent shape")]
121+
fn test_plaintext_ternary_struct_key_order_mismatch_panics() {
122+
let first = sample(Mode::Private, "{ x: 1field, y: 2field }");
123+
let second = sample(Mode::Private, "{ y: 3field, x: 4field }");
124+
let cond = Boolean::<Circuit>::new(Mode::Private, false);
125+
let _ = Plaintext::ternary(&cond, &first, &second);
126+
}
127+
128+
#[test]
129+
#[should_panic(expected = "ternary operands must have equivalent shape")]
130+
fn test_plaintext_ternary_variant_mismatch_panics() {
131+
let first = sample(Mode::Private, "1field");
132+
let second = sample(Mode::Private, "[ 1field ]");
133+
let cond = Boolean::<Circuit>::new(Mode::Private, false);
134+
let _ = Plaintext::ternary(&cond, &first, &second);
135+
}
136+
}

circuit/types/string/src/identifier_literal/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
mod equal;
1717
mod helpers;
18+
mod ternary;
1819

1920
use snarkvm_circuit_environment::prelude::*;
2021
use snarkvm_circuit_types_boolean::Boolean;

0 commit comments

Comments
 (0)