package main import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "encoding/gob" "encoding/hex" "fmt" "log" "math/big" "strings" ) const subsidy = 10 type Transaction struct { ID []byte Vin []TXInput Vout []TXOutput } // check if the transaction is coinbase func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } // serialize `tx` func (tx Transaction) Serialize() []byte { var encoded bytes.Buffer enc := gob.NewEncoder(&encoded) err := enc.Encode(tx) logErr(err) return encoded.Bytes() } // serialize `tx` and hash it with SHA-256 algorithm. func (tx *Transaction) Hash() []byte { var hash [32]byte txCopy := *tx txCopy.ID = []byte{} hash = sha256.Sum256(txCopy.Serialize()) return hash[:] } // return a transaction which empties Sinature and PubKey filed in all TXInpus func (tx *Transaction) TrimmedCopy() Transaction { var inputs []TXInput var outputs []TXOutput for _, vin := range tx.Vin { inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil}) } for _, vout := range tx.Vout { outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash}) } txCopy := Transaction{tx.ID, inputs, outputs} return txCopy } // signs each input of `tx` func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { return } txCopy := tx.TrimmedCopy() for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] // previous transactions txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() //? SegWit: txid doesn't include signature txCopy.Vin[inID].PubKey = nil // what does this mean? // tutorial says "After getting the hash we should reset the PubKey // field, so it doesn’t affect further iterations." // Signing process // 1. Empty Signature and PubKey filed in all TXInputs // 2. Fill PubKey field in corresponding TXInput // 3. Hash whole transaction // 4. Sign the hash value r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) logErr(err) signature := append(r.Bytes(), s.Bytes()...) tx.Vin[inID].Signature = signature } } // check if Pubkey in `tx` TXInputs could verify // Signature in transaction TXOutputs from `prevTXs` // prevTXs structure: Transaction.ID->Transaction func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { if tx.IsCoinbase() { // coinbase transaction don't need verification return true } txCopy := tx.TrimmedCopy() curve := elliptic.P256() for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] // previous transactions txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil // signature (r, s) is a pair of numbers r := big.Int{} s := big.Int{} sigLen := len(vin.Signature) r.SetBytes(vin.Signature[:(sigLen / 2)]) s.SetBytes(vin.Signature[(sigLen / 2):]) // public key (x, y) is a pair of coordinates x := big.Int{} y := big.Int{} keyLen := len(vin.PubKey) x.SetBytes(vin.PubKey[:(keyLen / 2)]) y.SetBytes(vin.PubKey[(keyLen / 2):]) rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { return false } } return true } // create a new coinbase transaction func NewCoinbaseTX(to, data string) *Transaction { if data == "" { data = fmt.Sprintf("Reward to '%s'", to) } txin := TXInput{[]byte{}, -1, nil, []byte(data)} // coinbase have an empty TXInput txout := NewTXOutput(subsidy, to) tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} tx.ID = tx.Hash() return &tx } // create a general transaction func NewUTXOTransaction(from, to string, amount int, UTXOSet *UTXOSet) *Transaction { var inputs []TXInput var outputs []TXOutput wallets, err := NewWallets() // load wallets logErr(err) wallet := wallets.GetWallet(from) // 1. load wallet by address `from` pubKeyHash := HashPubKey(wallet.PublicKey) // 2. find UTXO that address `from` can spend acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount) if acc < amount { log.Print("ERROR: Not enough funds") return nil } // 3. construct TXInputs for txid, outs := range validOutputs { txID, err := hex.DecodeString(txid) // decode UTXO's txid into []byte logErr(err) for _, out := range outs { input := TXInput{txID, out, nil, wallet.PublicKey} inputs = append(inputs, input) } } // 4. construct TXOutputs outputs = append(outputs, *NewTXOutput(amount, to)) if acc > amount { // change(找零) outputs = append(outputs, *NewTXOutput(acc-amount, from)) } tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey) return &tx } // return a human-readable representation of a transaction func (tx Transaction) String() string { var lines []string if tx.IsCoinbase() { lines = append(lines, fmt.Sprintf("--- Coinbase %x:", tx.ID)) lines = append(lines, fmt.Sprintf(" Data: %s:", tx.Vin[0].PubKey)) } else { lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID)) for i, input := range tx.Vin { lines = append(lines, fmt.Sprintf(" Input %d:", i)) lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) } } for i, output := range tx.Vout { lines = append(lines, fmt.Sprintf(" Output %d:", i)) lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) } return strings.Join(lines, "\n") } // DeserializeTransaction deserializes a transaction func DeserializeTransaction(data []byte) Transaction { var transaction Transaction decoder := gob.NewDecoder(bytes.NewReader(data)) err := decoder.Decode(&transaction) if err != nil { log.Panic(err) } return transaction }