惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
V
Visual Studio Blog
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
博客园 - 【当耐特】
大猫的无限游戏
大猫的无限游戏
Application and Cybersecurity Blog
Application and Cybersecurity Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
The Cloudflare Blog
B
Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Apple Machine Learning Research
Apple Machine Learning Research
M
MIT News - Artificial intelligence
Know Your Adversary
Know Your Adversary
I
InfoQ
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
C
Cisco Blogs
Spread Privacy
Spread Privacy
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Palo Alto Networks Blog
Simon Willison's Weblog
Simon Willison's Weblog
月光博客
月光博客
博客园 - Franky
Project Zero
Project Zero
G
Google Developers Blog
S
SegmentFault 最新的问题
博客园 - 聂微东
P
Privacy & Cybersecurity Law Blog
The GitHub Blog
The GitHub Blog
阮一峰的网络日志
阮一峰的网络日志
P
Privacy International News Feed
T
Threat Research - Cisco Blogs
S
Schneier on Security
Microsoft Security Blog
Microsoft Security Blog
G
GRAHAM CLULEY
S
Security @ Cisco Blogs
Martin Fowler
Martin Fowler
A
Arctic Wolf
T
Tenable Blog
L
LINUX DO - 最新话题
TaoSecurity Blog
TaoSecurity Blog
Hugging Face - Blog
Hugging Face - Blog
有赞技术团队
有赞技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

虞双齐的博客

共识机制PoW-真的是去中心化的吗 共识算法PoW之算力与挖矿难度 共识算法PoW之由来 理解以太坊Gas和手续费 理解与计算比特币难度值Difficulty 程序员该掌握区块链技术 Docker使用问题集 区块链比特币学习路线与资源 Mac下搭建以太坊私有链 技术大牛成长 做爱折腾的技术人 如何发现自我优势成为斜杠青年 Golang Generate命令说明与使用 Go读取通达信历史日线数据 希腊八日游游记 Go面试题答案与解析 你误会蟑螂了 如何提高邮件件打开率和回复率 简述电子邮件发展历史
解析比特币区块
虞双齐 · 2017-12-12 · via 虞双齐的博客

在解析前,需要准备比特币区块数据。可在安装比特币QT客户端后,到指定目录下获取。注意不需要全部同步完成,只需要开启同步后,有第一个区块数据即可。文件路径,分别是:

  • Mac: $HOME/Library/Application Support/Bitcoin/blocks/blkXXXX.dat
  • Windows: %APPDATA%/Bitcoin/blocks/blkXXXX.dat

比特币钱包

通过Go代码的方式,一步一步讲解比特币区块的解析逻辑。 下图是比特币区块数据结构,主要由区块头和交易清单组成。 比特币区块结构

name字节用途
神奇数magic4固定为0xD9B4BEF9,作为区块间的分隔符
区块大小block_size4记录当前区块的大小
区块头信息header80记录当前区块的头部信息
交易总数tx_count1-9当前区块所含记录数
交易清单tx_list*记录当前区块交易细节

其中,神奇数固定为0xD9B4BEF9,作为区块的分隔标识符。而比特币的交易数据是以128Mb为上限记录在dat文件中的。利用此神奇数和区块大小,可将区块数据读取。

先打开文件blk00001.bat

f, err := os.Open(name)
if err != nil {
   log.Fatal(err)
}
defer f.Close() 

再按分隔符读取区块数据:

for {
   mg := ReadUint32(f)
   if mg != MARGIC { // MARGIC uint32 = 0xd9b4bef9
     Failt(fmt.Errorf("expect margic %x,not %x", margic, mg))
   }
   //get block size
   blockSize := ReadUint32(f)
   //get block bytes
   blockData := ReadBytes(f, uint64(blockSize))
}

区块头信息

区块头是重要的信息,保障了区块内容的不可篡改。因此一些轻钱包,可不下载完整的区块,而只需获取区块头,便可验证交易。

字节用途
版本号4区块的版本号
上一区块头Hash32上一区块的 SHA256 Hash值,以确保上一区块无法修改,继续加强稳定性
Merkle树根值32交易Merkle树根节点Hash
时间戳4区块生成时间UNIX格式
目标值4本区块头Hash必须>=本值
随机数4一个使得本区块头Hash>=目标值的任意数。

