Updated rct code with ring multisig and fixing key image bug I found last night

This commit is contained in:
shnoe 2016-02-07 08:42:01 -07:00
parent bfb18a0eed
commit b215a98a74
11 changed files with 267 additions and 207 deletions

View file

@ -1,17 +0,0 @@
--for quick conversion of the MiniNero python code to c++
--
if #arg > 0 then
class = string.lower(arg[1]) --asdf
object = arg[1] --Asdf
deff = string.upper(arg[1]) --ASDF
hfile = io.input("TemplateHead"):read("*a")
cppfile = io.input("TemplateBody"):read("*a")
hfile = string.gsub(hfile, "asdf", class)
hfile = string.gsub(hfile, "Asdf", object)
hfile = string.gsub(hfile, "ASDF", deff)
cppfile = string.gsub(cppfile, "asdf", class)
cppfile = string.gsub(cppfile, "Asdf", object)
cppfile = string.gsub(cppfile, "ASDF", deff)
io.output(object..".h"):write(hfile)
io.output(object..".cpp"):write(cppfile)
end

View file

@ -1,4 +0,0 @@
gtags.exe
mingw32-make.exe clean
mingw32-make.exe
a.exe > .results

View file

@ -1,4 +0,0 @@
gtags.exe
make clean
make
./a.exe

View file

@ -1,3 +0,0 @@
rm.exe a.exe
g++ bits.cpp
./a.exe

View file

@ -1 +0,0 @@
%1

View file

@ -34,4 +34,74 @@ using namespace std;
namespace rct {
}
int i;
//Generate Signing Keys
//This function is called by each participant in
//A ring multisignature transaction.
//The participant will send the returned parameters
//to whomever is managing the transaction.
//returns a, aG, aHP and I
tuple<key, key, key, key> InitiateRMS(key x) {
key I = scalarmultKey(hashToPoint(scalarmultBase(x)), x);
key a, aG;
skpkGen(a, aG);
key aHP = scalarmultKey(hashToPoint(scalarmultBase(x)), a);
return make_tuple(a, aG, aHP, I);
}
//returns "c" which is the last index needed to get the last s-values
key rmsMgSigStart(const keyM & pk, mgSig & rv, keyV aG, keyV aHP, const int index) {
int rows = pk[0].size();
int cols = pk.size();
if (cols < 2) {
printf("Error! What is c if cols = 1!");
}
int i = 0, j = 0;
key c, c_old, c0, L, R, Hi;
sc_0(c_old.bytes);
vector<ge_dsmp> Ip(rows);
rv.ss = keyM(cols, rv.II);
unsigned char m2[96];
for (i = 0; i < rows; i++) {
memcpy(m2, pk[index][i].bytes, 32);
memcpy(m2 + 32, aG[i].bytes, 32);
memcpy(m2 + 64, aHP[i].bytes, 32);
precomp(Ip[i], rv.II[i]);
sc_add(c_old.bytes, c_old.bytes, cn_fast_hash96(m2).bytes);
}
int oldi = index;
i = (index + 1) % cols;
while (i != index) {
rv.ss[i] = skvGen(rows);
sc_0(c.bytes);
for (j = 0; j < rows; j++) {
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
hashToPoint(Hi, pk[i][j]);
addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j]);
memcpy(m2, pk[i][j].bytes, 32);
memcpy(m2 + 32, L.bytes, 32);
memcpy(m2 + 64, R.bytes, 32);
sc_add(c.bytes, c.bytes, cn_fast_hash96(m2).bytes);
}
c_old = copy(c);
if (i == 0) {
c0 = copy(c);
}
oldi = i;
i = (i + 1) % cols;
}
return c;
}
//have to return s = a - cx
//for each participant in the MG sig..
key rmsSign(key a, key c, key x) {
key s;
sc_mulsub(s.bytes, c.bytes, x.bytes, a.bytes);
return s;
}
}

View file

@ -50,6 +50,7 @@
#include "rctTypes.h"
#include "rctOps.h"
#include "rctSigs.h"
@ -58,11 +59,8 @@ using namespace std;
using namespace crypto;
namespace rct {
int i;
//rctSig genRMS(ctkeyV & inSk, ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> amounts, const int mixin);
tuple<key, key, key, key> InitiateRMS(key x);
key rmsMgSigStart(const keyM & pk, mgSig & rv, keyV aG, keyV aHP, const int index);
key rmsSign(key a, key c, key x) ;
}
#endif

