Skip to content

Commit 15a7ce9

Browse files
committed
WIP: spend transaction claims
1 parent d425731 commit 15a7ce9

6 files changed

Lines changed: 370 additions & 2 deletions

File tree

src/Makefile.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,9 @@ libspark_a_SOURCES = \
675675
libspark/f4grumble.h \
676676
libspark/f4grumble.cpp \
677677
libspark/bech32.h \
678-
libspark/bech32.cpp
678+
libspark/bech32.cpp \
679+
libspark/claim.h \
680+
libspark/claim.cpp
679681

680682
liblelantus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
681683
liblelantus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)

src/libspark/claim.cpp

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#include "claim.h"
2+
#include "transcript.h"
3+
4+
namespace spark {
5+
6+
Claim::Claim(const GroupElement& F_, const GroupElement& G_, const GroupElement& H_, const GroupElement& U_):
7+
F(F_), G(G_), H(H_), U(U_) {
8+
}
9+
10+
Scalar Claim::challenge(
11+
const Scalar& mu,
12+
const std::vector<unsigned char>& identifier,
13+
const std::vector<unsigned char>& message,
14+
const std::vector<GroupElement>& S,
15+
const std::vector<GroupElement>& T,
16+
const GroupElement& A1,
17+
const std::vector<GroupElement>& A2
18+
) {
19+
Transcript transcript(LABEL_TRANSCRIPT_CLAIM);
20+
transcript.add("F", F);
21+
transcript.add("G", G);
22+
transcript.add("H", H);
23+
transcript.add("U", U);
24+
transcript.add("mu", mu);
25+
transcript.add("identifier", identifier);
26+
transcript.add("message", message);
27+
transcript.add("S", S);
28+
transcript.add("T", T);
29+
transcript.add("A1", A1);
30+
transcript.add("A2", A2);
31+
32+
return transcript.challenge("c");
33+
}
34+
35+
void Claim::prove(
36+
const Scalar& mu,
37+
const std::vector<unsigned char>& identifier,
38+
const std::vector<unsigned char>& message,
39+
const std::vector<Scalar>& x,
40+
const std::vector<Scalar>& y,
41+
const std::vector<Scalar>& z,
42+
const std::vector<GroupElement>& S,
43+
const std::vector<GroupElement>& T,
44+
ChaumProof& proof
45+
) {
46+
// Check statement validity
47+
std::size_t n = x.size();
48+
if (!(y.size() == n && z.size() == n && S.size() == n && T.size() == n)) {
49+
throw std::invalid_argument("Bad claim statement!");
50+
}
51+
for (std::size_t i = 0; i < n; i++) {
52+
if (!(F*x[i] + G*y[i] + H*z[i] == S[i] && T[i]*x[i] + G*y[i] == U)) {
53+
throw std::invalid_argument("Bad claim statement!");
54+
}
55+
}
56+
57+
std::vector<Scalar> r;
58+
r.resize(n);
59+
std::vector<Scalar> s;
60+
s.resize(n);
61+
for (std::size_t i = 0; i < n; i++) {
62+
r[i].randomize();
63+
s[i].randomize();
64+
}
65+
Scalar t;
66+
t.randomize();
67+
68+
proof.A1 = H*t;
69+
proof.A2.resize(n);
70+
for (std::size_t i = 0; i < n; i++) {
71+
proof.A1 += F*r[i] + G*s[i];
72+
proof.A2[i] = T[i]*r[i] + G*s[i];
73+
}
74+
75+
Scalar c = challenge(mu, identifier, message, S, T, proof.A1, proof.A2);
76+
77+
proof.t1.resize(n);
78+
proof.t3 = t;
79+
Scalar c_power(c);
80+
for (std::size_t i = 0; i < n; i++) {
81+
if (c_power.isZero()) {
82+
throw std::invalid_argument("Unexpected challenge!");
83+
}
84+
proof.t1[i] = r[i] + c_power*x[i];
85+
proof.t2 += s[i] + c_power*y[i];
86+
proof.t3 += c_power*z[i];
87+
c_power *= c;
88+
}
89+
}
90+
91+
bool Claim::verify(
92+
const Scalar& mu,
93+
const std::vector<unsigned char>& identifier,
94+
const std::vector<unsigned char>& message,
95+
const std::vector<GroupElement>& S,
96+
const std::vector<GroupElement>& T,
97+
const ChaumProof& proof
98+
) {
99+
// Check proof semantics
100+
std::size_t n = S.size();
101+
if (!(T.size() == n && proof.A2.size() == n && proof.t1.size() == n)) {
102+
throw std::invalid_argument("Bad claim semantics!");
103+
}
104+
105+
Scalar c = challenge(mu, identifier, message, S, T, proof.A1, proof.A2);
106+
if (c.isZero()) {
107+
throw std::invalid_argument("Unexpected challenge!");
108+
}
109+
std::vector<Scalar> c_powers;
110+
c_powers.emplace_back(c);
111+
for (std::size_t i = 1; i < n; i++) {
112+
c_powers.emplace_back(c_powers[i-1]*c);
113+
if (c_powers[i].isZero()) {
114+
throw std::invalid_argument("Unexpected challenge!");
115+
}
116+
}
117+
118+
// Weight the verification equations
119+
Scalar w;
120+
while (w.isZero()) {
121+
w.randomize();
122+
}
123+
124+
std::vector<Scalar> scalars;
125+
std::vector<GroupElement> points;
126+
scalars.reserve(3*n + 5);
127+
points.reserve(3*n + 5);
128+
129+
// F
130+
Scalar F_scalar;
131+
for (std::size_t i = 0; i < n; i++) {
132+
F_scalar -= proof.t1[i];
133+
}
134+
scalars.emplace_back(F_scalar);
135+
points.emplace_back(F);
136+
137+
// G
138+
scalars.emplace_back(proof.t2.negate() - w*proof.t2);
139+
points.emplace_back(G);
140+
141+
// H
142+
scalars.emplace_back(proof.t3.negate());
143+
points.emplace_back(H);
144+
145+
// U
146+
Scalar U_scalar;
147+
for (std::size_t i = 0; i < n; i++) {
148+
U_scalar += c_powers[i];
149+
}
150+
U_scalar *= w;
151+
scalars.emplace_back(U_scalar);
152+
points.emplace_back(U);
153+
154+
// A1
155+
scalars.emplace_back(Scalar((uint64_t) 1));
156+
points.emplace_back(proof.A1);
157+
158+
// {A2}
159+
GroupElement A2_sum = proof.A2[0];
160+
for (std::size_t i = 1; i < n; i++) {
161+
A2_sum += proof.A2[i];
162+
}
163+
scalars.emplace_back(w);
164+
points.emplace_back(A2_sum);
165+
166+
// {S}
167+
for (std::size_t i = 0; i < n; i++) {
168+
scalars.emplace_back(c_powers[i]);
169+
points.emplace_back(S[i]);
170+
}
171+
172+
// {T}
173+
for (std::size_t i = 0; i < n; i++) {
174+
scalars.emplace_back(w.negate()*proof.t1[i]);
175+
points.emplace_back(T[i]);
176+
}
177+
178+
secp_primitives::MultiExponent multiexp(points, scalars);
179+
// merged equalities and doing check in one multiexponentation,
180+
// for weighting we use random w
181+
return multiexp.get_multiple().isInfinity();
182+
}
183+
184+
}

