主页 > imtoken钱包苹果版下载官网 > 《区块链研究实验室》区块链技术之比特币交易地址(下)

《区块链研究实验室》区块链技术之比特币交易地址(下)

比特币的合约地址_比特币合约多空比在哪看_比特币合约单位

前言

在上一篇文章中,我们已经初步实现了事务。 相信你应该了解交易中的一些自然属性,这些属性没有丝毫的“个人”色彩:在比特币中,没有用户账户,也没有个人数据(如姓名、护照号码或 SSN)。 但是,我们必须始终通过某种方式将您识别为交易输出的所有者(也就是说,您拥有锁定在这些输出上的代币)。 这就是比特币地址需要做的。 在上一篇文章中,我们将用户定义的任意字符串视为一个地址,而现在我们要实现一个像比特币这样的真实地址。

比特币地址

这是一个真实的比特币地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。 这是历史上第一个比特币地址,据说属于中本聪。 比特币地址是完全公开的。 如果你想给某人发送硬币,你只需要知道他的地址。 但是,地址(虽然地址也是唯一的)并不是用来证明你是“钱包”所有者的令牌。 实际上,所谓地址无非是将公钥以人类可读的形式表达出来,因为原始的公钥是人类难以阅读的。 在比特币中,你的身份(identity)是一对(或多对)存储在你的计算机上(或你可以获得它们的地方)的公钥(public key)和私钥(private keys)。 比特币基于一些加密算法的组合创建这些密钥,并确保世界上没有其他人可以拿走你的硬币,除非他们得到你的密钥。 接下来,让我们讨论一下这些算法到底是什么。

公钥加密

公钥加密算法使用成对的密钥:公钥和私钥。 公钥不是敏感信息,可以与他人共享。 但是私钥绝对不能告诉别人:只有拥有者(owner)才能知道私钥,而能识别、认证和证明拥有者身份的是私钥。 在加密货币的世界里,你的私钥就是你的身份,而你的私钥就是一切。

本质上,比特币钱包无非就是这样的一对密钥。 当您安装钱包应用程序或使用比特币客户端生成新地址时,它会为您生成一对密钥。 在比特币中,谁拥有私钥,谁就控制了发送到该公钥的所有硬币。

私钥和公钥只不过是随机的字节序列,因此它们无法打印在屏幕上或被人眼读取。 这就是为什么比特币使用一种转换算法将公钥转换为人类可读的字符串(这就是我们所看到的地址)。

如果您使用过比特币钱包应用程序,它很可能会为您生成一个助记符。 这样的助记词可以用来代替私钥,可以用来生成私钥。 BIP-039 已经实现了这个机制。

好了,那么现在我们知道了在比特币中证明用户身份的是私钥。 那么,比特币如何检查交易输出(以及存储在其中的硬币)的所有权?

电子签名

在数学和密码学中,有一个数字签名的概念,该算法可以保证:

数据从发送方传输到接收方时没有被修改;

数据由确定的发件人创建;

发件人不能否认数据已发送的事实。

通过对数据应用签名算法(即对数据签名),您将获得一个签名,稍后会对其进行验证。 生成数字签名需要私钥,验证签名需要公钥。 签名有点类似于印章。 比如我画了一幅画比特币的合约地址,画完了盖上印章,就说明这幅画是我的作品。 为数据生成签名意味着对数据进行标记。

为了签署数据,我们需要两件事:

要签名的数据

私钥

应用签名算法可以生成一个签名,这个签名将存储在交易输入中。 为了验证签名,我们需要三样东西:

签名数据

符号

比特币合约多空比在哪看_比特币的合约地址_比特币合约单位

公钥

简单来说,验证过程可以描述为:检查签名是通过将私钥添加到签名数据中得到的,而公钥恰好是由私钥生成的。

数据签名不是加密,您不能从签名中重建数据。 这有点像散列:对数据运行散列算法,然后得到该数据的唯一表示。 签名和散列的区别在于密钥对:有了密钥对,才有签名验证。 但密钥对也可用于加密数据:私钥用于加密,公钥用于解密数据。 但是,比特币不使用加密算法。

在比特币中,每个交易输入都由创建交易的人签名。 每笔交易在被纳入区块之前都必须经过验证。 在其他步骤中,验证意味着:

检查交易输入是否有权使用以前交易的输出

检查交易签名是否正确

如图所示,签名数据和验证签名的过程大致如下:

