Skip to content

Instantly share code, notes, and snippets.

@HarryR
Created April 5, 2023 11:54
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/7bc718b794085e24abe653e15ff4fce9 to your computer and use it in GitHub Desktop.
Save HarryR/7bc718b794085e24abe653e15ff4fce9 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.9;
contract WW
{
event EncryptedResponse(bytes32 nonce, bytes data);
event PublicKey(bytes32 x25519_public);
struct Coupon {
bytes32 nonce;
uint256 value;
bytes32 auth;
}
struct WithdrawOp {
address pay_to;
uint256 value;
}
struct EncryptedOperations {
Coupon[] input_coupons;
uint256[] output_values;
WithdrawOp[] withdraws;
}
// ------------------------------------------------------------------
// Separate secret to secure the coupons
bytes32 immutable private m_coupon_secret;
// Separate keypair to hide client<->contract comms from relayer
bytes32 immutable private m_comms_secret_x25519;
bytes32 immutable private m_comms_public_x25519;
// Keep track of which coupons have been spent
mapping(bytes32 => bool) private m_invalidations;
mapping(address => uint256) private m_fees;
// ------------------------------------------------------------------
constructor()
{
m_coupon_secret = _random_bytes32();
(m_comms_secret_x25519, m_comms_public_x25519) = _x25519_keypair();
}
function emitPublicKey()
external
{
emit PublicKey(m_comms_public_x25519);
}
function deposit(bytes32 ephemeral_pubkey)
external payable
{
bytes32 comms_secret_x25519 = m_comms_secret_x25519;
bytes32 coupon_secret = m_coupon_secret;
(bytes32 ephemeral_derived, bytes32 ephemeral_IV) = _relay_anti_tamper_secret(ephemeral_pubkey, comms_secret_x25519);
Coupon memory coupon = _coupon_create(msg.value, coupon_secret);
_encrypt_and_emit_response(ephemeral_derived, ephemeral_IV, abi.encode(coupon));
}
function join_split_pay(
bytes32 ephemeral_pubkey,
bytes calldata encrypted_operations,
uint256 fee
)
external
{
bytes32 comms_secret_x25519 = m_comms_secret_x25519;
bytes32 coupon_secret = m_coupon_secret;
(bytes32 ephemeral_derived, bytes32 ephemeral_IV) = _relay_anti_tamper_secret(ephemeral_pubkey, comms_secret_x25519);
(EncryptedOperations memory ops) = abi.decode(
_decrypt(ephemeral_derived, // Key
ephemeral_IV, // IV
encrypted_operations, // Ciphertext
abi.encodePacked(fee)), // Extra authenticated data
(EncryptedOperations));
uint256 total = 0;
// Open all input tokens
for( uint i = 0; i < ops.input_coupons.length; i++ )
{
total += _coupon_use_once(ops.input_coupons[i], coupon_secret);
}
// Create output tokens & encrypt them for client
{
Coupon[] memory output_coupons = new Coupon[](ops.output_values.length);
for( uint i = 0; i < ops.output_values.length; i++ )
{
uint256 value = ops.output_values[i];
total -= value;
output_coupons[i] = _coupon_create(value, coupon_secret);
}
ephemeral_IV = keccak256(abi.encodePacked(ephemeral_IV));
_encrypt_and_emit_response(ephemeral_derived, ephemeral_IV, abi.encode(output_coupons));
}
// Provide fee to relayer
{
total -= fee;
m_fees[msg.sender] += fee;
}
// Perform withdraws
{
for( uint i = 0; i < ops.withdraws.length; i++ )
{
WithdrawOp memory w = ops.withdraws[i];
require( 0 == _codesize(w.pay_to) );
uint256 value = w.value;
total -= value;
payable(w.pay_to).transfer(value);
}
}
require( 0 == total );
}
function withdrawFees()
external
{
uint256 value = m_fees[msg.sender];
require( 0 != value );
m_fees[msg.sender] = 0;
payable(msg.sender).transfer(value);
}
function withdraw(Coupon[] memory coupons, address pay_to)
external
{
bytes32 coupon_secret = m_coupon_secret;
uint256 total = 0;
for( uint i = 0; i < coupons.length; i++ )
{
total += _coupon_use_once(coupons[i], coupon_secret);
}
payable(pay_to).transfer(total);
}
// ------------------------------------------------------------------
address private constant RANDOM_BYTES = 0x0100000000000000000000000000000000000001;
address private constant DERIVE_KEY = 0x0100000000000000000000000000000000000002;
address private constant ENCRYPT = 0x0100000000000000000000000000000000000003;
address private constant DECRYPT = 0x0100000000000000000000000000000000000004;
address private constant CURVE25519_PUBLIC_KEY = 0x0100000000000000000000000000000000000008;
function _random_bytes32()
private view
returns (bytes32)
{
bytes memory p13n = abi.encodePacked(block.chainid, block.number, block.timestamp, msg.sender, address(this));
(bool success, bytes memory entropy) = RANDOM_BYTES.staticcall(abi.encode(uint256(32), p13n));
require(success);
return bytes32(entropy);
}
function _encrypt(bytes32 key, bytes32 nonce, bytes memory plaintext, bytes memory additionalData)
private view
returns (bytes memory)
{
(bool success, bytes memory ciphertext) = ENCRYPT.staticcall(
abi.encode(key, nonce, plaintext, additionalData)
);
require(success);
return ciphertext;
}
function _decrypt(bytes32 key, bytes32 nonce, bytes memory ciphertext, bytes memory additionalData)
private view
returns (bytes memory)
{
(bool success, bytes memory plaintext) = DECRYPT.staticcall(
abi.encode(key, nonce, ciphertext, additionalData)
);
require(success);
return plaintext;
}
function _coupon_auth(uint256 value, bytes32 nonce, bytes32 secret)
private view
returns (bytes32)
{
return keccak256(abi.encodePacked(value, nonce, secret, address(this)));
}
function _coupon_create(uint256 value, bytes32 secret)
private view
returns (Coupon memory r)
{
r.value = value;
r.nonce = _random_bytes32();
r.auth = _coupon_auth(value, r.nonce, secret);
}
function _coupon_use_once(Coupon memory t, bytes32 secret)
private
returns (uint256)
{
require( _coupon_auth(t.value, t.nonce, secret) == t.auth );
require( m_invalidations[t.nonce] == false );
m_invalidations[t.nonce] = true;
return t.value;
}
function _relay_anti_tamper_secret(bytes32 ephemeral_x25519_public, bytes32 secret)
private view
returns (bytes32 ephemeral_derived, bytes32 ephemeral_IV)
{
(bool success, bytes memory derived) = DERIVE_KEY.staticcall(
abi.encode(ephemeral_x25519_public, secret)
);
require( success );
ephemeral_derived = bytes32(derived);
ephemeral_IV = keccak256(abi.encodePacked(ephemeral_x25519_public));
}
function _encrypt_and_emit_response(bytes32 key, bytes32 IV, bytes memory plaintext)
private
{
bytes memory response = _encrypt(key, IV, plaintext, new bytes(0));
emit EncryptedResponse(IV, response);
}
function _x25519_keypair()
private view
returns (bytes32 x25519_secret, bytes32 x25519_public)
{
x25519_secret = _random_bytes32();
require( 0 != uint256(x25519_secret) );
(bool success, bytes memory public_bytes) = CURVE25519_PUBLIC_KEY.staticcall(abi.encodePacked(x25519_secret));
require( success );
x25519_public = bytes32(public_bytes);
}
function _codesize(address _addr)
private view
returns (uint size)
{
assembly { size := extcodesize(_addr) }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment