Skip to content

Commit

Permalink
Prio3: Merge the measurement and proof share seed (*)
Browse files Browse the repository at this point in the history
Replace the measurement and proof share seeds in the helper's input
share with a single seed from which both the measurement and proof share
are derived. Usage of the same seed for both shares is safe as long as
we have domain separation between them.
  • Loading branch information
cjpatton committed Sep 5, 2024
1 parent 369f99d commit ea39dcc
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 234 deletions.
78 changes: 31 additions & 47 deletions draft-irtf-cfrg-vdaf.md
Original file line number Diff line number Diff line change
Expand Up @@ -2489,14 +2489,14 @@ subsections. These methods refer to constants enumerated in
| Parameter | Value |
|:------------------|:------------------------------------------------|
| `VERIFY_KEY_SIZE` | `Xof.SEED_SIZE` |
| `RAND_SIZE` | `Xof.SEED_SIZE * (1 + 2 * (SHARES - 1)) if flp.JOINT_RAND_LEN == 0 else Xof.SEED_SIZE * (1 + 2 * (SHARES - 1) + SHARES)` |
| `RAND_SIZE` | `Xof.SEED_SIZE * SHARES if flp.JOINT_RAND_LEN == 0 else 2 * Xof.SEED_SIZE * SHARES` |
| `NONCE_SIZE` | `16` |
| `ROUNDS` | `1` |
| `SHARES` | in `[2, 256)` |
| `Measurement` | `Flp.Measurement` |
| `AggParam` | `None` |
| `PublicShare` | `Optional[list[bytes]]` |
| `InputShare` | `tuple[list[F], list[F], Optional[bytes]] | tuple[bytes, bytes, Optional[bytes]]` |
| `InputShare` | `tuple[list[F], list[F], Optional[bytes]] | tuple[bytes, Optional[bytes]]` |
| `OutShare` | `list[F]` |
| `AggShare` | `list[F]` |
| `AggResult` | `Flp.AggResult` |
Expand Down Expand Up @@ -2588,23 +2588,15 @@ def shard_without_joint_rand(
seeds: list[bytes]) -> tuple[
Optional[list[bytes]],
list[Prio3InputShare[F]]]:
k_helper_seeds, seeds = front((self.SHARES - 1) * 2, seeds)
k_helper_meas_shares = [
k_helper_seeds[i]
for i in range(0, (self.SHARES - 1) * 2, 2)
]
k_helper_proofs_shares = [
k_helper_seeds[i]
for i in range(1, (self.SHARES - 1) * 2, 2)
]
k_helper_shares, seeds = front(self.SHARES - 1, seeds)
(k_prove,), seeds = front(1, seeds)

# Shard the encoded measurement into shares.
leader_meas_share = meas
for j in range(self.SHARES - 1):
leader_meas_share = vec_sub(
leader_meas_share,
self.helper_meas_share(j + 1, k_helper_meas_shares[j]),
self.helper_meas_share(j + 1, k_helper_shares[j]),
)

# Generate and shard each proof into shares.
Expand All @@ -2619,7 +2611,7 @@ def shard_without_joint_rand(
leader_proofs_share,
self.helper_proofs_share(
j + 1,
k_helper_proofs_shares[j],
k_helper_shares[j],
),
)

Expand All @@ -2633,8 +2625,7 @@ def shard_without_joint_rand(
))
for j in range(self.SHARES - 1):
input_shares.append((
k_helper_meas_shares[j],
k_helper_proofs_shares[j],
k_helper_shares[j],
None,
))
return (None, input_shares)
Expand All @@ -2648,9 +2639,9 @@ The steps in this method are as follows:
1. Encode each measurement and shares of each proof into an input share

Notice that only one pair of measurement and proof(s) share (called the
"leader" shares above) are vectors of field elements. The other shares
(called the "helper" shares) are represented instead by XOF seeds, which
are expanded into vectors of field elements.
"leader" shares above) are vectors of field elements. The other shares (called
the "helper" shares) are represented instead by an XOF seed, which is expanded
into vectors of field elements.

