Suppose we wish to model a UTXO system on the EVM. We need to represent UTXO tokens such that all value is preserved when tokens are spent. Note: For this example, we are not concerned about the security of the system and are satisfied with giving authorities the power to create tokens (e.g. as in a plasma system).
Consider the following object:
{
owner: <address>,
value: <uint>,
createdBy: <bytes32>,
id: <bytes32>
}
We can design a system of accounting these objects as follows.
Creation
A UTXO is created by an authority in the system. The createdBy
field is left blank to indicate this is an origin UTXO.
Spending
A UTXO is consumed when the owner spends it. In the event that less than the total value is spent, this creates two UTXOs: one for the spender and one for the recipient.
e.g. suppose someone owns this:
{
owner: '0x...ab',
value: 10,
createdBy: '0x...0',
id: '0x...1'
}
and spends 5 units. This creates the following two UTXOs:
{
owner: '0x...ab',
value: 5,
createdBy: '0x...1',
id: '0x...2'
}
{
owner: '0x...ac',
value: 5,
createdBy: '0x...1',
id: '0x...3'
}
The original UTXO (id=0x...1
) is consumed and deleted from the system, i.e. it cannot be spent again.
Mock Contract
This could be implemented in the following solidity contract:
struct UTXO {
owner: address,
value: uint,
createdBy: bytes32,
id: bytes32
}
mapping (bytes32 => UTXO) utxos;
uint totalSupply;
event create(address indexed owner, bytes32 indexed id, uint value);
event spend(address indexed from, address indexed to, bytes32 oldId, bytes32 newId, uint newValue);
// Update from @maurelian: https://gist.github.com/maurelian/d34c0e6fec9a5f60147b9faf27c39295#file-utxotoken-sol-L53
function getId(address _to, bytes32 _input) internal returns(bytes32) {
return keccak256(block.number, msg.sender, _to, _input);
}
function create(to, value) onlyAdmin() {
bytes32 id = keccak256(block.number, msg.sender, to);
UTXO utxo = new UTXO(to, value, bytes32(0), id);
utxos[id] = utxo;
totalSupply += value;
}
function spend(id, amount, to) {
assert(utxos[id].owner == msg.sender);
assert(utxos[id].value >= amount);
utxo memory oldUtxo = utxos[id];
delete utxos[id];
bytes32 newId1 = getId(_to, _id);
utxo spend1 = new UTXO(to, amount, id, newId1);
utxos[newId1] = spend1;
spend(msg.sender, to, oldUtxo.id, newId1, amount);
if (amount < oldUtxo.value) {
bytes32 newId2 = getId(msg.sender, _id ^ bytes32(1));
utxo spend2 = new UTXO(msg.sender, oldUtxo.value - amount, id, newId2);
utxos[newId2] = spend2;
spend(msg.sender, to oldUtxo.id, newId2, oldUtxo.value - amount);
}
}
Rationale
The above is a simple scheme to represent a UTXO token in an EVM system. This could be useful from the perspective of a plasma chain, as UTXO fraud proofs are significantly smaller (and therefore less expensive) than account-based proofs.
Interestingly, one could imagine an ERC20 wrapper on top of this UTXO scheme, where a user might burn them and use that transaction to recreate them in an ERC20 contract.
There are many potential uses and interfaces for UTXO tokens and their simplicity for proofs makes them attractive from an architectural perspective.
I came across this gist and found it super interesting - is there other related work out there?