Skip to content

Commit

Permalink
WIP: spend transaction claims
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronFeickert committed Apr 16, 2024
1 parent 95622c6 commit bd687b0
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,9 @@ libspark_a_SOURCES = \
libspark/f4grumble.h \
libspark/f4grumble.cpp \
libspark/bech32.h \
libspark/bech32.cpp
libspark/bech32.cpp \
libspark/claim.h \
libspark/claim.cpp

liblelantus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
liblelantus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
Expand Down
184 changes: 184 additions & 0 deletions src/libspark/claim.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#include "claim.h"
#include "transcript.h"

namespace spark {

Claim::Claim(const GroupElement& F_, const GroupElement& G_, const GroupElement& H_, const GroupElement& U_):
F(F_), G(G_), H(H_), U(U_) {
}

Scalar Claim::challenge(
const Scalar& mu,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
const std::vector<GroupElement>& S,
const std::vector<GroupElement>& T,
const GroupElement& A1,
const std::vector<GroupElement>& A2
) {
Transcript transcript(LABEL_TRANSCRIPT_CLAIM);
transcript.add("F", F);
transcript.add("G", G);
transcript.add("H", H);
transcript.add("U", U);
transcript.add("mu", mu);
transcript.add("identifier", identifier);
transcript.add("message", message);
transcript.add("S", S);
transcript.add("T", T);
transcript.add("A1", A1);
transcript.add("A2", A2);

return transcript.challenge("c");
}

void Claim::prove(
const Scalar& mu,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
const std::vector<Scalar>& x,
const std::vector<Scalar>& y,
const std::vector<Scalar>& z,
const std::vector<GroupElement>& S,
const std::vector<GroupElement>& T,
ChaumProof& proof
) {
// Check statement validity
std::size_t n = x.size();
if (!(y.size() == n && z.size() == n && S.size() == n && T.size() == n)) {
throw std::invalid_argument("Bad claim statement!");
}
for (std::size_t i = 0; i < n; i++) {
if (!(F*x[i] + G*y[i] + H*z[i] == S[i] && T[i]*x[i] + G*y[i] == U)) {
throw std::invalid_argument("Bad claim statement!");
}
}

std::vector<Scalar> r;
r.resize(n);
std::vector<Scalar> s;
s.resize(n);
for (std::size_t i = 0; i < n; i++) {
r[i].randomize();
s[i].randomize();
}
Scalar t;
t.randomize();

proof.A1 = H*t;
proof.A2.resize(n);
for (std::size_t i = 0; i < n; i++) {
proof.A1 += F*r[i] + G*s[i];
proof.A2[i] = T[i]*r[i] + G*s[i];
}

Scalar c = challenge(mu, identifier, message, S, T, proof.A1, proof.A2);

proof.t1.resize(n);
proof.t3 = t;
Scalar c_power(c);
for (std::size_t i = 0; i < n; i++) {
if (c_power.isZero()) {
throw std::invalid_argument("Unexpected challenge!");
}
proof.t1[i] = r[i] + c_power*x[i];
proof.t2 += s[i] + c_power*y[i];
proof.t3 += c_power*z[i];
c_power *= c;
}
}

bool Claim::verify(
const Scalar& mu,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
const std::vector<GroupElement>& S,
const std::vector<GroupElement>& T,
const ChaumProof& proof
) {
// Check proof semantics
std::size_t n = S.size();
if (!(T.size() == n && proof.A2.size() == n && proof.t1.size() == n)) {
throw std::invalid_argument("Bad claim semantics!");
}

Scalar c = challenge(mu, identifier, message, S, T, proof.A1, proof.A2);
if (c.isZero()) {
throw std::invalid_argument("Unexpected challenge!");
}
std::vector<Scalar> c_powers;
c_powers.emplace_back(c);
for (std::size_t i = 1; i < n; i++) {
c_powers.emplace_back(c_powers[i-1]*c);
if (c_powers[i].isZero()) {
throw std::invalid_argument("Unexpected challenge!");
}
}

// Weight the verification equations
Scalar w;
while (w.isZero()) {
w.randomize();
}

std::vector<Scalar> scalars;
std::vector<GroupElement> points;
scalars.reserve(3*n + 5);
points.reserve(3*n + 5);

// F
Scalar F_scalar;
for (std::size_t i = 0; i < n; i++) {
F_scalar -= proof.t1[i];
}
scalars.emplace_back(F_scalar);
points.emplace_back(F);

// G
scalars.emplace_back(proof.t2.negate() - w*proof.t2);
points.emplace_back(G);

// H
scalars.emplace_back(proof.t3.negate());
points.emplace_back(H);

// U
Scalar U_scalar;
for (std::size_t i = 0; i < n; i++) {
U_scalar += c_powers[i];
}
U_scalar *= w;
scalars.emplace_back(U_scalar);
points.emplace_back(U);

// A1
scalars.emplace_back(Scalar((uint64_t) 1));
points.emplace_back(proof.A1);

// {A2}
GroupElement A2_sum = proof.A2[0];
for (std::size_t i = 1; i < n; i++) {
A2_sum += proof.A2[i];
}
scalars.emplace_back(w);
points.emplace_back(A2_sum);

// {S}
for (std::size_t i = 0; i < n; i++) {
scalars.emplace_back(c_powers[i]);
points.emplace_back(S[i]);
}

// {T}
for (std::size_t i = 0; i < n; i++) {
scalars.emplace_back(w.negate()*proof.t1[i]);
points.emplace_back(T[i]);
}

secp_primitives::MultiExponent multiexp(points, scalars);
// merged equalities and doing check in one multiexponentation,
// for weighting we use random w
return multiexp.get_multiple().isInfinity();
}

}
52 changes: 52 additions & 0 deletions src/libspark/claim.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef FIRO_LIBSPARK_CLAIM_H
#define FIRO_LIBSPARK_CLAIM_H

