Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spend transaction claims #1395

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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!");
}
}
Comment on lines +46 to +55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prove method includes checks for the validity of the statement being proven. This is a good practice for ensuring the integrity of the input data. However, consider extracting these checks into a separate method to improve code readability and maintainability. Additionally, ensure that error messages provided in exceptions are descriptive enough to help diagnose issues.


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
Comment on lines +14 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prove method takes a significant number of parameters, which might indicate that the method is doing too much or that some of these parameters could be encapsulated into a higher-level object. Consider whether some of these parameters are related closely enough to be grouped into a struct or class to simplify the method signature and improve code readability.

);
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
Loading