Skip to content

13.2 设备身份与可信数据

13.2.1 基于DID的设备身份设计方案

传统物联网身份管理的基本形态是:设备出厂时烧录对称密钥或X.509证书,接入平台时通过中心化认证服务器的校验。这个模式在单一平台内工作良好,但设备一旦需要跨组织交换数据——比如一台车载温湿度传感器同时向物流系统和交通管理系统上报数据——那个中心化注册表就成了瓶颈和单点故障。设备若想更换平台,需重新烧录身份;数据接收方也无法独立验证设备本身,只能信任颁发证书的平台。

去中心化标识符(Decentralized Identifier, DID)提供了一条不同的路径。DID的设计思路是让标识符不由单一注册机构管理,而由标识符的主体——设备本身或它的合法控制者——自主生成和控制。一个DID的字符串结构形如 did:<method-name>:<method-specific-id>,比如 did:example:abcd1234,其中example是DID方法,abcd1234是该方法域下的唯一标识。DID的核心价值不在于字符串本身,而在解析它之后得到的DID文档。这份结构化数据(通常为JSON-LD格式)包含当前有效的公钥列表、服务端点以及认证协议。它回答了验证方的问题:“声称是这台设备的家伙,该用哪把公钥核实它的签名?”(W3C DID Core标准定义了DID的核心数据模型和操作语义。)

将这套方案部署到物联网设备上,需要逐一解决三个工程环节:密钥与硬件的物理绑定、DID 文档在所选可验证数据注册表中的发布与更新,以及设备丢失或私钥泄露后的停用与恢复。注册表可以是分布式账本、去中心化文件系统、数据库或其他可信存储,具体机制由 DID Method 决定;DID Core 不要求使用区块链。

密钥与设备绑定:物理锚定。 高保证场景应尽量让私钥在安全元件、Secure Enclave 或 TPM 中生成并保持不可导出,主控只调用签名接口;具体防护等级仍取决于器件认证、供应链和侧信道能力。DID 的 method-specific-id 如何构造由 DID Method 决定,并不普遍等于公钥哈希。若某方法选择内容寻址,可以使用明确规定的摘要算法;SHA-256 与以太坊常用的 Keccak-256 都能生成指纹,但输出不同、不可互换,验证端必须严格使用注册时约定的算法与规范化字节序列。

工程权衡上,选择安全飞地需平衡成本与防护等级。大批量消费级设备(如智能灯泡)对成本敏感,允许私钥在安全芯片中存活但可能无法抵御侧信道攻击;工业级设备(如医疗输液泵)则需要更高等级认证的专用安全元件。这是身份管理的根本工程判断:防护等级与部署成本之间必须找到一个可接受的平衡点。

注册与更新:由 DID Method 决定。 设备生成 DID 后,控制者按所选 DID Method 把解析所需信息发布到可验证数据注册表。使用许可链的方法可以把 DID 文档哈希和控制密钥写入智能合约;使用 Web、数据库或点对点注册表的方法则有不同的创建、更新和停用流程。关键约束不是“必须上链”,而是解析者能够验证更新确由当前控制者授权。下面的 Solidity 代码只展示一种链上注册表的教学实现,不代表 DID Core 的通用流程:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract DeviceDIDRegistry {
    struct DIDDocument {
        address owner;          // 控制该DID的链上地址
        bytes32 publicKeyHash;  // 公钥哈希(实际可用公钥本身,此处用哈希缩减存储)
        uint256 timestamp;      // 注册或最后更新时间戳
        bool isActive;          // 是否有效
    }

    mapping(bytes32 => DIDDocument) private didDocs;  // DID哈希 -> 文档

    event DIDRegistered(bytes32 indexed didHash, address indexed owner, uint256 timestamp);
    event DIDUpdated(bytes32 indexed didHash, address indexed owner, uint256 timestamp);
    event DIDRevoked(bytes32 indexed didHash, uint256 timestamp);

    // 注册:用DID和公钥哈希注册一台设备
    function registerDevice(string calldata _did, bytes32 _publicKeyHash) external {
        bytes32 didHash = keccak256(bytes(_did));
        require(didDocs[didHash].timestamp == 0, "DID already registered");
        didDocs[didHash] = DIDDocument({
            owner: msg.sender,
            publicKeyHash: _publicKeyHash,
            timestamp: block.timestamp,
            isActive: true
        });
        emit DIDRegistered(didHash, msg.sender, block.timestamp);
    }

    // 验证:给定DID和消息哈希,验证ecrecover恢复出的地址是否与控制者匹配
    //(示意实现:省略了publicKeyHash与签名公钥的比对,实际部署应补充该校验)
    function verifySignature(
        string calldata _did, bytes32 _messageHash,
        uint8 _v, bytes32 _r, bytes32 _s
    ) external view returns (bool) {
        bytes32 didHash = keccak256(bytes(_did));
        DIDDocument storage doc = didDocs[didHash];
        require(doc.isActive, "Device is not active");
        address signer = ecrecover(_messageHash, _v, _r, _s);
        return (signer == doc.owner);
    }

    // 吊销:仅设备所有者可操作
    function revokeDevice(string calldata _did) external {
        bytes32 didHash = keccak256(bytes(_did));
        require(didDocs[didHash].owner == msg.sender, "Not the owner");
        didDocs[didHash].isActive = false;
        emit DIDRevoked(didHash, block.timestamp);
    }
}

