6. Adding Digital Signatures

지갑에 디지털 서명을 추가해보자.

TxInput에 wallet의 private key를 이용해서 sign을 하고 검증하는 과정을 추가.

Sign

  • wallet의 privateKey와 해당 TxInput이 참조하는 TxOutput의 public key hash, random number를 섞어서 ecdsa를 이용해 만든다.

  • esdsa 함수를 이용하면 r, s 2개의 수가 나오는데 이 두 수를 바이트로 변환해서 합하면 signature가 된다.

  • 이 과정을 반대로 하여 r, s를 뽑아내어 비교하는 것이 verify 함수다.

wallet.go

  • address 검증 함수 추가.

package wallet

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"log"

	"golang.org/x/crypto/ripemd160"
)

const (
	// checksum length in byte.
	// 4byte length
	checksumLength = 4
	// 0 byte를 16진법으로 표현.
	version = byte(0x00)
)

// Wallet : PrivateKey와 PublicKey를 가지고 있는 Wallet structure.
type Wallet struct {
	PrivateKey ecdsa.PrivateKey
	PublicKey  []byte
}

// Address : version, publicKeyHash, checksum 3가지를 concatenate한 후에 Base58로 인코딩해서 address를 만든다.
func (w Wallet) Address() []byte {
	pubHash := PublicKeyHash(w.PublicKey)

	versionedHash := append([]byte{version}, pubHash...)
	checksum := Checksum(versionedHash)

	fullHash := append(versionedHash, checksum...)
	address := Base58Encode(fullHash)

	fmt.Printf("pub key: %x\n", w.PublicKey)
	fmt.Printf("pub hash: %x\n", pubHash)
	fmt.Printf("address: %s\n", address)

	fmt.Printf("fullHash: %x\n", fullHash)
	fmt.Printf("checksum: %x\n", checksum)
	fmt.Printf("version: %x\n", versionedHash)

	return address
}

// Address: 1EwsppsVck2B5Ndf7nPyHi8uYtEhK44ndm
// FullHash: 0098fa85f9e6b04827b2ce6db7e8e18ff8e9882c99eefb8610
// [Version] 00
// [Pub Key Hash] 98fa85f9e6b04827b2ce6db7e8e18ff8e9882c99
// [CheckSum] eefb8610
// FullHash = Version + Pub Key Hash + CheckSum

// ValidateAddress : Address의 유효성을 검증한다.
// 1 : address의 checksum 부분 (address를 base58로 디코드하여 나온 배열에서 마지막 checksumLength만큼의 부분)
// 2 : address의 version부분과 pubKeyHash 부분을 뽑아서 만든 checksum 부분.
// 1과 2가 같은지 확인.
func ValidateAddress(address string) bool {
	// 1 : address의 checksum 부분
	pubKeyHash := Base58Decode([]byte(address))
	actualChecksum := pubKeyHash[len(pubKeyHash)-checksumLength:]

	// 2 : address의 version부분과 pubKeyHash 부분을 뽑아서 만든 checksum 부분.
	version := pubKeyHash[0]
	pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-checksumLength]
	targetChecksum := Checksum(append([]byte{version}, pubKeyHash...))

	return bytes.Compare(actualChecksum, targetChecksum) == 0
}

// NewKeyPair : 새로운 키페어를 만든다.
// ecdsa라는 비대칭키 암호 알고리즘을 사용.
func NewKeyPair() (ecdsa.PrivateKey, []byte) {
	curve := elliptic.P256()

	// private key는 랜덤하게 뽑혀진 256bit의 숫자이다.
	private, err := ecdsa.GenerateKey(curve, rand.Reader)
	if err != nil {
		log.Panic(err)
	}

	// X, Y가 concatenate 되어 pub이 됨.
	// private key로부터 public key를 생성함.
	pub := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
	return *private, pub
}

// MakeWallet : NewKeyPair를 이용해서 Wallet을 만든다.
func MakeWallet() *Wallet {
	private, public := NewKeyPair()
	wallet := Wallet{private, public}

	return &wallet
}

// PublicKeyHash : sha256, ripemd160을 이용해 퍼블릭키를 해쉬로 변환.
// address 생성에 쓰임.
func PublicKeyHash(pubKey []byte) []byte {
	pubHash := sha256.Sum256(pubKey)

	hasher := ripemd160.New()
	_, err := hasher.Write(pubHash[:])
	if err != nil {
		log.Panic(err)
	}

	publicRipMD := hasher.Sum(nil)

	return publicRipMD
}

