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