这个合约抓住身份管理的最小操作集。每次注册记录一个链上地址作为owner,verifySignature利用Solidity的ecrecover从签名中恢复签名地址,与owner比对。这隐含了一个工程前提:设备必须能组织合法的以太坊格式交易,或生成能被ecrecover验证的离线签名。实际部署中,完整DID文档(含公钥、服务端点)通常托管在IPFS等链下存储,链上只保留IPFS哈希与文档指针,以降低存储成本。

对应一个关键工程检查点:

  • 密钥生成:确保安全飞地中生成的密钥不可被主控MCU导出。
  • DID构造:确认method-specific-id的哈希算法与合约中的keccak256(Ethereum标准)一致。
  • 交易签名:验证设备离线签名的交易格式能被ecrecover正确解析。
  • 链上状态:检查isActive字段在注册、更新后始终为true
  • 链下存储:确认DID文档的IPFS哈希与链上指针匹配。

停用与恢复:传播延迟仍然存在。 链上方法可以由控制者调用 revokeDevice,待交易确认后把状态标记为失效;其他 DID Method 可能通过更新注册表或解析元数据完成停用。任何方案都存在提交、复制、缓存和离线验证造成的可见延迟,不能承诺“即时全网撤销”。验证方应规定解析结果最大缓存时间、签名时间窗和高风险操作的在线状态检查;设备丢失私钥时还需要预置恢复密钥或多方恢复策略。

此外,若设备被物理破坏,私钥虽未泄露但设备无法再发起签名交易。此时需要预先设置“继承者”或“恢复密钥”。典型做法是注册时指定一个备用公钥地址(例如工厂管理密钥),该密钥有权在出示“设备死亡证明”(例如连续N个时间周期无心跳)后执行吊销。这个设计增加了链上逻辑的复杂性,在简约合约中通常省略,实际产品中值得纳入。

DID 带来的核心转变,是把标识符控制、解析和密钥轮换规则显式写入 DID Method,而不是天然把信任迁移到区块链。链上方法依赖账本共识并承担交易、同步和治理成本;非链方法则依赖其注册表、域名、数据库或点对点网络的信任假设。无论选哪一种,设备硬件只能证明某个密钥在受保护环境中使用,不能独自证明传感器读数真实,也不能替代制造、校准和运营主体的治理责任。

以下时序图展示了从设备出厂、注册到验证的完整身份生命周期。

图 13-3 设备DID注册与验证流程设备注册 DID 与公钥哈希后,验证方结合一次性 nonce、签名和最新链上状态完成身份验证。图 13-3 设备DID注册与验证流程验证方发起一次性挑战,签名回复须结合最新 DID 状态验证设备 1 · 身份主体安全飞地 · 私钥不可导出DeviceDIDRegistry智能合约 · 区块链设备 2 · 验证方生成并消费一次性 nonce1 生成 ECDSA 密钥对2 公钥哈希 → DID3 registerDevice(did, publicKeyHash) + 账户签名4 验证签名;写入 owner/hash/timestamp/isActive5 记录 DIDRegistered 日志注册阶段6 生成并登记一次性 nonce7 nonce 挑战 → 设备 18 用设备私钥签名 nonce9 DID + nonce 签名 → 设备 210 查询最新 didDocs[didHash]11 返回 owner / publicKeyHash / isActive12 校验签名与 isActive;消费 nonce,拒绝重放13 验证通过 / 失败链下操作:密钥生成、nonce 生成与签名链上操作 / 查询:注册交易、状态写入、DID 文档查询图 13-3 设备注册 DID 与公钥哈希后,验证方结合一次性 nonce、签名和最新链上状态完成身份验证。
图 13-3 设备DID注册与验证流程