// Checksum : payload를 sha256을 이용해 해쉬로 변환하고 checksumLength만큼의 바이트만 사용한다.
func Checksum(payload []byte) []byte {
	firstHash := sha256.Sum256(payload)
	secondHash := sha256.Sum256(firstHash[:])

	return secondHash[:checksumLength]
}

transaction.go

  • sign과 verify 함수 구현.

  • 새로운 tx 생성시 sign과정 추가.

package blockchain

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"log"
	"math/big"
	"strings"

	"github.com/HTaeha/Blockchain-in-Golang/wallet"
)

// Transaction : 블록에 쓰여질 데이터.
type Transaction struct {
	// hash
	ID []byte
	// Array of input
	Inputs []TxInput
	// Array of output
	Outputs []TxOutput
}

// Serialize : Bytes로 변환.
func (tx Transaction) Serialize() []byte {
	var encoded bytes.Buffer

	enc := gob.NewEncoder(&encoded)
	err := enc.Encode(tx)
	if err != nil {
		log.Panic(err)
	}

	return encoded.Bytes()
}

// Hash : Transaction의 ID를 비우고 hashing한 값을 리턴한다.
func (tx *Transaction) Hash() []byte {
	var hash [32]byte

	txCopy := *tx
	txCopy.ID = []byte{}

	hash = sha256.Sum256(txCopy.Serialize())

	return hash[:]
}

// CoinbaseTx : 하나의 인풋과 하나의 아웃풋이 있음. 채굴자가 아웃풋을 받는다.
// to : data 받을 사람의 address
func CoinbaseTx(to, data string) *Transaction {
	if data == "" {
		data = fmt.Sprintf("Coins to %s", to)
	}

	// 참조하는 TxOutput이 없다.
	txin := TxInput{[]byte{}, -1, nil, []byte(data)}
	// 100 coin을 to에게 보낸다.
	txout := NewTXOutput(100, to)

	// Transaction Init
	tx := Transaction{nil, []TxInput{txin}, []TxOutput{*txout}}

	return &tx
}

// NewTransaction : from account, to account
// amount : 보내고 싶은 코인의 양
// from이 to에게 amount만큼의 코인을 보낸다.
func NewTransaction(from, to string, amount int, chain *BlockChain) *Transaction {
	var inputs []TxInput
	var outputs []TxOutput

	// from의 wallet 생성.
	wallets, err := wallet.CreateWallets()
	Handle(err)
	w := wallets.GetWallet(from)
	pubKeyHash := wallet.PublicKeyHash(w.PublicKey)

	acc, validOutputs := chain.FindSpendableOutputs(pubKeyHash, amount)

	// 보내고 싶은 만큼의 코인이 없다.
	if acc < amount {
		// 시간과 에러 문자열을 출력한 뒤 패닉을 발생시킴.
		// 패닉 : 프로그램을 종료시킨다. (런타임 에러)
		// recover 함수를 사용하면 panic 후에 복구할 수 있다. (프로그램이 종료되지 않음.)
		log.Panic("Error: not enough funds")
	}

	// 사용할 수 있는 아웃풋의 인덱스들.
	for txid, outs := range validOutputs {
		txID, err := hex.DecodeString(txid)
		Handle(err)

		for _, out := range outs {
			input := TxInput{txID, out, nil, w.PublicKey}
			inputs = append(inputs, input)
		}
	}

	// to address로 amount만큼의 코인을 보낸다.
	outputs = append(outputs, *NewTXOutput(amount, to))

	// 모은 UTXO의 양이 보낼 양보다 크면 거스름돈을 받는다.
	if acc > amount {
		outputs = append(outputs, *NewTXOutput(acc-amount, from))
	}

	tx := Transaction{nil, inputs, outputs}
	tx.ID = tx.Hash()
	chain.SignTransaction(&tx, w.PrivateKey)

	return &tx
}

// IsCoinbase : Coinbase 인지 판별한다.
func (tx *Transaction) IsCoinbase() bool {
	return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].Out == -1
}

