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) } 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 / BlockSecsDiff) * BlockSecsDiff 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/BlockSecsDiff)*BlockSecsDiff } 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 *NewTxOpts) Validate() (err error) { switch { case !opts.ValidateBlockId(): return werr.Wrapf(ErrInvalidArgument, "invalid block id") case !opts.ValidateHash(): return werr.Wrapf(ErrInvalidArgument, "invalid hash") case !opts.ValidateSignature(): return werr.Wrapf(ErrInvalidArgument, "invalid signature") } return nil } func (tx *Tx) ValidateBlockId() (valid bool) { return tx.BlockId == (tx.CreatedAt/BlockSecsDiff)*BlockSecsDiff } func (tx *Tx) ValidateHash() (valid bool) { return bytes.Equal(tx.Hash, GetTxHash( tx.ReceiverPublicKey, tx.Message, tx.Amount, tx.BlockId, tx.CreatedAt, tx.CreatedAtNs, )) } func (tx *Tx) ValidateSignature() (valid bool) { return ed25519.Verify(tx.SenderPublicKey, tx.Hash, tx.Signature) } func (tx *Tx) Validate() (err error) { if tx.ValidateBlockId() { return werr.Wrapf(ErrInvalidArgument, "invalid block id") } else if tx.ValidateHash() { return werr.Wrapf(ErrInvalidArgument, "invalid hash") } else if tx.ValidateSignature() { return werr.Wrapf(ErrInvalidArgument, "invalid signature") } else if ValidateBurningBool(tx) { return werr.Wrapf(ErrInvalidArgument, "invalid burning") } return nil } // 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 (tx *Tx) HashString() string { return base64.RawURLEncoding.EncodeToString(tx.Hash) } // SignatureString uses base64.RawURLEncoding encoding func (tx *Tx) SignatureString() string { return base64.RawURLEncoding.EncodeToString(tx.Signature) } func (opts *NewTxOpts) ToJSON(byteEncodingStr string) ([]byte, error) { encoding, err := GetEncoding(byteEncodingStr) if err != nil { return nil, werr.Wrapf(err, "error while getting 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 marshaling") } return jsonBytes, nil } func (tx *Tx) ToJSON(byteEncodingStr string) ([]byte, error) { encoding, err := GetEncoding(byteEncodingStr) if err != nil { return nil, werr.Wrapf(err, "error while getting byte->string encoding") } data := &TxJson{ Hash: encoding.EncodeToString(tx.Hash), BlockId: tx.BlockId, SenderPublicKey: encoding.EncodeToString(tx.SenderPublicKey), ReceiverPublicKey: encoding.EncodeToString(tx.ReceiverPublicKey), IsReward: tx.IsReward, Amount: tx.Amount, AmountBurned: tx.AmountBurned, Message: tx.Message, Signature: encoding.EncodeToString(tx.Signature), CreatedAt: tx.CreatedAt, CreatedAtNs: tx.CreatedAtNs, AddedAt: tx.AddedAt, IsAdded: tx.IsAdded, ByteEncoding: byteEncodingStr, } jsonBytes, err := json.Marshal(data) if err != nil { return nil, werr.Wrapf(err, "error while marshaling") } 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 getting byte->string encoding") } if out.Hash, err = encoding.DecodeString(opts.Hash); err != nil { return nil, werr.Wrapf(err, "error while decoding hash") } if out.SenderPublicKey, err = encoding.DecodeString(opts.SenderPublicKey); err != nil { return nil, werr.Wrapf(err, "error while decoding sender public key") } if out.ReceiverPublicKey, err = encoding.DecodeString(opts.ReceiverPublicKey); err != nil { return nil, werr.Wrapf(err, "error while decoding receiver public key") } if out.Signature, err = encoding.DecodeString(opts.Signature); err != nil { return nil, werr.Wrapf(err, "error while decoding 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, err } if out.Hash, err = encoding.DecodeString(tx.Hash); err != nil { return nil, werr.Wrapf(err, "error while decoding hash") } if out.SenderPublicKey, err = encoding.DecodeString(tx.SenderPublicKey); err != nil { return nil, werr.Wrapf(err, "error while decoding sender public key") } if out.ReceiverPublicKey, err = encoding.DecodeString(tx.ReceiverPublicKey); err != nil { return nil, werr.Wrapf(err, "error while decoding receiver public key") } if out.Signature, err = encoding.DecodeString(tx.Signature); err != nil { return nil, werr.Wrapf(err, "error while decoding 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 unmarshaling") } 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 unmarshaling") } 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 unmarshaling") } 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 unmarshaling") } return data.Process() }