Files

124 lines
2.3 KiB
Go

package chain
import (
"crypto/ed25519"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"strings"
)
// CoinSpend references a coin being spent, with proof of ownership.
type CoinSpend struct {
CoinID CoinID
Owner ed25519.PublicKey
Signature []byte
}
type Transaction struct {
Spends []CoinSpend
Coins []Coin
cachedID string
}
func (tx *Transaction) IsCoinbase() bool {
return len(tx.Spends) == 0
}
func (tx *Transaction) TxID() string {
if tx.cachedID != "" {
return tx.cachedID
}
var b strings.Builder
for _, s := range tx.Spends {
b.WriteString(s.CoinID.TxHash)
b.WriteString(strconv.Itoa(s.CoinID.Index))
}
for _, c := range tx.Coins {
b.Write(c.Owner)
b.WriteString(strconv.Itoa(c.Amount))
}
h := sha256.Sum256([]byte(b.String()))
tx.cachedID = hex.EncodeToString(h[:])
return tx.cachedID
}
func (tx *Transaction) Verify() bool {
if tx.IsCoinbase() {
return true
}
hash := []byte(tx.TxID())
for _, s := range tx.Spends {
if s.Owner == nil {
return false
}
if !ed25519.Verify(s.Owner, hash, s.Signature) {
return false
}
}
return true
}
func (tx *Transaction) String() string {
if tx.IsCoinbase() {
return fmt.Sprintf("COINBASE -> %s: %d", tx.Coins[0].OwnerHex()[:7], tx.Coins[0].Amount)
}
from := hex.EncodeToString(tx.Spends[0].Owner)[:7]
to := tx.Coins[0].OwnerHex()[:7]
return fmt.Sprintf("%s -> %s: %d", from, to, tx.Coins[0].Amount)
}
func NewCoinbaseTransaction(to ed25519.PublicKey, amount int) *Transaction {
return &Transaction{
Coins: []Coin{{Amount: amount, Owner: to}},
}
}
func NewTransaction(wallet *Wallet, to ed25519.PublicKey, amount int, unspent []UnspentCoin) *Transaction {
var selected []UnspentCoin
var total int
for _, u := range unspent {
selected = append(selected, u)
total += u.Amount
if total >= amount {
break
}
}
if total < amount {
return nil
}
coins := []Coin{{Amount: amount, Owner: to}}
change := total - amount
if change > 0 {
coins = append(coins, Coin{Amount: change, Owner: wallet.PublicKey})
}
spends := make([]CoinSpend, len(selected))
for i, u := range selected {
spends[i] = CoinSpend{
CoinID: u.CoinID,
Owner: wallet.PublicKey,
}
}
tx := &Transaction{Spends: spends, Coins: coins}
hash := []byte(tx.TxID())
for i := range tx.Spends {
tx.Spends[i].Signature = ed25519.Sign(wallet.PrivateKey, hash)
}
return tx
}