Authenticators
Authenticators allow developers to implement custom asymmetric signature schemes that can be used to verify transactions on their network. Any wallet with a corresponding authenticator can be used to deploy schemas, hold and transfer native gas tokens, or perform any other action that requires a signed transaction.
Authenticator Interface
Any struct that implements the following interface can be registered as an authenticator:
// Authenticator is an interface for verifying signatures and deriving a
// string identifier from the sender bytes. Custom asymmetric signature
// algorithms may be implemented by developers by implementing this interface.
type Authenticator interface {
// Verify verifies whether a signature is valid for a given message and sender.
// It is meant to be used with asymmetric signature algorithms such as ECDSA,
// Ed25519 RSA, etc. If the signature is invalid, the method should return an error.
// If the signature is valid, the method should return nil.
Verify(sender, msg, signature []byte) error
// Identifier returns a string identifier for a given sender. This string identifier is
// used to identify the sender when interacting with the database engine, and will
// be used as the `@caller` variable in the engine.
Identifier(sender []byte) (string, error)
}
Register
To register an authenticator, use the RegisterAuthenticator
method in the auth
package. This method takes a string identifier and an authenticator struct as arguments.
In the example below, we register an authenticator with the string identifier my-authenticator
:
package main
import (
"github.com/kwilteam/kwil-db/extensions/auth"
)
func init() {
err := auth.RegisterAuthenticator("my-authenticator", customAuthenticator{})
if err != nil {
panic(err)
}
}
type customAuthenticator struct {}
func(customAuthenticator) Verify(sender, msg, signature []byte) error {
// Verify the signature
}
func(customAuthenticator) Identifier(sender []byte) (string, error) {
// Return the string identifier
}
Security Considerations
When implementing a custom authenticator, there are a few security considerations to keep in mind:
- Both the
Verify()
andIdentifier()
methods should be deterministic. This means that the same input should always produce the same output. - It is critically important to avoid collisions in the
sender
field of theVerify
method. AnyVerify
that returnsnil
will allow the sender to perform any action on the user's behalf. This will give them access to the user's funds and schemas. It is important to ensure that thesender
field is unique for each user. - Care must be taken to avoid collisions in the string identifier returned from the
Identifier()
method. If two different senders produce the same string identifier, it will be impossible to distinguish between them in the database. They will be treated as the same user in the database engine. - State should not be stored in any custom authenticator struct. Nodes do not call
Verify
orIdentifier
in a deterministic way, and may call them at any time.