← 返回首页
English Version

TRC20-USDT 链上证据固定实验

摘要

TRC20-USDT 交易记录具有公开可查、字段结构相对稳定、可通过多种链上数据接口复核等特点,是虚拟货币资金链溯源中常见的分析对象。然而,链上交易“可查询”并不等同于“已取证”。单纯保存区块链浏览器截图,难以满足后续复核、结构化分析、文件完整性校验和报告生成的需要。围绕单笔 TRC20-USDT 转账记录的证据固定问题,选取公开 USDT-TRC20 transaction-history 返回样例作为实验对象,构建最小链上证据包生成流程。实验对交易哈希、Token 合约、转出地址、转入地址、转账金额和链上时间等字段进行规范化处理,并生成原始 JSON、基础信息 JSON、交易事件表、时间线表、关系边表、Graphviz 关系图、采集日志和 SHA256 哈希清单。实验结果表明,单笔链上交易可以被转化为结构化、可复核、可校验的证据单元,为后续地址扩展、资金路径分析和链上取证报告形成提供基础。

关键词:Forensics · OnChain · TRC20-USDT

#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_idtoken_infoblock_timestampfromtotypevalue 等字段。原始样例如下:

json
{
  "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 或空值。链上取证中缺失字段必须如实保留,不能为了字段完整而人工编造。

金额换算方式如下:

text
15500000 / 10^6 = 15.5 USDT

时间戳换算如下:

text
1651903617000 ms = 2022-05-07T06:06:57+00:00

#3 方法

#3.1 证据包结构

围绕单笔 TRC20-USDT 交易,构建如下最小证据包:

text
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 合约核验。样例中的合约地址为:

text
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 个文件。核心输出如下:

text
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 生成内容如下:

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 内容如下:

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 内容如下:

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 内容如下:

csv
source,target,txid,block_time_utc,amount_decimal,token_symbol
TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny,TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss,d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668,2022-05-07T06:06:57+00:00,15.5,USDT

关系边文件将转账关系抽象为:

text
source → target

这一步是后续图谱分析的基础。单笔交易对应一条边,多笔交易可以构成地址交易图。便于后续进行图谱分析。

#4.5 Graphviz 关系图

06_relation_graph.dot 内容如下:

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"];
}

该图只表达一个最小资金方向:

text
TYPrKF2s...cybny  →  TTRmEA73...VefYss
             15.5 USDT

#4.6 哈希清单

08_hash_manifest.txt 内容如下:

text
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,输出为证据包目录。

python
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])

运行方式:

bash
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 校验,固定为一个最小链上证据包。该证据包不能直接证明人员身份,也不能替代完整案件证据,但能够为后续资金链溯源提供可靠起点。

在虚拟货币资金链分析中,交易哈希不应只是查询入口,而应成为证据固定流程的入口。链上取证的基础工作不是绘制复杂图谱,而是先把每一笔关键交易保存成可复核、可校验、可继续扩展的证据单元。