497 lines
14 KiB
Go
497 lines
14 KiB
Go
// Copyright 2017-2021 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 transaction
|
|
|
|
import "fmt"
|
|
import "bytes"
|
|
import "math/big"
|
|
import "encoding/binary"
|
|
|
|
import "github.com/deroproject/derohe/cryptography/crypto"
|
|
import "github.com/deroproject/derohe/cryptography/bn256"
|
|
import "github.com/deroproject/derohe/rpc"
|
|
|
|
type TransactionType uint64
|
|
|
|
const (
|
|
PREMINE TransactionType = iota // premine is represented by this
|
|
REGISTRATION // registration tx are represented by this
|
|
COINBASE // normal coinbase tx ( if miner address is already registered)
|
|
NORMAL // one to one TX with ring members
|
|
BURN_TX // if user burns an amount to control inflation
|
|
SC_TX // smart contract transaction
|
|
|
|
)
|
|
|
|
func (t TransactionType) String() string {
|
|
switch t {
|
|
case PREMINE:
|
|
return "PREMINE"
|
|
case REGISTRATION:
|
|
return "REGISTRATION"
|
|
case COINBASE:
|
|
return "COINBASE"
|
|
case NORMAL:
|
|
return "NORMAL"
|
|
case BURN_TX:
|
|
return "BURN"
|
|
case SC_TX:
|
|
return "SC"
|
|
|
|
default:
|
|
return "unknown transaction type"
|
|
}
|
|
}
|
|
|
|
const PAYLOAD_LIMIT = 1 + 144 // entire payload header is mandatorily encrypted
|
|
// sender position in ring representation in a byte, uptp 256 ring
|
|
// 144 byte payload ( to implement specific functionality such as delivery of keys etc), user dependent encryption
|
|
const PAYLOAD0_LIMIT = 144 // 1 byte has been reserved for sender position in ring representation in a byte, uptp 256 ring
|
|
|
|
const ENCRYPTED_DEFAULT_PAYLOAD_CBOR = byte(0)
|
|
|
|
// the core transaction
|
|
// in our design, tx cam be sent by 1 wallet, but SC part/gas can be signed by any other user, but this is not implemented
|
|
type Transaction_Prefix struct {
|
|
Version uint64 `json:"version"`
|
|
|
|
SourceNetwork uint64 `json:"source_network"` // which network originated the transaction acting as source
|
|
DestNetwork uint64 `json:"dest_network"` // which network is acting as sink
|
|
// other networks may work with 0 fees and custom block time of < 1 sec
|
|
// above protocol which is majorly complete would be enough for entire EARTH !!
|
|
// thereby representing immense scalability and privacy both at the same time
|
|
// default dero network has id 0
|
|
|
|
TransactionType TransactionType `json:"txtype"`
|
|
|
|
Value uint64 `json:"value"` // represents value for premine, SC, BURN transactions
|
|
MinerAddress [33]byte `json:"miner_address"` // miner address // 33 bytes also used for registration
|
|
C [32]byte `json:"c"` // used for registration
|
|
S [32]byte `json:"s"` // used for registration
|
|
Height uint64 `json:"height"` // height at the state, used to cross-check state
|
|
BLID [32]byte `json:"blid"` // which is used to build the tx
|
|
SCDATA rpc.Arguments `json:"scdata"` // all SC related data is provided here, an SC tx uses all the fields
|
|
}
|
|
|
|
type AssetPayload struct {
|
|
SCID crypto.Hash // which asset, it's zero for main asset
|
|
BurnValue uint64 `json:"value"` // represents value for premine, SC, BURN transactions
|
|
|
|
RPCType byte // its unencrypted and is by default 0 for almost all txs
|
|
RPCPayload []byte // rpc payload encryption depends on RPCType
|
|
|
|
// sender position in ring representation in a byte, uptp 256 ring
|
|
// 144 byte payload ( to implement specific functionality such as delivery of keys etc), user dependent encryption
|
|
Statement crypto.Statement // note statement containts fees
|
|
Proof *crypto.Proof
|
|
}
|
|
|
|
// marshal asset
|
|
/*
|
|
func (a AssetPayload) MarshalHeader() ([]byte, error) {
|
|
|
|
return writer.Bytes(), nil
|
|
}
|
|
*/
|
|
|
|
func (a AssetPayload) MarshalHeaderStatement() ([]byte, error) {
|
|
var writer bytes.Buffer
|
|
var buffer_backing [binary.MaxVarintLen64]byte
|
|
buf := buffer_backing[:]
|
|
_ = buf
|
|
|
|
n := binary.PutUvarint(buf, a.BurnValue)
|
|
writer.Write(buf[:n])
|
|
|
|
writer.Write(a.SCID[:])
|
|
|
|
writer.WriteByte(a.RPCType) // payload type byte
|
|
writer.Write(a.RPCPayload) // src Id is always payload limit bytes
|
|
|
|
if len(a.RPCPayload) != PAYLOAD_LIMIT {
|
|
return nil, fmt.Errorf("RPCPayload should be %d bytes, but have %d bytes", PAYLOAD_LIMIT, len(a.RPCPayload))
|
|
}
|
|
|
|
a.Statement.Serialize(&writer)
|
|
//if err != nil {
|
|
// return nil,err
|
|
//}
|
|
|
|
return writer.Bytes(), nil
|
|
}
|
|
|
|
func (a *AssetPayload) UnmarshalHeaderStatement(r *bytes.Reader) (err error) {
|
|
if a.BurnValue, err = binary.ReadUvarint(r); err != nil {
|
|
return err
|
|
}
|
|
if _, err = r.Read(a.SCID[:]); err != nil {
|
|
return err
|
|
}
|
|
if a.RPCType, err = r.ReadByte(); err != nil {
|
|
return err
|
|
}
|
|
|
|
a.RPCPayload = make([]byte, PAYLOAD_LIMIT, PAYLOAD_LIMIT)
|
|
if _, err = r.Read(a.RPCPayload[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = a.Statement.Deserialize(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a AssetPayload) MarshalProofs() ([]byte, error) {
|
|
var writer bytes.Buffer
|
|
a.Proof.Serialize(&writer)
|
|
|
|
return writer.Bytes(), nil
|
|
}
|
|
|
|
func (a *AssetPayload) UnmarshalProofs(r *bytes.Reader) (err error) {
|
|
a.Proof = &crypto.Proof{}
|
|
|
|
if err = a.Proof.Deserialize(r, crypto.GetPowerof2(len(a.Statement.Publickeylist_pointers)/int(a.Statement.Bytes_per_publickey))); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
type Transaction struct {
|
|
Transaction_Prefix // same as Transaction_Prefix
|
|
|
|
Payloads []AssetPayload // each transaction can have a number of payloads
|
|
}
|
|
|
|
// this excludes the proof part, so it can pruned
|
|
func (tx *Transaction) GetHash() (result crypto.Hash) {
|
|
switch tx.Version {
|
|
case 1:
|
|
result = crypto.Hash(crypto.Keccak256(tx.SerializeCoreStatement()))
|
|
default:
|
|
panic("Transaction version unknown")
|
|
|
|
}
|
|
return
|
|
}
|
|
|
|
// returns whether the tx is coinbase
|
|
func (tx *Transaction) IsCoinbase() (result bool) {
|
|
return tx.TransactionType == COINBASE
|
|
}
|
|
|
|
func (tx *Transaction) IsRegistration() (result bool) {
|
|
return tx.TransactionType == REGISTRATION
|
|
}
|
|
|
|
func (tx *Transaction) IsPremine() (result bool) {
|
|
return tx.TransactionType == PREMINE
|
|
}
|
|
|
|
func (tx *Transaction) IsSC() (result bool) {
|
|
return tx.TransactionType == SC_TX
|
|
}
|
|
|
|
// if external proof is required
|
|
func (tx *Transaction) IsProofRequired() (result bool) {
|
|
return (tx.IsCoinbase() || tx.IsRegistration() || tx.IsPremine()) == false
|
|
|
|
}
|
|
|
|
func (tx *Transaction) Fees() (fees uint64) {
|
|
var zero_scid [32]byte
|
|
for i := range tx.Payloads {
|
|
if zero_scid == tx.Payloads[i].SCID {
|
|
fees += tx.Payloads[i].Statement.Fees
|
|
}
|
|
}
|
|
return fees
|
|
}
|
|
|
|
func (tx *Transaction) IsRegistrationValid() (result bool) {
|
|
|
|
var u bn256.G1
|
|
|
|
if tx.TransactionType != REGISTRATION {
|
|
return false
|
|
}
|
|
|
|
if err := u.DecodeCompressed(tx.MinerAddress[0:33]); err != nil {
|
|
return false
|
|
}
|
|
|
|
s := new(big.Int).SetBytes(tx.S[:])
|
|
c := new(big.Int).SetBytes(tx.C[:])
|
|
|
|
tmppoint := new(bn256.G1).Add(new(bn256.G1).ScalarMult(crypto.G, s), new(bn256.G1).ScalarMult(&u, new(big.Int).Neg(c)))
|
|
serialize := []byte(fmt.Sprintf("%s%s", u.String(), tmppoint.String()))
|
|
|
|
c_calculated := crypto.ReducedHash(serialize)
|
|
if c.String() == c_calculated.String() {
|
|
return true
|
|
}
|
|
//return fmt.Errorf("Registration signature is invalid")
|
|
return false
|
|
}
|
|
|
|
func (tx *Transaction) Deserialize(buf []byte) (err error) {
|
|
var tmp_uint64 uint64
|
|
var r *bytes.Reader
|
|
tx.Clear() // clear existing
|
|
|
|
done := 0
|
|
tx.Version, done = binary.Uvarint(buf)
|
|
if done <= 0 {
|
|
return fmt.Errorf("Invalid Version in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
|
|
if tx.Version != 1 {
|
|
return fmt.Errorf("Transaction version not equal to 1 \n")
|
|
}
|
|
|
|
tx.SourceNetwork, done = binary.Uvarint(buf)
|
|
if done <= 0 {
|
|
return fmt.Errorf("Invalid SourceNetwork in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
|
|
tx.DestNetwork, done = binary.Uvarint(buf)
|
|
if done <= 0 {
|
|
return fmt.Errorf("Invalid DestNetwork in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
|
|
tmp_uint64, done = binary.Uvarint(buf)
|
|
if done <= 0 {
|
|
return fmt.Errorf("Invalid TransactionType in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
tx.TransactionType = TransactionType(tmp_uint64)
|
|
|
|
switch tx.TransactionType {
|
|
case PREMINE, REGISTRATION, COINBASE, BURN_TX, NORMAL, SC_TX:
|
|
default:
|
|
panic("unknown transaction type")
|
|
}
|
|
|
|
if tx.TransactionType == PREMINE || tx.TransactionType == BURN_TX || tx.TransactionType == SC_TX {
|
|
tx.Value, done = binary.Uvarint(buf)
|
|
if done <= 0 {
|
|
return fmt.Errorf("Invalid Premine value in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
}
|
|
|
|
if tx.TransactionType == PREMINE || tx.TransactionType == COINBASE || tx.TransactionType == REGISTRATION {
|
|
if 33 != copy(tx.MinerAddress[:], buf[:]) {
|
|
return fmt.Errorf("Invalid Miner Address in Transaction\n")
|
|
}
|
|
buf = buf[33:]
|
|
}
|
|
|
|
if tx.TransactionType == REGISTRATION {
|
|
if 32 != copy(tx.C[:], buf[:32]) {
|
|
return fmt.Errorf("Invalid C in Transaction\n")
|
|
}
|
|
buf = buf[32:]
|
|
|
|
if 32 != copy(tx.S[:], buf[:32]) {
|
|
return fmt.Errorf("Invalid S in Transaction\n")
|
|
}
|
|
buf = buf[32:]
|
|
}
|
|
|
|
if tx.TransactionType == BURN_TX || tx.TransactionType == NORMAL || tx.TransactionType == SC_TX {
|
|
// parse height and root hash
|
|
tx.Height, done = binary.Uvarint(buf)
|
|
if done <= 0 {
|
|
return fmt.Errorf("Invalid Height value in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
if len(buf) < 32 {
|
|
return fmt.Errorf("Invalid BLID value in Transaction\n")
|
|
}
|
|
copy(tx.BLID[:], buf[:32])
|
|
buf = buf[32:]
|
|
|
|
var asset_count uint64
|
|
asset_count, done = binary.Uvarint(buf)
|
|
if done <= 0 || asset_count < 1 {
|
|
return fmt.Errorf("Invalid asset_count in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
|
|
for i := uint64(0); i < asset_count; i++ {
|
|
var a AssetPayload
|
|
|
|
r = bytes.NewReader(buf[:])
|
|
if err = a.UnmarshalHeaderStatement(r); err != nil {
|
|
panic(err)
|
|
}
|
|
tx.Payloads = append(tx.Payloads, a)
|
|
buf = buf[len(buf)-r.Len():]
|
|
}
|
|
|
|
}
|
|
|
|
if tx.TransactionType == SC_TX {
|
|
var sc_len uint64
|
|
sc_len, done = binary.Uvarint(buf)
|
|
if done <= 0 {
|
|
return fmt.Errorf("Invalid sc length in Transaction\n")
|
|
}
|
|
buf = buf[done:]
|
|
if sc_len > uint64(len(buf)) { // we are are crossing tx_boundary
|
|
return fmt.Errorf("SC len out of possible range")
|
|
}
|
|
if err := tx.SCDATA.UnmarshalBinary(buf[:sc_len]); err != nil {
|
|
return err
|
|
}
|
|
buf = buf[sc_len:]
|
|
}
|
|
|
|
if tx.TransactionType == BURN_TX || tx.TransactionType == NORMAL || tx.TransactionType == SC_TX {
|
|
|
|
for i := range tx.Payloads {
|
|
tx.Payloads[i].Proof = &crypto.Proof{}
|
|
|
|
r = bytes.NewReader(buf[:])
|
|
if err = tx.Payloads[i].UnmarshalProofs(r); err != nil {
|
|
panic(err)
|
|
}
|
|
buf = buf[len(buf)-r.Len():]
|
|
}
|
|
}
|
|
|
|
if len(buf) != 0 && !(tx.TransactionType == PREMINE || tx.TransactionType == COINBASE) { // these tx are complete
|
|
return fmt.Errorf("Extra unknown data in Transaction, extrabytes %d\n", len(buf))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// // clean the transaction everything
|
|
func (tx *Transaction) Clear() {
|
|
tx = &Transaction{}
|
|
}
|
|
|
|
func (tx *Transaction) SerializeHeader() []byte {
|
|
|
|
var serialised_header bytes.Buffer
|
|
|
|
var buffer_backing [binary.MaxVarintLen64]byte
|
|
|
|
buf := buffer_backing[:]
|
|
|
|
n := binary.PutUvarint(buf, tx.Version)
|
|
serialised_header.Write(buf[:n])
|
|
|
|
n = binary.PutUvarint(buf, tx.SourceNetwork)
|
|
serialised_header.Write(buf[:n])
|
|
|
|
n = binary.PutUvarint(buf, tx.DestNetwork)
|
|
serialised_header.Write(buf[:n])
|
|
|
|
switch tx.TransactionType {
|
|
case PREMINE, REGISTRATION, COINBASE, BURN_TX, NORMAL, SC_TX:
|
|
default:
|
|
panic("unknown transaction type")
|
|
}
|
|
|
|
n = binary.PutUvarint(buf, uint64(tx.TransactionType))
|
|
serialised_header.Write(buf[:n])
|
|
|
|
if tx.TransactionType == PREMINE || tx.TransactionType == BURN_TX || tx.TransactionType == SC_TX {
|
|
n := binary.PutUvarint(buf, tx.Value)
|
|
serialised_header.Write(buf[:n])
|
|
}
|
|
if tx.TransactionType == PREMINE || tx.TransactionType == COINBASE || tx.TransactionType == REGISTRATION {
|
|
serialised_header.Write(tx.MinerAddress[:])
|
|
}
|
|
if tx.TransactionType == REGISTRATION {
|
|
serialised_header.Write(tx.C[:])
|
|
serialised_header.Write(tx.S[:])
|
|
}
|
|
|
|
if tx.TransactionType == BURN_TX || tx.TransactionType == NORMAL || tx.TransactionType == SC_TX {
|
|
n = binary.PutUvarint(buf, uint64(tx.Height))
|
|
serialised_header.Write(buf[:n])
|
|
serialised_header.Write(tx.BLID[:])
|
|
|
|
n = binary.PutUvarint(buf, uint64(len(tx.Payloads)))
|
|
serialised_header.Write(buf[:n])
|
|
|
|
for _, p := range tx.Payloads {
|
|
if pheader_bytes, err := p.MarshalHeaderStatement(); err == nil {
|
|
serialised_header.Write(pheader_bytes)
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if tx.TransactionType == SC_TX {
|
|
if data, err := tx.SCDATA.MarshalBinary(); err != nil {
|
|
panic(err)
|
|
} else {
|
|
n = binary.PutUvarint(buf, uint64(len(data)))
|
|
serialised_header.Write(buf[:n])
|
|
serialised_header.Write(data)
|
|
}
|
|
}
|
|
|
|
return serialised_header.Bytes()
|
|
}
|
|
|
|
// serialize entire transaction include signature
|
|
func (tx *Transaction) Serialize() []byte {
|
|
var serialised bytes.Buffer
|
|
header_bytes := tx.SerializeHeader()
|
|
serialised.Write(header_bytes)
|
|
for _, p := range tx.Payloads {
|
|
if pheader_bytes, err := p.MarshalProofs(); err == nil {
|
|
serialised.Write(pheader_bytes)
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
return serialised.Bytes() //buf
|
|
}
|
|
|
|
// TXID excludes proof, rest everything is included
|
|
func (tx *Transaction) SerializeCoreStatement() []byte {
|
|
var serialised bytes.Buffer
|
|
header_bytes := tx.SerializeHeader()
|
|
serialised.Write(header_bytes)
|
|
|
|
switch tx.TransactionType {
|
|
case PREMINE, COINBASE:
|
|
case REGISTRATION:
|
|
case NORMAL, BURN_TX, SC_TX:
|
|
default:
|
|
panic("unknown transaction type")
|
|
}
|
|
|
|
return serialised.Bytes() //buf
|
|
}
|