Skip to content

Commit 75d6e85

Browse files
authored
feat: Add sha512 precompiles (#179)
* feat: Add simple 64-bit versions of SHA512 core operations These versions are essentially just copies of their 32-bit counterparts, made to work with a Word64 type. It's not as nice as a fully generic version * feat: Working SHA512 extend single syscall Also add Xor64 operation, fix typo in Add64, add simple test * feat: Working SHA512 compress single syscall * feat: Add sha512 test with patched sha2 crate * fix: Clippy * fix: Bump `SPHINX_CIRCUIT_VERSION` * chore: Rename `SP1_COMMIT` in the plonk artifacts * chore: Remove unused `Word64::reduce` function Not very useful considering it will almost always overflow. Can be added back if necessary. * chore: Update comments * chore: Remove `FIXME`s, add utils/uint.rs --------- Co-authored-by: wwared <[email protected]>
1 parent 3103411 commit 75d6e85

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4571
-8
lines changed

Diff for: core/src/air/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod polynomial;
66
mod public_values;
77
mod sub_builder;
88
mod word;
9+
mod word_64;
910

1011
pub use builder::*;
1112
pub use extension::*;
@@ -15,3 +16,4 @@ pub use polynomial::*;
1516
pub use public_values::*;
1617
pub use sub_builder::*;
1718
pub use word::*;
19+
pub use word_64::*;

Diff for: core/src/air/word_64.rs

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use core::fmt::Debug;
2+
use std::{
3+
array::IntoIter,
4+
ops::{Index, IndexMut},
5+
};
6+
7+
use p3_field::{AbstractField, Field};
8+
use serde::{Deserialize, Serialize};
9+
use sphinx_derive::AlignedBorrow;
10+
11+
use super::BaseAirBuilder;
12+
use crate::air::Word;
13+
14+
/// The size of a word64 in bytes.
15+
pub const WORD64_SIZE: usize = 8;
16+
17+
/// A double word is a 64-bit value represented in an AIR.
18+
#[derive(
19+
AlignedBorrow, Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize,
20+
)]
21+
#[repr(C)]
22+
pub struct Word64<T>(pub [T; WORD64_SIZE]);
23+
24+
impl<T> Word64<T> {
25+
/// Applies `f` to each element of the word64.
26+
pub fn map<F, S>(self, f: F) -> Word64<S>
27+
where
28+
F: FnMut(T) -> S,
29+
{
30+
Word64(self.0.map(f))
31+
}
32+
33+
/// Extends a variable to a word64.
34+
pub fn extend_var<AB: BaseAirBuilder<Var = T>>(var: T) -> Word64<AB::Expr> {
35+
Word64([
36+
AB::Expr::zero() + var,
37+
AB::Expr::zero(),
38+
AB::Expr::zero(),
39+
AB::Expr::zero(),
40+
AB::Expr::zero(),
41+
AB::Expr::zero(),
42+
AB::Expr::zero(),
43+
AB::Expr::zero(),
44+
])
45+
}
46+
}
47+
48+
impl<T: Clone> Word64<T> {
49+
/// Splits into two words.
50+
pub fn to_le_words(self) -> [Word<T>; 2] {
51+
let limbs: Vec<T> = self.into_iter().collect();
52+
[
53+
limbs[..4].iter().cloned().collect(),
54+
limbs[4..].iter().cloned().collect(),
55+
]
56+
}
57+
}
58+
59+
impl<T: AbstractField> Word64<T> {
60+
/// Extends a variable to a word64.
61+
pub fn extend_expr<AB: BaseAirBuilder<Expr = T>>(expr: T) -> Word64<AB::Expr> {
62+
Word64([
63+
AB::Expr::zero() + expr,
64+
AB::Expr::zero(),
65+
AB::Expr::zero(),
66+
AB::Expr::zero(),
67+
AB::Expr::zero(),
68+
AB::Expr::zero(),
69+
AB::Expr::zero(),
70+
AB::Expr::zero(),
71+
])
72+
}
73+
74+
/// Returns a word64 with all zero expressions.
75+
pub fn zero<AB: BaseAirBuilder<Expr = T>>() -> Word64<T> {
76+
Word64([
77+
AB::Expr::zero(),
78+
AB::Expr::zero(),
79+
AB::Expr::zero(),
80+
AB::Expr::zero(),
81+
AB::Expr::zero(),
82+
AB::Expr::zero(),
83+
AB::Expr::zero(),
84+
AB::Expr::zero(),
85+
])
86+
}
87+
}
88+
89+
impl<F: Field> Word64<F> {
90+
/// Converts a word64 to a u64.
91+
pub fn to_u64(&self) -> u64 {
92+
// TODO: avoid string conversion
93+
u64::from_le_bytes(self.0.map(|x| x.to_string().parse::<u8>().unwrap()))
94+
}
95+
}
96+
97+
impl<T> Index<usize> for Word64<T> {
98+
type Output = T;
99+
100+
fn index(&self, index: usize) -> &Self::Output {
101+
&self.0[index]
102+
}
103+
}
104+
105+
impl<T> IndexMut<usize> for Word64<T> {
106+
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
107+
&mut self.0[index]
108+
}
109+
}
110+
111+
impl<F: AbstractField> From<u64> for Word64<F> {
112+
fn from(value: u64) -> Self {
113+
Word64(value.to_le_bytes().map(F::from_canonical_u8))
114+
}
115+
}
116+
117+
impl<T> IntoIterator for Word64<T> {
118+
type Item = T;
119+
type IntoIter = IntoIter<T, WORD64_SIZE>;
120+
121+
fn into_iter(self) -> Self::IntoIter {
122+
self.0.into_iter()
123+
}
124+
}
125+
126+
impl<T: Clone> FromIterator<T> for Word64<T> {
127+
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
128+
let mut iter = iter.into_iter();
129+
let elements = std::array::from_fn(|_| iter.next().unwrap());
130+
Word64(elements)
131+
}
132+
}

