TAPRegistry:AI代理的跨链身份合约

TAPRegistry 是 AI 代理的跨链身份合约,部署在 Push Chain 上,为每个代理创建不可转让的规范身份,并通过 EIP-712 签名绑定到以太坊、Base 等链上的 ERC-8004 注册身份。它解决了不同链上代理身份碎片化的问题,支持所有者级去重、全局唯一绑定和反向解析。文章详细介绍了其双层身份模型、注册流程、绑定机制、存储架构以及实际用例,展示了如何通过 UEA(通用执行账户)和签名验证实现统一的跨链代理身份图谱。

问题

ERC-8004 为每条链赋予了独立的 IdentityRegistry。在以太坊主网上注册的 Agent 会获得一个以太坊上的 agentId。同一个 Agent 在 Base 上注册,会获得 Base 上一个不同的 agentId。这两个身份是完全割裂的:

  • Base 上的用户无法验证 Base 上的 Agent #42 与以太坊上的 Agent #17 是同一个实体。
  • 在一条链上获得的信誉不会延续到另一条链。
  • Agent 操作者必须独立地在每条链上管理单独的注册、元数据 URI 和 Agent 卡。
  • 没有方法可以跨所有链以原子方式撤销或更新 Agent 的身份。

没有规范注册表,“跨链 Agent 身份”就成了需要手动操作且信任操作者的事情。用户必须依赖链下社交信号(网站、推特、文档)来判断不同链上的 Agent 是否是同一个实体。一旦 Agent 变得自主运行,并需要机器可读、密码学可验证的身份时,这种方式就会失效。

TAPRegistry 如何解决

TAPRegistry 引入了一个两层身份模型:

  1. Push Chain 上的规范身份 —— Agent 通过其通用执行账户(UEA)在 Push Chain 上注册一次。这会创建一个灵魂绑定的、不可转让的身份记录。
  2. 绑定到各条链的身份 —— Agent 将其每条链上的 ERC-8004 注册(称为“绑定”)连接到规范身份。每个绑定都通过 EIP-712 签名进行密码学验证。

UEA 作为规范锚点

Push Chain 的通用执行账户(UEA)是通过工厂部署的账户,它将外部链身份桥接到 Push Chain。当来自以太坊的用户在 Push Chain 上创建 UEA 时,UEA 工厂会记录他们的来源链、链 ID 和持有者密钥(控制 UEA 的以太坊地址)。

agentId 是确定性的:agentId = uint256(uint160(ueaAddress)) % 10_000_000。这意味着:

  • Agent ID 是一个从 UEA 地址派生的 7 位数字。
  • 没有计数器。没有外部映射。如果两个地址共享相同的截断 ID,碰撞防护会回退。
  • ID 0 保留为哨兵值;截断为 0 的地址会获得 ID 10_000_000。
  • 任何知道 UEA 地址的人都可以通过 agentIdOfUEA() 计算 Agent ID。

注册

Agent 通过在其 Push Chain 的 UEA 上调用 register(agentURI, agentCardHash) 来注册:

  • agentURI 是一个元数据 URI(通常是 IPFS CID),指向 Agent 的卡——一个描述 Agent 能力、模型、版本和其他元数据的 JSON 文档。
  • agentCardHash 是 Agent 卡片内容的 keccak-256 哈希,用于链上完整性验证。

首次注册时,合约会查询 UEA 工厂以确定:

  • originChainNamespace:CAIP-2 命名空间(例如,EVM 链使用 "eip155")。
  • originChainId:CAIP-2 链 ID(例如,以太坊主网使用 "1")。
  • ownerKey:来源链上控制地址的原始字节。
  • nativeToPush:调用者是否是 Push Chain 的原生账户(不是 UEA)。

后续对 register() 的调用会更新 agentURIagentCardHash,而不会修改来源元数据。这是重新注册路径——同一个函数,具有幂等语义。

持有者级别去重