13.2.2 数据上链模型:链下存储与链上指纹

解决了“设备是谁”的问题,接下来要回答:“这台设备产生的数据,怎么让人相信是真实且完整的?”一个温度传感器每秒上报一个读数,一天就是86,400条。如果全部写入区块链,成本会迅速膨胀到无法接受——主流区块链的区块空间和网络吞吐,根本承受不了毫秒级、海量设备的数据洪流。把原始数据一股脑塞进链上,既不经济,也没必要。

常见做法是链下存储原始数据,账本记录哈希承诺。账本不保存全部内容,只保存用于比较的摘要;其作用是提高提交后改写的可发现性,而不是把链下数据变成天然可信或绝对不可篡改。

哈希函数是整个模型的基石。它能把任意大小的数据(一张照片、一条1KB的温度曲线)压缩成一个固定长度的数字指纹,通常是256位。同一个数据,哈希值永远相同;数据变了哪怕一个比特,哈希值就会完全改变。有了这个性质,区块链上只要存这个哈希,任何人拿到原始数据后都可以用哈希运算来验证它是否被改过。

实际工程中,一条数据上链流程大致分五步(见图 13-4)。

  1. 传感器采样:温度传感器读到25.3℃,生成一条JSON记录 {"device_id":"sensor001","temp":25.3,"ts":1700000000}

  2. 边缘节点聚合与哈希计算:边缘网关或雾节点接收多台设备的数据,将它们打包成一个数据块,并计算这个数据块的哈希值。如果需要处理大量设备,还可以构建一棵Merkle树——把多条数据的哈希两两拼接后再哈希,最终得到树根哈希。Merkle树的工程价值在于:验证某条数据是否在原始包中时,不需要下载整个数据包,只需要提供从该数据到树根的路径哈希,路径大小和节点数量成对数关系。这在物联网设备带宽有限的场景下很有实际意义。

  3. 链下存储:原始数据可以进入对象存储、受控数据库或内容寻址网络。IPFS 的内容标识符对应特定字节,但内容仍可能因无人 pin、节点离线或访问策略而不可用;Arweave 的目标是长期持久保存,也要评估付费、网关和可用性假设。链下存储不能只写“去中心化”,还要明确副本、保留、加密、删除与取证责任。

  4. 账本提交:边缘节点把数据块哈希、Merkle 根和必要元数据作为交易提交,合约将其记录为事件或状态。区块时间只表示交易被网络接受的大致时间,不等于采集时间,也不能单独保证法律意义上的不可抵赖;需要同时保存设备签名、可信时间来源、提交者身份和最终性证据。

  5. 验证:验证方取出链下数据,按约定的规范化方式与算法重算哈希,再与账本记录比较。匹配只说明当前字节与已提交摘要一致,不说明内容真实;不匹配表示副本、编码、切分或摘要至少有一处不同,需要继续调查,不能直接把原因判定为恶意篡改。

整个流程把成本最高的部分——“海量数据存储”和“高频写入”——分配给了链下,只把最精简的“证据指纹”留给区块链。这个设计直接回应了物联网规模化部署中两个核心顾虑:成本和可信度。

这个模型的边界是:哈希只能比较字节一致性。如果传感器、网关或规范化过程在摘要形成前已出错,账本只会忠实记录错误摘要。多方签名、校准、抽检和异常检测可以降低风险,却不能彻底消除源头造假或共谋。

图 13-4 数据上链流程图数据从传感器采样,经边缘节点分两路:原始数据存入链下存储,哈希与元数据写入链上;验证方重算哈希与链上比对确认完整性。图 13-4 数据上链流程图存证是“链下存原文、链上存指纹”的分工,兼顾存储成本与不可篡改性传感器设备采样生成 JSON 记录边缘节点聚合数据 · 计算哈希(可构建 Merkle 树)IPFS / Arweave去中心化存储节点链下存储区块链智能合约接收哈希 · 写入日志事件链上存证验证方取原始数据 · 重算哈希链上比对 · 确认完整性采样数据原始数据哈希+元数据提供原始数据查询哈希是否存在青绿 = 设备 / 传感器蓝 = 核心处理 / 链上灰 = 外部存储 / 链下实线 = 同步推送 · 虚线 = 查询 / 读取图 13-4 数据从传感器采样后,原始数据存链下、哈希指纹存链上,验证方通过两侧数据比对确认完整性。
图 13-4 数据上链流程图

