署名付き領収書による API データの証明 — 決済検証のその先へ

署名付き領収書による API データの証明 — 決済検証のその先へ

検証可能な領収書を使用して自律型エージェントと従量課金制 API の間に信頼を構築する方法

エージェント間決済の新しい世界

史上初めて、AI エージェントが自律的に支払いを行えるようになりました。x402 プロトコルは HTTP 402 Payment Required ステータスコードを再利用し、クライアント(人間か人工知能かを問わず)が USDC ステーブルコインで API 呼び出しごとに支払いを行い、API キーを完全にスキップして、アカウントを作成することなく利用できるようにします。

これは実に巧妙です。しかし、エージェントがデータに基づいて行動し始めるまで、誰も話題にしない問題が存在します。

2026年6月30日現在、私たちは転換点にいます。大規模言語モデルはタスクのオーケストレーションにおいてより賢くなり、自律型エージェントは本番環境にデプロイされ、オンザフライでサービスの支払いを行う機能は必須条件となりつつあります。しかし、現在の決済証明(ブロックチェーン上のトランザクション)は取引の片側、つまりお金が移動したことしか確認していません。見返りとして受信したデータが本物であるか、改ざんされているかについては何も語っていません。

エージェントが完璧に支払いを完了しても、偽装されたデータに基づいて行動してしまう可能性があります。

決済証明はデータを証明しない

1回の呼び出しにつき 0.05 USDC を課金する API を構築したとしましょう。顧客のエージェントがエンドポイントにアクセスし、x402 フローが完了して USDC が送金され、エージェントはレスポンスとしてデータを受信します。決済システムは、トランザクションがオンチェーンで発生したことを証明できます。

しかし、レスポンスボディが実際にあなたから送信されたものであることを誰が検証したのでしょうか? ブロックチェーンではありません。決済領収書は意図の証明書であり、真正性の証明書ではありません。ネットワークを傍受する攻撃者や、エージェントと API の間にあるバグのあるプロキシでさえ、改ざんされたデータを紛れ込ませることができ、その場合、決済証明は何の意味もなさなくなります。

これは深刻なギャップであり、規模が大きくなるにつれて悪化します。エージェントが複数の API にまたがって呼び出しを連鎖させ、それぞれ異なるプロバイダーからデータを購入している場合、対価を支払ったものを正しく受領したという暗号学的保証が必要です。そうでなければ、検証できないデータに意思決定を賭けることになり、手探り状態になってしまいます。

解決策:署名付き領収書

解決策は、決済証明をデータ自体に紐付けることです。API が支払い済みの顧客にレスポンスを発行する際、レスポンスボディと決済トランザクション ID の両方に秘密鍵で署名します。顧客は以下を受け取ります:

  1. データ自体。
  2. オンチェーンで決済が行われたことを証明する領収書。
  3. 両者を紐付けるあなたの暗号署名。

これにより、エージェントは独立して検証できるようになります:このデータがこの API から来たこと、この決済が本物であること、そして両者が一致することです。セットアップ時に公開鍵を1回検証すること以外、信頼を前提とする必要はありません。

これは革新的なものではありません — 署名付き領収書は RSA と同程度に歴史のある概念です — しかし、x402 を単なる決済プリミティブから完全な検証システムへと変化させるための、失われていたピースなのです。

Express での構築

これを実装する方法は以下の通りです。鍵対(Ed25519 で十分です)、呼び出しの支払いが完了したことを検知する方法、およびレスポンスに署名するためのミドルウェアが必要になります。

まず、鍵と依存関係をセットアップします:

npm install express tweetnacl base64-js axios

鍵対を生成し、安全に保存します(絶対に git には保存しないでください):

const nacl = require('tweetnacl');
const base64js = require('base64-js');

const keyPair = nacl.sign.keyPair();
const publicKey = base64js.fromByteArray(keyPair.publicKey);
const secretKey = base64js.fromByteArray(keyPair.secretKey);

console.log('Public Key:', publicKey);
console.log('Secret Key:', secretKey);

次に、決済チェック後にレスポンスをインターセプトするミドルウェアを作成します:

const express = require('express');
const app = express();

const verifyPayment = async (req, res, next) => {
  const paymentTxId = req.headers['x-payment-tx-id'];
  
  if (!paymentTxId) {
    return res.status(402).json({
      error: 'Payment Required',
      message: 'Provide a payment transaction ID'
    });
  }
  
  // Verify the tx on chain (sketch version)
  const txValid = await verifyTransactionOnChain(paymentTxId, 'REPLACE_WITH_VAULT_REFERENCE');
  
  if (!txValid) {
    return res.status(402).json({ error: 'Invalid payment' });
  }
  
  req.paymentTxId = paymentTxId;
  next();
};

次に、レスポンス署名ミドルウェアを作成します:

const nacl = require('tweetnacl');
const base64js = require('base64-js');

const secretKey = base64js.toByteArray(process.env.API_SECRET_KEY);

const signResponse = (req, res, next) => {
  const originalJson = res.json.bind(res);
  
  res.json = function(data) {
    const payload = JSON.stringify({
      data: data,
      txId: req.paymentTxId,
      timestamp: Date.now()
    });
    
    const message = Buffer.from(payload);
    const signature = nacl.sign.detached(message, secretKey);
    const signatureBase64 = base64js.fromByteArray(signature);
    
    res.setHeader('X-Signature', signatureBase64);
    res.setHeader('X-Payload-Hash', payload);
    
    return originalJson({ data, receipt: { txId: req.paymentTxId, signature: signatureBase64 } });
  };
  
  next();
};

