#!/usr/bin/env python3 """ Simple wallet operations for AITBC blockchain Compatible with existing keystore structure """ import json import sys import os import argparse from pathlib import Path from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives import hashes import requests from typing import Optional # Default paths DEFAULT_KEYSTORE_DIR = Path("/var/lib/aitbc/keystore") DEFAULT_RPC_URL = "http://localhost:8006" def decrypt_private_key(keystore_path: Path, password: str) -> str: """Decrypt private key from keystore file""" with open(keystore_path) as f: ks = json.load(f) crypto = ks['crypto'] salt = bytes.fromhex(crypto['kdfparams']['salt']) kdf = PBKDF2HMAC(hashes.SHA256(), 32, salt, crypto['kdfparams']['c']) 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() def create_wallet(name: str, password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> str: """Create a new wallet""" keystore_dir.mkdir(parents=True, exist_ok=True) # Generate new key pair private_key = ed25519.Ed25519PrivateKey.generate() private_key_hex = private_key.private_bytes_raw().hex() public_key = private_key.public_key() public_key_hex = public_key.public_bytes_raw().hex() # Calculate address (simplified - in real implementation this would be more complex) address = f"ait1{public_key_hex[:40]}" # Encrypt private key salt = os.urandom(32) kdf = PBKDF2HMAC(hashes.SHA256(), 32, salt, 100000) key = kdf.derive(password.encode()) aesgcm = AESGCM(key) nonce = os.urandom(12) ciphertext = aesgcm.encrypt(nonce, bytes.fromhex(private_key_hex), None) # Create keystore file keystore_data = { "address": address, "public_key": public_key_hex, "crypto": { "kdf": "pbkdf2", "kdfparams": { "salt": salt.hex(), "c": 100000, "dklen": 32, "prf": "hmac-sha256" }, "cipher": "aes-256-gcm", "cipherparams": { "nonce": nonce.hex() }, "ciphertext": ciphertext.hex() }, "version": 1 } keystore_path = keystore_dir / f"{name}.json" with open(keystore_path, 'w') as f: json.dump(keystore_data, f, indent=2) print(f"Wallet created: {name}") print(f"Address: {address}") print(f"Keystore: {keystore_path}") return address def send_transaction(from_wallet: str, to_address: str, amount: float, fee: float, password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]: """Send transaction from one wallet to another""" # Get sender wallet info sender_keystore = keystore_dir / f"{from_wallet}.json" if not sender_keystore.exists(): print(f"Error: Wallet '{from_wallet}' not found") return None with open(sender_keystore) as f: sender_data = json.load(f) sender_address = sender_data['address'] # Decrypt private key try: private_key_hex = decrypt_private_key(sender_keystore, password) private_key = ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(private_key_hex)) except Exception as e: print(f"Error decrypting wallet: {e}") return None # Create transaction transaction = { "sender": sender_address, "recipient": to_address, "value": int(amount), "fee": int(fee), "nonce": 0, # In real implementation, get current nonce "type": "transfer", "payload": {} } # Sign transaction (simplified) message = json.dumps(transaction, sort_keys=True).encode() signature = private_key.sign(message) transaction["signature"] = signature.hex() # Submit transaction try: response = requests.post(f"{rpc_url}/sendTx", json=transaction) if response.status_code == 200: result = response.json() print(f"Transaction submitted successfully") print(f"From: {sender_address}") print(f"To: {to_address}") print(f"Amount: {amount} AIT") print(f"Fee: {fee} AIT") return result.get("hash") else: print(f"Error submitting transaction: {response.text}") return None except Exception as e: print(f"Error: {e}") return None def list_wallets(keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> list: """List all wallets""" wallets = [] if keystore_dir.exists(): for wallet_file in keystore_dir.glob("*.json"): try: with open(wallet_file) as f: data = json.load(f) wallets.append({ "name": wallet_file.stem, "address": data["address"], "file": str(wallet_file) }) except Exception: pass return wallets def main(): parser = argparse.ArgumentParser(description="AITBC Wallet CLI") subparsers = parser.add_subparsers(dest="command", help="Available commands") # Create wallet command create_parser = subparsers.add_parser("create", help="Create a new wallet") create_parser.add_argument("--name", required=True, help="Wallet name") create_parser.add_argument("--password", help="Wallet password") create_parser.add_argument("--password-file", help="File containing wallet password") # Send transaction command send_parser = subparsers.add_parser("send", help="Send AIT") send_parser.add_argument("--from", required=True, dest="from_wallet", help="From wallet name") send_parser.add_argument("--to", required=True, dest="to_address", help="To address") send_parser.add_argument("--amount", type=float, required=True, help="Amount to send") send_parser.add_argument("--fee", type=float, default=10.0, help="Transaction fee") send_parser.add_argument("--password", help="Wallet password") send_parser.add_argument("--password-file", help="File containing wallet password") send_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") # List wallets command list_parser = subparsers.add_parser("list", help="List wallets") list_parser.add_argument("--format", choices=["table", "json"], default="table", help="Output format") args = parser.parse_args() if args.command == "create": # Get password password = None if args.password: password = args.password elif args.password_file: with open(args.password_file) as f: password = f.read().strip() else: import getpass password = getpass.getpass("Enter wallet password: ") if not password: print("Error: Password is required") sys.exit(1) address = create_wallet(args.name, password) print(f"Wallet address: {address}") elif args.command == "send": # Get password password = None if args.password: password = args.password elif args.password_file: with open(args.password_file) as f: password = f.read().strip() else: import getpass password = getpass.getpass(f"Enter password for wallet '{args.from_wallet}': ") if not password: print("Error: Password is required") sys.exit(1) tx_hash = send_transaction( args.from_wallet, args.to_address, args.amount, args.fee, password, rpc_url=args.rpc_url ) if tx_hash: print(f"Transaction hash: {tx_hash}") else: sys.exit(1) elif args.command == "list": wallets = list_wallets() if args.format == "json": print(json.dumps(wallets, indent=2)) else: print("Wallets:") for wallet in wallets: print(f" {wallet['name']}: {wallet['address']}") else: parser.print_help() if __name__ == "__main__": main()