essential-contributions / essential-integration

Integration of Pint and the Essential protocol.
Apache License 2.0
3 stars 1 forks source link

Update the token to use simpler authentication #74

Open freesig opened 3 weeks ago

freesig commented 3 weeks ago

Goal

Update the token app to use inline authentication using unions.

Authentication

Authentication is one of two things:

Contract owned

Any predicate can have it's own account. We hash the PredicateAddress and that gives the account. What this means is if that predicate is solved then that authenticates transferring tokens from that account. This is how it's currently done. But basically it comes down to:

type PredicateAddress = { contract: b256, addr: b256 };
type Instance = {  addr: PredicateAddress, pathway: int };

pub var key: b256;

var predicate_that_owns_account: Instance;

// Check the owning predicate is being solved in this solution.
constraint __predicate_at(predicate_that_owns_account.pathway) == predicate_that_owns_account.addr;

// Check the owning predicate is the one that owns this key
constraint __sha256(predicate_that_owns_account.addr) == key;

Signed

A user can sign over values that they want to authorize like signing over the from, to and amount. The public key of that signature is hashed and that is the account that is authorized.

// The address that the amount is being sent from.
pub var key: b256;
// The address that the amount is being sent to.
pub var to: b256;
// The amount being transfered.
pub var amount: int;

// Read the nonce from storage.
state nonce = mut storage::nonce[key];

var sig: Secp256k1Signature;

// Check that the signature over the data verifies to the key
constraint __sha256(__recover_secp256k1(__sha256({key, to, amount, nonce', { contract: __this_contract_address(), addr: __this_address() }}}), sig)) == key;

Unions

So these are the two most basic forms of authentication. There are more types (like only signing over some data but requiring other constraints) but let's focus on these two. The challenge with including these in the token contract is that they have different data depending on the type of authentication. So we need to do something like:

type PredicateAddress = { contract: b256, addr: b256 };
type Instance = {  addr: PredicateAddress, pathway: int };

type ContractAuth = { predicate_that_owns_account: Instance };
type SignedAuth = { sig: Secp256k1Signature };

union Auth = ContractAuth(ContractAuth) | SignedAuth(SignedAuth);

// The address that the amount is being sent from.
pub var key: b256;
// The address that the amount is being sent to.
pub var to: b256;
// The amount being transfered.
pub var amount: int;

// Read the nonce from storage.
state nonce = mut storage::nonce[key];

var auth: Auth;

constraint match auth {
    Auth::ContractAuth(c) => {
        constraint __predicate_at(c.predicate_that_owns_account.pathway) == c.predicate_that_owns_account.addr;
        constraint __sha256(c.predicate_that_owns_account.addr) == key;
    },
    Auth::SignedAuth(s) => {
        constraint __sha256(__recover_secp256k1(__sha256({key, to, amount, nonce', { contract: __this_contract_address(), addr: __this_address() }}}), s.sig)) == key;
    }
}

Resources