src/libspark/claim.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#ifndef FIRO_LIBSPARK_CLAIM_H
2+
#define FIRO_LIBSPARK_CLAIM_H
3+
4+
#include "chaum_proof.h"
5+
#include <secp256k1/include/MultiExponent.h>
6+
7+
namespace spark {
8+
9+
// A claim proof, which is used to assert control of the consumed coins in a spend transaction
10+
class Claim {
11+
public:
12+
Claim(const GroupElement& F, const GroupElement& G, const GroupElement& H, const GroupElement& U);
13+
14+
void prove(
15+
const Scalar& mu,
16+
const std::vector<unsigned char>& identifier,
17+
const std::vector<unsigned char>& message,
18+
const std::vector<Scalar>& x,
19+
const std::vector<Scalar>& y,
20+
const std::vector<Scalar>& z,
21+
const std::vector<GroupElement>& S,
22+
const std::vector<GroupElement>& T,
23+
ChaumProof& proof
24+
);
25+
bool verify(
26+
const Scalar& mu,
27+
const std::vector<unsigned char>& identifier,
28+
const std::vector<unsigned char>& message,
29+
const std::vector<GroupElement>& S,
30+
const std::vector<GroupElement>& T,
31+
const ChaumProof& proof
32+
);
33+
34+
private:
35+
Scalar challenge(
36+
const Scalar& mu,
37+
const std::vector<unsigned char>& identifier,
38+
const std::vector<unsigned char>& message,
39+
const std::vector<GroupElement>& S,
40+
const std::vector<GroupElement>& T,
41+
const GroupElement& A1,
42+
const std::vector<GroupElement>& A2
43+
);
44+
const GroupElement& F;
45+
const GroupElement& G;
46+
const GroupElement& H;
47+
const GroupElement& U;
48+
};
49+
50+
}
51+
52+
#endif

src/libspark/spend_transaction.cpp

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,4 +459,129 @@ const std::map<uint64_t, uint256>& SpendTransaction::getBlockHashes() {
459459
return set_id_blockHash;
460460
}
461461

