ZOOBC / 開発者
開発者
プログラムがこのチェーンを読み書きするのに必要なすべてをまとめています。ゲートウェイはHTTPSの玄関口です。TLSを終端し、レート制限を行い、チェーンAPIをノードへプロキシするので、ブラウザはノードを動かさなくてもZooBCと通信できます。
どのチェーンを対象に開発しますか?
以下の例は公開TestNetゲートウェイに対して実行されます。アドレス、残高、トランザクションハッシュはネットワーク間で引き継がれないため、操作の前にどのネットワークを使っているかを確認してください。
MainNetは2027年2月14日に開始
ベースURLは1つ
チェーンに関するすべては、このゲートウェイの/api/v1/の下にあります。このページと同一オリジンなので、CORSの調整も、キーの取得も、料金プランも不要です。
zoobc.networkは公開されているZooBC TestNetのゲートウェイであり、MainNetではありません。以下の例はテストネットワークに対して実行されます。
curl https://this-gateway/api/v1/blockchain/status
読み取りと書き込みは行き先が異なり、これは見た目以上に重要です:
| GET | アーカイブノードにプロキシされます。アーカイブノードは、任意の高さ時点でのブロック、トランザクション、アカウントといった深い履歴を保持しています。 |
|---|---|
| POST | アーカイブノードではなく、常にフルノードへ直接プロキシされます。トランザクションの送信にはメンプールが必要ですが、アーカイブノードは読み取り専用の履歴サービスで、以前はPOSTに404を返していました。かつてゲートウェイ経由のブロードキャストが機能しなかったのはそのためです。 |
最初に使うことになるルート
| /api/v1/blockchain/status | 高さ、確認済みの高さ、最新ブロック |
| /api/v1/blocks/latest | 最新ブロックの全データ |
| /api/v1/accounts/<address> | 残高と使用可能残高。ZBC_…形式のアドレス、または36バイトの生の16進数を受け付けます |
| /api/v1/accounts/<address>/tokens | そのアカウントが保有するすべてのトークン |
| /api/v1/transactions?limit=25 | 最近の署名済みトランザクション |
| /api/v1/movements/latest | チェーン自体が行う残高の変動(報酬、ベスティング、返金) |
| /api/v1/exchange/markets | オープンなマーケット。板の厚みは…/orderbook?market=<id>、スワップパッケージは…/offers |
| /api/v1/tokens | すべてのトークン(供給量と小数桁数付き) |
| /api/v1/release/list | 公開済みのリリースとそのハッシュ。/verifyが照合に使うもの |
| /api/v1/registry/nodes | ノードレジストリ。ほかに次のルートもあります:
/gateways, /relays, /archivals |
一部のルートはアーカイブノードにしかなく、一部はフルノードにしかありません。このゲートウェイはまずアーカイブノードを試し、ボディが空の404が返ればノードAPIで再試行するため、どちらなのかを気にする必要はほとんどありません。ただし、ボディ付きの404は正当な回答であり、行き先の誤りではありません。
トランザクションの送信
zbc-cliで構築して署名します。パラメーターは標準入力からJSONで受け取るため、秘密鍵がpsやシェル履歴に残ることはありません:
echo '{"sender_privkey":"…","recipient":"ZBC_…","amount":100000000}' \
| zbc-cli send --json-input --api https://this-gateway
1 ZBCは108アトミック単位です。APIの金額はすべてアトミック単位で、ワイヤーフォーマットのどこにも小数はありません。
この基盤の上に築かれているもの
ここで呼び出すのと同じトランザクションタイプが、進行中の取り組みの土台になっています:
| AIエージェント | スケジュール送金、エスクロー、マルチシグを、自律エージェントが自分では引き上げられない支出上限として利用します。公開TestNetで動作する依存関係のないPythonリファレンスとともに、現在稼働中です。 |
|---|---|
| 分散型AI | 独立して運用されるモデルスペシャリストを、ユーザー側で統合し、オープンに決済します。これにより、人とその人が使う知性との間に特定の1社が入り込むことはありません。MainNet後の方向性です。 |
| ProxCell | 対面決済のためのローカルな楽観的承認と、その上に重なる地理的な決済レイヤー。ワーキングペーパー段階です。 |
ダウンロード
運用者がこのゲートウェイで公開したファイルは/dl/<name>で配信されます。名前は[A-Za-z0-9._-]からなる単一のセグメントで、すべて添付ファイルとして送信され、ページとして表示されることはありません。このオリジンはウォレットも提供しており、ここで表示されたページはウォレットのオリジンを引き継いでしまうためです。
リリースバンドルは/releases/<version>/<arch>/の下に置かれ、/releases/latestが現在のバンドルを指します。ノードインストーラーの場所:
/install.
このゲートウェイは正常ですか?確認中…
/gateway/status は、このゲートウェイが自身と到達可能なノードについて把握している情報を報告します:モード、ドメイン、許可リストにあるノード数、応答しているノード。
/gateway/system-stats はマシンの状態を報告します:CPU、メモリ、ディスク、稼働時間。ゲートウェイのページに表示される数値は、ここから取得しています。
curl https://this-gateway/gateway/status
curl https://this-gateway/gateway/system-stats
どちらもキーは不要です。どちらもチェーンについては何も公開しません。チェーンの情報には上記の/api/v1/…を使ってください。
特定のノードへのアクセス
チェーンのルートは、このゲートウェイが選んだいずれかのアーカイブノードから応答します。特定のノードに問い合わせるには、そのIPをホスト名に入れ(ドットはダッシュに置き換えます)、必要に応じて-p<port>でAPIポートを指定します:
curl https://ip-192-168-1-100.this-gateway/api/v1/node/info
curl https://ip-192-168-1-100-p8080.this-gateway/api/v1/node/info
応答するのは、このゲートウェイの許可リストにあるノードだけです。それ以外はプロキシされずに拒否されるため、ホスト名を使ってゲートウェイ経由で任意のホストにアクセスすることはできません。
リレー
音声、ビデオ、画面共有はピアツーピアで行われます。ファイアウォールが直接接続を妨げる場合は、リレーがクレジットと引き換えに通信を中継します。/relay/infoはリレーのアドレス、料金、決済先チェーンのジェネシスハッシュを報告します。ウォレットは支払いの前にそれを自身のノードと照合します。別のチェーン上のリレーは、受け取った支払いをクレジットとして反映できないからです。
curl https://this-gateway/relay/info
管理パネル
ゲートウェイ自体の管理機能はzoobc.network/adminにあります。/etc/zoobc/gateway.jsonにあるadmin_api_keyで保護されており、このキーはインストーラーがサーバーごとに生成します。デフォルトのキーはありません。
許可リスト
ゲートウェイは、知らされたノードにのみプロキシします。このリストがあるからこそ、ip-…形式のホスト名によってゲートウェイがオープンプロキシになることを防げます。
| POST /gateway/allowlist/add | ノードを追加: {"ip":"…","port":8080} |
| POST /gateway/allowlist/remove | ノードを1つ削除 |
| POST /gateway/allowlist/refresh | レジストリを再読み込みし、ヘルスを再確認 |
| GET /gateway/allowlist | 現在許可されているノード |
3つの書き込み操作には、いずれも管理キーが必要です。パブリックモードでは、ゲートウェイはチェーン自体のレジストリからもノードを検出します。プライベートモードでは次のものだけを使います: static_nodes.
2つのドキュメント
ハードウェアウォレットのチームに必要なことはすべてここに記載しており、詳細は下の2つのPDFにまとめています。どちらもZooBCノードv0.4.0(7ad161a4時点のzoobc-main)と、稼働中のv0.4.0ノードが受理したトランザクションベクターに対して検証済みです。
バージョン2.1は、1つの重要な点でドラフト1に取って代わります:トランザクション署名がチェーンにバインドされるようになりました。ドラフト1に基づいて実装した方にとって、署名ダイジェストとApprovalEscrowのボディという2つの変更は互換性を壊すものです。旧v0.3.2の構成を実装しているものはないため、現行のドキュメントのみに基づいて実装してください。
1ページの要約
| 署名方式 | Ed25519(RFC 8032、pure、プリハッシュ版なし、コンテキスト文字列なし)。64バイトの署名、32バイトの公開鍵。 |
|---|---|
| 署名対象 | Ed25519.sign(sk, SHA3-256("ZBC-TX" ‖ genesis_hash(32) ‖ unsigned_bytes))。32バイトのダイジェストそのものがEd25519のメッセージです。 |
| チェーンバインディング | ダイジェストはチェーンのジェネシスブロックハッシュを含むため、あるチェーン向けの署名を別のチェーンで再利用(リプレイ)することはできません。ワイヤーフォーマットは変わらず、ジェネシスハッシュはトランザクションのフィールドではありません。 |
| 署名タグ | "ZBC-TX"、6バイトのASCII 5a 42 43 2d 54 58。終端文字なし、長さプレフィックスなし。 |
| ハッシュ関数 | SHA3-256(FIPS 202)。Keccak-256でもSHA-256でもありません。 |
| 鍵導出 | BIP-39ニーモニックからシード(PBKDF2-HMAC-SHA512、2048ラウンド)を得て、次にSLIP-0010 Ed25519で導出します。パスはm/44'/883'/account'。ハードン化された3階層で、change階層とindex階層はありません。 |
| SLIP-44コインタイプ | 883 (登録済み: 883 | ZBC | ZooBC). |
| アカウント(ワイヤー上) | 36バイト:u32le(0) ‖ pubkey(32)。JSON APIが受け付けるのは16進数形式です。 |
| アドレス(表示用) | ZBC_ に続けてbase32の56文字(8文字×7グループ)。3バイトのチェックサム = SHA3-256(pubkey ‖ "ZBC")[0..3]. |
| バージョンバイト | 0x01、変更なし。チェーンバインディングのために値を上げてはおらず、バージョンの識別子でもありません。 |
| ネイティブ単位 | 1 ZBC = 100 000 000アトミック(小数8桁)。金額と手数料はu64のリトルエンディアンのアトミック単位です。 |
| バイト順 | ワイヤー上のすべてはリトルエンディアンです。ただし、SLIP-0010の子インデックスは例外です(規格に従いビッグエンディアン)。 |
| トランザクションハッシュ | tx_hash = SHA3-256(unsigned_bytes ‖ signature); tx_id = int64le(tx_hash[0..8])。ジェネシスハッシュが含まれるのは署名ダイジェストだけです。 |
| リプレイ保護 | チェーンバインディング、ハッシュの一意性、そして3600秒の取り込みウィンドウ。ナンスはありません。 |
| 最低手数料 | 基本0.025 ZBC(2 500 000アトミック)にネットワーク手数料スケールを掛け、サイズに応じた賃料を加えた額。ノードに問い合わせてください: GET /api/v1/blockchain/estimate-fee. |
| ティア1のタイプ | 1 SendZBC, 11 TransferToken, 4 ApprovalEscrow. |
| チェーンの検出 | GET /api/v1/node/info はgenesis_hash、signing_version: 2、signing_tag: "ZBC-TX"を返します。ブロードキャスト先のエンドポイントから読み取ってください。 |
まずこの4点を
ほかの何よりも実装を左右する4つのポイント。
| 1 | ナンスはなく、未署名トランザクションを返すエンドポイントもありません。ZooBCはアカウントベースですが、ここではアカウントベースだからといって、そのどちらかがあるわけではありません。ホストがトランザクションを自らシリアライズします。以下のレイアウトは完全で、テストベクターで固定されています。 |
|---|---|
| 2 | 署名はタグ付きのプレフィックスを含みます。対象はトランザクションのバイトだけでなく、"ZBC-TX"とチェーンのジェネシスハッシュも含みます。ここを誤ると、すべての署名が汎用的なエラーで失敗します。 |
| 3 | ブロードキャスト用エンドポイントは、署名済みトランザクションのblobを受け付けません。受け付けるのはトランザクションのボディのバイトと、名前付きJSONキーとしてのエンベロープで、エンベロープはノード側で再構築されます。 |
| 4 | 履歴は新しい順に返され、limitで件数が制限されます。2つのエンドポイントを合わせると全体像が得られます。また、金額はエンベロープのフィールドではなく、タイプによって決まります。 |
鍵導出とアドレス
リファレンス:zoobc-signer/extension/lib/zoobc-crypto.js(リファレンスウォレットとバイト単位で同一)。ノード側の同等の実装: src/crypto/slip10.cpp.
seed = PBKDF2-HMAC-SHA512(NFKD(mnemonic), "mnemonic" + passphrase, 2048, 64)
I = HMAC-SHA512("ed25519 seed", seed); k = I[0..32], c = I[32..64]
for index in [44, 883, account]: # all hardened
data = 0x00 || k || u32be(index + 0x80000000)
I = HMAC-SHA512(c, data); k = I[0..32], c = I[32..64]
pubkey = Ed25519.publicKeyFromSeed(k) # k is the 32-byte seed
アカウント0はm/44'/883'/0'、アカウント1はm/44'/883'/1'、以下同様です。鍵導出はチェーンバインディングの影響を受けません。1組の鍵ですべてのZooBCチェーンに対応でき、異なるのは署名だけです。
表示用アドレスは、36バイトのアカウントではなく公開鍵から生成されます:
buf[35] = pubkey(32) || "ZBC"
h = SHA3-256(buf); buf[32..35] = h[0..3] # 3-byte checksum
s = base32(buf) # RFC 4648 A-Z2-7, no padding, 56 chars
address = "ZBC_" + s[0..8] + "_" + s[8..16] + ... # 7 groups, canonical length 66
デコード時は、アンダースコア、ダッシュ、スペース、区切りなしのいずれも、大文字・小文字を問わず受け付け、チェックサムを再計算して照合する必要があります。上記の3バイトのSHA3チェックサムは、コードとチェーンが用いる定義です。これと、計算例のアドレスを再現する仕様書内のリファレンス実装に基づいて実装してください。
未署名トランザクションのワイヤーフォーマット
信頼できる情報源:src/util/transaction_util.cpp:144-270。ワイヤーフォーマットはv0.4.0で変更されていません。以下の長さやオフセットは、チェーンバインディングの影響を受けません。
| # | サイズ | フィールド | 備考 |
|---|---|---|---|
| 1 | 4 | transaction_type | u32le。下記のタイプ一覧を参照 |
| 2 | 1 | バージョン | 常に0x01。それ以外はノードが拒否します |
| 3 | 8 | タイムスタンプ | u64le、Unix秒、0より大きい値 |
| 4 | 36 | 送信者 | 00000000 ‖ pubkey(32)(ZBC署名者の場合) |
| 5 | 4または4+n | 受取人 | ZBCでは36バイト。それ以外はu32le(type) ‖ payload。受取人がない場合は02 00 00 00のみ |
| 6 | 8 | 手数料 | u64le、アトミック単位 |
| 7 | 4 | body_length | u32le |
| 8 | n | ボディ | タイプごとのレイアウト |
| 9 | 4または可変 | エスクロー | エスクローなし:4バイトの02 00 00 00。エスクローあり:approver(36) ‖ commission u64le ‖ timeout u64le ‖ instr_len u32le ‖ instruction ‖ multi_party(1) |
| 10 | 4 | message_length | u32le |
| 11 | m | メッセージ | プレーンテキスト。通常のトランザクションでは256バイト以下 |
エスクローブロック末尾のmulti_partyバイトは必須です。これを省略したり、エスクローなしのマーカーを別の値で書いたりすると、ノードは「Invalid transaction signature」とだけ返し、それ以上の診断情報はありません。
計算例(チェーンで検証済み)
自分宛てに1 ZBCを送るSendZBC。手数料0.05 ZBC、タイムスタンプ1700000000、エスクローなし、メッセージなし。仕様書にある7つのベクターのうちのベクター0です。
01000000 type = 1 (SendZBC)
01 version
00f1536500000000 timestamp 1700000000
00000000 5e8eb28d...2152 sender (type 0 + pubkey)
00000000 5e8eb28d...2152 recipient (type 0 + pubkey)
404b4c0000000000 fee 5 000 000
08000000 body length 8
00e1f50500000000 body: amount 100 000 000
02000000 no-escrow marker
00000000 message length 0
これで113バイトです。署名用のプリイメージは、その前にさらに38バイトを付加します:
5a42432d5458 tag "ZBC-TX" 6 bytes
090ab3c7...a61878 genesis hash 32 bytes
0100000001...00000000 the 113 bytes above 113 bytes
---------
151 bytes
digest = SHA3-256(preimage) = f37e5340...4644fc、署名9a3b044d...03bb07、tx_hash = f00a6692...ce78b0。稼働中のv0.4.0ノードはHTTP 202と、まさにこのハッシュを返しました。
署名手順(デバイス側)
input : account_index, genesis_hash(32), unsigned_bytes
1. k = SLIP10(m/44'/883'/account_index')
2. pub = Ed25519.pub(k); self = 00000000 || pub
3. parse unsigned_bytes; every length must land exactly at the end
4. require version == 1 and sender == self
5. decode body per type; render the approval screen; wait for the user
6. digest = SHA3-256("ZBC-TX" || genesis_hash || unsigned_bytes)
7. sig = Ed25519.sign(k, digest) # the digest IS the message
8. return sig(64), and tx_hash = SHA3-256(unsigned_bytes || sig) for the host to cross-check
ステップ6でトランザクション全体をRAMに保持しなくて済むよう、ストリーミングSHA3を使ってください。6バイトのタグ、32バイトのジェネシス、トランザクションの順に投入します。
タグは、仕様全体の中で最も実装ミスが起きやすい箇所です。"ZBC-TX"は6バイトのASCII(5a 42 43 2d 54 58)で、NUL終端文字も長さプレフィックスもありません。文字列を終端したり7バイトを書き込んだりするファームウェアは、一見有効な署名を生成しますが、チェーン上では汎用的なエラーで失敗し、診断情報も得られません。何よりも先に、ベクター0で検証してください。
チェーンバインディングにできること、できないこと
32バイトのジェネシスはホストが提供し、デバイスが自分で調べることはありません。そのため署名はチェーンに依存せず、1つのコードパスでTestNet、devnet、MainNet、そしてあらゆるプライベートまたは並行のZooBCチェーンに対応できます。
| できること | チェーンをまたいだリプレイを防ぎます。チェーンA向けに作られた署名は、チェーンBでは検証に通りません。それこそが設計目的であり、その点では完全に機能します。 |
|---|---|
| できないこと | 侵害されたホストが誤ったチェーンを指定することは防げません。デバイスは、渡されたハッシュがどれであってもそれに対して署名します。 |
そのため、既知のジェネシスハッシュの一覧をファームウェアに持たせることは依然として望ましいものの、その目的は1つだけです:信頼できるネットワーク名を画面に表示することです。ハッシュが一覧にない場合は、「UNKNOWN CHAIN」と先頭8桁の16進数を表示します。
未知のチェーンをデフォルトで拒否しないでください。devnetのハッシュは再起動のたびに変わり、プライベートチェーンや並行チェーンはZooBCではごく普通のものです。一律に拒否すると、デバイスは統合作業にも正当なデプロイにも使えなくなります。ベンダーがそのロックを望む場合は、デフォルトでオフの明示的なユーザー設定にしてください。
トランザクションタイプ:標準ファームウェアが対応すべきもの
タイプID = group + 256 × subtype。全部で56のタイプがあります。次の3つでZBCの支払い、任意のトークンの支払い、エスクローの完了をカバーでき、いずれもボディは小さく固定長です。
| ID | 名前 | ボディ | 受取人 |
|---|---|---|---|
| 1 | SendZBC | amount u64le (8) | 必須、任意のタイプ |
| 11 | TransferToken | token_id i64le (8) ‖ amount i64le (8) ‖ fee_in_token u8(省略可) | 必須 |
| 4 | ApprovalEscrow | approval u32le ‖ escrowed_transaction_hash (32) = 36バイト | 空のマーカー。送信者が承認者 |
token_id はハッシュから導出される符号付き64ビット値のため、負になることがあります。ジェネシスコインはID 1から14で、ZBC自体はID 0であり、TransferTokenに現れることはありません。各トークンには、デバイスでは検証できない独自のdecimals(0から8)があります。
ApprovalEscrowはv0.4.0で変更されました。ボディはかつてapprovalと8バイトのID(12バイト)でしたが、現在はapprovalとエスクローされたトランザクションの32バイトのハッシュ(36バイト)になり、パーサーはそれ以外の長さを拒否します。理由はまさにハードウェアウォレットに関わるものです。8バイトのIDしか見えない署名者は、何を解放しようとしているのか確認できず、しかも8バイトのIDは、デバイスに提示される偽のエスクローに対して2^64の探索で見つけられてしまいます。完全なハッシュがあれば、デバイスは承認対象を検証できます。
デバイスが検証する項目
| 構造 | 11のフィールドを解析します。各長さプレフィックスは整合している必要があり、バッファはメッセージの直後でぴったり終わらなければなりません。それ以外の場合は拒否します。 |
|---|---|
| バージョン | 1であること。 |
| 送信者 | 要求されたパスに対してデバイス自身が導出したアカウントと一致すること。デバイスは渡された送信者のバイトを決して信用せず、自ら再導出して36バイトすべてを比較します。これが「どこから送られるか」を保証する唯一の仕組みです。 |
| タイプ | 対応しているタイプであること。またはエキスパートモードが有効であること。 |
| ボディ長 | 8 (SendZBC)、16または17(TransferToken)、36(ApprovalEscrow)。余分なバイトがあれば拒否します。ノードはより長いSendZBCのボディを受け入れて末尾を無視するため、まさにそこに隠れたペイロードが紛れ込みえます。 |
| エスクローのタイムアウト | トランザクションのタイムスタンプより大きいこと。 |
| ジェネシスハッシュ | ちょうど32バイトであること。デバイスはリストと照合して検証はしませんが、長さが誤っていれば不正なリクエストです。 |
| 禁止事項 | 事前に計算されたダイジェストを受け取って署名すること。デバイスはタグとジェネシスを含め、バイトを自らハッシュ化します。 |
| サイズ上限 | 推奨値:未署名トランザクションは2 KB以下、メッセージは256 B、エスクローの指示は512 Bとし、それを超えるものはTOO_LARGEで拒否します。ティア1のトランザクションは300バイト未満ですが、ApprovalEscrowには検証用に2つ目のトランザクションが添付されるため、上限は両方のバッファをカバーする必要があります。より大きなペイロードはソフトウェアウォレットで扱うべきです。 |
ユーザーが画面で承認する内容
チェーンで検証済みのリファレンス署名ツールに基づき、画面ごとに次の順序で表示します。
| ネットワーク | 提供されたジェネシスハッシュに対応する、ファームウェアの一覧上の名前。一覧にない場合は「UNKNOWN CHAIN」と先頭8桁の16進数。 |
|---|---|
| タイプ | 「Send ZBC」「Send token」「Approve escrow」。 |
| 金額 | amount / 1e8 (ZBC記号を付けて表示)。 |
| 受取人 | 完全なアドレスを表示し、決して省略しないこと。中間を省略したアドレスは、バニティ生成されたそっくりの鍵によって簡単に欺かれます。 |
| 手数料 | アトミック値 / 1e8 ZBC。 |
| 送出合計 | 金額と手数料の合計を1つの数値で表示(ZBC建ての金額の場合のみ)。トークンの金額とZBCの手数料は単位が異なるため、合算してはいけません。 |
| メッセージ | 表示可能な文字であればそのまま表示し、そうでなければ「N bytes (binary)」とSHA3-256の先頭8桁の16進数を表示します。 |
| エスクロー | 承認者(完全表示)、コミッション、日時で表したタイムアウト、指示テキスト。 |
| トークン(タイプ11) | トークンID(符号付き10進数)とアトミック単位の金額。ホストが表示用のヒントとしてdecimalsやsymbolを提供した場合は、デバイスがそれらを検証できないため、スケール後の金額に「as reported by the app」(アプリの申告値)というラベルを付けて表示します。 |
| エスクローの承認 | 大きな文字で「APPROVE」または「REJECT」、続いて検証済みのエスクロー詳細、最後に手数料。 |
タイムスタンプは表示しなくて構いません。ユーザーにとって意味のある情報ではなく、ウィンドウはノードが強制します。
ホストとデバイスのプロトコル
トランスポートには、ベンダー独自のUSB/HIDレイヤーと既存の分割転送の仕組みを使います。重要なのは内容です。
| コマンド | リクエスト | レスポンス |
|---|---|---|
| GET_APP_VERSION | なし | ファームウェアのバージョン、対応タイプ一覧、最大トランザクションサイズ |
| GET_PUBLIC_KEY | account_index, confirm_on_device | pubkey(32), address (66文字の文字列)。確認時には、ユーザーがウェブサイトと照合できるよう、デバイスが完全なアドレスを表示します。 |
| SIGN_TX | account_index, genesis_hash(32), unsigned_tx_bytes | signature(64), tx_hash(32)、または型付きの拒否: SENDER_MISMATCH, UNSUPPORTED_TYPE, MALFORMED, USER_REJECTED, TOO_LARGE, ESCROW_HASH_MISMATCH |
| SIGN_MESSAGE | account_index, message_bytes | signature(64)、生のEd25519署名。下記のルールを参照 |
genesis_hashは表示用のヒントではなく、署名を左右する値です。ドラフト1では、ネットワークのパラメーターはラベルを選ぶだけだったため、列挙型でも構いませんでした。現在はダイジェストの内部に含まれるため、実際の32バイトでなければならず、誤った値からはどのチェーンも受け付けない署名が生成されます。
生データ署名のルール。生データ署名コマンドは、ちょうど32バイトの入力には決して署名してはならず、テキストとして表示できる入力にのみ署名すべきです。32バイトの生入力は、ユーザーが見たこともないトランザクションのSHA3ダイジェストかもしれず、そうなるとメッセージ署名コマンドがブラインドでのトランザクション署名に化けてしまいます。長さ32を拒否すれば、チェーン側を変更することなくその穴を塞げます。
全体のシーケンス
Website (page) Companion / host device Gateway / node
|-- connect ---------->| | |
| |-- GET_PUBLIC_KEY(0) ----->| (shows ZBC_... address) |
|<-- address ----------|<-- pubkey, address -------| |
|-- GET /api/v1/node/info (genesis_hash, signing_version, height) ---------->|
|-- GET /api/v1/accounts/<addr> (balances) --------------------------------->|
|-- GET /api/v1/accounts/<addr>/tokens, /api/v1/tokens/<id> (decimals) ------>|
| user fills the form | |
|-- GET /api/v1/blockchain/estimate-fee?type=1&body_length=8 ---------------->|
|<-- {minimum_fee, timestamp} ------------------------------------------------|
|-- fields {type, recipient, body, fee, message} -->| |
| | serialize the 11 fields | |
| |-- SIGN_TX(0, genesis, -->| parse, verify, display, |
| | bytes) | user approves |
| |<-- signature, tx_hash ---| |
|<-- signature --------| | |
|-- POST /api/v1/transactions {JSON} ---------------------------------------->|
|<-- 202 {status:"success", transaction_hash} --------------------------------|
|-- GET /api/v1/transactions/<hash>/status (staging, mempool, confirmed) --->|
ホストはノードのtransaction_hashとデバイスのtx_hashを比較します。両者は一致しなければならず、一致しない場合はホストが署名後にバイトを改変したことを意味します。
genesis_hashとsigning_versionは、ブロードキャスト先のエンドポイントから読み取ってください。署名を判定するのはそのチェーンだからです。
チェーンの識別
ZooBCにある唯一のネットワーク識別子はジェネシスブロックハッシュです。ノードはピア接続の前にこれを比較します。ネットワークIDのバイトはありません。GET /api/v1/node/infoは、v0.4.0で追加された2つのフィールドとともにこれを返します:
"genesis_hash": "090ab3c7...", "signing_version": 2, "signing_tag": "ZBC-TX"
signing_version がない場合、そのノードはv0.4.0より前のもので、チェーンにバインドされていない旧来のダイジェストを適用しています。現行のチェーンのみをサポートするホストは、このフィールドがないことを「ここでは署名しない」の合図として扱って構いません。ノードは小文字の16進数を出力し、アーカイブAPIは大文字で出力するため、大文字・小文字を区別せずにデコードし、先頭に0xがあれば取り除いてください。どちらでもバイト列は同じです。
どこから読み取るか。ゲートウェイは/api/v1/node/infoに、ノードではなくアーカイブサービスから応答します。アーカイブはミラー元のノードから2つの署名フィールドを引き継ぎ、ビルド7ad161a4以降では、ノードが報告している場合に限りそのまま出力します。signing_versionはノードのエンドポイントから、またはそのビルド以降のゲートウェイから読み取り、フィールドがない場合は回答とみなさず、「ノードに問い合わせる」合図として扱ってください。
ブロードキャスト
POST /api/v1/transactions. v0.4.0でも変更なし、新しいフィールドもありません。transaction_body_bytesはボディのみです。ノードは名前付きフィールドからエンベロープを再構築し、自身のジェネシスハッシュとタグを前に付けてダイジェストを再計算するため、すべてのフィールドは署名した内容と一致していなければなりません。
{
"version": 1,
"timestamp": 1700000000,
"transaction_type": 1,
"fee": 5000000,
"sender_account_address": "00000000 5e8eb28d ... 2152",
"recipient_account_address": "00000000 5e8eb28d ... 2152",
"transaction_body_bytes": "00e1f50500000000",
"signature": "9a3b044d ... 03bb07",
"message_hex": "...optional...",
"escrow": { "approver_address": "...", "commission": 0, "timeout": 1700003600 }
}
成功時はHTTP 202と{"status":"success","duplicate":false,"transaction_hash":"..."}が返ります。duplicate:trueはノードがすでにそのトランザクションを保持していることを意味するので、再送しないでください。200も成功として扱ってください。失敗時は400とエラーボディ、または負荷が高いときは503が返ります。
役立つ統合テスト:ステージングプールは最初に署名をチェックするため、「Sender account does not exist」のような後段のエラーに達したトランザクションは、すでに署名検証を通過しています。資金のない鍵でも、署名の経路を検証できます。
署名が拒否されたとき
現時点では、チェーンの不一致を他の署名エラーと区別できません。旧来のダイジェストも、誤ったチェーンのダイジェストも、まったく同じボディを返します:
{"success":false,"error":"Transaction validation failed: ValidationError: Invalid transaction signature","code":400}
そのため、原因は可能性の高いものから順に、次の順序で確認してください:
| 1 | タグの省略、NUL終端、または7バイトでの書き込み。 |
|---|---|
| 2 | SHA3-256の代わりにKeccak-256を使用している。 |
| 3 | ブロードキャスト先のチェーンと一致しないジェネシスハッシュ。 |
| 4 | エスクロー末尾のmulti_partyバイトの省略。 |
| 5 | ApprovalEscrowのボディが36バイトではなく12バイトのまま。 |
開発に使うエンドポイント
| チェーン | ゲートウェイ(HTTPS) | 署名 | 備考 |
|---|---|---|---|
| testnet | https://this-gateway | v0.4.0へ移行中 | 切り替え後はこちらが連携先 |
| devnet | https://socialconnect.network | v0.4.0 | 11ノード、フォーセット:/faucet、ジェネシスハッシュは再起動のたびに変更 |
| mainnet | 未ローンチ | v0.4.0以降 | 公開時にチェーンからジェネシスハッシュを読み取る |
読み取りはアーカイブ、ゲートウェイ、ノードの順に振り分けます。ブロードキャストはゲートウェイかノードに送り、アーカイブエンドポイントには決して送らないでください.
ジェネシスハッシュはハードコードせず、実行時に読み取ってください。devnetのジェネシスハッシュは再起動のたびに変わります。ファームウェア内のハッシュ表は画面に名前を表示するためだけのものなので、古いエントリが残っていても署名が壊れることはなく、「UNKNOWN CHAIN」と表示されるだけです。devnetのハッシュが実際に変わると、それ以前に作成された署名はすべて無効になりますが、これは機能が正しく働いている証拠です。