-
-
Save Aqcurate/1759ad80cf1d443d23a20fb7e012d38d to your computer and use it in GitHub Desktop.
Escrow, Ransom, and Registry Contracts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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