352 lines
9.4 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/romana/rlog"
import "github.com/deroproject/derohe/crypto"
import "github.com/deroproject/derohe/crypto/bn256"
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
MULTIUSER_TX // multi-user transaction
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 MULTIUSER_TX:
return "MULTIUSER_TX"
case SC_TX:
return "SMARTCONTRACT_TX"
default:
return "unknown transaction type"
}
}
// the core transaction
type Transaction_Prefix struct {
Version uint64 `json:"version"`
TransactionType TransactionType `json:"version"`
Value uint64 `json:"value"` // represnets premine, value for SC, BURN amount
Amounts []uint64 // open amounts for multi user tx
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
PaymentID [8]byte `json:"paymentid"` // hardcoded 8 bytes
}
type Transaction struct {
Transaction_Prefix // same as Transaction_Prefix
Statement crypto.Statement
Proof *crypto.Proof
}
// 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) IsRegistrationValid() (result bool) {
var u bn256.G1
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) DeserializeHeader(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")
}
if tx.Version != 1 {
return fmt.Errorf("Transaction version not equal to 1 \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:
tx.Value, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Premine value in Transaction\n")
}
buf = buf[done:]
if 33 != copy(tx.MinerAddress[:], buf[:]) {
return fmt.Errorf("Invalid Miner Address in Transaction\n")
}
buf = buf[33:]
goto done
case REGISTRATION:
if 33 != copy(tx.MinerAddress[:], buf[:33]) {
return fmt.Errorf("Invalid Miner Address in Transaction\n")
}
buf = buf[33:]
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:]
goto done
case COINBASE:
if 33 != copy(tx.MinerAddress[:], buf[:]) {
return fmt.Errorf("Invalid Miner Address in Transaction\n")
}
buf = buf[33:]
goto done
case NORMAL: // 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) < 8 {
return fmt.Errorf("Invalid payment id value in Transaction\n")
}
copy(tx.PaymentID[:], buf[:8])
buf = buf[8:]
tx.Proof = &crypto.Proof{}
r = bytes.NewReader(buf[:])
tx.Statement.Deserialize(r)
statement_size := len(buf) - r.Len()
// fmt.Printf("tx Statement size deserialing %d\n", statement_size)
// fmt.Printf("tx Proof size %d\n", len(buf) - statement_size)
buf = buf[statement_size:]
r = bytes.NewReader(buf[:])
if err := tx.Proof.Deserialize(r, crypto.GetPowerof2(len(tx.Statement.Publickeylist_compressed))); err != nil {
fmt.Printf("error deserialing proof err %s", err)
return err
}
// fmt.Printf("tx Proof size deserialed %d bytes remaining %d \n", len(buf) - r.Len(), r.Len())
if r.Len() != 0 {
return fmt.Errorf("Extra unknown data in Transaction, extrabytes %d\n", r.Len())
}
case BURN_TX:
panic("TODO")
case MULTIUSER_TX:
panic("TODO")
case SC_TX:
panic("TODO")
default:
panic("unknown transaction type")
}
done:
if len(buf) != 0 {
//return fmt.Errorf("Extra unknown data in Transaction\n")
}
//rlog.Tracef(8, "TX deserialized %+v\n", tx)
return nil //fmt.Errorf("Done Transaction\n")
}
// // 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, uint64(tx.TransactionType))
serialised_header.Write(buf[:n])
switch tx.TransactionType {
case PREMINE:
n := binary.PutUvarint(buf, tx.Value)
serialised_header.Write(buf[:n])
serialised_header.Write(tx.MinerAddress[:])
return serialised_header.Bytes()
case REGISTRATION:
serialised_header.Write(tx.MinerAddress[:])
serialised_header.Write(tx.C[:])
serialised_header.Write(tx.S[:])
return serialised_header.Bytes()
case COINBASE:
serialised_header.Write(tx.MinerAddress[:])
return serialised_header.Bytes()
case NORMAL:
n = binary.PutUvarint(buf, uint64(tx.Height))
serialised_header.Write(buf[:n])
serialised_header.Write(tx.PaymentID[:8]) // payment Id is always 8 bytes
return serialised_header.Bytes()
case BURN_TX:
panic("TODO")
case MULTIUSER_TX:
panic("TODO")
case SC_TX:
panic("TODO")
default:
panic("unknown transaction type")
}
return serialised_header.Bytes()
}
// serialize entire transaction include signature
func (tx *Transaction) Serialize() []byte {
var serialised bytes.Buffer
header_bytes := tx.SerializeHeader()
//base_bytes := tx.RctSignature.SerializeBase()
//prunable := tx.RctSignature.SerializePrunable()
serialised.Write(header_bytes)
if tx.Proof != nil {
// done_bytes := serialised.Len()
tx.Statement.Serialize(&serialised)
// statement_size := serialised.Len() - done_bytes
// fmt.Printf("tx statement_size serializing %d\n", statement_size)
//done_bytes =serialised.Len()
tx.Proof.Serialize(&serialised)
// fmt.Printf("tx Proof serialised size %d\n", serialised.Len() - done_bytes)
}
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, REGISTRATION, COINBASE:
case NORMAL, BURN_TX, MULTIUSER_TX, SC_TX:
tx.Statement.Serialize(&serialised)
default:
panic("unknown transaction type")
}
return serialised.Bytes() //buf
}