はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、企業や組織がブロックチェーン上の資産を安全かつ効率的に管理するために、階層構造をもつ秘密鍵管理と権限分離の仕組みを提案しているERC7908についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。
概要
ERC7908は、ブロックチェーン上での企業・組織向け資産管理(オンチェーン・トレジャリー管理)を標準化することを目的としています。
特に、秘密鍵の安全な生成方法、階層的な管理体制の確立、部門ごとの権限分離を実現し、マルチチェーン環境(複数のブロックチェーンを同時に運用する環境)でも資産の安全性と取引効率を両立させる仕組みを提供します。
ERC7908では、複数のブロックチェーン間で一貫した運用が可能となるように、統一された鍵の導出パス(鍵を生成・派生させるためのルール)とセキュリティメカニズムを定義しています。
これにより、組織のトレジャリー運用(資産の保管や送金など)を、より効率的かつ安全に行えるようになります。
要するに、ERC7908は「ブロックチェーン上での企業資産管理を安全・効率的に行うためのルールブック」を提案しています。
既存のばらばらな仕組みを統一し、運用上の混乱やリスクを減らします。
動機
ブロックチェーンやDeFi(分散型金融)の発展が急速に進む中で、オンチェーン資産を安全に管理することがこれまで以上に重要になっています。
個人利用とは異なり、企業や機関では管理対象となる資産が多く、運用の流れも複雑です。
そのため、従来の単純な秘密鍵管理方法では、セキュリティ面での要求を満たせなくなっています。
特に以下のような課題が存在します:
| 課題 | 説明 |
|---|---|
| 階層的な鍵管理の欠如 | 組織内での役職や部門に応じた鍵の管理ができず、管理者権限が集中してしまう。 |
| 権限コントロールの不足 | 各部署・担当者ごとの操作権限を細かく制御できず、誤操作や内部リスクが発生しやすい。 |
| マルチシグ(複数署名)機能の必要性 | 複数人の承認を経て初めて取引を実行できる仕組みが求められている。単独の秘密鍵管理ではリスクが高い。 |
ERC7908は、こうした問題を解決するために設計されています。
具体的には、組織が持つすべての資産を階層的に管理できるようにし、各部門の権限を明確に分けることで、安全性を高めながら運用効率も損なわないようにしています。
また、複数のブロックチェーンを同時に扱う場合でも、統一された鍵管理ルールと取引処理フローにより、一貫したセキュリティと利便性を維持できます。
結果として、組織全体の資産運用を標準化し、どのチェーン上でも同じ安全基準で運用できる環境を整えます。
仕様
導出パス
オンチェーンのトレジャリー(組織の資産口座)を安全に管理するため、以下の 階層型決定論的(HD: Hierarchical Deterministic) パスを使います。
HDとは、ひとつの親鍵から規則に従って子鍵を無限に派生できる仕組みのことです。
m/44'/60'/entity_id' / department_id' / account_index
| レイヤ | 役割 | 必須/推奨 | 説明 |
| -------------- | ----- | ------------------- |
| m | マスター鍵 | SHALL | HD ウォレットの最上位の秘密鍵です。外部に出しません。 |
| 44' | BIP 44 準拠 | MUST | 「44’」は BIP 44(HD の一般的な規約)に従うことを示す記号です(※「'」はハードンド:親から子への逆算が困難になる導出方式)。 |
| 60' | コイン種別 | SHALL | 「60’」は Ethereum/EVM 系チェーンの識別子です。 |
| entity_id' | 事業体識別子 | MUST | 子会社や事業単位を一意に表すハードンドのインデックスです。名前からハッシュで作ります。異なる事業体で再利用してはいけません。 |
| department_id' | 部門識別子 | MUST | 事業体の中の部門を表すハードンドのインデックスです。名前からハッシュで作ります。部門間で鍵が混ざらないようにハードンドにします。 |
| account_index | 口座番号 | MUST | 非ハードンドです。部署内で多数のアカウントを一元管理しやすくします。 |
BIP 44の変更レイヤ(change)
Ethereum/EVM は UTXO(未使用トランザクション出力)モデルではなくアカウントモデルなので、BIP 44 にある「change」レイヤは省略するのが望ましいとします。
BIP44については以下の記事を参考にしてください。
ハッシュ変換(Hash Conversion)
entity_id と department_id は文字列から SHA-256(一般的なハッシュ関数)でインデックスに変換し、その上でハードンド範囲(2^31 以上)に調整します。
こうすることで名前の衝突を避け、同じ規則で誰でも同じインデックスに到達できます。
事業体インデックス(entity_id)の計算
from hashlib import sha256
def entity_to_index(entity: str) -> int:
entity_hash = sha256(f"ENTITY:{entity}".encode()).digest()
# 先頭4バイトを整数化し、ハードンド(2^31〜)にする
return int.from_bytes(entity_hash[:4], "big") | 0x80000000
- 文字列
"ENTITY:{entity}"を SHA-256 でハッシュします。 - 先頭 4 バイトを 32bit 整数に変換し、
0x80000000(2^31)を OR して [2^31, 2^32-1] の範囲に入れます(ハードンドを保証)。
部門インデックス(department_id)の計算
from hashlib import sha256
def department_to_index(entity_hash_bytes: bytes, department: str) -> int:
dept_hash = sha256(f"DEPT:{entity_hash_bytes.hex()}:{department}".encode()).digest()
return int.from_bytes(dept_hash[:4], "big") | 0x80000000
- 事業体ハッシュの情報を部門ハッシュの材料に含め、同名部門でも事業体ごとに別の値になります。
- 同様に [2^31, 2^32-1] に収めてハードンドを保証します。
生成される entity_id と department_id は必ず 2^31〜2^32-1 の整数(hardened)でなければなりません。
ロールベースのアクセス拡張
部署の中で「承認者」、「起票者」など役割ごとに鍵を分離したい場合は、レイヤを1段追加します。
m/60'/entity_id' / department_id' / role_id' / account_index
role_id はhardenedを使います。
これにより、同じ部門でも役割ごとに鍵が隔離されます。
44’ を省くと一般的なウォレット(例:MetaMask)と互換性が落ちる可能性があります。
この省略を選ぶ場合、それを扱えるカスタムプラグインの実装が必須になります。
小規模組織向けの簡易パス
子会社のない組織や構成が単純な場合は、構造を簡略化できます。
m/44'/60' / department_id' / 0 / account_index
0 は固定の中間レイヤとして使い、BIP 44準拠を維持します。
この構造は主流のBIP 44ウォレットと互換性を保てると想定しています。
鍵導出アルゴリズム
HD 派生(BIP 32)とインデックス計算を組み合わせた実装の骨子は以下のとおりです。
ここでBIP 32は secp256k1 楕円曲線を使う一般的な HD 派生の規格、EVMは Ethereum 仮想マシン互換チェーンの総称です。
BIP32については以下の記事を参考にしてください。
# 用語:
# E: Map<entity, List[Department]> 例: {"ACME": ["Finance", "Ops"]}
# n: secp256k1 の曲線次数(実装側で参照するパラメータ)
# BIP32(): BIP-0032 による鍵派生(secp256k1)
# hash: SHA-256
def derive_treasury_keys(root_seed: bytes, E: dict[str, list[str]]):
"""
root_seed: BIP32 マスター鍵生成に使うシード
E: 事業体 -> 部門リスト
戻り値: {(entity, department, account_index): child_key}
"""
from hashlib import sha256
# 1) ルート鍵(m)を作る
root = BIP32_master_key(root_seed) # 実装の BIP32 を利用
results = {}
for entity, departments in E.items():
# 2) entity_id'(ハードンド)を計算
e_hash = sha256(f"ENTITY:{entity}".encode()).digest()
entity_index = int.from_bytes(e_hash[:4], "big") | 0x80000000
# 3) パス m/44'/60'/entity_id' まで派生
k44 = BIP32_ckd_priv(root, 44 | 0x80000000)
k60 = BIP32_ckd_priv(k44, 60 | 0x80000000)
k_ent = BIP32_ckd_priv(k60, entity_index)
for dept in departments:
# 4) department_id'(ハードンド)を計算
d_hash = sha256(f"DEPT:{e_hash.hex()}:{dept}".encode()).digest()
dept_index = int.from_bytes(d_hash[:4], "big") | 0x80000000
# 5) パス m/44'/60'/entity_id'/department_id' まで派生
k_dept = BIP32_ckd_priv(k_ent, dept_index)
# 6) account_index は非ハードンドで必要に応じて増やす
for account_index in range(0, 3): # 例として 0..2 を生成
k_acct = BIP32_ckd_priv(k_dept, account_index)
# 7) ルート鍵素材とハッシュを連結して最終鍵を強化(クロスレイヤ漏えい防止)
final_material = k_acct.key + sha256(k_dept.key + k_ent.key).digest()
final_key = sha256(final_material).digest() # 実装で秘密鍵化
results[(entity, dept, account_index)] = final_key
return results
**BIP 32(secp256k1)**を使って派生します。
派生レイヤをまたいだ情報漏えい(たとえば下位レイヤから上位レイヤを推測されるリスク)を避けるため、ハッシュとルート鍵素材を連結して最終鍵を強化する処理が必須です。
互換性に関する考慮
ERC7908はBIP 44(m/purpose'/coin_type'/account'/change/address_index)に着想を得ていますが、以下の点で調整しています。
-
Ethereum 系では
changeレイヤを使わない
アカウントモデルに適合させるためです。 -
組織要件に応じて5レイヤを超える拡張
ロールレイヤ(role_id’)の追加など、実務的なアクセス制御を実現します。
44’ を省く構成を採る場合は、一般的なウォレットと整合しないことがあるため、そのチェーンやウォレットに合わせたカスタムプラグインを用意することが必須です。
実装の進め方(手順)
-
事業体と部門の命名を確定
命名は安定していることが重要です。
将来の改名は鍵の再配置コストにつながります。 -
entity_id’とdepartment_id’を計算
仕様のSHA-256規則で2^31以上のインデックスに変換します。 -
HD パスを構築
標準形(m/44'/60'/entity_id'/department_id'/account_index)か、必要ならロールレイヤを挿入します。 -
BIP 32(secp256k1)で派生
各レイヤを順に派生し、口座数に応じてaccount_indexを進めます(非ハードンド)。 -
鍵素材の強化
親階層の鍵素材とハッシュを連結して最終鍵を生成し、レイヤ間の推測を困難にします。 -
互換性の検証を行う
既存ウォレットや社内ツールでの復元、署名、アドレス生成が期待どおりか確認します。 -
運用ポリシーと監査を整備
事業体・部門・役割ごとの発行権限、承認フロー(マルチシグ)と監査ログの保持を決めます。
参考コード(インデックス計算とパス組み立て)
以下は最小構成のユーティリティです。
BIP 32実装部分(bip32_*)は実際のライブラリで置き換えてください。
from hashlib import sha256
from dataclasses import dataclass
HARDENED = 0x80000000
def hardened32(x: int) -> int:
return (x & 0x7fffffff) | HARDENED
def entity_index(entity: str) -> int:
h = sha256(f"ENTITY:{entity}".encode()).digest()
return hardened32(int.from_bytes(h[:4], "big"))
def department_index(entity_hash: bytes, department: str) -> int:
h = sha256(f"DEPT:{entity_hash.hex()}:{department}".encode()).digest()
return hardened32(int.from_bytes(h[:4], "big"))
@dataclass
class Path:
purpose: int = 44 | HARDENED
coin: int = 60 | HARDENED
entity_h: int = 0
dept_h: int = 0
account: int = 0
def to_str(self) -> str:
return f"m/{self.purpose & 0x7fffffff}'/{self.coin & 0x7fffffff}'/{self.entity_h & 0x7fffffff}'/{self.dept_h & 0x7fffffff}'/{self.account}"
def build_path(entity_name: str, dept_name: str, account_index_n: int) -> str:
e_hash = sha256(f"ENTITY:{entity_name}".encode()).digest()
e_idx = hardened32(int.from_bytes(e_hash[:4], "big"))
d_idx = department_index(e_hash, dept_name)
return Path(entity_h=e_idx, dept_h=d_idx, account=account_index_n).to_str()
# 例:
print(build_path("ACME", "Finance", 0)) # m/44'/60'/...'/...'/0
補足
企業および部門の分離
ERC7908では、子会社ごと・部門ごとに完全に独立したオンチェーンアカウントを作成できます。
「導出パス(Derivation Path)」によって、事業体や部門ごとに異なる鍵が派生するため、どの組織単位でも他の領域に干渉できません。
これにより、万が一ある部門で秘密鍵の漏洩や不正アクセスが発生しても、他の部門や子会社には影響が及びません。
さらに、導出パスの階層構造がセキュリティ境界として機能します。
つまり、部門単位で鍵が完全に分離され、**リスクが局所化(isolated exposure)**されるため、大規模な企業でも安全な運用が可能になります。
グループ全体での統合的管理権限
グループ全体の管理者(例えば持株会社の財務責任者)は、**マスター秘密鍵(Master Private Key)**を保持します。
この鍵からは、すべての子会社の秘密鍵を導出することができます。
つまり、管理者は各子会社・部門の資産に対して、閲覧権限や取引発行権限を持つ「最上位の権限者」として振る舞うことができます。
一方で、各子会社や部門はその下位の鍵のみを持ち、自分たちの範囲内の資産にのみアクセスできます。
この構造により、全体統制と現場の自律運用を両立させることができます。
グループ管理者は必要に応じて全体の資産状況を把握し、リスクを監視できますが、各現場の権限範囲は明確に制限されます。
部門共有用の秘密鍵
例えば、子会社Aの管理者アリス(Alice)が新任の管理者ボブ(Bob)とアカウント管理を共有したい場合を考えます。
この場合、アリスは子会社Aのマスター秘密鍵のみを共有すれば十分です。
この鍵からは、A社のすべての部門アカウント(財務部・開発部など)が導出できます。
つまり、ボブはアリスから受け取ったマスター鍵を使って、自社内のすべての資産を再構成できます。
一方で、A社の鍵はB社やC社には影響しません。
この仕組みは、「必要な範囲だけを共有できる柔軟性」を持ちながら、鍵管理を簡潔にする効果があります。
部門単位の鍵共有や引き継ぎを行う時も、個別にアカウントを共有する必要がありません。
監査部門向けの公開鍵共有
監査部門が特定の部門に属するオンチェーン取引を確認したい場合、その部門の拡張公開鍵(Extended Public Key)を共有します。
この拡張公開鍵からは、その部門に属するすべての公開鍵を派生できるため、監査部門は各アドレスのトランザクション履歴を追跡できます。
しかし、公開鍵からは秘密鍵を導出できないため、監査部門は取引を確認できても操作はできないという安全な分離が保たれます。
この構造は、ブロックチェーンの透明性を活かしつつ、社内監査や外部監査に必要な閲覧権限を安全に提供する方法です。
監査ログや取引履歴をリアルタイムで照会できるため、監査作業を自動化・効率化できます。
互換性
ERC7908は、BIP 39とBIP 32、BIP 44と互換性があります。
BIP39については以下の記事を参考にしてください。
参考実装
"""
Secure Treasury Management System
Enterprise-grade hierarchical deterministic wallet implementation compliant with BIP-44
"""
import hashlib
import logging
from typing import Tuple, Dict
from bip32utils import BIP32Key
from eth_account import Account
from mnemonic import Mnemonic # Add BIP39 support
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("TreasurySystem")
class TreasurySystem:
def __init__(self, mnemonic: str):
"""
Initialize the treasury system
:param mnemonic: BIP-39 mnemonic (12/24 words)
"""
if not Mnemonic("english").check(mnemonic):
raise ValueError("Invalid BIP-39 mnemonic")
# Generate seed using standard BIP-39
self.seed = Mnemonic.to_seed(mnemonic, passphrase="")
self.root_key = BIP32Key.fromEntropy(self.seed)
logger.info("Treasury system initialized. Master key fingerprint: %s",
self.root_key.Fingerprint().hex())
@staticmethod
def _hierarchical_hash(entity: str, department: str) -> Tuple[int, int]:
"""
Hierarchical hash calculation (compliant with proposal spec)
Returns: (entity_index, department_index)
"""
# Entity hash
entity_hash = hashlib.sha256(f"ENTITY:{entity}".encode()).digest()
entity_index = int.from_bytes(entity_hash[:4], 'big') | 0x80000000
# Department hash (chained)
dept_input = f"DEPT:{entity_hash.hex()}:{department}".encode()
dept_hash = hashlib.sha256(dept_input).digest()
dept_index = int.from_bytes(dept_hash[:4], 'big') | 0x80000000
return entity_index, dept_index
def _derive_key(self, path: list) -> BIP32Key:
"""General key derivation method"""
current_key = self.root_key
for index in path:
if not isinstance(index, int):
raise TypeError(f"Invalid derivation index type: {type(index)}")
current_key = current_key.ChildKey(index)
return current_key
def generate_account(self, entity: str, department: str,
account_idx: int = 0) -> Dict[str, str]:
"""
Generate department account (BIP44 5-layer structure)
Path: m/44'/60'/entity'/dept'/account_idx
"""
e_idx, d_idx = self._hierarchical_hash(entity, department)
# BIP-44 standard path
derivation_path = [
0x8000002C, # 44' (hardened)
0x8000003C, # 60' (Ethereum)
e_idx, # entity_index (hardened)
d_idx, # department_index (hardened)
account_idx # address index
]
key = self._derive_key(derivation_path)
priv_key = key.PrivateKey().hex()
return {
'path': f"m/44'/60'/{e_idx}'/{d_idx}'/{account_idx}",
'private_key': priv_key, # Warning: Never expose this in production
'address': Account.from_key(priv_key).address
}
def get_audit_xpub(self, entity: str, department: str) -> str:
"""
Retrieve department-level extended public key (for auditing)
Path: m/44'/60'/entity'/dept'
"""
e_idx, d_idx = self._hierarchical_hash(entity, department)
path = [
0x8000002C, # 44'
0x8000003C, # 60'
e_idx, # entity'
d_idx # dept'
]
return self._derive_key(path).ExtendedKey()
def get_dept_xprv(self, entity: str, department: str) -> str:
"""
Get department-level extended private key (strictly controlled)
Path: m/44'/60'/entity'/dept'
"""
e_idx, d_idx = self._hierarchical_hash(entity, department)
path = [
0x8000002C, # 44'
0x8000003C, # 60'
e_idx, # entity'
d_idx # dept'
]
return self._derive_key(path).ExtendedKey()
@staticmethod
def derive_addresses_from_xpub(xpub: str, count: int = 20) -> list:
"""Derive addresses from extended public key (audit use)"""
audit_key = BIP32Key.fromExtendedKey(xpub)
return [
Account.from_key(
audit_key
.ChildKey(i) # Address index
.PrivateKey()
).address
for i in range(count)
]
if __name__ == "__main__":
# Example usage (remove private key printing in production)
try:
# Use standard mnemonic
mnemo = Mnemonic("english")
mnemonic = mnemo.generate(strength=256)
treasury = TreasurySystem(mnemonic)
print(f"mnemonic: {mnemonic}")
print("\n=== Finance Department Account Generation ===")
finance_acc1 = treasury.generate_account("GroupA", "Finance", 0)
finance_acc2 = treasury.generate_account("GroupA", "Finance", 1)
print(f"Account1 path: {finance_acc1['path']}")
print(f"Account1 address: {finance_acc1['address']}")
print(f"Account1 private key: {finance_acc1['private_key']}")
print(f"Account2 path: {finance_acc2['path']}")
print(f"Account2 address: {finance_acc2['address']}")
print(f"Account2 private key: {finance_acc2['private_key']}")
print("\n=== Audit Verification Test===")
audit_xpub = treasury.get_audit_xpub("GroupA", "Finance")
print(f"Audit xpub: {audit_xpub}")
audit_addresses = TreasurySystem.derive_addresses_from_xpub(audit_xpub, 2)
print(f"Audit-derived addresses: {audit_addresses}")
assert finance_acc1['address'] in audit_addresses, "Audit verification failed"
assert finance_acc2['address'] in audit_addresses, "Audit verification failed"
print("✅ Audit verification successful")
print("\n=== Department Isolation Test ===")
other_dept_acc = treasury.generate_account("GroupA", "Audit", 0)
print(f"Account3 path: {other_dept_acc['path']}")
print(f"Account3 address: {other_dept_acc['address']}")
assert other_dept_acc['address'] not in audit_addresses, "Isolation breach"
print("✅ Department isolation effective")
print("\n=== Department Private Key Sharing Test ===")
# Gets the department layer extension private key
dept_xprv = treasury.get_audit_xpub("GroupA", "Finance").replace('xpub', 'xprv') # 实际应通过专用方法获取
print(f"Fiance xprv: {dept_xprv}")
# Derive the account private key from the extension private key
dept_key = BIP32Key.fromExtendedKey(dept_xprv)
derived_acc0_key = dept_key.ChildKey(0).PrivateKey().hex()
derived_acc1_key = dept_key.ChildKey(1).PrivateKey().hex()
print(f"Fiance derived_acc0_key: {derived_acc0_key}")
print(f"Fiance derived_acc1_key: {derived_acc1_key}")
# Verify the private key derivation capability
assert derived_acc0_key == finance_acc1['private_key'], \
"Account 0 private key derivation failed"
assert derived_acc1_key == finance_acc2['private_key'], \
"Account 1 private key derivation failed"
print("✅ Private key derivation from department xprv successful")
except Exception as e:
logger.error("System error: %s", e, exc_info=True)
セキュリティ
トレジャリー管理者(企業や組織でオンチェーン資産を扱う責任者)にとって、**階層型決定論的ウォレット(HDウォレット)**は非常に便利な仕組みです。
HDウォレットでは、1つの「マスター鍵」から無数の子鍵を体系的に生成できるため、複数の部署・子会社・役職に応じた鍵管理を一元的に行うことができます。
しかし、この便利さの裏には、マスター鍵の保護に対する強い責任が伴います。
マスター鍵の重要性
マスター鍵(Master Key)は、HDウォレット全体の根幹です。
この鍵が漏洩すると、そこから導出されるすべての子鍵(=全ての資産アカウント)も推測・復元されてしまいます。
つまり、マスター鍵を失えば「全資産を失う」のと同じことです。
一方で、マスター鍵を安全に保管できていれば、下位の鍵を再生成できるため、バックアップや復旧が非常に容易になります。
保護のための追加対策
マスター鍵やそれに対応するニーモニックフレーズ(12〜24単語の人間が読み書きできる形式)は、追加的な防御策を講じる必要があります。
例えば、以下のような方法が考えられます。
| 対策方法 | 説明 |
|---|---|
| 鍵の分割保管(Shamir’s Secret Sharingなど) | マスター鍵を複数の断片に分け、それぞれを異なる場所や担当者が保管します。一定数の断片を集めないと鍵を再構成できません。これにより、1人や1箇所の漏洩で全資産が危険にさらされることを防げます。 |
| オフライン保管(Cold Storage) | マスター鍵やニーモニックをネットワークに接続されていない環境で保管します。物理的なセキュリティを確保し、オンライン攻撃のリスクを排除します。 |
| マルチシグ運用 | マスター鍵の操作自体を複数の署名者で分担し、1人では資産を動かせないようにします。 |
| 暗号化バックアップ | ニーモニックや鍵データを強力な暗号化で保管し、アクセス制御を設けます。 |
これらの方法を組み合わせることで、利便性を損なわずにセキュリティを強化することができます。
引用
Xiaoyu Liu (@elizabethxiaoyu) jiushi.lxy@antgroup.com, Yuxiang Fu (@tmac4096) kunfu.fyx@antgroup.com, Yanyi Liang eason.lyy@antgroup.com, Hao Zou (@BruceZH0915) situ.zh@antgroup.com, Siyuan Zheng (@andrewcoder666) zhengsiyuan.zsy@antgroup.com, yuanshanhshan (@xunayuan) yuanshanshan.yss@antgroup.com, "ERC-7908: HD wallet In Treasury Management [DRAFT]," Ethereum Improvement Proposals, no. 7908, March 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7908.
最後に
今回は「企業や組織がブロックチェーン上の資産を安全かつ効率的に管理するために、階層構造をもつ秘密鍵管理と権限分離の仕組みを提案しているERC7908」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!