Blockchain on Go. Part 5: Addresses

Original author: Ivan Kuznetsov
  • Transfer
  • Tutorial
Content

  1. Blockchain on Go. Part 1: Prototype
  2. Blockchain on Go. Part 2: Proof-of-Work
  3. Blockchain on Go. Part 3: read-only memory and command line interface
  4. Blockchain on Go. Part 4: Transactions, Part 1
  5. Blockchain on Go. Part 5: Addresses
  6. Blockchain on Go. Part 6: Transactions, Part 2
  7. 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:

  1. that the data has not been changed during transmission from the sender to the recipient;
  2. that the data was created by a specific sender;
  3. 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:

  1. data for signature;
  2. private key.

The algorithm creates a signature that is stored in transaction inputs. To verify your signature, you need the following:

  1. data that has been signed;
  2. signature;
  3. 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):

  1. Verifying that the inputs have sufficient rights to use the outputs from previous transactions.
  2. 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:

  1. 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))).
  2. 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.
  3. 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).
  4. When the node is ready to mine a new block, it will place the transaction in the block and begin to mine it.
  5. 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.
  6. 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 Walletsto store a collection of wallets, save them to a file, and unload them from it. WalletA new key pair is created in the constructor . The function newKeyPairis 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:

  1. Take the public key and write it down twice using hashing algorithms RIPEMD160 (SHA256 (PubKey)).
  2. Prepare the version.
  3. 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.
  4. Add the checksum to the combination version+PubKeyHash.
  5. We encrypt the combination version+PubKeyHash+checksumwith 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 ScriptPubKeyand ScriptSig, instead, ScriptSigis divided into fields Signatureand PubKey, and is ScriptPubKeyrenamed 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 UsesKeychecks 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 FindUnspentTransactionsto build connections between transactions.

Lockjust 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:

  1. Public key hashes stored in unlocked outputs. This identifies the "sender" of the transaction.
  2. Public key hashes stored in new, locked outlets. This identifies the "receiver" of the transaction.
  3. The meanings of the new outputs.

In Bitcoin, the lock / unlock logic is stored in scripts that are stored in ScriptSigboth ScriptPubKeythe input and output fields, respectively. Since Bitcoin accepts different types of such scripts, it signs all the contents ScriptPubKey.

In this regard, in Bitcoin, the transaction is not signed, but its processed copy with inputs containing the ScriptPubKeyspecified output

A 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.Signatureand TXInput.PubKeywill 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 Signatureset to nil (just a double check), and we PubKeyassign a link to the output to PubKeyHash. At the moment, all transactions except the current one are “empty”, that is, the signature fields PubKeyare 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 Hashserializes 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 PubKeyso 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.IDwith 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.Signatureand 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.PublicKeyusing 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)
}

FindTransactionFinds the transaction by identifier (this requires iteration over all blocks in the chain); SignTransactiontakes one transaction, finds the transactions to which it refers, and signs it; VerifyTransactionjust 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 NewUTXOTransactionto 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.

References


  1. Full source codes
  2. Public-key cryptography
  3. Digital signatures
  4. Elliptic curve
  5. Elliptic curve cryptography
  6. ECDSA
  7. Technical background of Bitcoin addresses
  8. Address
  9. Base58
  10. A gentle introduction to elliptic curve cryptography

Also popular now: