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 }