View file

@ -352,16 +352,43 @@ namespace rct {
sc_reduce32(hash.bytes);
return hash;
}
//returns cn_fast_hash(input) * G where G is the basepoint
key hashToPoint(const key & in) {
return scalarmultBase(cn_fast_hash(in));
//cn_fast_hash for a 96 byte unsigned char
key cn_fast_hash96(const void * in) {
uint8_t md2[32];
int j = 0;
key hash;
keccak((uint8_t *)in, 96, md2, 32);
for (j = 0; j < 32; j++) {
hash[j] = (unsigned char)md2[j];
}
sc_reduce32(hash.bytes);
return hash;
}
//returns cn_fast_hash(input) * G where G is the basepoint
void hashToPoint(key & out, const key & in) {
scalarmultBase(out, cn_fast_hash(in));
key hashToPoint(const key & hh) {
key pointk;
ge_p2 point;
ge_p1p1 point2;
ge_p3 res;
key h = cn_fast_hash(hh);
ge_fromfe_frombytes_vartime(&point, h.bytes);
ge_mul8(&point2, &point);
ge_p1p1_to_p3(&res, &point2);
ge_p3_tobytes(pointk.bytes, &res);
return pointk;
}
void hashToPoint(key & pointk, const key & hh) {
ge_p2 point;
ge_p1p1 point2;
ge_p3 res;
key h = cn_fast_hash(hh);
ge_fromfe_frombytes_vartime(&point, h.bytes);
ge_mul8(&point2, &point);
ge_p1p1_to_p3(&res, &point2);
ge_p3_tobytes(pointk.bytes, &res);
}
//sums a vector of curve points (for scalars use sc_add)
void sumKeys(key & Csum, const keyV & Cis) {

View file

@ -48,6 +48,7 @@
#include "rctTypes.h"
using namespace std;
using namespace crypto;
@ -128,6 +129,8 @@ namespace rct {
void cn_fast_hash(key &hash, const key &in);
//cn_fast_hash for a 32 byte key
key cn_fast_hash(const key &in);
//for mg sigs
key cn_fast_hash96(const void * in);
//returns cn_fast_hash(input) * G where G is the basepoint
key hashToPoint(const key &in);

View file

@ -129,150 +129,140 @@ namespace rct {
keyV II(xx.size());
int i = 0;
for (i = 0; i < xx.size(); i++) {
II[i] = scalarmultKey(hashToPoint(scalarmultBase(xx[i])), xx[i]);
II[i] = scalarmultKey(hashToPoint(scalarmultBase(xx[i])), xx[i]);
}
return II;
}
/*
keyV skvGen(int n) {
keyV rv(n);
int i = 0;
for (i = 0; i < n; i++) {
skGen(rv[i]);
}
return rv;
}
*/
//Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
//This is a just slghtly more efficient version than the ones described below
//(will be explained in more detail in Ring Multisig paper
//These are aka MG signatutes in earlier drafts of the ring ct paper
// c.f. http://eprint.iacr.org/2015/1098 section 2.
// keyImageV just does I[i] = xx[i] * Hash(xx[i] * G) for each i
// Gen creates a signature which proves that for some column in the keymatrix "pk"
// the signer knows a secret key for each row in that column
// Ver verifies that the MG sig was created correctly
mgSig MLSAG_Gen(const keyM & pk, const keyV & xx, const int index) {
mgSig rv;
// Ver verifies that the MG sig was created correctly
mgSig MLSAG_Gen(const keyM & pk, const keyV & xx, const int index) {
mgSig rv;
int rows = pk[0].size();
int cols = pk.size();
if (cols < 2) {
printf("Error! What is c if cols = 1!");
}
int i = 0, j = 0;
keyV c(cols);
keyV alpha = skvGen(rows);
rv.II = keyImageV(xx);
DP(rv.II);
key c, c_old, L, R, Hi;
sc_0(c_old.bytes);
vector<ge_dsmp> Ip(rows);
keyM L(cols, rv.II);
keyM R(cols, rv.II);
rv.II = keyV(rows);
rv.ss = keyM(cols, rv.II);
keyV Hi(rows);
keyV alpha(rows);
keyV aG(rows);
keyV aHP(rows);
key m2hash;
unsigned char m2[96];
DP("here1");
for (i = 0; i < rows; i++) {
L[index][i] = scalarmultBase(alpha[i]);
hashToPoint(Hi[i], pk[index][i]);
R[index][i] = scalarmultKey(Hi[i], alpha[i]);
skpkGen(alpha[i], aG[i]); //need to save alphas for later..
Hi = hashToPoint(pk[index][i]);
aHP[i] = scalarmultKey(Hi, alpha[i]);
memcpy(m2, pk[index][i].bytes, 32);
memcpy(m2 + 32, aG[i].bytes, 32);
memcpy(m2 + 64, aHP[i].bytes, 32);
rv.II[i] = scalarmultKey(Hi, xx[i]);
precomp(Ip[i], rv.II[i]);
m2hash = cn_fast_hash96(m2);
sc_add(c_old.bytes, c_old.bytes, m2hash.bytes);
}
char * m1 = (char *)malloc(32 * rows * (cols + 2));
//vector<char> m1(32 * rows * (cols + 2));
for (i = 0; i < cols; i++) {
for (j = 0; j < rows; j++) {
memcpy(m1 + rows * 32 * i + (32 * j), pk[i][j].bytes, 32);
}
}
int oldi = index;
i = (index + 1) % cols;
for (j = 0; j < rows; j++) {
memcpy(m1 + rows * 32 * cols + (32 * j), L[oldi][j].bytes, 32);
memcpy(m1 + rows * 32 * (cols + 1) + (32 * j), R[oldi][j].bytes, 32);
}
if (i == 0) {
copy(rv.cc, c_old);
}
while (i != index) {
cn_fast_hash(c[i], m1, 32 * rows * (cols + 2));
rv.ss[i] = skvGen(rows);
rv.ss[i] = skvGen(rows);
sc_0(c.bytes);
for (j = 0; j < rows; j++) {
addKeys2(L[i][j], rv.ss[i][j], c[i], pk[i][j]);
hashToPoint(Hi[j], pk[i][j]);
addKeys3(R[i][j], rv.ss[i][j], Hi[j], c[i], Ip[j]);
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
hashToPoint(Hi, pk[i][j]);
addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j]);
memcpy(m2, pk[i][j].bytes, 32);
memcpy(m2 + 32, L.bytes, 32);
memcpy(m2 + 64, R.bytes, 32);
m2hash = cn_fast_hash96(m2);
sc_add(c.bytes, c.bytes, m2hash.bytes);
}
copy(c_old, c);
oldi = i;
i = (i + 1) % cols;
for (j = 0; j < rows; j++) {
memcpy(m1 + rows * 32 * cols + (32 * j), L[oldi][j].bytes, 32);
memcpy(m1 + rows * 32 * (cols + 1) + (32 * j), R[oldi][j].bytes, 32);
}
}
cn_fast_hash(c[index], m1, 32 * rows * (cols + 2));
i = (i + 1) % cols;
if (i == 0) {
copy(rv.cc, c_old);
}
}
for (j = 0; j < rows; j++) {
sc_mulsub(rv.ss[index][j].bytes, c[index].bytes, xx[j].bytes, alpha[j].bytes);
}
memcpy(rv.cc.bytes, c[0].bytes, 32);
free(m1);
sc_mulsub(rv.ss[index][j].bytes, c.bytes, xx[j].bytes, alpha[j].bytes);
}
return rv;
}
//Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
//This is a just slghtly more efficient version than the ones described below
//(will be explained in more detail in Ring Multisig paper
//These are aka MG signatutes in earlier drafts of the ring ct paper
// c.f. http://eprint.iacr.org/2015/1098 section 2.
// keyImageV just does I[i] = xx[i] * Hash(xx[i] * G) for each i
// Gen creates a signature which proves that for some column in the keymatrix "pk"
// the signer knows a secret key for each row in that column
// Ver verifies that the MG sig was created correctly
bool MLSAG_Ver(keyM &pk, mgSig &sig) {
// Ver verifies that the MG sig was created correctly
bool MLSAG_Ver(keyM & pk, mgSig & rv) {
int rows = pk[0].size();
int cols = pk.size();
if (cols < 2) {
printf("Error! What is c if cols = 1!");
}
DP("Verifying MG sig");
keyV c(cols + 1);
memcpy(c[0].bytes, sig.cc.bytes, 32);
int i = 0, j = 0;
key c, L, R, Hi;
key c_old = copy(rv.cc);
vector<ge_dsmp> Ip(rows);
keyM L(cols, pk[0]);
keyM R(cols, pk[0]);
for (i= 0 ; i< rows ; i++) {
precomp(Ip[i], rv.II[i]);
}
unsigned char m2[96];
key m2hash;
int i = 0, oldi = 0, j = 0;
keyV Hi(rows);
for (i = 0; i < rows; i++) {
precomp(Ip[i], sig.II[i]);
}
i = 0;
char * m1 = (char *)malloc(32 * rows * (cols + 2));
//vector<char> m1(32 * rows * (cols + 2));
for (i = 0; i < cols; i++) {
for (j = 0; j < rows; j++) {
memcpy(m1 + rows * 32 * i + (32 * j), pk[i][j].bytes, 32);
}
}
i = 0;
int oldi = 0;
i = 0;
while (i < cols) {
sc_0(c.bytes);
for (j = 0; j < rows; j++) {
addKeys2(L[i][j], sig.ss[i][j], c[i], pk[i][j]);
hashToPoint(Hi[j], pk[i][j]);
addKeys3(R[i][j], sig.ss[i][j], Hi[j], c[i], Ip[j]);
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
hashToPoint(Hi, pk[i][j]);
addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j]);
memcpy(m2, pk[i][j].bytes, 32);
memcpy(m2 + 32, L.bytes, 32);
memcpy(m2 + 64, R.bytes, 32);
m2hash = cn_fast_hash96(m2);
sc_add(c.bytes, c.bytes, m2hash.bytes);
}
copy(c_old, c);
oldi = i;
i = (i + 1);
for (j = 0; j < rows; j++) {
memcpy(m1 + rows * 32 * cols + (32 * j), L[oldi][j].bytes, 32);
memcpy(m1 + rows * 32 * (cols + 1) + (32 * j), R[oldi][j].bytes, 32);
}
cn_fast_hash(c[i], m1, 32 * rows * (cols + 2));
}
key cc;
DP("c0");
DP(rv.cc);
DP("c_old");
DP(c_old);
sc_sub(c.bytes, c_old.bytes, rv.cc.bytes);
return sc_isnonzero(c.bytes) == 0;
}
sc_sub(cc.bytes, c[0].bytes, c[cols].bytes);
free(m1);
return sc_isnonzero(cc.bytes) == 0;
}
//proveRange and verRange
//proveRange gives C, and mask such that \sumCi = C

