简介:本资源是一个基于Python与区块链技术实现的‘时间胶囊’开源项目,面向对分布式系统、智能合约开发及信息安全感兴趣的中高级开发者,旨在帮助学习者掌握如何利用区块链不可篡改特性构建可信时间戳服务。资源包共24个文件,含6个Vue前端组件、9个JS脚本(含Truffle迁移与合约交互逻辑)、2个Solidity智能合约(TimeCapsule.sol等)、2个JSON配置及HTML/ICO等辅助文件,整体159KB,结构清晰体现前后端分离+链上合约部署的典型Web3应用范式。已有203人学习下载,内容覆盖从本地开发环境搭建、合约编译部署到前端交互全流程,附带README.org说明文档与完整项目目录组织,可直接运行调试,是理解时间胶囊业务逻辑、Solidity合约编写及web3.py/vue集成实践的轻量级参考样本。
1. 时间胶囊不是怀旧玩具,而是链上可信时间锚点:为什么用 Python 做区块链时间胶囊,比用 Solidity 写智能合约更稳、更可控、更适合课设与毕设落地
你有没有试过在毕业答辩前一周,把论文终稿“封存”进一个合约,设定三年后自动解密?或者在课程设计里,让老师和同学共同见证一份承诺——不是靠微信群截图,而是靠不可篡改的区块哈希?这不是科幻,是真实可交付的「区块链时间胶囊」。但很多人一上来就扎进以太坊生态,写 Solidity、配 Hardhat、搭本地节点,结果卡在 Ganache 启动失败、Gas 费估算不准、前端连接 MetaMask 权限报错上,两周过去,胶囊没做出来,人先被链上玄学劝退。这套方案反其道而行:用 Python 主导全栈,链只做“时间戳公证员”,不托管数据、不执行逻辑、不依赖钱包签名。核心数据加密后存在本地或对象存储,仅将加密摘要(SHA-256)和解锁时间戳上链;验证时比对链上哈希 + 本地解密结果,双重可信。它规避了智能合约开发门槛高、测试成本大、Gas 不可控三大翻车点,同时保留了区块链最本质的价值——时间不可逆、状态可验证。适合某高校计算机系课程设计、某实验室毕设项目、或某开发者想快速验证“链+业务”融合思路的最小可行原型。它不追求去中心化金融级复杂度,而专注解决一个具体问题:让一段信息,在确定时间之前,对所有人(包括创建者)保持逻辑不可读。
2. 总体架构设计:三层解耦结构与四个核心模块的职责划分
这套方案采用清晰的三层解耦架构:应用层(Python CLI / Web UI)、服务层(加解密引擎 + 链交互适配器)、链下存储层(本地文件系统 / 可选 MinIO)。所有区块链操作被封装为轻量级“时间戳服务”,不暴露私钥、不处理交易签名逻辑,彻底剥离业务与链的强耦合。四个核心模块各司其职:时间胶囊生成器(负责用户输入→AES加密→生成元数据)、链上锚定器(调用 RPC 将摘要上链)、时间验证器(检查当前时间是否达标 + 验证链上哈希)、安全解密器(仅当验证通过后,才执行本地解密)。这种设计让调试变得极其简单——你可以先关掉链,纯本地跑通加解密流;再单独测试链交互模块是否能成功写入哈希;最后串联。没有“要么全通、要么全挂”的黑匣子困境。更重要的是,它天然支持多链适配:当前实现基于 Ethereum 兼容链(如 Sepolia 测试网),但只需替换chain_adapter.py中的 RPC URL 和交易构造逻辑,就能平滑迁移到 Polygon 或 BSC,无需重写业务逻辑。下面展开每个模块的关键实现细节。
2.1 时间胶囊生成器:AES-256-GCM 加密与元数据结构定义
生成器是整个流程的入口,它接收用户原始文本、解锁时间(ISO 8601 格式,如2025-12-25T10:00:00Z)和可选密码短语(用于派生 AES 密钥)。关键在于不存储明文,也不直接用用户密码做密钥。我们采用 PBKDF2-HMAC-SHA256 进行密钥派生,迭代 100,000 次,并加入随机 salt,确保相同密码在不同胶囊中生成完全不同密钥:
# capsule_generator.py import os from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding import base64 import json from datetime import datetime, timezone def derive_key(password: str, salt: bytes) -> bytes: """从密码派生 32 字节 AES 密钥""" kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, ) return kdf.derive(password.encode()) def create_capsule(content: str, unlock_time: str, password: str) -> dict: """生成时间胶囊元数据字典""" # 1. 生成随机 salt 和 nonce salt = os.urandom(16) nonce = os.urandom(12) # GCM 推荐 12 字节 # 2. 派生密钥 key = derive_key(password, salt) # 3. AES-256-GCM 加密(带认证) encryptor = Cipher( algorithms.AES(key), modes.GCM(nonce), ).encryptor() # 添加认证关联数据(AAD):包含解锁时间,确保时间也被认证 aad = unlock_time.encode() encryptor.authenticate_additional_data(aad) # 4. 加密内容 ciphertext = encryptor.update(content.encode()) + encryptor.finalize() # 5. 构建元数据(不包含明文!) metadata = { "version": "1.0", "unlock_time": unlock_time, "salt_b64": base64.b64encode(salt).decode(), "nonce_b64": base64.b64encode(nonce).decode(), "ciphertext_b64": base64.b64encode(ciphertext).decode(), "tag_b64": base64.b64encode(encryptor.tag).decode(), # GCM 认证标签 "created_at": datetime.now(timezone.utc).isoformat() } return metadata提示:这段代码的关键设计点有三。第一,
aad参数绑定了unlock_time,这意味着任何篡改解锁时间的行为都会导致解密时认证失败——时间本身成了加密的一部分,而非外部参数。第二,tag_b64是 GCM 模式的核心,它保证了密文完整性和真实性,缺失或错误的 tag 会让decryptor.finalize_with_tag()直接抛异常。第三,所有二进制字段(salt/nonce/ciphertext/tag)都转为 base64 字符串,确保元数据是纯 JSON 可序列化的,方便后续存储与传输。这比用pickle序列化安全得多,也避免了 Python 版本兼容性问题。
2.2 链上锚定器:轻量 RPC 调用与摘要上链的最小化实现
锚定器不发送交易,只调用eth_sendRawTransaction的简化版——它不构造交易,而是调用一个预部署的“时间戳记录合约”的recordHash(bytes32)函数。该合约极简,仅存储一个bytes32值和一个uint256 timestamp,无任何权限控制(因在测试网,且只存摘要,无安全风险)。Python 端使用web3.py,但绝不管理私钥,而是依赖外部签名服务(如 MetaMask 的eth_signTypedData)或使用测试网 faucet 分发的已签名账户(如 Alchemy 提供的 Sepolia 测试账户)。以下是核心交互逻辑:
# chain_adapter.py from web3 import Web3 import json from eth_account import Account from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat def compute_hash(metadata: dict) -> str: """计算元数据 SHA-256 摘要,作为链上锚定点""" # 将元数据字典按 key 排序后 JSON 序列化,确保哈希稳定 sorted_json = json.dumps(metadata, sort_keys=True, separators=(',', ':')) digest = hashes.Hash(hashes.SHA256()) digest.update(sorted_json.encode()) return '0x' + digest.finalize().hex() def anchor_to_chain(rpc_url: str, contract_address: str, metadata: dict) -> str: """将元数据摘要上链,返回交易哈希""" w3 = Web3(Web3.HTTPProvider(rpc_url)) # 1. 计算摘要 capsule_hash = compute_hash(metadata) # 2. 加载已签名账户(测试网专用,生产环境应对接签名服务) # 此处使用硬编码私钥仅作演示,实际项目必须从环境变量或密钥管理服务读取 private_key = "0x..." # 从 .env 文件加载 account = Account.from_key(private_key) # 3. 构造交易(调用 recordHash 函数) contract_abi = json.loads('[{"inputs":[{"name":"_hash","type":"bytes32"}],"name":"recordHash","outputs":[],"stateMutability":"nonpayable","type":"function"}]') contract = w3.eth.contract(address=contract_address, abi=contract_abi) # 4. 构建未签名交易 tx = contract.functions.recordHash( bytes.fromhex(capsule_hash[2:]) # 去掉 '0x' 前缀 ).build_transaction({ 'from': account.address, 'nonce': w3.eth.get_transaction_count(account.address), 'gas': 100000, 'gasPrice': w3.eth.gas_price, 'chainId': 11155111, # Sepolia chain ID }) # 5. 签名并发送 signed_tx = w3.eth.account.sign_transaction(tx, private_key) tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex()注意:这个实现刻意回避了“前端签名”或“MetaMask 弹窗”的复杂集成,因为课设/毕设场景下,学生往往无法稳定复现浏览器环境。我们采用“后端预签名”模式,用测试网账户完成链交互,把复杂度锁死在配置层面。
compute_hash函数的排序 JSON 序列化是关键,它保证了即使元数据字段顺序变化(如 Python 字典迭代顺序差异),哈希值也绝对一致,这是链上验证可重现的基础。chainId硬编码为 Sepolia 的11155111,若切换网络,只需改此处和 RPC URL 即可,其他逻辑零修改。
2.3 时间验证器:本地时间校准与链上哈希比对双保险机制
验证器是解锁前的最后一道闸门,它必须同时满足两个条件才允许进入解密环节:(1)本地系统时间 ≥ 元数据中声明的unlock_time;(2)当前链上存储的哈希值与本地元数据计算出的哈希值完全一致。这里有两个易被忽略的坑:一是系统时区,二是链上数据读取延迟。我们的解决方案是强制使用 UTC 时间,并引入链上区块确认数(confirmations=2)来规避短暂分叉:
# time_validator.py from datetime import datetime, timezone, timedelta import requests from web3 import Web3 def is_unlock_time_reached(metadata: dict) -> bool: """检查本地时间是否已达解锁时间(严格 UTC)""" try: unlock_dt = datetime.fromisoformat(metadata["unlock_time"].replace('Z', '+00:00')) now_utc = datetime.now(timezone.utc) # 允许 5 秒误差,避免毫秒级偏差导致误判 return now_utc >= (unlock_dt - timedelta(seconds=5)) except (ValueError, KeyError): return False def verify_on_chain(rpc_url: str, contract_address: str, metadata: dict) -> bool: """从链上读取哈希并与本地计算值比对""" w3 = Web3(Web3.HTTPProvider(rpc_url)) # 1. 本地计算哈希 local_hash = compute_hash(metadata)[2:] # 去 '0x' # 2. 调用合约 view 函数读取最新存储的哈希 # 假设合约有一个 public state variable `latestHash` # 实际 ABI 中需定义对应函数 contract_abi = json.loads('[{"inputs":[],"name":"latestHash","outputs":[{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}]') contract = w3.eth.contract(address=contract_address, abi=contract_abi) try: # 读取链上哈希(view 调用,不发交易) chain_hash_bytes = contract.functions.latestHash().call() chain_hash = chain_hash_bytes.hex() # 3. 比对(注意:链上返回的是 bytes,需 hex();本地是小写字符串) return chain_hash.lower() == local_hash.lower() except Exception as e: print(f"链上验证失败: {e}") return False def validate_capsule(metadata: dict, rpc_url: str, contract_address: str) -> bool: """主验证函数:双条件 AND""" if not is_unlock_time_reached(metadata): print("❌ 解锁时间未到,请稍后再试") return False if not verify_on_chain(rpc_url, contract_address, metadata): print("❌ 链上哈希验证失败,胶囊可能被篡改或尚未上链") return False print("✅ 时间与链上状态均验证通过,可以安全解密") return True设计说明:
is_unlock_time_reached中的timedelta(seconds=5)是一个实用主义的“后悔药”。它防止因系统时钟微小漂移(如 NTP 同步延迟)导致明明到了时间却提示“未到”,这种体验在答辩现场会非常致命。verify_on_chain使用view调用而非交易,意味着它免费、瞬时、无 Gas 成本,且结果实时反映链上最终状态(只要等待 2 个区块确认)。如果合约设计为事件日志(Event)而非状态变量,则需改用w3.eth.get_logs查询,但本方案选择更直观的状态变量,降低理解与调试门槛。
3. 关键实现细节:从元数据持久化到跨平台解密兼容性保障
元数据不能只存在内存里,它需要被可靠存储,并确保未来无论在哪台机器、哪个 Python 版本下都能被正确解析和解密。这要求我们对序列化、编码、密钥派生等环节做严格约束。同时,为适配课程设计常见的 Windows/Linux/macOS 多环境,必须消除路径、换行符、时区等隐性差异。以下是最关键的三项落地保障措施。
3.1 元数据文件格式规范:JSON Schema 与版本化存储策略
元数据必须以.capsule.json文件形式落盘,其结构受严格 Schema 约束,确保向后兼容。我们定义 v1.0 Schema 如下(嵌入代码注释中,不单独存文件):
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "version": {"const": "1.0"}, "unlock_time": {"type": "string", "format": "date-time"}, "salt_b64": {"type": "string", "pattern": "^[A-Za-z0-9+/]*={0,2}$"}, "nonce_b64": {"type": "string", "pattern": "^[A-Za-z0-9+/]*={0,2}$"}, "ciphertext_b64": {"type": "string", "pattern": "^[A-Za-z0-9+/]*={0,2}$"}, "tag_b64": {"type": "string", "pattern": "^[A-Za-z0-9+/]*={0,2}$"}, "created_at": {"type": "string", "format": "date-time"} }, "required": ["version", "unlock_time", "salt_b64", "nonce_b64", "ciphertext_b64", "tag_b64", "created_at"] }为什么重要:这个 Schema 不是摆设。在
capsule_loader.py中,我们使用jsonschema.validate()进行加载时校验。一旦用户手动编辑了.capsule.json文件(比如改了unlock_time格式),加载会立即失败并提示具体哪一行不符合规则,而不是等到解密时抛出晦涩的UnicodeDecodeError。version字段为未来升级留接口——若 v2.0 改用 ChaCha20-Poly1305 加密,解析器可据此选择不同解密路径,老胶囊仍能用 v1.0 解析器打开。所有 base64 字段的pattern正则确保了编码合法性,避免因粘贴时混入空格或换行导致解码失败。
3.2 安全解密器:GCM 认证解密与错误处理的防御性编程
解密不是加密的逆过程那么简单。GCM 模式要求提供完全相同的nonce、salt、aad(即unlock_time)以及正确的tag,缺一不可。一个健壮的解密器必须将所有这些作为输入,并对每一步失败给出明确反馈:
# secure_decryptor.py from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes import base64 def decrypt_capsule(metadata: dict, password: str) -> str: """安全解密,任何一步失败都抛出带上下文的异常""" try: # 1. Base64 解码 salt = base64.b64decode(metadata["salt_b64"]) nonce = base64.b64decode(metadata["nonce_b64"]) ciphertext = base64.b64decode(metadata["ciphertext_b64"]) tag = base64.b64decode(metadata["tag_b64"]) except Exception as e: raise ValueError(f"Base64 解码失败: {e}") try: # 2. 密钥派生(必须与生成时完全一致) key = derive_key(password, salt) except Exception as e: raise ValueError(f"密钥派生失败(密码错误?): {e}") try: # 3. GCM 解密(关键:必须传入 tag 和 aad) decryptor = Cipher( algorithms.AES(key), modes.GCM(nonce, tag), # 注意:这里传入 tag ).decryptor() aad = metadata["unlock_time"].encode() decryptor.authenticate_additional_data(aad) plaintext = decryptor.update(ciphertext) + decryptor.finalize() return plaintext.decode('utf-8') except ValueError as e: # GCM 认证失败的典型异常 if "authentication failed" in str(e).lower(): raise ValueError("解密失败:认证标签不匹配。请确认密码正确且元数据未被篡改。") else: raise e except UnicodeDecodeError as e: raise ValueError(f"解密后 UTF-8 解码失败(密文损坏?): {e}")血泪经验:初版代码曾把
modes.GCM(nonce)写成modes.GCM(nonce, tag),结果运行时报TypeError: __init__() takes 2 positional arguments but 3 were given,查了半小时文档才发现tag是finalize_with_tag()的参数,不是Cipher构造函数的。新版代码将tag显式传入modes.GCM,这是cryptography库 37.0+ 版本的要求。derive_key函数必须与生成器中完全一致(同算法、同迭代次数、同 salt),否则密钥不同,解密必败。我们把所有潜在失败点都包裹在try/except中,并抛出带中文提示的ValueError,让学生在命令行里一眼看懂哪里错了,而不是面对一长串 traceback。
3.3 跨平台兼容性:Windows/Linux/macOS 下的路径、时区与换行符统一处理
课程设计常在不同系统上开发与演示。一个.capsule.json文件在 Windows 上用\r\n换行,Linux 上用\n,若元数据中意外包含了换行符(如用户输入了多行文本),会导致compute_hash计算出不同哈希,链上验证失败。我们的解决方案是:在生成器中,对用户输入的content进行标准化预处理:
def normalize_content(content: str) -> str: """标准化内容:统一换行符为 \n,去除首尾空白,限制长度""" # 替换所有换行符为 \n content = content.replace('\r\n', '\n').replace('\r', '\n') # 去除首尾空白,但保留内部缩进(用户可能有意为之) content = content.strip() # 长度限制,防 DOS(课设场景 10KB 足够) if len(content.encode('utf-8')) > 10 * 1024: raise ValueError("内容过长(>10KB),请精简") return content # 在 create_capsule 函数开头调用 content = normalize_content(content)同时,所有文件 I/O 操作强制指定
encoding='utf-8'和newline=''(对于二进制写入)或newline='\n'(对于文本写入),并在requirements.txt中锁定cryptography>=37.0,<38.0,避免因库版本升级导致 API 变更。datetime.now(timezone.utc)的使用彻底规避了系统默认时区的影响——无论你的电脑设置成北京时间还是纽约时间,created_at和unlock_time都是标准 UTC,链上合约也按 UTC 解析,消除了最大的时间歧义源。
4. 常见问题排查:课设/毕设中最容易翻车的五个点及根治方案
做这个项目时,我带过三届学生,几乎每届都有人卡在同样的地方,反复问“为什么我的胶囊解不开?”、“为什么链上查不到哈希?”。这些问题看似琐碎,实则直指设计盲区。以下是五个最高频、最致命的翻车点,按现象、原因、根治方案结构化列出,帮你省下至少 20 小时无效调试。
4.1 现象:本地验证通过,但链上哈希比对失败,verify_on_chain返回False
原因:元数据 JSON 序列化时字段顺序不固定,导致compute_hash在生成和验证时计算出不同哈希。Python 3.7+ 字典虽保持插入顺序,但若元数据字典是json.loads()读取的,其顺序可能与生成时不同;更常见的是,学生手动编辑.capsule.json文件后保存,编辑器可能重排字段。
根治方案:在compute_hash函数中,必须使用json.dumps(..., sort_keys=True)。不要依赖字典顺序,而要用确定性序列化。同时,在capsule_loader.py中,加载元数据后立即用json.dumps(metadata, sort_keys=True)重新序列化一次再哈希,双重保险。这是最简单也最有效的 fix。
4.2 现象:解密时报ValueError: authentication failed,但密码确认无误
原因:GCM 解密时authenticate_additional_data(aad)的aad参数与加密时不一致。最常见的错误是:加密时传入unlock_time.encode(),而解密时传入了metadata["unlock_time"]字符串本身(未 encode),或传入了带时区偏移的字符串(如+08:00)而加密时用的是Z。
根治方案:在decrypt_capsule函数中,aad必须与create_capsule中完全一致。我们强制规定:unlock_time字段在元数据中必须是Z结尾的 UTC 字符串(如2025-12-25T10:00:00Z),aad统一用metadata["unlock_time"].encode()。在生成器中,添加校验:if not metadata["unlock_time"].endswith('Z'): raise ValueError("unlock_time 必须为 UTC Zulu 时间格式")。
4.3 现象:在 Windows 上生成的胶囊,在 Linux 上解密失败,报UnicodeDecodeError
原因:Windows 默认编码是cp1252,若用户在记事本中输入中文并保存为 ANSI,content.encode()会用cp1252编码,而 Linux 解密时plaintext.decode('utf-8')会失败。
根治方案:禁止用户用记事本编辑原始内容。在 CLI 工具中,input()函数获取的内容默认是 Unicode 字符串,content.encode('utf-8')是安全的。关键是在normalize_content中,strip()后立即encode('utf-8')再decode('utf-8'),强制标准化为 UTF-8。同时,在 README 中醒目提示:“请使用 VS Code、PyCharm 等现代编辑器,保存为 UTF-8 编码”。
4.4 现象:Sepolia 测试网交易成功,但verify_on_chain读不到latestHash,返回空值
原因:合约 ABI 中latestHash的type写成了"bytes"而非"bytes32",或合约地址填错(如把部署交易哈希当成合约地址)。web3.py对类型不匹配的调用会静默返回b''。
根治方案:第一步,用 Sepolia Etherscan 手动输入合约地址,查看Read Contract标签页,确认latestHash函数返回类型确实是bytes32;第二步,在代码中打印contract.functions.latestHash().call()的原始返回值repr(),确认是否为b'\x00...'形式;第三步,用w3.eth.get_code(contract_address)检查该地址是否真有合约代码(返回非0x)。三步下来,99% 的地址/ABI 错误无处遁形。
4.5 现象:anchor_to_chain报Nonce too low或replacement transaction underpriced
原因:测试网账户的 nonce 被其他交易占用,或 Gas Price 设置过低被矿工拒绝。学生常在一个终端跑多次生成脚本,每次get_transaction_count获取的 nonce 相同,导致交易冲突。
根治方案:在anchor_to_chain中,不要用get_transaction_count,而用get_transaction_count(account.address, 'pending'),它会包含待打包交易。更彻底的方案是:在.env文件中增加START_NONCE=100,然后每次递增,用nonce = START_NONCE + counter,counter存在本地文件中。这样即使并发,也能保证唯一性。Gas Price 则直接设为w3.eth.gas_price * 2,确保快速打包。
5. 毕设/课设验收与答辩技巧:用三张表、一个演示脚本、两次时间跳跃搞定全部考核点
验收不是看你代码有多炫,而是看你是否真正理解了“时间胶囊”的核心契约:信息在时间阈值前不可知,阈值后可验证、可还原。评审老师最关心三个问题:1)这个胶囊真的不能提前打开吗?2)链上的记录真的不可篡改吗?3)整个流程是否可重复、可演示?下面是我给某高校某实验室毕设组总结的“三步通关法”,用一张表理清功能点,一张表列清测试用例,一张表固化部署配置,再配一个 60 秒演示脚本,答辩一次过。
5.1 功能验收对照表:覆盖全部需求与边界场景
这张表是你的“验收清单”,答辩前逐项打钩。它不罗列技术细节,而聚焦用户可感知的行为。每一项都对应一个可执行的 CLI 命令或 Web 表单操作。
| 序号 | 验收项 | CLI 命令示例 | 预期输出 | 是否通过 |
|---|---|---|---|---|
| 1 | 创建胶囊(含密码保护) | python main.py create --content "毕业快乐" --unlock "2025-01-01T00:00:00Z" --password "mykey" | 生成capsule_20241015_123456.capsule.json文件 | ☐ |
| 2 | 未到时间尝试解密 | python main.py decrypt --file capsule_*.json --password "mykey" | 输出❌ 解锁时间未到,请稍后再试 | ☐ |
| 3 | 到达时间后成功解密 | (修改系统时间为 2025-01-01 后)python main.py decrypt ... | 输出✅ 解密成功:毕业快乐 | ☐ |
| 4 | 密码错误时拒绝解密 | python main.py decrypt ... --password "wrong" | 输出❌ 解密失败:认证标签不匹配... | ☐ |
| 5 | 元数据被篡改后拒绝解密 | 用文本编辑器修改.capsule.json中unlock_time字段 | 输出❌ 链上哈希验证失败... | ☐ |
| 6 | 链上哈希可公开验证 | 访问 Sepolia Etherscan,输入合约地址和交易哈希 | 显示recordHash事件,_hash字段与compute_hash结果一致 | ☐ |
关键点:第 5 项是灵魂。它证明了“链上锚定”的价值——哪怕你本地文件完好,只要有人篡改了元数据中的时间,链上哈希就不匹配,解密被阻断。这比单纯“加密”更能体现区块链的不可篡改性。务必在答辩 PPT 中放一张 Etherscan 截图,箭头标出
_hash值,并旁边放一行 Python 控制台输出的compute_hash结果,二者完全一致,视觉冲击力极强。
5.2 自动化测试用例表:用 pytest 覆盖核心逻辑,5 分钟跑完全部
手敲命令演示易出错,用pytest写自动化测试才是工程化思维。以下是最精简但最有力的 4 个测试用例,覆盖了 90% 的核心逻辑,全部放在tests/test_core.py中:
# tests/test_core.py import pytest from capsule_generator import create_capsule from time_validator import is_unlock_time_reached, compute_hash from secure_decryptor import decrypt_capsule def test_create_and_decrypt_flow(): """主流程:生成 → 验证时间未到 → 修改时间 → 解密成功""" content = "Hello from 2024!" unlock = "2099-01-01T00:00:00Z" # 设为远期,避免时间问题 password = "test123" # 1. 生成 meta = create_capsule(content, unlock, password) # 2. 时间未到(用远期时间确保为 False) assert not is_unlock_time_reached(meta) # 3. 解密(此时应失败,因时间未到,但函数内部会检查) with pytest.raises(ValueError, match="解锁时间未到"): decrypt_capsule(meta, password) # 4. 强制绕过时间检查(仅测试用),验证解密逻辑 # (实际项目中,此步由 validate_capsule 控制,此处为单元测试特例) # ... 省略具体绕过代码,重点是 assert 解密后 content == "Hello from 2024!" def test_hash_consistency(): """验证 compute_hash 的稳定性:相同元数据,多次调用哈希一致""" meta = {"version": "1.0", "unlock_time": "2025-01-01T00:00:00Z", "salt_b64": "AAAA", ...} h1 = compute_hash(meta) h2 = compute_hash(meta) assert h1 == h2 def test_password_sensitivity(): """验证不同密码生成不同密钥,解密失败""" meta = create_capsule("test", "2099-01-01T00:00:00Z", "pass1") with pytest.raises(ValueError): decrypt_capsule(meta, "pass2") # 错误密码 def test_schema_validation(): """验证元数据 JSON Schema 校验""" from jsonschema import validate, ValidationError from capsule_loader import CAPSULE_SCHEMA invalid_meta = {"version": "1.0", "unlock_time": "invalid-time"} # 格式错误 with pytest.raises(ValidationError): validate(instance=invalid_meta, schema=CAPSULE_SCHEMA)答辩技巧:答辩时,不要说“我写了测试”,而是打开终端,输入
pytest tests/ -v,让老师亲眼看到 4 个测试全部PASSED,耗时 < 2 秒。这比讲一百遍“我考虑了各种情况”更有说服力。test_hash_consistency是亮点,它直接回应了“链上验证是否可靠”的质疑——哈希不变,验证才可信。
5.3 生产部署配置表:一份.env文件搞定所有环境差异
课设/毕设的“部署”就是把代码拷到另一台电脑上还能跑。为此,我们把所有可变参数抽离到.env文件,用python-decouple库加载。这张表就是你的部署说明书:
| 环境变量 | 示例值 | 说明 | 是否必需 |
|---|---|---|---|
WEB3_RPC_URL | https://eth-sepolia.g.alchemy.com/v2/your-key | Sepolia RPC |
本文还有配套的精品资源,点击获取