// Sign : Tx의 Input들에게 privateKey를 이용하여 Sign을 한다.
// prevTXs : 현재 Tx의 인풋들이 사용한 이전 Tx.
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
	// coinbase는 sign이 필요없음.
	if tx.IsCoinbase() {
		return
	}

	for _, in := range tx.Inputs {
		// input의 ID가 nil인지 체크.
		// 이전 Tx가 유효하지 않다.
		if prevTXs[hex.EncodeToString(in.ID)].ID == nil {
			log.Panic("ERROR: Previous transaction is not correct")
		}
	}

	txCopy := tx.TrimmedCopy()

	for inID, in := range txCopy.Inputs {
		prevTX := prevTXs[hex.EncodeToString(in.ID)]
		txCopy.Inputs[inID].Signature = nil
		txCopy.Inputs[inID].PubKey = prevTX.Outputs[in.Out].PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Inputs[inID].PubKey = nil

		// random number generator, privKey, txCopy.ID(txCopy.Hash)를 ecdsa.Sign함수에 넣으면 2개의 수가 나온다.
		// 두 수를 바이트로 변환해서 signature를 만든다.
		r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID)
		Handle(err)
		signature := append(r.Bytes(), s.Bytes()...)

		tx.Inputs[inID].Signature = signature
	}
}

// TrimmedCopy method
// Trim : 잘라 내다, 다듬다
// TxInput의 Signature과 PubKey 부분을 nil로 하여 새로운 Transaction을 만들어 리턴한다.
func (tx *Transaction) TrimmedCopy() Transaction {
	var inputs []TxInput
	var outputs []TxOutput

	for _, in := range tx.Inputs {
		inputs = append(inputs, TxInput{in.ID, in.Out, nil, nil})
	}

	for _, out := range tx.Outputs {
		outputs = append(outputs, TxOutput{out.Value, out.PubKeyHash})
	}

	txCopy := Transaction{tx.ID, inputs, outputs}

	return txCopy
}

// Verify : Sign이 유효한지 검증한다.
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
	if tx.IsCoinbase() {
		return true
	}

	// input을 돌며 존재하는지 체크.
	for _, in := range tx.Inputs {
		if prevTXs[hex.EncodeToString(in.ID)].ID == nil {
			log.Panic("Previous transaction does not exist")
		}
	}

	txCopy := tx.TrimmedCopy()
	curve := elliptic.P256()

	for inID, in := range txCopy.Inputs {
		prevTX := prevTXs[hex.EncodeToString(in.ID)]
		txCopy.Inputs[inID].Signature = nil
		txCopy.Inputs[inID].PubKey = prevTX.Outputs[in.Out].PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Inputs[inID].PubKey = nil

		r := big.Int{}
		s := big.Int{}
		sigLen := len(in.Signature)
		// Signature를 반으로 나눠서 r, s에 할당.
		r.SetBytes(in.Signature[:(sigLen / 2)])
		s.SetBytes(in.Signature[(sigLen / 2):])

		x := big.Int{}
		y := big.Int{}
		keyLen := len(in.PubKey)
		x.SetBytes(in.PubKey[:(keyLen / 2)])
		y.SetBytes(in.PubKey[(keyLen / 2):])

		// 잘못된 Sign이 하나라도 있으면 false
		rawPubKey := ecdsa.PublicKey{Curve: curve, X: &x, Y: &y}
		if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false {
			return false
		}
	}

	return true
}

// String : Trnasaction에 대한 정보 출력.
func (tx Transaction) String() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID))
	for i, input := range tx.Inputs {
		lines = append(lines, fmt.Sprintf("     Input %d:", i))
		lines = append(lines, fmt.Sprintf("       TXID:     %x", input.ID))
		lines = append(lines, fmt.Sprintf("       Out:       %d", input.Out))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
		lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PubKey))
	}

	for i, output := range tx.Outputs {
		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")
}

tx.go

package blockchain

import (
	"bytes"

	"github.com/HTaeha/Blockchain-in-Golang/wallet"
)

// TxOutput : Transaction Output
// Indivisible - 나눌 수 없는, 불가분의
// Output은 쪼갤 수 없다.
// ex) 500원짜리 물건을 사는데 1000원을 내면 1000원을 반으로 쪼개는 것이 아니라 500원(새로운 아웃풋)을 돌려준다.
// Value만큼의 값을 PubKey가 받는다.
type TxOutput struct {
	// locked value in token
	Value int
	// 공개키 : token을 언락하기 위해 필요하다. (value의 안쪽을 보기 위해)
	// Bitcoin에서는 Pubkey가 복잡한 스크립트 언어로 되어 있다.
	// address의 public key hash 부분.
	PubKeyHash []byte
}

