zkSNARK: Enforce spend-authority of input notes.
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
#include "zcash/circuit/utils.tcc"
|
#include "zcash/circuit/utils.tcc"
|
||||||
|
#include "zcash/circuit/prfs.tcc"
|
||||||
|
#include "zcash/circuit/note.tcc"
|
||||||
|
|
||||||
template<typename FieldT, size_t NumInputs, size_t NumOutputs>
|
template<typename FieldT, size_t NumInputs, size_t NumOutputs>
|
||||||
class joinsplit_gadget : gadget<FieldT> {
|
class joinsplit_gadget : gadget<FieldT> {
|
||||||
@@ -19,6 +21,9 @@ private:
|
|||||||
// Aux inputs
|
// Aux inputs
|
||||||
pb_variable<FieldT> ZERO;
|
pb_variable<FieldT> ZERO;
|
||||||
|
|
||||||
|
// Input note gadgets
|
||||||
|
boost::array<std::shared_ptr<input_note_gadget<FieldT>>, NumInputs> zk_input_notes;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
joinsplit_gadget(protoboard<FieldT> &pb) : gadget<FieldT>(pb) {
|
joinsplit_gadget(protoboard<FieldT> &pb) : gadget<FieldT>(pb) {
|
||||||
// Verification
|
// Verification
|
||||||
@@ -67,7 +72,14 @@ public:
|
|||||||
// to be one automatically for us, and is known as `ONE`.
|
// to be one automatically for us, and is known as `ONE`.
|
||||||
ZERO.allocate(pb);
|
ZERO.allocate(pb);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < NumInputs; i++) {
|
||||||
|
// Input note gadget for commitments, hmacs, nullifiers,
|
||||||
|
// and spend authority.
|
||||||
|
zk_input_notes[i].reset(new input_note_gadget<FieldT>(
|
||||||
|
pb,
|
||||||
|
ZERO
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void generate_r1cs_constraints() {
|
void generate_r1cs_constraints() {
|
||||||
@@ -77,6 +89,11 @@ public:
|
|||||||
|
|
||||||
// Constrain `ZERO`
|
// Constrain `ZERO`
|
||||||
generate_r1cs_equals_const_constraint<FieldT>(this->pb, ZERO, FieldT::zero(), "ZERO");
|
generate_r1cs_equals_const_constraint<FieldT>(this->pb, ZERO, FieldT::zero(), "ZERO");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < NumInputs; i++) {
|
||||||
|
// Constrain the JoinSplit input constraints.
|
||||||
|
zk_input_notes[i]->generate_r1cs_constraints();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void generate_r1cs_witness(
|
void generate_r1cs_witness(
|
||||||
@@ -91,6 +108,11 @@ public:
|
|||||||
// Witness `zero`
|
// Witness `zero`
|
||||||
this->pb.val(ZERO) = FieldT::zero();
|
this->pb.val(ZERO) = FieldT::zero();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < NumInputs; i++) {
|
||||||
|
// Witness the input information.
|
||||||
|
zk_input_notes[i]->generate_r1cs_witness(inputs[i].key, inputs[i].note);
|
||||||
|
}
|
||||||
|
|
||||||
// This happens last, because only by now are all the
|
// This happens last, because only by now are all the
|
||||||
// verifier inputs resolved.
|
// verifier inputs resolved.
|
||||||
unpacker->generate_r1cs_witness_from_bits();
|
unpacker->generate_r1cs_witness_from_bits();
|
||||||
|
|||||||
82
src/zcash/circuit/note.tcc
Normal file
82
src/zcash/circuit/note.tcc
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
template<typename FieldT>
|
||||||
|
class note_gadget : public gadget<FieldT> {
|
||||||
|
public:
|
||||||
|
pb_variable_array<FieldT> value;
|
||||||
|
std::shared_ptr<digest_variable<FieldT>> r;
|
||||||
|
|
||||||
|
note_gadget(protoboard<FieldT> &pb) : gadget<FieldT>(pb) {
|
||||||
|
value.allocate(pb, 64);
|
||||||
|
r.reset(new digest_variable<FieldT>(pb, 256, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_r1cs_constraints() {
|
||||||
|
for (size_t i = 0; i < 64; i++) {
|
||||||
|
generate_boolean_r1cs_constraint<FieldT>(
|
||||||
|
this->pb,
|
||||||
|
value[i],
|
||||||
|
"boolean_value"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
r->generate_r1cs_constraints();
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_r1cs_witness(const Note& note) {
|
||||||
|
r->bits.fill_with_bits(this->pb, uint256_to_bool_vector(note.r));
|
||||||
|
value.fill_with_bits(this->pb, uint64_to_bool_vector(note.value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename FieldT>
|
||||||
|
class input_note_gadget : public note_gadget<FieldT> {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<digest_variable<FieldT>> a_sk;
|
||||||
|
std::shared_ptr<digest_variable<FieldT>> a_pk;
|
||||||
|
|
||||||
|
std::shared_ptr<PRF_addr_a_pk_gadget<FieldT>> spend_authority;
|
||||||
|
|
||||||
|
input_note_gadget(
|
||||||
|
protoboard<FieldT>& pb,
|
||||||
|
pb_variable<FieldT>& ZERO
|
||||||
|
) : note_gadget<FieldT>(pb) {
|
||||||
|
a_sk.reset(new digest_variable<FieldT>(pb, 252, ""));
|
||||||
|
a_pk.reset(new digest_variable<FieldT>(pb, 256, ""));
|
||||||
|
spend_authority.reset(new PRF_addr_a_pk_gadget<FieldT>(
|
||||||
|
pb,
|
||||||
|
ZERO,
|
||||||
|
a_sk->bits,
|
||||||
|
a_pk
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_r1cs_constraints() {
|
||||||
|
note_gadget<FieldT>::generate_r1cs_constraints();
|
||||||
|
|
||||||
|
a_sk->generate_r1cs_constraints();
|
||||||
|
|
||||||
|
// TODO: This constraint may not be necessary if SHA256
|
||||||
|
// already boolean constrains its outputs.
|
||||||
|
a_pk->generate_r1cs_constraints();
|
||||||
|
|
||||||
|
spend_authority->generate_r1cs_constraints();
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_r1cs_witness(const SpendingKey& key, const Note& note) {
|
||||||
|
note_gadget<FieldT>::generate_r1cs_witness(note);
|
||||||
|
|
||||||
|
// Witness a_sk for the input
|
||||||
|
a_sk->bits.fill_with_bits(
|
||||||
|
this->pb,
|
||||||
|
trailing252(uint256_to_bool_vector(key))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Witness a_pk for a_sk with PRF_addr
|
||||||
|
spend_authority->generate_r1cs_witness();
|
||||||
|
|
||||||
|
// [SANITY CHECK] Witness a_pk with note information
|
||||||
|
a_pk->bits.fill_with_bits(
|
||||||
|
this->pb,
|
||||||
|
uint256_to_bool_vector(note.a_pk)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
77
src/zcash/circuit/prfs.tcc
Normal file
77
src/zcash/circuit/prfs.tcc
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
template<typename FieldT>
|
||||||
|
class PRF_gadget : gadget<FieldT> {
|
||||||
|
private:
|
||||||
|
std::shared_ptr<block_variable<FieldT>> block;
|
||||||
|
std::shared_ptr<sha256_compression_function_gadget<FieldT>> hasher;
|
||||||
|
std::shared_ptr<digest_variable<FieldT>> result;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PRF_gadget(
|
||||||
|
protoboard<FieldT>& pb,
|
||||||
|
pb_variable<FieldT>& ZERO,
|
||||||
|
bool a,
|
||||||
|
bool b,
|
||||||
|
bool c,
|
||||||
|
bool d,
|
||||||
|
pb_variable_array<FieldT> x,
|
||||||
|
boost::optional<pb_variable_array<FieldT>> y,
|
||||||
|
std::shared_ptr<digest_variable<FieldT>> result
|
||||||
|
) : gadget<FieldT>(pb), result(result) {
|
||||||
|
|
||||||
|
pb_linear_combination_array<FieldT> IV = SHA256_default_IV(pb);
|
||||||
|
|
||||||
|
pb_variable_array<FieldT> discriminants;
|
||||||
|
discriminants.emplace_back(a ? ONE : ZERO);
|
||||||
|
discriminants.emplace_back(b ? ONE : ZERO);
|
||||||
|
discriminants.emplace_back(c ? ONE : ZERO);
|
||||||
|
discriminants.emplace_back(d ? ONE : ZERO);
|
||||||
|
|
||||||
|
if (!y) {
|
||||||
|
// Create y and pad it with zeroes.
|
||||||
|
y = pb_variable_array<FieldT>();
|
||||||
|
while (y->size() < 256) {
|
||||||
|
y->emplace_back(ZERO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block.reset(new block_variable<FieldT>(pb, {
|
||||||
|
discriminants,
|
||||||
|
x,
|
||||||
|
*y
|
||||||
|
}, "PRF_block"));
|
||||||
|
|
||||||
|
hasher.reset(new sha256_compression_function_gadget<FieldT>(
|
||||||
|
pb,
|
||||||
|
IV,
|
||||||
|
block->bits,
|
||||||
|
*result,
|
||||||
|
"PRF_hasher"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_r1cs_constraints() {
|
||||||
|
hasher->generate_r1cs_constraints();
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_r1cs_witness() {
|
||||||
|
hasher->generate_r1cs_witness();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename FieldT>
|
||||||
|
class PRF_addr_a_pk_gadget : public PRF_gadget<FieldT> {
|
||||||
|
public:
|
||||||
|
PRF_addr_a_pk_gadget(
|
||||||
|
protoboard<FieldT>& pb,
|
||||||
|
pb_variable<FieldT>& ZERO,
|
||||||
|
pb_variable_array<FieldT>& a_sk,
|
||||||
|
std::shared_ptr<digest_variable<FieldT>> result
|
||||||
|
) : PRF_gadget<FieldT>(pb, ZERO, 1, 1, 0, 0, a_sk, boost::none, result) {}
|
||||||
|
|
||||||
|
void generate_r1cs_constraints() {
|
||||||
|
PRF_gadget<FieldT>::generate_r1cs_constraints();
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_r1cs_witness() {
|
||||||
|
PRF_gadget<FieldT>::generate_r1cs_witness();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
|
std::vector<bool> trailing252(std::vector<bool> input) {
|
||||||
|
if (input.size() != 256) {
|
||||||
|
throw std::length_error("trailing252 input invalid length");
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<bool>(input.begin() + 4, input.end());
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<bool> uint256_to_bool_vector(uint256 input) {
|
std::vector<bool> uint256_to_bool_vector(uint256 input) {
|
||||||
std::vector<unsigned char> input_v(input.begin(), input.end());
|
std::vector<unsigned char> input_v(input.begin(), input.end());
|
||||||
std::vector<bool> output_bv(256, 0);
|
std::vector<bool> output_bv(256, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user