Archived
2
0
This repository has been archived on 2023-03-02. You can view files and clone it, but cannot push or open issues or pull requests.
kerma/models/transaction.go
2023-03-02 15:28:43 +01:00

252 lines
5.5 KiB
Go

// Package models provides the models for the KermaGo application
package models
import (
"bytes"
"crypto/ed25519"
"crypto/sha256"
"encoding/hex"
"errors"
"kerma/helpers"
"strconv"
"github.com/docker/go/canonical/json"
)
/*
An Outpoint is a struct that represents an outpoint as per the Kerma specification
*/
type Outpoint struct {
Txid string `json:"txid" binding:"required"`
Index int64 `json:"index" binding:"required"`
}
/*
An Input is a struct that represents an input as per the Kerma specification
*/
type Input struct {
Outpoint Outpoint `json:"outpoint" binding:"required"`
Sig string `json:"sig" binding:"required"`
}
type inputVerifier struct {
Outpoint Outpoint `json:"outpoint" binding:"required"`
Sig *string `json:"sig" binding:"required"`
}
/*
An Output is a struct that represents an output as per the Kerma specification
*/
type Output struct {
Pubkey string `json:"pubkey" binding:"required"`
Value uint64 `json:"value" binding:"required"`
}
/*
A Transaction is a struct that represents a transaction as per the Kerma specification
*/
type Transaction struct {
Type string `json:"type" binding:"required"`
Height *uint64 `json:"height,omitempty"`
Inputs []Input `json:"inputs,omitempty"`
Outputs []Output `json:"outputs,omitempty"`
Confirmed bool `json:"-"`
}
type transactionVerifier struct {
Type string `json:"type" binding:"required"`
Inputs []inputVerifier `json:"inputs,omitempty"`
Outputs []Output `json:"outputs,omitempty"`
}
/*
Hash returns the sha256 hash of the canonical JSON of the transaction
*/
func (t *Transaction) Hash() (string, error) {
transaction, err := t.MarshalJson()
if err != nil {
return "", errors.New("could not parse transaction as json")
}
hashSum := sha256.Sum256(transaction)
return hex.EncodeToString(hashSum[:]), nil
}
/*
GetID returns the transaction id
*/
func (b *Transaction) GetID() string {
id, err := b.Hash()
if err != nil {
return ""
}
return id
}
/*
GetType returns the type of the transaction
*/
func (t *Transaction) GetType() string {
return t.Type
}
/*
GetEntity returns the transaction
*/
func (t *Transaction) GetEntity() interface{} {
return t
}
/*
MarshalJson returns the canonical JSON of the transaction
*/
func (t *Transaction) MarshalJson() ([]byte, error) {
return json.MarshalCanonical(t)
}
/*
UnmarshalJSON creates a transaction from the input JSON byte array
*/
func (t *Transaction) UnmarshalJSON(data []byte) error {
if len(data) == 0 {
return nil
}
type tmp Transaction
err := json.Unmarshal(data, (*tmp)(t))
if err != nil {
return err
}
err = t.Validate()
if err != nil {
return err
}
return nil
}
/*
Validate is responsible for validating the transaction; returns an error if unsuccessful
*/
func (t *Transaction) Validate() error {
if t.Type != "transaction" {
return errors.New("object not a transaction")
}
if t.IsCoinbaseTx() {
if len(t.Inputs) > 0 {
return errors.New("coinbase transactions cannot have an input")
}
if len(t.Outputs) != 1 {
return errors.New("coinbase transactions must have exactly one output")
}
}
txOutpoints := map[string]int{}
for _, input := range t.Inputs {
if input.Outpoint.Index < 0 {
return errors.New("transaction input index cannot be negative")
}
outpointId := input.Outpoint.Txid + "-" + strconv.FormatInt(input.Outpoint.Index, 10)
if _, ok := txOutpoints[outpointId]; ok {
return errors.New("multiple transaction inputs with same outpoint found")
}
txOutpoints[outpointId] = 1
}
for _, output := range t.Outputs {
if output.Value < 0 {
return errors.New("transaction output value cannot be negative")
}
pubkeyBytes, err := hex.DecodeString(output.Pubkey)
if err != nil {
return err
}
if ed25519.PublicKey(string(pubkeyBytes)) == nil {
return errors.New("invalid public key value, transaction invalid")
}
}
return nil
}
/*
String returns the canonical JSON of the transaction as a string
*/
func (t *Transaction) String() string {
res, _ := t.MarshalJson()
return string(res)
}
/*
VerifySign verifies whether the signature of the transaction with the provided pubkey matches the signature provided
*/
func (t *Transaction) VerifySign(sig string, pubkeyString string) bool {
var logger helpers.Logger
newTransaction := transactionVerifier{
Type: t.Type,
Inputs: []inputVerifier{},
Outputs: t.Outputs,
}
for _, i := range t.Inputs {
newInput := inputVerifier{
Outpoint: i.Outpoint,
Sig: nil,
}
newTransaction.Inputs = append(newTransaction.Inputs, newInput)
}
txBytes := new(bytes.Buffer)
encoder := json.NewEncoder(txBytes)
encoder.Canonical()
encoder.Encode(newTransaction)
pubkeyBytes, err := hex.DecodeString(pubkeyString)
if err != nil {
return false
}
sigBytes, err := hex.DecodeString(sig)
if err != nil {
return false
}
logger.Debug("Checking transaction signature with params: SIGNATURE(" + sig + "), PUBKEY(" + pubkeyString + "), TRANSACTION(" + txBytes.String() + ")")
pubkey := ed25519.PublicKey(string(pubkeyBytes))
if len(pubkey) != ed25519.PublicKeySize {
return false
}
return ed25519.Verify(ed25519.PublicKey(pubkey), txBytes.Bytes(), sigBytes)
}
/*
IsTxInput checks if the transaction ID supplied is an input of the current transaction
*/
func (t *Transaction) IsTxInput(txid string) bool {
for _, input := range t.Inputs {
if txid == input.Outpoint.Txid {
return true
}
}
return false
}
/*
IsCoinbaseTx checks if the transaction is a coinbase transaction
*/
func (t *Transaction) IsCoinbaseTx() bool {
return t.Height != nil
}