View file

@ -1,23 +1,23 @@
// Copyright (c) 2016, Monero Research Labs
//
// Author: Shen Noether <shen.noether@gmx.com>
//
//
// All rights reserved.
//
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@ -59,75 +59,76 @@ using namespace crypto;
namespace rct {
//Schnorr Non-linkable
//Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2
//Ver Verifies that signer knows an "x" such that xG = one of P1 or P2
//These are called in the below ASNL sig generation
void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index);
bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2);
//Schnorr Non-linkable
//Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2
//Ver Verifies that signer knows an "x" such that xG = one of P1 or P2
//These are called in the below ASNL sig generation
void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index);
bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2);
//Aggregate Schnorr Non-linkable Ring Signature (ASNL)
// c.f. http://eprint.iacr.org/2015/1098 section 5.
// These are used in range proofs (alternatively Borromean could be used)
// Gen gives a signature which proves the signer knows, for each i,
// an x[i] such that x[i]G = one of P1[i] or P2[i]
// Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i
asnlSig GenASNL(key64 x, key64 P1, key64 P2, bits indices);
bool VerASNL(key64 P1, key64 P2, asnlSig &as);
//Aggregate Schnorr Non-linkable Ring Signature (ASNL)
// c.f. http://eprint.iacr.org/2015/1098 section 5.
// These are used in range proofs (alternatively Borromean could be used)
// Gen gives a signature which proves the signer knows, for each i,
// an x[i] such that x[i]G = one of P1[i] or P2[i]
// Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i
asnlSig GenASNL(key64 x, key64 P1, key64 P2, bits indices);
bool VerASNL(key64 P1, key64 P2, asnlSig &as);
//Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
//These are aka MG signatutes in earlier drafts of the ring ct paper
// c.f. http://eprint.iacr.org/2015/1098 section 2.
// keyImageV just does I[i] = xx[i] * Hash(xx[i] * G) for each i
// Gen creates a signature which proves that for some column in the keymatrix "pk"
// the signer knows a secret key for each row in that column
// Ver verifies that the MG sig was created correctly
keyV keyImageV(const keyV &xx);
mgSig MLSAG_Gen(const keyM &pk, const keyV &xx, const int index);
bool MLSAG_Ver(keyM &pk, mgSig &sig);
//Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
//These are aka MG signatutes in earlier drafts of the ring ct paper
// c.f. http://eprint.iacr.org/2015/1098 section 2.
// keyImageV just does I[i] = xx[i] * HashToPoint(xx[i] * G) for each i
// Gen creates a signature which proves that for some column in the keymatrix "pk"
// the signer knows a secret key for each row in that column
// Ver verifies that the MG sig was created correctly
keyV keyImageV(const keyV &xx);
mgSig MLSAG_Gen(const keyM & pk, const keyV & xx, const int index);
bool MLSAG_Ver(keyM &pk, mgSig &sig);
//mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index);
//proveRange and verRange
//proveRange gives C, and mask such that \sumCi = C
// c.f. http://eprint.iacr.org/2015/1098 section 5.1
// and Ci is a commitment to either 0 or 2^i, i=0,...,63
// thus this proves that "amount" is in [0, 2^64]
// mask is a such that C = aG + bH, and b = amount
//verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i
rangeSig proveRange(key & C, key & mask, const xmr_amount & amount);
bool verRange(key & C, rangeSig & as);
//proveRange and verRange
//proveRange gives C, and mask such that \sumCi = C
// c.f. http://eprint.iacr.org/2015/1098 section 5.1
// and Ci is a commitment to either 0 or 2^i, i=0,...,63
// thus this proves that "amount" is in [0, 2^64]
// mask is a such that C = aG + bH, and b = amount
//verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i
rangeSig proveRange(key & C, key & mask, const xmr_amount & amount);
bool verRange(key & C, rangeSig & as);
//Ring-ct MG sigs
//Prove:
// c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10.
// This does the MG sig on the "dest" part of the given key matrix, and
// the last row is the sum of input commitments from that column - sum output commitments
// this shows that sum inputs = sum outputs
//Ver:
// verifies the above sig is created corretly
mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, int index);
bool verRctMG(mgSig mg, ctkeyM & pubs, ctkeyV & outPk);
//Ring-ct MG sigs
//Prove:
// c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10.
// This does the MG sig on the "dest" part of the given key matrix, and
// the last row is the sum of input commitments from that column - sum output commitments
// this shows that sum inputs = sum outputs
//Ver:
// verifies the above sig is created corretly
mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, int index);
bool verRctMG(mgSig mg, ctkeyM & pubs, ctkeyV & outPk);
//These functions get keys from blockchain
//replace these when connecting blockchain
//getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with
//populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk
// the return value are the key matrix, and the index where inPk was put (random).
void getKeyFromBlockchain(ctkey & a, size_t reference_index);
tuple<ctkeyM, xmr_amount> populateFromBlockchain(ctkeyV inPk, int mixin);
//These functions get keys from blockchain
//replace these when connecting blockchain
//getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with
//populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk
// the return value are the key matrix, and the index where inPk was put (random).
void getKeyFromBlockchain(ctkey & a, size_t reference_index);
tuple<ctkeyM, xmr_amount> populateFromBlockchain(ctkeyV inPk, int mixin);
//RingCT protocol
//genRct:
// creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the
// columns that are claimed as inputs, and that the sum of inputs = sum of outputs.
// Also contains masked "amount" and "mask" so the receiver can see how much they received
//verRct:
// verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct
//decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1)
// uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number
rctSig genRct(ctkeyV & inSk, ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> amounts, const int mixin);
bool verRct(rctSig & rv);
xmr_amount decodeRct(rctSig & rv, key & sk, int i);
//RingCT protocol
//genRct:
// creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the
// columns that are claimed as inputs, and that the sum of inputs = sum of outputs.
// Also contains masked "amount" and "mask" so the receiver can see how much they received
//verRct:
// verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct
//decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1)
// uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number
rctSig genRct(ctkeyV & inSk, ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> amounts, const int mixin);
bool verRct(rctSig & rv);
xmr_amount decodeRct(rctSig & rv, key & sk, int i);