-
Notifications
You must be signed in to change notification settings - Fork 705
Add ERC: HD wallet In Treasury Management #978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eip-review-bot
merged 27 commits into
ethereum:master
from
elizabethxiaoyu:treasury_hd_wallet_proposal
Jun 16, 2025
+550
−0
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
4e1ef49
add new erc
c700c5b
add new erc
43c75a9
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 14fc69a
Merge remote-tracking branch 'origin/master' into treasury_hd_wallet_…
f83a446
modify proposal according to reviewer's comments
025fbd7
Merge remote-tracking branch 'origin/treasury_hd_wallet_proposal' int…
20f9537
update img trace
e5453f3
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 1f61e1a
update name format using pr number
ad1b394
optimize format
9d21034
optimize format according to auto check
7d6dec2
optimize format according to auto check-1
cf60b35
optimize format according to auto check-2
d04c4dc
optimize format according to auto check-3
1f5e158
fix typo
2480158
Assign EIP number
SamWilsn 5f29189
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 1346d20
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu d69d0cd
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu eec104a
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 3c85cbc
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 4df080a
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 5098135
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 05bcaa2
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu b39b90b
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 4700158
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu 31093fd
Merge branch 'master' into treasury_hd_wallet_proposal
elizabethxiaoyu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,367 @@ | ||||||
--- | ||||||
eip: 7908 | ||||||
title: HD wallet In Treasury Management | ||||||
description: HD wallets for treasury systems, isolating entities and departments via cryptographic key paths. | ||||||
author: Xiaoyu Liu (@elizabethxiaoyu) <[email protected]>, Yuxiang Fu (@tmac4096) <[email protected]>, Yanyi Liang <[email protected]>, Hao Zou (@BruceZH0915) <[email protected]>, Siyuan Zheng (@andrewcoder666) <[email protected]>, yuanshanhshan (@xunayuan) <[email protected]> | ||||||
discussions-to: https://ethereum-magicians.org/t/new-erc-deterministic-account-hierarchy-in-treasury-management/23073 | ||||||
status: Draft | ||||||
type: Standards Track | ||||||
category: ERC | ||||||
created: 2025-03-07 | ||||||
--- | ||||||
|
||||||
## Abstract | ||||||
|
||||||
This proposal aims to provide a standardized method for on-chain treasury management of institutional assets, ensuring secure private key generation, hierarchical management, and departmental permission isolation while supporting asset security and transaction efficiency in multi-chain environments. By defining a unified derivation path and security mechanisms, this proposal offers an efficient and secure solution for treasury management. | ||||||
|
||||||
## Motivation | ||||||
|
||||||
With the rapid development of blockchain and DeFi, secure management of on-chain assets has become critical. Traditional private key management struggles to meet the security demands of large organizations in complex scenarios, where hierarchical key management, permission controls, and multi-signature mechanisms are essential. This proposal provides a standardized solution for institutional treasury management, ensuring asset security and transaction efficiency. | ||||||
|
||||||
## Specification | ||||||
|
||||||
### Derivation Path | ||||||
|
||||||
For secure on-chain treasury account key management, implementations **MUST** use the following hierarchical | ||||||
deterministic (HD) path: | ||||||
|
||||||
```latex | ||||||
m/44'/60'/entity_id' / department_id' / account_index | ||||||
``` | ||||||
|
||||||
Path Components: | ||||||
|
||||||
1. **Master Key (**`m`**)** | ||||||
|
||||||
- **SHALL** represent the root HD wallet private key. | ||||||
|
||||||
2. **BIP-44 Compliance Layer (`44'`)** | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
- **MUST** use `44'` (hardened) to indicate BIP-44 compliance. | ||||||
|
||||||
3. **Coin Type Layer ( `60'` )** | ||||||
|
||||||
- **SHALL** use `60'` (hardened) for Ethereum and EVM-compatible chains. | ||||||
|
||||||
4. **Entity Identifier ( `entity_id'`**) | ||||||
|
||||||
- **MUST** be derived by hashing the subsidiary name into a hardened index. | ||||||
- **MUST NOT** reuse the same `entity_id'` across distinct subsidiaries. | ||||||
|
||||||
5. **Department Identifier (**`department_id'`**)** | ||||||
|
||||||
- **SHALL** be derived by hashing the department name into a hardened index. | ||||||
- **MUST** isolate keys between departments via hardened derivation. | ||||||
|
||||||
6. **Account Index (**`account_index`**)** | ||||||
|
||||||
- **MUST** use non-hardened derivation to allow unified account management. | ||||||
|
||||||
**Note on BIP-44 Adaptation**: | ||||||
|
||||||
- The BIP-44 `change` layer **SHOULD** be omitted for Ethereum/EVMs due to their account model (not UTXO). | ||||||
|
||||||
### Hash Conversion | ||||||
|
||||||
To derive `entity_id` and `department_id`: | ||||||
|
||||||
1. **Entity Index Calculation** | ||||||
|
||||||
- **SHALL** compute `entity_id` as: | ||||||
|
||||||
```python | ||||||
entity_hash = sha256(f"ENTITY:{entity}".encode()).digest() | ||||||
entity_index = int.from_bytes(entity_hash[:4], "big") | 0x80000000 # 2^31 ≤ index < 2^32 | ||||||
``` | ||||||
|
||||||
2. **Department Index Calculation** | ||||||
|
||||||
- **MUST** compute `department_id` as: | ||||||
|
||||||
```python | ||||||
dept_hash = sha256(f"DEPT:{entity_hash}:{department}".encode()).digest() | ||||||
dept_index = int.from_bytes(dept_hash[:4], "big") | 0x80000000 # 2^31 ≤ index < 2^32 | ||||||
``` | ||||||
|
||||||
3. **Output Constraints** | ||||||
|
||||||
- Generated indices **MUST** be integers in `[2^31, 2^32-1]` to enforce hardened derivation. | ||||||
|
||||||
### Extended Path for Role-Based Access | ||||||
|
||||||
For finer access control (e.g., roles within departments): | ||||||
|
||||||
```latex | ||||||
m/60'/entity_id' / department_id' /role_id'/ account_index | ||||||
``` | ||||||
|
||||||
1. **Role Identifier (**`role_id'`**)** | ||||||
|
||||||
- **SHOULD** use hardened derivation to isolate role-specific keys. | ||||||
|
||||||
**Compatibility Note**: | ||||||
|
||||||
- Omitting the `44'` layer **MAY** cause incompatibility with standard wallets (e.g., MetaMask). | ||||||
- Integrations with such wallets **MUST** implement custom plugins to handle this deviation. | ||||||
|
||||||
### Simplified Path for Smaller Entities | ||||||
|
||||||
For entities without subsidiaries: | ||||||
|
||||||
```latex | ||||||
m/44'/60' / department_id' /0/ account_index | ||||||
``` | ||||||
|
||||||
**Compatibility Guarantee** | ||||||
|
||||||
- This structure **SHOULD** ensure compatibility with mainstream BIP-44 wallets. | ||||||
|
||||||
### Key Derivation Algorithm | ||||||
|
||||||
Implementations **MUST** adhere to: | ||||||
|
||||||
```latex | ||||||
E = Map<entity, List<Department>> | ||||||
n = Layer2 curve order | ||||||
path = m/44'/60'/entity_id' / department_id' / account_index | ||||||
BIP32() = Official BIP-0032 derivation function on secp256k1 | ||||||
hash = SHA256 | ||||||
root_key = BIP32(path) | ||||||
for each E: | ||||||
key = hash(root_key|hierarchical_hash_to_index(entity,department)) | ||||||
return key | ||||||
``` | ||||||
|
||||||
**Cryptographic Requirements**: | ||||||
|
||||||
- **SHALL** use BIP-0032 with `secp256k1` for HD derivation. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
- **MUST** concatenate hashes with root keys to prevent cross-layer key leakage. | ||||||
|
||||||
### **Compatibility Considerations** | ||||||
|
||||||
This specification is inspired by BIP-44 (`m/purpose'/coin_type'/account'/change/address_index`), but: | ||||||
|
||||||
- **SHALL NOT** use the `change` layer for Ethereum-based systems. | ||||||
- **MAY** extend the hierarchy beyond BIP-44’s 5-layer structure for organizational needs. | ||||||
|
||||||
## Rationale | ||||||
|
||||||
The scenarios for which the proposal applies are: | ||||||
|
||||||
1. **Company and Department Isolation**: Different subsidiaries within the group, as well as different departments within each subsidiary, can create isolated on-chain accounts. Enhanced derivation is used to isolate exposure risks. | ||||||
2. **Group Unified Management Authority**: The group administrator holds the master private key, which can derive all subsidiary private keys, granting the highest authority to view and initiate transactions across the entire group, facilitating unified management by the group administrator. | ||||||
3. **Shared Department Private Key**: If subsidiary A's administrator, Alice, needs to share accounts under subsidiary A with a new administrator, Bob, she only needs to share the master private key of subsidiary A. Accounts from various departments can then be derived from this key. | ||||||
4. **Shared Audit Public Key**: If the audit department needs to audit transactions under a specific department, the extended public key of the specified department can be shared with the audit department. Through this extended public key, all subordinate public keys under the department can be derived, allowing the audit department to track all transactions associated with these public key addresses. | ||||||
|
||||||
## Backwards Compatibility | ||||||
|
||||||
This standard complies with BIP39、BIP32、BIP44. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## Reference Implementation | ||||||
|
||||||
```python | ||||||
""" | ||||||
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) | ||||||
``` | ||||||
|
||||||
run script: | ||||||
|
||||||
```shell | ||||||
pip install bip32utils eth_account | ||||||
|
||||||
python stms.py | ||||||
``` | ||||||
|
||||||
output: | ||||||
|
||||||
 | ||||||
|
||||||
## Security Considerations | ||||||
|
||||||
For treasury managers, hierarchical deterministic wallet management is more convenient, but it requires additional consideration of protective measures for the master key, such as schemes for splitting and storing mnemonic phrases or master keys. | ||||||
|
||||||
|
||||||
## Copyright | ||||||
|
||||||
Copyright and related rights waived via [CC0](../LICENSE.md). |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section of your description is basically just restating your title, which is an inefficient use of the limited space in the description field. I'd remove it.
Instead, you might consider expanding "HD" or perhaps describing a use case.