
Blockchain on Go. Part 5: Addresses
- Transfer
- Tutorial
Content
In the previous article, we started implementing transactions, and also got acquainted with the principle of its operation: there are no accounts, personal data (for example, name or series and passport number) are not required and are not stored anywhere in Bitcoin. But still, there must be something that identifies you as the owner of the outputs of the transaction (i.e., the owner of the coins blocked on the outputs). And that’s what Bitcoin addresses are for. So far, we have used arbitrary strings as addresses, now it's time to implement real addresses in the way they are implemented in Bitcoin.
In this part, we will change a lot of code, so I see no reason to explain everything in detail. Visit this page to see all the changes compared to the previous article.
Here is an example of a Bitcoin address: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa . This is the very first Bitcoin address that supposedly belongs to Satoshi Nakamoto. Bitcoin addresses are publicly available. If you want to send coins to someone, you need to know the address of the recipient. But the addresses (despite the uniqueness) are not the identifier of you, as the owner of the "wallet". In fact, such addresses are a public key. In Bitcoin, your identity is a pair (or pairs) of private and public keys stored on your computer (or in some other place that you have access to). To create such keys, cryptographic algorithms are used that guarantee that no one else can access your coins without physical access to your keys. Let's look at what these algorithms are.
Public key cryptosystems use key pairs: public and private. Public keys can be shared with anyone. Private keys, on the contrary, should not be disclosed to anyone: no one except the owner should have access to them, since these are private keys that serve as an identifier for the owner. Your face is your private keys (in the world of cryptocurrencies, of course).
In essence, a Bitcoin wallet is just a pair of such keys. When you install the wallet application or use the Bitcoin client to create a new address, a key pair is generated for you. The one who controls the private key controls all the coins that were sent to it.
Private and public keys are just random sequences of bytes, so they cannot be printed on the screen and read by a person. This is why Bitcoin uses an algorithm to convert public keys into a human-readable string.
So now we know what identifies users in Bitcoin. But how does Bitcoin verify the owner of the transaction exit (and the coins that are stored there)?
In mathematics and cryptography there is a concept of electronic digital signature - algorithms that guarantee:
Applying the electronic digital signature algorithm to the data (i.e., signing the data), a signature is obtained that can later be verified. Data is signed using a private key, and a public key is required for verification.
To sign the data, we need the following:
The algorithm creates a signature that is stored in transaction inputs. To verify your signature, you need the following:
In simple words, the verification process can be described as follows: we need to make sure that the signature is obtained from this data using the private key that was used to generate the public key.
Each transaction entry in Bitcoin is signed by the person who created this transaction. Every transaction in Bitcoin must be verified before placing it in a block. Verification means (among other procedures):
Schematically, the process of signing and verifying data looks like this:

Let's look at the complete transaction life cycle:
As we have already said, public and private keys are sequences of random bytes. We do not want to generate a private key that belongs to someone else, so there is a need for a special algorithm for generating sequences.
Bitcoin uses elliptical curves to generate secret keys. Elliptic curves are a complex mathematical concept that we will not explain in detail here (if interested, you can read about it here WARNING : a lot of mathematics!). The curve used by Bitcoin can randomly select a number between 0 and 2²⁵⁶ (which is approximately 10⁷⁷, note, atoms in the visible universe are somewhere between 10⁷⁸ and 10⁸²). Such a limit means that almostit is not possible to generate the same private key twice.
In addition, Bitcoin uses (and we will) the ECDSA algorithm to sign transactions.
Now let's get back to the above address 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa. Now we know that this is a generally accepted representation of a public key for a person. And if we decode it, it will look something like this (like a sequence of bytes written in a hexadecimal system):
Bitcoin uses the Base58 algorithm to convert public keys into a human-readable format. The algorithm is very similar to the well-known Base64, but it uses a shorter alphabet: some letters have been removed from the alphabet to avoid homographic attacks . In this regard, the following characters are absent in this alphabet: 0 (zero), O (capital letter “o”), I (capital letter “i”), l (lowercase “L”), as well as signs “+” and “/ "
The process of obtaining an address from a public key looks something like

