328 lines
11 KiB
Go
328 lines
11 KiB
Go
|
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()
|
||
|
}
|