搭建实现账户和交易操作的区块链示例系统
第一步:
在项目中创建crypto_utils.py文件,并在其中定义相应的函数及功能。
#账户公钥和私钥的生成方式
import ecdsa
import random
import hashlib
import base58
from hashlib import sha256
def create_seed():
"""
创建绝对随机的种子
:return: string绝对随机数
:return:随机数
"""
return ''.join(random.sample('abcdefghijklmnopqrstuvwxyz!@#$%^&*()',32)).encode()
def create_private_key(seed):
"""
使用种子创建密钥
:param seed:创建私钥需要的随机数
:return :以pem形式保存的私钥
"""
return ecdsa.SigningKey.from_string(seed,curve=ecdsa.SECP256k1).to_pem()
def create_public_key(private_key):
"""
使用私钥生成公钥
:param private_key:生成公钥需要的私钥
:return:以pem形式保存的公钥
"""
return ecdsa.SigningKey.from_pem(private_key).verifying_key.to_pem()
def sha256d(string):
"""
双重 SHA-256 哈希计算
将字符串转为哈希值
:param string: 需要计算的字符串(bytes 或 str 类型)
:return: 计算结果的 16 进制字符串形式
"""
if not isinstance(string, bytes):
string = string.encode()
return sha256 (sha256(string).digest()).hexdigest()
#地址生成的具体过程如下:
# 1.利用SHA-256将公开的密钥进行哈希处理生成哈希值
# 2.将第一步中的哈希值通过RT\IPEMD-160进行哈希处理后生成哈希值
# 3.将第二步中的哈希值记性Base58编码
def create_account():
"""
创建区块链用户地址
1.如果代码存在飘红内容,将鼠标移动到飘红点,查看问题,一般性为变量命名错误,或者是包未引入,修改
2.如果存在base58这个包无法解析,解决方法为:pip install base58
"""
new_seed = create_seed()
private_key_pem =create_private_key(new_seed)
public_key_pem = create_public_key(private_key_pem)
in_public_key = ecdsa.VerifyingKey.from_pem(public_key_pem).to_string()
intermediate =hashlib.sha256(in_public_key).digest() #中间哈希值
#作二次hash生成double_hash
ripemd160=hashlib.new('ripemd160')
ripemd160.update(intermediate)
hash160 = ripemd160.digest()
double_hash = hashlib.sha256(hashlib.sha256(hash160).digest()).digest()
#取前四位,作为校验值
checksum = double_hash[:4]
#将中间哈希与校验值拼接
pre_address = hash160 +checksum
#通过base58编码生成地址
address = base58.b58encode(pre_address)
print(f"生成地址是:{address.decode()}")
#将区块链账户三要素返回:adress、private_key、public_key
return{
"address":address.decode(),
'private_key':private_key_pem.decode(),
'public_key':public_key_pem.decode()
}
def data_sign(data, private_key):
"""
签名
:param data: 签名时使用的数据
:param private_key: 签名时使用的私钥
:return:
"""
if not isinstance(data,bytes):
data = data.encode()
sk = ecdsa.SigningKey.from_pem(private_key)
sig = sk.sign(data)
return sig
def data_verify(data, sig, public_key):
"""
验证签名
:param data: 验证时使用的数据
:param sig: 要验证的签名
:param public_key: 验证时使用的公钥
:return: 验证结果,返回0表示成功,返回1表示失败,返回2表示出现问题
"""
if not isinstance(data,bytes):
data = data.encode()
vk = ecdsa.VerifyingKey.from_pem(public_key)
try:
if vk.verify(sig, data):
return 0 # 如果验证成功则返回0
else:
return 1 # 如果验证失败则返回1
except Exception as e:
return 2 # 如果验证出现问题则返回2
第二步:
创建区块链的相关对象
在项目中创建models.py文件,并在其中定义区块链对象。
import hashlib
from datetime import datetime
from crypto_utils import data_sign,sha256d
import binascii
INITIAL_BITS=0x1e777777 #此配置为模块真实区块的数据设置(预留)
#预设一个私钥,用于创建创世区块使用
d_pk="""-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIClncSpsc2Fua3ljeiR2aCNydSFkbXQoQHAleGZl
YiZqoAcGBSuBBAAK\noUQDQgAEyTx/sAlhdNUOwcfnCjOVp9fxMF6DUwSLKFqj2E6sDFuPVrKF9wVWH8J3\nntxWh+kR3GFKcB48v3eTfElUs5L7Zw==\n
-----END EC PRTVATE KEY -----\n
"""
class Transaction (object):
def __init__(self,sender,recipient,data,timestamp,private_key,):
"""
交易初始化
:param sender:发送者的地址
:param recipient:接收者的地址
:param data:交易的内容
:param timestamp:交易的时间戳
:param private_key:发送者的私钥
"""
self.sender = sender
self.recipient=recipient
self.data = data
self.timestamp = timestamp
#生成交易的哈希值
self.id=sha256d(self.to_string())
#生成签名
self.sig = data_sign(self.id,private_key)
def to_string(self):
"""
将交易元素拼接为一个字符串
:return:
"""
return f"{self.sender}{self.recipient}{self.data}" f"{self.timestamp.strftime('%Y/%m/%d %H:%M:%S')}"
def to_json(self):
"""
将交易转换变为一个DICT对象
:return:
"""
return {
"id":self.id,
"sender":self.sender,
"recipient":self.recipient,
"data":self.data,
"timestamp":self.timestamp.strftime('%Y/%m/%d %H:%M:%S'),
"sig":binascii.hexlify(self.sig).decode(),
}
#区块对象
class Block(object):
def __init__(self, index, prev_hash, data, timestamp, bits):
"""
区块的初始化方法,在创建一个区块需传入包括索引号等相关信息
:param index: 区块索引号
:param prev_hash: 前一区块的哈希值
:param data: 区块中需保存的记录
:param timestamp: 区块生成的时间戳
:param bits: 区块需传入的比特值(预留)
"""
self.index = index
self.prev_hash = prev_hash
self.data = data
self.timestamp = timestamp
self.bits = bits
self.nonce = 0
#计算新区块的默克尔根
self.merkle_root=self.calc_merkle_root()
#计算区块哈希值
self.block_hash=self.calc_block_hash()
def to_json(self):
"""
将区块内容以JSON的形式输出
:return:
"""
tx_list = [tx.to_json() for tx in self.data]
# tx_json = json.dumps(tx_list, indent=3)
return {
"index": self.index,
"prev_hash": self.prev_hash,
"merkle_root": self.merkle_root,
"data": tx_list,
"timestamp": self.timestamp.strftime('%Y/%m/%d %H:%M:%S'),
'bits': hex(self.bits)[2:].rjust(8, "0"),
'nonce': hex(self.nonce)[2:].rjust(8, "0"),
'block_hash': self.block_hash
}
def calc_merkle_root(self):
"""
计算默克树的根(Merkle Root)
:return:
"""
calc_txs = [tx.id for tx in self.data]
if len(calc_txs) == 1:
return calc_txs[0]
while len(calc_txs) > 1:
if len(calc_txs) % 2 == 1:
calc_txs.append(calc_txs[-1])
sub_hash_roots = []
for i in range(0, len(calc_txs), 2):
join_str = "".join(calc_txs[i:i+2])
sub_hash_roots.append(hashlib.sha256(join_str.encode()).hexdigest())
calc_txs = sub_hash_roots
return calc_txs[0]
def calc_block_hash(self):
"""
生成区块对应的哈希值
:return:
"""
blockheader = str(self.index) + str(self.prev_hash) \
+ str(self.data) + str(self.timestamp) + \
hex(self.bits)[2:] + str(self.nonce)
h = hashlib.sha256(blockheader.encode()).hexdigest()
self.block_hash = h
return h
# 区块链对象,包括一个以chain为对象的数组
class Blockchain(object):
def __init__(self):
"""
初始化区块链对象,操作包括:
1、定义一个以chain命名的区块链数组
2、在链中加入创世区块(genesis block)
"""
self.chain = []
self.create_genesis_block()
def add_block(self, block):
self.chain.append(block)
def query_block_info(self, index=0):
"""
通过索引值查询区块链chain中的区块信息
"""
block_json = self.chain[index].to_json()
return block_json
def create_genesis_block(self):
"""
创建创世区块
"""
tx = Transaction("0" * 32, "0" * 32, "第一笔交易", datetime.now(), d_pk)
genesis_block = Block(0,
"0" * 64,
[tx],
datetime.now(),
INITIAL_BITS)
self.add_block(genesis_block)
def add_new_block(self, data):
last_block = self.chain[-1]
block = Block(last_block.index + 1,
last_block.block_hash,
data,
datetime.now(),
last_block.bits)
self.chain.append(block)
return last_block.index + 1
# 模拟的节点
class Peer:
def __init__(self):
self.tx_pool = []
def add_tx(self, tx):
self.tx_pool.append(tx)
def clear_pool(self):
self.tx_pool = []
在代码中定义了d_pk变量,其作用为项目启动时内置的私钥,用于创建创世区块时创建第一笔交易时使用。
第三步:
添加项目的其他功能
在项目中创建会创建utils.py文件,用于添加一些通用的方法,本项目中calc_merkle_root用于验证区块数据的正确性。
from hashlib import sha256
def calc_merkle_root(data):
"""
计算默克树的根(Merkle Root)
:return:
"""
calc_txs = [tx.id for tx in data]
if len(calc_txs) == 1:
return calc_txs[0]
while len(calc_txs) > 1:
if len(calc_txs) % 2 == 1:
calc_txs.append(calc_txs[-1])
sub_hash_roots = []
for i in range(0, len(calc_txs), 2):
join_str = "".join(calc_txs[i:i + 2])
sub_hash_roots.append(sha256(join_str.encode()).hexdigest())
calc_txs = sub_hash_roots
return calc_txs[0]
第四步:
创建项目接口,使用Postman进行验证接口正确性
from models import Transaction, Blockchain,Peer
from datetime import datetime
from flask import Flask,request,jsonify
import crypto_utils
import utils
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False
#空数据返回内容
empty_res={
'code':404,
'data':'empty'
}
#执行成功返回内容
success_res={
'code':200,
'data':'ok'
}
#创建区块链
blockchain = Blockchain()
#创建一个模拟节点
peer = Peer()
@app.route("/account_create",methods=['GET'])
def account_create():
"""
创建账户接口
:return:
"""
return jsonify({
'code':200,
'data':crypto_utils.create_account()
})
@app.route('/add_transaction',methods=['POST'])
def add_transaction():
"""
新增一笔交易
:return:
"""
body = request.json
if 'sender'not in body or 'recipient' not in body \
or 'data' not in body or 'private_key' not in body:
return jsonify(empty_res)
new_transaction = Transaction(body['sender'],
body['recipient'],
body['data'],
datetime.now(),
body['private_key'])
peer.tx_pool.append(new_transaction)
return jsonify(success_res)
@app.route('/add_block',methods=['GET'])
def add():
"""
添加新区快接口,将peer节点的交易缓存数据加入新区块中
"""
tx_list=peer.tx_pool
index = blockchain.add_new_block(tx_list)
peer.tx_pool=[]
#数据返回内容,在data中加入新加入的区块的索引
return jsonify({
'code':200,
"data":index
})
@app.route('/query_block',methods=['GET'])
def query_block():
"""
区块查询接口,通过HTTP GET 方法接收查询区块的索引
请求示例如:http://127.0.0.1:5000/query?index=0
:return:
"""
index = int(request.args['index'])
#数据返回内容,返回查询区块的完整信息
return jsonify({
'code':200,
'data':blockchain.query_block_info(index)
})
@app.route('/query_tx',methods=['GET'])
def query_tx():
"""
查询交易接口
:return:
"""
id = request.args['id']
for block in blockchain.chain:
for tx in block.data:
if tx.id == id:
return jsonify({
'code':200,
"data":tx.to_json()
})
return jsonify({
'code':404,
'data':'未查询到相关信息'
})
@app.route('/validate_tx',methods=['POST'])
def validate_tx():
"""
验证交易接口,需要通过post传递交易哈希(id)和公钥(public-key)
"""
res_json= request.json
if 'id' not in res_json or 'public_key' not in res_json:
return jsonify(empty_res)
tx_id = res_json['id']
for block in blockchain.chain:
for tx in block.data:
if tx.id == tx_id:
verification_res = crypto_utils.data_verify(tx.id,tx.sig,res_json['public_key'])
if verification_res ==0:
return jsonify({
'code':200,
"data":'验证成功'
})
else:
return jsonify({
'code':500,
"data":'验证失败!'
})
@app.route('/validate_block', methods=['GET'])
def validate_block():
"""
验证区块接口
:return:
"""
index = int(request.args['index'])
block = blockchain.chain[index]
root = block.merkle_root
calc_root = utils.calc_merkle_root(block.data)
if root == calc_root:
return jsonify({
'code':200,
'data':'ok'
})
else:
return jsonify({
'code':500,
'data':'结果不符'
})
if __name__ =='__main__':
app.run()
(1).创建账户
接口路由:/account_create 方法:GET
响应参数:JSON形式
Code:200表示正确返回,否则为错误返回
Data:账户三要素分别为:
Address:账户地址
Private_key:账户私钥
Public_key:账户公钥
验证创建账户功能,当有如code返回200则表示接口相应正确
(2)新增交易
接口路由:/add_transaction
方法:POST
请求参数:JSON形式,sender:交易发起者地址,recipient:交易接收者地址, data:交易的实际内容,private_key:交易发起者的私钥
响应参数:code:200正确返回,否则返回错误,data:返回的具体信息
使用postman调用配置,当前有code返回200则表示接口相应正确
(3).添加新区块接口
接口路由:/add_block
方法:GET
响应参数: code:200正确返回,否则返回错误,data:新增区块索引
验证区块新增接口,使用postman调用配置,当前有code返回200则表示接口相应正确
(4)区块查询
接口路由:/query_block
方法:GET
请求参数:GET请求参数,index:区块索引值
响应参数: code:200正确返回,否则返回错误,data:查询区块数据
验证区块查询接口,使用postman调用配置,当前有code返回200则表示接口相应正确
查询区块高度为上一次新增区块索引的区块内容。
(5).交易查询
接口路由:/query_tx
方法:GET
请求参数:GET请求参数,id:查询交易哈希值
响应参数: code:200正确返回,否则返回错误,data:查询交易的数据
验证查询交易接口,以GET请求传输交易哈希值,使用postman调用配置。
(6).验证交易
接口路由:/validate_tx
方法:POST
请求参数:id:查询交易哈希值,public_key发送账户的公钥
响应参数: code:200正确返回,否则返回错误,data:返回具体的信息
验证交易验证接口,使用postman以POST形式发送交易哈希值和账户A的公钥,当前有code返回200则表示接口相应正确
(7).验证区块
接口路由:/validate_block
方法:GET
请求参数:GET请求参数,index区块索引值
响应参数: code:200正确返回,否则返回错误,data:返回的具体信息。
验证区块查询接口,使用postman调用配置,当前有code返回200则表示接口相应正确
验证高度为0。
通过以上构建系统的区块、交易、区块链等对象,实现创世区块的创建、默克尔根计算以及基于私钥的交易哈希生成等特殊操作,然后使用Flask等Web服务去框架运行持久化进程,实现·账户创建(地址,公钥,私钥),新增交易(体现交易缓存功能),新增区块(区块打包功能),区块查询,交易查询,交易验证,区块的验证。