Verifiable Secret Sharing for Coordinate Integrity
Plain Shamir sharing protects confidentiality and nothing else. A party that returns a corrupted share does not break the secrecy of the coordinate — it makes reconstruction return a different, perfectly well-formed coordinate, somewhere else entirely. There is no error, no exception, and no way to tell from the output which party lied. Verifiable secret sharing (VSS) closes that gap by publishing commitments against which every share can be checked before it is used. This page implements Feldman-style VSS for coordinate data, under secret sharing for coordinates in Secure Multi-Party Computation in Spatial Analytics.
Parameter Configuration and Calibration
primeandgenerator— the group the commitments live in. Feldman VSS commits to polynomial coefficients as , so the group must be one where discrete log is hard: a safe prime of at least 2048 bits, with a generator of a large prime-order subgroup. Reusing the small field used for the coordinate arithmetic itself is the classic mistake — it makes the commitments invertible and reveals the secret.threshold— unchanged from plain sharing. VSS adds integrity, not a different reconstruction rule. What changes is that a dealer or party who deviates is detected, so can be chosen for availability rather than padded to absorb suspected faults.commitment_publication— where the commitments go. They must reach every party over an authenticated channel, and they are public: they reveal nothing about the secret. Publishing them to an append-only log makes a later dispute resolvable without re-running the protocol.scaled_precision— the fixed-point scale of the coordinate. Verification operates on the scaled integer, so the scale is part of the committed statement. A party that verifies at a different scale will reject every share, which is a common and confusing misconfiguration.
| Property | Plain Shamir | Feldman VSS |
|---|---|---|
| Confidentiality below | yes | yes |
| Detects a corrupted share | no | yes, before reconstruction |
| Detects a cheating dealer | no | yes |
| Extra cost | — | one exponentiation per coefficient per share |
| Reveals anything extra | — | nothing (commitments are hiding under DL) |
Reference Implementation
from __future__ import annotations
import secrets
from dataclasses import dataclass
from typing import Sequence
# A 2048-bit safe prime would be used in production; this smaller one keeps the
# example runnable while preserving the structure.
P = (1 << 521) - 1
Q = (P - 1) // 2
G = 4 # a generator of the order-Q subgroup for this prime
class ShareRejected(RuntimeError):
"""A share failed verification against the published commitments."""
@dataclass(frozen=True)
class VSSDealing:
"""What the dealer publishes: shares (privately) and commitments (publicly)."""
shares: tuple[tuple[int, int], ...]
commitments: tuple[int, ...]
threshold: int
def deal(secret_scaled: int, *, threshold: int, parties: int) -> VSSDealing:
"""Split a scaled coordinate and publish coefficient commitments.
The commitments are g^a_i mod p. They are public and reveal nothing about the
coefficients under the discrete-log assumption, but they bind the dealer: any
share inconsistent with them is detectable by its holder alone.
"""
if not 1 < threshold <= parties:
raise ValueError("need 1 < threshold <= parties")
coeffs = [secret_scaled % Q] + [secrets.randbelow(Q) for _ in range(threshold - 1)]
shares = []
for x in range(1, parties + 1):
y = 0
for c in reversed(coeffs):
y = (y * x + c) % Q
shares.append((x, y))
commitments = tuple(pow(G, c, P) for c in coeffs)
return VSSDealing(tuple(shares), commitments, threshold)
def verify_share(share: tuple[int, int], commitments: Sequence[int]) -> bool:
"""Check g^y == prod(C_i ^ (x^i)) — the share lies on the committed polynomial.
Each party runs this on its OWN share, with no interaction and no knowledge of
any other share. That is what makes VSS practical: detection is local.
"""
x, y = share
lhs = pow(G, y, P)
rhs = 1
for i, c in enumerate(commitments):
rhs = (rhs * pow(c, pow(x, i), P)) % P
return lhs == rhs
def reconstruct(shares: Sequence[tuple[int, int]], commitments: Sequence[int],
*, threshold: int) -> int:
"""Verify every share, then interpolate. Refuses to use an unverified share.
Verifying first is the whole point: interpolating over a corrupted share yields
a valid-looking coordinate with no indication that anything went wrong.
"""
good = []
bad = []
for share in shares:
(good if verify_share(share, commitments) else bad).append(share)
if bad:
raise ShareRejected(
f"{len(bad)} share(s) failed verification: parties {[x for x, _ in bad]} — "
"reconstruction would have returned a plausible wrong coordinate"
)
if len(good) < threshold:
raise ShareRejected(f"only {len(good)} valid shares, threshold is {threshold}")
total = 0
picked = good[:threshold]
for i, (xi, yi) in enumerate(picked):
num, den = 1, 1
for j, (xj, _) in enumerate(picked):
if i != j:
num = (num * (-xj)) % Q
den = (den * (xi - xj)) % Q
total = (total + yi * num * pow(den, Q - 2, Q)) % Q
return total
Validation Checkpoint
def _validate() -> None:
lat_scaled = 51_507_351 # 51.507351 deg at 1e6 fixed point
dealing = deal(lat_scaled, threshold=3, parties=5)
# 1. Every honestly dealt share verifies.
assert all(verify_share(s, dealing.commitments) for s in dealing.shares)
# 2. Reconstruction from any t verified shares returns the exact coordinate.
assert reconstruct(dealing.shares[:3], dealing.commitments, threshold=3) == lat_scaled
assert reconstruct(dealing.shares[2:5], dealing.commitments, threshold=3) == lat_scaled
# 3. A tampered share fails verification — locally, with no interaction.
x, y = dealing.shares[1]
tampered = (x, (y + 12_345) % Q)
assert not verify_share(tampered, dealing.commitments)
# 4. Reconstruction refuses to proceed with a bad share, rather than returning
# a plausible wrong coordinate — the failure plain Shamir cannot detect.
mixed = [dealing.shares[0], tampered, dealing.shares[2]]
try:
reconstruct(mixed, dealing.commitments, threshold=3)
except ShareRejected as exc:
assert "failed verification" in str(exc)
else:
raise AssertionError("a corrupted share must be rejected before interpolation")
# 5. Demonstrate what plain Shamir would have done: interpolating the tampered
# set yields a well-formed but WRONG value.
def naive_interpolate(shares):
total = 0
for i, (xi, yi) in enumerate(shares):
num, den = 1, 1
for j, (xj, _) in enumerate(shares):
if i != j:
num = (num * (-xj)) % Q
den = (den * (xi - xj)) % Q
total = (total + yi * num * pow(den, Q - 2, Q)) % Q
return total
wrong = naive_interpolate(mixed)
assert wrong != lat_scaled and 0 <= wrong < Q
# 6. Fewer than t valid shares must fail loudly.
try:
reconstruct(dealing.shares[:2], dealing.commitments, threshold=3)
except ShareRejected:
pass
else:
raise AssertionError("insufficient shares must raise")
print("verifiable secret sharing: all assertions passed")
_validate()
Assertion 5 is the argument for VSS in one comparison. The naive interpolation over a tampered share set returns a number that is a perfectly valid field element and decodes to a perfectly plausible latitude — no exception, no anomaly, just the wrong place.
Incident Response and Edge Cases
- A party’s share fails verification. Do not reconstruct without it and do not silently exclude it either: record which party, notify, and reconstruct from the remaining verified shares if the threshold still allows. A repeated failure from one party is either a bug in their client or a deliberate act, and the two need different responses.
- Every share fails verification. Almost always a parameter mismatch — a different generator, prime, or fixed-point scale — rather than mass tampering. Check the published parameter version before escalating.
- The dealer is suspected of cheating. Feldman VSS binds the dealer to a single polynomial, so inconsistent shares are detectable by their holders. If parties disagree about what was published, the commitments need to have been posted to an authenticated, append-only channel — retrofitting that after a dispute is not possible.
- Verification is too slow at high volume. Each verification is modular exponentiations. Batch-verify a set of shares with random linear combinations, which reduces the cost substantially at a negligible soundness loss, and reserve individual verification for the failing batch.
- Commitments are treated as secret. They are not, and treating them so tends to mean they are distributed over an unauthenticated side channel instead. Publish them; the security rests on discrete log, not on hiding them.
Frequently Asked Questions
Do the commitments leak anything about the coordinate?
No, under the discrete-logarithm assumption: g raised to a coefficient reveals nothing about that coefficient in a group where discrete log is hard. That is exactly why the commitment group must be a large safe-prime group and not the small field used for the coordinate arithmetic.
When is plain Shamir sufficient?
When every share holder is trusted for integrity and transport is authenticated end to end — a single organisation's own services, for instance. As soon as shares cross an organisational boundary, or a share can be modified in storage, the silent-wrong-answer failure becomes reachable and VSS earns its cost.
How much does verification cost?
Roughly t modular exponentiations per share, which at 2048-bit parameters is milliseconds — negligible for interactive reconstruction and significant for bulk jobs. Batch verification over random linear combinations brings bulk costs down by an order of magnitude.
Does VSS protect against a party that refuses to participate?
No — that is availability, handled by the threshold. VSS detects a party that participates dishonestly. The two failure modes are independent, and a deployment needs both a threshold sized for dropouts and verification sized for integrity.
Related
- Secret Sharing for Coordinates — the parent method and its field arithmetic.
- Shamir Secret Sharing for GPS Coordinate Protection — the plain scheme this one hardens.
- Additive Secret Sharing for Coordinates in Python — the simpler sharing used where a threshold is not needed.
- Partition-Tolerant Share Routing in Python — delivering these shares across an unreliable network.
Up one level: Secret Sharing for Coordinates · Section: Secure Multi-Party Computation in Spatial Analytics.