Diff for: core/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ use stark::StarkGenericConfig;
3030
/// This string should be updated whenever any step in verifying an SP1 proof changes, including
3131
/// core, recursion, and plonk-bn254. This string is used to download SP1 artifacts and the gnark
3232
/// docker image.
33-
pub const SPHINX_CIRCUIT_VERSION: &str = "v1.0.8.1-testnet";
33+
pub const SPHINX_CIRCUIT_VERSION: &str = "v1.0.8.2-testnet";

Diff for: core/src/operations/add_64.rs

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use crate::air::{Word64, WordAirBuilder, WORD64_SIZE};
2+
use crate::bytes::event::ByteRecord;
3+
4+
use p3_air::AirBuilder;
5+
use p3_field::{AbstractField, Field};
6+
use sphinx_derive::AlignedBorrow;
7+
8+
/// A set of columns needed to compute the add of two double words.
9+
#[derive(AlignedBorrow, Default, Debug, Clone, Copy)]
10+
#[repr(C)]
11+
pub struct Add64Operation<T> {
12+
/// The result of `a + b`.
13+
pub value: Word64<T>,
14+
15+
/// Trace.
16+
pub carry: [T; WORD64_SIZE - 1],
17+
}
18+
19+
impl<F: Field> Add64Operation<F> {
20+
pub fn populate(
21+
&mut self,
22+
record: &mut impl ByteRecord,
23+
shard: u32,
24+
channel: u32,
25+
a_u64: u64,
26+
b_u64: u64,
27+
) -> u64 {
28+
let expected = a_u64.wrapping_add(b_u64);
29+
self.value = Word64::from(expected);
30+
let a = a_u64.to_le_bytes();
31+
let b = b_u64.to_le_bytes();
32+
33+
let mut carry = [0u8; WORD64_SIZE - 1];
34+
if u64::from(a[0]) + u64::from(b[0]) > 255 {
35+
carry[0] = 1;
36+
self.carry[0] = F::one();
37+
}
38+
for i in 1..WORD64_SIZE - 1 {
39+
if u64::from(a[i]) + u64::from(b[i]) + u64::from(carry[i - 1]) > 255 {
40+
carry[i] = 1;
41+
self.carry[i] = F::one();
42+
}
43+
}
44+
45+
let base = 256u64;
46+
let overflow = u64::from(
47+
a[0].wrapping_add(b[0])
48+
.wrapping_sub(expected.to_le_bytes()[0]),
49+
);
50+
debug_assert_eq!(overflow.wrapping_mul(overflow.wrapping_sub(base)), 0);
51+
52+
// Range check
53+
{
54+
record.add_u8_range_checks(shard, channel, &a);
55+
record.add_u8_range_checks(shard, channel, &b);
56+
record.add_u8_range_checks(shard, channel, &expected.to_le_bytes());
57+
}
58+
expected
59+
}
60+
61+
pub fn eval<AB: WordAirBuilder<F = F>>(
62+
builder: &mut AB,
63+
a: Word64<AB::Var>,
64+
b: Word64<AB::Var>,
65+
cols: Add64Operation<AB::Var>,
66+
shard: AB::Var,
67+
channel: impl Into<AB::Expr> + Clone,
68+
is_real: AB::Expr,
69+
) {
70+
let one = AB::Expr::one();
71+
let base = AB::F::from_canonical_u32(256);
72+
73+
let mut builder_is_real = builder.when(is_real.clone());
74+
75+
// For each limb, assert that difference between the carried result and the non-carried
76+
// result is either zero or the base.
77+
let overflow_0 = a[0] + b[0] - cols.value[0];
78+
let overflow_1 = a[1] + b[1] - cols.value[1] + cols.carry[0];
79+
let overflow_2 = a[2] + b[2] - cols.value[2] + cols.carry[1];
80+
let overflow_3 = a[3] + b[3] - cols.value[3] + cols.carry[2];
81+
let overflow_4 = a[4] + b[4] - cols.value[4] + cols.carry[3];
82+
let overflow_5 = a[5] + b[5] - cols.value[5] + cols.carry[4];
83+
let overflow_6 = a[6] + b[6] - cols.value[6] + cols.carry[5];
84+
let overflow_7 = a[7] + b[7] - cols.value[7] + cols.carry[6];
85+
builder_is_real.assert_zero(overflow_0.clone() * (overflow_0.clone() - base));
86+
builder_is_real.assert_zero(overflow_1.clone() * (overflow_1.clone() - base));
87+
builder_is_real.assert_zero(overflow_2.clone() * (overflow_2.clone() - base));
88+
builder_is_real.assert_zero(overflow_3.clone() * (overflow_3.clone() - base));
89+
builder_is_real.assert_zero(overflow_4.clone() * (overflow_4.clone() - base));
90+
builder_is_real.assert_zero(overflow_5.clone() * (overflow_5.clone() - base));
91+
builder_is_real.assert_zero(overflow_6.clone() * (overflow_6.clone() - base));
92+
builder_is_real.assert_zero(overflow_7.clone() * (overflow_7.clone() - base));
93+
94+
// If the carry is one, then the overflow must be the base.
95+
builder_is_real.assert_zero(cols.carry[0] * (overflow_0.clone() - base));
96+
builder_is_real.assert_zero(cols.carry[1] * (overflow_1.clone() - base));
97+
builder_is_real.assert_zero(cols.carry[2] * (overflow_2.clone() - base));
98+
builder_is_real.assert_zero(cols.carry[3] * (overflow_3.clone() - base));
99+
builder_is_real.assert_zero(cols.carry[4] * (overflow_4.clone() - base));
100+
builder_is_real.assert_zero(cols.carry[5] * (overflow_5.clone() - base));
101+
builder_is_real.assert_zero(cols.carry[6] * (overflow_6.clone() - base));
102+
103+
// If the carry is not one, then the overflow must be zero.
104+
builder_is_real.assert_zero((cols.carry[0] - one.clone()) * overflow_0.clone());
105+
builder_is_real.assert_zero((cols.carry[1] - one.clone()) * overflow_1.clone());
106+
builder_is_real.assert_zero((cols.carry[2] - one.clone()) * overflow_2.clone());
107+
builder_is_real.assert_zero((cols.carry[3] - one.clone()) * overflow_3.clone());
108+
builder_is_real.assert_zero((cols.carry[4] - one.clone()) * overflow_4.clone());
109+
builder_is_real.assert_zero((cols.carry[5] - one.clone()) * overflow_5.clone());
110+
builder_is_real.assert_zero((cols.carry[6] - one.clone()) * overflow_6.clone());
111+
112+
// Assert that the carry is either zero or one.
113+
builder_is_real.assert_bool(cols.carry[0]);
114+
builder_is_real.assert_bool(cols.carry[1]);
115+
builder_is_real.assert_bool(cols.carry[2]);
116+
builder_is_real.assert_bool(cols.carry[3]);
117+
builder_is_real.assert_bool(cols.carry[4]);
118+
builder_is_real.assert_bool(cols.carry[5]);
119+
builder_is_real.assert_bool(cols.carry[6]);
120+
builder_is_real.assert_bool(is_real.clone());
121+
122+
// Range check each byte.
123+
{
124+
builder.slice_range_check_u8(&a.0, shard, channel.clone(), is_real.clone());
125+
builder.slice_range_check_u8(&b.0, shard, channel.clone(), is_real.clone());
126+
builder.slice_range_check_u8(&cols.value.0, shard, channel, is_real);
127+
}
128+
}
129+
}

