From e08d7c5597a9e3d8c1304e17b49058a0b07c105a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E6=BA=90?= <1171472032@qq.com> Date: Fri, 27 Jun 2025 21:58:19 +0800 Subject: [PATCH] access_control_security --- .../community/dac_attr_security/DAC/DAC.py | 234 +++++++++++ .../DAC/chainEnv/brownie-config.yaml | 13 + .../contracts/AttributeBasedAccessControl.sol | 155 ++++++++ .../dac_attr_security/DAC/interact.py | 374 ++++++++++++++++++ .../dac_attr_security/DAC/requirements.txt | 14 + .../community/dac_attr_security/README.md | 50 +++ .../dac_attr_security/dataset/gen_dataset.py | 28 ++ .../dac_attr_security/model/access_control.py | 227 +++++++++++ 8 files changed, 1095 insertions(+) create mode 100644 examples/community/dac_attr_security/DAC/DAC.py create mode 100644 examples/community/dac_attr_security/DAC/chainEnv/brownie-config.yaml create mode 100644 examples/community/dac_attr_security/DAC/chainEnv/contracts/AttributeBasedAccessControl.sol create mode 100644 examples/community/dac_attr_security/DAC/interact.py create mode 100644 examples/community/dac_attr_security/DAC/requirements.txt create mode 100644 examples/community/dac_attr_security/README.md create mode 100644 examples/community/dac_attr_security/dataset/gen_dataset.py create mode 100644 examples/community/dac_attr_security/model/access_control.py diff --git a/examples/community/dac_attr_security/DAC/DAC.py b/examples/community/dac_attr_security/DAC/DAC.py new file mode 100644 index 0000000..0291be1 --- /dev/null +++ b/examples/community/dac_attr_security/DAC/DAC.py @@ -0,0 +1,234 @@ +import json +import os +from web3 import Web3 +from interact import chainProxy + +def run_DAC(client_num=10): + if os.path.exists('./static/DAC/data.json'): + os.remove('./static/DAC/data.json') + + # Instantiate the chainProxy + proxy = chainProxy() + + # Store registration information for all clients + clients_info = [] + + for _ in range(client_num): + # Register the client and get transaction information + tx_info = proxy.client_regist() + + # Get user information after registration + registered, data_volume, training_ability, ban_count, public_key = proxy.networkmanage_proxy.getUserInfo(proxy.client_list[str(tx_info['client_num'])]) + + # Construct the client registration information dictionary + client_info = { + "client_id": str(tx_info['client_num']), + "public_key": public_key, + "registration": { + "success": registered, + "tx_hash": tx_info['交易哈希'], + "gas_used": tx_info['Gas 消耗'], + "block_number": tx_info['最新区块']['区块号'], + "timestamp": tx_info['最新区块']['时间戳'], + "miner": tx_info['最新区块']['矿工地址'], + "parent_hash": Web3.toHex(tx_info['最新区块']['父区块哈希']), # Replace toHex with to_hex + "block_hash": Web3.toHex(tx_info['最新区块']['区块哈希']), + "block_size": tx_info['最新区块']['区块大小'] + }, + "user_info": { + "data_volume": data_volume, + "training_ability": training_ability, + "ban_count": ban_count + } + } + + # Add to the client information list + clients_info.append(client_info) + + # Print the public and private keys of all clients + proxy.print_clients_info() + + # Identity authentication phase: Check if the original public key of the account matches the public key obtained through ecrecover from the signature result. + while True: + client_id = input("Please enter the client ID for identity authentication: ") + private_key = input("Please enter the private key of this client: ") + + # Get the public key (address) of the corresponding client from clients_info + user_address = next((client['public_key'] for client in clients_info if client['client_id'] == client_id), None) + message = f"Authentication for client {client_id}" # Define the message content + # Call the authenticate_user method + auth_result, block_info = proxy.authenticate_user(user_address, message, private_key) + + # Output the authentication result + if auth_result: + print(f"Authentication successful: Client {client_id}") + print(f"Block information: {block_info}") + else: + print(f"Authentication failed: Client {client_id}") + + # Update the dictionary + for client in clients_info: + if client['client_id'] == client_id: + client['authentication'] = { # Add an authentication field + "success": auth_result, # Whether the verification is successful + "block_info": { # The latest block + "gas_used": block_info['gasUsed'], + "block_number": block_info['number'], + "timestamp": block_info['timestamp'], + "miner": block_info['miner'], + "parent_hash": Web3.toHex(block_info['parentHash']), + "block_hash": Web3.toHex(block_info['hash']), + "block_size": block_info['size'] + } + } + break + + # Ask if you want to continue + cont = input("Do you want to continue authenticating the next client? (y/n): ") + if cont.lower() == 'n': + break + + # Set default attributes for each client + proxy.set_default_attributes_on_chain(clients_info) # Set default attributes for each client and pass them to the smart contract + proxy.print_all_clients_attributes(clients_info) + + # Attribute management phase + while True: + client_number = input("Please enter the user number whose attributes you want to update: ") + user_address = proxy.public_keys[client_number] + + # Get all attribute values to be updated + new_data_volume = int(input("Please enter the new data volume: ")) + new_training_ability = int(input("Please enter the new training ability: ")) + new_ban_count = int(input("Please enter the new ban count: ")) + + # Call the method in chainProxy to update on-chain data + tx_data_volume, tx_training_ability, tx_ban_count = proxy.update_user_attributes(user_address, new_data_volume, new_training_ability, new_ban_count) + + # Update the local dictionary + for client in clients_info: + if client['client_id'] == client_number: + client['user_info']['data_volume'] = new_data_volume + client['user_info']['training_ability'] = new_training_ability + client['user_info']['ban_count'] = new_ban_count + client['attributes_block_info'] = { + "tx_data_volume": { + "tx_hash": tx_data_volume['tx_hash'], + "gas_used": tx_data_volume['gas_used'], + "block_number": tx_data_volume['latest_block']['number'], + "timestamp": tx_data_volume['latest_block']['timestamp'], + "miner": tx_data_volume['latest_block']['miner'], + "parent_hash": Web3.toHex(tx_data_volume['latest_block']['parentHash']), + "block_hash": Web3.toHex(tx_data_volume['latest_block']['hash']), + "block_size": tx_data_volume['latest_block']['size'] + }, + "tx_training_ability": { + "tx_hash": tx_training_ability['tx_hash'], + "gas_used": tx_training_ability['gas_used'], + "block_number": tx_training_ability['latest_block']['number'], + "timestamp": tx_training_ability['latest_block']['timestamp'], + "miner": tx_training_ability['latest_block']['miner'], + "parent_hash": Web3.toHex(tx_training_ability['latest_block']['parentHash']), + "block_hash": Web3.toHex(tx_training_ability['latest_block']['hash']), + "block_size": tx_training_ability['latest_block']['size'] + }, + "tx_ban_count": { + "tx_hash": tx_ban_count['tx_hash'], + "gas_used": tx_ban_count['gas_used'], + "block_number": tx_ban_count['latest_block']['number'], + "timestamp": tx_ban_count['latest_block']['timestamp'], + "miner": tx_ban_count['latest_block']['miner'], + "parent_hash": Web3.toHex(tx_ban_count['latest_block']['parentHash']), + "block_hash": Web3.toHex(tx_ban_count['latest_block']['hash']), + "block_size": tx_ban_count['latest_block']['size'] + } + } + break + + # Ask if you want to continue updating the next client + continue_auth = input("Do you want to continue updating the next client? (y/n): ").strip().lower() + if continue_auth == 'n': + print("Attribute update phase ended.") + break + + # Attribute query + while True: + client_id_to_query = input("Please enter the client ID you want to query: ") + user_address_to_query = proxy.public_keys.get(client_id_to_query, None) + if user_address_to_query: + user_info = proxy.get_user_info(user_address_to_query) + print(f"Information of client {client_id_to_query}: {user_info}") + else: + print(f"Client {client_id_to_query} not found.") + + cont_query = input("Do you want to query the next client? (y/n): ") + if cont_query.lower() == 'n': + break + + # Permission evaluation + while True: + client_id_to_evaluate = input("Please enter the client ID for permission evaluation: ") + user_address_to_evaluate = proxy.public_keys.get(client_id_to_evaluate, None) + + if user_address_to_evaluate: + # Call permission evaluation + user_info = proxy.get_user_info(user_address_to_query) + permission_result = proxy.can_perform_action(user_address_to_evaluate, user_info['data_volume'], user_info['training_ability'], user_info['ban_count']) + print(f"Permission evaluation result of client {client_id_to_evaluate}: {permission_result}") + + # Add the permission evaluation result to the dictionary + for client in clients_info: + if client['client_id'] == client_id_to_evaluate: + client['permission_evaluation'] = { + "permission_result": permission_result + } + break + + # If the permission fails, update the ban count + if not permission_result: + print(f"Client {client_id_to_evaluate} failed the permission check. Increasing the ban count.") + ban_update_info = proxy.update_ban_count(client_id_to_evaluate) + if ban_update_info: + print(f"Ban count updated successfully.") + # Update the ban count in the local dictionary + for client in clients_info: + if client['client_id'] == client_id_to_evaluate: + client['user_info']['ban_count'] += 1 + # Update the block information of the ban count + client['ban_count_block_info'] = { + "tx_hash": ban_update_info['tx_hash'], + "gas_used": ban_update_info['gas_used'], + "block_number": ban_update_info['latest_block']['number'], + "timestamp": ban_update_info['latest_block']['timestamp'], + "miner": ban_update_info['latest_block']['miner'], + "parent_hash": Web3.toHex(ban_update_info['latest_block']['parentHash']), + "block_hash": Web3.toHex(ban_update_info['latest_block']['hash']), + "block_size": ban_update_info['latest_block']['size'] + } + break + else: + print(f"Failed to update the ban count.") + else: + print(f"Client {client_id_to_evaluate} not found.") + + # Ask if you want to continue evaluating the next client + cont_eval = input("Do you want to evaluate the permissions of the next client? (y/n): ") + if cont_eval.lower() == 'n': + break + + # Construct the final result dictionary + result_dict = { + "status": "completed", + "clients": clients_info + } + + # save + output_dir = './static/DAC' + os.makedirs(output_dir, exist_ok=True) + with open('./static/DAC/data.json', 'w') as f: + json.dump(result_dict, f) + print('data.json has been already saved in cipher/static/DAC/data.json') + + +if __name__ == "__main__": + run_DAC() \ No newline at end of file diff --git a/examples/community/dac_attr_security/DAC/chainEnv/brownie-config.yaml b/examples/community/dac_attr_security/DAC/chainEnv/brownie-config.yaml new file mode 100644 index 0000000..1a6971d --- /dev/null +++ b/examples/community/dac_attr_security/DAC/chainEnv/brownie-config.yaml @@ -0,0 +1,13 @@ +# exclude SafeMath when calculating test coverage +# https://eth-brownie.readthedocs.io/en/v1.10.3/config.html#exclude_paths +reports: + exclude_contracts: + - SafeMath + +compiler: + solc: + version: "0.8.6" + +networks: + ganache-local: + host: "http://127.0.0.1:7545" diff --git a/examples/community/dac_attr_security/DAC/chainEnv/contracts/AttributeBasedAccessControl.sol b/examples/community/dac_attr_security/DAC/chainEnv/contracts/AttributeBasedAccessControl.sol new file mode 100644 index 0000000..b669713 --- /dev/null +++ b/examples/community/dac_attr_security/DAC/chainEnv/contracts/AttributeBasedAccessControl.sol @@ -0,0 +1,155 @@ +pragma solidity ^0.8.6; + +contract AttributeBasedAccessControl { + // Enumerate user roles + enum Role { None, Trainer, Evaluator, Admin } + + // Define the User structure + struct User { + bool registered; + Role role; + uint reputation; // Reputation + uint dataVolume; // Data volume + uint trainingAbility; // Training ability + uint banCount; // Ban count + address publicKey; // User's public key + } + + // Mapping from address to User + mapping(address => User) private users; + + // Event: User registered + event UserRegistered(address indexed user, address publicKey); + // Event: Role assigned to user + event RoleAssigned(address indexed user, Role role); + // Event: Role revoked from user + event RoleRevoked(address indexed user, Role role); + // Event: User reputation updated + event ReputationUpdated(address indexed user, uint newReputation); + // Event: User data volume updated + event DataVolumeUpdated(address indexed user, uint newDataVolume); + // Event: User training ability updated + event TrainingAbilityUpdated(address indexed user, uint newTrainingAbility); + // Event: User ban count updated + event BanCountUpdated(address indexed user, uint newBanCount); + // Declare events + // event SignerRecovered(address indexed signer, bytes32 message); + // Newly declared event + // event MessageReceived(bytes message); + + // Constructor: Set the deployer as an admin + constructor() { + // The deployer is set as an admin + address deployer = msg.sender; + users[deployer] = User({ + registered: true, + role: Role.Admin, + reputation: 0, + dataVolume: 0, + trainingAbility: 0, + banCount: 0, + publicKey: deployer + }); + emit UserRegistered(deployer, deployer); + emit RoleAssigned(deployer, Role.Admin); + } + + // Register a user and submit the public key + function register(address publicKey) public { + require(!users[msg.sender].registered, "User already registered"); + users[msg.sender] = User({ + registered: true, + role: Role.None, + reputation: 0, + dataVolume: 0, + trainingAbility: 0, + banCount: 0, + publicKey: publicKey + }); + emit UserRegistered(msg.sender, publicKey); + } + + // Assign a role to a user + function assignRole(address user, Role role) public { + require(users[msg.sender].role == Role.Admin, "Only admin can assign roles"); + require(users[user].registered, "User not registered"); + users[user].role = role; + emit RoleAssigned(user, role); + } + + // Revoke a user's role + function revokeRole(address user) public { + require(users[msg.sender].role == Role.Admin, "Only admin can revoke roles"); + require(users[user].registered, "User not registered"); + Role previousRole = users[user].role; + users[user].role = Role.None; + emit RoleRevoked(user, previousRole); + } + + // Update user attributes + function updateReputation(address user, uint newReputation) public { + require(users[msg.sender].role == Role.Admin, "Only admin can update reputation"); + require(users[user].registered, "User not registered"); + users[user].reputation = newReputation; + emit ReputationUpdated(user, newReputation); + } + + function updateDataVolume(address user, uint newDataVolume) public { + require(users[msg.sender].role == Role.Admin, "Only admin can update data volume"); + require(users[user].registered, "User not registered"); + users[user].dataVolume = newDataVolume; + emit DataVolumeUpdated(user, newDataVolume); + } + + function updateTrainingAbility(address user, uint newTrainingAbility) public { + require(users[msg.sender].role == Role.Admin, "Only admin can update training ability"); + require(users[user].registered, "User not registered"); + users[user].trainingAbility = newTrainingAbility; + emit TrainingAbilityUpdated(user, newTrainingAbility); + } + + function updateBanCount(address user, uint newBanCount) public { + require(users[msg.sender].role == Role.Admin, "Only admin can update ban count"); + require(users[user].registered, "User not registered"); + users[user].banCount = newBanCount; + emit BanCountUpdated(user, newBanCount); + } + + // // Query user information + // function getUserInfo(address user) public view returns (bool registered, Role role, uint reputation, uint dataVolume, uint trainingAbility, uint banCount, address publicKey) { + // User memory u = users[user]; + // return (u.registered, u.role, u.reputation, u.dataVolume, u.trainingAbility, u.banCount, u.publicKey); + // } + + // Query user information + function getUserInfo(address user) public view returns (bool registered, uint dataVolume, uint trainingAbility, uint banCount, address publicKey) { + User memory u = users[user]; + return (u.registered, u.dataVolume, u.trainingAbility, u.banCount, u.publicKey); + } + + // Check user permission + function canPerformAction(address user) public view returns (bool) { + User storage u = users[user]; + + // Check if the user is registered and meets the authorization rules + return u.registered && u.dataVolume > 5000 && u.trainingAbility > 20 && u.banCount < 1; + } + + // Verify user signature (identity authentication) + function verifySignature( + address user, + bytes32 messageHash, + uint8 v, + bytes32 r, + bytes32 s + ) public view returns (bool) { + require(users[user].registered, "User not registered"); + + // Use ecrecover to recover the signer's public key + address signer = ecrecover(messageHash, v, r, s); + + // Check if the signer matches the user's public key + return (signer == users[user].publicKey); + } + +} \ No newline at end of file diff --git a/examples/community/dac_attr_security/DAC/interact.py b/examples/community/dac_attr_security/DAC/interact.py new file mode 100644 index 0000000..ebc5fb4 --- /dev/null +++ b/examples/community/dac_attr_security/DAC/interact.py @@ -0,0 +1,374 @@ +from collections import defaultdict +from web3 import Web3 +from brownie import * +from brownie import network, project +from eth_account.messages import encode_defunct, _hash_eip191_message +import random +import logging + +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from model.access_control import AccessController +import mindspore +logger = logging.getLogger(__name__) + +# Chain initialization +p = project.load(project_path="./chainEnv", name="chainServer") +p.load_config() +from brownie.project.chainServer import * +network.connect('development') +server_accounts = accounts[0] +AttributeBasedAccessControl.deploy({'from': server_accounts}) + +# Utilities for blockchain +# The client communicates with the blockchain through chainProxy. + +class chainProxy(): + def __init__(self): + self.upload_params = None + self.account_num = len(accounts) - 1 + self.networkmanage_proxy = AttributeBasedAccessControl[-1] + self.server_accounts = accounts[0] + self.client_num = 0 + self.client_list = defaultdict(type(accounts[0].address)) + self.private_keys_dict = {} + self.public_keys = {} + + def get_account_num(self): + return self.account_num + + def get_client_num(self): + return self.client_num + + def get_client_list(self): + return self.client_list + + def add_account(self) -> str: + account = accounts.add() + self.account_num += 1 + return account.address + + def client_regist(self) -> str: + self.client_num += 1 + if self.account_num < self.client_num: + self.add_account() + + # Generate a new private key with good randomness + private_key = Web3.keccak(text=f"client_{self.client_num}_random_seed").hex() + self.private_keys_dict[str(self.client_num)] = private_key + + # Create an account using the generated private key + new_account = accounts.add(private_key) + self.client_list[str(self.client_num)] = new_account + + # Get the public key + public_key = new_account.address + self.public_keys[str(self.client_num)] = public_key + + # Register the client on the blockchain (contract) and pass in the public key + tx = self.networkmanage_proxy.register(public_key, {'from': new_account}) + tx.wait(1) + + print(f"Client {self.client_num} registered successfully with public key: {public_key}") + + tx_info = { + 'client_num': self.client_num, + 'Transaction Hash': tx.txid, + 'Gas Used': tx.gas_used, + 'Latest Block': { + 'Block Number': tx.block_number, + 'Timestamp': web3.eth.get_block(tx.block_number).timestamp, + 'Miner Address': web3.eth.get_block(tx.block_number).miner, + 'Parent Block Hash': web3.eth.get_block(tx.block_number).parentHash, + 'Block Hash': web3.eth.get_block(tx.block_number).hash, + 'Block Size': web3.eth.get_block(tx.block_number).size + } + } + + # Check if the user is registered successfully + registered, data_volume, training_ability, ban_count, public_key = self.networkmanage_proxy.getUserInfo(new_account) + if registered: + print(f"User is registered: Data Volume = {data_volume}, Training Ability = {training_ability}, Ban Count = {ban_count}") + else: + print("User registration failed") + + return tx_info + + def get_private_key(self, client_id: str) -> str: + # Return the private key of the given client ID + return self.private_keys_dict.get(client_id) + + def print_clients_info(self): + print("Client IDs, Public Keys, and Private Keys:") + for client_id in self.public_keys: + public_key = self.public_keys[client_id] + private_key = self.private_keys_dict.get(client_id, "Not Available") + print(f"Client ID: {client_id}, Public Key: {public_key}, Private Key: {private_key}") + + def assign_role(self): + ''' + Assign a role to a user. + :param user_address: User address. + :param role: The role to be assigned (integer). + ''' + # Get the user number and map it to the address + client_number = input("Please enter the user number to assign a role: ") + user = self.public_keys[client_number] + role = int(input("Please enter the role number (0: None, 1: Trainer, 2: Evaluator, 3: Admin): ")) + # Assign the role + tx = self.networkmanage_proxy.assignRole(user, role, {'from': self.server_accounts}) + tx.wait(1) + print(f"Role assigned successfully to {user}.") + # Check if the user role is assigned successfully + _, role, _, _, _, _, _ = self.networkmanage_proxy.getUserInfo(user) + print(f"The role of user {user} is: {role}") + + # Administrator revokes a role + def revoke_role(self): + # Get the user number and map it to the address + client_number = input("Please enter the user number to revoke a role: ") + user = self.public_keys[client_number] + + # Revoke the role + tx = self.networkmanage_proxy.revokeRole(user, {'from': self.server_accounts}) + tx.wait(1) + print(f"Role revoked for user: {user}") + + # Check if the user role is revoked + _, role, _, _, _, _, _ = self.networkmanage_proxy.getUserInfo(user) + print(f"The role of user {user} has been revoked, and the current role is: {role}") + + # Set attributes for each client and pass them to the smart contract + def set_default_attributes_on_chain(self, clients_info): + for client in clients_info: + new_data_volume = random.randint(1000, 10000) + new_training_ability = random.randint(1, 100) + new_ban_count = 0 + user_address = client['public_key'] + + self.update_user_attributes(user_address, new_data_volume, new_training_ability, new_ban_count) + # Update the attributes in client_info + client['user_info'] = { + "data_volume": new_data_volume, + "training_ability": new_training_ability, + "ban_count": new_ban_count + } + + print(f"Default attributes set for client {user_address}:") + print(f"Data Volume: {new_data_volume}, Training Ability: {new_training_ability}, Ban Count: {new_ban_count}") + + def update_user_attributes(self, user_address: str, new_data_volume: int, new_training_ability: int, new_ban_count: int): + """ + Update user attributes on the chain, including data volume, training ability, and ban count. + :param user_address: User address + :param new_data_volume: New data volume + :param new_training_ability: New training ability + :param new_ban_count: New ban count + """ + tx1 = self.networkmanage_proxy.updateDataVolume(user_address, new_data_volume, {'from': self.server_accounts}) + tx1.wait(1) + print(f"The data volume of user {user_address} has been updated to {new_data_volume}.") + tx1_info = { + 'client_num': self.client_num, + 'tx_hash': tx1.txid, + 'gas_used': tx1.gas_used, + 'latest_block': { + 'number': tx1.block_number, + 'timestamp': web3.eth.get_block(tx1.block_number).timestamp, + 'miner': web3.eth.get_block(tx1.block_number).miner, + 'parentHash': web3.eth.get_block(tx1.block_number).parentHash, + 'hash': web3.eth.get_block(tx1.block_number).hash, + 'size': web3.eth.get_block(tx1.block_number).size + } + } + + tx2 = self.networkmanage_proxy.updateTrainingAbility(user_address, new_training_ability, {'from': self.server_accounts}) + tx2.wait(1) + print(f"The training ability of user {user_address} has been updated to {new_training_ability}.") + tx2_info = { + 'client_num': self.client_num, + 'tx_hash': tx2.txid, + 'gas_used': tx2.gas_used, + 'latest_block': { + 'number': tx2.block_number, + 'timestamp': web3.eth.get_block(tx2.block_number).timestamp, + 'miner': web3.eth.get_block(tx2.block_number).miner, + 'parentHash': web3.eth.get_block(tx1.block_number).parentHash, + 'hash': web3.eth.get_block(tx2.block_number).hash, + 'size': web3.eth.get_block(tx2.block_number).size + } + } + + tx3 = self.networkmanage_proxy.updateBanCount(user_address, new_ban_count, {'from': self.server_accounts}) + tx3.wait(1) + print(f"The ban count of user {user_address} has been updated to {new_ban_count}.") + tx3_info = { + 'client_num': self.client_num, + 'tx_hash': tx3.txid, + 'gas_used': tx3.gas_used, + 'latest_block': { + 'number': tx3.block_number, + 'timestamp': web3.eth.get_block(tx3.block_number).timestamp, + 'miner': web3.eth.get_block(tx3.block_number).miner, + 'parentHash': web3.eth.get_block(tx1.block_number).parentHash, + 'hash': web3.eth.get_block(tx3.block_number).hash, + 'size': web3.eth.get_block(tx3.block_number).size + } + } + return tx1_info, tx2_info, tx3_info + + def update_ban_count(self, client_id: str): + """ + Update the ban count of a client by incrementing it by 1. + :param client_id: Client ID + """ + # Get the user address based on the client ID + user_address = self.public_keys.get(client_id, None) + + if not user_address: + print(f"Address not found for client {client_id}.") + return None + + try: + # Call getUserInfo to get user information + user_info = self.networkmanage_proxy.getUserInfo(user_address, {'from': self.server_accounts}) + current_ban_count = user_info[3] # Get the ban count + + # Update the ban count by incrementing it by 1 + new_ban_count = current_ban_count + 1 + + # Call the smart contract to update the ban count + tx = self.networkmanage_proxy.updateBanCount(user_address, new_ban_count, {'from': self.server_accounts}) + + # Wait for the transaction to be confirmed + tx.wait(1) + print(f"The ban count of user {user_address} has been updated to {new_ban_count}.") + + # Get the transaction receipt information + tx_info = { + 'client_num': client_id, + 'tx_hash': tx.txid, + 'gas_used': tx.gas_used, + 'latest_block': { + 'number': tx.block_number, + 'timestamp': web3.eth.get_block(tx.block_number).timestamp, + 'miner': web3.eth.get_block(tx.block_number).miner, + 'parentHash': web3.eth.get_block(tx.block_number).parentHash, + 'hash': web3.eth.get_block(tx.block_number).hash, + 'size': web3.eth.get_block(tx.block_number).size + } + } + + return tx_info + + except Exception as e: + print(f"Error updating ban count for {client_id}: {e}") + return None + + # Print the attribute list of all clients + def print_all_clients_attributes(self, clients_info): + print("\nAttribute list of all clients: ") + for client in clients_info: + print(f"Client {client['public_key']} - Data Volume: {client['user_info']['data_volume']}, " + f"Training Ability: {client['user_info']['training_ability']}, Ban Count: {client['user_info']['ban_count']}") + + def authenticate_user(self, user_address: str, message: bytes, private_key: str): # Ensure that the message is signed by the private key of the specified user through signature verification + ''' + Verify the user's identity by checking the signature. + :param user_address: The address of the user to be authenticated. + :param message: The hash of the message signed by the user. + :param signature: The signature generated by the user using the private key. + :return: Return True if the signature is valid, otherwise return False. + ''' + # Encode the message to generate a signable message object + message_encoded = encode_defunct(text=message) + + # Generate the message hash + # message_hash = Web3.keccak(message_encoded.body) + message_hash = _hash_eip191_message(message_encoded) + hex_message_hash = Web3.toHex(message_hash) + + # Sign the message using the private key + signed_message = web3.eth.account.sign_message(message_encoded, private_key) + print(f"Signed message: {signed_message}") + # Convert to a 32-byte hexadecimal string + r_hex = Web3.toHex(Web3.toBytes(signed_message.r).rjust(32, b'\0')).zfill(64) + s_hex = Web3.toHex(Web3.toBytes(signed_message.s).rjust(32, b'\0')).zfill(64) + result = self.networkmanage_proxy.verifySignature(user_address, hex_message_hash, signed_message.v, r_hex, s_hex, {'from': self.server_accounts}) # {'from': user_address}, returns True/False + + # Get the latest block information + latest_block = web3.eth.get_block('latest') + latest_block_dict = dict(latest_block) # Convert to a normal dictionary + # result.wait(1) + # result = self.networkmanage_proxy.verifySignature(user_address, message.encode('utf-8'), signed_message.signature, {'from': self.server_accounts}) # {'from': user_address} + # Directly check the value of result + if result: + print(f"User {user_address} authenticated successfully!") + else: + print(f"User {user_address} authentication failed!") + return result, latest_block_dict # Todo: Why return latest_block_dict at the end? Is the call to this authentication contract packed into this latest block? + + def get_user_info(self, user_address: str): + """ + Call the getUserInfo method of the smart contract to get user information. + + :param user_address: The address of the user to be queried + :return: An information dictionary containing registration status, data volume, training ability, ban count, and public key + """ + try: + result = self.networkmanage_proxy.getUserInfo(user_address, {'from': self.server_accounts}) + user_info = { + "registered": result[0], + "data_volume": result[1], + "training_ability": result[2], + "ban_count": result[3], + "public_key": result[4] + } + return user_info + except Exception as e: + print(f"Error fetching user info for {user_address}: {e}") + return None + + def can_perform_action(self, user_address: str, a: int, b: int, c: int): + """ + Call the canPerformAction method of the smart contract to check if the user has permission. + + :param user_address: The address of the user to check permissions + :return: Return True if the user has permission to perform the action, otherwise return False + """ + try: + has_permission = self.networkmanage_proxy.canPerformAction(user_address) + + model = AccessController() + param_dict = mindspore.load_checkpoint("../dataset/access_controler.ckpt") + mindspore.load_param_into_net(model, param_dict) + + input_data = mindspore.Tensor([[a, b, c]], dtype=mindspore.float32) + + output = model(input_data) + prediction = (output.squeeze() > 0.5).astype(mindspore.int32) + print(f"Prediction: {prediction.asnumpy()}") + + has_permission = prediction.asnumpy() + return bool(has_permission) + except Exception as e: + print(f"Error checking permissions for {user_address}: {e}") + return False # Return False when an exception occurs + + def get_public_key(self, client_id: str) -> str: + ''' Get the public key of the specified client ''' + return self.public_keys.get(client_id, None) + + def ecrecover(self, message: str, signature: str) -> str: + """ + Recover the signer's address using ECDSA. + :param message: The message to recover the signer from + :return: The recovered signer address + """ + + # Recover the address using ecrecover + signer = web3.eth.account.recover_message(encode_defunct(text=message), signature=signature) + return signer + +chain_proxy = chainProxy() \ No newline at end of file diff --git a/examples/community/dac_attr_security/DAC/requirements.txt b/examples/community/dac_attr_security/DAC/requirements.txt new file mode 100644 index 0000000..eecd47d --- /dev/null +++ b/examples/community/dac_attr_security/DAC/requirements.txt @@ -0,0 +1,14 @@ +# Ethereum Development Environment +eth-brownie==1.19.5 +web3==5.31.3 +# ganache==7.9.2 + +# Python Environment +mindspore==2.5.0 +numpy>=1.21.0 +pandas>=2.0.2 +scipy>=1.10.1 +matplotlib>=3.7.1 +flask>=2.3.2 +# python-dateutil>=2.8.2 +python-dateutil==2.8.1 diff --git a/examples/community/dac_attr_security/README.md b/examples/community/dac_attr_security/README.md new file mode 100644 index 0000000..6124430 --- /dev/null +++ b/examples/community/dac_attr_security/README.md @@ -0,0 +1,50 @@ + +# 基于MindSpore和区块链的属性安全访问控制 + +## 项目简介 + +本项目是一个基于 **MindSpore** 和 **区块链** 的属性安全访问控制模块。通过训练神经网络模型,结合区块链的可信防篡改特性,实现对用户访问权限的动态控制。 + +--- + +## 文件结构说明 + +``` +dac_attr_security/ +├── DAC/ # 动态访问控制模块 +│ ├── chainEnv/ # 区块链开发环境配置 +│ │ ├── contracts/ # 智能合约文件 +│ │ └── brownie-config.yaml # Brownie配置文件 +│ ├── interact.py # 与MindSpore模型及区块链交互的代理 +│ └── DAC.py # 主程序, 可直接运行 +├── dataset/ # 数据集生成模块 +│ └── gen_dataset.py # 生成训练和测试数据集 +└── model/ # 神经网络模型模块 + └── access_control.py # 基于MindSpore的访问控制模型 +``` + +--- + +## 依赖安装 + +在运行项目前,请确保使用 `requirements.txt` 安装依赖: + +```bash +pip install -r requirements.txt +``` + +--- + +## 功能说明 + +### 身份认证 + +支持通过数字签名对用户身份进行验证。用户需提供私钥对指定消息进行签名,系统通过智能合约验证签名的有效性,实现安全身份认证。 + +### 属性管理 + +可以动态更新用户的属性(如数据量、训练能力、禁用次数等),并通过智能合约同步到链上,确保用户属性数据安全。 + +### 权限评估 + +模型获取区块链上用户属性后,基于安全可信的属性值实现用户的访问权限动态评估。 \ No newline at end of file diff --git a/examples/community/dac_attr_security/dataset/gen_dataset.py b/examples/community/dac_attr_security/dataset/gen_dataset.py new file mode 100644 index 0000000..ad26c0c --- /dev/null +++ b/examples/community/dac_attr_security/dataset/gen_dataset.py @@ -0,0 +1,28 @@ +import random + +def generate_dataset(num_samples, file_path): + half_samples = num_samples // 2 + with open(file_path, 'w') as f: + + for _ in range(half_samples): + data_volume = random.randint(5001, 10000) + training_ability = random.randint(21, 100) + ban_count = 0 + label = 1 + line = f"{data_volume} {training_ability} {ban_count} {label}\n" + f.write(line) + + for _ in range(num_samples - half_samples): + data_volume = random.randint(1000, 10000) + training_ability = random.randint(1, 100) + ban_count = random.randint(0, 5) + if data_volume > 5000 and training_ability > 20 and ban_count == 0: + label = 1 + else: + label = 0 + line = f"{data_volume} {training_ability} {ban_count} {label}\n" + f.write(line) + +generate_dataset(20000, 'train.txt') + +generate_dataset(2000, 'test.txt') \ No newline at end of file diff --git a/examples/community/dac_attr_security/model/access_control.py b/examples/community/dac_attr_security/model/access_control.py new file mode 100644 index 0000000..130d736 --- /dev/null +++ b/examples/community/dac_attr_security/model/access_control.py @@ -0,0 +1,227 @@ +import mindspore +from mindspore import nn, save_checkpoint +from mindspore.dataset import NumpySlicesDataset +import numpy as np +from sklearn.metrics import precision_score, recall_score, f1_score +import matplotlib.pyplot as plt +import logging + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +def load_data(file_path): + data = [] + labels = [] + with open(file_path, 'r') as f: + for line in f.readlines(): + line = line.strip().split(' ') + data.append([int(x) for x in line[:3]]) + labels.append(int(line[3])) + data = np.array(data, dtype=np.float32) + labels = np.array(labels, dtype=np.int32) + return data, labels + +train_data, train_labels = load_data('../dataset/train.txt') +test_data, test_labels = load_data('../dataset/test.txt') + +train_dataset = NumpySlicesDataset((train_data, train_labels), column_names=['data', 'label']) +test_dataset = NumpySlicesDataset((test_data, test_labels), column_names=['data', 'label']) + +class AccessController(nn.Cell): + def __init__(self): + super(AccessController, self).__init__() + + self.fc1 = nn.Dense(3, 64) + self.bn1 = nn.BatchNorm1d(64) + self.relu1 = nn.ReLU() + self.dropout1 = nn.Dropout(0.2) + + self.fc2 = nn.Dense(64, 128) + self.bn2 = nn.BatchNorm1d(128) + self.relu2 = nn.ReLU() + self.dropout2 = nn.Dropout(0.2) + + self.fc3 = nn.Dense(128, 256) + self.bn3 = nn.BatchNorm1d(256) + self.relu3 = nn.ReLU() + self.dropout3 = nn.Dropout(0.2) + + self.fc4 = nn.Dense(256, 128) + self.bn4 = nn.BatchNorm1d(128) + self.relu4 = nn.ReLU() + self.dropout4 = nn.Dropout(0.2) + + self.fc5 = nn.Dense(128, 64) + self.bn5 = nn.BatchNorm1d(64) + self.relu5 = nn.ReLU() + self.dropout5 = nn.Dropout(0.2) + + self.fc6 = nn.Dense(64, 1) + self.sigmoid = nn.Sigmoid() + + def construct(self, x): + x = self.fc1(x) + x = self.bn1(x) + x = self.relu1(x) + x = self.dropout1(x) + + x = self.fc2(x) + x = self.bn2(x) + x = self.relu2(x) + x = self.dropout2(x) + + x = self.fc3(x) + x = self.bn3(x) + x = self.relu3(x) + x = self.dropout3(x) + + x = self.fc4(x) + x = self.bn4(x) + x = self.relu4(x) + x = self.dropout4(x) + + x = self.fc5(x) + x = self.bn5(x) + x = self.relu5(x) + x = self.dropout5(x) + + x = self.fc6(x) + x = self.sigmoid(x) + return x + +model = AccessController() + +loss_fn = nn.BCELoss() + +learning_rate = mindspore.nn.CosineDecayLR(min_lr=0.0001, max_lr=0.001, decay_steps=10 * len(train_dataset)) +optimizer = nn.Adam(model.trainable_params(), learning_rate=learning_rate) + +def train_step(data, label, gradient_accumulation_steps, accumulated_grads): + def forward_fn(data, label): + logits = model(data) + loss = loss_fn(logits.squeeze(), label.astype(mindspore.float32)) + return loss + grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters) + loss, grads = grad_fn(data, label) + + if gradient_accumulation_steps > 1: + for i in range(len(accumulated_grads)): + accumulated_grads[i] += grads[i] / gradient_accumulation_steps + return loss, accumulated_grads + else: + optimizer(grads) + return loss, None + +def evaluate(model, dataset): + all_predictions = [] + all_labels = [] + for data, label in dataset: + logits = model(data) + predictions = (logits.squeeze() > 0.5).astype(mindspore.int32).asnumpy() + all_predictions.extend(predictions) + all_labels.extend(label.asnumpy()) + + accuracy = np.mean(np.array(all_predictions) == np.array(all_labels)) + precision = precision_score(all_labels, all_predictions) + recall = recall_score(all_labels, all_predictions) + f1 = f1_score(all_labels, all_predictions) + + return accuracy, precision, recall, f1 + +num_epochs = 100 +gradient_accumulation_steps = 4 +early_stopping_patience = 3 +best_f1 = 0 +no_improvement_epochs = 0 + +train_losses = [] +train_accuracies = [] +train_precisions = [] +train_recalls = [] +train_f1s = [] +test_accuracies = [] +test_precisions = [] +test_recalls = [] +test_f1s = [] + +for epoch in range(num_epochs): + accumulated_loss = 0 + accumulated_grads = [mindspore.ops.ZerosLike()(p) for p in optimizer.parameters] + for step, (data, label) in enumerate(train_dataset): + loss, accumulated_grads = train_step(data, label, gradient_accumulation_steps, accumulated_grads) + accumulated_loss += loss.asnumpy() + + if (step + 1) % gradient_accumulation_steps == 0: + optimizer(accumulated_grads) + accumulated_grads = [mindspore.ops.ZerosLike()(p) for p in optimizer.parameters] + + average_loss = accumulated_loss / len(train_dataset) + train_losses.append(average_loss) + + train_accuracy, train_precision, train_recall, train_f1 = evaluate(model, train_dataset) + test_accuracy, test_precision, test_recall, test_f1 = evaluate(model, test_dataset) + + train_accuracies.append(train_accuracy) + train_precisions.append(train_precision) + train_recalls.append(train_recall) + train_f1s.append(train_f1) + test_accuracies.append(test_accuracy) + test_precisions.append(test_precision) + test_recalls.append(test_recall) + test_f1s.append(test_f1) + + logger.info(f'Epoch {epoch + 1}:') + logger.info(f'Train Loss: {average_loss}') + logger.info(f'Train Accuracy: {train_accuracy}, Train Precision: {train_precision}, Train Recall: {train_recall}, Train F1: {train_f1}') + logger.info(f'Test Accuracy: {test_accuracy}, Test Precision: {test_precision}, Test Recall: {test_recall}, Test F1: {test_f1}') + + if test_f1 > best_f1: + best_f1 = test_f1 + no_improvement_epochs = 0 + else: + no_improvement_epochs += 1 + + if no_improvement_epochs >= early_stopping_patience: + logger.info("Early stopping: No improvement in test F1 score for several epochs.") + break + +save_checkpoint(model, "access_controler.ckpt") +logger.info("Model weights saved as access_controler.ckpt") + +accuracy, precision, recall, f1 = evaluate(model, test_dataset) +logger.info(f'Final Test Accuracy: {accuracy}, Final Test Precision: {precision}, Final Test Recall: {recall}, Final Test F1: {f1}') + +plt.figure(figsize=(12, 8)) +plt.subplot(2, 2, 1) +plt.plot(train_losses, label='Train Loss') +plt.xlabel('Epoch') +plt.ylabel('Loss') +plt.title('Training Loss') +plt.legend() + +plt.subplot(2, 2, 2) +plt.plot(train_accuracies, label='Train Accuracy') +plt.plot(test_accuracies, label='Test Accuracy') +plt.xlabel('Epoch') +plt.ylabel('Accuracy') +plt.title('Accuracy') +plt.legend() + +plt.subplot(2, 2, 3) +plt.plot(train_precisions, label='Train Precision') +plt.plot(test_precisions, label='Test Precision') +plt.xlabel('Epoch') +plt.ylabel('Precision') +plt.title('Precision') +plt.legend() + +plt.subplot(2, 2, 4) +plt.plot(train_f1s, label='Train F1') +plt.plot(test_f1s, label='Test F1') +plt.xlabel('Epoch') +plt.ylabel('F1 Score') +plt.title('F1 Score') +plt.legend() + +plt.tight_layout() +plt.show() \ No newline at end of file -- Gitee