同一个 EOA 钱包可以在不同的来源链(以太坊、Base、BSC 等)上创建 UEA,每个 UEA 都会在 Push Chain 上产生一个不同的 UEA 地址。如果没有去重,同一个人或实体最终可能拥有多个独立的 Agent 身份。

TAPRegistry 通过 ownerKeyToAgentId 防止这种情况,这是一个从 keccak256(origin.owner)agentId + 1 的映射。注册流程有三个分支:

  1. 同一个 UEA 重新注册:该 UEA 已经有一个 ownerToAgentId 条目。只更新 agentURIagentCardHash
  2. 新 UEA,同一个持有者(别名):该 UEA 没有条目,但 ownerKeyToAgentId 找到了匹配项。新 UEA 连接到现有身份——它获得相同的 agentId,可以调用 setAgentURI/setAgentCardHash,并触发一个 UEALinked 事件。
  3. 新 UEA,新持有者(新铸造):两个映射都没有匹配项。使用确定性的 agentId 创建一个新的 AgentRecord

这确保了每个底层钱包对应一个身份,无论该钱包从多少个来源链操作。

绑定

注册后,Agent 会将其每条链上的 ERC-8004 注册连接到规范身份。每个绑定表示以下两者之间的链接:

  • Push Chain 上的规范 Agent(由 agentId 标识)
  • 另一条链上的链上 Agent(由 chainNamespacechainIdregistryAddressboundAgentId 标识)

