一.开发环境准备
- centos7及以上/Ubuntu18.04及以上
- python3.6及以上
- pip20.0.4及以上
- pyCharm 或vscode代码编辑器
- Postman接口测试工具
1.curl命令
curl能访问远程服务器并从远程服务器下载数据,也可发送数据
curl工具在Ubuntu安装命令
apt install curl
#或者可以使用
sudo snap install curl
centos安装命令
yum install curl
2.tree命令
在Ubuntu安装命令
sudo apt install tree
在centos安装命令
yum install tree
- -a :显示所有文件和模式
- -L :显示层级数量
- -d:显示目录名称
- -D:列出文件或目录的更改时间
- -p:列出权限
- -s:列出文件或目录的大小
- -t:按文件更改时间排序
3.jq命令
#Ubuntu
sudo apt-get install jq
#centos
yum install -y epel-release
yum install -y jq
4.在python web服务端
安装flask
pip install Flask
创建一个post/get接口,具体代码可以看Flask框架_南栀北夏ii的博客-CSDN博客
二.密码学基础
1.哈希算法
概念:任意长度的输入转换为固定长度输出的一种方法
特性:抗碰撞型,不可逆性
种类:MD5,SHA-256,SM3
以下代码为使用hashlib的python实现:
#1 引用hanshlib依赖包
import hashlib
# 2。定义需要加密的变量
words = 'Hello World!'
# 3,使用sha256算法加密
hash_code = hashlib.sha256(words.encode()).hexdigest()
# 4.打印加密后的结果
print(hash_code) #7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069
2.非对称加密 算法
公钥加密,私钥解密
私钥验签,公钥解密(私钥签名,公钥验签)
椭圆曲线加密算法(ECC)签名与验签用python实现:
安装ecdsa依赖
pip install ecdsa
import ecdsa
def sign(data,private_key):
"""
签名
:param data:签名时使用的数据
:param private_key:签名时使用的私钥
:return:
"""
sk = ecdsa.SigningKey.from_string(private_key,curve=ecdsa.SECP256k1)
sig = sk.sign(data)
return sig
def verify(data,sig,public_key):
"""
验签
:param data:验签时使用的数据
:param sig:验签时使用的签名
:param public_key:验签时使用的公钥
:return:
"""
vk = ecdsa.VerifyingKey.from_pem(public_key)
try:
if vk.verify(sig,data):
return "验签成功"
else:
return "验签失败"
except Exception as e:
return "验签过程出现问题"
import random
string = "hello world"
private_key = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz!@#$%^&*()',32)).encode()
print(f'私钥是:{private_key}')
public_key = ecdsa.SigningKey.from_string(private_key,curve=ecdsa.SECP256k1).verifying_key.to_pem()
print(f'公钥是:{public_key}')
sig = sign(string.encode(),private_key)
print(f'签名是:{sig}')
print(verify(string.encode(),sig,public_key))
三.实战一:构建具备加密功能的Flask服务端
1.构建哈希算法的处理函数
import hashlib
def hash_encrypt(input):
"""
使用哈希算法加密
:param input:客户端发送的数据
:return:返回给客户端的加密数据
"""
hash_code = hashlib.sha256(input.encode()).hexdigest()
return hash_code
2.在程序运行入口app.py中加入哈希算法处理的HTTP请求响应
from flask import Flask,request
# 导入你加密的哈希算法函数文件
import shixun_1
app = Flask(__name__)
@app.route("/")
def index():
return "hello world"
@app.route("/encrypt",methods=['GET'])
def encrypt():
"""
哈希函数加密
:return:加密的字典对象
"""
#请求获取名为data的数据
data = request.args.get('data')
#运用shixun_1的hash_encrypt函数对data数据经行哈希加密
res = shixun_1.hash_encrypt(data)
#返回加密的数据
return {
'res':res
}
if __name__ == "__main__":
app.run()
3,运行python文件后,打开Postman验证开发接口
四.区块链的区块与账本
区块:用于存储电子信息的载体
区块:区块头和区块体
区块头:父块哈希,默克尔跟,挖矿难度,随机数,时间戳
区块体:交易记录,交易数量
区块链账本:区块(账页),区块体(账目),区块头(账目相关的关键信息)
1.账本的具体实现(创世区块)
- Block对象:代表区块的概念
- Blockchain对象:代表一个简易版的区块链
注意下面的代码文件名为qulian.py
import json
import hashlib
from datetime import datetime
INITIAL_BITS = 0x1e777777
#区块对象
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.elapsed_time = ""
self.block_hash = self.calc_block_hash()
def to_json(self):
"""
将区块内容以JSON的形式输出
:return:
"""
return {
"index": self.index,
"prev_hash": self.prev_hash,
"data": self.data,
"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_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
class Blockchain(object):
def __init__(self):
"""
初始化区块链对象,操作包括:
1、定义一个以chain命名的区块链数组
2、在链中加入创世区块(genesis block)
"""
self.chain = []
self.create_genesis_block()
def add_block(self, block):
"""
将新的区块加入区块链chain中,该方法将不被外界调用
:param block: 新加入的区块
:return:
"""
self.chain.append(block)
def query_block_info(self, index=0):
"""
通过索引值查询区块链chain中的区块信息
:param index: 查询区块的索引值
:return:
"""
return json.dumps(self.chain[index].to_json(), sort_keys=True, ensure_ascii=False, indent=2)
def create_genesis_block(self):
"""
创建创世区块,创世区块内容如下:
index -> 设置为0,代表第一个区块
prev_hash ->设置为64个"O"作为默认参数
data ->存储一段字符串
:return:
"""
genesis_block = Block(0,
"0" * 64,
"这是第一个区块(创世区块)",
datetime.now( ),
INITIAL_BITS)
self.add_block(genesis_block)
def add_new_block(self, data) :
"""
可供调用的方法,用于添加新的区块
:param data:
:return:
"""
last_block = self.chain[-1]
#通过last_block获取chain中的最新区块,从而获取相应的index和prev_hash
# #用于创建新区块时使用
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
创建test.py,加入以下代码:
import qulian
# 创建区块链
blockchain = qulian.Blockchain()
# 查询区块高度为0,创世区块信息
print(blockchain.query_block_info(0))
from time import sleep
sleep(1)
# 添加新区快,高度为1
blockchain.add_new_block("新增区块")
print(blockchain.query_block_info(1))
2.实战二:构建简单的区块链账本系统
- 创建shixun_2.py文件,加入以下代码:
import json
import hashlib
from datetime import datetime
INITIAL_BITS = 0x1e777777
#区块对象
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.elapsed_time = ""
self.block_hash = self.calc_block_hash()
def to_json(self):
"""
将区块内容以JSON的形式输出
:return:
"""
return {
"index": self.index,
"prev_hash": self.prev_hash,
"data": self.data,
"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_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
class Blockchain(object):
def __init__(self):
"""
初始化区块链对象,操作包括:
1、定义一个以chain命名的区块链数组
2、在链中加入创世区块(genesis block)
"""
self.chain = []
self.create_genesis_block()
def add_block(self, block):
"""
将新的区块加入区块链chain中,该方法将不被外界调用
:param block: 新加入的区块
:return:
"""
self.chain.append(block)
def query_block_info(self, index=0):
"""
通过索引值查询区块链chain中的区块信息
:param index: 查询区块的索引值
:return:
"""
return json.dumps(self.chain[index].to_json(), sort_keys=True, ensure_ascii=False, indent=2)
def create_genesis_block(self):
"""
创建创世区块,创世区块内容如下:
index -> 设置为0,代表第一个区块
prev_hash ->设置为64个"O"作为默认参数
data ->存储一段字符串
:return:
"""
genesis_block = Block(0,
"0" * 64,
"这是第一个区块(创世区块)",
datetime.now( ),
INITIAL_BITS)
self.add_block(genesis_block)
def add_new_block(self, data) :
"""
可供调用的方法,用于添加新的区块
:param data:
:return:
"""
last_block = self.chain[-1]
#通过last_block获取chain中的最新区块,从而获取相应的index和prev_hash
# #用于创建新区块时使用
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
- 编写Flask项目的HTTP接口,创建app_1.py文件
# 导入你的区块链的代码
import shixun_2
from flask import Flask,request,jsonify
app_1 = Flask(__name__)
app_1.config['JSON_AS_ASCII'] = False
blockchain = shixun_2.Blockchain()
@app_1.route('/add',methods=['POST'])
def add():
"""
添加新区快接口,接口形式为POST
请求时通过Body中加入类似{"data":"..."}的形式作为新区快的内容
:return:
"""
body = request.json
index = blockchain.add_new_block(body['data'])
# 数据返回内容,在data中加入新区快的索引
return jsonify({
'code':200,
'data':index
})
@app_1.route('/query',methods=['GET'])
def query():
"""
区块查询接口,通过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)
})
if __name__ == '__main__':
app_1.run()
- 用postman验证功能正确性
五.区块链的账户和交易
1.区块链中的账户
账户包含三要素:地址,公钥,私钥
账户公钥私钥的生成方式:
import ecdsa
import random
# 1.生成种子(随机数)
seed = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz!@#$%^&*()',32)).encode()
# 2.生成私钥
private_key = ecdsa.SigningKey.from_string(seed,curve=ecdsa.SECP256k1).to_pem()
# 3.生成公钥
publish_key = ecdsa.SigningKey.from_pem(private_key).verifying_key.to_pem()
# 4.输出打印
print(f'{private_key}')
print(f'{publish_key}')
账户地址的生成方式:
import ecdsa
import random
import hashlib
import base58
from hashlib import sha256
def create_seed():
"""
创建密钥对应的种子
: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_publish_key(private_key):
"""
使用私钥生成公钥
:param private_key:生成公钥需要的私钥
:return:以pem的形式保存公钥
"""
return ecdsa.SigningKey.from_pem(private_key).verifying_key.to_pem()
# 地址生成步骤如下:
# 1.利用SCA-256将公开密钥进行哈希处理生成哈希值
# 2.将第一步中的哈希值通过RIPEMD-160进行哈希处理后生成新的哈希值
# 3.将第二步中的哈希值记作Base58编码
def create_account():
"""
生成账户
:return:账户三要素:地址、私钥、公钥
"""
new_seed = create_seed()
private_key_pem = create_private_key(new_seed)
public_key_pem = create_publish_key(private_key_pem)
# 中间哈希值
in_public_key = ecdsa.VerifyingKey.from_pem(public_key_pem).to_string()
intermediate = hashlib.sha256(in_public_key).digest()
# 作二次hash生成doubl_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()}')
return{
'address':address.decode(),
'private_key':private_key_pem.decode(),
'public_key':public_key_pem.decode()
}
# 验证
print(create_account())
2.区块链中的交易
交易:一种特殊的数据存贮结构
交易在区块中的数据存贮方式
交易数据以默克尔根的形式进行存贮:
import hashlib
class MerkleTree(object):
def __init__(self,tx_list):
self.tx_list = tx_list
def calc_markle_root(self):
"""
计算Merkle根
"""
calc_txs = self.tx_list
if len(calc_txs) == 1:
# 如果交易长度为1,直接返回交易值
return calc_txs[0]
# 如果交易值长度大于1
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):
# 生成的哈希值进行累加
sub_hash_roots.append(hashlib.sha256("".join(calc_txs[i:i+2]).encode()).hexdigest())
# 将累加为新的哈希值列表赋值为下一次循环的内容
calc_txs = sub_hash_roots
return calc_txs[0]
# 验证
tx_list = ['1','2','3','4','5']
m_t = MerkleTree(tx_list)
merckle_root = m_t.calc_markle_root();
print(merckle_root)
3.实战三:交易对象的创建
注意:交易的验证方法:私钥签名,公钥验签
import ecdsa
import random
import hashlib
import base58
from hashlib import sha256
def create_seed():
"""
创建密钥对应的种子
: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_publish_key(private_key):
"""
使用私钥生成公钥
:param private_key:生成公钥需要的私钥
:return:以pem的形式保存公钥
"""
return ecdsa.SigningKey.from_pem(private_key).verifying_key.to_pem()
def sha256d(string):
"""
将字符串转为哈希值
:param string:
:return:
"""
if not isinstance(string,bytes):
string = string.encode()
return sha256(sha256(string).digest()).hexdigest()
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:
"""
if not isinstance(data, bytes):
data = data.encode()
vk = ecdsa.VerifyingKey.from_pem(public_key)
try:
if vk.verify(sig,data):
return "验签成功"
else:
return "验签失败"
except Exception as e:
print(e)
return "验签过程出现问题"
# 地址生成步骤如下:
# 1.利用SCA-256将公开密钥进行哈希处理生成哈希值
# 2.将第一步中的哈希值通过RIPEMD-160进行哈希处理后生成新的哈希值
# 3.将第二步中的哈希值记作Base58编码
def create_account():
"""
生成账户
:return:账户三要素:地址、私钥、公钥
"""
new_seed = create_seed()
private_key_pem = create_private_key(new_seed)
public_key_pem = create_publish_key(private_key_pem)
# 中间哈希值
in_public_key = ecdsa.VerifyingKey.from_pem(public_key_pem).to_string()
intermediate = hashlib.sha256(in_public_key).digest()
# 作二次hash生成doubl_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()}')
return{
'address':address.decode(),
'private_key':private_key_pem.decode(),
'public_key':public_key_pem.decode()
}
# 验证
print(create_account())
import hashlib
from datetime import datetime
from crypto_utils import data_sign,sha256d
import binascii
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(),
}
import crypto_utils
import model
from datetime import datetime
# 1.创建两个账户,一个模拟发送账户,一个模拟接受账户
print(f"{'*' * 10}生成第一个账户{'*' * 10}")
send_account = crypto_utils.create_account()
print(f"{'*' * 10}生成第二个账户{'*' * 10}")
rec_account = crypto_utils.create_account()
# 2.生成一个测试交易内容
tx = model.Transaction(send_account['address'],
rec_account['address'],
"测试交易",datetime.now(),send_account['private_key']
)
# 3.验证交易正确性
verification_res = crypto_utils.data_verify(tx.id,tx.sig,send_account['public_key'])
print(f'验证交易,返回结果:{verification_res}')