Suppose we have a scenario where we need to save a password in the smart contract. When the user calls the contract to deposit a certain amount of ether, the contract will inform the user of the password. This scenario is very simple, but it is not easy to implement, because the code of the smart contract is transparent to everyone, so it is easy to view the code and obtain the password. To solve this problem, we can introduce oracle. In the blockchain, smart contracts cannot directly interact with external systems. For example, in the classic application of a smart contract, two users bet on the results of the ball game and agree to pay the amount according to the results of the ball game. In this scenario, how does the smart contract know the external game results? At this time, a Oracle is needed as an intermediary to write the results of the ball game into the blockchain, and then the smart contract can be executed according to the results.
Back to our scenario, we also need a Oracle to save confidential information safely in a smart contract. The principle is to find a third-party trusted institution and ask it to provide a public key for encryption. The smart contract uses this public key to encrypt the password and save it in the contract code. When other users call the smart contract, they need to provide the user's own public key. The smart contract calls the Oracle contract issued by the third-party organization together with the encrypted password and the user's public key. After the Oracle contract is received, this information is written into the contract event. The third-party organization deploys a service to scan the account book of the blockchain, monitor the events sent by the Oracle contract, unlock the encrypted password with its private key, and then encrypt it with the user's public key. The encrypted password can be returned by calling the callback method of the smart contract, and the smart contract writes the password and the user's account address into the blockchain account book as an event. The user can get the password by monitoring the events of the smart contract and decrypt it with its private key. In the whole process, except for the third-party trusted organization and the user itself, no other user can obtain the password, even if the account book of the blockchain is transparent to all users.
The following is the code of smart contract:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; /** * @title Get Encrypted Password * @dev Store & retrieve password */ contract Getpass { address internal owner; string internal encrypted_password = "MQdnRwM8aSYyp+BdEP/MWGDnHsxcgpz1JFVKcUhca/BIFU5r3lCGa833ySE1vsBsLJ+edS7Iln5f6C73zThhsGuWzLhW2+jYGfbx1oQ5ELvsdAL9X9siZgsdOXGtCSk731eFxwNrbtJjCg/GMLdjLquZzPgtAMI5jJ/3JUaH4tI="; address oracle_address = 0x6f48774775241d29E4E66691855aF555D3E7ad61; uint256 amount=30000; event LogResult(Result); struct Result { address identifier; //Identify the data consumer string encrypted; //Encrypted data with identifier's pubkey } constructor() public { owner = msg.sender; oracle_address.call(abi.encodeWithSelector(bytes4(keccak256('register(string)')), encrypted_password)); } function deposit(string calldata publickey) payable public { require(msg.value==30000); oracle_address.call{gas:2100000}(abi.encodeWithSelector(bytes4(keccak256("requestEncrypt(string,string,address)")), publickey, encrypted_password, msg.sender)); } function getNewEncrypted(string calldata encrypted, address identifier) public { Result memory r = Result(identifier, encrypted); emit LogResult(r); } }
Contract code of Oracle:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; /** * @title Oracle * @dev Store & retrieve value in a variable */ contract Oracle { event LogRequest(RequestData); event LogRegister(string, address); struct RequestData { string pubkey; //the sender's pubkey for encryption string encryptdata; //the data encrypted use oracle's public key address requester; //The contract address that invoke the oracle address identifier; //Identify the data consumer } function register(string calldata encrypted_data) public { emit LogRegister(encrypted_data, msg.sender); } function requestEncrypt(string calldata pubkey, string calldata requestdata, address identifier) public { RequestData memory d = RequestData(pubkey, requestdata, msg.sender, identifier); emit LogRequest(d); } function callback(string calldata encrypted, address receiver, address identifier) public { receiver.call{gas:2100000}(abi.encodeWithSelector(bytes4(keccak256('getNewEncrypted(string,address)')), encrypted, identifier)); } }
Code of external service of Oracle
from web3 import Web3 import time import base64 from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA import pickle decrypt = '' account_address = '0x3312521D5892D45Eb23b38c95375D9376DCA8e0B' oracle_address = '0x6f48774775241d29E4E66691855aF555D3E7ad61' oracle_contract = '' w3 = '' def handle_logdata(logdata): global decrypt global account_address global oracle_contract global w3 logdata = logdata[2:] requester_address = Web3.toChecksumAddress(Web3.toHex(hexstr=logdata[(192+24):256])) identifier_address = Web3.toChecksumAddress(Web3.toHex(hexstr=logdata[(256+24):320])) publickey_len = Web3.toInt(hexstr=logdata[320:384])*2 publickey = Web3.toText(hexstr=logdata[384:(384+publickey_len)]) offset = 64-publickey_len%64 encrypted_len = Web3.toInt(hexstr=logdata[(384+publickey_len+offset):(384+publickey_len+offset+64)])*2 encrypted = Web3.toText(hexstr=logdata[(384+publickey_len+offset+64):(384+publickey_len+offset+64+encrypted_len)]) encrypted_bytes = base64.b64decode(encrypted) decrypted_message = decrypt.decrypt(encrypted_bytes) pu_key = RSA.import_key(publickey) cipher = PKCS1_OAEP.new(key=pu_key) cipher_text = base64.b64encode(cipher.encrypt(decrypted_message)) print("Requester:{}\nIdentifier:{}\nPublicKey:{}\nEncrypted:{}\nDecrypted:{}\nNew Encrypted:{}".format(requester_address, identifier_address, publickey, encrypted, decrypted_message, cipher_text)) w3.geth.personal.unlockAccount(account_address, 'abc12345') oracle_contract.functions.callback(cipher_text, requester_address, identifier_address).transact(transaction={'gas': 2100000}) def log_loop(event_filter, poll_interval): while True: for event in event_filter.get_new_entries(): if 'data' in event: handle_logdata(event['data']) time.sleep(poll_interval) def main(): global decrypt global account_address global oracle_address global oracle_contract global w3 with open('oracle_prikey.pem', 'r') as f: private_pem = f.read() pr_key = RSA.import_key(private_pem) decrypt = PKCS1_OAEP.new(key=pr_key) w3 = Web3(Web3.IPCProvider('data/geth.ipc')) w3.eth.default_account = account_address with open('oracle_abi', 'rb') as f: oracle_abi = pickle.loads(f.read()) oracle_contract = w3.eth.contract(address=oracle_address, abi=oracle_abi) block_filter = w3.eth.filter({'fromBlock':0, 'address':oracle_address}) log_loop(block_filter, 2) if __name__ == '__main__': main()