Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions noir-examples/embedded_curve_msm/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "embedded_curve_msm"
type = "bin"
authors = [""]
compiler_version = ">=0.22.0"

[dependencies]
5 changes: 5 additions & 0 deletions noir-examples/embedded_curve_msm/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# MSM: result = s1 * G + s2 * G = 1*G + 2*G = 3*G
scalar1_lo = "1"
scalar1_hi = "0"
scalar2_lo = "2"
scalar2_hi = "0"
51 changes: 51 additions & 0 deletions noir-examples/embedded_curve_msm/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::embedded_curve_ops::{
EmbeddedCurvePoint,
EmbeddedCurveScalar,
multi_scalar_mul,
};

/// Exercises the MultiScalarMul ACIR blackbox with 2 Grumpkin points.
/// Computes s1 * G + s2 * G where G is the Grumpkin generator.
fn main(
scalar1_lo: Field,
scalar1_hi: Field,
scalar2_lo: Field,
scalar2_hi: Field,
) {
// Grumpkin generator
let g = EmbeddedCurvePoint {
x: 1,
y: 17631683881184975370165255887551781615748388533673675138860,
is_infinite: false,
};

let s1 = EmbeddedCurveScalar { lo: scalar1_lo, hi: scalar1_hi };
let s2 = EmbeddedCurveScalar { lo: scalar2_lo, hi: scalar2_hi };

// MSM: result = s1 * G + s2 * G
let result = multi_scalar_mul([g, g], [s1, s2]);

// Prevent dead-code elimination - forces the blackbox to be retained
assert(!result.is_infinite);
}

#[test]
fn test_msm() {
// 3*G on Grumpkin
let expected_x = 18660890509582237958343981571981920822503400000196279471655180441138020044621;
let expected_y = 8902249110305491597038405103722863701255802573786510474664632793109847672620;

main(1, 0, 2, 0);

// Verify by computing independently: 3*G should match
let g = EmbeddedCurvePoint {
x: 1,
y: 17631683881184975370165255887551781615748388533673675138860,
is_infinite: false,
};
let s3 = EmbeddedCurveScalar { lo: 3, hi: 0 };
let check = multi_scalar_mul([g], [s3]);

assert(check.x == expected_x);
assert(check.y == expected_y);
}
7 changes: 7 additions & 0 deletions noir-examples/native_msm/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "native_msm"
type = "bin"
authors = [""]
compiler_version = ">=0.22.0"

[dependencies]
5 changes: 5 additions & 0 deletions noir-examples/native_msm/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# MSM: result = s1 * G + s2 * G = 1*G + 2*G = 3*G
scalar1_lo = "1"
scalar1_hi = "0"
scalar2_lo = "2"
scalar2_hi = "0"
104 changes: 104 additions & 0 deletions noir-examples/native_msm/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Grumpkin generator y-coordinate
global GRUMPKIN_GEN_Y: Field = 17631683881184975370165255887551781615748388533673675138860;

struct Point {
x: Field,
y: Field,
is_infinite: bool,
}

fn point_double(p: Point) -> Point {
if p.is_infinite | (p.y == 0) {
Point { x: 0, y: 0, is_infinite: true }
} else {
// Grumpkin has a=0, so lambda = 3*x1^2 / (2*y1)
let lambda = (3 * p.x * p.x) / (2 * p.y);
let x3 = lambda * lambda - 2 * p.x;
let y3 = lambda * (p.x - x3) - p.y;
Point { x: x3, y: y3, is_infinite: false }
}
}

fn point_add(p1: Point, p2: Point) -> Point {
if p1.is_infinite {
p2
} else if p2.is_infinite {
p1
} else if (p1.x == p2.x) & (p1.y == p2.y) {
point_double(p1)
} else if (p1.x == p2.x) & (p1.y == (0 - p2.y)) {
Point { x: 0, y: 0, is_infinite: true }
} else {
let lambda = (p2.y - p1.y) / (p2.x - p1.x);
let x3 = lambda * lambda - p1.x - p2.x;
let y3 = lambda * (p1.x - x3) - p1.y;
Point { x: x3, y: y3, is_infinite: false }
}
}

fn scalar_mul(p: Point, scalar_lo: Field, scalar_hi: Field) -> Point {
let lo_bits: [u1; 128] = scalar_lo.to_le_bits();
let hi_bits: [u1; 128] = scalar_hi.to_le_bits();

// Combine into a single 256-bit array (lo first, then hi)
let mut bits: [u1; 256] = [0; 256];
for i in 0..128 {
bits[i] = lo_bits[i];
bits[128 + i] = hi_bits[i];
}

// Find the highest set bit
let mut top = 0;
for i in 0..256 {
if bits[i] == 1 {
top = i;
}
}

// Double-and-add from MSB down to bit 0
let mut acc = Point { x: 0, y: 0, is_infinite: true };
for j in 0..256 {
let i = 255 - j;
acc = point_double(acc);
if bits[i] == 1 {
acc = point_add(acc, p);
}
}

acc
}