比特币合约单位_比特币的合约地址_比特币合约多空比在哪看

现在我们来回顾一下一个事务的完整生命周期:

最初,创世块包含一个 coinbase 交易。 在 coinbase 交易中,没有输入,因此不需要签名。 coinbase 交易的输出包含一个哈希公钥(使用 RIPEMD16(SHA256(PubKey)) 算法)

当一个人发送硬币时,就会创建一个交易。 此交易的输入参考了先前交易的输出。 每个输入都存储一个公钥(未散列)和整个交易的签名。

比特币网络中接收交易的其他节点验证交易。 除其他外,他们检查: 在输入上,公钥哈希与引用的输出哈希匹配(这保证发送者只能花费自己的硬币); 签名是正确的(这保证交易是由硬币的实际所有者创建的)。

当矿工准备好挖掘新区块时,他将交易放入区块并开始挖掘。

当一个新区块被挖掘出来时,网络中的所有其他节点都会收到一条消息,告诉其他人该区块已被挖掘并添加到区块链中。

当一个块被添加到区块链时,交易就完成了,它的输出可以在新的交易中被引用。

椭圆曲线密码学

如前所述,公钥和私钥是随机的字节序列。 私钥可以用来证明持有者的身份,有一个条件:随机算法必须产生真正随机的字节。 因为没有人愿意生成一个不小心被别人拥有的私钥。

比特币使用椭圆曲线生成私钥。 椭圆曲线是一个复杂的数学概念,这里不做过多的解释(如果你真的很好奇比特币的合约地址,可以查看这篇文章,注意:数学公式很多!)我们只需要知道这些曲线可以生成非常大的随机数就够了。 比特币使用的曲线可以在0到2^2^56之间随机选择(大概是10^77,整个可见宇宙的原子数在10^78到10^82之间)。 拥有如此高的上限意味着几乎不可能两次生成相同的私钥。

比特币使用ECDSA(Elliptic Curve Digital Signature Algorithm)算法对交易进行签名,我们也会使用这个算法。

Base58

回到上面提到的比特币地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。 现在,我们知道这只是公钥的人类可读表示。 如果我们解码它,我们会看到它的公钥(以十六进制表示的字节):

比特币的合约地址_比特币合约单位_比特币合约多空比在哪看

0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93

比特币使用 Base58 算法将公钥转换为人类可读的形式。 该算法与著名的Base64非常相似,不同之处在于它使用了更短的字母表:为了避免一些利用字母相似性的攻击,从字母表中删除了一些字母。 也就是说,没有这样的符号:0(零)、O(大写的 o)、I(大写的 i)、l(小写的 L),因为这些字母看起来很相似。 此外,没有 + 和 / 符号。

下图是从公钥获取地址的过程:

比特币的合约地址_比特币合约单位_比特币合约多空比在哪看

因此,上面提到的公钥在解码时包含三部分:

Version Public key hash Checksum
00 62E907B15CBF27D5425399EBF6F0FB50EBB88F18 C29B7D93

由于散列函数是单向的(即不能反向),因此不可能从散列中提取公钥。 但是通过执行哈希函数并比较哈希值,我们可以检查是否使用了公钥来生成哈希值。

好了,所有的细节都准备好了,我们来写代码吧。 很多概念只有在写代码的时候才能理解得更透彻。

实施地址

让我们从钱包结构开始:

type Wallet struct {
 PrivateKey ecdsa.PrivateKey
 PublicKey []byte}type Wallets struct {
 Wallets map[string]*Wallet}func NewWallet() *Wallet {
 private, public := newKeyPair()
 wallet := Wallet{private, public}
 return &wallet}func newKeyPair() (ecdsa.PrivateKey, []byte) {
 curve := elliptic.P256()
 private, err := ecdsa.GenerateKey(curve, rand.Reader)
 pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)

比特币的合约地址_比特币合约多空比在哪看_比特币合约单位

return *private, pubKey}

一个钱包只有一对密钥。 我们需要 Wallets 类型来保存多个钱包的组合,将它们保存到文件中,或从文件中加载它们。 钱包的构造函数生成一个新的密钥对。 newKeyPair 函数非常直观:ECDSA 基于椭圆曲线,所以我们需要椭圆曲线。 接下来,使用椭圆生成私钥,然后从私钥生成公钥。 一个警告:在基于椭圆曲线的算法中,公钥是曲线上的一个点。 因此,公钥是X、Y坐标的组合。 在比特币中,这些坐标被连接起来形成一个公钥。

