bitcoin

使用Golang实现区块链和比特币原型

区块链

区块链概念是由Satoshi Nakamoto(没有正式中文名,一般翻译为 中本聪)在2008年中提出的,目的是实现点对点的电子现金系统——比特币(Bitcoin)。

区块链的理念和P2P(Peer to Peer)是有本质区别的,尽管两者都有去中心化的概念,但是两者解决不同维度的问题。区块链是解决共识问题,P2P是解决资源共享。

区块链应具有以下三个核心能力:去中心化、防篡改、可追溯。

  • 去中心化:整个区块链服务没有中心节点,意味着整个服务无需访问第三方机构,在达成共识后,可以直接点对点的对接,具有一定的公平、公开的特点。但对外界来说,整个服务中的每个参与节点都是“中心”,同时也构成了一个“中心”。因此,是否真的是去中心化,还是半去中心化、伪去中心化要看提交者所部署的位置以及职责。
  • 防篡改:主要指的是已存储的区块防止被非法修改,通过非对称加密等实现,但其实,在整个提交过程中,既要防止请求被篡改,也要防止非法消息的提交。
  • 可追溯:依靠区块链的链式存储结构,每一次提交区块都是顺序执行。因此可以通过链式结构追溯到任意一次提交的信息,不用担心任何一个参与者逾期抵赖。

显然,区块链是由区块组成的链条,区块链最基本的单元就是区块,一个区块(Block)它的结构如下:

// 区块
type Block struct {
    Timestamp     int64 //时间戳
    Data          []byte // 数据
    Hash          []byte // 当前区块的hash
    PrevBlockHash []byte // 前一区块的hash
}

区块链结构如下:

// 区块链
type Blockchain struct {
    blocks []*Block //区块列表
}