this : Following this scheme, the key that we presented above is divided into three parts:
Well, now that we have put everything together, it's time to write the code! I hope that now everything that was incomprehensible will become clear.
Let's start with the wallet structure.
A wallet is nothing more than a pair of keys. We will also need a type
Now, create an address generation function:
Let's look at the steps for converting the public key to the Base58 address:
As a result, you will get a real Bitcoin address , you can even check its balance on blockchain.info . But I'm more than sure that there will be nothing on the account of this address. This is why choosing the right public key encryption algorithm is so important: given that private keys are random numbers, the probability of generating the same number should be as low as possible. Ideally, it should not be repeated at all.
Please note that you do not need to connect to the Bitcoin node to receive an address. The address generation algorithm uses a combination of algorithms that are already implemented in many standard libraries of popular programming languages.
Now we need to change the inputs and outputs to use the addresses:
Please note that we no longer use the fields
The method
Now let's check that everything is working correctly:
Excellent! It's time to implement transaction signatures.
Transactions must be signed, as this is the only way to guarantee transaction reliability in Bitcoin. If the signature is invalid, the transaction is considered invalid and, therefore, cannot be added to the chain.
We have everything for the implementation of transaction signatures, except for one: data for signature. What part of the transaction should we sign? Or is it necessary to sign the deal as a whole? The choice of data for signature is very important. The fact is that the data that must be signed must contain information that identifies the data in a unique way. For example, it makes no sense to sign only the output values, because such a signature will not take into account the sender and receiver.
Considering that transactions unlock previous outputs, redistribute their values and block new outputs, the following data should be signed:
In this regard, in Bitcoin, the transaction is not signed, but its processed copy with inputs containing the
This all seems complicated enough, let's start writing code. And we will start with the method
The method accepts a private key and an associative array of previous transactions. As mentioned above, to sign a transaction, we need to access the outputs specified in the transaction inputs, so we need the transactions that store these outputs.
Let's take a closer look at this method:
Coinbase transactions are not signed, since they do not have real outputs
We sign the processed copy, and not the entire transaction:
The copy will include all inputs and outputs,
Then go through each entry in the copy:
On each input it is
The method
We sign
Consider the verification function:
The method is pretty simple. First, get a copy of the transaction, as in the previous method:
Then we need a curve that is used to generate key pairs:
Then go through all the inputs and verify that they are signed:
This part is identical to that used in the Sign method, since during the verification we need the same data that we signed
Here we unpack the values stored in
Now we create
Now we need a function to get previous transactions. Since this requires interaction with the entire circuit, we will make it a method
Now we need to sign and verify the transactions. We will sign in the method
Validation of a transaction occurs before it is added to the block:
That's all! Let's check again:
We have not even broken anything, amazing!
Let's comment out the call
We have implemented almost all the key features of Bitcoin and this is amazing. And in the next part, we will finally complete the implementation of transactions.
- Blockchain on Go. Part 1: Prototype
- Blockchain on Go. Part 2: Proof-of-Work
- Blockchain on Go. Part 3: read-only memory and command line interface
- Blockchain on Go. Part 4: Transactions, Part 1
- Blockchain on Go. Part 5: Addresses
- Blockchain on Go. Part 6: Transactions, Part 2
- Blockchain on Go. Part 7: Network
Introduction
In the previous article, we started implementing transactions, and also got acquainted with the principle of its operation: there are no accounts, personal data (for example, name or series and passport number) are not required and are not stored anywhere in Bitcoin. But still, there must be something that identifies you as the owner of the outputs of the transaction (i.e., the owner of the coins blocked on the outputs). And that’s what Bitcoin addresses are for. So far, we have used arbitrary strings as addresses, now it's time to implement real addresses in the way they are implemented in Bitcoin.
In this part, we will change a lot of code, so I see no reason to explain everything in detail. Visit this page to see all the changes compared to the previous article.
Bitcoin Address
Here is an example of a Bitcoin address: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa . This is the very first Bitcoin address that supposedly belongs to Satoshi Nakamoto. Bitcoin addresses are publicly available. If you want to send coins to someone, you need to know the address of the recipient. But the addresses (despite the uniqueness) are not the identifier of you, as the owner of the "wallet". In fact, such addresses are a public key. In Bitcoin, your identity is a pair (or pairs) of private and public keys stored on your computer (or in some other place that you have access to). To create such keys, cryptographic algorithms are used that guarantee that no one else can access your coins without physical access to your keys. Let's look at what these algorithms are.
Public Key Cryptosystem
Public key cryptosystems use key pairs: public and private. Public keys can be shared with anyone. Private keys, on the contrary, should not be disclosed to anyone: no one except the owner should have access to them, since these are private keys that serve as an identifier for the owner. Your face is your private keys (in the world of cryptocurrencies, of course).
In essence, a Bitcoin wallet is just a pair of such keys. When you install the wallet application or use the Bitcoin client to create a new address, a key pair is generated for you. The one who controls the private key controls all the coins that were sent to it.
Private and public keys are just random sequences of bytes, so they cannot be printed on the screen and read by a person. This is why Bitcoin uses an algorithm to convert public keys into a human-readable string.
If you have ever used a Bitcoin wallet as an application, probably a mnemonic phrase has been created for you. Such phrases are used instead of private keys and can be used to generate them. This mechanism is implemented in BIP-039 .
So now we know what identifies users in Bitcoin. But how does Bitcoin verify the owner of the transaction exit (and the coins that are stored there)?
Electronic digital signature
In mathematics and cryptography there is a concept of electronic digital signature - algorithms that guarantee:
- that the data has not been changed during transmission from the sender to the recipient;
- that the data was created by a specific sender;
- that the sender cannot deny that he sent the data.
Applying the electronic digital signature algorithm to the data (i.e., signing the data), a signature is obtained that can later be verified. Data is signed using a private key, and a public key is required for verification.
To sign the data, we need the following:
- data for signature;
- private key.
The algorithm creates a signature that is stored in transaction inputs. To verify your signature, you need the following:
- data that has been signed;
- signature;
- public key.
In simple words, the verification process can be described as follows: we need to make sure that the signature is obtained from this data using the private key that was used to generate the public key.
Digital signatures are not encrypted, and data cannot be obtained from it. This is like hashing: you transform the data using an algorithm and get their unique representation. The difference between a hash and a signature are key pairs that allow verification of the latter.
But such key pairs can also be used to encrypt data: the private key is used for encryption, and the public key is used for decryption. However, Bitcoin does not use encryption algorithms.
Each transaction entry in Bitcoin is signed by the person who created this transaction. Every transaction in Bitcoin must be verified before placing it in a block. Verification means (among other procedures):
- Verifying that the inputs have sufficient rights to use the outputs from previous transactions.
- Verification of the signature of the transaction.
Schematically, the process of signing and verifying data looks like this:

