Additive Secret Sharing for Coordinates in Python
Additive secret sharing is the workhorse of privacy-preserving spatial aggregation: a quantized coordinate is split into n random shares that sum to the secret modulo a prime p, and reconstruction requires every one of them. It is the cheap, non-threshold cousin of the polynomial scheme in the parent guide on secret sharing for coordinates — no Lagrange interpolation, no field inversion, just modular addition — which is exactly why it, and not Shamir secret sharing for GPS coordinate protection, is what backs high-throughput share-and-sum protocols across the Secure Multi-Party Computation in Spatial Analytics architecture. This page is the narrow, implementation-first treatment of the n-of-n additive construction over : how it differs from a -of- threshold, how to size the modulus for a summation rather than a single value, and how to add shares locally so an aggregator learns a regional total without ever seeing a coordinate.
Parameter Configuration and Calibration
Additive sharing trades Shamir’s fault tolerance for speed and composability. Where the threshold scheme lets any of shares rebuild the secret, an additive split of into with is strictly n-of-n: drop a single share and the secret is information-theoretically gone. That property is a liability for storage but an asset for summation, because shares add coordinate-wise — party can fold the -th share of a thousand records into one running total without ever talking to another party. This is the primitive underneath federated secure aggregation protocols, where the server must learn only the sum of client contributions.
Four knobs govern a correct deployment. Each is a correctness or privacy lever, not a performance dial.
| Parameter | Additive choice | Rationale |
|---|---|---|
num_parties (n) |
one per independent trust domain | No threshold exists, so every party is load-bearing; n is bounded by how many independent custodians you can actually run. |
modulus (p) |
(Mersenne prime) | Must exceed the largest possible aggregate, not one coordinate — see below. A Mersenne prime makes reduction a shift-and-add. |
SCALE |
(microdegrees, ~0.11 m) | Fixed-point integers keep addition exact; floats do not. Coarser than the telemetry guide’s nanodegrees because sums, not single fixes, dominate here. |
| per-axis handling | latitude and longitude shared separately | Independent randomness per axis stops recovery of one axis from constraining the other. |
The modulus rule is the one that catches people migrating from Shamir. There, only has to exceed a single biased coordinate plus its random coefficients. In additive share-and-sum, reconstruction is , so if the true aggregate of quantized coordinates exceeds it silently wraps and the recovered total is wrong by a multiple of . Size so that
where is the largest number of records you will ever sum. With you can aggregate well over coordinates before the field boundary is in play — ample for city- or nation-scale density counts, and small enough that shares are single 64-bit integers on the wire. Unlike Shamir, additive sharing does not require a prime at all (any modulus, including , gives a perfectly uniform share distribution); a prime is chosen only so the same field interoperates with multiplicative sub-protocols and matches the rest of the pipeline.
Reference Implementation
The AdditiveSharing class below is pure standard-library Python: secrets.randbelow for cryptographically uniform shares and integer arithmetic for everything else. It exposes exactly three operations — split, reconstruct, and the secure_sum that is the reason to reach for this scheme. Inline comments flag the lines where a choice has a privacy consequence.
from __future__ import annotations
import secrets
from typing import List, Sequence
SCALE: int = 10 ** 6 # microdegrees, ~0.11 m at the equator
BIAS: int = 180 * SCALE # shift [-180, 180] into non-negative field elements
def quantize(deg: float) -> int:
"""Map one WGS84 axis (decimal degrees) to a non-negative integer in Z_p.
Fixed-point integers make modular addition exact; sharing raw IEEE 754
floats would make the share sum non-deterministic and leak sub-metre
artefacts that can fingerprint a location.
"""
if not (-180.0 <= deg <= 180.0):
raise ValueError(f"axis out of WGS84 range: {deg}")
return round(deg * SCALE) + BIAS
class AdditiveSharing:
"""n-of-n additive secret sharing over Z_p.
A quantized coordinate `s` is split into `n` shares whose sum is
congruent to `s` mod p. Each share is uniform on its own, so any
strict subset of the n shares reveals nothing about `s`. Unlike a
Shamir t-of-n threshold scheme there is NO fault tolerance: all n
shares are required, and one lost share makes `s` unrecoverable.
"""
def __init__(self, num_parties: int, modulus: int = (1 << 61) - 1) -> None:
if num_parties < 2:
raise ValueError("additive sharing needs at least 2 parties")
if modulus <= 0:
raise ValueError("modulus must be positive")
self.n: int = num_parties
self.p: int = modulus
def split(self, secret: int) -> List[int]:
"""Split one quantized coordinate into n additive shares.
The first n-1 shares are drawn uniformly from Z_p with a CSPRNG;
the final share is fixed to close the sum. That uniform sampling
is the entire security argument — never seed it from a coordinate.
"""
if not (0 <= secret < self.p):
raise ValueError("secret must lie in [0, p)")
shares: List[int] = [secrets.randbelow(self.p) for _ in range(self.n - 1)]
last: int = (secret - sum(shares)) % self.p # closes sum to s mod p
shares.append(last)
return shares
def reconstruct(self, shares: Sequence[int]) -> int:
"""Recover the secret. Requires ALL n shares — there is no threshold."""
if len(shares) != self.n:
raise ValueError(
f"n-of-n scheme: need all {self.n} shares, got {len(shares)}"
)
return sum(shares) % self.p
def secure_sum(self, share_vectors: Sequence[Sequence[int]]) -> int:
"""Aggregate several secrets from their shares without reconstructing any.
`share_vectors[k]` is the n-share vector of secret k. Party j adds
the j-th share of every secret locally (a column sum); only those n
party-local partial sums are ever revealed, and their total equals
the plaintext sum of the secrets mod p. No single coordinate is
materialised at any point in the protocol.
"""
partial_sums: List[int] = [0] * self.n
for vec in share_vectors:
if len(vec) != self.n:
raise ValueError("every share vector must hold n shares")
for j in range(self.n):
# this update happens ON party j's node — shares never pool centrally
partial_sums[j] = (partial_sums[j] + vec[j]) % self.p
return sum(partial_sums) % self.p
The design keeps the secure_sum reduction explicit so it maps onto real infrastructure: in production the inner partial_sums[j] update executes on party ’s own node, and only the scalars partial_sums cross the network. Because those partials are themselves an additive sharing of the aggregate, revealing all of them discloses only the total — the same reason a lone share discloses nothing about a single record.
Validation Checkpoint
Additive sharing fails silently on two axes — a wrapped aggregate returns a plausible wrong total, and a mis-sampled share weakens the guarantee without raising — so the invariants below belong in CI, not in a manual review. The harness reuses the class above.
def _validate() -> None:
p: int = (1 << 61) - 1
scheme = AdditiveSharing(num_parties=4, modulus=p)
# 1. Shares sum to the secret mod p, at the field edges and a real point.
for secret in (0, 1, p - 1, quantize(40.748817), quantize(-73.985428)):
shares = scheme.split(secret)
assert len(shares) == 4
assert scheme.reconstruct(shares) == secret
assert sum(shares) % p == secret
# 2. n-of-n: any n-1 shares must NOT reconstruct (there is no threshold).
shares = scheme.split(123_456_789)
try:
scheme.reconstruct(shares[:-1])
except ValueError:
pass
else:
raise AssertionError("n-1 shares must not reconstruct")
# 3. The first n-1 shares are uniform and independent of the secret:
# two different secrets can be split under an identical (n-1)-prefix,
# with only the closing share differing. This is why a strict subset
# leaks nothing — it is consistent with every possible coordinate.
prefix = [secrets.randbelow(p) for _ in range(scheme.n - 1)] # any uniform prefix
for target in (quantize(51.5074), quantize(-0.1278)):
closing = (target - sum(prefix)) % p # unique closing share
assert scheme.reconstruct(prefix + [closing]) == target
# 4. secure_sum over several coordinates equals the plaintext sum mod p,
# without any coordinate being reconstructed.
plain = [quantize(v) for v in (40.7128, 34.0522, 51.5074, -33.8688)]
vectors = [scheme.split(s) for s in plain]
assert scheme.secure_sum(vectors) == sum(plain) % p
print("all additive-sharing invariants hold")
if __name__ == "__main__":
_validate()
Assertion 3 is the structural heart of the scheme: any shares can be extended to any secret by an appropriate closing share, so they carry zero information. Assertion 4 confirms the share-and-sum property that makes additive sharing worth using — and if it ever fails while individual reconstruct calls pass, suspect a modulus too small for the aggregate.
Incident Response and Edge Cases
Additive sharing has fewer moving parts than Shamir, and its failure modes cluster around its two defining traits: strict n-of-n recovery and modular arithmetic on sums.
- Lost or unavailable share. A single offline party makes the secret and every aggregate that includes it unrecoverable — there is no interpolation to route around the gap. Detection: fewer than
nacknowledgements at the round timeout. Remediation: additive sharing is the wrong primitive if availability matters; either fall back to a Shamir layer for at-rest storage, or apply SecAgg-style pairwise dropout masks so a late party’s contribution can be recovered by the survivors, as covered under secure aggregation protocols. - Modulus overflow on the aggregate. Summing more records than wraps the total;
reconstructandsecure_sumreturn a value off by a multiple of with no error. Detection: an aggregate whose de-quantized value falls outside the plausible coordinate-sum range. Remediation: raise so , or shard the sum into bounded batches whose partial totals cannot each wrap. - Negative-bias sign error. Coordinates that skip the
BIASoffset enter the field as small residues near zero for southern/western points, and their additive shares still sum correctly — but de-quantizing the aggregate double-counts or drops the bias, skewing the mean. Detection: assert every secret is in afterquantize, and subtract exactly when converting a sum of coordinates back to degrees. Remediation: centralise bias handling inquantize/de-quantize and unit-test the southern-hemisphere sum. - Weak randomness on the first shares. Seeding
splitfrom anything but a CSPRNG (a PRNG seeded by a timestamp, a reused nonce) makes the shares predictable and collapses the information-theoretic guarantee. Detection: audit the entropy source feedingsplit. Remediation: keepsecrets.randbelow(or an HSM DRBG); never derive share randomness from the coordinate itself.
Frequently Asked Questions
When should I use additive sharing instead of Shamir's threshold scheme?
Use additive n-of-n sharing when the operation is summation across many parties or records — density counts, federated gradient aggregation, regional totals — because shares add locally and reconstruction is a single modular sum with no interpolation. Use the Shamir t-of-n threshold when you need fault-tolerant storage or access control, where a quorum smaller than n must be able to reconstruct. The two often compose: additive sharing for the hot aggregation path, Shamir for durable custody of the same secret.
Why does additive sharing have no fault tolerance, and how do I mitigate a lost share?
Because the shares are constrained only by summing to the secret mod p, any n-1 of them are jointly uniform and pin down nothing — which also means the missing share cannot be inferred. There is no polynomial to interpolate through the gaps as in Shamir. Mitigate it at the protocol layer: use SecAgg pairwise masks so surviving parties can cancel a dropped client's contribution, keep a separate Shamir copy for at-rest recovery, or provision enough redundancy that a round can simply be retried.
Does additive secret sharing need a prime modulus?
No. Additive sharing is uniform over any modulus, including a power of two, because the security argument only needs the first n-1 shares drawn uniformly at random. A prime — a Mersenne prime such as 2^61 - 1 for cheap reduction — is chosen so the same field interoperates with multiplicative MPC sub-protocols and with a Shamir layer that genuinely requires a field for Lagrange inversion, not because addition needs it.
How large must the modulus be for a secure sum of coordinates?
Large enough that the true aggregate never exceeds it: p must be greater than the maximum record count times the maximum biased quantized coordinate. With microdegree scaling a coordinate is under 3.6e8, so p = 2^61 - 1 safely sums over a billion coordinates before wrapping. If the plaintext total ever exceeds p it wraps modulo p and the reconstructed sum is silently wrong, so size p against your worst-case batch, not against a single value.
Related
- Secret Sharing for Coordinates — the parent guide covering the full quantize → split → distribute → reconstruct pipeline this technique plugs into.
- Shamir Secret Sharing for GPS Coordinate Protection — the t-of-n threshold sibling to use when fault tolerance, not summation, is the goal.
- Secure Aggregation Protocols — where additive share-and-sum becomes SecAgg with dropout-resilient masking.
- Coordinate Masking Protocols — the deterministic front end that normalises and quantises geometry before it is shared.
- Homomorphic Encryption Basics — the compute-on-ciphertext alternative when you must evaluate more than sums without reconstructing.
Up: Secret Sharing for Coordinates · Secure Multi-Party Computation in Spatial Analytics