绑定如何工作

  1. Agent 构建一条 EIP-712 类型化数据消息,包含:

    • canonicalOwner:Push Chain 上的 UEA 地址(调用者)
    • chainNamespace:目标链的 CAIP-2 命名空间(例如 "eip155"
    • chainId:CAIP-2 链 ID(例如 "1"
    • registryAddress:该链上的 ERC-8004 IdentityRegistry 合约地址
    • boundAgentId:Agent 在该链注册表中的 ID
    • nonce:一个唯一的 nonce,防止重放
    • deadline:签名过期的时间戳
  2. Agent 使用控制 UEA 的私钥(注册时记录的 ownerKey)签署此消息。签名证明控制规范身份的实体也控制了链上身份。

  3. Agent 在 Push Chain 上调用 bind(),并传入签名后的请求。合约会:

    • 验证 Agent 已注册
    • 检查链标识符和注册表地址是否有效
    • 验证截止日期未过期且 nonce 未被使用
    • 检查该绑定是否未被其他 Agent 占用(全局唯一性)
    • 检查 Agent 是否未超过 64 个绑定的限制
    • 根据 ownerKey 验证签名(支持 ECDSA 和 ERC-1271 合约签名)
    • 存储绑定条目并更新所有索引

签名验证

合约支持两种签名方案:

  • ECDSA (EOA):标准的 65 字节 (r, s, v) 签名。合约恢复签名者地址,并检查它是否与 ownerKey 的前 20 个字节匹配。
  • ERC-1271 (合约):适用于智能合约钱包。proofData 编码为 abi.encodePacked(signerAddress, signatureBytes)。合约使用 50,000 gas 限制在签名者地址上调用 isValidSignature()

去重和唯一性

  • 全局唯一性:一个绑定元组 (chainNamespace, chainId, registryAddress, boundAgentId) 只能由一个规范 Agent 认领。这防止了两个 Agent 认领同一个链上身份。
  • 每个 Agent 的唯一性:一个 Agent 对于给定的 (chainNamespace, chainId, registryAddress) 元组最多只能有一个绑定。要更改某个链+注册表的 boundAgentId,Agent 必须先解除绑定,然后再重新绑定。

解除绑定

Agent 调用 unbind(chainNamespace, chainId, registryAddress) 来移除绑定。为了节省 gas,在绑定数组上使用交换并弹出模式——待移除的条目与最后一个条目交换,然后弹出数组。所有索引都会相应更新。

灵魂绑定语义

Agent 身份是不可转让的。合约实现了 ERC-721 的传输接口(transferFromsafeTransferFromapprovesetApprovalForAll),但每个函数都无条件地以 IdentityNotTransferable() 回退。这确保了:

  • Agent 的身份不能被出售或转让给其他实体。
  • ownerOf() 关系是永久的(它返回底层的 EOA,而不是 UEA)。
  • agentId <-> owner 映射在注册后是不可变的。多个 UEA 可能别名为同一个身份,但规范持有者永远不会改变。

存储架构

TAPRegistry 使用基于 ERC-7201 命名空间的存储,以实现升级安全性。所有状态都位于一个确定性存储槽位的单个存储结构体中:

STORAGE_SLOT = keccak256(abi.encode(uint256(keccak256("tap.registry.storage")) - 1))
                & ~bytes32(uint256(0xff))

该存储结构体包含:

字段 类型 目的
records mapping(uint256 => AgentRecord) Agent ID 到注册记录的映射
bindings mapping(uint256 => BindEntry[]) Agent ID 到绑定数组的映射
bindToCanonical mapping(bytes32 => uint256) 去重键到规范 Agent ID + 1(全局唯一性)
bindIndex mapping(uint256 => mapping(bytes32 => uint256)) Agent ID + 链键到数组索引(用于 O(1) 查找)
bindExists mapping(uint256 => mapping(bytes32 => bool)) Agent ID + 链键到存在标志
usedNonces mapping(uint256 => mapping(uint256 => bool)) Agent ID + nonce 到已使用标志(重放保护)
ownerToAgentId mapping(address => uint256) UEA 地址到 Agent ID + 1(0 = 未注册)
ownerKeyToAgentId mapping(bytes32 => uint256) keccak256(ownerKey) 到 Agent ID + 1(持有者级别去重)

访问控制和暂停机制

  • DEFAULT_ADMIN_ROLE:可以授予/撤销角色。在初始化时设置。
  • PAUSER_ROLE:可以暂停/恢复合约。注册、元数据更新、绑定和解除绑定都是可暂停的。
  • 合约部署在 TransparentUpgradeableProxy 之后。

新特性(超越 ERC-8004)

ERC-8004 定义了各条链的身份注册表,使用可转让的 ERC-721 Token,并且没有跨链感知。TAPRegistry 引入了基础规范中不存在的一些特性。

灵魂绑定身份 Token

ERC-8004 为 Agent 身份发行可转让的 ERC-721 Token。TAPRegistry 覆盖了整个 ERC-721 传输接口(transferFromsafeTransferFromapprovesetApprovalForAll),无条件地以 IdentityNotTransferable() 回退。Agent 身份永久绑定到创建它的 UEA——它不能被出售、委托或转让给其他实体。这保证了注册后 agentId ↔ UEA 关系是不可变的。

带 EIP-712 密码学证明的绑定

ERC-8004 没有跨链身份绑定的概念。TAPRegistry 引入了 bind,其中 UEA 持有者签署一条 EIP-712 类型化数据消息,证明他们在另一条链的 ERC-8004 注册表上控制着相同的身份。签名将规范持有者地址、目标链命名空间、链 ID、注册表地址、绑定的 Agent ID、nonce 和截止日期绑定到一个单一的可验证证明中。同时支持 EOA 签名(ECDSA 恢复)和智能钱包签名(ERC-1271 isValidSignature),因此由多签或账户抽象钱包控制的 Agent 无需变通方法即可创建绑定。

全局绑定去重

一个绑定的身份元组 (chainNamespace, chainId, registryAddress, boundAgentId) 一次只能链接到一个规范 Agent。如果 Agent A 绑定到以太坊注册表上的 Agent ID 42,Agent B 不能认领相同的绑定——交易会以 BindingAlreadyClaimed 回退。当 Agent A 解除绑定时,去重键被释放,另一个 Agent 可以认领它。这在链上身份和规范身份之间强制执行严格的一对一绑定,防止了两个规范 Agent 声称是同一个链上实体的冒充行为。

TAPRegistry 如何与 ERC-8004 协同工作

ERC-8004 定义了各条链上 Agent 身份和信誉的标准。每条链部署自己的 IdentityRegistry(以及可选的 TAPReputationRegistryUpgradeable)。Agent 通过这些链上合约在每条链上独立注册。

TAPRegistry 作为跨链统一层位于 ERC-8004 之上:

                         Push Chain
                    +-----------------+
                    | TAPRegistry   |
                    |  (规范 ID)      |
                    +--------+--------+
                             |
                  绑定       |       绑定
           +-----------------+-----------------+
           |                 |                 |
    +------+------+   +------+------+   +------+------+
    | 以太坊      |   | Base        |   | Arbitrum    |
    | ERC-8004    |   | ERC-8004    |   | ERC-8004    |
    | IdentityReg |   | IdentityReg |   | IdentityReg |
    +-------------+   +-------------+   +-------------+

两者的关系是:

  • ERC-8004 处理每条链的注册、元数据和本地操作。
  • TAPRegistry 将每条链的注册映射到单个规范身份。
  • 绑定是桥梁——每个绑定说明“以太坊 0xABC... 上的 IdentityRegistry 中的 Agent #42 与 Push Chain 上的规范 Agent 0x123... 是同一个实体。”

反向查找

canonicalOwnerFromBinding() 函数实现了反向解析:给定一个链上 Agent 身份(链命名空间、链 ID、注册表地址、绑定的 Agent ID),找到 Push Chain 上的规范持有者(EOA)。这是允许任何链解析跨链 Agent 身份问题的关键原语。

实际示例:注册一个 AI 交易 Agent

考虑一个名为 “AlphaBot” 的 AI 交易 Agent,它在以太坊主网和 Base 上运行。该 Agent 的操作者希望拥有一个统一身份,以便任一条链上的用户都能验证他们正在与同一个 Agent 交互。

步骤 1:在 Push Chain 上创建一个 UEA

操作者的以太坊地址是 0xAlice...。他们使用 Push Chain UEA 工厂在 Push Chain 上创建一个通用执行账户。工厂记录:

  • 来源命名空间:"eip155"
  • 来源链 ID:"1"(以太坊主网)
  • 持有者密钥:0xAlice...(以太坊地址)

UEA 部署在 Push Chain 上的地址 0xUEA_Alice...

步骤 2:在 TAPRegistry 上注册

操作者从 UEA(0xUEA_Alice...)调用:

TAPRegistry.register(
    "ipfs://QmAlphaBotCard",        // Agent 卡元数据 URI
    keccak256(agentCardJSON)         // Agent 卡片内容的哈希
);

这会创建一个规范身份,其 7 位数字的 agentId 从 UEA 地址派生。注册记录存储了来源链信息和持有者密钥。

步骤 3:在各条链的 ERC-8004 注册表中注册

操作者在以太坊的 ERC-8004 IdentityRegistry 上注册 AlphaBot(获得 boundAgentId = 17),并在 Base 的 ERC-8004 IdentityRegistry 上注册(获得 boundAgentId = 42)。

步骤 4:绑定以太坊身份

操作者构建一条 EIP-712 消息:

Bind(
    canonicalOwner: 0xUEA_Alice...,
    chainNamespace: "eip155",
    chainId: "1",
    registryAddress: 0xEthIdentityRegistry...,
    boundAgentId: 17,
    nonce: 1,
    deadline: <当前时间戳 + 1 小时>
)

他们使用 0xAlice...(持有者密钥)的私钥签署此消息,然后调用:

TAPRegistry.bind(BindRequest({
    chainNamespace: "eip155",
    chainId: "1",
    registryAddress: 0xEthIdentityRegistry...,
    boundAgentId: 17,
    proofType: BindProofType.OWNER_KEY_SIGNED,
    proofData: signature,
    nonce: 1,
    deadline: block.timestamp + 1 hours
}));

合约验证签名,确认绑定未被占用,并存储该绑定。

步骤 5:绑定 Base 身份

Base 的流程相同:

TAPRegistry.bind(BindRequest({
    chainNamespace: "eip155",
    chainId: "8453",
    registryAddress: 0xBaseIdentityRegistry...,
    boundAgentId: 42,
    proofType: BindProofType.OWNER_KEY_SIGNED,
    proofData: baseSignature,
    nonce: 2,
    deadline: block.timestamp + 1 hours
}));

步骤 6:跨链身份解析

现在,Base 上与 Agent #42 交互的用户想知道这个 Agent 是否有规范身份。他们(或 dApp,或其他合约)查询 Push Chain:

(address canonical, bool verified) = TAPRegistry.canonicalOwnerFromBinding(
    "eip155",
    "8453",
    0xBaseIdentityRegistry...,
    42
);
// canonical = 0xAlice... (EOA,不是 UEA)
// verified = true

然后用户可以查询完整的 Agent 记录:

uint256 agentId = TAPRegistry.agentIdOfUEA(canonical);
ITAPRegistry.AgentRecord memory record = TAPRegistry.getAgentRecord(agentId);
// record.agentURI = "ipfs://QmAlphaBotCard"
// record.originChainNamespace = "eip155"
// record.originChainId = "1"
// record.registeredAt = <时间戳>

并查看所有绑定:

ITAPRegistry.BindEntry[] memory bindings = TAPRegistry.getBindings(agentId);
// bindings[0]: 以太坊主网, agentId 17
// bindings[1]: Base, agentId 42

用户现在拥有密码学证明,证明 Base 上的 Agent #42 和以太坊上的 Agent #17 是同一个实体,具有可验证的元数据 URI,并且能够检查 Agent 的跨链信誉(通过 TAPReputationRegistry)。

步骤 7:更新元数据

如果 AlphaBot 升级其模型或能力,操作者会更新 Agent 卡:

TAPRegistry.setAgentURI("ipfs://QmAlphaBotCardV2");
TAPRegistry.setAgentCardHash(keccak256(newAgentCardJSON));

此更新会立即对所有通过 TAPRegistry 解析的链可见。无需为身份元数据更新各条链的注册表。

步骤 8:解除绑定

如果 AlphaBot 停止在 Base 上运行,操作者会移除绑定:

TAPRegistry.unbind(
    "eip155",
    "8453",
    0xBaseIdentityRegistry...
);

绑定被移除,去重键被释放(其他 Agent 现在可以认领该链上身份),并且未来对该绑定的反向查询会返回 (address(0), false)

函数参考

注册

函数 访问权限 描述
register(agentURI, agentCardHash) UEA 持有者 注册或重新注册。返回 agentId
setAgentURI(newAgentURI) UEA 持有者 仅更新元数据 URI。
setAgentCardHash(newHash) UEA 持有者 仅更新 Agent 卡哈希。

绑定

函数 访问权限 描述
bind(req) UEA 持有者 使用 EIP-712 证明绑定一个链上 ERC-8004 身份。
unbind(ns, id, addr) UEA 持有者 移除一个绑定。

读取

函数 描述
ownerOf(agentId) Agent 的规范持有者地址(EOA)(ERC-721)。
tokenURI(agentId) 元数据 URI(兼容 ERC-721)。
agentURI(agentId) 元数据 URI(ERC-8004 别名)。
canonicalOwner(agentId) 给定 Agent ID 的规范持有者地址(EOA)。
agentIdOfUEA(uea) 给定 UEA 地址的 Agent ID(0 表示未注册)。
getBindings(agentId) 给定 Agent 的所有绑定条目。
canonicalOwnerFromBinding(ns, id, addr, boundId) 将绑定解析为规范持有者(EOA)。
isRegistered(agentId) 检查注册状态。
getAgentRecord(agentId) 完整的链上记录。

管理

函数 访问权限 描述
pause() PAUSER_ROLE 暂停所有状态变更操作。
unpause() PAUSER_ROLE 恢复操作。
  • 原文链接: github.com/zaryab2000/tr...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
zaryab2000
zaryab2000
江湖只有他的大名,没有他的介绍。