This is a creation in Article, where the information may have evolved or changed.
Using Go language to create blockchain (ii)
Introduced
In the previous article, we built a very simple data structure, which is the essence of a blockchain database. And we can add chunks to it using the chained relationship between them: each chunk is linked to the previous one. Alas, adding a chunk to the chain in reality is a daunting task.
Work Certificate
A key idea of a block chain is that you have to work with proof to put data into it. It is a daunting task to make the block chain secure and consistent. In addition, the hard work has been rewarded (this is how people get mining coins).
This mechanism is very similar to the real-life mechanism: people must work to be rewarded and sustain life. In the network, some of the network's participants (miners) try to maintain the network, add new blocks to it, and reward their work. As a result of its work, blocks are incorporated into the block chain in a secure manner, which preserves the stability of the entire blockchain database. It is worth noting that the person who completes the work must prove it.
This whole "hard work and proven work value" mechanism is known as proof of work. This is difficult because it requires a lot of computing power: even high-performance computers cannot be completed very quickly. In addition, the difficulty of this work is increased from time to times to keep the new block rate about 6 blocks per hour. In Bitcoin, the goal of such a job is to find a block of hashes that meet some of the requirements. This is a hash, as proof. Therefore, finding evidence is actually working.
The last thing to pay attention to. The work proof algorithm must satisfy the requirement: it is difficult to finish the work and proves that the work is easy to complete . The proofs are usually given to non-workers, so for them to verify that it shouldn't take too much time.
Hashing algorithm Encryption
In this article, we'll discuss the hash value. If you are familiar with this concept, you can skip this section.
A hash is the process of getting the hash value of the specified data. A hash value is a unique representation of the data it calculates. A hash function is a function that obtains data of any size and produces a fixed-size hash. The following are some of the key features of hashing:
- Raw data cannot be recovered from a hash value. Therefore, the hash is not encrypted.
- The data can only have one hash value, and the hash is unique.
- Changing one byte in the input data will result in a completely different hash.
Hash functions are widely used to check the consistency of data. In a blockchain, hashes are used to ensure block consistency. The input data of the hashing algorithm contains the hash value of the previous block, making the generated chain difficult to modify before the resulting chunk (or at least quite difficult): the hash value of all subsequent blocks must be recalculated.
Hash cash, Hashcash
Bitcoin uses Hashcash, the invention of the hash cash was originally developed to prevent e-mail spam. It can be divided into the following steps:
- Get the exposed data (in the case of e-mail, it is the recipient's e-mail address; In the case of Bitcoin, it is a block header).
- Add a counter. The counter starts at 0.
- Gets
数据+计数器 the combined hash.
- Check to see if the hash value meets the requirements.
- If the requirement is met, end the process.
- If the requirements are not met, increase the counters and repeat steps 3 and 4.
So, this is a brute force algorithm: You change the counter, calculate a new hash, check it, increase the counter, compute the hash, and so on. That's why it's computationally expensive.
Now let's take a look at the requirements that a hash must meet. In the original Hashcash implementation, the "first 20 bits of the hash must be 0". In Bitcoin, however, the hash requirement is adjusted from time to time, because while the computational power increases over the years, more and more miners join the network, so the design must generate a block every 10 minutes .
To demonstrate this algorithm, I took the data from the previous example ("I like Donuts") and found a hash starting with 0 0 bytes:
Writing code
Programmer's tip: Go and Python are all languages without semicolons
OK, we've finished the theory, let's write the code! First, let's define the difficulty of digging:
const targetBits = 24
In Bitcoin, the ' target bits ' is the block header storing the difficulty at which the block was mined. We won ' t implement a target adjusting algorithm, for now, so we can just define the difficulty as a global constant.
Arbitrary number, our goal are to has a target of takes less than. And we want the difference to being significant enough, but not too big, because the bigger the difference the more difficult It ' s to find a proper hash.
In Bitcoin, "target bit" is the difficult chunk of storage blocks being mined. We will not implement the target tuning algorithm now, so we can define the difficulty as a global constant .
24 is an arbitrary number, and our goal is to occupy less than 256 bits of the target in memory. And we want the difference to be big enough, but not too big, because the bigger the difference, the harder it is to find the right hash.
type ProofOfWork struct { block *Block target *big.Int //定义目标位}func NewProofOfWork(b *Block) *ProofOfWork { target := big.NewInt(1) target.Lsh(target, uint(256-targetBits)) //左移256个 target bits位 pow := &ProofOfWork{b, target} return pow}
This creates a working proof structure that holds pointers to blocks and pointers to targets. "Target" is another name for the requirements described in the previous paragraph. We use a large integer because we compare the hash to the target: we convert the hash to a large integer and check if it is smaller than the target.
big:https://golang.org/pkg/math/big/
In the new work proof function, we initialize a big with a value of 1. Int and move it to the left 256-targetbits bit. 256 is the length of the SHA-256 hash, in bits, which is the SHA-256 hash algorithm we want to use. The hexadecimal representation of the target is:
0x10000000000000000000000000000000000000000000000000000000000
It occupies 29 bytes in memory. This is compared to the hash in the previous example:
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e300000100000000000000000000000000000000000000000000000000000000000000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca
The first hash (calculated as "I like Donuts") is larger than the target, so it is not a valid proof of work . The second hash (calculated as "I like Donut Ca07ca") is less than the target, so this is a valid proof.
You can treat the target as the upper limit of the range: if the number (hash) is lower than the boundary, it is valid and vice versa. Reducing the boundaries will result in a reduction in the number of effective quantities, so the work required to find an effective quantity is more difficult.
Now, hash the data.
func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, pow.block.Data, IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), }, []byte{}, ) return data}
We just merge the block area with the target and the random number. Nonce here is the counter described from the above Hashcash, which is the cryptographic term.
OK, all the preparation is done, and we'll implement the core of the POW algorithm:
func (pow *ProofOfWork) Run() (int, []byte) { var hashInt big.Int var hash [32]byte nonce := 0 fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) for nonce < maxNonce { data := pow.prepareData(nonce) // 准备数据 hash = sha256.Sum256(data) // SHA-256加密 fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) // 讲hash转换成Big Integer if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ } } fmt.Print("\n\n") return nonce, hash[:]}
First, we initialize the variable: hashint is an integer representation of the hash; The nonce is the counter. Next, we run an "infinite" loop: it is limited to maxnonce, which equals math. MaxInt64; This is done to avoid possible random number overflows. Although our POW implementation is too difficult to prevent overflow, it is better to do this check just in case.
In the loop we:
- Hash with the SHA-256.
- Converts a hash to a large integer.
- Compares an integer to a target.
Now we can delete the block 's Sethash method and modify the newblock function:
func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} pow := NewProofOfWork(block) nonce, hash := pow.Run() block.Hash = hash[:] block.Nonce = nonce return block}
Here's can see that's nonce saved as a property Block . This is necessary because are nonce required to verify a proof. The Block structure now looks so:
type Block struct { Timestamp int64 Data []byte PrevBlockHash []byte Hash []byte Nonce int}
Verification Work Certificate
func (pow *ProofOfWork) Validate() bool { var hashInt big.Int data := pow.prepareData(pow.block.Nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) isValid := hashInt.Cmp(pow.target) == -1 return isValid}
Check the main function code again
func main() { ... for _, block := range bc.blocks { ... pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) fmt.Println() }}
Conclusion
Our blockchain is a step closer to its actual architecture: adding blocks now requires hard work, so digging is possible. But it still lacks some key features: The blockchain database is not persistent, no wallet, no address, no deal, no consensus mechanism. All of this we will be implementing in future articles, now, mining and mining!
Link:
- Source code, full source codes
- hashing algorithm, Blockchain hashing algorithm
- Working certificate, Proof of work
- Hash cash, Hashcash