#include "chaum_proof.h"
#include <secp256k1/include/MultiExponent.h>

namespace spark {

// A claim proof, which is used to assert control of the consumed coins in a spend transaction
class Claim {
public:
Claim(const GroupElement& F, const GroupElement& G, const GroupElement& H, const GroupElement& U);

void prove(
const Scalar& mu,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
const std::vector<Scalar>& x,
const std::vector<Scalar>& y,
const std::vector<Scalar>& z,
const std::vector<GroupElement>& S,
const std::vector<GroupElement>& T,
ChaumProof& proof
);
bool verify(
const Scalar& mu,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
const std::vector<GroupElement>& S,
const std::vector<GroupElement>& T,
const ChaumProof& proof
);

private:
Scalar challenge(
const Scalar& mu,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
const std::vector<GroupElement>& S,
const std::vector<GroupElement>& T,
const GroupElement& A1,
const std::vector<GroupElement>& A2
);
const GroupElement& F;
const GroupElement& G;
const GroupElement& H;
const GroupElement& U;
};

}

#endif
127 changes: 126 additions & 1 deletion src/libspark/spend_transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,4 +460,129 @@ const std::map<uint64_t, uint256>& SpendTransaction::getBlockHashes() {
return set_id_blockHash;
}

}
// Generate a claim on a given spend transaction.
//
// A claim is essentially just a Chaum authorizing proof that is bound to a transaction identifier and a message.
//
// To generate the claim, you must provide the same full view and spend keys that were used to generate the spend transaction, as well as the same input coin data.
// You must also provide an identifier that the verifier can use to uniquely identify the spend transaction when it verifies the claim.
// You can also provide an arbitrary message to sign, which can be useful to avoid replays or otherwise bind the claim to some particular context.
void SpendTransaction::proveClaim(
const FullViewKey& full_view_key,
const SpendKey& spend_key,
const SpendTransaction& transaction,
const std::vector<InputCoinData>& inputs,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
ChaumProof& claim
) {
// The number of inputs
const std::size_t w = inputs.size();

// Check that the input coin data corresponds to the prover statement data we'll get from the spend transaction
// This ensures we don't try to use incorrect data to build the proof!
if (transaction.S1.size() != w || transaction.T.size() != w) {
throw std::invalid_argument("Bad claim input coin data!");
}
for (std::size_t u = 0; u < w; u++) {
GroupElement S1 = transaction.params->get_F()*inputs[u].s
+ transaction.params->get_H().inverse()*SparkUtils::hash_ser1(inputs[u].s, full_view_key.get_D())
+ full_view_key.get_D();
if (S1 != transaction.S1[u]) {
throw std::invalid_argument("Bad claim input coin data!");
}
if (inputs[u].T != transaction.T[u]) {
throw std::invalid_argument("Bad claim input coin data!");
}
}

// Build the prover witness
std::vector<Scalar> claim_x, claim_y, claim_z;
for (std::size_t u = 0; u < w; u++) {
claim_x.emplace_back(inputs[u].s);
claim_y.emplace_back(spend_key.get_r());
claim_z.emplace_back(SparkUtils::hash_ser1(inputs[u].s, full_view_key.get_D()).negate());
}

// Compute the binding hash
Scalar mu = hash_bind(
hash_bind_inner(
transaction.cover_set_representations,
transaction.S1,
transaction.C1,
transaction.T,
transaction.grootle_proofs,
transaction.balance_proof,
transaction.range_proof
),
transaction.out_coins,
transaction.f + transaction.vout
);

// Produce the claim, binding in the identifier and message
Claim claim_prover(
transaction.params->get_F(),
transaction.params->get_G(),
transaction.params->get_H(),
transaction.params->get_U()
);
claim_prover.prove(
mu,
identifier,
message,
claim_x,
claim_y,
claim_z,
transaction.S1,
transaction.T,
claim
);
}

