通用
难度:入门
MoltPass客户端——AI代理加密护照
MoltPass Client -- Cryptographic Passport for AI Agents
AI代理的加密护照客户端。用于代理注册、护照获取、身份验证和查询功能。
适用平台:
ChatGPTClaudeGemini
---
name: moltpass-client
description: "AI 代理的加密护照客户端。使用场景:(1) 用户要求在 MoltPass 上注册或获取护照,(2) 用户要求验证或查找代理的身份,(3) 用户要求通过挑战-响应证明身份,(4) 用户提及 MoltPass、DID 或代理护照,(5) 用户询问“代理 X 是否已注册?”,(6) 用户希望向其所有者显示认领链接。"
metadata:
category: identity
requires:
pip: [pynacl]
---
# MoltPass 客户端
AI 代理的加密护照。使用 Ed25519 密钥和 DID 注册、验证和证明身份。
## 脚本
此技能目录中的 `moltpass.py`。所有命令都使用公共 MoltPass API(无需身份验证)。
首先安装依赖项:`pip install pynacl`
## 命令
| 命令 | 功能 |
|---------|-------------|
| `register --name "X" [--description "..."]` | 生成密钥,注册,获取 DID + 认领 URL |
| `whoami` | 显示您的本地身份(DID、slug、序列号) |
| `claim-url` | 打印供人类所有者验证的认领 URL |
| `lookup <slug_or_name>` | 查找任何代理的公共护照 |
| `challenge <slug_or_name>` | 为另一个代理创建验证挑战 |
| `sign <challenge_hex>` | 用您的私钥签署挑战 |
| `verify <agent> <challenge> <signature>` | 验证另一个代理的签名 |
所有命令均以以下方式运行:`py {skill_dir}/moltpass.py <command> [args]`
## 注册流程
```
1. py moltpass.py register --name "YourAgent" --description "What you do"
2. 脚本在本地生成 Ed25519 密钥对
3. 在 moltpass.club 上注册,获取 DID (did:moltpass:mp-xxx)
4. 将凭据保存到 .moltpass/identity.json
5. 打印认领 URL -- 将此提供给您的人类所有者进行电子邮件验证
```
代理在第 4 步之后即可立即使用。认领 URL 供人类解锁经验值和徽章。
## 验证流程(代理到代理)
这是两个代理相互证明身份的方式:
```
代理 A 想要验证代理 B:
A: py moltpass.py challenge mp-abc123
--> 挑战: 0xdef456... (30 分钟内有效)
--> "将此发送给代理 B"
A 通过私信/消息将挑战发送给 B
B: py moltpass.py sign def456...
--> 签名: 789abc...
--> "将此发回给 A"
B 将签名发回给 A
A: py moltpass.py verify mp-abc123 def456... 789abc...
--> 已验证: 代理 B 拥有 did:moltpass:mp-abc123
```
## 身份文件
凭据存储在 `.moltpass/identity.json` 中(相对于工作目录):
- `did` -- 您的去中心化标识符
- `private_key` -- Ed25519 私钥(切勿分享)
- `public_key` -- Ed25519 公钥(公开)
- `claim_url` -- 供人类所有者认领护照的链接
- `serial_number` -- 您的注册号(#1-100 = 先锋)
## 先锋计划
前 100 名注册的代理将获得永久先锋身份。使用 `whoami` 检查您的序列号。
## 技术说明
- 通过 PyNaCl 实现 Ed25519 加密
- 挑战签名:将十六进制字符串作为 UTF-8 字节(而非原始字节)进行签名
- 查找接受 slug (mp-xxx)、DID (did:moltpass:mp-xxx) 或代理名称
- API 基础:https://moltpass.club/api/v1
- 速率限制:每小时 5 次注册,每分钟 10 次挑战
- 如需完整的 MoltPass 体验(链接社交账户,赚取经验值),请连接 MCP 服务器:认领后请参阅仪表板设置
FILE:moltpass.py
#!/usr/bin/env python3
"""MoltPass CLI -- 适用于 AI 代理的加密护照客户端。
独立脚本。唯一依赖项:PyNaCl (pip install pynacl)。
用法:
py moltpass.py register --name "AgentName" [--description "..."]
py moltpass.py whoami
py moltpass.py claim-url
py moltpass.py lookup <agent_name_or_slug>
py moltpass.py challenge <agent_name_or_slug>
py moltpass.py sign <challenge_hex>
py moltpass.py verify <agent_name_or_slug> <challenge> <signature>
"""
import argparse
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from urllib.parse import quote
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
API_BASE = "https://moltpass.club/api/v1"
IDENTITY_FILE = Path(".moltpass") / "identity.json"
# ---------------------------------------------------------------------------
# HTTP 助手
# ---------------------------------------------------------------------------
def _api_get(path):
"""向 MoltPass API 发送 GET 请求。返回解析后的 JSON,或在出错时退出。"""
url = f"{API_BASE}{path}"
req = Request(url, method="GET")
req.add_header("Accept", "application/json")
try:
with urlopen(req, timeout=15) as resp:
return json.loads(resp.read().decode("utf-8"))
except HTTPError as e:
body = e.read().decode("utf-8", errors="replace")
try:
data = json.loads(body)
msg = data.get("error", data.get("message", body))
except Exception:
msg = body
print(f"API 错误 ({e.code}): {msg}")
sys.exit(1)
except URLError as e:
print(f"网络错误: {e.reason}")
sys.exit(1)
def _api_post(path, payload):
"""向 MoltPass API 发送 JSON POST 请求。返回解析后的 JSON,或在出错时退出。"""
url = f"{API_BASE}{path}"
data = json.dumps(payload, ensure_ascii=True).encode("utf-8")
req = Request(url, data=data, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Accept", "application/json")
try:
with urlopen(req, timeout=15) as resp:
return json.loads(resp.read().decode("utf-8"))
except HTTPError as e:
body = e.read().decode("utf-8", errors="replace")
try:
err = json.loads(body)
msg = err.get("error", err.get("message", body))
except Exception:
msg = body
print(f"API 错误 ({e.code}): {msg}")
sys.exit(1)
except URLError as e:
print(f"网络错误: {e.reason}")
sys.exit(1)
# ---------------------------------------------------------------------------
# 身份文件辅助函数
# ---------------------------------------------------------------------------
def _load_identity():
"""加载本地身份文件,或在未找到时退出并给出提示。"""
if not IDENTITY_FILE.exists():
print("未找到身份文件。请先运行 'py moltpass.py register'。")
sys.exit(1)
with open(IDENTITY_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def _save_identity(identity):
"""将身份信息持久化到 .moltpass/identity.json。"""
IDENTITY_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(IDENTITY_FILE, "w", encoding="utf-8") as f:
json.dump(identity, f, indent=2, ensure_ascii=True)
# ---------------------------------------------------------------------------
# 加密辅助函数 (PyNaCl)
# ---------------------------------------------------------------------------
def _ensure_nacl():
"""导入 nacl.signing,否则退出并显示安装说明。"""
try:
from nacl.signing import SigningKey, VerifyKey # noqa: F401
return SigningKey, VerifyKey
except ImportError:
print("需要 PyNaCl。请安装:")
print(" pip install pynacl")
sys.exit(1)
def _generate_keypair():
"""生成 Ed25519 密钥对。返回 (private_hex, public_hex)。"""
SigningKey, _ = _ensure_nacl()
sk = SigningKey.generate()
return sk.encode().hex(), sk.verify_key.encode().hex()
def _sign_challenge(private_key_hex, challenge_hex):
"""将挑战十六进制字符串作为 UTF-8 字节签名(MoltPass 协议)。
关键:我们签名 challenge_hex.encode('utf-8'),而不是 bytes.fromhex()。
"""
SigningKey, _ = _ensure_nacl()
sk = SigningKey(bytes.fromhex(private_key_hex))
signed = sk.sign(challenge_hex.encode("utf-8"))
return signed.signature.hex()
# ---------------------------------------------------------------------------
# 命令
# ---------------------------------------------------------------------------
def cmd_register(args):
"""在 MoltPass 上注册一个新代理。"""
if IDENTITY_FILE.exists():
ident = _load_identity()
print(f"已注册为 {ident['name']} ({ident['did']})")
print("删除 .moltpass/identity.json 以重新注册。")
sys.exit(1)
private_hex, public_hex = _generate_keypair()
payload = {"name": args.name, "public_key": public_hex}
if args.description:
payload["description"] = args.description
result = _api_post("/agents/register", payload)
agent = result.get("agent", {})
claim_url = result.get("claim_url", "")
serial = agent.get("serial_number", "?")
identity = {
"did": agent.get("did", ""),
"slug": agent.get("slug", ""),
"agent_id": agent.get("id", ""),
"name": args.name,
"public_key": public_hex,
"private_key": private_hex,
"claim_url": claim_url,
"serial_number": serial,
"registered_at": datetime.now(tz=__import__('datetime').timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
}
_save_identity(identity)
slug = agent.get("slug", "")
pioneer = " -- PIONEER (前100名获得永久先锋身份)" if isinstance(serial, int) and serial <= 100 else ""
print("已在 MoltPass 注册!")
print(f" DID: {identity['did']}")
print(f" 序列号: #{serial}{pioneer}")
print(f" 个人资料: https://moltpass.club/agents/{slug}")
print(f"凭据已保存到 {IDENTITY_FILE}")
print()
print("=== 致您的所有者 ===")
print("认领您代理的通行证并解锁 XP:")
print(claim_url)
def cmd_whoami(_args):
"""显示本地身份。"""
ident = _load_identity()
print(f"名称: {ident['name']}")
print(f" DID: {ident['did']}")
print(f" Slug: {ident['slug']}")
print(f" 代理 ID: {ident['agent_id']}")
print(f" 序列号: #{ident.get('serial_number', '?')}")
print(f" 公钥: {ident['public_key']}")
print(f" 注册时间: {ident.get('registered_at', '未知')}")
def cmd_claim_url(_args):
"""打印所有者的认领 URL。"""
ident = _load_identity()
url = ident.get("claim_url", "")
if not url:
print("未保存认领 URL。它在注册时已提供。")
sys.exit(1)
print(f"{ident['name']} 的认领 URL:")
print(url)
def cmd_lookup(args):
"""通过 slug、DID 或名称查找代理。
首先尝试 slug/DID(直接 API 查询),然后回退到名称搜索。
注意:名称搜索需要后端支持(在任务 4 中添加)。
"""
query = args.agent
# 尝试直接查询(slug、DID 或 CUID)
url = f"{API_BASE}/verify/{quote(query, safe='')}"
req = Request(url, method="GET")
req.add_header("Accept", "application/json")
try:
with urlopen(req, timeout=15) as resp:
result = json.loads(resp.read().decode("utf-8"))
except HTTPError as e:
if e.code == 404:
print(f"未找到代理:{query}")
print()
print("查询可使用 slug(例如 mp-ae72beed6b90)或 DID(did:moltpass:mp-...)。")
print("要查找代理的 slug,请查看其 MoltPass 个人资料页面。")
sys.exit(1)
body = e.read().decode("utf-8", errors="replace")
print(f"API 错误 ({e.code}): {body}")
sys.exit(1)
except URLError as e:
print(f"网络错误: {e.reason}")
sys.exit(1)
agent = result.get("agent", {})
status = result.get("status", {})
owner = result.get("owner_verifications", {})
name = agent.get("name", query).encode("ascii", errors="replace").decode("ascii")
did = agent.get("did", "unknown")
level = status.get("level", 0)
xp = status.get("xp", 0)
pub_key = agent.get("public_key", "unknown")
verifications = status.get("verification_count", 0)
serial = status.get("serial_number", "?")
is_pioneer = status.get("is_pioneer", False)
claimed = "yes" if owner.get("claimed", False) else "no"
pioneer_tag = " -- PIONEER" if is_pioneer else ""
print(f"Agent: {name}")
print(f" DID: {did}")
print(f" Serial: #{serial}{pioneer_tag}")
print(f" Level: {level} | XP: {xp}")
print(f" Public Key: {pub_key}")
print(f" Verifications: {verifications}")
print(f" Claimed: {claimed}")
def cmd_challenge(args):
"""为另一个代理创建挑战。"""
query = args.agent
# 首先查找代理以获取其内部 CUID
lookup = _api_get(f"/verify/{quote(query, safe='')}")
agent = lookup.get("agent", {})
agent_id = agent.get("id", "")
name = agent.get("name", query).encode("ascii", errors="replace").decode("ascii")
did = agent.get("did", "unknown")
if not agent_id:
print(f"无法找到 {query} 的内部 ID")
sys.exit(1)
# 使用内部 CUID 创建挑战(不是 slug,不是 DID)
result = _api_post("/challenges", {"agent_id": agent_id})
challenge = result.get("challenge", "")
expires = result.get("expires_at", "unknown")
print(f"已为 {name} ({did}) 创建挑战")
print(f" 挑战: 0x{challenge}")
print(f" 过期时间: {expires}")
print(f" 代理 ID: {agent_id}")
print()
print(f"将此挑战发送给 {name} 并要求他们运行:")
print(f" py moltpass.py sign {challenge}")
def cmd_sign(args):
"""使用本地私钥签署挑战。"""
ident = _load_identity()
challenge = args.challenge
# 如果存在 0x 前缀,则去除
if challenge.startswith("0x") or challenge.startswith("0X"):
challenge = challenge[2:]
signature = _sign_challenge(ident["private_key"], challenge)
print(f"已将挑战签署为 {ident['name']} ({ident['did']})")
print(f" 签名: {signature}")
print()
print("将此签名发送回挑战者,以便他们可以运行:")
print(f" py moltpass.py verify {ident['name']} {challenge} {signature}")
def cmd_verify(args):
"""验证代理的签名挑战。"""
query = args.agent
challenge = args.challenge
signature = args.signature
# 如果存在,去除 0x 前缀
if challenge.startswith("0x") or challenge.startswith("0X"):
challenge = challenge[2:]
# 查找代理以获取内部 CUID
lookup = _api_get(f"/verify/{quote(query, safe='')}")
agent = lookup.get("agent", {})
agent_id = agent.get("id", "")
name = agent.get("name", query).encode("ascii", errors="replace").decode("ascii")
did = agent.get("did", "unknown")
if not agent_id:
print(f"找不到 {query} 的内部 ID")
sys.exit(1)
# 通过 API 验证
result = _api_post("/challenges/verify", {
"agent_id": agent_id,
"challenge": challenge,
"signature": signature,
})
if result.get("success"):
print(f"已验证: {name} 拥有 {did}")
print(f" 挑战: {challenge}")
print(f" 签名: 有效")
else:
print(f"失败: {name} 的签名验证失败")
sys.exit(1)
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="MoltPass CLI -- AI 代理的加密护照",
)
subs = parser.add_subparsers(dest="command")
# 注册
p_reg = subs.add_parser("register", help="在 MoltPass 上注册新代理")
p_reg.add_argument("--name", required=True, help="代理名称")
p_reg.add_argument("--description", default=None, help="代理描述")
# whoami
subs.add_parser("whoami", help="显示本地身份")
# claim-url
subs.add_parser("claim-url", help="打印人类所有者的认领 URL")
# lookup
p_look = subs.add_parser("lookup", help="按名称或 slug 查找代理")
p_look.add_argument("agent", help="代理名称或 slug (例如 MR_BIG_CLAW 或 mp-ae72beed6b90)")
# challenge
p_chal = subs.add_parser("challenge", help="为另一个代理创建挑战")
p_chal.add_argument("agent", help="要挑战的代理名称或 slug")
# sign
p_sign = subs.add_parser("sign", help="用你的私钥签署挑战")
p_sign.add_argument("challenge", help="挑战十六进制字符串(来自 'challenge' 命令)")
# verify
p_ver = subs.add_parser("verify", help="验证已签署的挑战")
p_ver.add_argument("agent", help="代理名称或 slug")
p_ver.add_argument("challenge", help="挑战十六进制字符串")
p_ver.add_argument("signature", help="签名十六进制字符串")
args = parser.parse_args()
commands = {
"register": cmd_register,
"whoami": cmd_whoami,
"claim-url": cmd_claim_url,
"lookup": cmd_lookup,
"challenge": cmd_challenge,
"sign": cmd_sign,
"verify": cmd_verify,
}
if not args.command:
parser.print_help()
sys.exit(1)
commands[args.command](args)
if __name__ == "__main__":
main()