Skip to content

Instantly share code, notes, and snippets.

@HarryR
Created July 28, 2017 20:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HarryR/f0e0ca6e3f242bd16125fd9398299a22 to your computer and use it in GitHub Desktop.
Save HarryR/f0e0ca6e3f242bd16125fd9398299a22 to your computer and use it in GitHub Desktop.
pragma solidity ^0.4.13;
/*
EthPipe creates cryptographically secure off-chain financial channels.
Copyright (C) 2017 Harry Roberts
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
library SafeMath
{
function mul(uint a, uint b)
internal
returns (uint)
{
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint a, uint b)
internal
returns (uint)
{
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint c = a / b;
return c;
}
function sub(uint a, uint b)
internal
returns (uint)
{
assert(b <= a);
return a - b;
}
function add(uint a, uint b)
internal
returns (uint)
{
uint c = a + b;
assert(c >= a);
return c;
}
}
library SaferEcRecover
{
/**
* A safer version of ecrecover which will throw if not a valid signature.
*/
function safer_ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
returns (address)
{
bool ret;
address addr;
assembly
{
let size := mload(0x40)
mstore(size, hash)
mstore(add(size, 32), v)
mstore(add(size, 64), r)
mstore(add(size, 96), s)
ret := call(3000, 1, 0, size, 128, size, 32)
addr := mload(size)
}
assert( false == ret );
return addr;
}
}
library Account
{
using SafeMath for uint;
struct State
{
// Hashable fields
address addr;
uint balance;
uint balance_held;
uint held_until;
// Unhashed fields, for validation/integrity
uint deposited;
uint withdrawn;
}
function Withdraw( Account.State storage self, uint amount, address addr_to )
internal
{
uint balance = self.balance;
uint remaining_balance = balance.sub(amount);
uint txfer_balance = balance.sub(remaining_balance);
require( balance > 0 );
require( txfer_balance > 0 );
require( remaining_balance >= self.balance_held );
addr_to.transfer(txfer_balance);
self.withdrawn = self.withdrawn.sub(txfer_balance);
self.balance = remaining_balance;
}
function Deposit( Account.State storage self, uint amount )
internal
{
require( amount > 0 );
self.balance = self.balance.add(amount);
self.deposited = self.deposited.add(amount);
}
function VerifyHash( Account.State storage self, bytes32 hash, uint8 v, bytes32 r, bytes32 s )
internal
{
address recovered = safer_ecrecover(hash, v, r, s);
assert( recovered == self.addr );
}
}
library Channel
{
using Account for Account.State;
struct State
{
uint sequence;
Account.State A;
Account.State B;
}
/**
* Synchronises the Channel internal state with that of the off-chain state.
*
* Both Accounts sign a hash of the previous and new states, ensuring that
* everybody agrees that whichever operations transpired in between have
* resulted in the same state.
*/
function Sync( Channel.State storage self,
uint8 V[2], bytes32 R[2], bytes32 S[2],
uint sequence,
uint deposited_A_above, uint deposited_B_above,
uint held_until_A, uint held_until_B,
uint held_A, uint held_B,
uint balance_A, uint balance_B )
internal
{
uint total_balance = balance_A.add(balance_B);
uint total_deposited = self.A.deposited.add(self.B.deposited);
// Verify the new state is valid
require( sequence > self.sequence );
require( total_balance <= total_deposited );
require( held_A <= balance_A );
require( held_B <= balance_B );
require( A.deposited >= deposited_A_above );
require( B.deposited >= deposited_B_above );
// Verify
bytes32 hash = sha3( sequence, held_A, held_B,
deposited_A_above, deposited_B_above,
held_until_A, held_until_B,
balance_A, balance_B );
self.A.VerifyHash(hash, V[0], R[0], S[0]);
self.B.VerifyHash(hash, V[1], R[1], S[1]);
// Save Channel state
self.sequence = sequence;
// Save Account states
self.A.balance = balance_A;
self.A.balance_held = held_A;
self.A.held_until = held_until_A;
self.B.balance = balance_B;
self.B.balance_held = held_B;
self.B.held_until = held_until_B;
}
function getAccount( Channel.State storage self, address addr )
internal
returns (Account.State)
{
if( addr_from == self.A.addr )
return self.A;
if( addr_from == self.B.addr )
return self.B;
// Unknown address
throw;
}
function Deposit( Channel.State storage self, address addr_to, uint amount )
internal
{
return getAccount(addr_to).Deposit(amount);
}
function Withdraw( Channel.State storage self, uint amount, address addr_from, address addr_to )
internal
{
return getAccount(addr_from).Withdraw(addr_to);
}
}
contract EthPipes
{
using Channel for Channel.State;
mapping (uint256 => Channel.State) channels;
function Fetch( uint256 channel_id )
internal
returns (Channel.State)
{
var channel = channels[channel_id];
require( channel.sequence != 0 );
return channel;
}
function Exists( uint256 channel_id )
public
returns (bool)
{
return channels[channel_id].start != 0;
}
function ChannelId( address partyA, address partyB )
returns (uint256)
{
return uint256(sha3(partyA, partyB)) ^ uint256(sha3(partyB, partyA));
}
function Open( address sender, address receiver, uint timeout )
public
returns (uint256)
{
uint256 channel_id = ChannelId(sender, receiver);
require( Exists(channel_id) );
channels[channel_id] = Channel({
sender: sender,
// ...
return channel_id;
}
function OpenTo( address receiver, uint timeout )
public
returns (uint256)
{
return Open(msg.sender, receiver, timeout);
}
function DepositFor( uint256 channel_id, address owner )
payable
public
{
}
function Deposit( uint256 channel_id )
payable
public
{
DepositFor(channel_id, msg.sender);
}
function OpenWithDeposit( address sender, address receiver )
payable
public
{
require(msg.value > 0);
uint256 channel_id = Open(sender, receiver)
}
function OpenWithDepositTo( address receiver, uint timeout )
payable
public
{
return OpenWithDeposit(msg.sender, receiver, timeout);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment