// Copyright 2017-2018 DERO Project. All rights reserved.
|
|
// Use of this source code in any form is governed by RESEARCH license.
|
|
// license can be found in the LICENSE file.
|
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
|
//
|
|
//
|
|
// 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.
|
|
|
|
package ringct
|
|
|
|
import "io"
|
|
import "fmt"
|
|
|
|
import "github.com/deroproject/derosuite/crypto"
|
|
|
|
// enable debuggin mode within ringct
|
|
// if true debugging mode enabled
|
|
const DEBUGGING_MODE = false
|
|
|
|
// TODO this package need serious love of atleast few weeks
|
|
// but atleast the parser and serdes works
|
|
// we neeed to expand everthing so as chances of a bug slippping in becomes very low
|
|
// NOTE:DO NOT waste time implmenting pre-RCT code
|
|
|
|
const (
|
|
RCTTypeNull = iota
|
|
RCTTypeFull
|
|
RCTTypeSimple
|
|
)
|
|
|
|
// Pedersen Commitment is generated from this struct
|
|
// C = aG + bH where a = mask and b = amount
|
|
// senderPk is the one-time public key for ECDH exchange
|
|
type ECdhTuple struct {
|
|
Mask Key `msgpack:"M"`
|
|
Amount Key `msgpack:"A"`
|
|
// senderPk Key
|
|
}
|
|
|
|
// Range proof commitments
|
|
type Key64 [64]Key
|
|
|
|
// Range Signature
|
|
// Essentially data for a Borromean Signature
|
|
type RangeSig struct {
|
|
asig BoroSig
|
|
ci Key64
|
|
}
|
|
|
|
// Borromean Signature
|
|
type BoroSig struct {
|
|
s0 Key64
|
|
s1 Key64
|
|
ee Key
|
|
}
|
|
|
|
// MLSAG (Multilayered Linkable Spontaneous Anonymous Group) Signature
|
|
type MlsagSig struct {
|
|
ss [][]Key
|
|
cc Key // this stores the starting point
|
|
II []Key // this stores the keyimage, but is taken from the tx,it is NOT serialized
|
|
}
|
|
|
|
// Confidential Transaction Keys, mask is Pedersen Commitment
|
|
// most of the time, it holds public keys, except (transaction making ) where it holds private keys
|
|
type CtKey struct {
|
|
Destination Key `msgpack:"D"` // this is the destination and needs to expanded from blockchain
|
|
Mask Key `msgpack:"M"` // this is the public key amount/commitment homomorphic mask
|
|
}
|
|
|
|
// Ring Confidential Signature parts that we have to keep
|
|
type RctSigBase struct {
|
|
sigType uint8
|
|
Message Key // transaction prefix hash
|
|
MixRing [][]CtKey // this is not serialized
|
|
pseudoOuts []Key
|
|
ECdhInfo []ECdhTuple
|
|
OutPk []CtKey // only mask amount is serialized
|
|
txFee uint64
|
|
|
|
Txid crypto.Hash // this field is extra and only used for logging purposes to track which txid was at fault
|
|
}
|
|
|
|
// Ring Confidential Signature parts that we can just prune later
|
|
type RctSigPrunable struct {
|
|
rangeSigs []RangeSig
|
|
MlsagSigs []MlsagSig // there can be as many mlsagsigs as many vins
|
|
}
|
|
|
|
// Ring Confidential Signature struct that can verify everything
|
|
type RctSig struct {
|
|
RctSigBase
|
|
RctSigPrunable
|
|
}
|
|
|
|
func (k *Key64) Serialize() (result []byte) {
|
|
for _, key := range k {
|
|
result = append(result, key[:]...)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (b *BoroSig) Serialize() (result []byte) {
|
|
result = append(b.s0.Serialize(), b.s1.Serialize()...)
|
|
result = append(result, b.ee[:]...)
|
|
return
|
|
}
|
|
|
|
func (r *RangeSig) Serialize() (result []byte) {
|
|
result = append(r.asig.Serialize(), r.ci.Serialize()...)
|
|
return
|
|
}
|
|
|
|
func (m *MlsagSig) Serialize() (result []byte) {
|
|
for i := 0; i < len(m.ss); i++ {
|
|
for j := 0; j < len(m.ss[i]); j++ {
|
|
result = append(result, m.ss[i][j][:]...)
|
|
}
|
|
}
|
|
result = append(result, m.cc[:]...)
|
|
return
|
|
}
|
|
|
|
func (r *RctSigBase) SerializeBase() (result []byte) {
|
|
result = []byte{r.sigType}
|
|
// Null type returns right away
|
|
if r.sigType == RCTTypeNull {
|
|
return
|
|
}
|
|
result = append(result, Uint64ToBytes(r.txFee)...)
|
|
if r.sigType == RCTTypeSimple {
|
|
for _, input := range r.pseudoOuts {
|
|
result = append(result, input[:]...)
|
|
}
|
|
}
|
|
for _, ecdh := range r.ECdhInfo {
|
|
result = append(result, ecdh.Mask[:]...)
|
|
result = append(result, ecdh.Amount[:]...)
|
|
}
|
|
for _, ctKey := range r.OutPk {
|
|
result = append(result, ctKey.Mask[:]...)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RctSigBase) BaseHash() (result crypto.Hash) {
|
|
result = crypto.Keccak256(r.SerializeBase())
|
|
return
|
|
}
|
|
|
|
func (r *RctSig) SerializePrunable() (result []byte) {
|
|
if r.sigType == RCTTypeNull {
|
|
return
|
|
}
|
|
for _, rangeSig := range r.rangeSigs {
|
|
result = append(result, rangeSig.Serialize()...)
|
|
}
|
|
for _, mlsagSig := range r.MlsagSigs {
|
|
result = append(result, mlsagSig.Serialize()...)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RctSig) Get_Sig_Type() byte {
|
|
return r.sigType
|
|
}
|
|
|
|
func (r *RctSig) Get_TX_Fee() (result uint64) {
|
|
if r.sigType == RCTTypeNull {
|
|
panic("RCTTypeNull cannot have TX fee")
|
|
}
|
|
return r.txFee
|
|
}
|
|
|
|
func (r *RctSig) PrunableHash() (result crypto.Hash) {
|
|
if r.sigType == RCTTypeNull {
|
|
return
|
|
}
|
|
result = crypto.Keccak256(r.SerializePrunable())
|
|
return
|
|
}
|
|
|
|
// this is the function which should be used by external world
|
|
// if any exceptions occur while handling, we simply return false
|
|
// transaction must be expanded before verification
|
|
// coinbase transactions are always success, since they are tied to PoW of block
|
|
func (r *RctSig) Verify() (result bool) {
|
|
|
|
result = false
|
|
defer func() { // safety so if anything wrong happens, verification fails
|
|
if r := recover(); r != nil {
|
|
//connection.logger.Fatalf("Recovered while Verify transaction", r)
|
|
fmt.Printf("Recovered while Verify transaction")
|
|
result = false
|
|
}
|
|
}()
|
|
|
|
switch r.sigType {
|
|
case RCTTypeNull:
|
|
return true /// this is only possible for miner tx
|
|
case RCTTypeFull:
|
|
return r.VerifyRctFull()
|
|
case RCTTypeSimple:
|
|
return r.VerifyRctSimple()
|
|
|
|
default:
|
|
return false
|
|
}
|
|
|
|
// can never reach here
|
|
// return false
|
|
}
|
|
|
|
// Verify a RCTTypeSimple RingCT Signature
|
|
func (r *RctSig) VerifyRctSimple() bool {
|
|
sumOutPks := identity()
|
|
for _, ctKey := range r.OutPk {
|
|
AddKeys(sumOutPks, sumOutPks, &ctKey.Mask)
|
|
}
|
|
//txFeeKey := ScalarMultH(d2h(r.txFee))
|
|
txFeeKey := Commitment_From_Amount(r.txFee)
|
|
AddKeys(sumOutPks, sumOutPks, &txFeeKey)
|
|
sumPseudoOuts := identity()
|
|
for _, pseudoOut := range r.pseudoOuts {
|
|
AddKeys(sumPseudoOuts, sumPseudoOuts, &pseudoOut)
|
|
}
|
|
if *sumPseudoOuts != *sumOutPks {
|
|
return false
|
|
}
|
|
for i, ctKey := range r.OutPk {
|
|
if !VerifyRange(&ctKey.Mask, r.rangeSigs[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return r.VerifyRCTSimple_Core()
|
|
}
|
|
|
|
func (r *RctSig) VerifyRctFull() bool {
|
|
for i, ctKey := range r.OutPk {
|
|
if !VerifyRange(&ctKey.Mask, r.rangeSigs[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return r.VerifyRCTFull_Core()
|
|
}
|
|
|
|
func ParseCtKey(buf io.Reader) (result CtKey, err error) {
|
|
if result.Mask, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func ParseKey64(buf io.Reader) (result Key64, err error) {
|
|
for i := 0; i < 64; i++ {
|
|
if result[i], err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// parse Borromean signature
|
|
func ParseBoroSig(buf io.Reader) (result BoroSig, err error) {
|
|
if result.s0, err = ParseKey64(buf); err != nil {
|
|
return
|
|
}
|
|
if result.s1, err = ParseKey64(buf); err != nil {
|
|
return
|
|
}
|
|
if result.ee, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// range data consists of Single Borromean sig and 64 keys for 64 bits
|
|
func ParseRangeSig(buf io.Reader) (result RangeSig, err error) {
|
|
if result.asig, err = ParseBoroSig(buf); err != nil {
|
|
return
|
|
}
|
|
if result.ci, err = ParseKey64(buf); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// parser for ringct signature
|
|
// we need to be extra cautious as almost anything cam come as input
|
|
func ParseRingCtSignature(buf io.Reader, nInputs, nOutputs, nMixin int) (result *RctSig, err error) {
|
|
r := new(RctSig)
|
|
sigType := make([]byte, 1)
|
|
_, err = buf.Read(sigType)
|
|
if err != nil {
|
|
return
|
|
}
|
|
r.sigType = uint8(sigType[0])
|
|
if r.sigType == RCTTypeNull {
|
|
result = r
|
|
return
|
|
}
|
|
|
|
/* This triggers go vet saying suspect OR
|
|
if (r.sigType != RCTTypeFull) || (r.sigType != RCTTypeSimple) {
|
|
err = fmt.Errorf("Bad signature Type %d", r.sigType)
|
|
return
|
|
}*/
|
|
|
|
switch r.sigType {
|
|
case RCTTypeFull:
|
|
case RCTTypeSimple:
|
|
default:
|
|
err = fmt.Errorf("Bad signature Type %d", r.sigType)
|
|
return
|
|
|
|
}
|
|
|
|
r.txFee, err = ReadVarInt(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var nMg, nSS int
|
|
if r.sigType == RCTTypeSimple {
|
|
nMg = nInputs
|
|
nSS = 2
|
|
r.pseudoOuts = make([]Key, nInputs)
|
|
for i := 0; i < nInputs; i++ {
|
|
if r.pseudoOuts[i], err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
nMg = 1
|
|
nSS = nInputs + 1
|
|
}
|
|
r.ECdhInfo = make([]ECdhTuple, nOutputs)
|
|
for i := 0; i < nOutputs; i++ {
|
|
if r.ECdhInfo[i].Mask, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
if r.ECdhInfo[i].Amount, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
r.OutPk = make([]CtKey, nOutputs)
|
|
for i := 0; i < nOutputs; i++ {
|
|
if r.OutPk[i], err = ParseCtKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
r.rangeSigs = make([]RangeSig, nOutputs)
|
|
for i := 0; i < nOutputs; i++ {
|
|
if r.rangeSigs[i], err = ParseRangeSig(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
r.MlsagSigs = make([]MlsagSig, nMg)
|
|
for i := 0; i < nMg; i++ {
|
|
r.MlsagSigs[i].ss = make([][]Key, nMixin+1)
|
|
for j := 0; j < nMixin+1; j++ {
|
|
r.MlsagSigs[i].ss[j] = make([]Key, nSS)
|
|
for k := 0; k < nSS; k++ {
|
|
if r.MlsagSigs[i].ss[j][k], err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if r.MlsagSigs[i].cc, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
result = r
|
|
return
|
|
}
|
|
|
|
/*
|
|
//Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a
|
|
// where C= aG + bH
|
|
void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec) {
|
|
key sharedSec1 = hash_to_scalar(sharedSec);
|
|
key sharedSec2 = hash_to_scalar(sharedSec1);
|
|
//encode
|
|
sc_add(unmasked.mask.bytes, unmasked.mask.bytes, sharedSec1.bytes);
|
|
sc_add(unmasked.amount.bytes, unmasked.amount.bytes, sharedSec2.bytes);
|
|
}
|
|
void ecdhDecode(ecdhTuple & masked, const key & sharedSec) {
|
|
key sharedSec1 = hash_to_scalar(sharedSec);
|
|
key sharedSec2 = hash_to_scalar(sharedSec1);
|
|
//decode
|
|
sc_sub(masked.mask.bytes, masked.mask.bytes, sharedSec1.bytes);
|
|
sc_sub(masked.amount.bytes, masked.amount.bytes, sharedSec2.bytes);
|
|
}
|
|
*/
|
|
func ecdhEncode(tuple *ECdhTuple, shared_secret Key) {
|
|
shared_secret1 := HashToScalar(shared_secret[:])
|
|
shared_secret2 := HashToScalar(shared_secret1[:])
|
|
|
|
// encode
|
|
ScAdd(&tuple.Mask, &tuple.Mask, shared_secret1)
|
|
ScAdd(&tuple.Amount, &tuple.Amount, shared_secret2)
|
|
}
|
|
|
|
func ecdhDecode(tuple *ECdhTuple, shared_secret Key) {
|
|
shared_secret1 := HashToScalar(shared_secret[:])
|
|
shared_secret2 := HashToScalar(shared_secret1[:])
|
|
|
|
// encode
|
|
ScSub(&tuple.Mask, &tuple.Mask, shared_secret1)
|
|
ScSub(&tuple.Amount, &tuple.Amount, shared_secret2)
|
|
}
|
|
|
|
// decode and verify a previously encrypted tuple
|
|
// the keys come in from the wallet
|
|
// tuple is the encoded data
|
|
// skey is the secret scalar key
|
|
// outpk is public key used to verify whether the decode was sucessfull
|
|
func Decode_Amount(tuple ECdhTuple, skey Key, outpk Key) (amount uint64, mask Key, result bool) {
|
|
var Ctmp Key
|
|
|
|
ecdhDecode(&tuple, skey) // decode the amounts
|
|
|
|
// saniity check similiar to original one
|
|
// addKeys2(Ctmp, mask, amount, H);
|
|
AddKeys2(&Ctmp, &tuple.Mask, &tuple.Amount, &H)
|
|
|
|
if Ctmp != outpk {
|
|
fmt.Printf("warning, amount decoded incorrectly, will be unable to spend")
|
|
result = false
|
|
return
|
|
}
|
|
amount = h2d(tuple.Amount)
|
|
mask = tuple.Mask
|
|
|
|
result = true
|
|
return
|
|
}
|