API‑First Infrastructure Powering WEB3

世界で最も安全にweb3インフラを構築できます。

秘密鍵が完全な形で存在することはありません — メモリにも、ディスクにも、どこにも。

npm install @btcp-io/sdk クリックでコピー

クイックスタート

1. 認証

curl -X POST https://api.btcp.io/auth/request-otp \
  -H "Content-Type: application/json" \
  -d '{"email": "you@company.com"}'

# メールに届いたOTPコードを入力:
curl -X POST https://api.btcp.io/auth/verify-otp \
  -H "Content-Type: application/json" \
  -d '{"email": "you@company.com", "code": "123456"}'
# → { "accessToken": "eyJ...", "refreshToken": "eyJ...", "walletId": null }

2. ウォレット作成

curl -X POST https://api.btcp.io/api/v1/wallets \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chain": "polygon-amoy"}'
# → { "wallet_id": "...", "status": "pending_keygen", "server_public_share_x": "0x...", ... }
開発者ガイド →

BTCPと他社の比較

PrivyWeb3AuthBTCP
鍵がメモリに復元されない✗ 復元される✗ 復元される✓ 一度も復元されない
サーバー侵害で鍵漏洩?リスクありリスクあり✓ シェアのみ、署名不可
サーバー側で送金ブロック✗ 不可✗ 不可✓ 8ルールのポリシーエンジン
サービス終了時の資金回収✗ 資金ロスト✗ 資金ロスト✓ 2-of-3 Shamir
セルフホスト可能✗ SaaSのみ△ 部分的✓ 完全OSS
パスキー / 生体認証△ 部分的△ 部分的✓ WebAuthn完全対応
監査ログ(ハッシュチェーン)✗ なし✗ なし✓ 改竄不可

なぜ「秘密鍵が一度も復元されない」と言えるのか

従来のウォレット

秘密鍵 = シェアA + シェアB + シェアC を結合
→ メモリ上に完全な秘密鍵が存在する瞬間がある
→ その鍵で署名

✓ BTCP の方式(2-of-2 threshold ECDSA)

サーバー: 秘密シェア x₁ を持つ
クライアント: 秘密シェア x₂ を持つ
完全な秘密鍵 x = x₁ + x₂ だが、この計算は誰もやらない

署名時の流れ

①

サーバー計算

自分のシェア x₁ で部分的な計算を実行

②

クライアント計算

自分のシェア x₂ で部分的な計算を実行

③

準同型暗号で合成

Paillier暗号により暗号化されたまま2つの結果を合成

④

署名完成

ECDSA署名 (r, s) が生成される

コードによる証拠

1. 鍵生成 — 秘密鍵は存在しない

# サーバー側: x₁ を生成して保存
# クライアント側: x₂ を生成して保存
# x = x₁ + x₂ だが、この足し算は誰も実行しない
# サーバーは x₁ しか知らない。クライアントは x₂ しか知らない。

2. 署名時 — Paillier準同型暗号で暗号化したまま計算

# signing.py — クライアント側の計算:
enc_x_s = EncryptedNumber(pk, encrypted_server_share, ...)

# Paillier準同型演算(暗号化されたまま乗算・加算):
# enc_partial = k_c⁻¹ * r * Enc(x_s) + k_c⁻¹ * (z + r * x_c)
#             = Enc(k_c⁻¹ * (z + r * (x_s + x_c)))
#             = Enc(k_c⁻¹ * (z + r * x))
#
# → クライアントは x_s の値を知ることなく、署名計算を完了

3. サーバーも完全な鍵を見ない

# signing.py — サーバー側の完了処理:
s_prime = paillier_private_key.decrypt(enc_s_prime)
# → s' = k_c⁻¹(z + rx) + ρn を得るだけ
# x_c は含まれているが ρn でマスクされているので分離不可能

s = (k_s_inv * s_prime) % ORDER
# → 最終署名 s = k⁻¹(z + rx) が完成
# サーバーが得られるのは署名値 s であって、x_c ではない