Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
9598891
feat(poly): Add polynomial and prime field implementations
jjllzhang Feb 2, 2026
e71fda1
chore: consolidate empty code change entries for clarity
jjllzhang Feb 2, 2026
6f5103c
refactor(poly): Simplify polynomial construction and improve variable…
jjllzhang Feb 2, 2026
e494d27
refactor(poly): Improve readability and structure of NTT functions
jjllzhang Feb 2, 2026
52bbb32
chore(poly): remove outdated poly interpolation porting plan document
jjllzhang Feb 2, 2026
897c2d8
refactor(poly): Enhance FpPolynomial class by introducing SetCoeffs m…
jjllzhang Mar 6, 2026
0608cdf
refactor(poly): Replace raw pointers with std::optional for context m…
jjllzhang Mar 6, 2026
0f6d662
refactor(poly): Introduce narrow prime selection for NTT length and c…
jjllzhang Mar 6, 2026
b1ee233
refactor(poly): Update MulCoeffsNTTTruncNarrow to use NarrowPrimeSele…
jjllzhang Mar 6, 2026
ff1c394
fix(poly): Update MulCoeffsNTTTruncNarrow to use detail::NarrowPrimeS…
jjllzhang Mar 6, 2026
5c5e259
Merge branch 'main' into feat/crypto-experimental-poly
jjllzhang Mar 7, 2026
1e48678
Merge branch 'main' into feat/crypto-experimental-poly
jjllzhang Mar 12, 2026
7394770
refactor(test): Replace RandStr with MakeTestString for deterministic…
jjllzhang Mar 12, 2026
7941c12
refactor(factory_test): Introduce MakeHosts function for dynamic host…
jjllzhang Mar 12, 2026
6e51091
Merge remote-tracking branch 'upstream/main' into feat/crypto-experim…
jjllzhang Jun 17, 2026
be74034
fix(poly): address review feedback
jjllzhang Jun 18, 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
67 changes: 67 additions & 0 deletions yacl/crypto/experimental/poly/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright 2026 Ant Group Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load("//bazel:yacl.bzl", "yacl_cc_library", "yacl_cc_test")

package(default_visibility = ["//visibility:public"])

yacl_cc_library(
name = "poly",
srcs = [
"fp_poly.cc",
"fp_poly_div.cc",
"fp_poly_interp.cc",
"fp_poly_mul.cc",
],
hdrs = [
"prime_field.h",
"fp_poly.h",
],
defines = select({
"@yacl//bazel/config:gmp": ["BIGNUM_WITH_GMP"],
"//conditions:default": [],
}),
deps = [
"@abseil-cpp//absl/numeric:bits",
"//yacl/base:exception",
"//yacl/base:int128",
] + select({
"//bazel/config:gmp": ["//yacl/math/bigint/gmp:gmp_loader"],
"//conditions:default": [],
}),
)

yacl_cc_test(
name = "prime_field_test",
srcs = ["prime_field_test.cc"],
deps = [
":poly",
],
)

yacl_cc_test(
name = "fp_poly_test",
srcs = ["fp_poly_test.cc"],
deps = [
":poly",
],
)

yacl_cc_test(
name = "interp_correctness_test",
srcs = ["interp_correctness_test.cc"],
deps = [
":poly",
],
)
29 changes: 29 additions & 0 deletions yacl/crypto/experimental/poly/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# yacl/crypto/experimental/poly

This directory contains **experimental** prime-field and polynomial utilities:

- Prime field: `prime_field.h` (`Fp`, `FpContext`)
- Polynomial + interpolation: `fp_poly.h` (`FpPolynomial`)

## Code layout

`FpPolynomial` is declared in `fp_poly.h` and implemented across multiple `.cc`
files to keep each piece focused:

- `fp_poly.cc`: core polynomial utilities (constructors, add/sub/eval, etc.)
- `fp_poly_mul.cc`: multiplication backends (naive / NTT+CRT, optional GMP)
- `fp_poly_div.cc`: division/mod and series inversion helpers
- `fp_poly_interp.cc`: subproduct tree, multipoint evaluation, interpolation

## Stability

These headers live under `yacl/crypto/experimental/`, so the API is **not**
considered stable and may change without notice.

## Bazel

- Library: `//yacl/crypto/experimental/poly:poly`
- Tests:
- `//yacl/crypto/experimental/poly:prime_field_test`
- `//yacl/crypto/experimental/poly:fp_poly_test`
- `//yacl/crypto/experimental/poly:interp_correctness_test`
276 changes: 276 additions & 0 deletions yacl/crypto/experimental/poly/fp_poly.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// Copyright 2026 Ant Group Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "yacl/crypto/experimental/poly/fp_poly.h"

