// Copyright (c) 2016, Monero Research Labs // // Author: Shen Noether // // 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 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "rctSigs.h" using namespace crypto; using namespace std; 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) { key c1, c2, L2; key a = skGen(); if (index == 0) { scalarmultBase(L1, a); skGen(s2); cn_fast_hash(c2, L1); addKeys2(L2, s2, c2, P2); cn_fast_hash(c1, L2); sc_mulsub(s1.bytes, x.bytes, c1.bytes, a.bytes); } if (index == 1) { scalarmultBase(L2, a); skGen(s1); cn_fast_hash(c1, L2); addKeys2(L1, s1, c1, P1); cn_fast_hash(c2, L1); sc_mulsub(s2.bytes, x.bytes, c2.bytes, a.bytes); } } //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 bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2) { key c2, L2, c1, L1p, cc; cn_fast_hash(c2, L1); addKeys2(L2, s2, c2, P2); cn_fast_hash(c1, L2); addKeys2(L1p, s1, c1, P1); sc_sub(cc.bytes, L1.bytes, L1p.bytes); return sc_isnonzero(cc.bytes) == 0; } //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) { DP("Generating Aggregate Schnorr Non-linkable Ring Signature\n"); key64 s1; int j = 0; asnlSig rv; rv.s = zero(); for (j = 0; j < ATOMS; j++) { //void GenSchnorrNonLinkable(Bytes L1, Bytes s1, Bytes s2, const Bytes x, const Bytes P1,const Bytes P2, int index) { GenSchnorrNonLinkable(rv.L1[j], s1[j], rv.s2[j], x[j], P1[j], P2[j], indices[j]); sc_add(rv.s.bytes, rv.s.bytes, s1[j].bytes); } return rv; } //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 bool VerASNL(key64 P1, key64 P2, asnlSig &as) { DP("Verifying Aggregate Schnorr Non-linkable Ring Signature\n"); key LHS = identity(); key RHS = scalarmultBase(as.s); key c2, L2, c1; int j = 0; for (j = 0; j < ATOMS; j++) { cn_fast_hash(c2, as.L1[j]); addKeys2(L2, as.s2[j], c2, P2[j]); addKeys(LHS, LHS, as.L1[j]); cn_fast_hash(c1, L2); addKeys(RHS, RHS, scalarmultKey(P1[j], c1)); } key cc; sc_sub(cc.bytes, LHS.bytes, RHS.bytes); return sc_isnonzero(cc.bytes) == 0; } //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) { keyV II(xx.size()); int i = 0; for (i = 0; i < xx.size(); i++) { II[i] = scalarmultKey(hashToPoint(scalarmultBase(xx[i])), xx[i]); } return II; } //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; 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, L, R, Hi; sc_0(c_old.bytes); vector Ip(rows); rv.II = keyV(rows); rv.ss = keyM(cols, rv.II); keyV alpha(rows); keyV aG(rows); keyV aHP(rows); key m2hash; unsigned char m2[96]; DP("here1"); for (i = 0; i < rows; 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); } int oldi = index; i = (index + 1) % cols; if (i == 0) { copy(rv.cc, c_old); } 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); m2hash = cn_fast_hash96(m2); sc_add(c.bytes, c.bytes, m2hash.bytes); } copy(c_old, c); oldi = i; 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.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 & 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; key c, L, R, Hi; key c_old = copy(rv.cc); vector Ip(rows); for (i= 0 ; i< rows ; i++) { precomp(Ip[i], rv.II[i]); } unsigned char m2[96]; key m2hash; int oldi = 0; i = 0; while (i < cols) { 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); m2hash = cn_fast_hash96(m2); sc_add(c.bytes, c.bytes, m2hash.bytes); } copy(c_old, c); oldi = i; i = (i + 1); } 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; } //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) { sc_0(mask.bytes); identity(C); bits b; d2b(b, amount); rangeSig sig; key64 ai; key64 CiH; int i = 0; for (i = 0; i < ATOMS; i++) { sc_0(ai[i].bytes); if (b[i] == 0) { scalarmultBase(sig.Ci[i], ai[i]); } if (b[i] == 1) { addKeys1(sig.Ci[i], ai[i], H2[i]); } subKeys(CiH[i], sig.Ci[i], H2[i]); sc_add(mask.bytes, mask.bytes, ai[i].bytes); addKeys(C, C, sig.Ci[i]); } sig.asig = GenASNL(ai, sig.Ci, CiH, b); return sig; } //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 bool verRange(key & C, rangeSig & as) { key64 CiH; int i = 0; key Ctmp = identity(); for (i = 0; i < 64; i++) { subKeys(CiH[i], as.Ci[i], H2[i]); addKeys(Ctmp, Ctmp, as.Ci[i]); } bool reb = equalKeys(C, Ctmp); DP("is sum Ci = C:"); DP(reb); bool rab = VerASNL(as.Ci, CiH, as.asig); DP("Is in range?"); DP(rab); return (reb && rab); } //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 ctkeyV &outSk, const ctkeyV & outPk, int index) { mgSig mg; //setup vars int rows = pubs[0].size(); int cols = pubs.size(); keyV sk(rows + 1); keyV tmp(rows + 1); int i = 0, j = 0; for (i = 0; i < rows + 1; i++) { sc_0(sk[i].bytes); identity(tmp[i]); } keyM M(cols, tmp); //create the matrix to mg sig for (i = 0; i < cols; i++) { M[i][rows] = identity(); for (j = 0; j < rows; j++) { M[i][j] = pubs[i][j].dest; addKeys(M[i][rows], M[i][rows], pubs[i][j].mask); } } sc_0(sk[rows].bytes); for (j = 0; j < rows; j++) { sk[j] = copy(inSk[j].dest); sc_add(sk[rows].bytes, sk[rows].bytes, inSk[j].mask.bytes); } for (i = 0; i < cols; i++) { for (j = 0; j < outPk.size(); j++) { subKeys(M[i][rows], M[i][rows], outPk[j].mask); } } for (j = 0; j < outPk.size(); j++) { sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); } return MLSAG_Gen(M, sk, index); } //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 bool verRctMG(mgSig mg, ctkeyM & pubs, ctkeyV & outPk) { //setup vars int rows = pubs[0].size(); int cols = pubs.size(); keyV tmp(rows + 1); int i = 0, j = 0; for (i = 0; i < rows + 1; i++) { identity(tmp[i]); } keyM M(cols, tmp); //create the matrix to mg sig for (j = 0; j < rows; j++) { for (i = 0; i < cols; i++) { M[i][j] = pubs[i][j].dest; addKeys(M[i][rows], M[i][rows], pubs[i][j].mask); } } for (j = 0; j < outPk.size(); j++) { for (i = 0; i < cols; i++) { subKeys(M[i][rows], M[i][rows], outPk[j].mask); } } return MLSAG_Ver(M, mg); } //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) { a.mask = pkGen(); a.dest = pkGen(); } //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). tuple populateFromBlockchain(ctkeyV inPk, int mixin) { int rows = inPk.size(); ctkeyM rv(mixin, inPk); int index = randXmrAmount(mixin); int i = 0, j = 0; for (i = 0; i < mixin; i++) { if (i != index) { for (j = 0; j < rows; j++) { getKeyFromBlockchain(rv[i][j], (size_t)randXmrAmount); } } } return make_tuple(rv, index); } //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 amounts, const int mixin) { rctSig rv; rv.outPk.resize(destinations.size()); rv.rangeSigs.resize(destinations.size()); rv.ecdhInfo.resize(destinations.size()); int i = 0; keyV masks(destinations.size()); //sk mask.. ctkeyV outSk(destinations.size()); for (i = 0; i < destinations.size(); i++) { //add destination to sig rv.outPk[i].dest = copy(destinations[i]); //compute range proof rv.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, amounts[i]); #ifdef DBG verRange(rv.outPk[i].mask, rv.rangeSigs[i]); #endif //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(amounts[i]); ecdhEncode(rv.ecdhInfo[i], destinations[i]); } int index; tie(rv.mixRing, index) = populateFromBlockchain(inPk, mixin); rv.MG = proveRctMG(rv.mixRing, inSk, outSk, rv.outPk, index); return rv; } //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 bool verRct(rctSig & rv) { int i = 0; bool rvb = true; bool tmp; DP("range proofs verified?"); for (i = 0; i < rv.outPk.size(); i++) { tmp = verRange(rv.outPk[i].mask, rv.rangeSigs[i]); DP(tmp); rvb = (rvb && tmp); } bool mgVerd = verRctMG(rv.MG, rv.mixRing, rv.outPk); DP("mg sig verified?"); DP(mgVerd); return (rvb && mgVerd); } //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 xmr_amount decodeRct(rctSig & rv, key & sk, int i) { //mask amount and mask ecdhDecode(rv.ecdhInfo[i], sk); key mask = rv.ecdhInfo[i].mask; key amount = rv.ecdhInfo[i].amount; key C = rv.outPk[i].mask; DP("C"); DP(C); key Ctmp; addKeys2(Ctmp, mask, amount, H); DP("Ctmp"); DP(Ctmp); if (equalKeys(C, Ctmp) == false) { printf("warning, amount decoded incorrectly, will be unable to spend"); } return h2d(amount); } }