区块链基础 & 公链原理
Mon, Sep 10, 2018
0x0 区块链相关概念
法币:日常生活中用到的纸币
去中心化:比特币不需要中间机构做货币的发行工作 - 货币是被「发现」的,而不是「发明」的。需要注意的是,并不是所有的区块链项目都是去中心化的
挖矿:「结点A」希望给「结点B」转钱,需要「矿工」帮A把钱「搬运」过去
- 这种搬运的行为就是「挖矿」
- 挖矿的平均时长:10分钟(转钱不是实时到账的)
广播:矿工除了能「挖矿」,还可以「广播」 - 每挖成功一次矿,就会给所有结「广播」一次
账本:每个结点都有一个「账本」
- 每个结点每次收到广播,就会在自己的账本上记录一条信息
- 唯一的作弊方式就是篡改所有结点的账本记录
区块链:每个结点维护的「账本」就叫做「区块链」
区块:每笔转账记录消息,就叫「区块」
创世区块:区块链的第一个区块「创世区块」
矿工挖到矿之后,希望向区块链中,追加一个区块
矿工之所以能挖出来矿,是因为有人在转账
0x1 公链原理(共识算法)
PoW原理
Proof of Work - 工作量证明系统
区块参数
Nonce值:可以理解为序号
Timestamp:挖区块的时间戳
Data:交易信息
Hash:根据当前区块信息计算出来的哈希值
Pre Hash:上一个区块的哈希值
说明
有上表我们知道,参数中除Nonce和Hash,其他参数都是已知且固定的
矿工挖矿的时候,需要先把Nonce值置为1,把四个参数组合起来,计算Hash值。如果当前组合计算出来的Hash值不符合「要求」,则把Nonce累加后,再次计算新的Hash,直到符合要求
要求
比如当前难度为4,则要求计算出来的Hash值,前4个数必须为0000,才算挖矿成功
比特币难度系数有调整机制,会随着所有结点的总算力增强而增强,所以挖矿越来越难
过程模拟
https://anders.com/blockchain/blockchain.html
区块的链接
两个区块的链接方式,是通过后一个区块保存前一个区块的hash值,而链接起来的
公链
把上述步骤原理用代码实现而产生的产品
示意代码片段
type Block struct { PreHash []byte Timestamp int64 Data []byte Hash []byte Nonce int } func main() { //创建创世区块 var genesisBlock = CreateGenesisBlock() var newBlock = GenerateNextBlock(genesisBlock) fmt.Println("挖出的新区快Data为", newBlock.Data) fmt.Println("挖出的新区快Data为", hex.EncodeToString(newBlock.Hash)) } func CreateGenesisBlock() *Block { var genesisBlock = &Block{[]byte{0}, time.Now().Unix(), []byte("ab交易1bitcoin"), nil, 0} //计算当前区块的hash genesisBlock.getBlockHash() return genesisBlock } //通过PoW挖矿的方式挖新区块 func GenerateNextBlock(oldBlock *Block) *Block { //假设难度系数为4,则挖的区块的hash前边必须有4个0才算挖矿成功 var newBlock = &Block{oldBlock.Hash, time.Now().Unix(), []byte("bc交易"), nil, 0} //不断改变nonce值,最终实现当前区块的hash的0的个数与系统中要求的难度系数值一致 nonce := 1 for { var blockInfo = hex.EncodeToString(newBlock.PreHash) + hex.EncodeToString(newBlock.Data) + string(nonce) + string(newBlock.Timestamp) h := sha256.New() h.Write([]byte(blockInfo)) hashed := h.Sum(nil) hashString := hex.EncodeToString(hashed) fmt.Println("挖矿中", hashString) if strings.HasPrefix(hashString, "0000") { fmt.Println("挖矿成功") newBlock.Hash = hashed return newBlock } nonce++ } } func (block *Block) getBlockHash() []byte { //拼接区块信息 var blockInfo = hex.EncodeToString(block.Data) + string(block.Nonce) + string(block.Timestamp) + hex.EncodeToString(block.PreHash) h := sha256.New() h.Write([]byte(blockInfo)) hashed := h.Sum(nil) block.Hash = hashed return hashed } |
PoS原理
Proof of Stack 权益证明
目的:解决PoW浪费资源的问题
典型产品:以太坊
原理:谁当矿工?根据结点的「总币龄」,计算成为本次矿工的概率
- 谁挖到币,不是由矿机性能决定的,而是「币量」、「币龄」而定
- 币量:持币的数量
- 币龄:当前时间点 减去 币的上一次变动(交易,挖到矿等)时间点
举例:
- 每个币每天产生1币龄,比如你持有100个币,总共持有了30天,那么,此时你的币龄就为3000
- 这个时候,如果你发现了一个PoS区块,你的币龄就会被清空为0
- 你每被清空365币龄,就会从区块中获得0.05个币的利息
优势:
- 不需要浪费算力
- 进行51%攻击的代价更高(想要进行51%攻击的话,需要拥有51%的货币)
- 这东西越值钱,攻击的成本就越高
- 这东西越值钱,攻击的成本就越高
缺点:马太效应
注意:
- 不是去中心化的
- 举例:星云链、以太坊(公链,用Solidity做开发),都是有中央服务器的
- 楼下代码片段的简易PoS中,未涉及「转账功能」
- 转账功能系统:UTXO - 未花费模式
- 区块链交易中的重要部分
代码片段
//创建区块 type Block struct { Index int Timestamp string Prehash string Hash string Data int //矿工终端地址 Validator string } //创建conn终端连接的数组 var connAddr []net.Conn //创建节点类型 type Node struct { //终端的地址 Address string //币领 Coins int } // 保存终端的对象 var nodes []Node //通道实现线程通信 var announcements = make(chan string) //生成新区块 var Blockchain []Block func main() { //区块链中是否有三个区块 genesisBlock := genesisBlock() go func() { //通过矿工实现区块的挖矿 for { //此代码会将for循环卡死, w := <- announcements //将新的区块,利用w旷工,添加到数组中 generateNextBlock(genesisBlock, 100, w) } }() go func() { //每隔开10S选择一次矿工 for { time.Sleep(10 * time.Second) winner := pickWinner() fmt.Println("系统通过PoS帮您选出的旷工为", winner) //将旷工放入到通到中 announcements <- winner } }() //如何通过终端链接代码上 netListen, _ := net.Listen("tcp", "127.0.0.1:1234") defer netListen.Close() //3,等待连接 for { conn, _ := netListen.Accept() //将所有的链接保存到数组 connAddr = append(connAddr, conn) //扫描终端 scanbalance := bufio.NewScanner(conn) io.WriteString(conn, "请输入币龄") go addNode(scanbalance, &nodes) } } func addNode(scanbalance *bufio.Scanner, nodes *[]Node) { for scanbalance.Scan() { txt := scanbalance.Text() //打印终端输入的信息 fmt.Println("您刚才从终端输入的币龄为:", txt) //通过时间戳创建地址 addr := calculateHash(time.Now().String()) cons, _ := strconv.Atoi(txt) node := Node{addr, cons} // 将链接终端对象存放到数组 - 协程不安全,建议用channel 或 加锁 *nodes = append(*nodes, node) fmt.Println(nodes) } } //计算某个字符串的hash func calculateHash(record string) string { h := sha256.New() h.Write([]byte(record)) hashed := h.Sum(nil) return hex.EncodeToString(hashed) } //计算block的hash func calculateBlockHash(block Block) string { record := block.Timestamp + string(block.Data) + block.Prehash + string(block.Index) hashCode := calculateHash(record) return hashCode } func generateNextBlock(oldBlock Block, data int, vald string) Block { var newBlock Block //设置区块高度 newBlock.Index = oldBlock.Index + 1 newBlock.Timestamp = time.Now().String() newBlock.Prehash = oldBlock.Hash newBlock.Data = data newBlock.Hash = calculateBlockHash(newBlock) newBlock.Validator = vald //添加到区块链 Blockchain = append(Blockchain, newBlock) return newBlock } //创建创世区块 func genesisBlock() Block { var genesisBlock = Block{0, time.Now().String(), "", "", 0, ""} //计算genesisBlock的hash值 genesisBlock.Hash = calculateBlockHash(genesisBlock) //将创世区块添加到数组 Blockchain = append(Blockchain, genesisBlock) return genesisBlock } //通过PoS共识算法选择旷工 func pickWinner() string { //选择旷工,利用PoS共识算法选择旷工 var lottyPool []string //根据币领把对应的旷工地址,存放到数组中 for i := 0; i < len(nodes); i++ { node := nodes[i] for j := 0; j < node.Coins; j++ { lottyPool = append(lottyPool, node.Address) } } if len(lottyPool) != 0 { //通过随机值,找到准备挖矿的旷工 rand.Seed(time.Now().Unix()) r := rand.Intn(len(lottyPool)) workerAddress := lottyPool[r] //返回旷工地址 return workerAddress } return "" } |
DPoS原理
Delegated Proof of Stake 委托权益证明
目的:解决PoS存在的「马太效应」问题
原理:让每一个人选出可以代表自己利益的人参与到获取区块的争夺中,这样多个小股东就能够通过投票选出自己的代理人,争取自己的利益
- 每一个参与者都能够选举任意数量的节点生成下一个区块
- 得票最多的前 M 个节点会被选择成为区块的创建者
- M的数量也是由整个网络投票决定的,可以尽可能地保证网络的去中心化
- 随机因素:M数量不确定,M中选取哪一个也不确定,再加上足够多的总量,更大程度上确保了不可篡改性
代码片段
//实现DPoS原理 var Blockchain []*Block //选举 type Node struct { Name string //节点名字 Votes int // 被选举的票数 } type Block struct { Index int Timestamp string Prehash string Hash string Data int //增加代理 delegate *Node } //创建数组,保存所有的节点 var n = make([]*Node, 5) func main() { //创建所有选民 mockNodes() c := sortNodes() //下边所有的挖矿均有这三个主节点完成,三个人轮流挖矿 g := genesisBlock() //创建新区块 newBlock := generateNextBlock(g, 1) //模拟挖矿 - 最好使用「队列」 newBlock.setDelete(c[0]) newBlock = generateNextBlock(*newBlock, 2) newBlock.setDelete(c[1]) newBlock = generateNextBlock(*newBlock, 3) newBlock.setDelete(c[2]) newBlock = generateNextBlock(*newBlock, 4) newBlock.setDelete(c[0]) } //创建节点 func mockNodes() { //创建随机种子 rand.Seed(time.Now().Unix()) node1 := Node{"node1", rand.Intn(10)} node2 := Node{"node2", rand.Intn(10)} node3 := Node{"node3", rand.Intn(10)} node4 := Node{"node4", rand.Intn(10)} node5 := Node{"node5", rand.Intn(10)} n[0] = &node1 n[1] = &node2 n[2] = &node3 n[3] = &node4 n[4] = &node5 } //DPoS中选出票数最高的前n位 func sortNodes() []*Node { //对所有选民的票数进行排序 for i := 0; i < 4; i++ { for j := 0; j < 4-i; j++ { if n[j].Votes < n[j+1].Votes { //二者的位置交换 t := n[j] n[j] = n[j+1] n[j+1] = t } } } return n[:3] } func generateNextBlock(oldBlock Block, data int) *Block { var newBlock = Block{oldBlock.Index + 1, time.Now().String(), oldBlock.Hash, "", data, &Node{"", 0}} calculateHash(&newBlock) //将新的区块添加到数组 Blockchain = append(Blockchain, &newBlock) return &newBlock } func calculateHash(block *Block) { record := strconv.Itoa(block.Index) + strconv.Itoa(block.Data) + block.Timestamp + block.Prehash h := sha256.New() h.Write([]byte(record)) hashed := h.Sum(nil) block.Hash = hex.EncodeToString(hashed) } func (block *Block) setDelete(node *Node) { block.delegate = node } //创世区块 func genesisBlock() Block { genesis := Block{0, time.Now().String(), "", "", 0, &Node{"", 0}} calculateHash(&genesis) Blockchain = append(Blockchain, &genesis) return genesis } |
0x2 密码学补充
零知识证明
让别人知道你有某个能力,但不能告诉他你是怎么做到的
举例
A拥有B的公钥,A没有见过B,而B见过A的照片,偶然一天2人见面了,B认出了A,但A不能确定面前的人是否是B,这时B要向A证明自己是B,有2个方法:
(一)B把自己的私钥给A,A用这个私钥对某个数据加密,然后用B的公钥解密,如果正确,则证明对方确实是B。
(二)A给出一个随机值,B用自己的私钥对其签名,然后把加密后的数据交给A,A用B的公钥验签,如果能够得到原来的随机值,则证明对方是B
方法(二)属于零知识证明
思考
如何让一个盲人相信我给他的两个相同的求是不同颜色的球
应用
区块链中的「零知识证明」原理:数字签名和验签的过程,就是零知识证明的过程
PKI
证书:用公钥生成证书,证书由CA颁发
- 格式为certificate类型
- 证书能证明,这个公钥属于你本人
PKI:是一个总称,公钥基础设施