// TxInput : Transaction Input
// ID에 해당하는 Transaction의 아웃풋에서 Out값 위치에 있는 UTXO를 Sig가 보낸다.
type TxInput struct {
	// Input의 ID
	// 사용할 Transaction의 ID.
	ID []byte
	// Output 의 인덱스.
	// 해당 transaction에서 몇 번째 위치한 output과 연결되어 있는지 알려줌.
	Out int
	// Signature : TxOutput의 PubKey와 비슷한 역할.
	// User's account, address
	Signature []byte
	// 소유자 지갑의 public key.
	PubKey []byte
}

// NewTXOutput : 새로운 TXO를 생성해서 리턴.
func NewTXOutput(value int, address string) *TxOutput {
	txo := &TxOutput{value, nil}
	txo.Lock([]byte(address))

	return txo
}

// UsesKey : TxInput의 public key hash와 pubKeyHash인자가 같은지 판별.
func (in *TxInput) UsesKey(pubKeyHash []byte) bool {
	lockingHash := wallet.PublicKeyHash(in.PubKey)

	return bytes.Compare(lockingHash, pubKeyHash) == 0
}

// Lock : Output의 PubKeyHash를 할당.
func (out *TxOutput) Lock(address []byte) {
	pubKeyHash := wallet.Base58Decode(address)
	// 4 : version byte
	pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
	out.PubKeyHash = pubKeyHash
}

// IsLockedWithKey : TxOutput의 public key hash와 pubKeyHash가 같은지 판별.
func (out *TxOutput) IsLockedWithKey(pubKeyHash []byte) bool {
	return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}

blockchain.go

  • 기존의 address로 TxInput, TxOutput을 검증하던 것을 pubKeyHash로 검증한다.

package blockchain

import (
	"bytes"
	"crypto/ecdsa"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"runtime"

	"github.com/dgraph-io/badger/v3"
)

const (
	// database Path.
	dbPath = "./tmp/blocks"
	// DB가 존재하는지 체크하기 위한 MANIFEST file.
	dbFile      = "./tmp/blocks/MANIFEST"
	genesisData = "First Transaction from Genesis"
)

// BlockChain structure
// 마지막 해쉬를 저장하고 DB 포인터를 저장해서 블록을 관리.
type BlockChain struct {
	LastHash []byte
	Database *badger.DB
}

// BlockChainIterator : DB에 저장된 블록체인을 순회하기 위해 생성.
type BlockChainIterator struct {
	// 현재 가리키고 있는 hash
	CurrentHash []byte
	Database    *badger.DB
}

// DBexists : DB가 존재하는지 체크.
func DBexists() bool {
	if _, err := os.Stat(dbFile); os.IsNotExist(err) {
		return false
	}
	return true
}

// InitBlockChain : Genesis 블록을 시작으로 하는 블록체인을 생성한다.
func InitBlockChain(address string) *BlockChain {
	var lastHash []byte

	// DB가 이미 존재하면 종료.
	if DBexists() {
		fmt.Println("Blockchain already exists")
		runtime.Goexit()
	}

	opts := badger.DefaultOptions(dbPath)
	// store key and metadata
	opts.Dir = dbPath
	// store all values
	opts.ValueDir = dbPath

	db, err := badger.Open(opts)
	Handle(err)

	// Update : read and write transactions on our databases.
	// Txn : Transaction, which can be read-only or read-write.
	err = db.Update(func(txn *badger.Txn) error {
		// 첫 블록을 Coinbase로 한다.
		cbtx := CoinbaseTx(address, genesisData)
		genesis := Genesis(cbtx)
		fmt.Println("Genesis created")
		err = txn.Set(genesis.Hash, genesis.Serialize())
		Handle(err)
		err = txn.Set([]byte("lh"), genesis.Hash)

		lastHash = genesis.Hash

		return err
	})

	Handle(err)

	blockchain := BlockChain{lastHash, db}
	return &blockchain
}

