pkg/math/polynomial: add LagrangeAtZeroBigInt for tfhe combine (issue #20 precursor)#24
Open
abhicris wants to merge 1 commit into
Open
pkg/math/polynomial: add LagrangeAtZeroBigInt for tfhe combine (issue #20 precursor)#24abhicris wants to merge 1 commit into
abhicris wants to merge 1 commit into
Conversation
Issue #20 (Threshold-FHE: replace fake partial-decrypt stub with real Lagrange-interpolated distributed decryption) is the goal; this PR is the precursor primitive — a big.Int sibling of the existing curve.Scalar Lagrange. It is the function the real CombineShares will call once the PartialDecrypt side lands. What this adds: LagrangeAtZeroBigInt(shares, modulus) -> p(0), error Returns p(0) where p is the polynomial of minimal degree passing through (x_i = bigEndianBytes(id_i) mod modulus, y_i = shares[id_i]) for every party.ID in shares. All arithmetic in F_modulus; modulus is expected prime (Lagrange combine requires modular inverses to exist). Error contract is explicit and deterministic: - empty shares - modulus <= 1 or nil - two party IDs reducing to the same x-coordinate (caught up front before any contribution is summed, so failures don't taint state) - zero denominator (x_j == x_i mod modulus) - non-invertible denominator (composite modulus path) Result is always canonicalized to 0 <= result < modulus. Why a big.Int sibling and not reuse the existing curve.Scalar version: the LWE/RLWE ciphertext modulus that TFHE decryption combines in is not an elliptic-curve scalar field — it's a ring/lattice modulus (the PN9QP28_STD128 set referenced in issue #20 uses 28-bit and 54-bit coefficient moduli). curve.Scalar can't represent those without shoehorning, and reusing it would conflate two distinct algebraic contexts in the same call site. Keeping them sibling functions in the same package matches the structure FROST / CMP already uses. Test coverage: - constant polynomial (every share == c → p(0) == c) - random degree-(t-1) polynomial, recover p(0) for (3,3), (3,5), and (11,21) thresholds; uses a near-2^64 prime for the (11,21) case to exercise the multi-word path - subset independence: two disjoint t-sized subsets of a (t,n) sharing recover the same secret - canonical range invariant (0 <= result < modulus) - empty shares, invalid modulus, duplicate x-coordinate error paths What this does NOT close: Issue #20 is fully closed only when protocols/tfhe/tfhe.go stops being an HMAC stub and PartialDecrypt produces real LWE partial-decryption shares. That's the PartialDecrypt side. This PR is the Combine side — the math the real CombineShares will call. They can land independently; the Combine is testable in isolation against a hand-rolled polynomial, which is what the test file does. Refs: - issue #20 (this repo) - LP-137 (TFHE Real Threshold Spec, referenced in protocols/tfhe/tfhe.go header) - LP-181 Magnetar (prerequisite chain per project memory) - luxfi/fhe PR #21 (prior art, closed; partial-decrypt API shape)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
protocols/tfhe/tfhe.gois gated byALLOW_FAKE_TFHE_FOR_TESTING_ONLY=1andCombineSharesruns single-party decryption while every party holds the full master key — the entire point of "threshold" is missing. Closing issue #20 requires (a) realPartialDecryptthat produces noise-added LWE partial shares and (b) realCombineSharesthat Lagrange-interpolates those partials in the LWE ciphertext modulus.The (b) side is testable in isolation: given (x_i, y_i) points in F_q, return p(0). That's what this PR ships, so the eventual real
CombineSharescan call it without re-inventing the math under time pressure.The existing
pkg/math/polynomial/Lagrangeoperates overcurve.Scalar(for FROST / CMP signing). TFHE combine needs the same shape but over a*big.Intmodulus — the LWE/RLWE coefficient modulus is not an elliptic-curve scalar field.What this changes
Adds two files in
pkg/math/polynomial/:lagrange_bigint.goLagrangeAtZeroBigInt(shares map[party.ID]*big.Int, modulus *big.Int) (*big.Int, error)lagrange_bigint_test.goBehaviour:
p(0) = Σᵢ yᵢ · Lᵢ(0)withLᵢ(0) = Πⱼ≠ᵢ xⱼ · (xⱼ - xᵢ)⁻¹, all arithmetic inF_modulus.xᵢ = BigEndianBytes(id) mod modulus— same shapecurve.Scalar.SetBytesuses, so the x-coordinate semantics match the existingLagrangefor the same party.ID.0 ≤ result < modulus.What this does NOT close
Issue #20 is fully closed when
protocols/tfhe/tfhe.go::PartialDecryptstops being an HMAC stub and actually produces noise-added LWE shares, andCombineSharescallsLagrangeAtZeroBigInton those shares. That's a separate PR — the math here is the half that doesn't depend on the LWE plumbing and is testable independently against hand-rolled polynomials.Acceptance
go test -run 'LagrangeAtZero' ./pkg/math/polynomial/...passes locally (verified).pkg/math/polynomial/.lagrange.go/lagrange_test.go(sibling for big.Int, identical test conventions, sameinternal/test.PartyIDs(n)helper).protocols/tfhe/tfhe.go).Cost
pkg/math/polynomial/, no new external imports beyondmath/big(stdlib).tis the share count — same complexity class as the existingLagrange. Att=21and a near-2^64 modulus the test runs in well under 100 ms.Lagrange/LagrangeFor/LagrangeSingle— same package, same names, no policy surprise).