// 添加区块
func (bc *Blockchain) AddBlock(data string) {
    prevBlock := bc.blocks[len(bc.blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.blocks = append(bc.blocks, newBlock)
}

由此可见,新增一个block的前提是前一个block已经存在,但是一开始blockchain中并没有任何block。因此,在任何blockchain中都必须有一个特殊的初始block存在,称之为GenesisBlock, Genesis在英文中,是创世纪的意思,GenesisBlock即为创世区块,它是第一个区块,它没有前序区块。

区块链的图示如下:
[ Genesis Block ] <— [ Block #1 ] <— [ Block #2 ]

工作量证明

到目前为止,一个简单地区块链已经完成,但是添加一个区块实在太容易了,只需调用AddBlock()即可,任何内容都能轻易添加到区块链中,这显然与设计初衷不符。

现实世界中,煤矿工人(矿工)需要努力挖出符合质量标准的煤炭才能获得回报;区块链中也是一样,矿工新增一个区块需要有一定的难度,而且新增的区块需要符合一定的条件才能加入到区块链中。

上述机制被称作“工作量证明”(Proof of work,即PoW)。PoW需要大量运算,这使得即使是高性能计算机也不能很快的完成计算。此外,随着时间的推移,难度也相应有所增加。这种难度是有好处的,它使得对整个区块链网络的篡改成为几乎不可能完成的任务,那需要消耗极大的算力。此外,PoW算法必须满足一个要求:虽然计算的难度很高,但计算结果需要很容易验证。

区块链中广泛使用Hash函数进行工作量证明
Hash函数(英语:Hash function)又称散列算法哈希算法,是一种从任何数据中创建小的数字“指纹”的方法,这个指纹通常很短小,常用随机字母和数字组成的字符串来代表。例如:e10adc3949ba59abbe56e057f20f883e

(上述 e10adc3949ba59abbe56e057f20f883e 是 字符串123456的md5值)

hash需要具备以下特性:

  • 从hash值中无法恢复出原始数据。(注意,hash不是加密,只是数据摘要)
  • 对于某个特定数据,每次hash结果必须是一致的
  • 输入数据即使只改变了一个字节,其hash值也会大不为相同

比特币使用 Hashcash 算法进行工作量证明,该算法一开始用于反垃圾邮件。该算法分为以下步骤:
1. 获取某种公开数据data(在反垃圾邮件场景下,使用收件人地址;在比特币中,使用block头信息)
2. 使用一个计数器counter,初始化为0
3. 计算data+counter的hash值
4. 检查hash值是否满足某种特殊要求
* 满足,结束
* 不满足,counter加1,然后重复3-4步骤

在hashcash最初实现中,hash值要满足“头20个bit全部为0”的特殊要求。
PoW的核心算法:

const targetBits = 24
const maxNonce = math.MaxInt64

type ProofOfWork struct {
    block  *Block
    target *big.Int
}

func NewProofOfWork(b *Block) *ProofOfWork {
    target := big.NewInt(1)
    target.Lsh(target, uint(256-targetBits))

    pow := &ProofOfWork{b, target}

    return pow
}


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
}

func (pow *ProofOfWork) Run() (int, []byte) {
    var hashInt big.Int
    var hash [32]byte
    nonce := 0

    fmt.Printf("Mining the block for \"%s\"\n", pow.block.Data)
    // 循环直到找出符合条件的hash
    for nonce < maxNonce {
        data := pow.prepareData(nonce)
        hash = sha256.Sum256(data)
        hashInt.SetBytes(hash[:])

        if hashInt.Cmp(pow.target) == -1 {
              fmt.Printf("\r%x", hash)
            break
        } else {
            nonce++
        }
    }
    fmt.Print("\n\n")

    return nonce, hash[:]
}

验证工作量证明(验证只需要计算一次Hash):

// 验证工作证明
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
}

比特币

比特币是一种基于去中心化,采用点对点网络与共识机制,开放源代码的,以区块链作为底层技术的加密货币,比特币由中本聪于2008年10月31日发表论文,2009年1月3日,创世区块诞生。在某些国家、央行、政府机关则将比特币视为虚拟商品,而不认为是货币。 任何人皆可参与比特币活动,可以通过称为挖矿的电脑运算来发行。

交易

交易是比特币的核心,而blockchain的唯一目的就是安全可靠地存储交易信息,确保创建交易后,没人可以修改该交易信息。

在Web应用一般需要创建如下数据表用于实现支付逻辑:账户表(accounts)、交易表(transactions)。比特币的实现则完全不同:
1. 没有账户
2. 没有余额
3. 没有住址信息
4. 没有付款人、收款人信息
区块链是完全开放、公开的,它不希望存储任何用户敏感信息。交易时也不会将钱从一个账户转到另一个账户,也不存储任何账户余额信息。仅有的就是交易信息(Transaction)。

一个交易信息(Transaction)中包含多个输入和输出:

type Transaction struct {
    ID   []byte
    Vin  []TXInput
    Vout []TXOutput
}

注意:
1. 有些TXO没有与任何其他TXI相关联
2. 一个TXI可以引用之前的多个TXO
3. 一个TXI必须引用至少一个TXO

TXI中存储了阈值关联的交易id,及引用的TXOUT索引信息

type TXInput struct {
    Txid      []byte
    Vout      int
    ScriptSig string
}

TXO存储交易金额信息

type TXOutput struct {
    Value        int
    ScriptPubKey string
}

也就是说,每一笔交易中,总是包含了使用了之前的哪些钱,用完后,剩下多少钱等信息。而这些剩下的钱,可以在下一次交易时被使用。

在比特币中,value 字段存储的是 satoshi 的数量,而不是 BTC 的数量。一个 satoshi 等于一亿分之一的 BTC(0.00000001 BTC),这也是比特币里面最小的货币单位(就像是 1 分的硬币)。

注意:每一个TXO都必须作为一个整体使用,是不可分割的,不能只使用该TXO的一部分。这和现实世界是一样的:用10块钱购买价值1块钱的商品,获得商品的同时还会获得9元找零;而不是把10块钱撕开成1/10和9/10两截来使用,这是不允许的。

比特币中,由于每一笔交易都需要使用之前产生的TXO,这使得TXI关联TXO的逻辑也存在“先有鸡还是先有蛋”的矛盾。但是在比特币的世界,是先有蛋(TXO)后有鸡(TXI)的,TXO会先于TXI出现。这是因为当blockchain的新block(例如genesis block)被挖到后,会生成一个coinbase交易。coinbase交易是一种特殊的交易,该TXI不会引用任何TXO,而会直接生成一个TXO,作为奖励给矿工。(coinbase这个词已成为美国一家虚拟货币交易所名称,2021年4月14日已在纳斯达克直接挂牌上市)

在比特币中,新区块奖励值是基于总block数量计算得到的。中本聪最初挖出genesis block的奖励为50 BTC,此后每挖出210000个block,奖励值都会减半。

查询余额:
由于比特币区块链中存储的只有交易信息,并没有直接存储余额,所以,要获取余额必须遍历整个区块链,找出未被消费的TXO(unspent transaction outputs ,简称UTXO),汇总后才能得到余额。

转账:
1、查询自身UTXO
2、引用自身UTXO创建新交易,新交易中包含属于目标账户的TXO(如果交易还能获得找零的话,也会包含找零TXO)

func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
    var inputs []TXInput
    var outputs []TXOutput

    acc, validOutputs := bc.FindSpendableOutputs(from, amount)

    if acc < amount {
        log.Panic("ERROR: Not enough funds")
    }

    // Build a list of inputs
    for txid, outs := range validOutputs {
        txID, err := hex.DecodeString(txid)

        for _, out := range outs {
            input := TXInput{txID, out, from}
            inputs = append(inputs, input)
        }
    }

    // Build a list of outputs
    outputs = append(outputs, TXOutput{amount, to})
    if acc > amount {
        outputs = append(outputs, TXOutput{acc - amount, from}) // a change
    }

    tx := Transaction{nil, inputs, outputs}
    tx.SetID()

    return &tx
}

