Article

FinTech開発事例:ブロックチェーン活用で取引の透明性を確保

高田晃太郎
FinTech開発事例:ブロックチェーン活用で取引の透明性を確保

監査のためのデータ開示に数日単位を要していた決済事業者でも、ハイブリッド型ブロックチェーンの組み込みにより分オーダーまで短縮できる事例が国内外で報告されている[1,4]。実務や研究では、改ざん検知や監査のレイテンシを縮めるほど、不正・誤謬の早期発見とコスト低減に寄与することが示唆されている[1,4]。現場で効くのは抽象論ではなく、実装と運用により“測れる”仕組みを積み上げることだ。Permissionedブロックチェーン(参加者を限定する許可型台帳)を証跡の最小単位として用い、基幹DBはそのままに、ハッシュ化と証明経路を整備する構成は、コストとパフォーマンスの折り合いを取りやすい選択肢として広く採られている。透明性の実体は、誰が、いつ、何を、なぜ正しいといえるのかを、可観測性と再計算可能性で裏づけることにある[3]。

取引の透明性を数値で語る:課題と解決の軸

ここでいう透明性は、UIの見やすさではなく、規制や監査の観点での検証可能性を指す[2,3]。測定指標としては、まず監査リクエストに対する応答時間がある。これは取引明細、署名、ハッシュ証跡、経路の突合を完了するまでの所要時間で、従来の手作業やバッチ中心の運用では日単位に伸びやすいが、ハッシュ証跡を整備しAPI化することで一般に分〜時間オーダーへ圧縮しやすい。次に改ざん検知の遅延がある。定期バッチ前提では数時間の遅延が常態化しがちだが、ストリーム処理とオンチェーンへのハッシュ係留を組み合わせると、秒〜分オーダーの検出が可能になることが研究や公開事例で示されている[4]。最後にカバレッジ、つまり証跡が取引総数の何割を網羅できているかという指標がある。サンプリング運用では抜けが生じやすいが、ハイブリッド設計では100%に近い継続的カバレッジを狙える。

ケース背景:決済事業者の一般的な前提と制約

対象として想定するのは、国内でカード・口座引落系の大量トランザクションを扱う決済事業者である。約款や規制により基幹システムのRDB構造は大幅変更が難しく、計画停止の許容も厳しい。証跡は外部監査法人や加盟店など複数の利害関係者に提示する必要があり、可用性と説明可能性の両立が求められる。鍵管理はクラウドHSM、データ保全は国内リージョン内などの要件を前提とすると、すべてをパブリックチェーンに載せる選択肢は現実的でなく、オフチェーン主記録+オンチェーン証跡の構えが有力となる[5]。

透明性の定義を実装可能な要件に落とす

ここでの透明性は、再計算で再現できること、第三者が独立に検証できること、運用者が後から都合よく書き換えられないことの三点で定義する。具体的には、各取引の不可逆ハッシュ、ハッシュの集合をまとめるマークル木(二分木で根に要約値を集約するデータ構造)、木の根を台帳外部へ定期係留し、これらに署名と時刻証明を伴わせることで、過去の状態を疑似的に凍結する[6]。ハッシュのソースは基幹DBの正規化済みレコードに限定し、アプリケーション層の一時情報は含めない。これにより、監査クエリは基幹DBから取引を抽出し、ハッシュ再計算を行い、オンチェーン記録の該当ルートと一致するかで正当性を判定できる。

アーキテクチャ:オフチェーン主記録とオンチェーン証跡の二層化

設計の主眼は、基幹DBを変更しすぎず、証跡を緩く結合させることにある。データはまずRDBに記録され、その直後にイベントとしてストリームに流れる。ストリーム処理で取引単位ハッシュが計算され、一定時間または件数でマークル木へ集約される。根はPermissionedブロックチェーン(例としてHyperledger Fabric)へ書き込まれ、トランザクションID、タイムスタンプ、バッチの範囲をメタデータとして保持する。署名鍵はHSMで管理し、クライアント認証はmTLSで行う。これにより、オフチェーンの完全な履歴と、オンチェーンの改ざん検知可能な索引が、互いを補完する関係になる[7]。

RDBに不可逆なレジャー列を持たせる

RDBでは、取引のアペンドオンリーなレジャーテーブルを設け、前レコードのハッシュと結合してチェーン化する。次のSQLはPostgreSQLでの実装例で、pgcrypto拡張を使い、JSONBの正規化とタイムスタンプを含めてSHA-256を計算している。