// ContinueBlockChain : Blockchain이 이미 존재하고 있을 경우 그 블록체인을 리턴.
// address를 인자로 받고 있지만 이 함수 어디에서도 쓰이지 않는다.
// 현재는 블록체인이 하나만 생성되도록 되어 있어서 address에 아무 값이나 넣어도 상관이 없다.
// DB에 하나의 블록체인만 존재.
func ContinueBlockChain(address string) *BlockChain {
	// DB가 존재하지 않으면 종료.
	if DBexists() == false {
		fmt.Println("No existing blockchain found, create one!")
		runtime.Goexit()
	}

	var lastHash []byte

	opts := badger.DefaultOptions(dbPath)
	// store key and metadata
	opts.Dir = dbPath
	// store all values
	opts.ValueDir = dbPath

	db, err := badger.Open(opts)
	Handle(err)

	// Update : read and write transactions on our databases.
	// Txn : Transaction, which can be read-only or read-write.
	err = db.Update(func(txn *badger.Txn) error {
		// 마지막 해쉬를 찾는다.
		item, err := txn.Get([]byte("lh"))
		Handle(err)
		lastHash, err = item.ValueCopy(nil)

		return err
	})
	Handle(err)

	blockchain := BlockChain{lastHash, db}
	return &blockchain
}

// AddBlock : transactions를 가지고 있는 블록을 추가한다.
func (chain *BlockChain) AddBlock(transactions []*Transaction) {
	var lastHash []byte

	// View : read-only type of transaction
	err := chain.Database.View(func(txn *badger.Txn) error {
		// 마지막 해쉬를 찾는다.
		item, err := txn.Get([]byte("lh"))
		Handle(err)
		lastHash, err = item.ValueCopy(nil)

		return err
	})
	Handle(err)

	// 이전 해쉬값과 데이터로 새로운 블록을 생성.
	newBlock := CreateBlock(transactions, lastHash)

	err = chain.Database.Update(func(txn *badger.Txn) error {
		// 새로운 블록의 해쉬값을 저장한다.
		err := txn.Set(newBlock.Hash, newBlock.Serialize())
		Handle(err)
		// lh 키값을 새로운 블록의 해쉬값으로 바꾼다.
		err = txn.Set([]byte("lh"), newBlock.Hash)

		// BlockChain의 마지막 해쉬를 새로운 블록의 해쉬값으로 지정한다.
		chain.LastHash = newBlock.Hash

		return err
	})
	Handle(err)
}

// Iterator : 블록체인 이터레이터. LastHash부터 이전 해쉬로 가며 순회할 수 있다.
func (chain *BlockChain) Iterator() *BlockChainIterator {
	iter := &BlockChainIterator{chain.LastHash, chain.Database}

	return iter
}

// Next : BlockChain의 다음 블록을 반환한다.
func (iter *BlockChainIterator) Next() *Block {
	var block *Block

	err := iter.Database.View(func(txn *badger.Txn) error {
		// 현재 가리키고 있는 hash를 deserialize해서 Block을 복원한다.
		item, err := txn.Get(iter.CurrentHash)
		Handle(err)
		encodedBlock, err := item.ValueCopy(nil)
		block = Deserialize(encodedBlock)

		return err
	})
	Handle(err)

	// iter가 이전 해시를 가리키도록 한다.
	iter.CurrentHash = block.PrevHash

	return block
}

// FindUnspentTransactions : 사용하지 않은 Transaction들을 찾는다.
// Transaction에 사용하지 않은 output이 한개라도 있으면 추가해서 반환한다.
func (chain *BlockChain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
	var unspentTxs []Transaction

	// key : string
	// value : []int
	spentTXOs := make(map[string][]int)

	iter := chain.Iterator()

	for {
		block := iter.Next()

		for _, tx := range block.Transactions {
			txID := hex.EncodeToString(tx.ID)

		Outputs:
			for outIdx, out := range tx.Outputs {
				// 사용한 TXO인지 체크.
				// 사용했다면 (spentTXOs에 들어있다면) continue
				if spentTXOs[txID] != nil {
					for _, spentOut := range spentTXOs[txID] {
						if spentOut == outIdx {
							continue Outputs
						}
					}
				}
				// 사용하지 않았고 unlock할 수 있다면 unspentTxs에 추가.
				if out.IsLockedWithKey(pubKeyHash) {
					unspentTxs = append(unspentTxs, *tx)
					// 강의에서는 break문이 없다.
					// tx를 unspentTxs에 추가했다면 다음 tx로 넘어가야하지 않을까?
					// 같은 tx가 여러개 추가되는 현상이 발생할 것 같음.
					break
				}
			}
			// Coinbase가 아닐 때
			if tx.IsCoinbase() == false {
				// Output을 찾기 위해 Input을 돈다.
				// inTxID를 ID로 가지는 Tx 의 in.Out 번째 output은 사용한 output이다.
				for _, in := range tx.Inputs {
					if in.UsesKey(pubKeyHash) {
						inTxID := hex.EncodeToString(in.ID)
						spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Out)
					}
				}
			}
		}

		// Genesis block 까지 오면 break.
		if len(block.PrevHash) == 0 {
			break
		}
	}
	return unspentTxs
}