app.use(verifyPayment);
app.use(signResponse);

最後に、エンドポイントを追加します:

app.get('/data/:id', (req, res) => {
  const result = { userId: req.params.id, balance: 42, updated: '2026-06-30' };
  res.json(result);
});

app.listen(3000, () => console.log('Listening on :3000'));

クライアント側では、データに基づいて行動する前に署名を検証します:

const nacl = require('tweetnacl');
const base64js = require('base64-js');

const publicKey = base64js.toByteArray('REPLACE_WITH_YOUR_PUBLIC_KEY');

function verifyReceipt(response) {
  const signature = base64js.toByteArray(response.headers['x-signature']);
  const payload = response.headers['x-payload-hash'];
  const message = Buffer.from(payload);
  
  const isValid = nacl.sign.detached.verify(
    message,
    signature,
    publicKey
  );
  
  if (!isValid) throw new Error('Signature verification failed');
  return response.data;
}

const response = await fetch('http://app.example.com/data/user123', {
  headers: { 'X-Payment-Tx-Id': 'USDC_TXN_ABC123' }
});

const verified = verifyReceipt(response);
console.log('Trusted data:', verified);

本番環境でこれが重要となる理由

このパターンにより、いくつかのことが可能になります。エージェントは、これまで利用したことのない API からトラストレスにデータを購入できるようになります。監査人は、意思決定チェーンが本物の購入済みデータに基づいているかを検証できます。そして API プロバイダーは、約束通りのものを提供したという明確な暗号学的証明を得られます。

また、スケールにも対応します。1つの署名で1つのレスポンスをカバーします。何百万回もの呼び出しの証明が必要な場合は、プロバイダー側でそれらを Merkle ツリーにバッチ処理し、バッチごとに1つの包括署名を発行できます。顧客はバッチルートを1回検証すれば、その下のすべてを信頼できます。

結論

x402 プロトコルは自律型エージェント API の決済側面を解決しました。署名付き領収書はデータの真正性側面を解決します。両者を組み合わせることで、エージェントは信頼できない情報源から確実にデータを購入し、確信を持ってそれに基づいて行動できるようになります。

メリット

  • エージェントは、公開鍵以外の面で API プロバイダーを信頼することなく、データの完全性を検証できます。
  • 決済とデータは暗号学的に結び付けられており、一方を偽造することなくもう一方を捏造することはできません。
  • Merkle ツリーやバッチ署名により、単一の呼び出しから何百万回もの呼び出しまでスケーリングします。
  • 新しいインフラは不要で、標準的な暗号技術と既存のブロックチェーンのみで動作します。
  • 既存の x402 決済フローで動作します。

デメリット

  • レイテンシの追加:すべてのレスポンスに署名する必要があります(ただし、ほとんどのユースケースでは許容範囲内です)。
  • プロバイダー側での安全な鍵管理が必要:秘密鍵の漏洩は信頼性を無効にします。
  • クライアント側で署名検証を実装する必要がある(初心者にとって容易ではありません)。
  • API プロバイダーが虚偽のデータに署名することを防げない(信頼の対象は公開鍵であり、プロバイダーの誠実さではありません)。
  • API コントラクトに複雑さを追加する:クライアントとサーバーがペイロードフォーマットに合意する必要があります。

注意事項

名前 app.example.com, REPLACE_WITH_VAULT_REFERENCE, REPLACE_WITH_YOUR_PUBLIC_KEY、およびここに示されているすべてのトランザクション ID はプレースホルダーです。秘密鍵、API シークレット、または実際の資格情報をコード内にハードコードしないでください。本番環境にデプロイする前に、必ずローカルテスト環境で署名検証をテストしてください。秘密鍵はシークレット保管庫(HashiCorp Vault、AWS Secrets Manager、または類似のもの)に保存し、環境変数やソース管理には絶対に保存しないでください。自己責任のもとで進め、実際の決済を受け入れる前にテストネット環境で実装を徹底的に検証してください。

よくある質問

  • API プロバイダーの秘密鍵が侵害された場合はどうなりますか?
  • 複数の API 呼び出しを1つの署名付き領収書にバッチ処理できますか?
  • JavaScript 以外の言語で署名を検証するにはどうすればよいですか?
  • レスポンス内のすべてのフィールドに署名する必要がありますか、それともデータだけでよいですか?
  • 署名の作成と検証にかかるパフォーマンス上のオーバーヘッドはどのくらいですか?
  • 従量課金ではなく固定サブスクリプションを課金する API でも機能しますか?
  • 本番環境で署名検証の失敗をエージェントはどのように処理しますか?
  • 使用すべき署名アルゴリズムは Ed25519 のみですか、それとも他のアルゴリズムも許容されますか?

タグ

#x402 #web3 #api #usdc #cryptography #agents #authentication #blockchain

Free field guide

Incident Response: First Hour

A calm, evidence-preserving checklist for establishing control, bounding impact, communicating clearly, and containing an incident safely.