Let's look at the complete transaction life cycle:
- At the very beginning, there is a genesis block with a coinbase transaction. There are no real inputs in coinbase transactions, so no signature is required. The transaction output contains a hashed public key (algorithms are used
RIPEMD16(SHA256(PubKey))
). - When someone sends coins, a transaction is created. Transaction inputs will refer to exits from previous transactions. Each entry will store a public key (not hashed) and the signature of the entire transaction.
- Other nodes in the Bitcoin network that receive the transaction will also verify it. Among other things, there is a comparison of the hash of the public key at the input of the transaction with the hash of the corresponding output (this ensures that the sender spends only his coins) the signature is correct (this ensures that the transaction is created by the real owner of the coins).
- When the node is ready to mine a new block, it will place the transaction in the block and begin to mine it.
- When a block is mined, each other node in the network receives a message that the block is mined and adds it to the circuit.
- After the block is added to the chain, the transaction is completed, now its outputs can be referenced in new transactions.
Elliptical cryptography
As we have already said, public and private keys are sequences of random bytes. We do not want to generate a private key that belongs to someone else, so there is a need for a special algorithm for generating sequences.
Bitcoin uses elliptical curves to generate secret keys. Elliptic curves are a complex mathematical concept that we will not explain in detail here (if interested, you can read about it here WARNING : a lot of mathematics!). The curve used by Bitcoin can randomly select a number between 0 and 2²⁵⁶ (which is approximately 10⁷⁷, note, atoms in the visible universe are somewhere between 10⁷⁸ and 10⁸²). Such a limit means that almostit is not possible to generate the same private key twice.
In addition, Bitcoin uses (and we will) the ECDSA algorithm to sign transactions.
Base58
Now let's get back to the above address 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa. Now we know that this is a generally accepted representation of a public key for a person. And if we decode it, it will look something like this (like a sequence of bytes written in a hexadecimal system):
0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93
Bitcoin uses the Base58 algorithm to convert public keys into a human-readable format. The algorithm is very similar to the well-known Base64, but it uses a shorter alphabet: some letters have been removed from the alphabet to avoid homographic attacks . In this regard, the following characters are absent in this alphabet: 0 (zero), O (capital letter “o”), I (capital letter “i”), l (lowercase “L”), as well as signs “+” and “/ "
The process of obtaining an address from a public key looks something like