工程检查清单

  • 哈希算法的选择:链下通用场景SHA-256仍是默认选择,进入以太坊系合约时则统一使用keccak256(与13.2.1节示意一致),链上链下口径需保持一致。BLAKE2或SHA-3可作为替代,但需确认智能合约虚拟机是否原生支持。
  • 链下存储系统的选择:IPFS适合中等热度的数据共享场景;Arweave的一次付费永久存储模型适合监管合规需求。两者都不应对终端设备产生强依赖关系。
  • 时间戳的对齐:严格来说,区块确认时间才是“上链时间”。设备本地时间在验证环节只能作为参考,不应作为唯一证据锚点。

13.2.3 数据验证与溯源机制

哈希上链解决了“数据是否被篡改”的校验问题。你拿到一段数据,计算其哈希值,对比链上存储的哈希,一致就认定数据没被动过。但这只回答了“上链后是否被改”,更深层的问题是:上链那一刻的数据本身是否可信? 一段数据从传感器产生,经过边缘节点聚合、转发,最终写入区块链,中间经过多少跳,每跳做了哪些处理。如果这些过程没有记录,那所谓的“溯源”只是一句空话。

数据验证与溯源机制要覆盖两个层面。一是完整性验证:智能合约提供一个公开的验证函数,任何人提交原始数据和对应的数据ID,合约重新计算哈希并通过比对返回“有效/无效”。二是来源追踪:每次数据上报、转发、校验,都在链上留下一组事件日志,记录谁在什么时间对哪条数据做了什么事。这些日志串联起来,就构成一条可审计的数据流转链。

先从完整性验证说起。下面的Solidity合约演示了核心逻辑:用keccak256计算原始数据的哈希值,与链上预先存储的指纹做比对。

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract DataVerification {
    mapping(bytes32 => bytes32) private dataHashes;      // 数据ID -> 哈希
    mapping(bytes32 => uint256) private dataTimestamps;   // 数据ID -> 上链时间戳
    mapping(bytes32 => address) private dataOwners;       // 数据ID -> 设备地址

    // 事件:记录数据指纹上链
    event DataStored(
        bytes32 indexed dataId,
        bytes32 dataHash,
        uint256 timestamp,
        address indexed device
    );

    // 事件:记录验证结果
    event DataVerified(
        bytes32 indexed dataId,
        bytes32 actualHash,
        bool isValid,
        address indexed verifier
    );

    // 存储数据指纹
    function storeDataHash(bytes32 dataId, bytes32 dataHash) external {
        require(dataHashes[dataId] == bytes32(0), "Data ID already exists");
        dataHashes[dataId] = dataHash;
        dataTimestamps[dataId] = block.timestamp;
        dataOwners[dataId] = msg.sender;
        emit DataStored(dataId, dataHash, block.timestamp, msg.sender);
    }

    // 验证原始数据完整性
    function verifyData(bytes32 dataId, bytes memory rawData) external returns (bool) {
        bytes32 storedHash = dataHashes[dataId];
        require(storedHash != bytes32(0), "Data ID not found");
        bytes32 computedHash = keccak256(rawData);
        bool isValid = (computedHash == storedHash);
        emit DataVerified(dataId, computedHash, isValid, msg.sender);
        return isValid;
    }
}

核心函数只有两个。storeDataHash负责上链:将数据ID与哈希指纹写入合约的mapping,同时记录时间戳和设备地址。verifyData负责验证:验证者传入数据ID和原始数据,合约自动计算哈希,比对结果通过DataVerified事件广播出去。任何审计方、消费者或监管机构都可以监听这个事件来确认数据真伪。

事件日志(Event)是Solidity中开销很小的数据记录机制。写入事件的gas成本远低于修改storage变量。每个事件最多可以附带三个indexed参数,这些参数会被区块链客户端索引,链外程序可以通过这些索引快速过滤相关记录。在这个合约里,dataIddevice被标记为indexed,意味着你只要知道数据ID或者设备地址,就能通过区块浏览器或Web3工具快速定位所有相关事件。在高频数据场景下,这比遍历全链高效得多。

完整性验证结合事件日志,构成了溯源的第一层:一份数据,一个哈希,一次验证,一条事件。但有时一条数据会经历多个节点:边缘网关先聚合一批传感器读数,再转发到厂区服务器,厂区服务器做格式校验后才提交上链。每个环节都应该在链上留下记录。这时溯源机制就要求把多个事件串联成一条完整的流转链。

