A commitment can be published before its hidden coordinate is chosen. Opening one side of each aligned 1-bit DPF pair sets that coordinate or the vector sum, and a shift moves it onto a public index. Co-authored-by: Cursor <cursoragent@cursor.com>
3.5 KiB
Point-programmable vector commitments
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
2^16. s is the Width parameter, from 1 to 64.
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 = 0programs the coordinate. After rotation, entryxiequalstau.mu = 1programs the sum of every coordinate. That sum equalstau.
Those two maps are bijections on Z/2^s Z. Programming one of them
programs the other.
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
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.
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
Naor's string commitment is Moni Naor, "Bit Commitment Using Pseudorandomness," Journal of Cryptology 4(2), 1991, pp. 151–158. The point keys are the Boyle–Gilboa–Ishai construction named in [DPF basics](@ref point_functions).