pragma solidity ^0.4.13;
library ECRecovery {
/**
* @dev Recover signer address from a message by using their signature
* @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
* @param sig bytes signature, the signature is generated using web3.eth.sign()
*/
function recover(bytes32 hash, bytes sig)
internal
pure
returns (address)
{
bytes32 r;
bytes32 s;
uint8 v;
// Check the signature length
if (sig.length != 65) {
return (address(0));
}
// Divide the signature in r, s and v variables
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solium-disable-next-line security/no-inline-assembly
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
if (v < 27) {
v += 27;
}
// If the version is correct return the signer address
if (v != 27 && v != 28) {
return (address(0));
} else {
// solium-disable-next-line arg-overflow
return ecrecover(hash, v, r, s);
}
}
/**
* toEthSignedMessageHash
* @dev prefix a bytes32 value with "\x19Ethereum Signed Message:"
* @dev and hash the result
*/
function toEthSignedMessageHash(bytes32 hash)
internal
pure
returns (bytes32)
{
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(
"\x19Ethereum Signed Message:\n32",
hash
);
}
}
contract DSMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x);
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x);
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x);
}
function min(uint x, uint y) internal pure returns (uint z) {
return x <= y ? x : y;
}
function max(uint x, uint y) internal pure returns (uint z) {
return x >= y ? x : y;
}
function imin(int x, int y) internal pure returns (int z) {
return x <= y ? x : y;
}
function imax(int x, int y) internal pure returns (int z) {
return x >= y ? x : y;
}
uint constant WAD = 10 ** 18;
uint constant RAY = 10 ** 27;
function wmul(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, y), WAD / 2) / WAD;
}
function rmul(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, y), RAY / 2) / RAY;
}
function wdiv(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, WAD), y / 2) / y;
}
function rdiv(uint x, uint y) internal pure returns (uint z) {
z = add(mul(x, RAY), y / 2) / y;
}
// This famous algorithm is called "exponentiation by squaring"
// and calculates x^n with x as fixed-point and n as regular unsigned.
//
// It's O(log n), instead of O(n) for naive repeated multiplication.
//
// These facts are why it works:
//
// If n is even, then x^n = (x^2)^(n/2).
// If n is odd, then x^n = x * x^(n-1),
// and applying the equation for even x gives
// x^n = x * (x^2)^((n-1) / 2).
//
// Also, EVM division is flooring and
// floor[(n-1) / 2] = floor[n / 2].
//
function rpow(uint x, uint n) internal pure returns (uint z) {
z = n % 2 != 0 ? x : RAY;
for (n /= 2; n != 0; n /= 2) {
x = rmul(x, x);
if (n % 2 != 0) {
z = rmul(z, x);
}
}
}
}
contract Htlc is DSMath {
using ECRecovery for bytes32;
// TYPES
// ATL Authority timelocked contract
struct Multisig { // Locked by authority approval (earlyResolve), time (timoutResolve) or conversion into an atomic swap
address owner; // Owns ether deposited in multisig
address authority; // Can approve earlyResolve of funds out of multisig
uint deposit; // Amount deposited by owner in this multisig
uint unlockTime; // Multisig expiration timestamp in seconds
}
struct AtomicSwap { // Locked by secret (regularTransfer) or time (reclaimExpiredSwaps)
bytes32 msigId; // Corresponding multisigId
address initiator; // Initiated this swap
address beneficiary; // Beneficiary of this swap
uint amount; // If zero then swap not active anymore
uint fee; // Fee amount to be paid to multisig authority
uint expirationTime; // Swap expiration timestamp in seconds
bytes32 hashedSecret; // sha256(secret), hashed secret of swap initiator
}
// FIELDS
address constant FEE_RECIPIENT = 0x478189a0aF876598C8a70Ce8896960500455A949;
uint constant MAX_BATCH_ITERATIONS = 25; // Assumption block.gaslimit around 7500000
mapping (bytes32 => Multisig) public multisigs;
mapping (bytes32 => AtomicSwap) public atomicswaps;
mapping (bytes32 => bool) public isAntecedentHashedSecret;
// EVENTS
event MultisigInitialised(bytes32 msigId);
event MultisigReparametrized(bytes32 msigId);
event AtomicSwapInitialised(bytes32 swapId);
// MODIFIERS
// METHODS
/**
@notice Send ether out of this contract to multisig owner and update or delete entry in multisig mapping
@param msigId Unique (owner, authority, balance != 0) multisig identifier
@param amount Spend this amount of ether
*/
function spendFromMultisig(bytes32 msigId, uint amount, address recipient)
internal
{
multisigs[msigId].deposit = sub(multisigs[msigId].deposit, amount);
if (multisigs[msigId].deposit == 0)
delete multisigs[msigId];
recipient.transfer(amount);
}
// PUBLIC METHODS
/**
@notice Initialise and reparametrize Multisig
@dev Uses msg.value to fund Multisig
@param authority Second multisig Authority. Usually this is the Exchange.
@param unlockTime Lock Ether until unlockTime in seconds.
@return msigId Unique (owner, authority, balance != 0) multisig identifier
*/
function initialiseMultisig(address authority, uint unlockTime)
public
payable
returns (bytes32 msigId)
{
// Require not own authority and non-zero ether amount are sent
require(msg.sender != authority);
require(msg.value > 0);
// Create unique multisig identifier
msigId = keccak256(
msg.sender,
authority,
msg.value,
unlockTime
);
emit MultisigInitialised(msigId);
// Create multisig
Multisig storage multisig = multisigs[msigId];
if (multisig.deposit == 0) { // New or empty multisig
// Create new multisig
multisig.owner = msg.sender;
multisig.authority = authority;
}
// Adjust balance and locktime
reparametrizeMultisig(msigId, unlockTime);
}
/**
@notice Inititate/extend multisig unlockTime and/or initiate/refund multisig deposit
@dev Can increase deposit and/or unlockTime but not owner or authority
@param msigId Unique (owner, authority, balance != 0) multisig identifier
@param unlockTime Lock Ether until unlockTime in seconds.
*/
function reparametrizeMultisig(bytes32 msigId, uint unlockTime)
public
payable
{
require(multisigs[msigId].owner == msg.sender);
Multisig storage multisig = multisigs[msigId];
multisig.deposit = add(multisig.deposit, msg.value);
assert(multisig.unlockTime <= unlockTime); // Can only increase unlockTime
multisig.unlockTime = unlockTime;
emit MultisigReparametrized(msigId);
}
/**
@notice Withdraw ether from the multisig. Equivalent to EARLY_RESOLVE in Nimiq
@dev the signature is generated using web3.eth.sign() over the unique msigId
@param msigId Unique (owner, authority, balance != 0) multisig identifier
@param amount Return this amount from this contract to owner
@param sig bytes signature of the not transaction sending Authority
*/
function earlyResolve(bytes32 msigId, uint amount, bytes sig)
public
{
// Require: msg.sender == (owner or authority)
require(
multisigs[msigId].owner == msg.sender ||
multisigs[msigId].authority == msg.sender
);
// Require: valid signature from not msg.sending authority
address otherAuthority = multisigs[msigId].owner == msg.sender ?
multisigs[msigId].authority :
multisigs[msigId].owner;
require(otherAuthority == msigId.toEthSignedMessageHash().recover(sig));
// Return to owner
spendFromMultisig(msigId, amount, multisigs[msigId].owner);
}
/**
@notice Withdraw ether and delete the htlc swap. Equivalent to TIMEOUT_RESOLVE in Nimiq
@param msigId Unique (owner, authority, balance != 0) multisig identifier
@dev Only refunds owned multisig deposits
*/
function timeoutResolve(bytes32 msigId, uint amount)
public
{
// Require time has passed
require(now >= multisigs[msigId].unlockTime);
// Return to owner
spendFromMultisig(msigId, amount, multisigs[msigId].owner);
}
/**
@notice First or second stage of atomic swap.
@param msigId Unique (owner, authority, balance != 0) multisig identifier
@param beneficiary Beneficiary of this swap
@param amount Convert this amount from multisig into swap
@param fee Fee amount to be paid to multisig authority
@param expirationTime Swap expiration timestamp in seconds; not more than 1 day from now
@param hashedSecret sha256(secret), hashed secret of swap initiator
@return swapId Unique (initiator, beneficiary, amount, fee, expirationTime, hashedSecret) swap identifier
*/
function convertIntoHtlc(bytes32 msigId, address beneficiary, uint amount, uint fee, uint expirationTime, bytes32 hashedSecret)
public
returns (bytes32 swapId)
{
// Require owner with sufficient deposit
require(multisigs[msigId].owner == msg.sender);
require(multisigs[msigId].deposit >= amount + fee); // Checks for underflow
require(
now <= expirationTime &&
expirationTime <= min(now + 1 days, multisigs[msigId].unlockTime)
); // Not more than 1 day or unlockTime
require(amount > 0); // Non-empty amount as definition for active swap
require(!isAntecedentHashedSecret[hashedSecret]);
isAntecedentHashedSecret[hashedSecret] = true;
// Account in multisig balance
multisigs[msigId].deposit = sub(multisigs[msigId].deposit, add(amount, fee));
// Create swap identifier
swapId = keccak256(
msigId,
msg.sender,
beneficiary,
amount,
fee,
expirationTime,
hashedSecret
);
emit AtomicSwapInitialised(swapId);
// Create swap
AtomicSwap storage swap = atomicswaps[swapId];
swap.msigId = msigId;
swap.initiator = msg.sender;
swap.beneficiary = beneficiary;
swap.amount = amount;
swap.fee = fee;
swap.expirationTime = expirationTime;
swap.hashedSecret = hashedSecret;
// Transfer fee to fee recipient
FEE_RECIPIENT.transfer(fee);
}
/**
@notice Batch execution of convertIntoHtlc() function
*/
function batchConvertIntoHtlc(
bytes32[] msigIds,
address[] beneficiaries,
uint[] amounts,
uint[] fees,
uint[] expirationTimes,
bytes32[] hashedSecrets
)
public
returns (bytes32[] swapId)
{
require(msigIds.length <= MAX_BATCH_ITERATIONS);
for (uint i = 0; i < msigIds.length; ++i)
convertIntoHtlc(
msigIds[i],
beneficiaries[i],
amounts[i],
fees[i],
expirationTimes[i],
hashedSecrets[i]
); // Gas estimate `infinite`
}
/**
@notice Withdraw ether and delete the htlc swap. Equivalent to REGULAR_TRANSFER in Nimiq
@dev Transfer swap amount to beneficiary of swap and fee to authority
@param swapId Unique (initiator, beneficiary, amount, fee, expirationTime, hashedSecret) swap identifier
@param secret Hashed secret of htlc swap
*/
function regularTransfer(bytes32 swapId, bytes32 secret)
public
{
// Require valid secret provided
require(sha256(secret) == atomicswaps[swapId].hashedSecret);
uint amount = atomicswaps[swapId].amount;
address beneficiary = atomicswaps[swapId].beneficiary;
// Delete swap
delete atomicswaps[swapId];
// Execute swap
beneficiary.transfer(amount);
}
/**
@notice Batch exection of regularTransfer() function
*/
function batchRegularTransfers(bytes32[] swapIds, bytes32[] secrets)
public
{
require(swapIds.length <= MAX_BATCH_ITERATIONS);
for (uint i = 0; i < swapIds.length; ++i)
regularTransfer(swapIds[i], secrets[i]); // Gas estimate `infinite`
}
/**
@notice Reclaim an expired, non-empty swap into a multisig
@dev Transfer swap amount to beneficiary of swap and fee to authority
@param msigId Unique (owner, authority, balance != 0) multisig identifier to which deposit expired swaps
@param swapId Unique (initiator, beneficiary, amount, fee, expirationTime, hashedSecret) swap identifier
*/
function reclaimExpiredSwap(bytes32 msigId, bytes32 swapId)
public
{
// Require: msg.sender == ower or authority
require(
multisigs[msigId].owner == msg.sender ||
multisigs[msigId].authority == msg.sender
);
// Require msigId matches swapId
require(msigId == atomicswaps[swapId].msigId);
// Require: is expired
require(now >= atomicswaps[swapId].expirationTime);
uint amount = atomicswaps[swapId].amount;
delete atomicswaps[swapId];
multisigs[msigId].deposit = add(multisigs[msigId].deposit, amount);
}
/**
@notice Batch exection of reclaimExpiredSwaps() function
*/
function batchReclaimExpiredSwaps(bytes32 msigId, bytes32[] swapIds)
public
{
require(swapIds.length <= MAX_BATCH_ITERATIONS); // << block.gaslimit / 88281
for (uint i = 0; i < swapIds.length; ++i)
reclaimExpiredSwap(msigId, swapIds[i]); // Gas estimate 88281
}
}
{
"compilationTarget": {
"Htlc.sol": "Htlc"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"swapId","type":"bytes32"},{"name":"secret","type":"bytes32"}],"name":"regularTransfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"msigId","type":"bytes32"},{"name":"unlockTime","type":"uint256"}],"name":"reparametrizeMultisig","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"msigId","type":"bytes32"},{"name":"beneficiary","type":"address"},{"name":"amount","type":"uint256"},{"name":"fee","type":"uint256"},{"name":"expirationTime","type":"uint256"},{"name":"hashedSecret","type":"bytes32"}],"name":"convertIntoHtlc","outputs":[{"name":"swapId","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"msigId","type":"bytes32"},{"name":"amount","type":"uint256"},{"name":"sig","type":"bytes"}],"name":"earlyResolve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"msigId","type":"bytes32"},{"name":"swapId","type":"bytes32"}],"name":"reclaimExpiredSwap","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"msigIds","type":"bytes32[]"},{"name":"beneficiaries","type":"address[]"},{"name":"amounts","type":"uint256[]"},{"name":"fees","type":"uint256[]"},{"name":"expirationTimes","type":"uint256[]"},{"name":"hashedSecrets","type":"bytes32[]"}],"name":"batchConvertIntoHtlc","outputs":[{"name":"swapId","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"multisigs","outputs":[{"name":"owner","type":"address"},{"name":"authority","type":"address"},{"name":"deposit","type":"uint256"},{"name":"unlockTime","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"msigId","type":"bytes32"},{"name":"amount","type":"uint256"}],"name":"timeoutResolve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"msigId","type":"bytes32"},{"name":"swapIds","type":"bytes32[]"}],"name":"batchReclaimExpiredSwaps","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"authority","type":"address"},{"name":"unlockTime","type":"uint256"}],"name":"initialiseMultisig","outputs":[{"name":"msigId","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"atomicswaps","outputs":[{"name":"msigId","type":"bytes32"},{"name":"initiator","type":"address"},{"name":"beneficiary","type":"address"},{"name":"amount","type":"uint256"},{"name":"fee","type":"uint256"},{"name":"expirationTime","type":"uint256"},{"name":"hashedSecret","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"swapIds","type":"bytes32[]"},{"name":"secrets","type":"bytes32[]"}],"name":"batchRegularTransfers","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"isAntecedentHashedSecret","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"msigId","type":"bytes32"}],"name":"MultisigInitialised","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"msigId","type":"bytes32"}],"name":"MultisigReparametrized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"swapId","type":"bytes32"}],"name":"AtomicSwapInitialised","type":"event"}]