// FindUTXO : 사용하지 않은 모든 output을 리턴한다.
// UTXOs의 Value를 모두 더하면 총잔고가 된다.
func (chain *BlockChain) FindUTXO(pubKeyHash []byte) []TxOutput {
	var UTXOs []TxOutput
	unspentTransactions := chain.FindUnspentTransactions(pubKeyHash)

	// UTXO중에 unlock할 수 있는(해당 address 소유자) 것만 모아서 리턴.
	for _, tx := range unspentTransactions {
		for _, out := range tx.Outputs {
			if out.IsLockedWithKey(pubKeyHash) {
				UTXOs = append(UTXOs, out)
			}

		}
	}
	return UTXOs
}

// FindSpendableOutputs : coin based transaction이 아닌 보통의 transaction을 생성한다.
// address로 amount만큼의 코인을 보낸다.
func (chain *BlockChain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) {
	unspentOuts := make(map[string][]int)
	unspentTxs := chain.FindUnspentTransactions(pubKeyHash)
	accumulated := 0

Work:
	// UTX 를 순회
	for _, tx := range unspentTxs {
		txID := hex.EncodeToString(tx.ID)

		// 한 transaction의 output을 순회.
		for outIdx, out := range tx.Outputs {
			// unlock할 수 있어야 하고 보내고 싶은 코인의 수가 UTXO로부터 모든 금액보다 클 때
			// 보낼 금액이 부족할 때 (accumulated를 더 증가시켜야 함.)
			if out.IsLockedWithKey(pubKeyHash) && accumulated < amount {
				accumulated += out.Value
				unspentOuts[txID] = append(unspentOuts[txID], outIdx)

				// 금액이 다 모임.
				if accumulated >= amount {
					break Work
				}
			}
		}
	}

	return accumulated, unspentOuts
}

// FindTransaction : ID 와 일치하느 Tx를 찾는다.
func (chain *BlockChain) FindTransaction(ID []byte) (Transaction, error) {
	iter := chain.Iterator()

	for {
		block := iter.Next()

		for _, tx := range block.Transactions {
			// 원하는 Tx 발견.
			if bytes.Compare(tx.ID, ID) == 0 {
				return *tx, nil
			}
		}

		if len(block.PrevHash) == 0 {
			break
		}
	}

	// 블록을 전부 돌았는데 같은 ID를 가진 Tx가 없다.
	return Transaction{}, errors.New("Transaction does not exist")
}

// SignTransaction : Tx에 sign을 하는 메소드.
func (chain *BlockChain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
	// key : ID (hex)
	// value : Transaction
	prevTXs := make(map[string]Transaction)

	// 해당 Tx의 모든 인풋을 돌면서 그 인풋이 사용한 이전 Tx를 찾아 prevTXs에 저장한다.
	for _, in := range tx.Inputs {
		prevTX, err := chain.FindTransaction(in.ID)
		Handle(err)
		prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
	}

	tx.Sign(privKey, prevTXs)
}

// VerifyTransaction : Tx를 검증하는 메소드.
func (chain *BlockChain) VerifyTransaction(tx *Transaction) bool {
	// key : ID (hex)
	// value : Transaction
	prevTXs := make(map[string]Transaction)

	// 해당 Tx의 모든 인풋을 돌면서 그 인풋이 사용한 이전 Tx를 찾아 prevTXs에 저장한다.
	for _, in := range tx.Inputs {
		prevTX, err := chain.FindTransaction(in.ID)
		Handle(err)
		prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
	}

	return tx.Verify(prevTXs)
}

Result