跨域溯源通过“数据ID”来实现。某台设备产生的原始数据,在整个流转周期里使用同一个全局唯一数据ID——通常由传感器ID和时间戳联合哈希生成。每个处理节点完成操作后,都调用合约记录一条事件,事件参数中包含“上一步处理者地址”。链外溯源应用遍历数据ID对应的所有事件,就能还原出完整的数据流转路径。

工程上,这个机制需要面对两个约束。

第一个约束是事件日志的存储边界。以太坊每个区块的gas上限限制了该区块能容纳的事件总数。IoT高频数据直接在链上逐条记录不可行,必须在边缘节点先做聚合。常见的做法是每5分钟或每10分钟将一批数据的Merkle根上链,验证时提供Merkle证明(Merkle Proof)来验证单条数据。

第二个约束是跨链流转。如果数据在多个独立的区块链网络(比如生产链、物流链、消费链)上流转,数据ID需要跨链统一。跨链桥需要把数据ID及其事件映射到目标链上,确保溯源查询不中断。这类方案的具体工程细节会在13.4.3节讨论。

图 13-5 数据验证与溯源机制时序图数据经边缘节点生成指纹并上链,验证者通过哈希比对确认完整性,并沿事件日志追溯历史操作。图 13-5 数据验证与溯源机制时序图哈希验证确认数据完整性,事件索引串联历史操作传感器温度计边缘节点网关智能合约3 mapping + 2 Event区块链网络区块链下监听器数据库验证者放大镜T0T1T2T3T4T5T61 原始数据 JSON2 上链交易 storeDataHash3 状态写入 · DataStored4 事件索引(广播)5 验证请求 verifyData(dataId, rawData)6 结果广播 DataVerified7 历史查询mapping:dataHashes · dataTimestamps · dataOwnersEvent:DataStored · DataVerified图 13-5 数据经边缘节点生成指纹并上链,验证者通过哈希比对确认完整性并沿事件追溯历史操作。
图 13-5 数据验证与溯源机制时序图

数据验证与溯源机制,从合约角度看是摘要比对加事件记录。摘要可证明当前副本是否匹配已提交值,事件可证明某个身份在账本规则下提交过操作。 能否形成不可抵赖证据,还取决于签名密钥、时间、最终性、链下原件和司法规则。它也回答不了设备是否有资格作出某项声明,例如校准证书是否有效、质检结论由谁出具;下一小节的可验证凭证处理的是这类声明。

13.2.4 可验证凭证(VC)与设备可信声明

DID 解决的是“设备是谁、用哪把公钥验证”,但工业协作里更高频的问题是“这台设备有没有资格做某事”:传感器是否在有效期内完成校准,一台压力容器是否通过了出厂质检,一块电表是否具备入网许可。这些声明需要可携带、可离线验证、且不依赖签发机构随叫随到。可验证凭证(Verifiable Credential, VC)就是为此设计的标准载体。

VC 采用签发者(Issuer)—持有者(Holder)—验证者(Verifier)的三角模型。以设备校准为例:计量院校准完成后,构造一条结构化声明(设备 DID、校准日期、有效期、误差范围),用计量院的私钥签名,交给设备或其网关——持有者将凭证存入本地安全存储;此后无论是采购方、监管平台还是跨域的协作系统,只要拿到凭证,就能用签发者的公钥离线验证其真伪,无须回访计量院。质检凭证同理:出厂检验报告签发为 VC 随批次流转,下游整厂验收时逐条核验,而不是调档案、发函询。

VC 与 DID 的分工要分清:DID 文档回答“这个标识符由谁控制、用什么验证方法”,由所选 DID Method 对应的注册表解析;VC 携带“谁对什么主体作出了什么可验证声明”,由持有者按需出示。VC 不强制使用 DID,DID 也不自动赋予设备任何业务资格。标准化方面,W3C VC Data Model 2.0 于 2025 年 5 月成为正式推荐标准;本书采用的 DID Core v1.0 是 2022 年 7 月发布的 W3C Recommendation。工程实现应固定具体规范版本和 DID Method,不能用仍在演进的编辑草案状态替代已发布标准。

身份标识与可验证声明齐备之后,接下来把这些能力放进第一个完整的跨组织场景——供应链溯源。

从工业软件到 AI 智能体 · 构建面向智能体演进的多协议、云原生、开源工业物联网平台