derohe-miniblock-mod/walletapi/transaction_build.go
2021-11-24 09:20:51 +00:00

309 lines
11 KiB
Go

package walletapi
import "fmt"
import "strconv"
import "math/big"
//import "encoding/binary"
import mathrand "math/rand"
import "github.com/deroproject/derohe/globals"
import "github.com/deroproject/derohe/rpc"
import "github.com/deroproject/derohe/config"
import "github.com/deroproject/derohe/transaction"
import "github.com/deroproject/derohe/cryptography/crypto"
import "github.com/deroproject/derohe/cryptography/bn256"
// this is run some tests and benchmarks
type GenerateProofFunc func(scid crypto.Hash, scid_index int, s *crypto.Statement, witness *crypto.Witness, u *bn256.G1, txid crypto.Hash, burn_value uint64) *crypto.Proof
var GenerateProoffuncptr GenerateProofFunc = crypto.GenerateProof
// generate proof etc
func (w *Wallet_Memory) BuildTransaction(transfers []rpc.Transfer, emap [][][]byte, rings [][]*bn256.G1, block_hash crypto.Hash, height uint64, scdata rpc.Arguments, roothash []byte, max_bits int) *transaction.Transaction {
sender := w.account.Keys.Public.G1()
sender_secret := w.account.Keys.Secret.BigInt()
var retry_count int
rebuild_tx:
var tx transaction.Transaction
tx.Version = 1
tx.Height = height
tx.BLID = block_hash
tx.TransactionType = transaction.NORMAL
/*
if burn_value >= 1 {
tx.TransactionType = transaction.BURN_TX
tx.Value = burn_value
}
*/
if len(scdata) >= 1 {
tx.TransactionType = transaction.SC_TX
tx.SCDATA = scdata
}
crand := mathrand.New(globals.NewCryptoRandSource())
var witness_list []crypto.Witness
umap := map[string][]byte{}
fees_done := false
if retry_count%len(rings[0]) == 0 {
max_bits += 3
}
if max_bits >= 240 {
panic("currently we cannot use more than 240 bits")
}
for t, _ := range transfers {
var publickeylist, C, CLn, CRn []*bn256.G1
var D bn256.G1
receiver_addr, _ := rpc.NewAddress(transfers[t].Destination)
receiver := receiver_addr.PublicKey.G1()
var witness_index []int
for i := 0; i < len(rings[t]); i++ { // todocheck whether this is power of 2 or not
witness_index = append(witness_index, i)
}
for ; max_bits%8 != 0; max_bits++ { // round to next higher byte size
}
//witness_index[3], witness_index[1] = witness_index[1], witness_index[3]
for {
crand.Shuffle(len(witness_index), func(i, j int) {
witness_index[i], witness_index[j] = witness_index[j], witness_index[i]
})
// make sure sender and receiver are not both odd or both even
// sender will always be at witness_index[0] and receiver will always be at witness_index[1]
if witness_index[0]%2 != witness_index[1]%2 {
break
}
}
// Lots of ToDo for this, enables satisfying lots of other things
anonset_publickeys := rings[t][2:]
anonset_balances := emap[t][2:]
ebalances_list := make([]*crypto.ElGamal, 0, len(rings[t]))
for i := range witness_index {
switch i {
case witness_index[0]:
publickeylist = append(publickeylist, sender)
ebalances_list = append(ebalances_list, new(crypto.ElGamal).Deserialize(emap[t][0]))
case witness_index[1]:
publickeylist = append(publickeylist, receiver)
ebalances_list = append(ebalances_list, new(crypto.ElGamal).Deserialize(emap[t][1]))
default:
publickeylist = append(publickeylist, anonset_publickeys[0])
ebalances_list = append(ebalances_list, new(crypto.ElGamal).Deserialize(anonset_balances[0]))
anonset_publickeys = anonset_publickeys[1:]
anonset_balances = anonset_balances[1:]
}
// fmt.Printf("adding %d %s (ring count %d) \n", i,publickeylist[i].String(), len(anonset_publickeys))
}
// use updated balances if possible
for i := range publickeylist {
if bal, ok := umap[transfers[t].SCID.String()+publickeylist[i].String()]; ok {
ebalances_list[i] = new(crypto.ElGamal).Deserialize(bal)
}
}
// fmt.Printf("len of publickeylist %d \n", len(publickeylist))
// revealing r will disclose the amount and the sender and receiver and separate anonymous ring members
// calculate r deterministically, so its different every transaction, in emergency it can be given to other, and still will not allows key attacks
rinputs := append([]byte{}, roothash[:]...)
for i := range publickeylist {
rinputs = append(rinputs, publickeylist[i].EncodeCompressed()...)
}
rencrypted := new(bn256.G1).ScalarMult(crypto.HashToPoint(crypto.HashtoNumber(append([]byte(crypto.PROTOCOL_CONSTANT), rinputs...))), sender_secret)
r := crypto.ReducedHash(rencrypted.EncodeCompressed())
//fmt.Printf("t %d building transfers %+v\n", t, transfers[t])
//fmt.Printf("building t %d r calculated %s\n", t, r.Text(16))
var asset transaction.AssetPayload
asset.SCID = transfers[t].SCID
asset.BurnValue = transfers[t].Burn
fees := uint64(0)
value := transfers[t].Amount
burn_value := transfers[t].Burn
if asset.SCID.IsZero() && !fees_done {
fees = uint64(len(transfers)+2) * config.FEE_PER_KB
if data, err := scdata.MarshalBinary(); err != nil {
panic(err)
} else {
fees = fees + uint64((len(data)/1024)+3)*config.FEE_PER_KB
}
fees_done = true
}
for i := range publickeylist { // setup commitments
var x bn256.G1
switch {
case i == witness_index[0]:
x.ScalarMult(crypto.G, new(big.Int).SetInt64(0-int64(value)-int64(fees)-int64(burn_value))) // decrease senders balance
//fmt.Printf("sender %d %s \n", i, sender.String())
case i == witness_index[1]:
x.ScalarMult(crypto.G, new(big.Int).SetInt64(int64(value))) // increase receiver's balance
//fmt.Printf("receiver %d %s \n",i, receiver.String())
// lets encrypt the payment id, it's simple, we XOR the paymentID
//blinder := new(bn256.G1).ScalarMult(publickeylist[i], r)
// we must obfuscate it for non-client call, actual limit is 128, but for future testing/support
if len(publickeylist) >= 512 {
panic("currently we donot support ring size >= 512")
}
asset.RPCType = transaction.ENCRYPTED_DEFAULT_PAYLOAD_CBOR
data, _ := transfers[t].Payload_RPC.CheckPack(transaction.PAYLOAD0_LIMIT)
shared_key := crypto.GenerateSharedSecret(r, publickeylist[i])
asset.RPCPayload = append([]byte{byte(uint(witness_index[0]))}, data...)
//fmt.Printf("buulding shared_key %x index of receiver %d\n",shared_key,i)
//fmt.Printf("building plaintext payload %x\n",asset.RPCPayload)
//fmt.Printf("%d packed rpc payload %d %x\n ", t, len(data), data)
// make sure used data encryption is optional, just in case we would like to play together with ring members
// we intoduce an element to create dependency of input key, so receiver cannot prove otherwise
crypto.EncryptDecryptUserData(crypto.Keccak256(shared_key[:], publickeylist[i].EncodeCompressed()), asset.RPCPayload)
//fmt.Printf("building encrypted payload %x\n",asset.RPCPayload)
default:
x.ScalarMult(crypto.G, new(big.Int).SetInt64(0))
}
x.Add(new(bn256.G1).Set(&x), new(bn256.G1).ScalarMult(publickeylist[i], r)) // hide all commitments behind r
C = append(C, &x)
}
D.ScalarMult(crypto.G, r)
for i := range publickeylist {
ebalance := ebalances_list[i]
var ll, rr bn256.G1
//ebalance := b.balances[publickeylist[i].String()] // note these are taken from the chain live
ll.Add(ebalance.Left, C[i])
CLn = append(CLn, &ll)
// fmt.Printf("%d CLnG %x\n", i,CLn[i].EncodeCompressed())
rr.Add(ebalance.Right, &D)
CRn = append(CRn, &rr)
// fmt.Printf("%d CRnG %x\n",i, CRn[i].EncodeCompressed())
}
// decode sender (our) balance now, it might have been updated
balance := w.DecodeEncryptedBalanceNow(ebalances_list[witness_index[0]])
//fmt.Printf("t %d scid %s balance %d\n", t, transfers[t].SCID, balance)
// time for bullets-sigma
statement := GenerateStatement(CLn, CRn, publickeylist, C, &D, fees) // generate statement
copy(statement.Roothash[:], roothash[:])
statement.Bytes_per_publickey = byte(max_bits / 8)
witness := GenerateWitness(sender_secret, r, value, balance-value-fees-burn_value, witness_index)
witness_list = append(witness_list, witness)
// this goes to proof.u
//Print(statement, witness)
asset.Statement = statement
tx.Payloads = append(tx.Payloads, asset)
// get ready for another round by internal processing of state
for i := range publickeylist {
balance := ebalances_list[i]
echanges := crypto.ConstructElGamal(statement.C[i], statement.D)
balance = balance.Add(echanges) // homomorphic addition of changes
umap[transfers[t].SCID.String()+publickeylist[i].String()] = balance.Serialize() // reserialize and store
}
}
scid_map := map[crypto.Hash]int{}
for t := range transfers {
// the u is dependent on roothash,SCID and counter ( counter is dynamic and depends on order of assets)
uinput := append([]byte(crypto.PROTOCOL_CONSTANT), tx.Payloads[t].Statement.Roothash[:]...)
scid_index := scid_map[tx.Payloads[t].SCID]
scid_index_str := strconv.Itoa(scid_index)
uinput = append(uinput, tx.Payloads[t].SCID[:]...)
uinput = append(uinput, scid_index_str...)
u := new(bn256.G1).ScalarMult(crypto.HashToPoint(crypto.HashtoNumber(uinput)), sender_secret) // this should be moved to generate proof
scid_map[tx.Payloads[t].SCID] = scid_map[tx.Payloads[t].SCID] + 1
//tx.Payloads[t].Proof = crypto.GenerateProof(&tx.Payloads[t].Statement, &witness_list[t], u, tx.GetHash(), tx.Payloads[t].BurnValue)
tx.Payloads[t].Proof = GenerateProoffuncptr(tx.Payloads[t].SCID, scid_index, &tx.Payloads[t].Statement, &witness_list[t], u, tx.GetHash(), tx.Payloads[t].BurnValue)
}
// after the tx is serialized, it loses information which is then fed by blockchain
scid_map_t := map[crypto.Hash]int{}
for t := range tx.Payloads {
if tx.Payloads[t].Proof.Verify(tx.Payloads[t].SCID, scid_map_t[tx.Payloads[t].SCID], &tx.Payloads[t].Statement, tx.GetHash(), tx.Payloads[t].BurnValue) {
//fmt.Printf("TX verified with proof successfuly %s burn_value %d\n", tx.GetHash(), tx.Payloads[t].BurnValue)
} else {
fmt.Printf("TX verificat1ion failed, did u try sending more than you have !!!!!!!!!!\n")
}
scid_map_t[tx.Payloads[t].SCID] = scid_map_t[tx.Payloads[t].SCID] + 1
}
if tx.TransactionType == transaction.SC_TX {
if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) {
if rpc.SC_INSTALL == rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) {
txid := tx.GetHash()
if txid[0] < 0x80 || txid[31] < 0x80 { // last byte should be more than 0x80
if retry_count <= 20 {
//fmt.Printf("rebuilding tx %s retry_count %d\n", txid, retry_count)
goto rebuild_tx
}
}
}
}
}
// these 2 steps are only necessary, since blockchain doesn't accept unserialized txs
//var dtx transaction.Transaction
//_ = dtx.DeserializeHeader(tx.Serialize())
return &tx
}
// generate statement
func GenerateStatement(CLn, CRn, publickeylist, C []*bn256.G1, D *bn256.G1, fees uint64) crypto.Statement {
return crypto.Statement{CLn: CLn, CRn: CRn, Publickeylist: publickeylist, C: C, D: D, Fees: fees}
}
// generate witness
func GenerateWitness(secretkey, r *big.Int, TransferAmount, Balance uint64, index []int) crypto.Witness {
return crypto.Witness{SecretKey: secretkey, R: r, TransferAmount: TransferAmount, Balance: Balance, Index: index}
}