好久没学区块链了。。。重新来波入门

我的项目地址:https://github.com/xuanxianyou/BlockChain(仅供未来我修改使用,建议参考下述资料)

参考资料地址:下述资料是一脉相承的,代码与视频资源基本吻合

视频资源:

       慕课网:https://www.imooc.com/learn/1021 (快速)

       学习猿地:https://www.lmonkey.com/courses/qpBa1eBJr/plans/oZBdXWBp9/chapter(详细)

代码资源:

       资料源码:https://github.com/pengjim520golang/blockchain-tutorial(全面)

钱包

 钱包又是Address,代表了你在区块链中的身份,是你在区块链网络中的标识。钱包是由一对公私密钥(PublicKey,PriavteKey)组成,公钥相当于你的账号,密钥相当于密码

钱包的结构:

//wallet中包含公钥和私钥
type Wallet struct{
	//私钥
	PrivateKey ecdsa.PrivateKey
	//公钥(该属性可以从私钥中获取)
	PublicKey []byte
}

生成公私钥对:

//基于椭圆曲线算法
func newPrivAndPub()(ecdsa.PrivateKey,[]byte){
	//声明p-256曲线
	p256curve := elliptic.P256()
	//使用p256曲线生成私钥
	privKey,err := ecdsa.GenerateKey(p256curve,rand.Reader)
	if err != nil {
		log.Panic(err)
	}
	//在私钥获取公钥
	pubKey := append(privKey.PublicKey.X.Bytes(),
		privKey.PublicKey.Y.Bytes()...)
	return *privKey,pubKey
}

生成地址(Address)

func (wallet *Wallet) GetAddress() []byte{
	//对公钥进行Ripemd160
	pubkey160 := HashPubKey(wallet.PublicKey)
	//组合version + pubkey160
	payload := append([]byte{version},pubkey160...)
	//取其前4个字节
	checkSum := checksum(payload)
	//组合version + pubkey160 + checksum
	fullPayload := append(payload,checkSum...)
	//进行base58编码
	return Base58Encode(fullPayload)
}

剩余的便不赘述,Talk is cheap,show me the code

区块和区块链

区块的结构:

type Block struct {
	Timestamp int64         //区块创建的时间戳
	Transactions []*Transaction//区块中的数据,Transaction作为数据
	PreBlockHash []byte     //前区块哈希
	CurBlockHash []byte     //当前区块哈希
	Nonce int               //工作量算法证明的计数器
}

当然这个区块结构还是还是太简单了,应该是分为Block Header和Block Body,这是我最初的想法,但是没实现,以后再实现吧。

区块链的结构:

type BlockChain struct {
	Tip []byte        //最新区块的哈希
	DB *bolt.DB       //数据库持久化存储
}

这里采用了bolt存储,本来想要用mongoDB或CouchDB实现的,这里也记录以下,以后实现吧。

工作量证明(POW)

工作量证明(Proof of Work)就是计算,计算出满足条件的hash值。这个过程是非常枯燥的,由于密码学中hash的puzzle friendly性质,使得挖矿没有捷径,必须通过大量的计算,试探随机数nonce的值来计算出满足条件的hash,如果想深入了解这里的知识,可以看一下北京大学肖臻老师的区块链公开课《区块链技术与应用》

const targetBits = 16     //设定0的个数,表示计算的难度

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

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

	fmt.Printf("Mining the block containing \"%v\"\n ",pow.block.Transactions)

	for nonce<maxNonce{
			data:=pow.generateData(nonce)
			hash=sha256.Sum256(data)              //进行哈希运算
			fmt.Printf("\r%x",hash)
			hashInt.SetBytes(hash[:])             //设置哈希整数

			if hashInt.Cmp(pow.target)==-1{       //哈希整数与pow的目标大数进行对比
				break
			}else {
				nonce++
			}
	}
	fmt.Println("\n\n")
	return nonce,hash[:]
}

交易和UTXO

交易:

交易相当于一次转账的过程,交易的输入是交易的数组,输出是交易的结果

type Transaction struct{
	ID []byte            //交易ID
	Vin []TXInput        //交易的输入
	Vout []TXOutput      //交易的输出
}


type TXInput struct {
	TXid  []byte         //输入交易的ID
	Vout int             //输出
	ScriptSignature string //签名,对输入的交易进行合法性验证

}

type TXOutput struct {
	Value int               //交易的结果,转账
	ScriptPublicKey string  //输出的签名
}