// Verify a claim on a given spend transaction.
//
// The verifier must have used the identifier to determine its own view of the spend transaction.
// It must not accept a spend transaction from the prover that it has not checked against its own view of the ledger.
// The spend transaction must also have been previously verified.
//
// If verification subsequently succeeds, it means the same spend key was used to produce the claim as was used to produce the spend transaction, and that the provided message was bound to the claim.
// It does _not_ say anything else about the spend transaction!
bool SpendTransaction::verifyClaim(
const SpendTransaction& transaction,
const std::vector<unsigned char>& identifier,
const std::vector<unsigned char>& message,
const ChaumProof& claim
) {
// Compute the binding hash
Scalar mu = hash_bind(
hash_bind_inner(
transaction.cover_set_representations,
transaction.S1,
transaction.C1,
transaction.T,
transaction.grootle_proofs,
transaction.balance_proof,
transaction.range_proof
),
transaction.out_coins,
transaction.f + transaction.vout
);

// Verify the claim, binding in the identifier and message
Claim claim_verifier(
transaction.params->get_F(),
transaction.params->get_G(),
transaction.params->get_H(),
transaction.params->get_U()
);
return claim_verifier.verify(
mu,
identifier,
message,
transaction.S1,
transaction.T,
claim
);
}

}
4 changes: 4 additions & 0 deletions src/libspark/spend_transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "grootle.h"
#include "bpplus.h"
#include "chaum.h"
#include "claim.h"

namespace spark {

Expand Down Expand Up @@ -60,6 +61,9 @@ class SpendTransaction {

static bool verify(const Params* params, const std::vector<SpendTransaction>& transactions, const std::unordered_map<uint64_t, std::vector<Coin>>& cover_sets);
static bool verify(const SpendTransaction& transaction, const std::unordered_map<uint64_t, std::vector<Coin>>& cover_sets);

static void proveClaim(const FullViewKey& full_view_key, const SpendKey& spend_key, const SpendTransaction& transaction, const std::vector<InputCoinData>& inputs, const std::vector<unsigned char>& identifier, const std::vector<unsigned char>& message, ChaumProof& claim);
static bool verifyClaim(const SpendTransaction& transaction, const std::vector<unsigned char>& identifier, const std::vector<unsigned char>& message, const ChaumProof& claim);

static std::vector<unsigned char> hash_bind_inner(
const std::map<uint64_t, std::vector<unsigned char>>& cover_set_representations,
Expand Down
Loading

0 comments on commit bd687b0

Please sign in to comment.