Some checks failed
CLI Tests / test-cli (push) Failing after 4s
Deploy to Testnet / deploy-testnet (push) Successful in 1m40s
Documentation Validation / validate-docs (push) Failing after 12s
Documentation Validation / validate-policies-strict (push) Successful in 4s
Integration Tests / test-service-integration (push) Successful in 2m42s
Package Tests / Python package - aitbc-agent-sdk (push) Failing after 34s
Package Tests / Python package - aitbc-core (push) Successful in 27s
Package Tests / Python package - aitbc-crypto (push) Successful in 13s
Package Tests / Python package - aitbc-sdk (push) Successful in 16s
Package Tests / JavaScript package - aitbc-sdk-js (push) Successful in 8s
Package Tests / JavaScript package - aitbc-token (push) Successful in 18s
Python Tests / test-python (push) Failing after 50s
Security Scanning / security-scan (push) Failing after 43s
Multi-Node Stress Testing / stress-test (push) Successful in 12s
Cross-Node Transaction Testing / transaction-test (push) Successful in 9s
- Created aitbc/_version.py with centralized version definition - Updated aitbc/__init__.py to import __version__ from _version module - Updated constants.py to use __version__ for PACKAGE_VERSION - Replaced print() calls with logger in decorators.py, events.py, queue_manager.py, and state.py - Added logger initialization using get_logger(__name__) in config.py, decorators.py, events.py, queue_manager.py, and state.py - Added cli/commands
72 lines
2.4 KiB
Python
72 lines
2.4 KiB
Python
"""
|
|
Wallet utility functions for AITBC CLI
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import hashlib
|
|
import base64
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
from cryptography.hazmat.primitives import hashes
|
|
from cryptography.hazmat.backends import default_backend
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
|
|
|
def decrypt_private_key(keystore_path: Path, password: str) -> str:
|
|
"""Decrypt private key from keystore file.
|
|
|
|
Supports both keystore formats:
|
|
- AES-256-GCM (blockchain-node standard)
|
|
- Fernet (scripts/utils standard)
|
|
"""
|
|
with open(keystore_path) as f:
|
|
ks = json.load(f)
|
|
|
|
crypto = ks.get('crypto', ks) # Handle both nested and flat crypto structures
|
|
|
|
# Detect encryption method
|
|
cipher = crypto.get('cipher', crypto.get('algorithm', ''))
|
|
|
|
if cipher == 'aes-256-gcm' or cipher == 'aes-256-gcm':
|
|
# AES-256-GCM (blockchain-node standard)
|
|
salt = bytes.fromhex(crypto['kdfparams']['salt'])
|
|
kdf = PBKDF2HMAC(
|
|
algorithm=hashes.SHA256(),
|
|
length=32,
|
|
salt=salt,
|
|
iterations=crypto['kdfparams']['c'],
|
|
backend=default_backend()
|
|
)
|
|
key = kdf.derive(password.encode())
|
|
aesgcm = AESGCM(key)
|
|
nonce = bytes.fromhex(crypto['cipherparams']['nonce'])
|
|
priv = aesgcm.decrypt(nonce, bytes.fromhex(crypto['ciphertext']), None)
|
|
return priv.hex()
|
|
|
|
elif cipher == 'fernet' or cipher == 'PBKDF2-SHA256-Fernet':
|
|
# Fernet (scripts/utils standard)
|
|
from cryptography.fernet import Fernet
|
|
|
|
# Derive Fernet key using the same method as scripts/utils/keystore.py
|
|
kdfparams = crypto.get('kdfparams', {})
|
|
if 'salt' in kdfparams:
|
|
salt = base64.b64decode(kdfparams['salt'])
|
|
else:
|
|
# Fallback for older format
|
|
salt = bytes.fromhex(kdfparams.get('salt', ''))
|
|
|
|
# Use PBKDF2 for secure key derivation (100,000 iterations for security)
|
|
dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000, dklen=32)
|
|
fernet_key = base64.urlsafe_b64encode(dk)
|
|
|
|
f = Fernet(fernet_key)
|
|
ciphertext = base64.b64decode(crypto['ciphertext'])
|
|
priv = f.decrypt(ciphertext)
|
|
return priv.decode()
|
|
|
|
else:
|
|
raise ValueError(f"Unsupported cipher: {cipher}")
|