2026-09-26 18:34:05 -06:00
|
|
|
|
# Point-programmable vector commitments {#ppvc_manual}
|
|
|
|
|
|
|
|
|
|
|
|
A point-programmable vector commitment binds a vector
|
|
|
|
|
|
`x` in `(Z/2^s Z)^n` and still lets one hidden coordinate be chosen
|
|
|
|
|
|
after the commitment is published.
|
|
|
|
|
|
|
|
|
|
|
|
`n` is a power of two, the bit length of the input type, and at most
|
2026-09-26 23:51:06 -06:00
|
|
|
|
2^20, because evaluation stores one entry per domain point. `s` is
|
|
|
|
|
|
the `Width` parameter, from 1 to 64.
|
2026-09-26 18:34:05 -06:00
|
|
|
|
The manual construction is `dpf::ppvc`. `dpf::k_ppvc<K, ...>` is `K`
|
|
|
|
|
|
independent copies of that object.
|
|
|
|
|
|
|
|
|
|
|
|
The committer samples an index `i` and builds `s` aligned 1-bit DPF
|
|
|
|
|
|
pairs there, the same point key as [DPF basics](@ref basics_body).
|
|
|
|
|
|
Both roots of every pair are bound with a Naor commitment under a
|
|
|
|
|
|
public matrix `A`. Opening releases one key from each pair, together
|
|
|
|
|
|
with a shift `delta = xi - i`.
|
|
|
|
|
|
|
|
|
|
|
|
Off `i`, the two keys of a pair evaluate to the same bit.
|
|
|
|
|
|
At `i`, they evaluate to opposite bits.
|
|
|
|
|
|
Choosing the side therefore writes an arbitrary value into that one
|
|
|
|
|
|
coordinate and leaves every other coordinate fixed.
|
|
|
|
|
|
The shift moves the written coordinate from `i` onto the public target
|
|
|
|
|
|
`xi`. The opening carries `delta`, not `i` and not `xi`.
|
|
|
|
|
|
|
|
|
|
|
|
`open(st, mu, tau, xi)` has two modes.
|
|
|
|
|
|
|
|
|
|
|
|
- `mu = 0` programs the coordinate. After rotation, entry `xi` equals `tau`.
|
|
|
|
|
|
- `mu = 1` programs the sum of every coordinate. That sum equals `tau`.
|
|
|
|
|
|
|
|
|
|
|
|
Those two maps are bijections on `Z/2^s Z`. Programming one of them
|
|
|
|
|
|
programs the other.
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
using scheme = dpf::ppvc<std::uint8_t, 8>;
|
|
|
|
|
|
const auto pp = scheme::setup();
|
|
|
|
|
|
const auto [com, st] = scheme::commit(pp);
|
|
|
|
|
|
|
|
|
|
|
|
const std::uint8_t xi = 40;
|
|
|
|
|
|
const auto op = scheme::open(st, 0, 0x5a, xi);
|
|
|
|
|
|
const auto x = scheme::eval(op); // hidden indexing
|
|
|
|
|
|
const auto rotated = scheme::eval_rotated(op); // value 0x5a sits at xi
|
|
|
|
|
|
const bool ok = scheme::accept(pp, com, op, x, xi);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`setup` samples `A`. `setup_from_seed` expands one 128-bit seed into the
|
|
|
|
|
|
same matrix, which is the common random string when many sessions share
|
|
|
|
|
|
it. `commit` samples `i`. `commit_at` uses an index the caller already
|
|
|
|
|
|
chose. The shift hides `i` when that index was sampled independently of
|
|
|
|
|
|
`xi`. `commit_from_seed` and `commit_at_from_seed` rerun key generation
|
|
|
|
|
|
from a replica seed. Seed expansion keeps its counter in thread-local
|
|
|
|
|
|
storage, so two expansions on one thread must not overlap.
|
|
|
|
|
|
|
|
|
|
|
|
## What an opening proves {#ppvc_verify}
|
|
|
|
|
|
|
|
|
|
|
|
`verify` checks each opened root against its Naor string.
|
|
|
|
|
|
`accept` also checks the programmed statement: the rotated coordinate
|
|
|
|
|
|
when `mu` is 0, the column sum when `mu` is 1.
|
|
|
|
|
|
|
|
|
|
|
|
Correction words travel with the opened key. They are not inside the
|
|
|
|
|
|
commitment. `verify` sees one side of each pair.
|
|
|
|
|
|
`check_well_formed` is the check on a replica the committer still holds:
|
|
|
|
|
|
shared correction words, party bits 0 and 1, both Naor openings, and
|
|
|
|
|
|
exactly one place where the two keys disagree, at the recorded index,
|
|
|
|
|
|
with payload 1.
|
|
|
|
|
|
`audit` expands a seed and accepts when the published commitment matches
|
|
|
|
|
|
that expansion and the replica is well formed.
|
2026-09-26 23:51:06 -06:00
|
|
|
|
When many replica seeds sit as leaves of a GGM tree, the audit opening of
|
|
|
|
|
|
the pool is a [`dpf::pprf_copath`](@ref dpf/pprf.hpp) built by
|
|
|
|
|
|
`dpf::puncture(master, live…, /*program_hidden=*/false)`: every audited
|
|
|
|
|
|
leaf re-expands with `dpf::pprf_eval`, and a live seed is never among the
|
|
|
|
|
|
published nodes. Sampling the audit set and combining live copies stay in
|
|
|
|
|
|
the protocol, not in this library.
|
2026-09-26 18:34:05 -06:00
|
|
|
|
|
|
|
|
|
|
`k_ppvc` asks for the same checks on every copy, and for distinct hidden
|
|
|
|
|
|
indices. `combine_rotated` adds the rotated vectors in `Z/2^s Z`.
|
|
|
|
|
|
Reprogramming copy `r` changes coordinate `xi[r]` of that sum.
|
|
|
|
|
|
|
|
|
|
|
|
The commitment is `2 * s * (3 * 128 + Sigma)` bits.
|
|
|
|
|
|
`Sigma` defaults to 128 and must be a multiple of 8.
|
|
|
|
|
|
The generator is `dpf::prg::aes128` unless another 128-bit PRG is named.
|
|
|
|
|
|
|
|
|
|
|
|
**Defined in**\n
|
|
|
|
|
|
@ref dpf/ppvc.hpp
|
|
|
|
|
|
|
|
|
|
|
|
**Try**\n
|
|
|
|
|
|
@ref mwe/ppvc.cpp
|
|
|
|
|
|
|
2026-09-26 23:51:06 -06:00
|
|
|
|
Naor's string commitment is Moni Naor, [Bit Commitment Using Pseudorandomness](@ref bib_naor), Journal of Cryptology 1991.
|
2026-09-26 18:34:05 -06:00
|
|
|
|
The point keys are the Boyle–Gilboa–Ishai construction named in
|
|
|
|
|
|
[DPF basics](@ref point_functions).
|