func (t *Transaction) ValidateSoftLimits() error {
	if t.Inputs == nil || len(t.Inputs) > MaxInputs {
		return errors.New("wrong input count")
	}
	if t.Outputs == nil || len(t.Outputs) > MaxOutputs {
		return errors.New("wrong output count")
	}
	if t.Signatures == nil || len(t.Signatures) > MaxSignatures {
		return errors.New("wrong signature count")
	}
	return nil
}
todo serialization and deserialization
todo maybe validation should be in a separate file
=== validation ===
ValidateSoftLimits checks that soft array size limits are not exceeded.
package blockchain
import (
	"bytes"
	"errors"
	"fmt"
	"github.com/BANKEX/plasma-research/src/node/types"
	"github.com/BANKEX/plasma-research/src/node/types/slice"
	"github.com/BANKEX/plasma-research/src/node/utils"
	"github.com/ethereum/go-ethereum/crypto"
)
const (
	MaxInputs     = 3
	MaxOutputs    = 3
	MaxSignatures = 2
)
type UnsignedTransaction struct {
	Inputs   []Input  `json:"inputs"`
	Outputs  []Output `json:"outputs"`
	Metadata Metadata `json:"metadata"`
}
type Transaction struct {
	UnsignedTransaction
	Signatures []types.Signature `json:"signatures"`
}
type Metadata struct {
	MaxBlockNumber uint32 `json:"maxBlockNumber"`
}
type Input struct {
	BlockIndex uint32 `json:"blockNumber"`
	TxIndex uint32 `json:"txNumber"`
	OutputIndex uint8 `json:"outputNumber"`
	Output
}
type Output struct {
	Owner types.Uint160 `json:"owner"`
	Slice slice.Slice   `json:"slice"`
}
func (i *Input) GetKey() string {
	return fmt.Sprintf("%d:%d:%d", i.BlockIndex, i.TxIndex, i.OutputIndex)
}
func (t *UnsignedTransaction) GetInputOwners() [][]byte {
	result := make([][]byte, 0, len(t.Inputs))
	for _, in := range t.Inputs {
		if !utils.Contains(result, in.Owner) {
			result = append(result, in.Owner)
		}
	}
	return result
}
func (t *UnsignedTransaction) GetMerkleRoot() types.Uint160 {
	var leaves []Item
	for _, data := range t.Inputs {
		rlpEncoded, _ := utils.EncodeToRLP(data)
		leaves = append(leaves, rlpEncoded)
	}
	for _, data := range t.Outputs {
		rlpEncoded, _ := utils.EncodeToRLP(data)
		leaves = append(leaves, rlpEncoded)
	}
	var rlpMetadata, _ = utils.EncodeToRLP(t.Metadata)
	leaves = append(leaves, rlpMetadata)
	tree := NewMerkleTree(leaves, 3, utils.Keccak160)
	return []byte(tree.GetRoot())
}
func PadHash(in types.Uint160) types.Uint256 {
	return append(make([]byte, 12), in...)
}
func (t *Transaction) GetSignaturesHash() types.Uint160 {
	result := make([]byte, 0, 65*len(t.Signatures))
	for _, s := range t.Signatures {
		result = append(result, s...)
	}
	return types.Uint160(utils.Keccak160(result))
}
func (t *Transaction) GetHash() types.Uint160 {
	var result []byte
	result = append(result, t.GetMerkleRoot()...)
	result = append(result, t.GetSignaturesHash()...)
	return types.Uint160(utils.Keccak160(result))
}
func (t *Transaction) Sign(key []byte) error {
	privateKey, err := crypto.ToECDSA(key)
	if err != nil {
		return err
	}
	signature, err := crypto.Sign(PadHash(t.GetMerkleRoot()), privateKey)
	if err != nil {
		return err
	}
	if len(signature) != 65 {
		return fmt.Errorf("wrong signature length %d, expected length: %d", len(signature), 65)
	}
	t.Signatures = append(t.Signatures, signature)
	return nil
}