diff --git a/yacl/crypto/primitives/vss/BUILD.bazel b/yacl/crypto/primitives/vss/BUILD.bazel new file mode 100644 index 00000000..5398f1c4 --- /dev/null +++ b/yacl/crypto/primitives/vss/BUILD.bazel @@ -0,0 +1,34 @@ +load("//bazel:yacl.bzl", "yacl_cc_library", "yacl_cc_test") + +package(default_visibility = ["//visibility:public"]) + +yacl_cc_library( + name = "poly", + srcs = ["poly.cc"], + hdrs = ["poly.h"], + deps = [ + "//yacl/math/mpint", + ], +) + +yacl_cc_library( + name = "vss", + srcs = ["vss.cc"], + hdrs = ["vss.h"], + deps = [ + ":poly", + "//yacl/crypto/base/ecc", + "//yacl/math/mpint", + ], +) + +yacl_cc_test( + name = "vss_test", + srcs = ["vss_test.cc"], + deps = [ + ":poly", + ":vss", + "//yacl/crypto/base/ecc", + "//yacl/math/mpint", + ], +) diff --git a/yacl/crypto/primitives/vss/poly.cc b/yacl/crypto/primitives/vss/poly.cc new file mode 100644 index 00000000..77fee51e --- /dev/null +++ b/yacl/crypto/primitives/vss/poly.cc @@ -0,0 +1,105 @@ +#include "yacl/crypto/primitives/vss/poly.h" + +namespace yacl::crypto { + +// Generate a random polynomial with the given zero value, threshold, and +// modulus_. +void Polynomial::CreatePolynomial(const math::MPInt& zero_value, + size_t threshold) { + // Create a vector to hold the polynomial coefficients. + std::vector coefficients(threshold); + + // Set the constant term (coefficient[0]) of the polynomial to the given + // zero_value. + coefficients[0] = zero_value; + + // Generate random coefficients for the remaining terms of the polynomial. + for (size_t i = 1; i < threshold; ++i) { + // Create a variable to hold the current coefficient being generated. + math::MPInt coefficient_i; + + // Generate a random integer less than modulus_ and assign it to + // coefficient_i. + math::MPInt::RandomLtN(this->modulus_, &coefficient_i); + + // Set the current coefficient to the generated random value. + coefficients[i] = coefficient_i; + } + + // Set the generated coefficients as the coefficients of the polynomial. + SetCoeffs(coefficients); +} + +// Horner's method for computing the polynomial value at a given x. +void Polynomial::EvaluatePolynomial(const math::MPInt& x, + math::MPInt& result) const { + // Initialize the result to the constant term (coefficient of highest degree) + // of the polynomial. + if (!coeffs_.empty()) { + result = coeffs_.back(); + } else { + // If the coefficients vector is empty, print a warning message. + std::cout << "coeffs_ is empty!!!" << std::endl; + } + + // Evaluate the polynomial using Horner's method. + // Starting from the second highest degree coefficient to the constant term + // (coefficient[0]). + for (int i = coeffs_.size() - 2; i >= 0; --i) { + // Create a duplicate of the given x to avoid modifying it. + // math::MPInt x_dup = x; + + // Multiply the current result with the x value and update the result. + // result = x_dup.MulMod(result, modulus_); + result = x.MulMod(result, modulus_); + // Add the next coefficient to the result. + result = result.AddMod(coeffs_[i], modulus_); + } +} + +// Lagrange Interpolation algorithm for polynomial interpolation. +void Polynomial::LagrangeInterpolation(std::vector& xs, + std::vector& ys, + math::MPInt& result) const { + // Initialize the accumulator to store the result of the interpolation. + math::MPInt acc(0); + + // Loop over each element in the input points xs and interpolate the + // polynomial. + for (size_t i = 0; i < xs.size(); ++i) { + // Initialize the numerator and denominator for Lagrange interpolation. + math::MPInt num(1); + math::MPInt denum(1); + + // Compute the numerator and denominator for the current interpolation + // point. + for (size_t j = 0; j < xs.size(); ++j) { + if (j != i) { + math::MPInt xj = xs[j]; + + // Update the numerator by multiplying it with the current xj. + num = num.MulMod(xj, modulus_); + + // Compute the difference between the current xj and the current xi + // (xs[i]). + math::MPInt xj_sub_xi = xj.SubMod(xs[i], modulus_); + + // Update the denominator by multiplying it with the difference. + denum = denum.MulMod(xj_sub_xi, modulus_); + } + } + + // Compute the inverse of the denominator modulo the modulus_. + math::MPInt denum_inv = denum.InvertMod(modulus_); + + // Compute the current interpolated value and add it to the accumulator. + acc = ys[i] + .MulMod(num, modulus_) + .MulMod(denum_inv, modulus_) + .AddMod(acc, modulus_); + } + + // Store the final interpolated result in the 'result' variable. + result = acc; +} +} // namespace yacl::crypto diff --git a/yacl/crypto/primitives/vss/poly.h b/yacl/crypto/primitives/vss/poly.h new file mode 100644 index 00000000..3ae74248 --- /dev/null +++ b/yacl/crypto/primitives/vss/poly.h @@ -0,0 +1,100 @@ +#pragma once + +#include "yacl/math/mpint/mp_int.h" + +namespace yacl::crypto { + +// Polynomial class for polynomial manipulation and sharing. +class Polynomial { + public: + /** + * @brief Construct a new Polynomial object with modulus + * + * @param modulus + */ + Polynomial(math::MPInt modulus) : modulus_(modulus) {} + + /** + * @brief Destroy the Polynomial object + * + */ + ~Polynomial(){}; + + /** + * @brief Creates a random polynomial with the given zero_value, threshold, + * and modulus. + * + * @param zero_value + * @param threshold + * @param modulus + */ + void CreatePolynomial(const math::MPInt& zero_value, size_t threshold); + + /** + * @brief Horner's method, also known as Horner's rule or Horner's scheme, is + * an algorithm for the efficient evaluation of polynomials. It is used to + * compute the value of a polynomial at a given point without the need for + * repeated multiplication and addition operations. The method is particularly + * useful for high-degree polynomials. + * + * The general form of a polynomial is: + * + * f(x) = a_n * x^n + a_{n-1} * x^{n-1} + ... + a_1 * x + a_0 + * + * Horner's method allows us to compute the value of the polynomial f(x) at a + * specific point x_0 in a more efficient way by factoring out the common + * terms: + * + * f(x_0) = (((a_n * x_0 + a_{n-1}) * x_0 + a_{n-2}) * x_0 + ... + a_1) * x_0 + * + a_0 + * + * The algorithm proceeds iteratively, starting with the coefficient of the + * highest degree term, and at each step, it multiplies the current partial + * result by the input point x_0 and adds the next coefficient. + * + * The advantages of using Horner's method include reducing the number of + * multiplications and additions compared to the straightforward + * + * @param x + * @param modulus + * @param result + */ + void EvaluatePolynomial(const math::MPInt& x, math::MPInt& result) const; + + /** + * @brief Performs Lagrange interpolation to interpolate the polynomial based + * on the given points. + * + * @param xs + * @param ys + * @param prime + * @param result + */ + void LagrangeInterpolation(std::vector& xs, + std::vector& ys, + math::MPInt& result) const; + + /** + * @brief Sets the coefficients of the polynomial to the provided vector of + * MPInt. + * + * @param coefficients + */ + void SetCoeffs(const std::vector& coefficients) { + coeffs_ = coefficients; + } + + /** + * @brief Returns the coefficients of the polynomial as a vector of MPInt. + * + * @return std::vector + */ + std::vector GetCoeffs() const { return coeffs_; } + + private: + // Vector to store the coefficients of the polynomial. + std::vector coeffs_; + math::MPInt modulus_; +}; + +} // namespace yacl::crypto diff --git a/yacl/crypto/primitives/vss/vss.cc b/yacl/crypto/primitives/vss/vss.cc new file mode 100644 index 00000000..89ea5ddf --- /dev/null +++ b/yacl/crypto/primitives/vss/vss.cc @@ -0,0 +1,132 @@ +#include "yacl/crypto/primitives/vss/vss.h" + +namespace yacl::crypto { + +// Generate shares for the Verifiable Secret Sharing scheme. +// Generate shares for the given secret using the provided polynomial. +std::vector +VerifiableSecretSharing::CreateShare(const math::MPInt& secret, + Polynomial& poly) { + // Create a polynomial with the secret as the constant term and random + // coefficients. + std::vector coefficients(this->GetThreshold()); + poly.CreatePolynomial(secret, this->GetThreshold()); + + std::vector xs(this->GetTotal()); + std::vector ys(this->GetTotal()); + + // Vector to store the generated shares (x, y) for the Verifiable Secret + // Sharing scheme. + std::vector shares; + + // Generate shares by evaluating the polynomial at random points xs. + for (size_t i = 0; i < this->GetTotal(); i++) { + math::MPInt x_i; + math::MPInt::RandomLtN(this->GetPrime(), &x_i); + + // EvaluatePolynomial uses Horner's method. + // Evaluate the polynomial at the point x_i to compute the share's + // y-coordinate (ys[i]). + poly.EvaluatePolynomial(x_i, ys[i]); + + xs[i] = x_i; + shares.push_back({xs[i], ys[i]}); + } + + return shares; +} + +// Generate shares with commitments for the Verifiable Secret Sharing scheme. +VerifiableSecretSharing::ShareWithCommitsResult +VerifiableSecretSharing::CreateShareWithCommits( + const math::MPInt& secret, + const std::unique_ptr& ecc_group, Polynomial& poly) { + // Create a polynomial with the secret as the constant term and random + // coefficients. + poly.CreatePolynomial(secret, this->threshold_); + + std::vector xs(this->total_); + std::vector ys(this->total_); + std::vector shares(this->total_); + + // Generate shares by evaluating the polynomial at random points xs. + for (size_t i = 0; i < this->total_; i++) { + math::MPInt x_i; + math::MPInt::RandomLtN(this->prime_, &x_i); + + poly.EvaluatePolynomial(x_i, ys[i]); + xs[i] = x_i; + shares[i] = {xs[i], ys[i]}; + } + + // Generate commitments for the polynomial coefficients using the elliptic + // curve group. + std::vector commits = + CreateCommits(ecc_group, poly.GetCoeffs()); + + return std::make_pair(shares, commits); +} + +// Recover the secret from the shares using Lagrange interpolation. +math::MPInt VerifiableSecretSharing::RecoverSecret( + absl::Span shares) { + YACL_ENFORCE(shares.size() == threshold_); + + math::MPInt secret(0); + std::vector xs(shares.size()); + std::vector ys(shares.size()); + + // Extract xs and ys from the given shares. + for (size_t i = 0; i < shares.size(); i++) { + xs[i] = shares[i].x; + ys[i] = shares[i].y; + } + + // Use Lagrange interpolation to recover the secret from the shares. + Polynomial poly(this->prime_); + poly.LagrangeInterpolation(xs, ys, secret); + + return secret; +} + +// Generate commitments for the given coefficients using the provided elliptic +// curve group. +std::vector CreateCommits( + const std::unique_ptr& ecc_group, + const std::vector& coefficients) { + std::vector commits(coefficients.size()); + for (size_t i = 0; i < coefficients.size(); i++) { + // Commit each coefficient by multiplying it with the base point of the + // group. + commits[i] = ecc_group->MulBase(coefficients[i]); + } + return commits; +} + +// Verify the commitments and shares in the Verifiable Secret Sharing scheme. +bool VerifyCommits(const std::unique_ptr& ecc_group, + const VerifiableSecretSharing::Share& share, + const std::vector& commits, + const math::MPInt& prime) { + // Compute the expected commitment of the share.y by multiplying it with the + // base point. + yacl::crypto::EcPoint expected_gy = ecc_group->MulBase(share.y); + + math::MPInt x_pow_i(1); + yacl::crypto::EcPoint gy = commits[0]; + + // Evaluate the Lagrange polynomial at x = share.x to compute the share.y and + // verify it. + for (size_t i = 1; i < commits.size(); i++) { + x_pow_i = x_pow_i.MulMod(share.x, prime); + gy = ecc_group->Add(gy, ecc_group->Mul(commits[i], x_pow_i)); + } + + // Compare the computed gy with the expected_gy to verify the commitment. + if (ecc_group->PointEqual(expected_gy, gy)) { + return true; + } + return false; +} + +} // namespace yacl::crypto diff --git a/yacl/crypto/primitives/vss/vss.h b/yacl/crypto/primitives/vss/vss.h new file mode 100644 index 00000000..6b17f827 --- /dev/null +++ b/yacl/crypto/primitives/vss/vss.h @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include + +#include "yacl/crypto/base/ecc/ecc_spi.h" +#include "yacl/crypto/primitives/vss/poly.h" +#include "yacl/math/mpint/mp_int.h" + +namespace yacl::crypto { + +/** + * Verifiable Secret Sharing (VSS) is a cryptographic technique that + * allows a secret to be divided into multiple shares, distributed among a group + * of participants, in such a way that the original secret can only be + * reconstructed when a sufficient number of participants combine their shares. + * The key feature of VSS is that it provides a mechanism to verify the + * correctness of the shares and the reconstruction process without revealing + * the secret itself.The concept of Secret Sharing is commonly used for secure + * key management, data protection, and various secure multi-party computations. + * Verifiable Secret Sharing adds an additional layer of security and trust by + * allowing participants to independently verify that the shares they receive + * are valid and that the reconstruction process is executed correctly. + * + * Here's a simplified explanation of how Verifiable Secret Sharing works: + * 1. Secret Sharing: The original secret is divided into multiple shares using + * a specific algorithm. The threshold value is set, which represents the + * minimum number of shares required to reconstruct the secret. + * + * 2. Distribution: Each participant receives one share of the secret. The + * shares are distributed in such a way that no individual share contains enough + * information to reconstruct the original secret. + * + * 3. Verification: Verifiable Secret Sharing introduces an additional step of + * verification. Each participant can verify the authenticity of their own share + * and the shares of others. This is typically achieved through cryptographic + * techniques and mathematical proofs. + * + * 4. Reconstruction: To reconstruct the original secret, a minimum threshold of + * participants must collaborate by combining their shares. The algorithm + * ensures that with less than the threshold number of shares, the secret + * remains secure and unrecoverable. + * + * Verifiable Secret Sharing has applications in various fields, including + * secure multiparty computation, key management, digital signatures, and secure + * cloud computing. It helps ensure that no single party can compromise the + * secret, and the verification mechanism enhances the transparency and security + * of the sharing and reconstruction process. + * + * Different cryptographic schemes and protocols exist for achieving Verifiable + * Secret Sharing, each with its own properties and security guarantees. These + * schemes may use techniques like polynomial interpolation, cryptographic + * commitments, and zero-knowledge proofs to achieve the desired properties. + */ +class VerifiableSecretSharing { + public: + /** + * @brief Construct a new Verifiable Secret Sharing object + * + * @param total + * @param threshold + * @param prime + */ + VerifiableSecretSharing(size_t total, size_t threshold, math::MPInt prime) + : total_(total), threshold_(threshold), prime_(std::move(prime)) { + YACL_ENFORCE(total >= threshold); + } + + /** + * @brief Destroy the Verifiable Secret Sharing object + * + */ + ~VerifiableSecretSharing() {} + + /** + * @brief Structure to hold a share (x, y) in the Verifiable Secret Sharing + * scheme. + * + */ + struct Share { + math::MPInt x; // The x-coordinate of the share. + math::MPInt y; // The y-coordinate of the share. + }; + + /** + * @brief Generate shares for the given secret using the provided polynomial. + * + * @param secret + * @param poly + * @return std::vector + */ + std::vector CreateShare(const math::MPInt& secret, Polynomial& poly); + + /** + * @brief Recover the secret from the given shares using Lagrange + * interpolation. + * + * @param shares + * @param poly + * @return math::MPInt + */ + math::MPInt RecoverSecret(absl::Span shares); + + // New name for the type representing the result of GenerateShareWithCommits + // function. + using ShareWithCommitsResult = + std::pair, std::vector>; + + /** + * @brief Generate shares with commitments for the given secret and elliptic + * curve group. + * + * @param secret + * @param ecc_group + * @param poly + * @return ShareWithCommitsResult + */ + ShareWithCommitsResult CreateShareWithCommits( + const math::MPInt& secret, + const std::unique_ptr& ecc_group, + Polynomial& poly); + + /** + * @brief Get the Total object + * + * @return size_t + */ + size_t GetTotal() const { return total_; } + + /** + * @brief Get the threshold (minimum number of shares required to reconstruct + * the secret. + * + * @return size_t + */ + size_t GetThreshold() const { return threshold_; } + + /** + * @brief Get the prime modulus used in the scheme. + * + * @return math::MPInt + */ + math::MPInt GetPrime() const { return prime_; } + + private: + size_t total_; // Total number of shares in the scheme. + size_t threshold_; // Minimum number of shares required to reconstruct the + // secret. + math::MPInt prime_; // Prime modulus used in the scheme. +}; + +/** + * @brief Generate commitments for the given coefficients using the provided + * elliptic curve group. + * + * @param ecc_group + * @param coefficients + * @return std::vector + */ +std::vector CreateCommits( + const std::unique_ptr& ecc_group, + const std::vector& coefficients); + +/** + * @brief Verify the commitments and shares in the Verifiable Secret Sharing + * scheme. + * + * @param ecc_group + * @param share + * @param commits + * @param prime + * @return true + * @return false + */ +bool VerifyCommits(const std::unique_ptr& ecc_group, + const VerifiableSecretSharing::Share& share, + const std::vector& commits, + const math::MPInt& prime); + +} // namespace yacl::crypto diff --git a/yacl/crypto/primitives/vss/vss_test.cc b/yacl/crypto/primitives/vss/vss_test.cc new file mode 100644 index 00000000..dcfea8c4 --- /dev/null +++ b/yacl/crypto/primitives/vss/vss_test.cc @@ -0,0 +1,59 @@ +#include "yacl/crypto/primitives/vss/vss.h" + +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" + +#include "yacl/crypto/base/ecc/ecc_spi.h" +#include "yacl/crypto/primitives/vss/poly.h" +#include "yacl/math/mpint/mp_int.h" + +namespace yacl::crypto::test { + +TEST(VerifiableSecretSharingTest, TestCreateAndVerifyShares) { + // Create an elliptic curve group using the SM2 algorithm + std::unique_ptr ec_group = EcGroupFactory::Instance().Create("sm2"); + + // Get the order of the elliptic curve group as the modulus + math::MPInt modulus = ec_group->GetOrder(); + + // Define the secret to be shared + math::MPInt original_secret("1234567890"); + + // Create a VerifiableSecretSharing instance with parameters (total_shares, + // required_shares, modulus) + yacl::crypto::VerifiableSecretSharing vss(20, 10, modulus); + + // Initialize a polynomial for the secret sharing scheme + yacl::crypto::Polynomial polynomial(modulus); + + // Generate shares and commitments for the secret + using ShareAndCommitPair = + std::pair, + std::vector>; + ShareAndCommitPair shares_and_commits = + vss.CreateShareWithCommits(original_secret, ec_group, polynomial); + + // Extract the shares from the shares_and_commits pair + std::vector shares(10); + for (size_t i = 0; i < shares.size(); i++) { + shares[i] = shares_and_commits.first[i + 1]; + } + + // Reconstruct the secret using the shares and the polynomial + math::MPInt reconstructed_secret = vss.RecoverSecret(shares); + // Check if the reconstructed secret matches the original secret + EXPECT_EQ(reconstructed_secret, original_secret); + + // Verify commitments for each share + for (size_t i = 0; i < shares.size(); i++) { + // Verify the commitment for a share using the EC group, share, commitments, + // and modulus + bool is_verified = + yacl::crypto::VerifyCommits(ec_group, shares_and_commits.first[i], + shares_and_commits.second, modulus); + + // Check if the commitment verification result is successful + EXPECT_EQ(is_verified, 1); + } +} +} // namespace yacl::crypto::test