区块头信息中记录上一个区块头HASH值,是对历史交易的再次确认。比特币中只有在6次确认后才交易稳定。

比特币区块链简图

那么此Hash是对头部所有内容经过两次SHA256加密获得,而在内容中含有交易记录的Merkle数根值,可以保障交易内容摘要也在此HASH范围内。注意每个区块的HASH并不存储在当前区块中,其他区块要引用此区块时需对此区块头进行HASH计算。

// head
head := decodeHeader(r)
head.Hash = DoubleHashH(blockData[:80]) //头部80个字节

func decodeHeader(r io.Reader) (h BlockHeader) {
    h.Version = ReadUint32(r)
    h.PrevBlock = ReadHash(r)
    h.MerkleRoot = ReadHash(r)
    h.Timestamp = time.Unix(int64(ReadUint32(r)), 0)
    h.Bits = ReadUint32(r)
    h.Nonce = ReadUint32(r)
    return
}

此处的Merkle树便是一个Hash二叉树。本区块中所有交易,按时间顺序将单笔交易的HASH值(TXID)两两组成一个二叉树的叶子,根便是最终的Hash值。 通过对每笔交易进行HASH签名,构成唯一的根HASH存放在区块头中,以确保每笔交易不可伪造,不可重复。

交易记录

交易记录便是整个账簿的一页内容,详细记录当下所有比特币交易信息。每个比特币账户下的收支被永久的记录在区块中。

在解析交易记录前,需要获取总记录数,这个总记录数是一个不固定长度数值。

// transaction                         
txCount := ReadVarInt(r)               
txs := make([]*MsgTx, txCount)         
for i := uint64(0); i < txCount; i++ { 
    txs[i] = decoderTX(r)              
}                                     
name字节用途
版本version4比特币协议版本
输入数tx_in_count1+区块中所有输入记录数
输入明细tx_in40+区块中所有支出明细
输出数tx_out_count1+区块中所有输出记录数
输出明细tx_out40+区块中所有输出明细
交易时间戳local_time4交易被网络确认的UNIX时间

其中,支出和接收明细是衔接在一块,拥有相同的数据结构。每一单笔收支明细都有自己的编号以供查询。

msg := &MsgTx{}
msg.Version = ReadUint32(r)
txInCount := ReadVarInt(r)
msg.TxIn = make([]*TxIn, txInCount)
for i := uint64(0); i < txInCount; i++ {
    //input
}
txOutCount := ReadVarInt(r)
msg.TxOut = make([]*TxOut, txOutCount)
for i := uint64(0); i < txOutCount; i++ {
    //output
}
msg.LockTime = ReadUint32(r)

输出和输入明细稍有不同,输入:

name字节用途
输出记录Hashhash32本输入资金来源的输出记录HASH
脚本长度length1+交易脚本可变长度值
脚本内容script+本输入的脚本内容,包含签名信息
Sequence4当前无用途的一个固定数值:0xFFFFFFFF
tx := TxIn{} 
// prev
tx.PreviousOutPoint.Hasx = ReadHash(r)
tx.PreviousOutPoint.Index = ReadUint32(r)

scriptLen := ReadVarInt(r)
tx.SignatureScript = ReadBytes(r, scriptLen)
tx.Sequence = ReadUint32(r)

输出信息仅仅包含两项信息,

name字节用途
金额value8接收方获得的金额,单位:聪
公钥脚本长度length1+交易脚本可变长度值
公钥脚本length接收方信息脚本
tx := TxOut{}                  
tx.Value = ReadUint64(r)                 
scriptLen := ReadVarInt(r)               
tx.PkScript = ReadBytes(r, scriptLen)   

区块完整数据结构

比特币区块数据以节约空间和简洁为前提,布局为一个区块头和交易明细结构。下图是完整的区块结构。

比特币区块数据结构

协助

实际在解析比特币区块时,为简化代码,提取了些公共方法。以方便错误处理和获取不同类型的数据,具体如下:

type Hash [HashSize]byte                                                  

const (                                                                   
    HashSize = 32                                                         
)                                                                         

// String returns the Hash as the hexadecimal string of the byte-reversed 
// hash.                                                                  
func (hash Hash) String() string {                                        
    for i := 0; i < HashSize/2; i++ {                                     
        hash[i], hash[HashSize-1-i] = hash[HashSize-1-i], hash[i]         
    }                                                                     
    return hex.EncodeToString(hash[:])                                    
}                                                                         
func DoubleHashH(b []byte) Hash {                                         
    first := sha256.Sum256(b)                                             
    return Hash(sha256.Sum256(first[:]))                                  
}                                                                         

func Failt(err error) {                                                   
    log.Output(2, err.Error())                                            
    os.Exit(1)                                                            
}                                                                        

常见的读取单项值方法:

func ReadHash(r io.Reader) Hash {                                              
    var h Hash                                                                 
    _, err := io.ReadFull(r, h[:])                                             
    if err != nil {                                                            
        Failt(err)                                                             
    }                                                                          
    return h                                                                   
}                                                                              
func ReadBytes(r io.Reader, size uint64) []byte {                              
    b := make([]byte, size)                                                    
    _, err := io.ReadFull(r, b)                                                
    if err != nil {                                                            
        Failt(err)                                                             
    }                                                                          
    return b                                                                   
}                                                                              
func ReadUint32(r io.Reader) uint32 {                                          
    var v uint32                                                               
    err := binary.Read(r, binary.LittleEndian, &v)                             
    if err != nil {                                                            
        Failt(err)                                                             
    }                                                                          
    return v                                                                   
}                                                                              

func ReadUint64(r io.Reader) uint64 {                                          
    var v uint64                                                               
    err := binary.Read(r, binary.LittleEndian, &v)                             
    if err != nil {                                                            
        Failt(err)                                                             
    }                                                                          
    return v                                                                   
}                                                                              

// ReadVarInt reads a variable length integer from r and returns it as a uint64
func ReadVarInt(r io.Reader) uint64 {                                          
    var discriminant uint8                                                     
    err := binary.Read(r, binary.LittleEndian, &discriminant)                  
    if err != nil {                                                            
        Failt(err)                                                             
    }                                                                          

    var rv uint64                                                              
    switch discriminant {                                                      
    case 0xff:                                                                 
        err = binary.Read(r, binary.LittleEndian, &rv)                         
        if err != nil {                                                        
            Failt(err)                                                         
        }                                                                      

        // The encoding is not canonical if the value could have been          
        // encoded using fewer bytes.                                          
        min := uint64(0x100000000)                                             
        if rv < min {                                                          
            Failt(fmt.Errorf("var int %x < min value %x", rv, min))            
        }                                                                      

    case 0xfe:                                                                 
        var v uint32                                                           
        err = binary.Read(r, binary.LittleEndian, &v)                          
        if err != nil {                                                        
            Failt(err)                                                         
        }                                                                      
        rv = uint64(v)                                                         

        // The encoding is not canonical if the value could have been          
        // encoded using fewer bytes.                                          
        min := uint64(0x10000)                                                 
        if rv < min {                                                          
            Failt(fmt.Errorf("var int %x < min value %x", rv, min))            
        }                                                                      

    case 0xfd:                                                                 
        var v uint16                                                           
        err = binary.Read(r, binary.LittleEndian, &v)                          
        if err != nil {                                                        
            Failt(err)                                                         
        }                                                                      
        rv = uint64(v)                                                         

        // The encoding is not canonical if the value could have been          
        // encoded using fewer bytes.                                          
        min := uint64(0xfd)                                                    
        if rv < min {                                                          
            Failt(fmt.Errorf("var int %x < min value %x", rv, min))            
        }                                                                      

    default:                                                                   
        rv = uint64(discriminant)                                              
    }                                                                          

    return rv                                                                  
}                                                                             

结尾

上文中还有许多内容未详细说明,如脚本是什么?Merkle树如何构建的?交易费如何计算?交易中的脚本的用途等。待后续一一写文。

本文完整源代码见:github gist