The methods on `Prio3` for deriving the prover randomness, measurement shares,
and proof shares and the methods for encoding the input shares are defined in
Expand All @@ -2669,18 +2660,14 @@ def shard_with_joint_rand(
seeds: list[bytes]) -> tuple[
Optional[list[bytes]],
list[Prio3InputShare[F]]]:
k_helper_seeds, seeds = front((self.SHARES - 1) * 3, seeds)
k_helper_meas_shares = [
k_helper_seeds[i]
for i in range(0, (self.SHARES - 1) * 3, 3)
]
k_helper_proofs_shares = [
k_helper_seeds, seeds = front((self.SHARES - 1) * 2, seeds)
k_helper_shares = [
k_helper_seeds[i]
for i in range(1, (self.SHARES - 1) * 3, 3)
for i in range(0, (self.SHARES - 1) * 2, 2)
]
k_helper_blinds = [
k_helper_seeds[i]
for i in range(2, (self.SHARES - 1) * 3, 3)
for i in range(1, (self.SHARES - 1) * 2, 2)
]
(k_leader_blind, k_prove), seeds = front(2, seeds)

Expand All @@ -2690,7 +2677,7 @@ def shard_with_joint_rand(
k_joint_rand_parts = []
for j in range(self.SHARES - 1):
helper_meas_share = self.helper_meas_share(
j + 1, k_helper_meas_shares[j])
j + 1, k_helper_shares[j])
leader_meas_share = vec_sub(leader_meas_share,
helper_meas_share)
k_joint_rand_parts.append(self.joint_rand_part(
Expand Down Expand Up @@ -2718,7 +2705,7 @@ def shard_with_joint_rand(
leader_proofs_share,
self.helper_proofs_share(
j + 1,
k_helper_proofs_shares[j],
k_helper_shares[j],
),
)

Expand All @@ -2733,20 +2720,19 @@ def shard_with_joint_rand(
))
for j in range(self.SHARES - 1):
input_shares.append((
k_helper_meas_shares[j],
k_helper_proofs_shares[j],
k_helper_shares[j],
k_helper_blinds[j],
))
return (k_joint_rand_parts, input_shares)
~~~
{: #prio3-shard-with-joint-rand title="Sharding an encoded measurement with joint randomness."}

The difference between this procedure and previous one is that here we compute
joint randomnesses `joint_rands`, split it into multiple `joint_rand`,
and pass each `joint_rand` to the proof generationg algorithm.
(In {{prio3-shard-without-joint-rand}} the joint randomness is the empty
vector, `[]`.) This requires generating an additional value, called the
"blind", that is incorporated into each input share.
joint randomnesses `joint_rands`, split it into multiple `joint_rand`, and pass
each `joint_rand` to the proof generationg algorithm. (In
{{prio3-shard-without-joint-rand}} the joint randomness is the empty vector,
`[]`.) This requires generating an additional value, called the "blind", that
is incorporated into each input share.

The joint randomness computation involves the following steps:

Expand Down Expand Up @@ -2982,15 +2968,14 @@ def expand_input_share(
list[F],
list[F],
Optional[bytes]]:
(meas_share, proofs_share, k_blind) = input_share
if agg_id > 0:
assert isinstance(meas_share, bytes)
assert isinstance(proofs_share, bytes)
meas_share = self.helper_meas_share(agg_id, meas_share)
proofs_share = self.helper_proofs_share(agg_id, proofs_share)
assert len(input_share) == 2
(k_share, k_blind) = input_share
meas_share = self.helper_meas_share(agg_id, k_share)
proofs_share = self.helper_proofs_share(agg_id, k_share)
else:
assert isinstance(meas_share, list)
assert isinstance(proofs_share, list)
assert len(input_share) == 3
(meas_share, proofs_share, k_blind) = input_share
return (meas_share, proofs_share, k_blind)

def prove_rands(self, k_prove: bytes) -> list[F]:
Expand Down Expand Up @@ -3086,9 +3071,9 @@ Aggregator's blind for generating its joint randomness part.
In addition, the encoding of the input shares depends on which aggregator is
receiving the message. If the aggregator ID is `0`, then the input share
includes the full measurement and share of proof(s). Otherwise, if the aggregator ID
is greater than `0`, then the measurement and shares of proof(s) are represented
by XOF seeds. We shall call the former the "Leader" and the latter the
"Helpers".
is greater than `0`, then the measurement and shares of proof(s) are
represented by an XOF seed. We shall call the former the "Leader" and the
latter the "Helpers".

In total there are four variants of the input share. When joint randomness is
not used, the Leader's share is structured as follows:
Expand All @@ -3105,8 +3090,7 @@ as follows:

~~~ tls-presentation
struct {
Prio3Seed k_meas_share;
Prio3Seed k_proofs_share;
Prio3Seed k_share;
} Prio3HelperShare;
~~~

Expand Down
64 changes: 24 additions & 40 deletions poc/vdaf_poc/vdaf_prio3.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
Optional[bytes], # joint randomness blind
] | \
tuple[ # helper input share
bytes, # measurement share seed
bytes, # proof share seed
bytes, # measurement and proof share seed
Optional[bytes], # joint randomness blind
]
Prio3PrepState: TypeAlias = tuple[
Expand Down Expand Up @@ -82,7 +81,7 @@ def __init__(
self.PROOFS = num_proofs
self.flp = flp

rand_size = (1 + 2 * (shares - 1)) * self.xof.SEED_SIZE
rand_size = shares * self.xof.SEED_SIZE
if flp.JOINT_RAND_LEN > 0:
rand_size += shares * self.xof.SEED_SIZE
self.RAND_SIZE = rand_size
Expand Down Expand Up @@ -270,23 +269,15 @@ def shard_without_joint_rand(
seeds: list[bytes]) -> tuple[
Optional[list[bytes]],
list[Prio3InputShare[F]]]:
k_helper_seeds, seeds = front((self.SHARES - 1) * 2, seeds)
k_helper_meas_shares = [
k_helper_seeds[i]
for i in range(0, (self.SHARES - 1) * 2, 2)
]
k_helper_proofs_shares = [
k_helper_seeds[i]
for i in range(1, (self.SHARES - 1) * 2, 2)
]
k_helper_shares, seeds = front(self.SHARES - 1, seeds)
(k_prove,), seeds = front(1, seeds)

# Shard the encoded measurement into shares.
leader_meas_share = meas
for j in range(self.SHARES - 1):
leader_meas_share = vec_sub(
leader_meas_share,
self.helper_meas_share(j + 1, k_helper_meas_shares[j]),
self.helper_meas_share(j + 1, k_helper_shares[j]),
)

# Generate and shard each proof into shares.
Expand All @@ -301,7 +292,7 @@ def shard_without_joint_rand(
leader_proofs_share,
self.helper_proofs_share(
j + 1,
k_helper_proofs_shares[j],
k_helper_shares[j],
),
)

Expand All @@ -315,8 +306,7 @@ def shard_without_joint_rand(
))
for j in range(self.SHARES - 1):
input_shares.append((
k_helper_meas_shares[j],
k_helper_proofs_shares[j],
k_helper_shares[j],
None,
))
return (None, input_shares)
Expand All @@ -333,18 +323,14 @@ def shard_with_joint_rand(
seeds: list[bytes]) -> tuple[
Optional[list[bytes]],
list[Prio3InputShare[F]]]:
k_helper_seeds, seeds = front((self.SHARES - 1) * 3, seeds)
k_helper_meas_shares = [
k_helper_seeds[i]
for i in range(0, (self.SHARES - 1) * 3, 3)
]
k_helper_proofs_shares = [
k_helper_seeds, seeds = front((self.SHARES - 1) * 2, seeds)
k_helper_shares = [
k_helper_seeds[i]
for i in range(1, (self.SHARES - 1) * 3, 3)
for i in range(0, (self.SHARES - 1) * 2, 2)
]
k_helper_blinds = [
k_helper_seeds[i]
for i in range(2, (self.SHARES - 1) * 3, 3)
for i in range(1, (self.SHARES - 1) * 2, 2)
]
(k_leader_blind, k_prove), seeds = front(2, seeds)

Expand All @@ -354,7 +340,7 @@ def shard_with_joint_rand(
k_joint_rand_parts = []
for j in range(self.SHARES - 1):
helper_meas_share = self.helper_meas_share(
j + 1, k_helper_meas_shares[j])
j + 1, k_helper_shares[j])
leader_meas_share = vec_sub(leader_meas_share,
helper_meas_share)
k_joint_rand_parts.append(self.joint_rand_part(
Expand Down Expand Up @@ -382,7 +368,7 @@ def shard_with_joint_rand(
leader_proofs_share,
self.helper_proofs_share(
j + 1,
k_helper_proofs_shares[j],
k_helper_shares[j],
),
)

Expand All @@ -397,8 +383,7 @@ def shard_with_joint_rand(
))
for j in range(self.SHARES - 1):
input_shares.append((
k_helper_meas_shares[j],
k_helper_proofs_shares[j],
k_helper_shares[j],
k_helper_blinds[j],
))
return (k_joint_rand_parts, input_shares)
Expand Down Expand Up @@ -438,15 +423,14 @@ def expand_input_share(
list[F],
list[F],
Optional[bytes]]:
(meas_share, proofs_share, k_blind) = input_share
if agg_id > 0:
assert isinstance(meas_share, bytes)
assert isinstance(proofs_share, bytes)
meas_share = self.helper_meas_share(agg_id, meas_share)
proofs_share = self.helper_proofs_share(agg_id, proofs_share)
assert len(input_share) == 2
(k_share, k_blind) = input_share
meas_share = self.helper_meas_share(agg_id, k_share)
proofs_share = self.helper_proofs_share(agg_id, k_share)
else:
assert isinstance(meas_share, list)
assert isinstance(proofs_share, list)
assert len(input_share) == 3
(meas_share, proofs_share, k_blind) = input_share
return (meas_share, proofs_share, k_blind)

def prove_rands(self, k_prove: bytes) -> list[F]:
Expand Down Expand Up @@ -501,15 +485,15 @@ def test_vec_set_type_param(self, test_vec: dict[str, Any]) -> list[str]:
return self.flp.test_vec_set_type_param(test_vec)

def test_vec_encode_input_share(self, input_share: Prio3InputShare[F]) -> bytes:
(meas_share, proofs_share, k_blind) = input_share
encoded = bytes()
if isinstance(meas_share, list) and isinstance(proofs_share, list): # Leader
if len(input_share) == 3: # Leader
(meas_share, proofs_share, k_blind) = input_share
assert len(proofs_share) == self.flp.PROOF_LEN * self.PROOFS
encoded += self.flp.field.encode_vec(meas_share)
encoded += self.flp.field.encode_vec(proofs_share)
elif isinstance(meas_share, bytes) and isinstance(proofs_share, bytes): # Helper
encoded += meas_share
encoded += proofs_share
elif len(input_share) == 2: # Helper
(k_share, k_blind) = input_share
encoded += k_share
if k_blind is not None: # joint randomness used
encoded += k_blind
return encoded
Expand Down
10 changes: 5 additions & 5 deletions test_vec/12/Prio3Count_0.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"prep": [
{
"input_shares": [
"1e01d12f55efd3a62c1fe0f84eee24d87103c377ad57c12f93e4cefe55c4be9887b14d4013d2ce25b33e6b12f83705b6",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
"1e01d12f55efd3a65a54324a0a06326e2b8a0d3c04aaf2e0ea7ca7985d4c6b88fb516fbfff3828a5e571618a455e7a7b",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
],
"measurement": 1,
"nonce": "000102030405060708090a0b0c0d0e0f",
Expand All @@ -26,12 +26,12 @@
],
"prep_shares": [
[
"a1701ba1e53a218282f4334b777c29537e24f54d9a9643577138dff393c9b1f2",
"608fe45e19c5de7d21548ac9296dca8f39faf4bbe728e4046d6a003c03be9efa"
"b79bc8334d82e9b7be43d343997ec3a4d72bc5b139413ec45f7039d8f47e2bd5",
"4a6437ccb17d1648443bcfb289f0a4499dbcb95e6c241f782548cc4536fec3c1"
]
],
"public_share": "",
"rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
"rand": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
}
],
"shares": 2,
Expand Down
Loading

0 comments on commit ea39dcc

Please sign in to comment.