#include <algorithm>
#include <string>
#include <utility>
#include <vector>

namespace yacl::crypto::experimental::poly {

void FpPolynomial::RequireContext() const {
YACL_ENFORCE(ctx_.has_value(), "FpPolynomial: null ctx");
}

void FpPolynomial::RequireCompat(const FpPolynomial& other) const {
RequireContext();
other.RequireContext();
if (ctx_->GetModulus() != other.ctx_->GetModulus()) {
YACL_THROW_ARGUMENT_ERROR(
"FpPolynomial: modulus mismatch between polynomials");
}
}

FpPolynomial::FpPolynomial(const FpContext& ctx) : ctx_(ctx) {}

FpPolynomial::FpPolynomial(const FpContext& ctx, std::vector<Fp> coeffs)
: ctx_(ctx) {
SetCoeffs(std::move(coeffs));
}

FpPolynomial::FpPolynomial(const FpContext& ctx,
std::initializer_list<u64> coeffs_u64)
: ctx_(ctx) {
c_.reserve(coeffs_u64.size());
for (u64 a : coeffs_u64) {
c_.push_back(ctx_->FromUint64(a));
}
Trim();
}

const FpContext& FpPolynomial::GetContext() const {
RequireContext();
return *ctx_;
}

u64 FpPolynomial::GetModulus() const { return GetContext().GetModulus(); }

const std::vector<Fp>& FpPolynomial::Coeffs() const noexcept { return c_; }

void FpPolynomial::SetCoeffs(std::vector<Fp> coeffs) {
RequireContext();
c_ = std::move(coeffs);
for (auto& x : c_) {
x.v %= ctx_->GetModulus();
}
Trim();
}
Comment on lines +63 to +70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Redundant modulo operation x.v %= ctx_->GetModulus() in SetCoeffs. Since Fp elements are guaranteed to be canonical, this loop is unnecessary and introduces significant overhead.

void FpPolynomial::SetCoeffs(std::vector<Fp> coeffs) {
  RequireContext();
  c_ = std::move(coeffs);
  Trim();
}


bool FpPolynomial::IsZero() const noexcept { return c_.empty(); }

std::string FpPolynomial::ToString() const {
if (IsZero()) {
return "0";
}
std::string out;
bool first = true;
for (std::size_t i = 0; i < c_.size(); ++i) {
const Fp ci = c_[i];
if (ci.v == 0) {
continue;
}
if (!first) {
out.append(" + ");
}
first = false;
out.append(std::to_string(ci.v));
if (i >= 1) {
out.append("*x");
}
if (i >= 2) {
out.push_back('^');
out.append(std::to_string(i));
}
}
return out;
}

int FpPolynomial::Degree() const noexcept {
return c_.empty() ? -1 : static_cast<int>(c_.size()) - 1;
}

void FpPolynomial::Trim() {
while (!c_.empty() && c_.back().v == 0) {
c_.pop_back();
}
}

Fp FpPolynomial::Coeff(size_type i) const noexcept {
if (i >= c_.size()) {
return Fp{0};
}
return c_[i];
}

Fp FpPolynomial::ConstantTerm() const noexcept {
return c_.empty() ? Fp{0} : c_[0];
}

Fp FpPolynomial::LeadingCoeff() const {
if (c_.empty()) {
YACL_THROW_ARGUMENT_ERROR("FpPolynomial::LeadingCoeff: zero polynomial");
}
return c_.back();
}

void FpPolynomial::SetCoeff(size_type i, Fp value) {
RequireContext();
value.v %= ctx_->GetModulus();
if (i >= c_.size()) {
c_.resize(i + 1, ctx_->Zero());
}
c_[i] = value;
Trim();
}
Comment on lines +129 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Redundant modulo operation value.v %= ctx_->GetModulus() in SetCoeff.

void FpPolynomial::SetCoeff(size_type i, Fp value) {
  RequireContext();
  if (i >= c_.size()) {
    c_.resize(i + 1, ctx_->Zero());
  }
  c_[i] = value;
  Trim();
}


FpPolynomial FpPolynomial::Add(const FpPolynomial& g) const {
RequireCompat(g);
const FpContext& F = *ctx_;

FpPolynomial r(F);
const size_type n = std::max(c_.size(), g.c_.size());
r.c_.assign(n, F.Zero());

for (size_type i = 0; i < n; ++i) {
Fp a = (i < c_.size()) ? c_[i] : F.Zero();
Fp b = (i < g.c_.size()) ? g.c_[i] : F.Zero();
r.c_[i] = F.Add(a, b);
}
r.Trim();
return r;
}

FpPolynomial FpPolynomial::Sub(const FpPolynomial& g) const {
RequireCompat(g);
const FpContext& F = *ctx_;

FpPolynomial r(F);
const size_type n = std::max(c_.size(), g.c_.size());
r.c_.assign(n, F.Zero());

for (size_type i = 0; i < n; ++i) {
Fp a = (i < c_.size()) ? c_[i] : F.Zero();
Fp b = (i < g.c_.size()) ? g.c_[i] : F.Zero();
r.c_[i] = F.Sub(a, b);
}
r.Trim();
return r;
}

FpPolynomial FpPolynomial::ScalarMul(Fp k) const {
RequireContext();
const FpContext& F = *ctx_;
k.v %= F.GetModulus();

if (k.v == 0 || IsZero()) {
return FpPolynomial(F);
}
Comment on lines +173 to +180

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Redundant modulo operation k.v %= F.GetModulus() in ScalarMul.

FpPolynomial FpPolynomial::ScalarMul(Fp k) const {
  RequireContext();
  const FpContext& F = *ctx_;

  if (k.v == 0 || IsZero()) {
    return FpPolynomial(F);
  }


FpPolynomial r(F);
r.c_.assign(c_.size(), F.Zero());
for (size_type i = 0; i < c_.size(); ++i) {
r.c_[i] = F.Mul(c_[i], k);
}
r.Trim();
return r;
}

FpPolynomial FpPolynomial::Derivative() const {
RequireContext();
const FpContext& F = *ctx_;

if (c_.size() <= 1) {
return FpPolynomial(F);
}

FpPolynomial r(F);
r.c_.assign(c_.size() - 1, F.Zero());

Fp ii = F.Zero();
const Fp one = F.One();
for (size_type i = 1; i < c_.size(); ++i) {
// (a_i * i) x^{i-1}
ii = F.Add(ii, one);
r.c_[i - 1] = F.Mul(c_[i], ii);
}
Comment on lines +204 to +208

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Performance optimization in Derivative. Instead of calling F.FromUint64(static_cast<u64>(i)) which performs modulo/reduction inside the loop, we can incrementally compute ii using F.Add(ii, F.One()).

  Fp ii = F.Zero();
  for (size_type i = 1; i < c_.size(); ++i) {
    ii = F.Add(ii, F.One());
    r.c_[i - 1] = F.Mul(c_[i], ii);
  }

r.Trim();
return r;
}

Fp FpPolynomial::Eval(Fp x) const {
RequireContext();
const FpContext& F = *ctx_;
x.v %= F.GetModulus();

Fp acc = F.Zero();
Comment on lines +213 to +218

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Redundant modulo operation x.v %= F.GetModulus() in Eval.

Suggested change
Fp FpPolynomial::Eval(Fp x) const {
RequireContext();
const FpContext& F = *ctx_;
x.v %= F.GetModulus();
Fp acc = F.Zero();
Fp FpPolynomial::Eval(Fp x) const {
RequireContext();
const FpContext& F = *ctx_;
Fp acc = F.Zero();

for (size_type i = c_.size(); i-- > 0;) {
acc = F.Add(F.Mul(acc, x), c_[i]);
}
return acc;
}

FpPolynomial FpPolynomial::Neg() const {
RequireContext();
const FpContext& F = *ctx_;
if (IsZero()) {
return FpPolynomial(F);
}

FpPolynomial r(F);
r.c_.assign(c_.size(), F.Zero());
for (size_type i = 0; i < c_.size(); ++i) {
r.c_[i] = F.Neg(c_[i]);
}
r.Trim();
return r;
}

bool FpPolynomial::Equal(const FpPolynomial& g) const noexcept {
if (!ctx_.has_value() || !g.ctx_.has_value()) {
return false;
}
if (ctx_->GetModulus() != g.ctx_->GetModulus()) {
return false;
}

size_type na = c_.size();
while (na > 0 && c_[na - 1].v == 0) {
--na;
}

size_type nb = g.c_.size();
while (nb > 0 && g.c_[nb - 1].v == 0) {
--nb;
}

if (na != nb) {
return false;
}
for (size_type i = 0; i < na; ++i) {
if (c_[i].v != g.c_[i].v) {
return false;
}
}
return true;
}

#ifdef YACL_CRYPTO_EXPERIMENTAL_POLY_ENABLE_OSTREAM
std::ostream& operator<<(std::ostream& os, const FpPolynomial& f) {
return os << f.ToString();
}
#endif

} // namespace yacl::crypto::experimental::poly
Loading
Loading