TAPRegistry 是 AI 代理的跨链身份合约,部署在 Push Chain 上,为每个代理创建不可转让的规范身份,并通过 EIP-712 签名绑定到以太坊、Base 等链上的 ERC-8004 注册身份。它解决了不同链上代理身份碎片化的问题,支持所有者级去重、全局唯一绑定和反向解析。文章详细介绍了其双层身份模型、注册流程、绑定机制、存储架构以及实际用例,展示了如何通过 UEA(通用执行账户)和签名验证实现统一的跨链代理身份图谱。
ERC-8004 为每条链赋予了独立的 IdentityRegistry。在以太坊主网上注册的 Agent 会获得一个以太坊上的 agentId。同一个 Agent 在 Base 上注册,会获得 Base 上一个不同的 agentId。这两个身份是完全割裂的:
没有规范注册表,“跨链 Agent 身份”就成了需要手动操作且信任操作者的事情。用户必须依赖链下社交信号(网站、推特、文档)来判断不同链上的 Agent 是否是同一个实体。一旦 Agent 变得自主运行,并需要机器可读、密码学可验证的身份时,这种方式就会失效。
TAPRegistry 引入了一个两层身份模型:
Push Chain 的通用执行账户(UEA)是通过工厂部署的账户,它将外部链身份桥接到 Push Chain。当来自以太坊的用户在 Push Chain 上创建 UEA 时,UEA 工厂会记录他们的来源链、链 ID 和持有者密钥(控制 UEA 的以太坊地址)。
agentId 是确定性的:agentId = uint256(uint160(ueaAddress)) % 10_000_000。这意味着:
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() 的调用会更新 agentURI 和 agentCardHash,而不会修改来源元数据。这是重新注册路径——同一个函数,具有幂等语义。
同一个 EOA 钱包可以在不同的来源链(以太坊、Base、BSC 等)上创建 UEA,每个 UEA 都会在 Push Chain 上产生一个不同的 UEA 地址。如果没有去重,同一个人或实体最终可能拥有多个独立的 Agent 身份。
TAPRegistry 通过 ownerKeyToAgentId 防止这种情况,这是一个从 keccak256(origin.owner) 到 agentId + 1 的映射。注册流程有三个分支:
ownerToAgentId 条目。只更新 agentURI 和 agentCardHash。ownerKeyToAgentId 找到了匹配项。新 UEA 连接到现有身份——它获得相同的 agentId,可以调用 setAgentURI/setAgentCardHash,并触发一个 UEALinked 事件。agentId 创建一个新的 AgentRecord。这确保了每个底层钱包对应一个身份,无论该钱包从多少个来源链操作。
注册后,Agent 会将其每条链上的 ERC-8004 注册连接到规范身份。每个绑定表示以下两者之间的链接:
agentId 标识)chainNamespace、chainId、registryAddress、boundAgentId 标识)Agent 构建一条 EIP-712 类型化数据消息,包含:
canonicalOwner:Push Chain 上的 UEA 地址(调用者)chainNamespace:目标链的 CAIP-2 命名空间(例如 "eip155")chainId:CAIP-2 链 ID(例如 "1")registryAddress:该链上的 ERC-8004 IdentityRegistry 合约地址boundAgentId:Agent 在该链注册表中的 IDnonce:一个唯一的 nonce,防止重放deadline:签名过期的时间戳Agent 使用控制 UEA 的私钥(注册时记录的 ownerKey)签署此消息。签名证明控制规范身份的实体也控制了链上身份。
Agent 在 Push Chain 上调用 bind(),并传入签名后的请求。合约会:
ownerKey 验证签名(支持 ECDSA 和 ERC-1271 合约签名)合约支持两种签名方案:
(r, s, v) 签名。合约恢复签名者地址,并检查它是否与 ownerKey 的前 20 个字节匹配。proofData 编码为 abi.encodePacked(signerAddress, signatureBytes)。合约使用 50,000 gas 限制在签名者地址上调用 isValidSignature()。(chainNamespace, chainId, registryAddress, boundAgentId) 只能由一个规范 Agent 认领。这防止了两个 Agent 认领同一个链上身份。(chainNamespace, chainId, registryAddress) 元组最多只能有一个绑定。要更改某个链+注册表的 boundAgentId,Agent 必须先解除绑定,然后再重新绑定。Agent 调用 unbind(chainNamespace, chainId, registryAddress) 来移除绑定。为了节省 gas,在绑定数组上使用交换并弹出模式——待移除的条目与最后一个条目交换,然后弹出数组。所有索引都会相应更新。
Agent 身份是不可转让的。合约实现了 ERC-721 的传输接口(transferFrom、safeTransferFrom、approve、setApprovalForAll),但每个函数都无条件地以 IdentityNotTransferable() 回退。这确保了:
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(持有者级别去重) |
TransparentUpgradeableProxy 之后。ERC-8004 定义了各条链的身份注册表,使用可转让的 ERC-721 Token,并且没有跨链感知。TAPRegistry 引入了基础规范中不存在的一些特性。
ERC-8004 为 Agent 身份发行可转让的 ERC-721 Token。TAPRegistry 覆盖了整个 ERC-721 传输接口(transferFrom、safeTransferFrom、approve、setApprovalForAll),无条件地以 IdentityNotTransferable() 回退。Agent 身份永久绑定到创建它的 UEA——它不能被出售、委托或转让给其他实体。这保证了注册后 agentId ↔ UEA 关系是不可变的。
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 声称是同一个链上实体的冒充行为。
ERC-8004 定义了各条链上 Agent 身份和信誉的标准。每条链部署自己的 IdentityRegistry(以及可选的 TAPReputationRegistryUpgradeable)。Agent 通过这些链上合约在每条链上独立注册。
TAPRegistry 作为跨链统一层位于 ERC-8004 之上:
Push Chain
+-----------------+
| TAPRegistry |
| (规范 ID) |
+--------+--------+
|
绑定 | 绑定
+-----------------+-----------------+
| | |
+------+------+ +------+------+ +------+------+
| 以太坊 | | Base | | Arbitrum |
| ERC-8004 | | ERC-8004 | | ERC-8004 |
| IdentityReg | | IdentityReg | | IdentityReg |
+-------------+ +-------------+ +-------------+
两者的关系是:
0xABC... 上的 IdentityRegistry 中的 Agent #42 与 Push Chain 上的规范 Agent 0x123... 是同一个实体。”canonicalOwnerFromBinding() 函数实现了反向解析:给定一个链上 Agent 身份(链命名空间、链 ID、注册表地址、绑定的 Agent ID),找到 Push Chain 上的规范持有者(EOA)。这是允许任何链解析跨链 Agent 身份问题的关键原语。
考虑一个名为 “AlphaBot” 的 AI 交易 Agent,它在以太坊主网和 Base 上运行。该 Agent 的操作者希望拥有一个统一身份,以便任一条链上的用户都能验证他们正在与同一个 Agent 交互。
操作者的以太坊地址是 0xAlice...。他们使用 Push Chain UEA 工厂在 Push Chain 上创建一个通用执行账户。工厂记录:
"eip155""1"(以太坊主网)0xAlice...(以太坊地址)UEA 部署在 Push Chain 上的地址 0xUEA_Alice...。
操作者从 UEA(0xUEA_Alice...)调用:
TAPRegistry.register(
"ipfs://QmAlphaBotCard", // Agent 卡元数据 URI
keccak256(agentCardJSON) // Agent 卡片内容的哈希
);
这会创建一个规范身份,其 7 位数字的 agentId 从 UEA 地址派生。注册记录存储了来源链信息和持有者密钥。
操作者在以太坊的 ERC-8004 IdentityRegistry 上注册 AlphaBot(获得 boundAgentId = 17),并在 Base 的 ERC-8004 IdentityRegistry 上注册(获得 boundAgentId = 42)。
操作者构建一条 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
}));
合约验证签名,确认绑定未被占用,并存储该绑定。
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
}));
现在,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)。
如果 AlphaBot 升级其模型或能力,操作者会更新 Agent 卡:
TAPRegistry.setAgentURI("ipfs://QmAlphaBotCardV2");
TAPRegistry.setAgentCardHash(keccak256(newAgentCardJSON));
此更新会立即对所有通过 TAPRegistry 解析的链可见。无需为身份元数据更新各条链的注册表。
如果 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码