ava-labs / hypersdk

Opinionated Framework for Building Hyper-Scalable Blockchains on Avalanche
Other
205 stars 113 forks source link

Account Abstraction #1275

Open aaronbuchwald opened 3 months ago

aaronbuchwald commented 3 months ago

Update existing auth types to support a different sponsor/actor address. If the sponsor/actor are the same, it should only require a single signature.

aaronbuchwald commented 4 weeks ago

Currently, the Auth interface supports separating the Sponsor from the Actor, but each implementation uses the same signature/address for both. In other words, the interface supports account abstraction, but the default implementations do not.

If we were to add account abstraction into each implementation (ed25519, secp256r1, and bls), we would most likely duplicate the same code across each.

To avoid that, we could separate the crypto libraries for sign/verify over arbitrary messages from the notion of a sponsor/actor. Why? There's no good reason for the BLS implementation to know what a sponsor/actor is. We can add that structure on top instead.

This would change the current crypto auth interfaces to:

type Auth interface {
    Verify(msg []byte) (Address, error)
}

type AuthFactory interface {
    Sign(msg []byte) (Auth, error)
}

type SignerFactory interface {
    New(src []byte) (AuthFactory, error)
}

To add the structure on top, we could append a typeID to messages to differentiate between signatures that are:

  1. Both the sponsor and actor (sign(append(msg, byte(0)))
  2. Only the actor (sign(append(msg, byte(1)))
  3. Only the sponsor on behalf of a specific actor (sign(append(msg, byte(1), actorBytes...))
  4. Any other combination of actors/sponsors that limit who the other actor/sponsor can be

Assuming that the signature library does not need to know the details of the message it's signing (the human/ledger signing it does, but the signing library does not), we can consider adding signatures with these sponsor/actor roles attached.

type Auth interface {
    Verify(msg []byte) (Address, error)
}
type AuthFactory interface {
    Sign(msg []byte) (Auth, error)
}

// Nice to have to make auth libraries easier to use
type SignerFactory interface {
    New(src []byte) (AuthFactory, error)
    NewRandom() (AuthFactory, error)
}

type TxAuth interface {
    Sponsor() Address
    Actor() Address
    // ... remainder of the auth interface including serialization and fees for the signature itself
}

type TxActorAuth interface {
    Actor() Address
    Verify(msg []byte) error
}

type TxSponsorAuth interface {
    Sponsor() Address
    Verify(msg []byte, actor Address) error
}

type TxSigner interface {
    Sign(msg []byte) (Auth, error)
    SignAsActor(msg []byte) (ActorAuth, error)
    SignAsSponsor(msg []byte, actor Address) (SponsorAuth, error)
}

We would then re-combine the TxActorAuth and TxSponsorAuth to a single TxAuth object and would like to ensure that we only need to parse and verify two different signatures when there are in fact two different signatures ie. let's avoid a naive implementation that creates and verifies two signatures even when a user is both the sponsor and actor.

aaronbuchwald commented 3 weeks ago

Hmm problem with this design is outlined here: https://github.com/ava-labs/hypersdk/blob/dbcc6c9a969fdf2799c8d30699af77b3c3d7175e/chain/dependencies.go#L262