HyperliquidのStarsをバイナリから読み解く
/ 18 min read
Table of Contents
調査時点: 2026-08-29(JST)
根拠の扱い: 本稿では、
hl-nodeの静的解析で確認できた実装事実、公式Docsで確認できる公開仕様、テストネットに関する外部観測を分けて記す。strip済みバイナリの解析から、未確認のfield名や署名payloadを推測して断定することは避ける。
HyperliquidのHIP-3 marketには、特定のアドレスだけに取引を許可する「Stars」と呼ばれる仕組みが実装されている。公開Docsではまだ詳細なschemaが説明されていない一方、現在のhl-nodeバイナリにはHip3DeployAction::Star、modifyApprovals、approveUsers、too many approved usersといった、かなり具体的な実装語彙が含まれている。
今回の解析で分かったのは、Starsが単なるフラグではなく、market単位の20-byte address whitelistをRustのordered mapで管理する仕組みだということだ。追加と削除は差分として適用され、最終的な承認数は10,000件以下に制限される。未承認アドレスを完全に無効化するのではなく、reduceOnlyという状態も用意されている。
概要(TL;DR)
Hip3DeployAction::Starは、HIP-3 deploy actionの独立したenum variantとして実装されている。- Starsのoperationには
activate、modifyApprovals、paがある。 modifyApprovalsのinner payloadは、[20-byte address, bool]の配列として解析される。trueは承認追加、falseは承認削除。既存状態との差分だけが適用される。- whitelistは20-byte addressをkeyとするRust
BTreeMap相当の構造で保持される。 - 適用後の承認数は10,000以下でなければならず、超過時のエラー文字列は
too many approved usersである。 disabled、reduceOnly、enabledのmodeが存在し、reduceOnly遷移ではwhitelist treeを初期化する書き込みが確認できる。- outer Star payloadの正確なfield名、完全な署名付きaction、注文検証側の最終lookup関数はまだ確定していない。
Starsは何をするのか
Starsは、HIP-3で作成されたmarketをアドレスallowlist型で運用するための機構と考えられる。テストネットの公開APIでは、ktobというBTC Star DEXが確認できた。外部観測でも、Starsの有効化、traderのapprove、unhaltの後に、未承認アドレスの注文が失敗し、承認済みアドレスの注文が成功するsequenceが報告されている。テストネット上の観測
また、公開報告では、未承認アドレスでもfund、deposit、決済方向の操作は可能とされている。これは今回バイナリで確認したreduceOnly modeと整合する。Starsの概要と上限に関する報告
現在の公式HIP-3 Docsが説明しているのは、主にsetSubDeployersとSubDeployerInput { variant, user, allowed }であり、StarsやmodifyApprovalsの公開schemaは確認できない。公式HIP-3 deployer actions、公式HIP-3 proposal
したがって、Starsについては「公開仕様を読めば全体が分かる」状態ではない。以下では、現在取得できたノードバイナリに何が実装されているかを中心に見ていく。
解析対象
対象は、Rustでコンパイルされ、debug symbolsが除去されたhl-nodeである。
path: /home/dev/hl-nodeversion: 6df83ed3250019f0cf5286b9ef214493d12ce157build time: 2026-08-28 11:25:59 +0000sha256: 9004d9c4c425e003dc657af542862eda67d9e3bfb936459f483e13c00e00c26compiler: rustc 1.95.0 (59807616e 2026-04-14)Ghidraでは/hl-nodeを解析し、Rustの型名やserde由来の文字列、parser、jump table、BTree操作関数を相互に照合した。バイナリはstrip済みなので、関数名はほぼFUN_...の自動名である。したがって、関数名そのものではなく、文字列、引数の流れ、比較幅、stateへの書き込み、helperの挙動を組み合わせて意味付けしている。
actionの全体像
まず、FUN_02a854c0がHip3DeployActionのJSON object-key parserであることを確認した。このparserはstarをlength 4のkeyとして認識し、内部tag 0x10を生成する。
Starsのoperation parserまでをつなぐと、構造は次のようになる。
Hip3DeployAction::Star tag = 0x10 └─ Hip3StarOperation ├─ activate tag = 0 ├─ modifyApprovals tag = 1 │ └─ [[20-byte address, bool], ...] └─ pa tag = 2Hip3StarOperationのobject-key parserは、FUN_01d7cdf0とFUN_01dcce70にある。両者で次の対応が一致している。
| JSON key | internal tag |
|---|---|
activate |
0 |
modifyApprovals |
1 |
pa |
2 |
FUN_02aa6df0は、このoperationを実際の内部表現へ変換するgenerated parserである。modifyApprovalsの分岐から、別のtyped sequence parserであるFUN_02b54330へ到達する。
なお、バイナリ中にはHip3StarOperation::ProxyAction with 2 elementsというserde metadataもある。ただし、outer Star objectが持つ2つのfieldの正確な名前までは、今回の解析では確定していない。paを見つけたからといって、完全な送信用JSONを組み立てられるわけではない。
modifyApprovalsの入力形式
FUN_02b54330のarray pathはFUN_02bbfd90に入る。ここで各要素についてFUN_01d61010が呼ばれ、要素を2つの値からなるtupleとして解析する。
FUN_01dea8e0: JSON stringから20-byte addressをdecodeFUN_01deae80: JSON booleanを解析し、falseを0、trueを1にするFUN_01d4ba40: arrayのbracket、comma、iteratorを処理
従って、確認済みのinner shapeは概念的に次の通りである。
"modifyApprovals": [ ["0x<40 hex characters>", true], ["0x<40 hex characters>", false]]ここで重要なのは、入力を単純なVec<Address>として扱っていない点だ。FUN_02bbfd90は読み込んだaddress/bool pairを内部BTreeへ入れ、重複keyの扱いをordered map側に寄せている。handlerへ渡る時点では、既に差分集合に近い内部表現になっている。
これはStarsの「誰を承認するか」という状態を直接渡すのではなく、「どのaddressを追加・削除するか」という変更集合を渡す設計である。
whitelistの内部構造
20-byte keyのBTree
FUN_02948a30、FUN_02954440、FUN_028612c0、FUN_028683d0を追うと、whitelistのstorageは20-byte keyを持つRust BTreeMapに近い構造だと分かる。
| 観測 | 意味づけ |
|---|---|
key幅 0x14 |
20-byte address |
node +0xe6 |
node内のkey数(16-bit) |
node +0x08からindex * 0x14 |
key slot |
node +0xe8 + index |
各keyに対応するbool |
wrapper +0x10 |
entry count |
nodeのkeyは20 bytesずつ並び、boolは別領域に並ぶ。これは、addressと承認状態をkey/value pairとして保持するordered mapのレイアウトと一致する。
addとremove
追加側のFUN_02954440では、必要に応じてnodeを確保し、20-byte keyをslotへコピーした後、wrapperのentry countをincrementする。削除側のFUN_028612c0では、同じ20-byte comparatorを使ってkeyを探し、FUN_01cf5380を通じてBTreeのdelete/rebalanceを行い、entry countをdecrementする。
つまり、falseは「承認フラグをfalseにするだけ」ではなく、現在のBTreeからkeyを削除する処理である。
Star stateのoffset
current Star market stateについて、handlerから次のoffsetが参照される。
state + 0x78 trading modestate + 0x80 BTree wrapperの先頭(root pointerを含む)state + 0x90 BTree entry countmarketの選択にはstate + 0x8b8とFUN_03e283f0が関係している。少なくとも確認できた処理はglobalな全market共通setではなく、選択されたStar marketのstate内にwhitelistを持つ形になっている。
承認更新の処理
承認更新の中心はFUN_025b01b0である。親側のStar処理から0x025ac9f6を経由して呼ばれ、current stateとaction payloadを受け取る。
型metadataとhandlerのtag dispatchから、sub-actionには次のvariantがある。
| tag | variant |
|---|---|
| 0 | reduceOnly |
| 1 | approveUsers |
| 2 | proxy |
このほか、別のgenerated parserであるFUN_01d7bc30はHip3StarSubDeployerVariantとして、activate / approveUsers / proxyをtag 0/1/2にしている。Rustのgenerated typeが複数あるため、同じ数値tagを型をまたいで解釈しないことが重要だ。
approveUsersの差分計算
handlerの承認分岐は概念的に次のように動く。
for (address, requested_flag) in modifications: current = whitelist.contains(address)
if requested_flag == true and current == false: new_approvals += 1
if requested_flag == false and current == true: revocations += 1既に承認済みのaddressを再度trueにしても、追加数は増えない。存在しないaddressをfalseにしても、削除数は増えない。この差分を計算してから、上限判定と実際のBTree mutationに進む。
適用時には、falseのentryに対してFUN_028612c0、trueのentryに対してFUN_02954440が呼ばれる。
10,000件の上限
handlerには、まず入力側の件数が10,000を超えていないかを見るチェックがある。さらに、現在の承認数、新規追加数、削除数を使って、実質的に次の条件を判定する。
current_count + new_approvals <= 10000 + revocationsこれは、適用後のwhitelist件数が10,000以下であることと同値である。
たとえば、次のようになる。
| 現在の件数 | 追加 | 削除 | 判定 |
|---|---|---|---|
| 9,999 | 1 | 0 | 許可 |
| 10,000 | 1 | 1 | 許可 |
| 10,000 | 1 | 0 | 拒否 |
超過時に生成されるエラー文字列は、バイナリ中で直接確認できる。
too many approved usersこの文字列と上限計算が同じhandler内にあるため、外部記事に現れる10,000件制限は、少なくとも今回の対象バイナリの実装とも一致している。公開報告
reduceOnlyとmode
FUN_03463f50およびcompanionのFUN_034b7c60では、次のmode enumが確認できる。
| JSON value | tag |
|---|---|
disabled |
0 |
reduceOnly |
1 |
enabled |
2 |
FUN_025b01b0のreduce-only分岐では、条件成立時に次のstate writeがある。
state + 0x78 = 1state + 0x80 = 0 // BTree root pointerをclearstate + 0x90 = 0 // entry countをclear静的解析から高い確度で言えるのは、「reduce-onlyの特定分岐がmodeを1へ変更し、whitelist treeのrootとcountを初期化する」ということだ。これがプロトコル仕様上の必須動作なのか、あるstate transitionに伴う副作用なのかは、constructorと注文側の処理をさらに追う必要がある。
ただし、未承認アドレスが資産を入金でき、決済方向の操作を行えるという公開観測は、このmodeの存在と自然に整合する。allowlistは、全操作を拒否するblacklistではなく、少なくとも新規リスクを増やす操作を制限する境界として設計されていると考えられる。テストネットのsequence観測
確認できた主要関数
| Ghidra address | 役割 |
|---|---|
0x02a854c0 |
Hip3DeployAction key parser。star -> tag 0x10 |
0x02a84690 |
outer enumのcompanion parser |
0x01d7cdf0 |
Hip3StarOperation key parser |
0x01dcce70 |
operation parserのcompanion |
0x02aa6df0 |
Star operationのgenerated parser |
0x02b54330 |
modifyApprovals token/array dispatcher |
0x02bbfd90 |
address/bool pairをBTreeへ構築するparser |
0x01d61010 |
1 tuple elementのparser |
0x01dea8e0 |
20-byte address parser |
0x01deae80 |
bool parser |
0x025b01b0 |
Stars sub-actionの検証・適用handler |
0x02954440 |
whitelist BTree insert helper |
0x028612c0 |
whitelist BTree delete/rebalance helper |
0x028683d0 |
BTree iterator/drop helper |
0x03463f50 |
disabled / reduceOnly / enabled parser |
0x01d7dd50 |
Hip3StarProxyAction parser |
proxy側のvariantも確認できており、cancel、order、sendAssetがそれぞれtag 0/1/2になる。ただし、proxy payloadの全fieldとStarsのouter envelopeは、今回の記事の主題であるwhitelist更新よりも未解析部分が多い。
公開仕様と実装の距離
公式Docsは、HIP-3のdeploy actionやsub-deployer permissionの公開インターフェースを説明している。しかし、今回確認したHip3StarOperationやmodifyApprovalsは、現行の公開schemaには登場しない。
この差は、次のいずれかを意味する可能性がある。
- Starsが公開Docsの更新より先にtestnetへ導入された。
- Starsのschemaが、一般のHIP-3 actionとは別の内部・試験的surfaceとして扱われている。
- 現在のdocsと取得したnode binaryのversionに差がある。
ここで「Docsにないから非公開機能だ」と断定することはできない。確実に言えるのは、今回のbinary versionにはStars関連のparserとstate mutationが存在し、公開Docsの説明範囲を超えた実装が含まれているということだ。
まだ分かっていないこと
今回の解析でwhitelist更新の内部構造はかなり絞り込めたが、次の点は未確定である。
Hip3DeployAction::Starouter objectの正確なfield名。- 完全なsigned action JSON、nonce、署名対象bytes、送信endpoint。
approveUsersのwhitelistを注文受付・position増加側が参照する正確な関数。Hip3StarStateの全field名と各offsetの正式な型。paのouter semanticsとproxy payloadの全field。- reduce-only時にwhitelistをclearする処理が仕様上必須かどうか。
特に、modifyApprovalsのinner形式が判明したからといって、すぐに署名付きL1 actionを生成できるわけではない。outer field、deployer権限、nonce、署名domain、実行時stateの対応が未確認だからである。
次に追うべき経路
Starsの全体像を完成させるには、更新handlerの反対側、つまり注文受付側を追う必要がある。
reduceOnly、approveUsers、too many approved usersの文字列と関数xrefから、order admissionの分岐を探す。ktobの公開testnet transactionまたはreplica commandから、outer Star actionの実例を取得する。Hip3StarStateのconstructor/deserializerを追い、+0x78、+0x80、+0x90を正式なfieldへ対応付ける。- Ghidraのauto-analysis完了後、現在orphan扱いになっているjump-table blockを再解析する。
- 実行時に未承認注文が拒否される箇所を、
rrと既知のテストネットtransactionで照合する。
まとめ
今回のバイナリ解析から、Starsは次のように整理できる。
market単位のStar state ├─ trading mode: disabled / reduceOnly / enabled └─ ordered whitelist └─ 20-byte address -> approval state
modifyApprovals └─ [(address, true|false), ...] ├─ 差分を計算 ├─ 最終件数 <= 10,000 を確認 ├─ true -> BTree insert └─ false -> BTree deleteしたがって、Starsの本質は「marketの取引可否を一つのbooleanで切り替える機能」ではない。承認addressをmarket stateに保持し、更新差分、上限、mode遷移、注文側の許可判定を組み合わせるmarket-level policyである。
なお、本稿は特定の取引、レバレッジ、署名付きactionの送信を推奨するものではない。Hyperliquidの仕様、node binary、API、testnet stateは更新され得る。実際の利用や再現実験を行う場合は、対象時点の公式Docsと実行中binaryのhashを確認してほしい。