252 lines
5.5 KiB
Go
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
|
|
}
|