micw/common/tx.go

328 lines
11 KiB
Go
Raw Permalink Normal View History

2024-11-17 21:24:19 +00:00
package common
import (
"bytes"
"crypto/sha512"
"encoding/base64"
"encoding/binary"
"encoding/json"
"github.com/matchsystems/werr"
"golang.org/x/crypto/ed25519"
"io"
"time"
)
func GetTxHash(receiverPubKey []byte, message string, amount float64, blockId, timestamp, timestampNs int64) []byte {
hash := sha512.New()
_ = binary.Write(hash, binary.BigEndian, blockId)
_ = binary.Write(hash, binary.BigEndian, timestamp)
_ = binary.Write(hash, binary.BigEndian, timestampNs)
hash.Write(float64ToBytes(amount))
hash.Write([]byte(message))
hash.Write(receiverPubKey)
return hash.Sum(nil)
}
func GetBlockId(t time.Time) int64 {
return (t.UTC().Unix() / 100) * 100
}
type NewTxOptsJson struct {
Hash string `json:"hash"` // use function GetTxHash
// (CreatedAt / 100) * 100
BlockId int64 `json:"block_id"`
SenderPublicKey string `json:"sender_public_key"`
ReceiverPublicKey string `json:"receiver_public_key"`
IsReward bool `json:"is_reward"`
Amount float64 `json:"amount"`
Message string `json:"message"`
Signature string `json:"signature"`
CreatedAt int64 `json:"created_at"`
CreatedAtNs int64 `json:"created_at_ns"`
ByteEncoding string `json:"byte_encoding"`
}
// NewTxOpts sent from client to server to add a new transaction
type NewTxOpts struct {
Hash []byte
BlockId int64
SenderPublicKey []byte
ReceiverPublicKey []byte
IsReward bool
Amount float64
Message string
// Signed TX Hash with senders private key
Signature []byte
CreatedAt int64
CreatedAtNs int64 // Nanoseconds
}
type TxJson struct {
Hash string `json:"hash"` // use function GetTxHash
// (CreatedAt / 100) * 100
BlockId int64 `json:"block_id"`
SenderPublicKey string `json:"sender_public_key"`
ReceiverPublicKey string `json:"receiver_public_key"`
IsReward bool `json:"is_reward"`
Amount float64 `json:"amount"`
AmountBurned float64 `json:"amount_burned"`
Message string `json:"message"`
Signature string `json:"signature"`
CreatedAt int64 `json:"created_at"`
CreatedAtNs int64 `json:"created_at_ns"`
AddedAt time.Time `json:"added_at"`
IsAdded bool `json:"is_added"`
ByteEncoding string `json:"byte_encoding"`
}
// Tx is a transaction structure
// that is sent from server to the client
// with the information about requested transaction
type Tx struct {
Hash []byte
BlockId int64
SenderPublicKey []byte
ReceiverPublicKey []byte
IsReward bool
Amount float64
AmountBurned float64
Message string
// Signed TX Hash with senders private key
Signature []byte
CreatedAt int64
CreatedAtNs int64 // Nanoseconds
AddedAt time.Time
IsAdded bool
}
func MakeNewTxOpts(senderPubKey, senderPrivateKey, receiverPubKey []byte,
amount float64, msg string, isReward bool) *NewTxOpts {
now := time.Now().UTC()
createdAt := now.Unix()
createdAtNs := now.UnixNano()
blockId := (createdAt / 100) * 100
txHash := GetTxHash(receiverPubKey, msg, amount, blockId, createdAt, createdAtNs)
sig := ed25519.Sign(senderPrivateKey, txHash)
return &NewTxOpts{
Hash: txHash,
BlockId: blockId,
SenderPublicKey: senderPubKey,
ReceiverPublicKey: receiverPubKey,
IsReward: isReward,
Amount: amount,
Message: msg,
Signature: sig,
CreatedAt: createdAt,
CreatedAtNs: createdAtNs,
}
}
func (opts *NewTxOpts) ValidateBlockId() (valid bool) {
return opts.BlockId == (opts.CreatedAt/100)*100
}
func (opts *NewTxOpts) ValidateHash() (valid bool) {
return bytes.Equal(opts.Hash, GetTxHash(
opts.ReceiverPublicKey, opts.Message, opts.Amount, opts.BlockId, opts.CreatedAt, opts.CreatedAtNs,
))
}
func (opts *NewTxOpts) ValidateSignature() (valid bool) {
return ed25519.Verify(opts.SenderPublicKey, opts.Hash, opts.Signature)
}
func (opts *Tx) ValidateBlockId() (valid bool) {
return opts.BlockId == (opts.CreatedAt/100)*100
}
func (opts *Tx) ValidateHash() (valid bool) {
return bytes.Equal(opts.Hash, GetTxHash(
opts.ReceiverPublicKey, opts.Message, opts.Amount, opts.BlockId, opts.CreatedAt, opts.CreatedAtNs,
))
}
func (opts *Tx) ValidateSignature() (valid bool) {
return ed25519.Verify(opts.SenderPublicKey, opts.Hash, opts.Signature)
}
// HashString uses base64.RawURLEncoding encoding
func (opts *NewTxOpts) HashString() string {
return base64.RawURLEncoding.EncodeToString(opts.Hash)
}
// SignatureString uses base64.RawURLEncoding encoding
func (opts *NewTxOpts) SignatureString() string {
return base64.RawURLEncoding.EncodeToString(opts.Signature)
}
// HashString uses base64.RawURLEncoding encoding
func (opts *Tx) HashString() string {
return base64.RawURLEncoding.EncodeToString(opts.Hash)
}
// SignatureString uses base64.RawURLEncoding encoding
func (opts *Tx) SignatureString() string {
return base64.RawURLEncoding.EncodeToString(opts.Signature)
}
func (opts *NewTxOpts) ToJSON(byteEncodingStr string) ([]byte, error) {
encoding, err := GetEncoding(byteEncodingStr)
if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
}
data := &NewTxOptsJson{
Hash: encoding.EncodeToString(opts.Hash),
BlockId: opts.BlockId,
SenderPublicKey: encoding.EncodeToString(opts.SenderPublicKey),
ReceiverPublicKey: encoding.EncodeToString(opts.ReceiverPublicKey),
IsReward: opts.IsReward,
Amount: opts.Amount,
Message: opts.Message,
Signature: encoding.EncodeToString(opts.Signature),
CreatedAt: opts.CreatedAt,
CreatedAtNs: opts.CreatedAtNs,
ByteEncoding: byteEncodingStr,
}
jsonBytes, err := json.Marshal(data)
if err != nil {
return nil, werr.Wrapf(err, "error while trying to marshal to json")
}
return jsonBytes, nil
}
func (opts *Tx) ToJSON(byteEncodingStr string) ([]byte, error) {
encoding, err := GetEncoding(byteEncodingStr)
if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
}
data := &TxJson{
Hash: encoding.EncodeToString(opts.Hash),
BlockId: opts.BlockId,
SenderPublicKey: encoding.EncodeToString(opts.SenderPublicKey),
ReceiverPublicKey: encoding.EncodeToString(opts.ReceiverPublicKey),
IsReward: opts.IsReward,
Amount: opts.Amount,
AmountBurned: opts.AmountBurned,
Message: opts.Message,
Signature: encoding.EncodeToString(opts.Signature),
CreatedAt: opts.CreatedAt,
CreatedAtNs: opts.CreatedAtNs,
AddedAt: opts.AddedAt,
IsAdded: opts.IsAdded,
ByteEncoding: byteEncodingStr,
}
jsonBytes, err := json.Marshal(data)
if err != nil {
return nil, werr.Wrapf(err, "error while trying to marshal to json")
}
return jsonBytes, nil
}
func (opts *NewTxOptsJson) Process() (*NewTxOpts, error) {
out := &NewTxOpts{
BlockId: opts.BlockId,
IsReward: opts.IsReward,
Amount: opts.Amount,
Message: opts.Message,
CreatedAt: opts.CreatedAt,
CreatedAtNs: opts.CreatedAtNs,
}
encoding, err := GetEncoding(opts.ByteEncoding)
if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
}
if out.Hash, err = encoding.DecodeString(opts.Hash); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode hash")
}
if out.SenderPublicKey, err = encoding.DecodeString(opts.SenderPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode sender public key")
}
if out.ReceiverPublicKey, err = encoding.DecodeString(opts.ReceiverPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode receiver public key")
}
if out.Signature, err = encoding.DecodeString(opts.Signature); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode signature")
}
return out, nil
}
func (opts *NewTxOpts) Process(isAdded bool, addedAt time.Time) (*Tx, error) {
out := &Tx{
Hash: opts.Hash,
BlockId: opts.BlockId,
SenderPublicKey: opts.SenderPublicKey,
ReceiverPublicKey: opts.ReceiverPublicKey,
IsReward: opts.IsReward,
Amount: opts.Amount,
AmountBurned: CalcBurning(opts.Amount),
Message: opts.Message,
Signature: opts.Signature,
CreatedAt: opts.CreatedAt,
CreatedAtNs: opts.CreatedAtNs,
AddedAt: addedAt,
IsAdded: isAdded,
}
return out, nil
}
func (tx *TxJson) Process() (*Tx, error) {
out := &Tx{
BlockId: tx.BlockId,
IsReward: tx.IsReward,
Amount: tx.Amount,
AmountBurned: tx.AmountBurned,
Message: tx.Message,
CreatedAt: tx.CreatedAt,
CreatedAtNs: tx.CreatedAtNs,
AddedAt: tx.AddedAt,
IsAdded: tx.IsAdded,
}
encoding, err := GetEncoding(tx.ByteEncoding)
if err != nil {
return nil, werr.Wrapf(err, "error while trying to get byte->string encoding")
}
if out.Hash, err = encoding.DecodeString(tx.Hash); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode hash")
}
if out.SenderPublicKey, err = encoding.DecodeString(tx.SenderPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode sender public key")
}
if out.ReceiverPublicKey, err = encoding.DecodeString(tx.ReceiverPublicKey); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode receiver public key")
}
if out.Signature, err = encoding.DecodeString(tx.Signature); err != nil {
return nil, werr.Wrapf(err, "error while trying to decode signature")
}
return out, nil
}
func NewTxOptsFromJSON(marshaledData []byte) (opts *NewTxOpts, err error) {
data := &NewTxOptsJson{}
if err := json.Unmarshal(marshaledData, data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
}
return data.Process()
}
func NewTxOptsFromJsonReader(r io.Reader) (opts *NewTxOpts, err error) {
data := &NewTxOptsJson{}
if err := json.NewDecoder(r).Decode(&data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
}
return data.Process()
}
func TxFromJSON(marshaledData []byte) (opts *Tx, err error) {
data := &TxJson{}
if err := json.Unmarshal(marshaledData, data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
}
return data.Process()
}
func TxFromJsonReader(r io.Reader) (opts *Tx, err error) {
data := &TxJson{}
if err := json.NewDecoder(r).Decode(&data); err != nil {
return nil, werr.Wrapf(err, "error while trying to unmarshal new transaction options")
}
return data.Process()
}