Appearance
Avatars
VirMesh のアバター manifest を解決し、署名と短命 grant で安全に取得する方法を説明します。
アバターの重い asset は AvatarServer から分離して配信できます。クライアントは me.virmesh.avatar.resolveAvatar で署名付き manifest を解決し、manifest 内の URL からモデル、サムネイル、WASM script を取得します。
Model
アバターは avatarId を持ちます。
text
medi:avatar:<scheme>:<publicKey>avatarId の公開鍵は manifest 署名の検証に使います。publisher の player 鍵は avatarId と publisher id を含む delegation payload に署名します。
この分離により、publisher は player identity を保ったまま、アバターごとに別の鍵を使えます。handle は表示用 snapshot として manifest に入れられますが、権威情報は player id です。
json
{
"avatarId": "medi:avatar:ed25519:avatar-public-key",
"publisherDelegation": {
"payload": {
"avatarId": "medi:avatar:ed25519:avatar-public-key",
"publisher": {
"id": "medi:player:ed25519:publisher-public-key",
"handle": "[email protected]"
},
"issuedAt": 1770000000
},
"signature": "base64-signature-by-publisher"
}
}publisherDelegation.signature は canonical JSON of publisherDelegation.payload に対する publisher の署名です。manifest response の signature は canonical JSON of payload.manifest に対する avatar 鍵の署名です。
Public avatars
public avatar は、同じ manifest と asset URL を複数の wearer が参照できます。これは VRChat の public avatar に近い使い方です。
クライアントは次の順で処理します。
- wearer の presence から
avatarReferenceを読む。 avatarReference.endpointのme.virmesh.avatar.resolveAvatarを呼ぶ。- response の manifest 署名を
avatarIdから検証する。 publisherDelegationを publisher id から検証する。- manifest 内の asset を取得し、
hashが一致するときだけロードする。
manifest には形式を固定しません。asset entry は contentType、profile、url、hash、size を持ちます。
json
{
"assets": [
{
"kind": "model",
"contentType": "model/gltf-binary",
"profile": "glb",
"url": "https://cdn.example.com/avatars/avatar.glb",
"hash": "sha256:base64url-hash",
"size": 2480000
}
]
}versionId または hash を指定して解決した場合、クライアントは manifest と asset の hash を必ず確認します。一致しない asset はロードしません。
Private avatars
private avatar は、world relay 経由で wearer が短命 fetchGrant を発行した場合だけ取得できます。これは VRChat の private avatar に近い制御ですが、認可は wearer の署名で表現します。
最小フローは次です。
- viewer は同じ world/instance に参加している wearer の avatar を表示したい。
- viewer は WorldServer または InstanceServer の relay に
fetchGrant要求を送る。 - relay は viewer が同じ world/instance の参加者であることを確認し、wearer client に中継する。
- wearer client は
avatarFetchGrant.payloadを署名して viewer に返す。 - viewer は
fetchGrantをme.virmesh.avatar.resolveAvatarに添えて manifest を解決する。 - AvatarServer は grant の署名、発行時刻、viewer、wearer、avatar 条件を検証する。
fetchGrant は issuedAt から 60 秒程度の短い許容時間だけ受け付けます。relay は world/instance の参加確認を行いますが、その context は fetchGrant.payload には入れません。
json
{
"payload": {
"avatarId": "medi:avatar:ed25519:avatar-public-key",
"versionId": "2026-04-11T00:00:00Z",
"wearerId": "medi:player:ed25519:wearer-public-key",
"viewerId": "medi:player:ed25519:viewer-public-key",
"issuedAt": 1770000000
},
"signature": "base64-signature-by-wearer"
}private asset URL は grant-bound URL または短命 signed URL にします。viewer は fetchGrant を CDN request に添えず、manifest 内の URL をそのまま取得します。manifest だけを保護して asset が恒久公開 URL から取れる形にはしません。
json
{
"assets": [
{
"kind": "model",
"contentType": "model/gltf-binary",
"profile": "glb",
"url": "https://cdn.example.com/avatars/private/avatar.glb?token=short-lived-asset-token",
"hash": "sha256:base64url-hash",
"size": 2480000
}
]
}Purchase receipts
購入証明は avatarPurchaseReceipt として扱います。これは seller が buyer に対して発行した検証可能な receipt claim です。
v1 の receipt は「正規販売者から購入された」ことまでは保証しません。クライアントや viewer は receipt の seller、buyer、shop URL、対象 avatar を検証し、seller を信頼するかを自分で判断します。
json
{
"payload": {
"avatarId": "medi:avatar:ed25519:avatar-public-key",
"shopUrl": "https://shop.example.com/items/alice-avatar",
"seller": {
"id": "medi:player:ed25519:seller-public-key",
"handle": "[email protected]"
},
"buyer": {
"id": "medi:player:ed25519:buyer-public-key",
"handle": "[email protected]"
},
"issuedAt": 1770000000
},
"signature": "base64-signature-by-seller"
}他の player は、提示された receipt が buyer id と一致しているかを確認できます。提示がない場合に分かるのは、検証可能な正規 receipt を提示していないことです。
Avatar scripts
WASM script は manifest の script に入れます。v1 では host API を定義せず、sandbox と permission model だけを固定します。
json
{
"script": {
"url": "https://cdn.example.com/avatars/avatar-script.wasm",
"hash": "sha256:base64url-hash",
"permissions": ["avatar.self"],
"sandbox": {
"runtime": "wasm",
"network": false,
"filesystem": false,
"worldMutation": false,
"maxMemoryBytes": 16777216,
"maxExecutionMillis": 5
}
}
}クライアントは script 実行を拒否できます。v1 では network access、filesystem access、world mutation は許可しません。world への影響や他 player との高権限 interaction は、別の host API 仕様で扱います。
Next steps
- endpoint の詳細は
me.virmesh.avatar.resolveAvatarを参照してください。 - object schema は
avatarManifest、avatarReference、avatarFetchGrant、avatarPurchaseReceiptを参照してください。 - 署名対象と canonical JSON は Transport を参照してください。