以太坊智能合约与预言机实现机密数据的传送

  • Post author:
  • Post category:其他


假设我们有一个场景是需要在智能合约里面保存一个密码,当用户调用合约存入一定数量的以太币,合约将把密码告知用户。这个场景很简单,但是实现起来却不太容易,因为智能合约的代码对所有人都是透明的,因此很容易就可以查看代码获取到密码。要解决这个问题,我们可以引入预言机(Oracle)。在区块链中,智能合约是无法直接与外部系统交互的,例如经典的一个智能合约的应用,两个用户打赌球赛结果,并约定按照球赛结果来支付金额。这个场景里面,智能合约如何获知外部的球赛结果呢?这时就需要一个预言机作为中介,把球赛的结果写入到区块链中,然后智能合约才能依据结果来执行。

回到我们的场景,要实现在智能合约里面安全的保存机密的信息,我们也需要预言机来实现。其原理是,找一个第三方受信任的机构,让其提供一个公钥用于加密。智能合约利用这个公钥把密码加密之后保存在合约代码中。当其他用户调用智能合约的时候,需要提供用户自己的公钥,智能合约把加密后的密码连同用户的公钥一起,去调用第三方机构发布的预言机合约。预言机合约收到之后,把这个信息写入到合约的事件中。第三方机构部署一个服务,扫描区块链的账簿,监控预言机合约所发出的事件,把加密过的密码用其私钥解开后,再用用户的公钥来加密。加密后的密码可以调用智能合约的回调方法传回,智能合约再把这个密码以及用户的账号地址作为事件写入到区块链账簿。用户监控智能合约的事件即可拿到密码,用其私钥即可解密。在这整个过程中,除了这个第三方受信任的机构以及用户本身,其他用户都无法获取密码,即使区块链的账簿对所有用户是透明的。

以下是智能合约的代码:

// 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);
    }
}

预言机的合约代码:

// 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));
    }
}

预言机的外部服务的代码

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()



版权声明:本文为gzroy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。