/// Native MSM: computes s1 * G + s2 * G using pure Noir field operations.
/// No blackbox functions -- all EC arithmetic is done natively over Grumpkin's
/// base field (= BN254 scalar field = Noir's native Field).
fn main(
scalar1_lo: Field,
scalar1_hi: Field,
scalar2_lo: Field,
scalar2_hi: Field,
) {
let g = Point { x: 1, y: GRUMPKIN_GEN_Y, is_infinite: false };

let r1 = scalar_mul(g, scalar1_lo, scalar1_hi);
let r2 = scalar_mul(g, scalar2_lo, scalar2_hi);
let result = point_add(r1, r2);

// Prevent dead-code elimination
assert(!result.is_infinite);
}

#[test]
fn test_msm() {
// 3*G on Grumpkin (known coordinates)
let expected_x = 18660890509582237958343981571981920822503400000196279471655180441138020044621;
let expected_y = 8902249110305491597038405103722863701255802573786510474664632793109847672620;

main(1, 0, 2, 0);

// Verify 1*G + 2*G = 3*G by computing 3*G directly
let g = Point { x: 1, y: GRUMPKIN_GEN_Y, is_infinite: false };
let three_g = scalar_mul(g, 3, 0);

assert(three_g.x == expected_x);
assert(three_g.y == expected_y);
}
55 changes: 54 additions & 1 deletion provekit/common/src/witness/scheduling/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ impl DependencyInfo {
WitnessBuilder::Sum(_, ops) => ops.iter().map(|SumTerm(_, idx)| *idx).collect(),
WitnessBuilder::Product(_, a, b) => vec![*a, *b],
WitnessBuilder::MultiplicitiesForRange(_, _, values) => values.clone(),
WitnessBuilder::Inverse(_, x) => vec![*x],
WitnessBuilder::Inverse(_, x)
| WitnessBuilder::SafeInverse(_, x)
| WitnessBuilder::ModularInverse(_, x, _)
| WitnessBuilder::IntegerQuotient(_, x, _) => vec![*x],
WitnessBuilder::IndexedLogUpDenominator(
_,
sz,
Expand Down Expand Up @@ -152,6 +155,28 @@ impl DependencyInfo {
}
v
}
WitnessBuilder::MultiLimbMulModHint {
a_limbs, b_limbs, ..
} => {
let mut v = a_limbs.clone();
v.extend(b_limbs);
v
}
WitnessBuilder::MultiLimbModularInverse { a_limbs, .. } => a_limbs.clone(),
WitnessBuilder::MultiLimbAddQuotient {
a_limbs, b_limbs, ..
} => {
let mut v = a_limbs.clone();
v.extend(b_limbs);
v
}
WitnessBuilder::MultiLimbSubBorrow {
a_limbs, b_limbs, ..
} => {
let mut v = a_limbs.clone();
v.extend(b_limbs);
v
}
WitnessBuilder::BytePartition { x, .. } => vec![*x],

WitnessBuilder::U32AdditionMulti(_, _, inputs) => inputs
Expand Down Expand Up @@ -198,6 +223,10 @@ impl DependencyInfo {
data.rs_cubed,
]
}
WitnessBuilder::FakeGLVHint { s_lo, s_hi, .. } => vec![*s_lo, *s_hi],
WitnessBuilder::EcScalarMulHint {
px, py, s_lo, s_hi, ..
} => vec![*px, *py, *s_lo, *s_hi],
WitnessBuilder::ChunkDecompose { packed, .. } => vec![*packed],
WitnessBuilder::SpreadWitness(_, input) => vec![*input],
WitnessBuilder::SpreadBitExtract { sum_terms, .. } => {
Expand Down Expand Up @@ -240,6 +269,9 @@ impl DependencyInfo {
| WitnessBuilder::Challenge(idx)
| WitnessBuilder::IndexedLogUpDenominator(idx, ..)
| WitnessBuilder::Inverse(idx, _)
| WitnessBuilder::SafeInverse(idx, _)
| WitnessBuilder::ModularInverse(idx, ..)
| WitnessBuilder::IntegerQuotient(idx, ..)
| WitnessBuilder::ProductLinearOperation(idx, ..)
| WitnessBuilder::LogUpDenominator(idx, ..)
| WitnessBuilder::LogUpInverse(idx, ..)
Expand Down Expand Up @@ -282,6 +314,27 @@ impl DependencyInfo {
let n = 1usize << *num_bits;
(*start..*start + n).collect()
}
WitnessBuilder::MultiLimbMulModHint {
output_start,
num_limbs,
..
} => {
let count = (4 * *num_limbs - 2) as usize;
(*output_start..*output_start + count).collect()
}
WitnessBuilder::MultiLimbModularInverse {
output_start,
num_limbs,
..
} => (*output_start..*output_start + *num_limbs as usize).collect(),
WitnessBuilder::FakeGLVHint {
output_start, ..
} => (*output_start..*output_start + 4).collect(),
WitnessBuilder::EcScalarMulHint {
output_start, ..
} => (*output_start..*output_start + 2).collect(),
WitnessBuilder::MultiLimbAddQuotient { output, .. } => vec![*output],
WitnessBuilder::MultiLimbSubBorrow { output, .. } => vec![*output],
WitnessBuilder::U32Addition(result_idx, carry_idx, ..) => {
vec![*result_idx, *carry_idx]
}
Expand Down
95 changes: 95 additions & 0 deletions provekit/common/src/witness/scheduling/remapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ impl WitnessIndexRemapper {
WitnessBuilder::Inverse(idx, operand) => {
WitnessBuilder::Inverse(self.remap(*idx), self.remap(*operand))
}
WitnessBuilder::SafeInverse(idx, operand) => {
WitnessBuilder::SafeInverse(self.remap(*idx), self.remap(*operand))
}
WitnessBuilder::ModularInverse(idx, operand, modulus) => {
WitnessBuilder::ModularInverse(self.remap(*idx), self.remap(*operand), *modulus)
}
WitnessBuilder::IntegerQuotient(idx, dividend, divisor) => {
WitnessBuilder::IntegerQuotient(self.remap(*idx), self.remap(*dividend), *divisor)
}
WitnessBuilder::ProductLinearOperation(
idx,
ProductLinearTerm(x, a, b),
Expand Down Expand Up @@ -215,6 +224,64 @@ impl WitnessIndexRemapper {
.collect(),
)
}
WitnessBuilder::MultiLimbMulModHint {
output_start,
a_limbs,
b_limbs,
modulus,
limb_bits,
num_limbs,
} => WitnessBuilder::MultiLimbMulModHint {
output_start: self.remap(*output_start),
a_limbs: a_limbs.iter().map(|&w| self.remap(w)).collect(),
b_limbs: b_limbs.iter().map(|&w| self.remap(w)).collect(),
modulus: *modulus,
limb_bits: *limb_bits,
num_limbs: *num_limbs,
},
WitnessBuilder::MultiLimbModularInverse {
output_start,
a_limbs,
modulus,
limb_bits,
num_limbs,
} => WitnessBuilder::MultiLimbModularInverse {
output_start: self.remap(*output_start),
a_limbs: a_limbs.iter().map(|&w| self.remap(w)).collect(),
modulus: *modulus,
limb_bits: *limb_bits,
num_limbs: *num_limbs,
},
WitnessBuilder::MultiLimbAddQuotient {
output,
a_limbs,
b_limbs,
modulus,
limb_bits,
num_limbs,
} => WitnessBuilder::MultiLimbAddQuotient {
output: self.remap(*output),
a_limbs: a_limbs.iter().map(|&w| self.remap(w)).collect(),
b_limbs: b_limbs.iter().map(|&w| self.remap(w)).collect(),
modulus: *modulus,
limb_bits: *limb_bits,
num_limbs: *num_limbs,
},
WitnessBuilder::MultiLimbSubBorrow {
output,
a_limbs,
b_limbs,
modulus,
limb_bits,
num_limbs,
} => WitnessBuilder::MultiLimbSubBorrow {
output: self.remap(*output),
a_limbs: a_limbs.iter().map(|&w| self.remap(w)).collect(),
b_limbs: b_limbs.iter().map(|&w| self.remap(w)).collect(),
modulus: *modulus,
limb_bits: *limb_bits,
num_limbs: *num_limbs,
},
WitnessBuilder::BytePartition { lo, hi, x, k } => WitnessBuilder::BytePartition {
lo: self.remap(*lo),
hi: self.remap(*hi),
Expand Down Expand Up @@ -299,6 +366,34 @@ impl WitnessIndexRemapper {
},
)
}
WitnessBuilder::FakeGLVHint {
output_start,
s_lo,
s_hi,
curve_order,
} => WitnessBuilder::FakeGLVHint {
output_start: self.remap(*output_start),
s_lo: self.remap(*s_lo),
s_hi: self.remap(*s_hi),
curve_order: *curve_order,
},
WitnessBuilder::EcScalarMulHint {
output_start,
px,
py,
s_lo,
s_hi,
curve_a,
field_modulus_p,
} => WitnessBuilder::EcScalarMulHint {
output_start: self.remap(*output_start),
px: self.remap(*px),
py: self.remap(*py),
s_lo: self.remap(*s_lo),
s_hi: self.remap(*s_hi),
curve_a: *curve_a,
field_modulus_p: *field_modulus_p,
},
WitnessBuilder::ChunkDecompose {
output_start,
packed,
Expand Down
Loading
Loading