mishmar について
mishmarは、取引ボットやAIエージェント向けのリスク管理レイヤーです。CLI、署名ツール、SDK、MCPサーバーを備え、注文をエンジンの判定に照らして確認し、分割鍵で単独取引を防ぎます。現在はテストネットとデモ取引に対応しています。
主な機能
- 01注文提案と判定の送信
- 02注文の承認・転送・拒否
- 03取引鍵の二者署名
- 04エージェント署名リクエストの検証
- 05Go、Python、TypeScript SDK
- 06CLIとMCPサーバー
活用シーン
- 01リスクルールによるボット注文の確認
- 02大口注文の人手承認への振り分け
- 03分割鍵によるBinanceまたはHyperliquidリクエストの署名
MCP 対応
MCPクライアントがMishmarと連携するためのMCPサーバーを含みます。READMEでは、`mishmar mcp` がGoクライアントとその再試行機能を使うことも説明されています。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mishmar
Mishmar は、トレーディングボットやAIエージェント向けのリスク管理レイヤーです。注文は取引所に届く前に毎回お客様の上限と照合され、大口注文は承認されるまで保留され、どのキーも単独では取引できません。これはクライアント側の構成要素です。APIコントラクト、すべてのSDKが合格しなければならないテストベクトル、エージェントの署名機能、mishmar CLIとMCPサーバー、そしてSDK自体が含まれます。ルールを適用するエンジンは別の非公開リポジトリにあり、このリポジトリにはエージェントと一緒に動かすものが含まれています。
ステータス:テストネットです。現在、MishmarはHyperliquidのテストネットとBinanceのデモ取引で動作していますが、実資金での運用はまだ開始されていません。コントラクト、Goパッケージ、エージェントの署名機能、mishmar CLIに加えて、PythonおよびTypeScript SDK(PyPIとnpmのmishmar)も含まれています。LinuxとmacOS向けのバイナリはリリースページにあります。
構成
spec/openapi.yaml the API contract, the single source of truth for every client and the engine
spec/vectors/ cross-language test vectors: proposal signing, agent request signing (agentAuth)
api/ the contract as Go types (generated; `make gen`)
canon/ canonical JSON: the bytes an agent signs
agentkey/ the agent's Ed25519 identity key: parse the secret, sign, verify, sign requests
proposal/ build, validate and sign a proposal for POST /v1/proposals
frost/ two-party FROST over Ed25519: the split key behind a Binance tradingAccount
ecdsa2p/ two-party ECDSA over secp256k1: the split key behind a Hyperliquid API wallet (keygen, presigning, signing)
signer/ the agent's signer: config, engine client, the checks, the signing rounds (FROST and two-party ECDSA), the poll loop
cmd/mishmar/ the CLI: keygen, add-agent, signer init|add-key|keygen|run, propose, mcp
mcp/ the MCP server behind `mishmar mcp`: the signer's tools for a language model, over stdio
python/ the Python SDK (`mishmar` on PyPI)
typescript/ the TypeScript SDK (`mishmar` on npm); no runtime dependencies
examples/ bots to copy from: two minimal ones (Python, TypeScript) and two rule walk-throughs (Hyperliquid testnet, Binance demo trading)
チェックの実行
2者間ECDSA(ecdsa2p、Hyperliquid APIウォレット)には、cgo経由のCoinbase製cb-mpc(C++)を使用します。mishmar-ioのcb-mpc-goフォークを、このリポジトリの隣にcbmpc.refで指定されたリビジョンでチェックアウトします(go.modでモジュールを../cb-mpc-goに置き換えています)。そのscripts/build_cpp.shがライブラリを一度ビルドし(CMakeと、scripts/opensslにある静的リンク用OpenSSL 3.6.1を使用)、makeはすべてのGoコマンドをcgoフラグを設定するscripts/go_with_cpp.sh経由で実行します。LinuxとmacOSに対応しています。WindowsではWSLまたはDockerを使用してください。
make test # Go packages, including the vectors and both signers
make lint
cd python && python -m pytest # the Python SDK against the same vectors
cd python && python -m pytest # the Python SDK against the same vectors
cd typescript && npm test # the TypeScript SDK against the same vectors (Node 22+)
go test ./proposal -update # rewrite spec/vectors after changing the cases
make gen # regenerate api/ and signerapi/ from spec/
vX.Y.Zタグを付けると、LinuxとmacOS向けの両アーキテクチャのバイナリがビルドされ、リリースに添付されます(.github/workflows/release.yml)。Dockerfileは同じバイナリをLinux向けにビルドし、署名サービスをデフォルトで実行します。
エージェントとエンジンの通信方法
- エンジンはエージェントにキーを発行し、シークレットを一度だけ表示します(
msk_…)。公開鍵はエンジンが保持します。 - エージェントは提案(銘柄、売買方向、数量、価格または成行、期限、nonce)を作成し、キーを使ってその正規化されたJSONに署名して、
/v1/proposalsにPOSTします。 - エンジンは署名を検証し、適用範囲内のすべてのルールを評価して判断を記録し、注文を転送するか、人の承認のために保留するか、ブロックします。レスポンスはその判断です。保留された注文は後で判断が確定し、エージェントが同じキーで署名するリクエストを使って
/v1/agent/decisions/{id}から取得します(agentAuthヘッダー)。 - 取引口座のキーが分割されている場合、エンジンは署名チャネル(
/v1/agent/signer/…)経由で、エージェントの署名機能に取引所リクエストへの共同署名を依頼します。署名機能は、提案された注文と一致することを確認してから署名します。
署名対象となる正確なバイト列はproposalとagentkeyで定義され、spec/vectors/に固定されています。
署名者
取引口座の取引所キーが分割されている場合、エンジンが一方のシェアを保持し、エージェントの署名者がもう一方を保持します。取引所へのリクエストは、両者が署名した場合にのみ署名されます。署名者は、ライブラリとしての signer パッケージであり、プロセスとしての mishmar signer run です。エージェントのキーを使って POST /v1/agent/signer/poll をロングポーリングし、各リクエストについて、次の順に確認し、条件を満たさない場合は拒否します。
- スキームが
frost-ed25519(Binance)またはecdsa-2p(Hyperliquid)であること。RSA とプロキシにはまだ対応していません。 - 取引所キーが設定に登録されており、リクエストの取引口座および取引所に属していること。
- リクエストの期限が切れておらず、このセッションですでに応答済みでないこと(応答後に再び渡されたリクエストは、2回目の署名ではなくリプレイです)。
- 目的が
order、cancel、verifyのいずれかであること。 orderの場合、エージェント自身の提案を含むこと。この署名者のキーで署名され、そのキーで検証でき、この取引口座用で、まだ期限切れになっていない提案であること。- メッセージをデコードした結果が、その注文だけであり、それ以外の内容を含まないこと。Binance では、メッセージは取引所が検証するパラメーター文字列です。エンジンが送信するパラメーターだけを含めることができます。
newClientOrderIdはリクエストの値であること。symbolはリクエストの値で、提案の値から始まっていること。sideは提案の値であること。指値提案ではtypeがGTCを伴うLIMITであること。成行提案では、MARKET(決済、または旧バージョンのエンジンの場合)か、エンジンが設定した上限価格でのIOCを伴うLIMITであること。quantityは0 < quantity <= sizeを満たす小数であること(エンジンはロット刻みに合わせて切り下げ、sizeを超えません)。指値のpriceは提案価格の1%以内で、かつ受動側であること(買いでは提案価格以下、売りでは提案価格以上)。MARKET注文にはpriceを含めないこと。IOCの上限価格は正の小数であり、署名者はその値を判定しません(エンジンは取引所の注文板から読み取ります。Hyperliquidでも同様です)。reduceOnlyは提案の値であること。verifyメッセージをデコードすると、注文ではなく読み取りであること。symbol、side、type、quantity、price、クライアント注文 ID を含めてはなりません。Hyperliquid では、メッセージは読み取りできない EIP-712 ダイジェストです。そのため、リクエストの本文には元となったアクションと nonce が含まれます。署名者はvenues/hyperliquidでそれらをデコードし、キーのネットワークに対するダイジェストを再計算して照合し、nonce が新しいことを確認します。また、アクションが提案された注文(asset、side、size、tick ルールに従い受動側で提案価格の1%以内にある指値、reduce-only、リクエストのクライアント注文 ID の cloid)または対象注文のキャンセルであることを確認します。 - 設定でフックが指定されている場合、フックが 0 で終了すること(リクエストの JSON が標準入力で渡され、実行時間は2秒です。出力の最初の行が拒否理由です)。
次に、署名者は自分の半分を作成します。FROST の場合は、エンジンのコミットメントを解析してコミットし、確認した正確なバイト列に対する署名シェアを作成して、{"kind":"share","commitment":…,"share":…} と応答します。2者 ECDSA の場合は、事前署名済みスロットを1つ消費します(事前にエンジンの署名者とプロトコルの最初の3つのメッセージを実行した nonce のバッチを、POST /v1/agent/signer/presign で用意しておきます)。ダイジェストに対する最後のメッセージを計算し、{"kind":"share","share":…,"engineCommitmentId":<the slot>} と応答します。エンジンの署名者はそのメッセージを検証し、単独で署名を完了します。
どちらの取引所でも、署名者が自ら作成できる注文は、代わりに1回の処理で完了します。取引所へのリクエストと署名者の半分は提案内(venueMessage)で送られるため、許可された注文はリクエストへの往復を必要としません。これに該当するのは指値注文、キャンセル、および Binance の決済(reduce-only の成行注文)です。それ以外の成行注文は、エンジンが取引所の注文板から上限価格を設定し、チャネル経由で署名します。拒否応答は {"kind":"refusal","refusal":{"reason":…}} で、最初に安定したコードを示します(message_mismatch: side "SELL" is not the proposed BUY)。エンジンはそれを判断の signing に記録します。小数は厳密に比較され、浮動小数点数は一切使われません。
設定ファイル
~/.mishmar/agent.json、または MISHMAR_AGENT_CONFIG が指定するファイルに、モード 0600 で書き込みます。
{
"engine": "https://api.testnet.mishmar.io/a/acc_k7f2q9x1m3n4/v1",
"keyId": "key_k7f2q9x1m3n4_abcdefghijkl",
"secret": "msk_testnet_k7f2q9x1m3n4_abcdefghijkl_…",
"agentId": "agt_…",
"venueKeys": {
"vk_…": {
"tradingAccountId": "ta_…",
"venue": "binance",
"scheme": "frost-ed25519",
"share": {"scheme": "frost-ed25519", "id": 1, "keyShare": "…", "group": "…"}
}
},
"hook": "/usr/local/bin/check-order"
}
share は、キー生成時のままのエージェントのシェア(または mishmar keygen が書き込んだもの)です。このファイルに保存されるのは、ID シークレットとエージェントのシェアのみです。エンジンのシェアが含まれている場合、読み込み時に拒否されます。
シークレットはエージェントの API キーであり、コントラクト 0.19.0 以降は、エンジンとキー ID も指定します:msk_<environment>_<account suffix>_<key random part>_<seed>。
testnet、stage、prod は Mishmar のホスト環境で、API ホスト(signer.HostedAPI)に対応します。それ以外の環境はセルフホストのエンジンで、その URL は MISHMAR_ENGINE で指定します。0.19.0 より前に発行されたキー(msk_<6 characters>_<seed>)も引き続き使えますが、エンジン URL とキー ID は手動で指定する必要があります(mishmar signer init)。
コマンド
go build ./cmd/mishmar
# The usual way: the agent's API key from the console (the agent's page) is
# all the signer needs. With no config at the path yet, "signer run" writes
# one from the key (the engine and key id are in the key; the agent comes
# from GET /agent/me) and starts. While it runs it keeps its trading keys in
# line with the engine's: a key the console adds for the agent is generated
# here, two rounds with the engine's signer, and the log says what the venue
# needs next (the API wallet to approve at Hyperliquid, under the name
# mishmar-<last 8 of the agent id>; the public key to register at Binance).
# No restart, no second command. MISHMAR_ENGINE names a self-hosted engine.
export MISHMAR_API_KEY=msk_testnet_…
mishmar signer run
# Everything for a new agent in one go, with an operator token: create the
# agent, issue its key, make a venue key per trading account by two-party
# key generation with the engine's signer (FROST for Binance, ECDSA for
# Hyperliquid; no machine ever holds the whole key), write the config. It
# prints what to register or approve at the venue. The agent joins the
# account's default portfolio unless --portfolio pf_… names another.
mishmar add-agent --engine http://localhost:8080/v1 --token "$MISHMAR_OPERATOR_TOKEN" \
--name momentum-1 --trading-account ta_…
# Or step by step: write the config first (from MISHMAR_API_KEY, or the key
# piped in; a key from before 0.19.0 also takes --engine, --key-id and
# --agent), then make each trading key yourself instead of leaving it to the
# running signer. Binance prints the public key to register at the exchange,
# Hyperliquid the API wallet address to approve.
echo "$MSK_SECRET" | mishmar signer init --secret-stdin
mishmar signer keygen --venue-key vk_… --trading-account ta_… --venue binance
mishmar signer keygen --venue-key vk_… --trading-account ta_… --venue hyperliquid --network testnet
# The older way for Binance, which makes the whole key exist on this machine
# for the length of the call: deal a FROST key, paste the engine share when
# adding the venue key in the console, store the agent share under the id it
# returns.
mishmar keygen --scheme ed25519 --agent-share-out agent.share > engine.share
mishmar signer add-key --venue-key vk_… --trading-account ta_… --venue binance --share agent.share
# Run the signer; then register the key at the venue and verify it in the
# console (the verification is a read the signer co-signs). One signer runs
# per config: it holds <config>.lock while it runs, and a second one on the
# same config stops at once, naming the first one's pid. A key "signer
# keygen" or "signer add-key" writes while a signer runs is picked up by it
# within a few seconds.
mishmar signer run
# Drive one order end to end with the signer running.
mishmar propose --config ~/.mishmar/agent.json --trading-account ta_… --symbol BTCUSDT --size 0.01 --price 60000
再起動とレート制限
エンジンは、再起動中は 503 unavailable を返します(最大 28 秒間、リクエストの受け付けを停止するため、すでに処理中のオーダーは完了します)。呼び出し元がリクエストを送りすぎると、Retry-After を付けて 429 rate_limited を返します。どちらの場合もリクエストは処理されていないため、ここで使うすべてのクライアントは自動的に再送します。対象は Go クライアント(署名者のポーリングループ、mishmar propose、mishmar mcp が使用する signer.Client)、Python の Client、TypeScript の Client です。Retry-After がある場合は指定された秒数だけ待ち、ない場合はジッターを加えながら 0.25 秒から 8 秒まで間隔を倍増させます。初回の試行から最大 45 秒間再試行し、その後は最後の応答を返します。署名付きの /agent リクエストは試行ごとに新たに署名されるため、再試行がリプレイになることはありません。提案はバイト単位で変更せずに再送されます。それ以外は再試行しません。413 too_large は再送しても同じ応答が返るためです。また、その他の応答はリクエストが受け付けられたことを意味します。再試行を無効にするか、Client.Retries(Go)、Client(..., retries=None)(Python)、new Client(..., { retries: false })(TypeScript)で設定を調整できます。
SDK から、またはモデルから
Python と TypeScript の SDK は署名ツールを子プロセスとして実行できるため、ボットは秘密情報を一切保持しません。Signer が設定を使って mishmar signer run --local … を起動し、プライベートな Unix ソケット(Windows ではトークン付きの localhost)経由で通信します。注文は意図であり、子プロセスがそれをエージェントのキーで署名した提案に変換し、可能な場合はワンショットで、そうでない場合はチャネル経由で送信し、判断結果を返します。読み取りも同様に子プロセス経由で行います。バイナリは PATH、MISHMAR_BIN、または ~/.mishmar/bin から見つけるか、リリースから download_binary / downloadBinary で取得し、リリースのチェックサムと照合して検証します。
from mishmar import Signer
with Signer() as s: # ~/.mishmar/agent.json
d = s.order("ta_…", "BTC", "buy", "0.001", price="79500")
if d["verdict"] == "require_approval":
d = s.wait(d["id"])
import { Signer } from "mishmar";
const s = await new Signer().start();
const d = await s.order({ tradingAccountId: "ta_…", symbol: "BTC", side: "buy", size: "0.001", price: "79500" });
await s.stop();
子プロセスはボットとともに起動し、終了します。SDK は --exit-on-stdin-close と、書き込みを一切行わない stdin パイプを使って起動します。ボットのプロセスがどのような形で終了しても(クラッシュや SIGKILL の場合も含めて)、その終了時にパイプが閉じ、署名ツールは SIGTERM を受けた場合と同様に停止します。そのため、ボットが終了したエージェントの署名ツールがオンライン状態のまま残ることはありません。
署名ツールを単独で実行し、systemd、launchd、または Docker で稼働させることもできます。mishmar signer run --local unix:/run/mishmar/signer.sock(または MISHMAR_LOCAL_TOKEN にトークンを設定したループバックポート)で起動し、ボットは署名ツールを起動する代わりに接続します。Python では Signer.connect("unix:/run/mishmar/signer.sock")、TypeScript では await Signer.connect("unix:/run/mishmar/signer.sock", { token }) を使います。connect は何も起動せず、stop を呼び出しても署名ツールは実行し続けます。複数のプロセスが1つのエージェントとして取引する場合(設定ごとに署名ツールは1つです。同じ設定で2つ目の Signer() を作成すると、その設定を保持しているプロセス名を示してエラーになります)、またはボットの再起動中も署名ツールの接続を維持し、事前署名済みのスロットを準備しておく場合に、この方法を使います。
mishmar mcp は、stdio 経由の MCP サーバーの背後で同じ署名ツールを実行します。言語モデルは、注文やキャンセルを提案する、人による対応待ちの判断を追跡する、ポジション、PnL、承認、取引口座、およびその取引所のルールを読み取るためのツールを利用できます。モデルがキーを参照することはなく、提案するすべての注文は、取引所に届く前にエンジンのルールを通過します。設定がデフォルトパスにない場合は --config を指定して、MCP クライアントからコマンドを指定してください。
{ "mcpServers": { "mishmar": { "command": "mishmar", "args": ["mcp"] } } }
Claude Code で /plugin install mishmar --marketplace mishmar-io/mishmar を実行すると、SDK を使ったボットの作成方法を記述したスキル(skills/mishmar/SKILL.md。kit/ には同じガイダンスを AGENTS.md のスニペットおよび Cursor ルールとして収録しています)とともにサーバーが追加されます。
npm と PyPI のパッケージは同じコマンドを PATH に追加するため、クライアント側でインストールする必要はありません。npx -y mishmar mcp(Node 20 以降)または uvx mishmar mcp を実行すると、MISHMAR_BIN、PATH、または ~/.mishmar/bin からバイナリを見つけます。見つからない場合は、まず固定されたリリースを ~/.mishmar/bin に取得し、リリースのチェックサムと照合して検証します。MCP レジストリには、サーバーが io.github.mishmar-io/mishmar(server.json)として登録されています。
エンジンの署名サービス
mishmar signer serve は、エンジンの役割を担う同じプログラムです
(spec/signer-service.yaml、signer/service パッケージ)。Mishmar 側で、
すべての二者間 ECDSA キーのエンジン側シェア、Paillier キー、事前署名済み
nonce を保持する唯一のプロセスです。エンジンは署名秘密情報を保持しません。
エージェントの署名サービスとこのサービスの間で鍵生成と事前署名を中継し、
各注文につき 1 つの署名を完成させるように依頼します。このサービスは、
自身でデコードした注文またはキャンセルであり、エンジンが自身のキーで署名した
判断(--engine-key。公開キーは GET /v1/ledger/head に表示されます)と
一致するものだけに署名し、アクションから取引所のダイジェストを再構築します。
接続元はエンジンだけにしてください。同じマシン上で動作する場合は Unix ソケット、
そうでなければ mTLS を使用します。プレーン HTTP は localhost 用です。
シェアは、MISHMAR_SIGNER_KEY のマスターキーを使って保存時に暗号化されます
(AES-256-GCM。キー ID は追加認証データとして結び付けられます)。マスターキーを
ストアの隣に置いてはいけません。Google Cloud では、代わりに KMS キーで
ラップできます。MISHMAR_KMS_KEY でキーを指定し、
MISHMAR_SIGNER_KEY_WRAPPED にラップ済みの値を格納します
(mishmar signer wrap-key で、元のキーから、または新たに作成できます)。
起動時にワークロードの ID を使ってアンラップされ、そのたびに KMS 監査ログへ
記録されます。KMS でアンラップできない場合、サービスは起動しません。
フォールバックはなく、隣に置かれた生のキーも拒否されます。--store は
暗号化されたシェアの保存先です。デフォルトはファイル(モード 0600、アトミックに置換)
ですが、オブジェクトストレージも指定できます。AWS S3 または S3 互換ストアでは
s3://bucket/prefix を使います(MISHMAR_S3_ENDPOINT、MISHMAR_S3_REGION、
MISHMAR_S3_ACCESS_KEY、MISHMAR_S3_SECRET_KEY、または AWS SDK 独自の
環境変数やロールを使用します)。Google Cloud Storage では
gs://bucket/prefix を使い、ワークロード自身の ID を使用するため、キーは不要です。
その ID にプレフィックスへのアクセス権を付与します。KMS と同じく、トークンは
メタデータサーバーから取得します。各キーは 1 つのオブジェクトとして、
条件付きで作成されるため、2 つのレプリカが同じキーを作成することはありません。
履歴を残すには、バケットのバージョニングを有効にしてください。すべてのシェアは
起動時にメモリへ読み込まれるため、注文がストアを待つことはありません。また、
誤ったマスターキーは最初の注文時ではなく、その時点でエラーになります。
--sync を指定すると、バックエンドを共有するレプリカ間で状態を同期します。
まだメモリにないキーは初回使用時にバックエンドから取得されるため、
アカウントを引き継いだセルは、そのアカウントのシェアをすぐに見つけられます。
スロットとセッションはメモリ上にあり、再起動すると失われます。実行中の署名サービスを
スナップショット、複製、ライブマイグレーションしてはいけません。事前署名済み nonce の
同じコピーが 2 つ存在すると、異なる 2 つのメッセージに署名してキーが漏えいします。
ストアとマスターキーは別々にバックアップしてください。どちらかを失うと、
すべてのエージェントがキーを再生成し、すべての所有者が再度承認する必要があります。
それ以上の影響はありません。エージェント側の半分だけでは役に立たず、これらのキーに
資金は保管されないためです。GET /metrics(Prometheus テキスト形式)は、
専用のプレーン HTTP リスナー MISHMAR_SIGNER_METRICS_LISTEN(デフォルトは :9103、
空の場合はなし)で利用できます。公開されるのは mishmar_signer_ready、
mishmar_signer_pending_requests、mishmar_signer_oldest_pending_seconds、
mishmar_signer_build_info で、キーは含まれません。これらはホステッド層のセルの
アラートが監視するメトリクスです。
export MISHMAR_SIGNER_KEY=$(openssl rand -hex 32) # keep it: it seals the shares
mishmar signer serve --listen unix:/var/run/mishmar/signer.sock --store /var/lib/mishmar/signer-keys.json --engine-key <base64>
# and on the engine: MISHMAR_SIGNER_URL=unix:/var/run/mishmar/signer.sock
監査状況
二者間 ECDSA は、独立したセキュリティレビューを受けている Coinbase の cb-mpc を使用しています。注文に必要な ラウンドトリップを 1 回にするため、署名メッセージ 4 つのうち最初の 3 つを 事前に実行するように加えた変更は、まだ監査を受けていません。 docs/ecdsa-presign-review.md はレビュー担当者向けの 説明であり、脆弱性の報告方法は SECURITY.md に記載されています。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。