比特币地址和钱包:
一个比特币地址的例子: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa 。这是世界上首个比特币地址,据说属于比特币发明人中本聪。比特币地址是公开的,如果你想转给某人一些BTC,那么就需要知道其地址。这些地址并不代表钱包,它仅仅是具备可读格式的公钥。在比特币世界中,你的ID是一个密钥对(公/私钥),该密钥对需要保存在你的电脑或者其他你可以直接存取到它的设备上。比特币通过密码学算法来创建密钥对,从而保证在不能直接存取该密钥情况下,没人可以动你的钱。

比特币地址生成算法:

由于生成密钥对和比特币地址完全是离线的,所以不需要连接到比特币网络进行注册就可以生成一个合法的比特币地址。

钱包结构如下:

const version = byte(0x00)
const addressChecksumLen = 4

// 钱包
type Wallet struct {
    PrivateKey ecdsa.PrivateKey
    PublicKey []byte
}

// 实例化钱包
func NewWallet() *Wallet {
    private, public := newKeyPair()
    wallet := Wallet{private, public}

    return &wallet
}

// 获取钱包地址(地址包括3个部分: version + pubkey hash + checksum)
func (w Wallet) GetAddress() []byte {
    pubKeyHash := HashPubKey(w.PublicKey)
    versionedPayload := append([]byte{version}, pubKeyHash...)
    checksum := checksum(versionedPayload)
    fullPayload := append(versionedPayload, checksum...)
    address := base58.Encode(fullPayload)
    return []byte(address)
}

// 计算pubkeyhash(先sha256, 再ripemd160)
func HashPubKey(pubKey []byte) []byte {
    publicSHA256 := sha256.Sum256(pubKey)
    RIPEMD160Hasher := ripemd160.New()
    RIPEMD160Hasher.Write(publicSHA256[:])
    publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
    return publicRIPEMD160
}

// 计算checksum
func checksum(payload []byte) []byte {
    firstSHA := sha256.Sum256(payload)
    secondSHA := sha256.Sum256(firstSHA[:])

    return secondSHA[:addressChecksumLen]
}

// 创建密钥对
func newKeyPair() (ecdsa.PrivateKey, []byte) {
    curve := elliptic.P256()
    // 使用椭圆曲线函数生成私钥
    privateKey, _ := ecdsa.GenerateKey(curve, rand.Reader)
    // 椭圆曲线点集合即是公钥
    pubKey := append(privateKey.PublicKey.X.Bytes(), privateKey.PublicKey.Y.Bytes()...)
    return *privateKey, pubKey
}

比特币使用椭圆曲线算法生成私钥,算法比较复杂。我们所需要知道的就是这些曲线可以生成非常大的随机数,具体有多大呢?比特币所使用的曲线算法将从0到2^256(大概是10^77左右,而可观测宇宙内大约只有10^78到10^82个原子)间随机选择一个数,这样大的一个上限使得同一私钥被生成两次的可能性几乎为0


优化:
截止2021/5/1,比特币区块链整个数据库大小超过340GB,最近1年增大了140%,对数据库中的所有block进行遍历,会非常低效!
比特币区块链做了一些优化:
1、缓存UTXO集合,单独存储起来
2、使用默克尔树快速校验交易


(默克尔树是一种特殊的树,它的任一父节点等于左右子节点数据相加后哈希的结果。这使得验证子节点是否在树中非常高效,如上图,验证Transaction 3是否在树中,只需下载并验证红框中的几个节点即可。)

分布式:
区块链网络是一个P2P网络:网络中各节点之间直接连接,不存在任何层级结构,拓扑结构示意图如下:


区块链网络节点需要同其他节点进行交互,获取其他节点的状态并维护状态信息

1、比特币客户端发布时,会硬编码一些DNS服务器
2、客户端运行过程中,会维护一个活跃节点列表
3、节点间数据共享、消息广播

其他概念及问题:
ICO:首次代币发行 (Initial coin offering), 该词是从证券界的IPO(Initial public offering),即首次公开募股一词演变而来的。是用 区块链 把使用权和 加密货币 合二为一,来为开发、维护、交换相关产品或者服务的项目进行 融资 的方式。
分叉(fork):比特币的分支,如比特币现金、比特币黄金等
若同时挖到新区块如何处理:继续挖下一个后继区块,先挖到的分支保留,其他分支废弃

blockchain in Go Building Blockchain in Go. Part 1: Basic Prototype – Going the distance

共识算法 POW POS DPOS
这可能是全网最简单的POS共识机制算法 – 知乎
工作量证明(Proof Of Work)
权益证明(Proof Of Stake)

比特币余额和交易查询
https://www.blockchain.com/btc/address/1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm

比特币钱包金额分布

https://bitinfocharts.com/top-100-richest-bitcoin-addresses.html
%1 $ S

发表回复