TRC20-USDT 链上证据固定实验
摘要
TRC20-USDT 交易记录具有公开可查、字段结构相对稳定、可通过多种链上数据接口复核等特点,是虚拟货币资金链溯源中常见的分析对象。然而,链上交易“可查询”并不等同于“已取证”。单纯保存区块链浏览器截图,难以满足后续复核、结构化分析、文件完整性校验和报告生成的需要。围绕单笔 TRC20-USDT 转账记录的证据固定问题,选取公开 USDT-TRC20 transaction-history 返回样例作为实验对象,构建最小链上证据包生成流程。实验对交易哈希、Token 合约、转出地址、转入地址、转账金额和链上时间等字段进行规范化处理,并生成原始 JSON、基础信息 JSON、交易事件表、时间线表、关系边表、Graphviz 关系图、采集日志和 SHA256 哈希清单。实验结果表明,单笔链上交易可以被转化为结构化、可复核、可校验的证据单元,为后续地址扩展、资金路径分析和链上取证报告形成提供基础。
#1 引言
虚拟货币资金流转具有链上公开、主体隐匿、跨平台流动和跨境传播等特点。以 USDT 为代表的稳定币,在场外兑换、跨境支付、网络诈骗资金转移等场景中被频繁使用。对于取证分析而言,交易哈希往往是最早出现的链上线索。通过交易哈希可以查询到转出地址、转入地址、金额、Token 合约、时间戳等信息,但这些信息如果只停留在浏览器页面展示层面,仍然难以形成稳定的取证材料。
链上证据固定的基础问题并不复杂:一笔交易被发现后,应当保存什么、如何保存、如何保证后续能够复核、如何证明保存文件没有被修改。这个问题看似细小,却直接影响后续资金路径分析的可靠性。若单笔交易的字段来源不清、金额精度处理错误、Token 合约未核验、原始返回数据未保存,后续基于该交易展开的地址扩展和资金流分析都可能出现偏差。
TRONGrid 文档提供了查询某一账户特定 TRC20 Token 历史交易的接口,并支持通过 contract_address 指定 TRC20 合约地址;官方示例中以 TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t 作为 TRC20-USDT 合约地址进行查询。(TRON Developer Hub) TRONSCAN 文档也提供了根据交易哈希查询交易详情、查询 TRC20/TRC721 转账列表等接口,说明链上交易不仅可以通过浏览器页面查看,也可以通过接口形式取得结构化数据。(docs.tronscan.org)
基于上述条件,单笔 TRC20-USDT 交易适合作为链上证据固定实验的最小对象。
#2 实验数据与字段说明
实验对象为一条公开 USDT-TRC20 transaction-history 返回样例。该样例包含 transaction_id、token_info、block_timestamp、from、to、type 和 value 等字段。原始样例如下:
{
"transaction_id": "d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668",
"token_info": {
"symbol": "USDT",
"address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"decimals": 6,
"name": "Tether USD"
},
"block_timestamp": 1651903617000,
"from": "TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny",
"to": "TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss",
"type": "Transfer",
"value": "15500000"
}其中,transaction_id 为交易哈希,token_info.address 为 TRC20-USDT 合约地址,from 为转出地址,to 为转入地址,value 为未按 Token 精度换算的原始数值,decimals 为精度位数。
该样例未包含区块高度字段,因此实验中不对区块高度进行补造,统一记为 null 或空值。链上取证中缺失字段必须如实保留,不能为了字段完整而人工编造。
金额换算方式如下:
15500000 / 10^6 = 15.5 USDT时间戳换算如下:
1651903617000 ms = 2022-05-07T06:06:57+00:00#3 方法
#3.1 证据包结构
围绕单笔 TRC20-USDT 交易,构建如下最小证据包:
usdt_trc20_evidence_experiment/
├── 01_basic_info.json
├── 02_raw_usdt_trc20_sample.json
├── 03_transfer_event.csv
├── 04_timeline.csv
├── 05_relation_edges.csv
├── 06_relation_graph.dot
├── 07_collection_log.txt
├── 08_hash_manifest.txt
├── README.md
└── build_evidence_package.py该结构不追求覆盖完整案件材料,只服务于单笔链上交易的固定。原始 JSON 用于保留数据来源,基础信息 JSON 用于字段规范化,CSV 文件用于后续分析,DOT 文件用于关系图生成,日志文件记录处理过程,哈希清单用于完整性校验。
#3.2 字段规范化
字段规范化主要处理三类问题。
第一,Token 合约核验。样例中的合约地址为:
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t该地址与 TRON 官方页面列出的 TRC20-USDT 合约地址一致。([tron.network](https://tron.network/usdt/?utm_source=chatgpt.com "TRON USDT"))
第二,金额精度换算。链上返回的 value 不是人类阅读意义上的 15.5,而是按最小单位保存的 15500000。只有结合 decimals=6,才能得到实际金额。
第三,时间戳转换。样例中的 block_timestamp 为毫秒级 Unix 时间戳,需转换为标准 UTC 时间,以便与其他交易记录、日志记录或报告时间线对齐。
#3.3 完整性校验
证据包生成后,对目录内文件计算 SHA256。哈希清单不是装饰性文件,而是后续复核的基础。只要证据包内任一文件发生变化,重新计算哈希后即可发现不一致。
#4 实验结果
实验代码在本地执行后,生成了 10 个文件。核心输出如下:
txid: d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668
block_time_utc: 2022-05-07T06:06:57+00:00
from: TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny
to: TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss
contract: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
amount: 15.5 USDT#4.1 基础信息文件
01_basic_info.json 生成内容如下:
{
"experiment": "USDT-TRC20 single-transfer evidence-package fixation",
"chain": "TRON",
"token_standard": "TRC20",
"token_name": "Tether USD",
"token_symbol": "USDT",
"contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"txid": "d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668",
"block": null,
"block_timestamp_ms": 1651903617000,
"block_time_utc": "2022-05-07T06:06:57+00:00",
"from": "TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny",
"to": "TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss",
"amount_raw": "15500000",
"decimals": 6,
"amount_decimal": "15.5",
"event_type": "Transfer"
}#4.2 交易事件表
03_transfer_event.csv 内容如下:
txid,block,block_time_utc,from,to,amount_raw,decimals,amount_decimal,token_symbol,contract_address,event_type
d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668,,2022-05-07T06:06:57+00:00,TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny,TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss,15500000,6,15.5,USDT,TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t,Transfer#4.3 时间线
04_timeline.csv 内容如下:
order,type,txid,block_time_utc,from,to,amount_decimal,note
1,target_transfer,d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668,2022-05-07T06:06:57+00:00,TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny,TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss,15.5,Single USDT-TRC20 Transfer event fixed as target object在单笔交易实验中,时间线只有一条记录。实际案件分析中,可以继续加入该地址上下游交易、交易所充值提现记录、聊天记录时间、设备日志时间,形成更完整的事件序列。
#4.4 关系边
05_relation_edges.csv 内容如下:
source,target,txid,block_time_utc,amount_decimal,token_symbol
TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny,TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss,d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668,2022-05-07T06:06:57+00:00,15.5,USDT关系边文件将转账关系抽象为:
source → target这一步是后续图谱分析的基础。单笔交易对应一条边,多笔交易可以构成地址交易图。便于后续进行图谱分析。
#4.5 Graphviz 关系图
06_relation_graph.dot 内容如下:
digraph USDT_TRC20_Evidence {
graph [rankdir=LR];
node [shape=box, fontname="Arial"];
"TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny" [label="FROM\nTYPrKF2s...cybny"];
"TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss" [label="TO\nTTRmEA73...VefYss"];
"TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny" -> "TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss" [label="15.5 USDT\n2022-05-07T06:06:57+00:00"];
}该图只表达一个最小资金方向:
TYPrKF2s...cybny → TTRmEA73...VefYss
15.5 USDT#4.6 哈希清单
08_hash_manifest.txt 内容如下:
7fe8aa03d9b86e1f32f4c1cdd131e6718cc41effd43d4fa0eeed5c5badea57a7 01_basic_info.json
b4522f4058e7aebde624f8e43c3e5c20cf14cebe4b9e35b04e07e861092c3df7 02_raw_usdt_trc20_sample.json
a622c54d7794cc15bf72e4db6a7e59b9afadce64f87d8c4585d3963c12b4b303 03_transfer_event.csv
15c731d2ec8228b14aaef436087441fdf0b35ce5a0c1b20369b0cb7bb4f479ec 04_timeline.csv
abff190982b09be8b90c26b342291d392c06868f330233ed5f5aae4f9d5323ef 05_relation_edges.csv
c1aa19fc6eeef16c89257e34d7ad580cf2602f9e0369cc469514de270fc58c29 06_relation_graph.dot
9347aeafc136f2158517e33e9ed57416efee8aa37c2ee8628cda24eab2bf9dfb 07_collection_log.txt
a2648a96b883b1e8726a049decb23169cbf328a013b3b0a0aa9ad67a82f2e8a8 README.md
87a584838c8b01c8241082aeb2c6ce13bd3d37303cb42eadea1214de236901be build_evidence_package.py哈希清单使证据包具备了文件完整性校验能力。后续复核时,只需对证据包重新计算 SHA256,并与清单比对,即可判断文件是否发生变化。
同样这也是电子数据固定的必备步骤,确保文件未被篡改,符合相关条例。
#5 代码实现
实验代码如下。输入为原始 USDT-TRC20 样例 JSON,输出为证据包目录。
from pathlib import Path
from decimal import Decimal, getcontext
import json, csv, hashlib, datetime, sys, shutil
getcontext().prec = 50
def sha256_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def main(raw_json: str, out_dir: str) -> None:
raw_path = Path(raw_json)
out = Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
data = json.loads(raw_path.read_text(encoding="utf-8"))
event = data["data"][0]
token = event["token_info"]
shutil.copy2(raw_path, out / "02_raw_usdt_trc20_sample.json")
amount_decimal = Decimal(event["value"]) / (
Decimal(10) ** int(token["decimals"])
)
block_time_utc = datetime.datetime.fromtimestamp(
event["block_timestamp"] / 1000,
tz=datetime.timezone.utc
)
basic = {
"experiment": "USDT-TRC20 single-transfer evidence-package fixation",
"chain": "TRON",
"token_standard": "TRC20",
"token_name": token.get("name"),
"token_symbol": token.get("symbol"),
"contract_address": token.get("address"),
"txid": event.get("transaction_id"),
"block_timestamp_ms": event.get("block_timestamp"),
"block_time_utc": block_time_utc.isoformat(),
"from": event.get("from"),
"to": event.get("to"),
"amount_raw": event.get("value"),
"decimals": token.get("decimals"),
"amount_decimal": str(amount_decimal),
"event_type": event.get("type"),
"generated_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat()
}
(out / "01_basic_info.json").write_text(
json.dumps(basic, ensure_ascii=False, indent=2),
encoding="utf-8"
)
with (out / "03_transfer_event.csv").open(
"w", encoding="utf-8", newline=""
) as f:
writer = csv.DictWriter(f, fieldnames=[
"txid", "block_time_utc", "from", "to", "amount_raw",
"decimals", "amount_decimal", "token_symbol",
"contract_address", "event_type"
])
writer.writeheader()
writer.writerow({
"txid": event.get("transaction_id"),
"block_time_utc": block_time_utc.isoformat(),
"from": event.get("from"),
"to": event.get("to"),
"amount_raw": event.get("value"),
"decimals": token.get("decimals"),
"amount_decimal": str(amount_decimal),
"token_symbol": token.get("symbol"),
"contract_address": token.get("address"),
"event_type": event.get("type")
})
with (out / "04_timeline.csv").open(
"w", encoding="utf-8", newline=""
) as f:
writer = csv.DictWriter(f, fieldnames=[
"order", "type", "txid", "block_time_utc",
"from", "to", "amount_decimal", "note"
])
writer.writeheader()
writer.writerow({
"order": 1,
"type": "target_transfer",
"txid": event.get("transaction_id"),
"block_time_utc": block_time_utc.isoformat(),
"from": event.get("from"),
"to": event.get("to"),
"amount_decimal": str(amount_decimal),
"note": "Single USDT-TRC20 Transfer event fixed as target object"
})
with (out / "05_relation_edges.csv").open(
"w", encoding="utf-8", newline=""
) as f:
writer = csv.DictWriter(f, fieldnames=[
"source", "target", "txid",
"block_time_utc", "amount_decimal", "token_symbol"
])
writer.writeheader()
writer.writerow({
"source": event.get("from"),
"target": event.get("to"),
"txid": event.get("transaction_id"),
"block_time_utc": block_time_utc.isoformat(),
"amount_decimal": str(amount_decimal),
"token_symbol": token.get("symbol")
})
from_addr = event.get("from")
to_addr = event.get("to")
dot = f"""digraph USDT_TRC20_Evidence {{
graph [rankdir=LR];
node [shape=box, fontname="Arial"];
"{from_addr}" [label="FROM\\\\n{from_addr[:8]}...{from_addr[-6:]}"];
"{to_addr}" [label="TO\\\\n{to_addr[:8]}...{to_addr[-6:]}"];
"{from_addr}" -> "{to_addr}" [label="{amount_decimal} {token.get("symbol")}\\\\n{block_time_utc.isoformat()}"];
}}
"""
(out / "06_relation_graph.dot").write_text(dot, encoding="utf-8")
rows = []
for p in sorted(out.iterdir()):
if p.name == "08_hash_manifest.txt" or p.is_dir():
continue
rows.append(f"{sha256_file(p)} {p.name}")
(out / "08_hash_manifest.txt").write_text(
"\n".join(rows) + "\n",
encoding="utf-8"
)
if __name__ == "__main__":
if len(sys.argv) != 3:
print(
"Usage: python build_evidence_package.py raw_usdt_trc20_sample.json output_dir",
file=sys.stderr
)
sys.exit(2)
main(sys.argv[1], sys.argv[2])运行方式:
python build_evidence_package.py 02_raw_usdt_trc20_sample.json output_dir#6 分析
实验结果可以说明三个问题。
第一,单笔链上交易能够被规范化为独立证据单元。交易哈希、Token 合约、地址、时间和金额经过结构化处理后,不再只是浏览器页面上的展示内容,而是可以被程序读取、复核、导入表格或生成图谱的数据。
第二,原始数据与提取数据必须同时保留。02_raw_usdt_trc20_sample.json 保存原始样例,01_basic_info.json 保存提取后的字段。前者用于复核来源,后者用于阅读和报告。只保存提取结果,会导致后续无法判断字段是否提取正确;只保存原始 JSON,又不利于快速阅读和分析。
第三,哈希清单是证据包完整性的基本保障。链上交易本身具备公开可查性,但本地保存的取证文件仍然可能被修改。对证据包内文件计算 SHA256,可以为后续校验提供基准。
#7 局限
本实验仍存在限制。
本实验只解决一个基础问题:如何把一笔 USDT-TRC20 转账记录固定为结构化证据包。至于资金链继续追踪、交易所账户落地、KYC 信息调取、设备痕迹关联和主观明知证明,仍需要后续证据结合完成。
#8 结论
围绕公开 USDT-TRC20 转账样例完成的实验表明,单笔链上交易可以通过字段规范化、原始数据保存、CSV 结构化、关系图生成和 SHA256 校验,固定为一个最小链上证据包。该证据包不能直接证明人员身份,也不能替代完整案件证据,但能够为后续资金链溯源提供可靠起点。
在虚拟货币资金链分析中,交易哈希不应只是查询入口,而应成为证据固定流程的入口。链上取证的基础工作不是绘制复杂图谱,而是先把每一笔关键交易保存成可复核、可校验、可继续扩展的证据单元。