this : Following this scheme, the key that we presented above is divided into three parts:
Version 00
Public key hash 62E907B15CBF27D5425399EBF6F0FB50EBB88F18
Checksum C29B7D93
Well, now that we have put everything together, it's time to write the code! I hope that now everything that was incomprehensible will become clear.
Address Implementation
Let's start with the wallet structure.
Wallet
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
type Wallets struct {
Wallets map[string]*Wallet
}
func NewWallet() *Wallet {
private, public := newKeyPair()
wallet := Wallet{private, public}
return &wallet
}
func newKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, pubKey
}
A wallet is nothing more than a pair of keys. We will also need a type
Wallets
to store a collection of wallets, save them to a file, and unload them from it. Wallet
A new key pair is created in the constructor . The function newKeyPair
is simple, here we use ECDSA. Then, the private key is created using the curve, and the public key is generated using the private key. One note: in elliptic curve algorithms, public keys are points on the curve. Thus, the public key is a combination of X, Y coordinates. In Bitcoin, these coordinates combine to form the public key. Now, create an address generation function:
func (w Wallet) GetAddress() []byte {
pubKeyHash := HashPubKey(w.PublicKey)
versionedPayload := append([]byte{version}, pubKeyHash...)
checksum := checksum(versionedPayload)
fullPayload := append(versionedPayload, checksum...)
address := Base58Encode(fullPayload)
return address
}
func HashPubKey(pubKey []byte) []byte {
publicSHA256 := sha256.Sum256(pubKey)
RIPEMD160Hasher := ripemd160.New()
_, err := RIPEMD160Hasher.Write(publicSHA256[:])
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
func checksum(payload []byte) []byte {
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
return secondSHA[:addressChecksumLen]
}
Let's look at the steps for converting the public key to the Base58 address:
- Take the public key and write it down twice using hashing algorithms
RIPEMD160 (SHA256 (PubKey))
. - Prepare the version.
- We calculate the checksum by hashing the result from step 2 and
SHA256 (SHA256 (payload))
. The checksum is the first four bytes of the received hash. - Add the checksum to the combination
version+PubKeyHash
. - We encrypt the combination
version+PubKeyHash+checksum
with Base58.
As a result, you will get a real Bitcoin address , you can even check its balance on blockchain.info . But I'm more than sure that there will be nothing on the account of this address. This is why choosing the right public key encryption algorithm is so important: given that private keys are random numbers, the probability of generating the same number should be as low as possible. Ideally, it should not be repeated at all.
Please note that you do not need to connect to the Bitcoin node to receive an address. The address generation algorithm uses a combination of algorithms that are already implemented in many standard libraries of popular programming languages.
Now we need to change the inputs and outputs to use the addresses:
type TXInput struct {
Txid []byte
Vout int
Signature []byte
PubKey []byte
}
func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
lockingHash := HashPubKey(in.PubKey)
return bytes.Compare(lockingHash, pubKeyHash) == 0
}
type TXOutput struct {
Value int
PubKeyHash []byte
}
func (out *TXOutput) Lock(address []byte) {
pubKeyHash := Base58Decode(address)
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
out.PubKeyHash = pubKeyHash
}
func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}
Please note that we no longer use the fields
ScriptPubKey
and ScriptSig
, instead, ScriptSig
is divided into fields Signature
and PubKey
, and is ScriptPubKey
renamed to PubKeyHash
. We will implement the same functions of blocking / unblocking outputs and the logic of signatures of inputs as in Bitcoin, but we will implement this using methods. The method
UsesKey
checks that the input uses a specific key to unlock the output. Please note that the inputs store unhashed public keys, and the function accepts a hashed one. IsLockedWithKey
checks if a public key hash has been used to block the exit. This is an optional feature for UsesKey
, and they are both used in FindUnspentTransactions
to build connections between transactions.Lock
just blocks the exit. When we send coins to someone, we only know the address, so the function takes the address as the only argument. Then the address is decoded, and the hash key of the public key is extracted from it and stored in the field PubKeyHash
. Now let's check that everything is working correctly:
$ blockchain_go createwallet
Your new address: 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
$ blockchain_go createwallet
Your new address: 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
$ blockchain_go createwallet
Your new address: 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
$ blockchain_go createblockchain -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
0000005420fbfdafa00c093f56e033903ba43599fa7cd9df40458e373eee724d
Done!
$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 10
$ blockchain_go send -from 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -to 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -amount 5
2017/09/12 13:08:56 ERROR: Not enough funds
$ blockchain_go send -from 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -to 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -amount 6
00000019afa909094193f64ca06e9039849709f5948fbac56cae7b1b8f0ff162
Success!
$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 4
$ blockchain_go getbalance -address 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
Balance of '15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h': 6
$ blockchain_go getbalance -address 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
Balance of '1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy': 0
Excellent! It's time to implement transaction signatures.
Signature Implementation
Transactions must be signed, as this is the only way to guarantee transaction reliability in Bitcoin. If the signature is invalid, the transaction is considered invalid and, therefore, cannot be added to the chain.
We have everything for the implementation of transaction signatures, except for one: data for signature. What part of the transaction should we sign? Or is it necessary to sign the deal as a whole? The choice of data for signature is very important. The fact is that the data that must be signed must contain information that identifies the data in a unique way. For example, it makes no sense to sign only the output values, because such a signature will not take into account the sender and receiver.
Considering that transactions unlock previous outputs, redistribute their values and block new outputs, the following data should be signed:
- Public key hashes stored in unlocked outputs. This identifies the "sender" of the transaction.
- Public key hashes stored in new, locked outlets. This identifies the "receiver" of the transaction.
- The meanings of the new outputs.
In Bitcoin, the lock / unlock logic is stored in scripts that are stored inScriptSig
bothScriptPubKey
the input and output fields, respectively. Since Bitcoin accepts different types of such scripts, it signs all the contentsScriptPubKey
.
In this regard, in Bitcoin, the transaction is not signed, but its processed copy with inputs containing the
ScriptPubKey
specified outputA detailed process for processing a copy of a transaction is described here . Most likely, it is outdated, but I could not find a more reliable source of information.
This all seems complicated enough, let's start writing code. And we will start with the method
Sign
: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)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID)
signature := append(r.Bytes(), s.Bytes()...)
tx.Vin[inID].Signature = signature
}
}
The method accepts a private key and an associative array of previous transactions. As mentioned above, to sign a transaction, we need to access the outputs specified in the transaction inputs, so we need the transactions that store these outputs.
Let's take a closer look at this method:
if tx.IsCoinbase() {
return
}
Coinbase transactions are not signed, since they do not have real outputs
txCopy := tx.TrimmedCopy()
We sign the processed copy, and not the entire transaction:
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
}
The copy will include all inputs and outputs,
TXInput.Signature
and TXInput.PubKey
will be equal to nil. Then go through each entry in the copy:
for inID, vin := range txCopy.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
On each input it is
Signature
set to nil (just a double check), and we PubKey
assign a link to the output to PubKeyHash
. At the moment, all transactions except the current one are “empty”, that is, the signature fields PubKey
are equal to zero. Thus, the inputs are signed separately , although this is not necessary for our application, but Bitcoin allows transactions to contain entries that refer to different addresses. txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
The method
Hash
serializes the transaction and hashes it using the SHA-256 algorithm. The result is data ready for signature. After receiving the hash, we must reset the field PubKey
so that there is no effect on our further iterations. r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID)
signature := append(r.Bytes(), s.Bytes()...)
tx.Vin[inID].Signature = signature
We sign
txCopy.ID
with privKey
. The ECDSA signature is a pair of numbers that we combine and store in the input field Signature
. Consider the verification function:
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
txCopy := tx.TrimmedCopy()
curve := elliptic.P256()
for inID, vin := range tx.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
r := big.Int{}
s := big.Int{}
sigLen := len(vin.Signature)
r.SetBytes(vin.Signature[:(sigLen / 2)])
s.SetBytes(vin.Signature[(sigLen / 2):])
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
}
The method is pretty simple. First, get a copy of the transaction, as in the previous method:
txCopy := tx.TrimmedCopy()
Then we need a curve that is used to generate key pairs:
curve := elliptic.P256()
Then go through all the inputs and verify that they are signed:
for inID, vin := range tx.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
This part is identical to that used in the Sign method, since during the verification we need the same data that we signed
r := big.Int{}
s := big.Int{}
sigLen := len(vin.Signature)
r.SetBytes(vin.Signature[:(sigLen / 2)])
s.SetBytes(vin.Signature[(sigLen / 2):])
x := big.Int{}
y := big.Int{}
keyLen := len(vin.PubKey)
x.SetBytes(vin.PubKey[:(keyLen / 2)])
y.SetBytes(vin.PubKey[(keyLen / 2):])
Here we unpack the values stored in
TXInput.Signature
and TXInput.PubKey
, since the signature is a pair of numbers, and the public key is a pair of coordinates. We have concatenated them before for storage, and now we need to unpack them for use in functions crypto/ecdsa
. rawPubKey := ecdsa.PublicKey{curve, &x, &y}
if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false {
return false
}
}
return true
Now we create
ecdsa.PublicKey
using the public key, which we take from the input, and execute ecdsa.Verify
, passing the signature, from the input. If all inputs are checked, we return true; if at least one input failed the test, return false. Now we need a function to get previous transactions. Since this requires interaction with the entire circuit, we will make it a method
Blockchain
:func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
bci := bc.Iterator()
for {
block := bci.Next()
for _, tx := range block.Transactions {
if bytes.Compare(tx.ID, ID) == 0 {
return *tx, nil
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return Transaction{}, errors.New("Transaction is not found")
}
func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin {
prevTX, err := bc.FindTransaction(vin.Txid)
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
tx.Sign(privKey, prevTXs)
}
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin {
prevTX, err := bc.FindTransaction(vin.Txid)
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
return tx.Verify(prevTXs)
}
FindTransaction
Finds the transaction by identifier (this requires iteration over all blocks in the chain); SignTransaction
takes one transaction, finds the transactions to which it refers, and signs it; VerifyTransaction
just verifies the transaction. Now we need to sign and verify the transactions. We will sign in the method
NewUTXOTransaction
:func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
...
tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash()
bc.SignTransaction(&tx, wallet.PrivateKey)
return &tx
}
Validation of a transaction occurs before it is added to the block:
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
var lastHash []byte
for _, tx := range transactions {
if bc.VerifyTransaction(tx) != true {
log.Panic("ERROR: Invalid transaction")
}
}
...
}
That's all! Let's check again:
$ blockchain_go createwallet
Your new address: 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR
$ blockchain_go createwallet
Your new address: 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab
$ blockchain_go createblockchain -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR
000000122348da06c19e5c513710340f4c307d884385da948a205655c6a9d008
Done!
$ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 6
0000000f3dbb0ab6d56c4e4b9f7479afe8d5a5dad4d2a8823345a1a16cf3347b
Success!
$ blockchain_go getbalance -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR
Balance of '1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR': 4
$ blockchain_go getbalance -address 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab
Balance of '1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab': 6
We have not even broken anything, amazing!
Let's comment out the call
bc.SignTransaction (& tx, wallet.PrivateKey)
in NewUTXOTransaction
to ensure that unsigned transactions cannot be mined:func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
...
tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash()
// bc.SignTransaction(&tx, wallet.PrivateKey)
return &tx
}
$ go install
$ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 1
2017/09/12 16:28:15 ERROR: Invalid transaction
Conclusion
We have implemented almost all the key features of Bitcoin and this is amazing. And in the next part, we will finally complete the implementation of transactions.