现在,生成一个地址:

func (w Wallet) GetAddress() []byte {
 pubKeyHash := HashPubKey(w.PublicKey)
 versionedPayload := append([]byte{version}, pubKeyHash...)
 checksum := checksum(versionedPayload)
 fullPayload := append(versionedPayload, checksum...)
 address := Base58Encode(fullPayload)
 return address}func HashPubKey(pubKey []byte) []byte {
 publicSHA256 := sha256.Sum256(pubKey)
 RIPEMD160Hasher := ripemd160.New()
 _, err := RIPEMD160Hasher.Write(publicSHA256[:])
 publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
 return publicRIPEMD160}func checksum(payload []byte) []byte {
 firstSHA := sha256.Sum256(payload)
 secondSHA := sha256.Sum256(firstSHA[:])
 return secondSHA[:addressChecksumLen]}

将公钥转换为 Base58 地址需要以下步骤:

使用 RIPEMD160(SHA256(PubKey)) 哈希算法,取出公钥并对其进行两次哈希

用地址生成算法的版本作为哈希前缀

比特币的合约地址_比特币合约单位_比特币合约多空比在哪看

对于第二步生成的结果,使用SHA256(SHA256(payload))重新哈希计算校验和。 校验和是结果散列的前四个字节。

将校验和附加到版本+PubKeyHash 的组合。

version+PubKeyHash+checksum 组合使用 Base58 编码。

此时,你可以获得一个真实的比特币地址,你甚至可以在 blockchain.info 上查看其余额。 但是我可以负责任的说,无论生成多少次新地址,查看其余额都是0。这就是为什么选择合适的公钥加密算法如此重要的原因:鉴于私钥是随机数,生成新地址的概率相同的数字必须尽可能低。 理想情况下,它必须足够低以“永不”重复。

另请注意:您无需连接到比特币节点即可获取地址。 地址生成算法使用的各种开源算法在很多编程语言和库中都有。

现在我们需要修改输入和输出以使用地址:

type TXInput struct {
 Txid []byte
 Vout int
 Signature []byte
 PubKey []byte}func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
 lockingHash := HashPubKey(in.PubKey)
 return bytes.Compare(lockingHash, pubKeyHash) == 0}type TXOutput struct {
 Value int
 PubKeyHash []byte}func (out *TXOutput) Lock(address []byte) {
 pubKeyHash := Base58Decode(address)
 pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
 out.PubKeyHash = pubKeyHash}func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
 return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0}

请注意,我们现在不需要 ScriptPubKey 和 ScriptSig 字段,因为我们不会实现脚本语言。 相反,ScriptSig

它将分为Signature和PubKey字段,ScriptPubKey将重命名为PubKeyHash。 我们将实现与比特币相同的输出锁定/解锁和输入签名逻辑,不同之处在于我们将通过方法实现。

比特币的合约地址_比特币合约多空比在哪看_比特币合约单位

UsesKey 方法检查输入是否使用指定的密钥来解锁输出。 请注意,输入存储原始公钥(即未经过哈希处理的公钥),但此函数需要经过哈希处理的公钥。 IsLockedWithKey 检查提供的公钥哈希是否用于锁定输出。 这是 UsesKey 的辅助函数,它们都在 FindUnspentTransactions 中用于形成交易之间的链接。

Lock 只是锁定输出。 当我们向某人发送硬币时,我们只知道他的地址,因为此函数将地址作为唯一参数。 然后,对地址进行解码,并从中提取公钥哈希并将其存储在 PubKeyHash 字段中。

现在,让我们检查一切是否按预期工作:

$ blockchain_go createwallet
Your new address: 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
$ blockchain_go createwallet
Your new address: 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
$ blockchain_go createwallet
Your new address: 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
$ blockchain_go createblockchain -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
0000005420fbfdafa00c093f56e033903ba43599fa7cd9df40458e373eee724d
Done!$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 10
$ blockchain_go send -from 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -to 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -amount 5
2017/09/12 13:08:56 ERROR: Not enough funds
$ blockchain_go send -from 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -to 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -amount 6
00000019afa909094193f64ca06e9039849709f5948fbac56cae7b1b8f0ff162
Success!$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 4
$ blockchain_go getbalance -address 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
Balance of '15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h': 6
$ blockchain_go getbalance -address 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
Balance of '1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy': 0

非常好! 下半场,我们将实现如何签署交易。