Diff for: core/src/operations/and.rs

+63
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use sphinx_derive::AlignedBorrow;
33

44
use crate::air::ByteAirBuilder;
55
use crate::air::Word;
6+
use crate::air::Word64;
7+
use crate::air::WORD64_SIZE;
68
use crate::bytes::event::ByteRecord;
79
use crate::bytes::ByteLookupEvent;
810
use crate::bytes::ByteOpcode;
@@ -69,3 +71,64 @@ impl<F: Field> AndOperation<F> {
6971
}
7072
}
7173
}
74+
75+
/// A set of columns needed to compute the and of two word64s.
76+
#[derive(AlignedBorrow, Default, Debug, Clone, Copy)]
77+
#[repr(C)]
78+
pub struct And64Operation<T> {
79+
/// The result of `x & y`.
80+
pub value: Word64<T>,
81+
}
82+
83+
impl<F: Field> And64Operation<F> {
84+
pub fn populate(
85+
&mut self,
86+
record: &mut ExecutionRecord,
87+
shard: u32,
88+
channel: u32,
89+
x: u64,
90+
y: u64,
91+
) -> u64 {
92+
let expected = x & y;
93+
let x_bytes = x.to_le_bytes();
94+
let y_bytes = y.to_le_bytes();
95+
for i in 0..WORD64_SIZE {
96+
let and = x_bytes[i] & y_bytes[i];
97+
self.value[i] = F::from_canonical_u8(and);
98+
99+
let byte_event = ByteLookupEvent {
100+
shard,
101+
channel,
102+
opcode: ByteOpcode::AND,
103+
a1: u32::from(and),
104+
a2: 0,
105+
b: u32::from(x_bytes[i]),
106+
c: u32::from(y_bytes[i]),
107+
};
108+
record.add_byte_lookup_event(byte_event);
109+
}
110+
expected
111+
}
112+
113+
pub fn eval<AB: ByteAirBuilder<F = F>>(
114+
builder: &mut AB,
115+
a: Word64<AB::Var>,
116+
b: Word64<AB::Var>,
117+
cols: And64Operation<AB::Var>,
118+
shard: AB::Var,
119+
channel: impl Into<AB::Expr> + Copy,
120+
is_real: AB::Var,
121+
) {
122+
for i in 0..WORD64_SIZE {
123+
builder.send_byte(
124+
AB::F::from_canonical_u32(ByteOpcode::AND as u32),
125+
cols.value[i],
126+
a[i],
127+
b[i],
128+
shard,
129+
channel,
130+
is_real,
131+
);
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)