462-
}
462+
// Generate a claim on a given spend transaction.
463+
//
464+
// A claim is essentially just a Chaum authorizing proof that is bound to a transaction identifier and a message.
465+
//
466+
// 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.
467+
// You must also provide an identifier that the verifier can use to uniquely identify the spend transaction when it verifies the claim.
468+
// 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.
469+
void SpendTransaction::proveClaim(
470+
const FullViewKey& full_view_key,
471+
const SpendKey& spend_key,
472+
const SpendTransaction& transaction,
473+
const std::vector<InputCoinData>& inputs,
474+
const std::vector<unsigned char>& identifier,
475+
const std::vector<unsigned char>& message,
476+
ChaumProof& claim
477+
) {
478+
// The number of inputs
479+
const std::size_t w = inputs.size();
480+
481+
// Check that the input coin data corresponds to the prover statement data we'll get from the spend transaction
482+
// This ensures we don't try to use incorrect data to build the proof!
483+
if (transaction.S1.size() != w || transaction.T.size() != w) {
484+
throw std::invalid_argument("Bad claim input coin data!");
485+
}
486+
for (std::size_t u = 0; u < w; u++) {
487+
GroupElement S1 = transaction.params->get_F()*inputs[u].s
488+
+ transaction.params->get_H().inverse()*SparkUtils::hash_ser1(inputs[u].s, full_view_key.get_D())
489+
+ full_view_key.get_D();
490+
if (S1 != transaction.S1[u]) {
491+
throw std::invalid_argument("Bad claim input coin data!");
492+
}
493+
if (inputs[u].T != transaction.T[u]) {
494+
throw std::invalid_argument("Bad claim input coin data!");
495+
}
496+
}
497+
498+
// Build the prover witness
499+
std::vector<Scalar> claim_x, claim_y, claim_z;
500+
for (std::size_t u = 0; u < w; u++) {
501+
claim_x.emplace_back(inputs[u].s);
502+
claim_y.emplace_back(spend_key.get_r());
503+
claim_z.emplace_back(SparkUtils::hash_ser1(inputs[u].s, full_view_key.get_D()).negate());
504+
}
505+
506+
// Compute the binding hash
507+
Scalar mu = hash_bind(
508+
hash_bind_inner(
509+
transaction.cover_set_representations,
510+
transaction.S1,
511+
transaction.C1,
512+
transaction.T,
513+
transaction.grootle_proofs,
514+
transaction.balance_proof,
515+
transaction.range_proof
516+
),
517+
transaction.out_coins,
518+
transaction.f + transaction.vout
519+
);
520+
521+
// Produce the claim, binding in the identifier and message
522+
Claim claim_prover(
523+
transaction.params->get_F(),
524+
transaction.params->get_G(),
525+
transaction.params->get_H(),
526+
transaction.params->get_U()
527+
);
528+
claim_prover.prove(
529+
mu,
530+
identifier,
531+
message,
532+
claim_x,
533+
claim_y,
534+
claim_z,
535+
transaction.S1,
536+
transaction.T,
537+
claim
538+
);
539+
}
540+
541+
// Verify a claim on a given spend transaction.
542+
//
543+
// The verifier must have used the identifier to determine its own view of the spend transaction.
544+
// It must not accept a spend transaction from the prover that it has not checked against its own view of the ledger.
545+
// The spend transaction must also have been previously verified.
546+
//
547+
// 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.
548+
// It does _not_ say anything else about the spend transaction!
549+
bool SpendTransaction::verifyClaim(
550+
const SpendTransaction& transaction,
551+
const std::vector<unsigned char>& identifier,
552+
const std::vector<unsigned char>& message,
553+
const ChaumProof& claim
554+
) {
555+
// Compute the binding hash
556+
Scalar mu = hash_bind(
557+
hash_bind_inner(
558+
transaction.cover_set_representations,
559+
transaction.S1,
560+
transaction.C1,
561+
transaction.T,
562+
transaction.grootle_proofs,
563+
transaction.balance_proof,
564+
transaction.range_proof
565+
),
566+
transaction.out_coins,
567+
transaction.f + transaction.vout
568+
);
569+
570+
// Verify the claim, binding in the identifier and message
571+
Claim claim_verifier(
572+
transaction.params->get_F(),
573+
transaction.params->get_G(),
574+
transaction.params->get_H(),
575+
transaction.params->get_U()
576+
);
577+
return claim_verifier.verify(
578+
mu,
579+
identifier,
580+
message,
581+
transaction.S1,
582+
transaction.T,
583+
claim
584+
);
585+
}
586+
587+
}

src/libspark/spend_transaction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "grootle.h"
88
#include "bpplus.h"
99
#include "chaum.h"
10+
#include "claim.h"
1011

1112
namespace spark {
1213

@@ -59,6 +60,9 @@ class SpendTransaction {
5960

6061
static bool verify(const Params* params, const std::vector<SpendTransaction>& transactions, const std::unordered_map<uint64_t, std::vector<Coin>>& cover_sets);
6162
static bool verify(const SpendTransaction& transaction, const std::unordered_map<uint64_t, std::vector<Coin>>& cover_sets);
63+
64+
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);
65+
static bool verifyClaim(const SpendTransaction& transaction, const std::vector<unsigned char>& identifier, const std::vector<unsigned char>& message, const ChaumProof& claim);
6266

6367
static std::vector<unsigned char> hash_bind_inner(
6468
const std::map<uint64_t, std::vector<unsigned char>>& cover_set_representations,

0 commit comments

Comments
 (0)