// 지갑 2개 생성.
go run main.go createwallet
pub key: a4cf5c97b5634e47d8a92a8f21d5106b0b713e9fb36c79220d862d940211605c3d9db8d89add0e15ccf55a00cfa9b7effd51dfcbd10099a29e081b639e4dcc65
pub hash: 1325c81699162278f70e2365aac0a26139398b8e
address: 12kF6krD5gvWRUJVYw47T1as2aj3jxAcvA
fullHash: 001325c81699162278f70e2365aac0a26139398b8ecc618e8f
checksum: cc618e8f
version: 001325c81699162278f70e2365aac0a26139398b8e
New address is: 12kF6krD5gvWRUJVYw47T1as2aj3jxAcvA
go run main.go createwallet
pub key: 47fc9ef4bc384a021d5ee3cc74760e167c27b7598d5bc82e490029318d2a22d61e957a193bae09775f70a4418105f89d78e082cae266f463ee4883e517137ac6
pub hash: 3043ec936f036df9d40c8231f109dd8846cb987f
address: 15QCofD8geLRwRnhVpNardj6jUHHWGtdRN
fullHash: 003043ec936f036df9d40c8231f109dd8846cb987fccc35a9d
checksum: ccc35a9d
version: 003043ec936f036df9d40c8231f109dd8846cb987f
New address is: 15QCofD8geLRwRnhVpNardj6jUHHWGtdRN
// 첫 블록 생성. 100코인 받음.
go run main.go createblockchain -address 12kF6krD5gvWRUJVYw47T1as2aj3jxAcvA
0005c62f997aaeac45e0009b933fb8595904f8f0fc0447a7594fa93b120b8fb1
Genesis created
Finished!
go run main.go printchain
Previous Hash: 
Hash: 0005c62f997aaeac45e0009b933fb8595904f8f0fc0447a7594fa93b120b8fb1
PoW: true
--- Transaction 7d20dd944b63bb401cdf016f18a6d587c56ec4507a3dca084079b95fd514e7cf:
     Input 0:
       TXID:     
       Out:       -1
       Signature: 
       PubKey:    4669727374205472616e73616374696f6e2066726f6d2047656e65736973
     Output 0:
       Value:  100
       Script: 1325c81699162278f70e2365aac0a26139398b8e
// A가 B에게 30코인 전송.
go run main.go send -from 12kF6krD5gvWRUJVYw47T1as2aj3jxAcvA -to 15QCofD8geLRwRnhVpNardj6jUHHWGtdRN -amount 30
0001f084ad32fb322eb8f5381c268d3f27026c91b602a21476f09e740396b38c
Successful send
go run main.go printchain
Previous Hash: 0005c62f997aaeac45e0009b933fb8595904f8f0fc0447a7594fa93b120b8fb1
Hash: 0001f084ad32fb322eb8f5381c268d3f27026c91b602a21476f09e740396b38c
PoW: true
--- Transaction 82f65affcede27a85259be9dedf63d3fa9a073d114c59062713279bf9acac1e4:
     Input 0:
       TXID:     7d20dd944b63bb401cdf016f18a6d587c56ec4507a3dca084079b95fd514e7cf
       Out:       0
       Signature: 3d0ffa6793d50f78ce9075e63504e2d6e9a9942638ad9c8eee233ed7ae510749656ef7e3b374cec58267baa05af6bd1748cef56f21c23f34e6a988f74be49b7e
       PubKey:    a4cf5c97b5634e47d8a92a8f21d5106b0b713e9fb36c79220d862d940211605c3d9db8d89add0e15ccf55a00cfa9b7effd51dfcbd10099a29e081b639e4dcc65
     Output 0:
       Value:  30
       Script: 3043ec936f036df9d40c8231f109dd8846cb987f
     Output 1:
       Value:  70
       Script: 1325c81699162278f70e2365aac0a26139398b8e

Previous Hash: 
Hash: 0005c62f997aaeac45e0009b933fb8595904f8f0fc0447a7594fa93b120b8fb1
PoW: true
--- Transaction 7d20dd944b63bb401cdf016f18a6d587c56ec4507a3dca084079b95fd514e7cf:
     Input 0:
       TXID:     
       Out:       -1
       Signature: 
       PubKey:    4669727374205472616e73616374696f6e2066726f6d2047656e65736973
     Output 0:
       Value:  100
       Script: 1325c81699162278f70e2365aac0a26139398b8e
// 남은 잔고
go run main.go getbalance -address 12kF6krD5gvWRUJVYw47T1as2aj3jxAcvA
Balance of 12kF6krD5gvWRUJVYw47T1as2aj3jxAcvA: 70
go run main.go getbalance -address 15QCofD8geLRwRnhVpNardj6jUHHWGtdRN
Balance of 15QCofD8geLRwRnhVpNardj6jUHHWGtdRN: 30

Last updated