-- PostgreSQL: ledger table with hash chaining
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE tx_ledger (
  id           BIGSERIAL PRIMARY KEY,
  tx_id        UUID NOT NULL,
  payload      JSONB NOT NULL,
  prev_hash    BYTEA,
  hash         BYTEA,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX ON tx_ledger (tx_id);

CREATE OR REPLACE FUNCTION compute_ledger_hash() RETURNS trigger AS $$
DECLARE
  prev BYTEA;
BEGIN
  SELECT hash INTO prev FROM tx_ledger ORDER BY id DESC LIMIT 1;
  NEW.prev_hash := prev;
  NEW.hash := digest(coalesce(prev, '\x'::bytea) ||
                     encode(NEW.tx_id::bytea, 'escape') ||
                     encode(convert_to((NEW.payload)::text, 'UTF8'), 'escape') ||
                     encode(convert_to(NEW.created_at::text, 'UTF8'), 'escape'), 'sha256');
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_compute_ledger_hash
BEFORE INSERT ON tx_ledger
FOR EACH ROW EXECUTE FUNCTION compute_ledger_hash();

このレイヤでは、アペンドオンリーの制約と監査ロール以外からのUPDATE禁止を権限で強制する。中規模トラフィックでもCPUボトルネックになりにくく、ハッシュ列の追加によるストレージ増加は取引あたり数十〜数百バイト程度に収まるのが一般的だ。

ストリームでマークル木を構築し、バッチ単位で係留する

ハッシュはイベントストリームでマイクロバッチ化される。以下はPythonでのサンプルで、Kafkaからハッシュを取り込み、マークル根を生成して次のトピックにパブリッシュしている。取りこぼしに備えたリトライや、空バッチのスキップも含んでいる。

# merkle_root_publisher.py
import os
import time
import hashlib
from typing import List
from confluent_kafka import Consumer, Producer, KafkaException

BOOTSTRAP = os.getenv("KAFKA_BOOTSTRAP", "localhost:9092")
IN_TOPIC = os.getenv("IN_TOPIC", "audit.hashes")
OUT_TOPIC = os.getenv("OUT_TOPIC", "audit.roots")
BATCH_SIZE = int(os.getenv("BATCH_SIZE", "1024"))
BATCH_MS = int(os.getenv("BATCH_MS", "1000"))

c = Consumer({
    'bootstrap.servers': BOOTSTRAP,
    'group.id': 'merkle-rooter',
    'enable.auto.commit': False,
    'auto.offset.reset': 'earliest'
})

p = Producer({'bootstrap.servers': BOOTSTRAP})


def merkle_root(leaves: List[bytes]) -> bytes:
    if not leaves:
        return b''
    level = [hashlib.sha256(x).digest() for x in leaves]
    while len(level) > 1:
        nxt = []
        it = iter(level)
        for a in it:
            b = next(it, a)
            nxt.append(hashlib.sha256(a + b).digest())
        level = nxt
    return level[0]


def run():
    c.subscribe([IN_TOPIC])
    batch, start = [], time.time()
    while True:
        msg = c.poll(0.05)
        if msg is None:
            pass
        elif msg.error():
            raise KafkaException(msg.error())
        else:
            batch.append(msg.value())
        now = time.time()
        if len(batch) >= BATCH_SIZE or (batch and (now - start) * 1000 >= BATCH_MS):
            root = merkle_root(batch)
            if root:
                payload = root.hex().encode()
                p.produce(OUT_TOPIC, value=payload)
                p.flush()
                c.commit(asynchronous=False)
            batch, start = [], now

if __name__ == '__main__':
    try:
        run()
    except Exception as e:
        # アラート用に標準出力へ記録しつつプロセスを落としてオーケストレータで再起動
        print(f"fatal: {e}")
        raise

バッチサイズやウィンドウを適切に設定すると、係留までの追加レイテンシは数秒以内に収まりやすい。バッチを小さくすれば遅延はさらに縮むが、チェーンへのトランザクション数が増え運用コストが上がるため、ビジネスSLAと監査要件のバランスで決めるのが現実的だ。

Hyperledger Fabricへ根を保存するチェーンコード

Permissioned台帳にはマークル根とメタ情報だけを置く。以下はGo製のChaincode例である。重複防止、入力検証、照会APIを含む。

// chaincode_merkle_root.go
package main

import (
    "encoding/json"
    "fmt"
    "time"

    "github.com/hyperledger/fabric-contract-api-go/contractapi"
)

type AuditContract struct { contractapi.Contract }

type RootRecord struct {
    RootHex   string    `json:"rootHex"`
    BatchFrom string    `json:"batchFrom"`
    BatchTo   string    `json:"batchTo"`
    CreatedAt time.Time `json:"createdAt"`
}

func (c *AuditContract) PutRoot(ctx contractapi.TransactionContextInterface, key string, rootHex string, from string, to string) error {
    if key == "" || rootHex == "" {
        return fmt.Errorf("invalid arguments")
    }
    exists, err := ctx.GetStub().GetState(key)
    if err != nil {
        return err
    }
    if exists != nil {
        return fmt.Errorf("duplicate key: %s", key)
    }
    rec := RootRecord{RootHex: rootHex, BatchFrom: from, BatchTo: to, CreatedAt: time.Now().UTC()}
    b, _ := json.Marshal(rec)
    return ctx.GetStub().PutState(key, b)
}

func (c *AuditContract) GetRoot(ctx contractapi.TransactionContextInterface, key string) (*RootRecord, error) {
    b, err := ctx.GetStub().GetState(key)
    if err != nil {
        return nil, err
    }
    if b == nil {
        return nil, fmt.Errorf("not found: %s", key)
    }
    var rec RootRecord
    if err := json.Unmarshal(b, &rec); err != nil {
        return nil, err
    }
    return &rec, nil
}

func main() {
    cc, err := contractapi.NewChaincode(new(AuditContract))
    if err != nil {
        panic(err)
    }
    if err := cc.Start(); err != nil {
        panic(err)
    }
}

エンドースメントポリシーは運用者と第三者機関の複数署名を必要とする設定にし、単独組織の恣意性を抑える[2]。Fabricのレイテンシは構成にもよるが、数百ミリ秒〜秒単位で収束することが多い。

実装とコード:透明性を担保する最小単位

ストリームから生成された根は、信頼できるクライアントで台帳にコミットする。Node.jsのFabric SDKを用いたコミッタは、再送、指数バックオフ、可観測性のための計測を持つ。

コミットサービス(Node.js, Fabric SDK)の例

// commit_service.js
import 'dotenv/config'
import { Gateway, Wallets } from 'fabric-network'
import fs from 'fs'
import path from 'path'
import express from 'express'

const ccpPath = process.env.CCP_JSON
const channel = process.env.CHANNEL || 'audit'
const chaincode = process.env.CHAINCODE || 'auditcc'

async function submit(key, rootHex, fromTs, toTs) {
  const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'))
  const wallet = await Wallets.newFileSystemWallet(process.env.WALLET_DIR)
  const gateway = new Gateway()
  try {
    await gateway.connect(ccp, {
      wallet,
      identity: process.env.IDENTITY,
      discovery: { enabled: true, asLocalhost: false }
    })
    const network = await gateway.getNetwork(channel)
    const contract = network.getContract(chaincode)
    const res = await contract.submitTransaction('PutRoot', key, rootHex, fromTs, toTs)
    return res.toString()
  } catch (e) {
    console.error('commit failed', e)
    throw e
  } finally {
    gateway.disconnect()
  }
}

const app = express()
app.use(express.json())
app.post('/commit', async (req, res) => {
  const { key, rootHex, fromTs, toTs } = req.body
  let attempt = 0
  while (true) {
    try {
      const r = await submit(key, rootHex, fromTs, toTs)
      res.json({ ok: true, tx: r })
      return
    } catch (e) {
      attempt++
      if (attempt > 5) return res.status(500).json({ ok: false })
      await new Promise((k) => setTimeout(k, Math.min(1000 * 2 ** attempt, 10000)))
    }
  }
})

app.listen(8080)

このコンポーネントは、ブロックサイズやコミット間隔の調整と併せて運用すれば安定動作しやすい。Fabric側のブロック集約を適切に設定することでコミット効率を高め、ネットワーク往復回数を抑制できる。

監査APIと検証クライアント

外部監査向けには、特定取引IDに対する証明経路とオンチェーン根のスナップショットを返すAPIを用意する。以下はTypeScript/Expressの例で、存在しない取引IDや古いスナップショット要求へのエラーハンドリングを含む[4]。

// audit_api.ts
import express, { Request, Response } from 'express'
import { Pool } from 'pg'
import fetch from 'node-fetch'
import crypto from 'crypto'

const app = express()
app.use(express.json())
const pool = new Pool({ connectionString: process.env.DATABASE_URL })

function sha256(buf: Buffer): Buffer {
  return crypto.createHash('sha256').update(buf).digest()
}

app.get('/audit/:txId', async (req: Request, res: Response) => {
  const { txId } = req.params
  try {
    const { rows } = await pool.query('SELECT payload, proof_path, batch_key FROM proofs WHERE tx_id = $1', [txId])
    if (rows.length === 0) return res.status(404).json({ error: 'not found' })
    const { payload, proof_path, batch_key } = rows[0]
    const r = await fetch(`${process.env.COMMITTER_BASE}/root/${batch_key}`)
    if (!r.ok) return res.status(502).json({ error: 'root unavailable' })
    const { rootHex } = await r.json()
    const leaf = sha256(Buffer.from(JSON.stringify(payload)))
    let h = Buffer.from(leaf)
    for (const step of proof_path) {
      const sib = Buffer.from(step.sibling, 'hex')
      h = step.left ? sha256(Buffer.concat([sib, h])) : sha256(Buffer.concat([h, sib]))
    }
    if (h.toString('hex') !== rootHex) return res.status(409).json({ error: 'mismatch' })
    res.json({ ok: true, rootHex, batch_key, payload })
  } catch (e) {
    res.status(500).json({ error: 'internal' })
  }
})

app.listen(3000)

監査側の独立検証用には、わずか数十行で動くPythonスクリプトを提供する。RESTで証明を取得し、経路を再計算して一致を確認する。

# verify_proof.py
import json
import hashlib
import requests
import sys

def h(x: bytes) -> bytes:
    return hashlib.sha256(x).digest()

if __name__ == '__main__':
    tx_id = sys.argv[1]
    r = requests.get(f"https://api.example.com/audit/{tx_id}")
    r.raise_for_status()
    d = r.json()
    payload = json.dumps(d['payload']).encode()
    leaf = h(payload)
    cur = leaf
    for step in d['proof_path']:
        sib = bytes.fromhex(step['sibling'])
        cur = h(sib + cur) if step['left'] else h(cur + sib)
    assert cur.hex() == d['rootHex']
    print('verified')

単体のCPUコアでも大量の証明検証を短時間で実行でき、監査バッチの照合を効率化しやすい。これにより、監査先のスケジュール制約に柔軟に対応しやすくなる。

結果とビジネス効果:数値で見る透明性の価値

効果は監査応答時間だけではない。争議(チャージバック)対応で必要な証跡提示の正確性が高まり、改ざん疑義の初動での解消が進むことで、法務・CSの連携が軽くなる。外部の公開事例や調査でも、ブロックチェーンの活用が透明性やトレーサビリティの向上、監査コストの抑制に寄与し得ることが示されている[1,8,9]。Permissioned台帳3ノード前後と周辺のメッセージング、HSM、監視といった最小構成であれば、全体システムに対する追加コストは限定的に設計できる。サンプリング監査の再作業削減や人件費の抑制効果を加味すると、投資回収は運用規模と要件に依存しつつも、早期に改善が見込める。

パフォーマンスとSLAの現場値

フロー全体のスループットは、基幹DBからのイベント抽出がボトルネックになりやすい。DebeziumによるCDCを採用する構成では、ストリーム処理と係留を非同期に分離することでピーク平準化が図れる。適切なチューニングにより、係留までのエンドツーエンド遅延は低い秒オーダーに収束させやすい。障害時にはバッファリングで追いつくまで遅延が生じるが、SLA上の監査要求応答を維持できるよう、バッチキーを時間ではなく件数に基づいて生成し、追いつきの際でも検証経路が破綻しないよう設計する。鍵管理はクラウドHSMで行い、証跡署名鍵のローテーションは定期的かつ自動化しておく[4]。

落とし穴と回避策、そして拡張

現場での落とし穴は、アプリケーション層の可変フィールドをハッシュ計算に混ぜてしまい、後の仕様変更で再計算不能になるケースである。これは、ハッシュのソースを正規化済みのスキーマ境界内に限定し、プロトコルバージョンをレコードに明示することで回避できる。また、係留バッチの境界が監査要求の切り口とズレると、結局再集計の手間が増える。加盟店別、スキーム別、リージョン別など、ビジネスの切り口を観察し、バッチキー設計に反映するのがよい。将来的な拡張としては、パブリックチェーンへの定期アンカーを追加し、Permissioned台帳の合意層まで疑われた場合の最終手段を持つ設計もある[9]。コストは微増するが、紛争解決の初動スピードはさらに向上する。

さらに深堀りした設計や運用ノウハウは、セキュリティの観点でも参考になる。コストの最適化は、意思決定が速くなるだろう。

まとめ:透明性は設計と運用で積み上がる

透明性は宣言ではなく、再計算できる設計と、第三者が検証できる運用の掛け算から生まれる。オフチェーンを主記録とし、オンチェーンを証跡として用いる二層構造は、既存システムの信頼性を壊さず、監査要件を満たしながら俊敏に進化できる実装である。ここで示した設計指針に沿えば、監査応答の短縮、改ざん検知の高速化、争議の減少といった効果を“測れる指標”で継続的に改善していける。あなたの組織でも、まずはハッシュの定義域を固定し、小さなバッチの係留から始めてほしい。短期間のPoCでも、応答時間のオーダーが変わることを体感できるケースは少なくない。次にどの切り口で透明性を可視化するのか、チームで議論する準備はできているだろうか。技術とビジネスの接点に、実装で答えを置きに行こう。

参考文献

  1. EY. How blockchain could introduce real-time auditing. https://www.ey.com/en_vn/assurance/how-blockchain-could-introduce-real-time-auditing#:~:text=Blockchain%20could%20have%20considerable%20implications,%E2%80%9D
  2. KPMG. Blockchain: What does it mean for audit? https://kpmg.com/za/en/home/insights/2021/10/blockchain-what-does-it-mean-for-audit.html#:~:text=verified%20information%20reduce%20the%20need,focus%20of%20the%20auditor%E2%80%99s%20responsibilities
  3. PwC. Financial services: Blockchain and building trust. https://www.pwc.com/us/en/industries/financial-services/library/financial-services-blockchain-trust.html#:~:text=A%20blockchain%20is%20a%20decentralized,details%20about%20how%20it%20works
  4. Fernandes M. et al. Continuous auditing with distributed ledgers (preprint). arXiv:2005.07627. https://arxiv.org/abs/2005.07627#:~:text=consuming%20process,ledger%20technologies%20build
  5. 日本銀行. DLTの利点と留意点に関する整理(貿易金融分野の事例含む). https://www.boj.or.jp/about/annai/genba/focusboj/focusboj21.htm#:~:text=%E3%80%8CDLT%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%A7%E5%BE%93%E6%9D%A5%E3%81%AE%E4%BA%8B%E5%8B%99%E3%81%8C%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E5%8A%B9%E7%8E%87%E5%8C%96%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%8B%E3%80%81%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AA%E6%96%B0%E3%81%97%E3%81%84%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%8C%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%AE%E3%81%8B%E3%80%81%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E4%B8%BB%E4%BD%93%E3%81%8C%E6%A8%A1%E7%B4%A2%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82
  6. KPMG Ireland. ESG assurance and blockchain technology in audit. https://kpmg.com/ie/en/home/insights/2023/09/all-eyes-on-the-future-of-audit/esg-assurance-blockchain-technology-audit.html#:~:text=By%20utilising%20the%20blockchain%2C%20all,have%20done%20in%20the%20past
  7. Deutsche Bank Flow. Trade finance and the blockchain: three essential case studies. https://flow.db.com/trade-finance/trade-finance-and-the-blockchain-three-essential-case-studies?language_id=1#:~:text=Digitisation%20has%20been%20difficult%20as,seen%20as%20a%20possible%20solution
  8. FinTechStrategy. How blockchain is enhancing transparency in financial services (2024-07-26). https://www.fintechstrategy.com/blog/2024/07/26/how-blockchain-is-enhancing-transparency-in-financial-services/#:~:text=Blockchain%20technology%20has%20elevated%20transparency,and%20security%20of%20financial%20data%2C%E2%80%9…
  9. FinTechStrategy. Top 10 blockchain innovations transforming finance in 2024 (2024-07-10). https://www.fintechstrategy.com/blog/2024/07/10/top-10-blockchain-innovations-transforming-finance-in-2024/#:~:text=In%202024%2C%20blockchain%20has%20seen,chain%20projects