这个虬龙甲值不值fgo进阶材料掉落培育(一掉落就三阶,我后来强化+2)

\ 经验分享
Python进阶强化训练之数据结构与算法进阶
手记来源于Python进阶强化训练一课,特此记录,以便于下次查找:
课程地址:
如何在列表、字典、集合中根据条件筛选数据?
过滤列表中的负数
筛选出字典种值高于90的项
筛选出集合种能被3整出的元素
围绕上面三个问题我们来进行讨论,比如下面有一个列表:
&&& from random import randint
&&& li = [randint(-10, 10) for _ in range(10)]
[-10, -9, 1, 10, -3, -7, -6, -7, 4, -5]
我们常规的做法就是通过for循环对列表中的每一个值进行迭代,然后判断如果值大于等于0,就确定这个值是一个整数,否则就丢弃,比如下面的代码:
&&& result = []
&&& for n in li:
# 如果这个元素大于等于0
if n &= 0:
# 加入的result列表中
result.append(n)
&&& result
[1, 10, 4]
本篇所有的代码均在Python 3.5.x种运行,如果你使用的是python 2.7.x,那么请自行测试,在此之前,请导入一下模块用于测试:
# 用于生成随机数
&&& from random import randint
# 准确测量小段代码的执行时间
&&& import timeit
请仔细阅读下面的代码,看完后你将会有不一样的收获。
filter函数
生成一个随机列表
&&& li = [randint(-10, 10) for _ in range(10)]
[6, -8, 9, 3, 3, 8, 9, -4, 9, -6]
# x=列表中的一个元素,有多少个元素就迭代多少次
&&& result = filter(lambda x: x &=0, li)
&&& for n in result:
生成一个随机列表
&&& li = [randint(-10, 10) for _ in range(10)]
[8, -5, -2, 8, 9, 4, -6, -5, 5, 4]
&&& [x for x in li if x &=0 ]
[8, 8, 9, 4, 5, 4]
filter与列表解析性能对比
使用filter执行时间
&&& timeit.Timer('filter(lambda x: x &=0, [4, -1, 1, 3, -10, 5, -8, 0, 6, 3])').timeit()
使用列表解析执行时间
&&& timeit.Timer('[x for x in [4, -1, 1, 3, -10, 5, -8, 0, 6, 3] if x &=0 ]').timeit()
通过以上的测试可以看出filter的执行时间明显比列表解析要快些,当然这并不是一个非常准确的数字,还是有待考察的。
先随机生成一个字典:
&&& dic = { x: randint(60, 100) for x in range(1, 21) }
{1: 61, 2: 75, 3: 69, 4: 70, 5: 79, 6: 90, 7: 74, 8: 85, 9: 77, 10: 86, 11: 93, 12: 96, 13: 86, 14: 79, 15: 60, 16: 84, 17: 70, 18: 72, 19: 61, 20: 87}
&&& { k: v for k, v in dic.items() if v & 90 }
{11: 93, 12: 96}
生成一个集合:
&&& li = [randint(-10, 10) for _ in range(10)]
&&& s = set(li)
{0, 1, 3, 4, 7, -9, -8}
&&& { x for x in s if x % 3 == 0 }
{0, 3, -9}
如何为元组中的每个元素命名、提高程序可读性?
某校的学生信息系统中的数据存储格式如下:
(名字,年龄,性别,邮箱地址)
比如有如下学生信息:
student1 = ('Hello', 15, 'Schoolboy', '')
student2 = ('World', 16, 'Girls', '')
student3 = ('ansheng', 20, 'Schoolboy', 'anshengme.')
通常我们会以如下方式进行取值:
&&& student1[2]
'Schoolboy'
&&& student1[3]
在代码比较多的情况下,使用大量的索引进行访问会降低程序的可读性,如何解决这个问题呢?
定义类似于其他语言的枚举类型,也就是定义一系列的数值常量.
# 创建一个学生
&&& student = ('ansheng', 20, 'Schoolboy', 'anshengme.')
# 定义常量
&&& NAME, AGE, SEX, EMAIL = range(4)
# 通过常量进行取值
&&& student[NAME]
&&& student[AGE]
&&& student[EMAIL]
'anshengme.'
使用标准库中的collections.namedtuple替代内置的tuple
&&& from collections import namedtuple
&&& Student = namedtuple('Student', ['name','age','sex','email'])
&&& s = Student('ansheng', 20, 'Schoolboy', 'anshengme.')
Student(name='ansheng', age=20, sex='Schoolboy', email='anshengme.')
# 使用属性进行访问
&&& s.name
'Schoolboy'
s是tuple的一个子类
&&& isinstance(s, tuple)
如何统计序列中元素的出现频度?
某随机的列表中,找出出现次数最高的三个元素,他们的出现次数是多少?
对某英文文章的单词进行词频统计,找出出现次数最高的10个单词,他们出现的次数是多少?
使用collections.Counter对象,将序列传入Counter的构造器,得到Counter对象是元素频度的字典,Counter.most_common(n)方法得到频度最高的n个元素的列表
常规的解决方法
生成随机序列的列表
&&& from random import randint
&&& li = [randint(0, 20) for _ in range(30)]
[14, 11, 10, 13, 19, 10, 3, 17, 12, 13, 18, 5, 10, 1, 9, 17, 1, 8, 3, 15, 8, 3, 20, 10, 9, 20, 6, 13, 8, 20]
以字典的形式创建每个数字出现的次数,
# 默认的出现次数为0
&&& count = dict.fromkeys(li, 0)
{1: 0, 3: 0, 5: 0, 6: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 17: 0, 18: 0, 19: 0, 20: 0}
每遇到一个x(列表中的数),就去字典中让值+1
&&& for x in li:
count[x] += 1
{1: 2, 3: 3, 5: 1, 6: 1, 8: 3, 9: 2, 10: 4, 11: 1, 12: 1, 13: 3, 14: 1, 15: 1, 17: 2, 18: 1, 19: 1, 20: 3}
然后循环count找到最大的三个数字,取出来就好。
使用collections.Counter对象
# 导入Counter
&&& from collections import Counter
&&& count = Counter(li)
Counter({10: 4, 3: 3, 8: 3, 13: 3, 20: 3, 1: 2, 9: 2, 17: 2, 5: 1, 6: 1, 11: 1, 12: 1, 14: 1, 15: 1, 18: 1, 19: 1})
&&& count.most_common(3)
[(10, 4), (3, 3), (8, 3)]
英文文章词频统计实例
&&& from collections import Counter
&&& import re
&&& txt = open('jquery.cookie.js').read()
&&& count = Counter(re.split('\W+', txt))
Counter({'s': 20, 'cookie': 18, 'options': 16, 'value': 12, 'function': 11, 'key': 10, 'var': 10, 'return': 9, 'expires': 8, 'if': 8, 'config': 8, 't': 6, 'result': 5, 'the': 5, 'a': 5, 'it': 5, '1': 4, 'decode': 4, 'i': 4, 'converter': 4, 'factory': 4, 'undefined': 4, 'cookies': 4, 'read': 3, 'domain': 3, 'g': 3, 'encode': 3, 'raw': 3, 'path': 3, 'name': 3, 'replace': 3, 'typeof': 3, 'parts': 3, 'we': 3, 'define': 3, 'document': 3, 'is': 3, 'pluses': 3, 'jquery': 3, 'If': 3, '': 2, 'else': 2, 'for': 2, 'object': 2, 'can': 2, 'json': 2, 'join': 2, 'days': 2, 'not': 2, 'jQuery': 2, 'in': 2, 'l': 2, 'parse': 2, 'ignore': 2, 'split': 2, 'isFunction': 2, 'unusable': 2, 'stringifyCookieValue': 2, 'secure': 2, 'extend': 2, 'decodeURIComponent': 2, 'parseCookieValue': 2, 'JSON': 2, '0': 2, 'defaults': 2, 'extending': 1, 'storing': 1, 'Copyright': 1, 'thus': 1, 'use': 1, 'place': 1, 'require': 1, 'max': 1, 'length': 1, 'according': 1, 'AMD': 1, 'carhartl': 1, 'first': 1, 'globals': 1, 'catch': 1, 'https': 1, 'are': 1, 'try': 1, 'Write': 1, 'spaces': 1, 'written': 1, 'array': 1, 'age': 1, 'supported': 1, 'attribute': 1, 'under': 1, 'exports': 1, 'that': 1, 'Klaus': 1, 'RFC2068': 1, 'Replace': 1, 'as': 1, 'shift': 1, 'Prevent': 1, '864e': 1, 'slice': 1, 'prevents': 1, 'stringify': 1, 'loop': 1, 'e': 1, 'at': 1, 'v1': 1, '5': 1, 'com': 1, 'when': 1, 'toUTCString': 1, 'setTime': 1, 'CommonJS': 1, 'false': 1, 'amd': 1, 'Plugin': 1, 'quoted': 1, 'couldn': 1, 'an': 1, 'no': 1, 'github': 1, 'argument': 1, 'Must': 1, 'license': 1, 'second': 1, 'break': 1, 'Browser': 1, 'IE': 1, 'Date': 1, 'to': 1, 'prevent': 1, 'To': 1, 'Released': 1, 'by': 1, 'with': 1, 'Cookie': 1, 'Also': 1, 'indexOf': 1, 'there': 1, 'side': 1, 'MIT': 1, 'odd': 1, 'case': 1, 'number': 1, 'encodeURIComponent': 1, 'calling': 1, 'Hartl': 1, 'unescape': 1, 'all': 1, 'removeCookie': 1, 'Read': 1, 'new': 1, 'assign': 1, 'fresh': 1, 'server': 1, '2013': 1, 'String': 1, 'empty': 1, '4': 1, 'This': 1, 'alter': 1})
&&& count.most_common(10)
[('s', 20), ('cookie', 18), ('options', 16), ('value', 12), ('function', 11), ('key', 10), ('var', 10), ('return', 9), ('expires', 8), ('if', 8)]
如何根据字典中值的大小, 对字典中的项排序?
某班英语成绩以字典形式进行存储,格式为:
'ansheng': 79,
'Jim': 66,
'Hello': 99,
要求根据成绩高低,计算学生排名。
使用内置函数sorted(),但是默认情况下sorted()并不能对字典进行排序,这里提供了两种解决方法:
利用zip()将字典数据转化为元组然后把值传给sorted()进行排序
传递sorted()函数的key参数
先创建一个成绩单:
&&& from random import randint
&&& Transcripts = { x: randint(60,100) for x in 'xyzabc' }
&&& Transcripts
{'z': 61, 'x': 74, 'b': 81, 'c': 65, 'y': 88, 'a': 98}
使用sorted()进行排序的时候是以字典的key进行的
&&& sorted(Transcripts)
['a', 'b', 'c', 'x', 'y', 'z']
第一种解决方法
获取字典的所有建
&&& Transcripts.keys()
dict_keys(['z', 'x', 'b', 'c', 'y', 'a'])
获取字典所有的值
&&& Transcripts.values()
dict_values([61, 74, 81, 65, 88, 98])
通过zip()把字典转换为元组
&&& T = zip(Transcripts.values(), Transcripts.keys())
通过sorted()进行排序得到结果
&&& sorted(T)
[(61, 'z'), (65, 'c'), (74, 'x'), (81, 'b'), (88, 'y'), (98, 'a')]
元组在进行比较的时候是先从第一个元素进行比较,如果比较值为True,则后面的就不进行比较:
&&& (100, 'a') & (50, 'b')
&&& (50, 'a') & (50, 'b')
# a不大于b,返回False
第二种解决方法
&&& Transcripts.items()
dict_items([('z', 61), ('x', 74), ('b', 81), ('c', 65), ('y', 88), ('a', 98)])
# key需要传入一个函数,每次迭代Transcripts.items()的时候,把第一个元素传入进去,然后进行排序
&&& sorted(Transcripts.items(), key=lambda x: x[1])
[('z', 61), ('c', 65), ('x', 74), ('b', 81), ('y', 88), ('a', 98)]
如何快速找到多个字典中的公共键(key)?
在每一个字典中都会出现的键称之为公共键。
&&& from random import randint, sample
# abcdefg是随机产生的key,每次随机取3-6个key
&&& sample('abcdefg', randint(3, 6))
['g', 'f', 'c', 'a', 'e', 'd']
生成随机的字典
&&& s1 = { x: randint(1, 4) for x in sample('abcdefg', randint(3, 6)) }
&&& s2 = { x: randint(1, 4) for x in sample('abcdefg', randint(3, 6)) }
&&& s3 = { x: randint(1, 4) for x in sample('abcdefg', randint(3, 6)) }
{'c': 3, 'g': 1, 'e': 2}
{'a': 2, 'f': 2, 'g': 3, 'e': 1, 'd': 2}
{'a': 2, 'c': 2, 'e': 2, 'f': 4}
传统的做法如下:
# 生成一个列表
&&& res = []
# 循环s1字典的所有键
&&& for k in s1:
# 如果键在s2和s3中都存在就添加到res列表中
if k in s2 and k in s3:
res.append(k)
# 得到的键e,在s2和s3中都存在
利用集合(set)的交集操作
使用字典的keys()方法,得到一个字典的keys的集合
&&& s1.keys() & s2.keys() & s3.keys()
使用map函数,得到所有字典的keys的集合,然后使用reduce函数,取所有字典的keys的集合的交集
&&& from functools import reduce
&&& reduce(lambda a, b: a & b, map(dict.keys, [s1, s2, s3]))
如何让字典保持有序?
使用collections.OrderedDict,以OrderedDict替代内置字典Dict,依次将数据存入OrderedDict
# 导入OrderedDict模块
&&& from collections import OrderedDict
# 创建一个OrderedDict()对象
&&& dic = OrderedDict()
# 添加数据
&&& dic['H1'] = ('a', 'b')
&&& dic['H2'] = ('ab', 'cd')
&&& dic['H3'] = ('ww', 'ss')
# 变量字典中的数据
&&& for n in dic: print(n)
&&& for n in dic: print(n)
如何实现用户的历史记录功能(最多n条)?
可以使用容量为n的队列存储历史纪录,使用标准库collections中的deque,它是一个双端循环队列。
如果要保存到文件中,可以在程序堆出前,可以使用pickle将队列对象存入文件,再次运行程序时将其导入。
制作一个简单的猜数字小游戏,添加历史纪录的功能,显示用户最近猜过的数字,限定最近5条。
# 导入deque模块
&&& from collections import deque
# 创建一个队列,容量初始值为空,最多放5个值
&&& q = deque([], 5)
deque([], maxlen=5)
&&& q.append(1)
&&& q.append(2)
&&& q.append(3)
&&& q.append(4)
&&& q.append(5)
deque([1, 2, 3, 4, 5], maxlen=5)
# 当超过5个值的时候,第一个就会被挤出去
&&& q.append(6)
deque([2, 3, 4, 5, 6], maxlen=5)
实例的脚本文件为:
from random import randint
from collections import deque
# 随机生成1-100的数字
N = randint(1, 100)
# 创建一个队列histort,默认为空,最大限度值为5个
histort = deque([], 5)
def guess(k):
# 数字猜对
if k == N:
print('right')
return True
print('%s is less-than N' % (k))
print('%s is greater-than N' % (k))
return False
while True:
line = input('please input a number: ')
if line.isdigit():
k = int(line)
histort.append(k)
if guess(k):
# 如果输入'history'就输出队列中的内容
elif line == 'history':
print(list(histort))
若觉得本文不错,就分享一下吧!
作者的热门手记
请登录后,发表评论
评论加载中...
Copyright (C) 2018 imooc.com All Rights Reserved | 京ICP备 号-2比特币技术进阶 | 巴比特
比特币技术进阶
简介:“比特币技术进阶”由知名比特币技术专家原创的三篇文章《比特币交易构成》、《时间戳服务与存在证明》、《工作证明与挖矿》组成,对于那些想要探究比特币工作原理却又不想费力分析源代码的朋友,这篇文章是您或不可缺的进阶指南。
一、工作证明与挖矿
工作证明(Proof Of Work,简称POW),顾名思义,即工作量的证明。通常来说只能从结果证明,因为监测工作过程通常是繁琐与低效的。
比特币在Block的生成过程中使用了POW机制,一个符合要求的Block Hash由N个前导零构成,零的个数取决于网络的难度值。要得到合理的Block Hash需要经过大量尝试计算,计算时间取决于机器的哈希运算速度。当某个节点提供出一个合理的Block Hash值,说明该节点确实经过了大量的尝试计算,当然,并不能得出计算次数的绝对值,因为寻找合理hash是一个概率事件。当节点拥有占全网n%的算力时,该节点即有n/100的概率找到Block Hash。
工作证明机制看似很神秘,其实在社会中的应用非常广泛。例如,毕业证、学位证等证书,就是工作证明,拥有证书即表明你在过去投入了学习与工作。生活大部分事情都是通过结果来判断的。
挖矿即不断接入新的Block延续Block Chain的过程。
挖矿为整个系统的运转提供原动力,是比特币的发动机,没有挖矿就没有比特币。挖矿有三个重要功能:
发行新的货币(总量达到之前)
维系货币的支付功能
通过算力保障系统安全
金矿消耗资源将黄金注入流通经济,比特币通过“挖矿”完成相同的事情,只不过消耗的是CPU时间与电力。当然,比特币的挖矿意义远大于此。
Block Hash算法
Block头部信息的构成:
大小(字节)
hashPrevBlock
上一个block hash值
hashMerkleRoot
上一个block产生之后至新block生成此时间内,
交易数据打包形成的Hash
Unix时间戳
目标值,即难度
nHeight+1) % nInterval != 0) {
// 未达到周期个数,无需调节
return pindexLast-&nB
// Go back by what we want to be 14 days worth of blocks
const CBlockIndex* pindexFirst = pindexL
for (int i = 0; pindexFirst && i & nInterval-1; i++) pindexFirst = pindexFirst-&
// 计算本次2016个块的实际产生时间
// Limit adjustment step
int64 nActualTimespan = pindexLast-&GetBlockTime() – pindexFirst-&GetBlockTime();
// 限定幅度,最低为1/4,最高为4倍
if (nActualTimespan & nTargetTimespan/4) nActualTimespan = nTargetTimespan/4; if (nActualTimespan & nTargetTimespan*4)
nActualTimespan = nTargetTimespan*4;
// 根据最近2016个块的时间,重新计算目标难度
// Retarget
CBigNum bnN
bnNew.SetCompact(pindexLast-&nBits);
bnNew *= nActualT
bnNew /= nTargetT
if (bnNew & bnProofOfWorkLimit)
bnNew = bnProofOfWorkL
return bnNew.GetCompact();
Block字段详解
Version,版本号,很少变动,一般用于软件全网升级时做标识
hashPrevBlock,前向Block Hash值,该字段强制多个Block之间形成链接
hashMerkleRoot,交易Hash树的根节点Hash值,起校验作用,保障Block在网络传输过程中的数据一致性,有新交易加入即发生变化
Time,Unix时间戳,每秒自增一,标记Block的生成时间,同时为block hash探寻引入一个频繁的变动因子
Bits,可以推算出难度值,用于验证block hash难度是否达标
Nonce,随机数,在上面数个字段都固定的情况下,不停地更换随机数来探寻
最为关键的字段是hashPrevBlock,该字段使得Block之间链接起来,形成一个巨大的“链条”。Block本是稀松平常的数据结构,但以链式结构组织起来后却使得它们具有非常深远的意义:
形成分支博弈,使得算力总是在主分支上角逐
算力攻击的概率难度呈指数上升(泊松分布)
每个block都必须指向前一个block,否则无法验证通过。追溯至源头,便是高度为零的创世纪块(Genesis Block),这里是Block Chain的起点,其前向block hash为零,或者说为空。
新block诞生过程
下面是一个简单的步骤描述,实际矿池运作会有区别,复杂一些:
节点监听全网交易,通过验证的交易进入节点的内存池(Tx Mem Pool),并更新交易数据的Merkle Hash值
更新时间戳
尝试不同的随机数(Nonce),进行hash计算
重复该过程至找到合理的hash
打包block:先装入block meta信息,然后是交易数据
对外部广播出新block
其他节点验证通过后,链接至Block Chain,主链高度加一,然后切换至新block后面挖矿
由于hashPrevBlock字段的存在,使得大家总是在最新的block后面开挖,稍后会分析原因。
从block hash算法我们知道,合理的block并不是唯一的,同一高度存在多个block的可能性。那么,当同一个高度出现多个时,主链即出现分叉(Fork)。遇到分叉时,网络会根据下列原则选举出Best Chain:
不同高度的分支,总是接受最高(即最长)的那条分支
相同高度的,接受难度最大的
高度相同且难度一致的,接受时间最早的
若所有均相同,则按照从网络接受的顺序
等待Block Chain高度增一,则重新选择Best Chain
按照这个规则运作的节点,称为诚实节点(Honest Nodes)。节点可以诚实也可以不诚实。
我们假设所有的节点:
都是理性的,追求收益最大化
都是不诚实的,且不惜任何手段获取利益
所有节点均独自挖矿不理会其他节点,并将所得收益放入自己口袋,现象就是一个节点挖一个分支。由于机器的配置总是有差别的,那么算力最强的节点挖得的分支必然是最长的,如果一个节点的分支不是最长的,意味其收益存在不被认可的风险(即零收益)。为了降低、逃避此风险,一些节点肯定会联合起来一起挖某个分支,试图成为最长的分支或保持最长分支优势。
一旦出现有少量的节点联合,那么其他节点必然会效仿,否则他们收益为零的风险会更大。于是,分支迅速合并汇集,所有节点都会选择算力更强的分支,只有这样才能保持收益风险最小。最终,只会存在一个这样的分支,就是主干分支(Best/Main Chain)。
对于不诚实节点来说,结局是无奈的:能且只能加入主干挖矿。不加入即意味被抛弃,零收益;加入就是老实干活,按占比分成。
Hash Dance
Block hash的计算是随机概率事件,当有节点广播出难度更高的block后,大家便跑到那个分支。在比特币系统运行过程中,算力经常在分支间跳来跳去,此现象称为Hash Dance。一般情况下,分支的高度为1~2,没有大的故障很难出现高于2的分支。
Hash Dance起名源于Google Dance.
算力攻击的概率
算力攻击是一个概率问题,这里作简单叙述:
p = 诚实节点挖出block概率
q = 攻击者挖出block概率,q = 1 – p
qz = 攻击者从z个block追上的概率
我们假设p&q,否则攻击者掌握了一半以上的算力,那么概率上永远是赢的。该事件(攻击者胜出)的概率是固定,且N次事件之间是相互独立的,那么这一系列随机过程符合泊松分布(Poisson Distribution)。Z个块时,攻击者胜出的期望为lambda:
攻击者在攻击时已经偷偷的计算了k个块,那么这k个块概率符合泊松分布(下图左侧部分),若k&=z,那么追赶上后续z-k个块的概率为(q/p)z-k,即:
展开为如下形式:
计算该过程的C语言代码如下:
#include double AttackerSuccessProbability(double q, int z)
double sum = 1.0;
double p = 1.0 –
double lambda = z * (q / p);
for (k = 0; k &= k++) {
double poisson = exp(-lambda);
for (i = 1; i &= i++)
poisson *= lambda /
sum -= poisson * (1 – pow(q / p, z – k));
我们选取几个值,结果如下:
可以看到,由于block的链式形式,随着块数的上升,攻击者赢得的概率呈指数下降。这是很多应用等待六个甚至六个以上确认的原因,一旦超过N个确认,攻击者得逞的可能微乎其微,概率值快速趋近零。
当攻击者的算力超过50%时,便可以控制Block Chain,俗称51%攻击。
算力攻击的危害
攻击者算出block后,block&Txs必须能够通过验证,否则其他节点都会拒掉,攻击便无意义。攻击者无法做出下列行为:
偷盗他人的币。消费某个地址的币时,需要对应的ECDSA私钥签名,而私钥是无法破解的。
凭空制造比特币。每个block奖励的币值是统一的规则,篡改奖励币值会导致其他节点会拒绝该block。
唯一的益处是可以选择性的收录进入block的交易,对自己的币进行多重消费(Double Spending)。
过程是这样的:假设现在block高度为100,攻击者给商户发了一个交易10BTC,记作交易A,通常这笔交易会被收录进高度101的block中,当商户在101块中看到这笔交易后,就把货物给了攻击者。此时,攻击者便开始构造另一个高度为101的block,但用交易B替换了交易A,交易B中的输入是同一笔,使得发给商户的那笔钱发给他自己。同时,攻击者需要努力计算block,使得他的分支能够赶上主分支,并合并(Merge)被大家接受,一旦接受,便成功地完成了一次Double Spending。
攻击难度呈指数上升,所以成功的Double Spending通常是一个极小概率事件。
全网算力的上升对比特币是极其有利的,这是毫无疑问的。但目前大矿池与矿业巨头使得算力高度集中化,这与中本聪所设想的一CPU一票(one-CPU-one-vote)的分散局面背道而驰,或许是他未曾预料的。
挖矿是一项专业劳动,最后必然会交给最专业的人或团队,因为这样才能实现资源配置最优,效率最高。普通投资人通过购买算力巨头的股票:1. 完成投资;2. 分享算力红利。看似中心化的背后其实依然是分散的:
矿业公司的背后是无数分散的投资人
矿池背后是无数分散的个体算力
既得利益使得算力巨头倾向于维护系统而不是破坏,因其收益均建立在比特币系统之上,既得利益者断然不会搬石头砸自己脚。甚至很多巨头在达到一定算力占比后会主动控制算力增长,使得低于某阈值内。
本篇几乎都在讲挖矿,因为挖矿对于比特币系统来说实在是太重要了。需要了解:1. block是基于工作量证明的。2. block以链式结构存在时的深远意义。
二、比特币交易构成
交易(Transaction)是比特币系统的信息载体,最小单元。而块(Block)就是将这些基础单元打包装箱,贴上封条,并串联起来。巨大算力保障了块的安全,也就保障了单个交易的安全。
交易有三种常见类型:产量交易(Generation),合成地址交易(Script Hash),通用地址交易(Pubkey Hash)。该分类并非严格意义的,只是根据交易的输入输出做的简单区分。
Generation TX
每个Block都对应一个产量交易(Generation TX),该类交易是没有输入交易的,挖出的新币是所有币的源头。
Script Hash TX
该类交易目前不是很常见,大部分人可能没有听说过,但是非常有意义。未来应该会在某些场合频繁使用。该类交易的接受地址不是通常意义的地址,而是一个合成地址,以3开头(对,以3开头的也是比特币地址!)。三对公私钥,可以生成一个合成地址。在生成过程时指定n of 3中的n,n范围是[1, 3],若n=1,则仅需一个私钥签名即可花费该地址的币,若n=3,则需要三把私钥依次签名才可以。
Pubkey Hash TX
该类是最常见的交易类型,由N个输入、M个输出构成。
交易中存放的是货币所有权的流转信息,所有权登记在比特币地址上(Public Key)。这些信息是全网公开的,以明文形式存储(比特币系统里的所有数据都是明文的),只有当需要转移货币所有权时,才需要用私钥签名来验证。
version, 版本
交易数据结构的版本号
tx_in count, 输入数量
输入交易的数量
输入交易的数组,每个输入&=41字节
tx_out count, 输出数量
输出地址的数量
输入地址的数组,每个输入&=9字节
lock_time, 锁定时间
见下方解释
lock_time是一个多意字段,表示在某个高度的Block之前或某个时间点之前该交易处于锁定态,无法收录进Block。
含义为Block高度,处于该Block之前为锁定(不生效)
含义为Unix时间戳,处于该时刻之前为锁定(不生效)
若该笔交易的所有输入交易的sequence字段,均为INT32最大值(0xffffffff),则忽略lock_time字段。否则,该交易在未达到Block高度或达到某个时刻之前,是不会被收录进Block中的。
为了演示方便,我们读取稍早期的块数据,以高度116219 Block为例。
# ~ bitcoind getblock 7c639f2cbb23efad92e99f
“hash” : &#f2cbb23efad92e99f″,
“confirmations” : 144667,
“size” : 1536,
“height” : 116219,
“version” : 1,
“merkleroot” : &#fefd748f899f84d0fa1d8a3876fdb406a4bb8f54a301daea6″,
“tx” : [
"be8f08d7f519eb863a68cf292ca51dbab7c9b49f50a96d13f2db32e432db363e",
"a387039eca6da3dcc8a0fc745bcb511e20edbe037bb",
"2bdabf59f065f8c8eed2a21fbf85d454ee4e0e4c267",
"028cfae228f8a4b0caee9c566bd41aed36bcd237cdc0eb18f11743",
"3a06b63a8567fbfa8fe978ee0ba06eb33fdf01149ad62"
“time” : ,
“nonce” : ,
“bits” : &#f339″,
“difficulty” : 21,
“previousblockhash” : &#135eb39bd3bbba85c57a1efbfb”,
“nextblockhash” : &#00000e9fcc59a76a30f5fe35d6d8c4b4ce0b1b43;
该Block里面有5笔交易,第一笔为Generation TX,解析出来看一下具体内容:
# ~ bitcoind getrawtransaction be8f08d7f519eb863a68cf292ca51dbab7c9b49f50a96d13f2db32e432db363e 1
“hex” : &#0000000ffffffff1b0134ffffffff5b3aaa284d169c5ae2d20d0b06aa8fea5976eacaf1ff2fbce1a646ae771a671f564ca6c03e484a1c394bf96e2a4ad01dceac43;,
“txid” : “be8f08d7f519eb863a68cf292ca51dbab7c9b49f50a96d13f2db32e432db363e”,
“version” : 1,
“locktime” : 0,
“vin” : [
"coinbase" : "34",
"sequence" :
“vout” : [
"value" : 50.,
"scriptPubKey" : {
"asm" : "045b3aaa284d169c5ae2d20d0b06aa8fea5976eacaf1ff2fbce1a646ae771a671f564ca6c03e484a1c394bf96e2a4ad01dce OP_CHECKSIG",
"hex" : "41045b3aaa284d169c5ae2d20d0b06aa8fea5976eacaf1ff2fbce1a646ae771a671f564ca6c03e484a1c394bf96e2a4ad01dceac",
"reqSigs" : 1,
"type" : "pubkey",
"addresses" : [
"1LgZTvoTJ6quJNCURmBUaJJkWWQZXkQnDn"
“blockhash” : &#f2cbb23efad92e99f″,
“confirmations” : 145029,
“time” : ,
“blocktime” :
Generation TX的输入不是一个交易,而带有coinbase字段的结构。该字段的值由挖出此Block的人填写,这是一种“特权”:可以把信息写入货币系统(大家很喜欢用系统中的数据结构字段名来命名站点,例如blockchain、coinbase等,这些词的各种后缀域名都被抢注一空)。中本聪在比特币的第一个交易中的写入的coinbase值是:
“coinbase”:”04ffff001da616e2fc6c6fb206ff6ec6fb73″
将该段16进制转换为ASCII字符,就是那段著名的创世块留言:
The Times 03/Jan/2009 Chancellor on brink of second bailout for banks
接下来展示的是一个三个输入、两个输出的普通交易:
# ~ bitcoind getrawtransaction 028cfae228f8a4b0caee9c566bd41aed36bcd237cdc0eb18f11743 1
“hex” : &#003c9f3b07ebfca68fd1afbb013c90ceaababac1856ecbc377dd5e869b1a84ed1dc31a4daf5fc27828aba43b464ecba509b5fbd6cac97ff3af0141048aefd78bba80e2ddacea890c9ca1be10ecf2fefbbf881a6e918f3b051f8aaaa3fcc18bbf6d5a7e5ef8d1005eaafd4b3fbeffffffffc9f3b07ebfca68fd1afbb013c90ceaabab993231adec55edc5ca6c19e42e744cd60abaff957b1c352b3ef9afec37dfa2c646c78d9a93e8d0b22dc580ef1aa6cccef208dbd6b3efccc3ab2dd926ff2ee48aacf35d785ec3cec92a5cf49e9e474fba9cd31026affffffffcfc7b3a506ad79affacd1dd80ffb230daa5d957e087ed61e80f1110bcaf7711a6cbc54d6b98b6a1e774b8f44ddac5a99bff2d0efc83ad261da5b4f1dd1a57e4ebd59edbdd1f8a11c679d96456cae75b1fda1c1f44dbe8a829f3a8fe2fffffffffacf40a02a05db93f2f98b768a8e0e61b88acc096c7a6aab2fcacd10cb790c71c74c288ac43;,
“txid” : &#cfae228f8a4b0caee9c566bd41aed36bcd237cdc0eb18f1;,
“version” : 1,
“locktime” : 0,
“vin” : [
"txid" : "b79aeacf80d039631afd68cabf7eb0f3c9",
"vout" : 0,
"scriptSig" : {
"asm" : "bac1856ecbc377dd5e869b1a84ed1dc31a4daf5fc27828aba43b464ecba509b5fbd6cac97ff3af01 048aefd78bba80e2ddacea890c9ca1be10ecf2fefbbf881a6e918f3b051f8aaaa3fcc18bbf6d5a7e5ef8d1005eaafd4b3fbe",
"hex" : "bac1856ecbc377dd5e869b1a84ed1dc31a4daf5fc27828aba43b464ecba509b5fbd6cac97ff3af0141048aefd78bba80e2ddacea890c9ca1be10ecf2fefbbf881a6e918f3b051f8aaaa3fcc18bbf6d5a7e5ef8d1005eaafd4b3fbe"
"sequence" :
"txid" : "b79aeacf80d039631afd68cabf7eb0f3c9",
"vout" : 1,
"scriptSig" : {
"asm" : "3231adec55edc5ca6c19e42e744cd60abaff957b1c352b3ef9afec37dfa2c646c78d9a93e8d0b22dc580ef1aa6cccef208d01 042ff65bd6b3efccc3ab2dd926ff2ee48aacf35d785ec3cec92a5cf49e9e474fba9cd31026a",
"hex" : "b993231adec55edc5ca6c19e42e744cd60abaff957b1c352b3ef9afec37dfa2c646c78d9a93e8d0b22dc580ef1aa6cccef208dbd6b3efccc3ab2dd926ff2ee48aacf35d785ec3cec92a5cf49e9e474fba9cd31026a"
"sequence" :
"txid" : "da30b272fb73d78108ff80ddd1ac2fad06a5b3c70fc4a62086c9",
"vout" : 1,
"scriptSig" : {
"asm" : "a5d957e087ed61e80f1110bcaf7711a6cbc54d6b98b6a1e774b8f44ddac5a99bff2d0efc83ad261da5b4f1d01 04a7d1a57e4ebd59edbdd1f8a11c679d96456cae75b1fda1c1f44dbe8a829f3a8fe2f",
"hex" : "a5d957e087ed61e80f1110bcaf7711a6cbc54d6b98b6a1e774b8f44ddac5a99bff2d0efc83ad261da5b4f1dd1a57e4ebd59edbdd1f8a11c679d96456cae75b1fda1c1f44dbe8a829f3a8fe2f"
"sequence" :
“vout” : [
"value" : 0.,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 cf40a02a05db93f2f98b768a8e0e61b OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76acf40a02a05db93f2f98b768a8e0e61b88ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1A3q9pDtR4h8wpvyb8SVpiNPpT8ZNbHY8h"
“value” : 156.,
“n” : 1,
“scriptPubKey” : {
“asm” : “OP_DUP OP_HASH160 fcacd10cb790c71c74c2 OP_EQUALVERIFY OP_CHECKSIG”,
“hex” : &#fcacd10cb790c71c74c288ac”,
“reqSigs” : 1,
“type” : “pubkeyhash”,
“addresses” : [
"1Bg44FZsoTeYteRykC1XHz8facWYKhGvQ8"
“blockhash” : &#f2cbb23efad92e99f″,
“confirmations” : 147751,
“time” : ,
“blocktime” :
字段hex记录了所有相关信息,后面显示的是hex解析出来的各类字段信息。下面把逐个分解hex内容(hex可以从上面的直接看到):
// 版本号,UINT32
03 // Tx输入数量,变长INT。3个输入。
/*** 第一组Input Tx ***/
// Tx Hash,固定32字节
c9f3b07ebfca68fd1afbb013c90ceaab7
// 消费的Tx位于前向交易输出的第0个,UINT32,固定4字节
8a // 签名的长度, 0x8A = 138字节
// 138字节长度的签名,含有两个部分:公钥+签名
47 // 公钥长度,0×47 = 71字节
bac1856ecbc377dd5e869b1a84ed1dc31a4daf5fc27828aba43b464ecba509b5fbd6cac97ff3af01
41 // 签名长度,0×41 = 65字节
048aefd78bba80e2ddacea890c9ca1be10ecf2fefbbf881a6e918f3b051f8aaaa3fcc18bbf6d5a7e5ef8d1005eaafd4b3fbe
ffffffff // sequence,0xffffffff = , UINT32, 固定4字节
/*** 第二组Input Tx。与上同理,省略分解 ***/
c9f3b07ebfca68fd1afbb013c90ceaabab993231adec55edc5ca6c19e42e744cd60abaff957b1c352b3ef9afec37dfa2c646c78d9a93e8d0b22dc580ef1aa6cccef208dbd6b3efccc3ab2dd926ff2ee48aacf35d785ec3cec92a5cf49e9e474fba9cd31026affffffff
/*** 第三组Input Tx ***/
cfc7b3a506ad79affacd1dd80ffb230daa5d957e087ed61e80f1110bcaf7711a6cbc54d6b98b6a1e774b8f44ddac5a99bff2d0efc83ad261da5b4f1dd1a57e4ebd59edbdd1f8a11c679d96456cae75b1fda1c1f44dbe8a829f3a8fe2fffffffff
02 // Tx输出数量,变长INT。两个输出。
/*** 第一组输出 ***/
00bd // 输出的币值,UINT64,8个字节。字节序需翻转,~= 0xbd00 =
19 // 输出目的地址字节数, 0×19 = 25字节,由一些操作码与数值构成
// 目标地址
// 0×76 -& OP_DUP(stack ops)
// 0xa9 -& OP_HASH160(crypto)
// 0×14 -& 长度,0×14 = 20字节
// 地址的HASH160值,20字节
cf40a02a05db93f2f98b768a8e0e61b
// 0×88 -& OP_EQUALVERIFY(bit logic)
// 0xac -& OP_CHECKSIG(crypto)
/*** 第二组输出 ***/
76 a9 14 fcacd10cb790c71c74c2 88 ac
// lock_time,UINT32,固定4字节
Tx Hash,俗称交易ID,由hex得出:Tx Hash = SHA256(SHA256(hex))。由于每个交易只能成为下一个的输入,有且仅有一次,那么不存在输入完全相同的交易,那么就不存在相同的Tx Hash(SHA256碰撞概率极小,所以无需考虑Hash碰撞的问题,就像无需考虑地址私钥被别人撞到一样)。
即便如此,在系统里依然产生了相同的Tx Hash,是某位矿工兄弟挖出Block后,打包Block时忘记修改Generation Tx coinbase字段的值,币量相同且输出至相同的地址,那么就构造了两个完全一模一样的交易,分别位于两个Block的第一个位置。这个对系统不会产生什么问题,但只要花费其中一笔,另一个也被花费了。相同的Generation Tx相当于覆盖了另一个,白白损失了挖出的币。该交易ID为e3bf3d07d4bf1dbc4cb067cd81b84ee974b,第一次出现在#91722,第二次出现在#91880。
签名是对所有权的验证,节点收到交易广播后,会对交易进行验证,通过后则收录进内存、打包进Block,否则,丢弃之。签名就类似传统纸质合同盖章、签字过程,合法转移所有权的保证手段。
由于一个交易的输入、输出都可能具有多个,那么签名也具有多种类型,目前共三类:SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE。
SIGHASH_ALL
该签名类型为默认类型,也是目前绝大部分交易采用的,顾名思义即签名整单交易。首先,组织所有输出、输入,就像上文分解Hex过程一样,每个输入都对应一个签名,暂时留空,其他包括sequence等字段均须填写,这样就形成了一个完整的交易Hex(只缺签名字段)。然后,每一个输入均需使用私钥对该段数据进行签名,签名完成后各自填入相应的位置,N个输入N个签名。简单理解就是:对于该笔单子,认可且只认可的这些输入、输出,并同意花费我的那笔输入。
SIGHASH_NONE
该签名类型是最自由松散的,仅对输入签名,不对输出签名,输出可以任意指定。某人对某笔币签名后交给你,你可以在任意时刻填入任意接受地址,广播出去令其生效。简单理解就是:我同意花费我的那笔钱,至于给谁,我不关心。
SIGHASH_SINGLE
该签名类型其次自由松散,仅对自己的输入、输出签名,并留空sequence字段。其输入的次序对应其输出的次序,比如输入是第3个,那么签名的输出也是第三个。简单理解就是:我同意花费我的那笔钱,且只能花费到我认可的输出,至于单子里的其他输入、输出,我不关心。
交易的构造、签名与广播
上篇介绍了交易结构、签名等,为了更直观的认识比特币,借助bitcoind演示手动构造并广播交易的完整过程。
1. 找出未花费的币(unspent output)
通过命令:listunspent [minconf=1] [maxconf=9999999] ["address",...]列出某个地址未花费的币(交易),minconf/maxconf表示该笔收入交易的确认数范围,如果需要列出还未确认的交易,需将minconf设置为0。
bitcoind listunspent 0 100 ‘["1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3"]‘
"txid" : "296ea7bf981bd17fe0ceb852a8a34e68fcd19f0a41e",
"vout" : 0,
"address" : "1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3",
"account" : "",
"scriptPubKey" : "76a914d6cfa42b8ad44ce76b67a88ac",
"amount" : 0.,
"confirmations" : 1
我们找到该地址的一个未花费交易,位于交易296ea7bf981ba41e的第0个位置。
2. 创建待发送交易
创建待发送交易,由命令:createrawtransaction [{"txid":txid,"vout":n},...] {address:amount,…}来完成。我们将 0.1 BTC发送至 1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x ,并支付 0.0001 BTC做为矿工费。输入交易的额度为 0.199 ,输出为 0.1 + 0.0001 = 0.1001 ,那么还剩余: 0.199 – 0.1001 = 0.0989 ,将此作为找零发回给自己。
bitcoind createrawtransaction \
‘[{"txid":"296ea7bf981bd17fe0ceb852a8a34e68fcd19f0a41e","vout":0}]‘ \
‘{&#s4qDRbCbFypG5AFNR9tFC57PStkPX1x&#, “1Lab618UuWjLmVA1Q64tHZXcLoc43;:0.0989}’
0a9fd1fc684ea3a852b8cee07fd41b98bfa76effffffffa914fdc3ea75cabdcc0ab4ee88acd0ecfa42b8ad44ce76b67a88ac
通过命令:decoderawtransaction &hex string&,可以将此段十六进制字符串解码。
bitcoind decoderawtransaction &#efc684ea3a852b8cee07fd41b98bfa76effffffffa914fdc3ea75cabdcc0ab4ee88acd0ecfa42b8ad44ce76b67a88ac42;
“txid” : &#a3fdf7cbc97ea0715886dbfd2f60e115fb3a8f0″,
“version” : 1,
“locktime” : 0,
“vin” : [
"txid" : "296ea7bf981bd17fe0ceb852a8a34e68fcd19f0a41e",
"vout" : 0,
"scriptSig" : {
"asm" : "",
"hex" : ""
"sequence" :
“vout” : [
"value" : 0.,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 fdc3ea75cabdcc0ab4ee OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914fdc3ea75cabdcc0ab4ee88ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x"
“value” : 0.,
“n” : 1,
“scriptPubKey” : {
“asm” : “OP_DUP OP_HASH160 d6cfa42b8ad44ce76b67a OP_EQUALVERIFY OP_CHECKSIG”,
“hex” : &#d6cfa42b8ad44ce76b67a88ac”,
“reqSigs” : 1,
“type” : “pubkeyhash”,
“addresses” : [
"1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3"
至此,一个“空白交易”就构造好了,尚未使用私钥对交易进行签名,字段scriptSig是留空的,无签名的交易是无效的。此时的Tx ID并不是最终的Tx ID,填入签名后Tx ID会发生变化。
在手动创建交易时,务必注意输入、输出的值,非常容易犯错的是忘记构造找零输出(如非必要勿手动构造交易)。曾经有人构造交易时忘记找零,发生了支付 200 BTC 的矿工费的人间惨剧,所幸的是收录该笔交易的Block由著名挖矿团队“烤猫(Friedcat)”挖得,该团队非常厚道的退回了多余费用。
交易签名使用命令:
signrawtransaction &hex string& \
[{"txid":txid,"vout":n,"scriptPubKey":hex,"redeemScript":hex},...] [&privatekey1&,...] \
[sighashtype="ALL"]
第一个参数是创建的待签名交易的十六进制字符串;
第二个参数有点类似创建交易时的参数,不过需要多出一个公钥字段scriptPubKey,其他节点验证交易时是通过公钥和签名来完成的,所以要提供公钥;如果是合成地址,则需要提供redeemScript;
第三个参数是即将花费的币所在地址的私钥,用来对交易进行签名,如果该地址私钥已经导入至bitcoind中,则无需显式提供;
最后一个参数表示签名类型,在上一篇里,介绍了三种交易签名类型;
签名之前需要找到scriptPubKey,提取输入交易信息即可获取(也可以根据其公钥自行计算),由命令:getrawtransaction &txid& [verbose=0]完成。
bitcoind getrawtransaction 296ea7bf981bd17fe0ceb852a8a34e68fcd19f0a41e
“hex” : &#f639e583dc88f5b5fbe8c8295e6dfded3d925c9ec421ca60bcf2d7b9fc91ff6b141de6ee5ee04d4012103cad07f6de0b1a5bc82b228feb0b5eeb5a4ffffffffa914d6cfa42b8ad44ce76b67a88ac43;,
“txid” : &#bf981bd17fe0ceb852a8a34e68fcd19f0a41e″,
“version” : 1,
“locktime” : 0,
“vin” : [
"txid" : "4ae277ddc79e631f331105",
"vout" : 0,
"scriptSig" : {
"asm" : "be8c8295e6dfded3d925c9ec421ca60bcf2d7b9fc91ff6b141de6ee5ee04d401 03cad07f6de0b1a5bc82b228feb0b5eeb5a4",
"hex" : "be8c8295e6dfded3d925c9ec421ca60bcf2d7b9fc91ff6b141de6ee5ee04d4012103cad07f6de0b1a5bc82b228feb0b5eeb5a4"
"sequence" :
“vout” : [
"value" : 0.,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 d6cfa42b8ad44ce76b67a OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914d6cfa42b8ad44ce76b67a88ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3"
“blockhash” : &#f18f7659acd85b2bd06a5ed2c4439eea74a8b968d1;,
“confirmations” : 19,
“time” : ,
“blocktime” :
scriptPubKey位于”vout”[0]–&“scriptPubKey”–&“hex”,即: 76a914d6cfa42b8ad44ce76b67a88ac 。
签名使用ECDSA算法,对其,“空白交易”签名之,执行:
bitcoind signrawtransaction \
&#efc684ea3a852b8cee07fd41b98bfa76effffffffa914fdc3ea75cabdcc0ab4ee88acd0ecfa42b8ad44ce76b67a88ac43; \
‘[{"txid":"296ea7bf981bd17fe0ceb852a8a34e68fcd19f0a41e","vout":0,"scriptPubKey":"76a914d6cfa42b8ad44ce76b67a88ac"}]‘
“hex” : &#efc684ea3a852b8cee07fd41b98bfa76ecf9da4f53a6a4ae9cd9a7b76e0f5e95dcdf70f1b1e2b3548eaa3a8d48aed79daee518ce9a88ea3d03f7b32eb818f7c8c6ec1ae3b9d49f798e28efb6ccca8c31ad8155cdc0ce87b4aaffffffffa914fdc3ea75cabdcc0ab4ee88acd0ecfa42b8ad44ce76b67a88ac43;,
“complete” : true
签名后,签名值会填入上文所述的空字段中,从而得到一个完整的交易。可通过上文介绍的命令decoderawtransaction &hex string&解码查看之。
最后一步,就是将其广播出去,等待网络传播至所有节点,约10~60秒广播至全球节点,取决与你的节点的网络连接状况。稍后一些时刻,就会进入Block中。广播由命令sendrawtransaction &hex string&来完成。如果没有运行节点,可以通过公共节点的API进行广播,例如:blockchain.info/pushtx。
bitcoind sendrawtransaction \
&#efc684ea3a852b8cee07fd41b98bfa76ecf9da4f53a6a4ae9cd9a7b76e0f5e95dcdf70f1b1e2b3548eaa3a8d48aed79daee518ce9a88ea3d03f7b32eb818f7c8c6ec1ae3b9d49f798e28efb6ccca8c31ad8155cdc0ce87b4aaffffffffa914fdc3ea75cabdcc0ab4ee88acd0ecfa42b8ad44ce76b67a88ac43;
b5f8da1ea9e02ec3cc945e94ed4b0c88ede213205d
返回的是Transaction Hash值,即该交易的ID。至此,交易构造、签名、发送的完整过程完成了。
合成地址交易
合成地址以3开头,可以实现多方管理资产,极大提高安全性,也可以轻松实现基于比特币原生的三方交易担保支付。一个M-of-N的模式:
m {pubkey}…{pubkey} n OP_CHECKMULTISIG
M和N需满足:
可以是1 of 1,1 of 2,2 of 3等组合,通常选择N=3:
1 of 3,最大程度私钥冗余。防丢私钥损失,3把私钥中任意一把即可签名发币,即使丢失2把都可以保障不受损失;
2 of 3,提高私钥冗余度的同时解决单点信任问题。3把私钥任意2把私钥可签名发币,三方不完全信任的情形,即中介交易中,非常适用;
3 of 3,最大程度解决资金信任问题,无私钥冗余。必须3把私钥全部签名才能发币,适用多方共同管理重要资产,但任何一方遗失私钥均造成严重损失;
合成地址的交易构造、签名、发送过程与普通交易类似,这里只介绍如何创建一个合成地址。大神Gavin Andresen已经演示过,下面内容摘自其gist.
首先,需要三对公钥、私钥。公钥创建地址、私钥用于签名。
0491bbabd37da1fb5bd2c6d812c514e91bfa9f2eb129e1cbd868e209aac2fbc02cb33d98fe74bf23f0c235d4f86 / 5JaTXbAUmfPYZFRwrYaALK48fN6sFJp4rHqq2QSXs8ucfpE4yQU
a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac09ef122b1acbc1d1fc5c3291ccffef4ec6874 / 5Jb7fCeh1Wtm4yBBg3q3XbT6B525i17kVhy3vMC9AqfR6FH2qGk
048d708fc1fb6cd83f992d4ab08afbab08ffad6edbfb1e754e35fa1ca / 5JFjmGo5Fww9p8gvx48qBYDJNAzR9pmH5S389axMtDyPT8ddqmw
使用命令:createmultisig &nrequired& &’["key","key"]‘&来合成,其中key为公钥,创建地址时仅需公钥。创建类型是2 of 3.
bitcoind createmultisig 2 \
‘["0491bbabd37da1fb5bd2c6d812c514e91bfa9f2eb129e1cbd868e209aac2fbc02cb33d98fe74bf23f0c235d4f86","a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac09ef122b1acbc1d1fc5c3291ccffef4ec6874","048d708fc1fb6cd83f992d4ab08afbab08ffad6edbfb1e754e35fa1ca"]‘
“address” : “3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC”,
“redeemScript” : &#1bbabd37da1fb5bd2c6d812c514e91bfa9f2eb129e1cbd868e209aac2fbc02cb33d98fe74bf23f0c235d4f293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac09ef122b1acbc1d1fc5c3291ccffef4ec55dfc1fb6cd83f992d4ab08afbab08ffad6edbfb1e754e35fa1caae”
得到的合成地址是:3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC,该地址没有公钥,仅有redeemScript,作用与公钥相同。后续的构造、签名、发送过程与上文普通地址交易类似,略去。
三、时间戳服务与存在证明
存在证明就是向第三方证明某个物品/事件,在过去的某个时刻存在过。
这是一件很简单的事情,提供票据、通信记录之类的就可以办到。但这些并不严格,因为这些证据都是非常易伪造或销毁。要完成证明,必须依赖强有力的证据链,这个必须是任何人都无法伪造与销毁的,或者说伪造成本极其高昂近乎不可能。
回忆一下,电影里经常出现的绑匪镜头,他们为了证明在某个时间确实拥有人质,而不是事前拍摄的视频,通常会用当天的发行量很大的报纸来辅助证明。当香港媒体误报“成龙高楼坠亡”时,成龙也不得不拿报纸来证明自己的存在:
报纸之所以能够成为有效的时间证明系统是因为:
不可伪造性。新闻等信息是无法预测的,尤其是证券大盘数据,报纸上大量充满这样的信息,所以无人能够提前伪造。
公开且不可销毁。报纸通常拥有很大的发行数量,受众广泛,一旦发布出去就分散到各个角落,很难再次收集齐全并全部销毁。通常图书馆也会存档数十年期限的报纸。
具有时间特征。报纸具有很强时间特征,版面到处可见的是时间标记。
借助报纸可以完成某个时间之后的存在证明,但无法完成某个时间之前的。例如,你拿9月1号的报纸拍摄进照片,那么仅能证明其在9月1号之后拍摄,可能是9月1号,也可能是9月15号。
时间戳服务
比特币本质是构造了一个永不停息、无坚不摧的时间戳系统。
然后该系统上添加若干特性后使得具有货币的功能。报纸从另一个角度讲也是一种时间戳服务。
比特币具有下列优良的特性可以更完美的用于存在证明:
不可预测/伪造。因block的计算是随机事件,其hash值是一个32字节的随机大数(2256)。想蒙对该数的概率实在是太低了。
不可销毁/修改。Block Chain拥有巨大的算力在维护与延续,对于N个确认的block,想篡改是不可能的。
block具有天然时间特性。timestamp是block meta字段之一。
block可以存储信息。对于block meta信息,是无法控制的。但block会收录交易,而交易是可以”写入”自己的数据。
简单来说,对一串数据进行Hash运算,得到的Hash值称为数字摘要。除了Hash函数,还有其他方式,如密钥签名等也可以得到。Hash值通常是一个非常巨大的数,例如用SHA256时,Hash值区间非常大:1~2256。数字摘要的计算过程不可逆,那么可以认为:
欲证明你拥有某个文件,提供该文件的Hash值(及Hash函数)即可。第三方可以轻易验证文件的Hash值来判断之。
比特币做存在证明
时间点后向证明
因为block hash的不可伪造性,能提供Block Hash即可证明存在于该Block时刻之后。例如,你在拍照的时候,拿着打印有block hash的纸即可证明:你在该block时刻之后进行的拍摄。
时间点前向证明
前向证明需要精心构造一个包含数字摘要的交易,待该交易进入block中。便可以证明你在该block时刻之前拥有该数字摘要。前向证明的关键是能把信息写入时间戳服务载体。
时间区间证明
有时候,仅仅证明时间点之前或之后是不够的,需要能够确认到某一个时刻。将上述方式综合即可完成:
将block A的hash值添入数据文件,并制作文件数字摘要。(时间点后向证明)
将摘要信息构造至交易中,广播之。(时间点前向证明)
当交易被block B收录进去,那么即可证明,该文件于block A与B的时间间隔中存在。
如果交易给了足够的矿工费(Transaction Fee),具有较高优先级的话,便很有可能被紧随其后的block收录。连续的block约10分钟,那么就在一个相对小的时间内作了证明,可以近似认为是时间点。
构造特殊交易
带有数字摘要的交易如何构造呢?下面以32字节的数字摘要为例,提出数个可行方法,其他长度的可变换得出。
方式一:交易额承载信息
32字节可以分割为16个双字节,每个双字节的数值范围是:0~65535。比特币的现行单位可以分割至小数点后八位,那么我们可以利用最后的5位来存放一个数值,一共需要16个输出(Tx output)即可完成32字节的信息存储。中间涉及比特币最大数量为:
. * 16 = .0104856 btc
需要的比特币数量很少,约0.01Btc,且输出依然发回给自己的地址,唯一的代价就是付出矿工费(Tx Fee)。任何人都可以使用之。
SatoshiDice种子文件时间证明
著名站点SatoshiDice就是采用这种方式为其服务端种子文件做时间前向证明的。下面演示一下步骤。服务端的种子文件为hash.keys,我们对其做SHA256运算,得到hash值,32个字节。
$ sha256sum hash.keys
# hash of file “hash.keys”, in hex:
9b0d87ac871518cfdfa74c01194cfeb25e7f3eecf43759d6ccb4 hash.keys
将该hash转为16个10进制数值:
9b0d = 39693
87ac = 34732
8715 = 34581
18cf = 6351
d860 = 55392
1aa4 = 6820
56b5 = 22197
8fa7 = 36775
4c01 = 19457
194c = 6476
feb2 = 65202
5e7f = 24191
3eec = 16108
f437 = 62519
59d6 = 22998
ccb4 = 52404
将这16个数除以108,作为输出额度,构造交易:
交易被收录,证明完成。
方式二:数字摘要的Hash作地址输入
回顾一下地址的生成算法(下图是一个未压缩公钥生成地址的过程,公钥是否压缩对该证明过程没有影响):
我们用数字摘要的Hash值代替图中红色框中的值,然后得到一个地址,我们把0. btc打入该地址,形成交易,收录后完成证明。验证时,需要首先得到数字摘要hash值,再生成对应的地址,核对地址是否一致即可。
这个方法有个缺点,打入该地址的币永远消失了,因为没有其对应的私钥。虽然可以只需1聪,目前价值几乎忽略不计,但毕竟浪费了。该方法可以进一步衍生一些类似的方法。曾有个网站使用之,后来该网站关闭了。
方式三:数字摘要的Hash作私钥
大小介于1 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141之间的数,都可以认为是一个合法的私钥,其大小为32字节。那么,可以把数字摘要的Hash作私钥,并推算出公钥和地址。
将任意币值输出至该地址构成交易,交易收录后,通过私钥再转移走即可。这样便在block chain里留下了这个地址。验证时重复该过程,检查地址是否一致即可。
该方法不会像方法二那样形成浪费,也比较容易操作。我们依然SatoshiDice的种子文件为例,种子数字摘要的Hash为:9b0d87ac871518cfdfa74c01194cfeb25e7f3eecf43759d6ccb4。
借助bitaddress.org,输入私钥(种子hash)后:
得到两把公钥,分别对应两个地址。证明时将币打入任何一个地址即可,建议使用未压缩公钥地址,因为并不是所有客户端都对压缩公钥支持良好。然后将该私钥导入任何一个客户端,再把该地址的钱转移到一个安全的地方。最后,公开私钥和对应收录地址的交易。
方法二浪费,应避免使用。方法一繁琐,需要工具辅助转换。方法三相对容易,门槛低一些,大部分客户端都支持,私钥公钥地址的推导也有很多工具支持。
就这样比特币系统轻松的完成了存在证明,安全稳固,公信力远胜任何第三方、机构、政府。过程极其简单,使得任何一人都可以轻易地做出存在证明,其意义非常重大。可以预见,未来将比特币作为存在证明会得到广泛的应用。

我要回帖

更多关于 20年的老房子值不值买 的文章

 

随机推荐