链上的交易包括发行货币(挖矿所得的报酬)和用户之间的转账交易

发行货币:

func NewCoinBaseTX(to string,data string)*Transaction{
	if data==""{
		data= fmt.Sprintf("Reward to '%s'", to)
	}
	txin:=TXInput{[]byte{},-1,data}              //发行货币输入交易ID为空,
	txout:=TXOutput{subsidy,to}					      //发行货币输出Value为10币奖励
	tx:=Transaction{nil,[]TXInput{txin},[]TXOutput{txout}}  //生成交易
	tx.SetID()                                                            //设置交易ID
	return &tx
}

UTXO(Unspent Transaction Output)未消费的交易输出

百度百科:

现实世界中没有比特币,只有UTXO。

比特币的交易由交易输入和交易输出组成,每一笔交易都要花费(spend)一笔输入,产生一笔输出(output),而其所产生的输出,就是“未花费过的交易输出”,也就是 UTXO。

//从所有的交易输出中找到未被花掉的输出,然后将其币值累加,如果足够支付转账就break,如果不够则交易失败
func NewUTXOTransaction(from string,to string,account int,bc *BlockChain) *Transaction{
	var inputs []TXInput
	var outputs []TXOutput

	wallets,err:=NewWallets()
	if err!=nil{
		log.Panic(err)
	}
	wallet:=wallets.GetWallet(from)
	pubKeyHash:=HashPubKey(wallet.PublicKey)
	acc,validOutputs:=bc.FindSpendableOutputs(string(pubKeyHash),account) //查账,查看有效余额
	if acc<account{
		log.Panic("Error:You don`t have enough money. Transaction failed..." )
	}
	//根据所有的输出,进行扣除,进而构建本次交易的输入
	for txid,outs:=range validOutputs{
		txID,err:=hex.DecodeString(txid)
		if err!=nil{
			log.Panic(err)
		}
		for _,out:=range outs{                                //
			input:=TXInput{txID,out,from}
			inputs=append(inputs,input)
		}

	}
	//创建本次交易的输出,
	outputs=append(outputs,TXOutput{account,to})   //添加交易输出
	//找零
	if acc > account{
		outputs=append(outputs,TXOutput{acc-account,from})  //交易输出,找零
	}
	//构建本次交易
	tx:=Transaction{nil,inputs,outputs}
	tx.SetID()
	return &tx
}

Merkle Tree

Merkle Tree是一个hash树

每个区块都会有一个 Merkle 树,它从叶子节点(树的底部)开始,一个叶子节点就是一个交易哈希(比特币使用双 SHA256 哈希)。叶子节点的数量必须是双数,但是并非每个块都包含了双数的交易。因为,如果一个块里面的交易数为单数,那么就将最后一个叶子节点(也就是 Merkle 树的最后一个交易,不是区块的最后一笔交易)复制一份凑成双数。

从下往上,两两成对,连接两个节点哈希,将组合哈希作为新的哈希。新的哈希就成为新的树节点。重复该过程,直到仅有一个节点,也就是树根。根哈希然后就会当做是整个块交易的唯一标示,将它保存到区块头,然后用于工作量证明。

Merkle 树的好处就是一个节点可以在不下载整个块的情况下,验证是否包含某笔交易。并且这些只需要一个交易哈希,一个 Merkle 树根哈希和一个 Merkle 路径。

数字签名

交易必须被签名,因为这是比特币里面保证发送方不会花费属于其他人的币的唯一方式。如果一个签名是无效的,那么这笔交易就会被认为是无效的,因此,这笔交易也就无法被加到区块链中。

在UTXO中,如果你向李四发送2个BTC,你就必须要消耗之前交易中1笔或者多笔的输出,作为李四的UTXO交易的实例,该实例的输入肯定引用了你的输出。但是凭什么保证这些一定是来自于你的消费呢?这就需要使用数字签名。

实现数字签名需要完成如下工作:

  • 能实现对当前交易的副本复制

  • 能在区块链中根据交易ID找到对应的引用交易

  • 必须使用私钥+交易副本的ID进行签名

  • 产生交易时必须对交易进行签名

  • 挖矿时必须验证签名

数字签名需要使用私钥,验证签名必须使用公钥。而公钥本身来自与私钥。

区块同步

            P2P网络

            节点发现

            区块同步

这里还没学,只了解了个大概,寒假再说吧,又要尘封了

唉,这个博客写的太糙了,如有错误,敬请指正。

 

Logo

一站式 AI 云服务平台

更多推荐