Public keys and Signers
On this page, we'll see how to manage public keys and signers in Umi which is partially made possible by the EdDSA interface.
The EdDSA interface is used to create keypairs, find PDAs and sign/verify messages using the EdDSA algorithm. We can either use this interface directly and/or use helper methods that delegate to this interface to provide a better developer experience.
Let's tackle this on a per-use case basis.
Public keys
In Umi, a public key is a simple base58 string
representing a 32-byte array. We use an opaque type to tell TypeScript that the given public key has been verified and is valid. We also use a type parameter to offer more granular type safety.
We can create a new valid public key from a variety of inputs using the publicKey
helper method. If the provided input cannot be converted to a valid public key, an error will be thrown.
It is possible to convert a public key to a Uint8Array
using the publicKeyBytes
helper method.
Additional helper methods are also available to help manage public keys.
PDAs
A PDA — or Program-Derived Address — is a public key that is derived from a program ID and an array of predefined seeds. A bump
number ranging from 0 to 255 is required to ensure the PDA does not live on the EdDSA elliptic curve and therefore does not conflict with cryptographically generated public keys.
In Umi, PDAs are represented as a tuple composed of the derived public key and the bump number. Similarly to public keys, it uses opaque types and type parameters.
To derive a new PDA, we can use the findPda
method of the EdDSA interface.
Each seed must be serialized as a Uint8Array
. You can learn more about serializers on the Serializers page but here is a quick example showing how to find the metadata PDA of a given mint address.
Note that in most cases, programs will provide helper methods to find specific PDAs. For instance, the code snippet above can be simplified to the following using the findMetadataPda
method of the @metaplex-foundation/mpl-token-metadata
Kinobi-generated library.
The following helper methods are also available to help manage PDAs.
Signers
A signer is a public key that can sign transactions and messages. This enables transactions to be signed by the required accounts and wallets to prove their identity by signing messages. In Umi, it is represented by the following interface.
You may generate a new signer cryptographically using the generateSigner
helper method. Under the hood, this method uses the generateKeypair
method of the EdDSA interface as described in the next section.
The following helper functions can also be used to manage signers.
As mentioned in the Umi interfaces page, the Umi
interface stores two instances of Signer
: The identity
using the app and the payer
paying for transaction and storage fees. Umi provides plugins to quickly assign new signers to these attributes. The signerIdentity
and signerPayer
plugins are available for this purpose. Note that, by default, the signerIdentity
method will also update the payer
attribute since, in most cases, the identity is also the payer.
You may also use the generatedSignerIdentity
and generatedSignerPayer
plugins to generate a new signer and immediately assign it to the identity
and/or payer
attributes.
In some cases, a library may require a Signer
to be provided but the current environment does not have access to this wallet as a signer. For instance, this can happen if a transaction is being created on the client but will be later on signed on a private server. It's for that reason that Umi provides a createNoopSigner
helper that creates a new signer from the given public key and simply ignores any signing request. It is then your responsibility to ensure that the transaction is signed before being sent to the blockchain.
Keypairs
Whilst Umi only relies on the Signer
interface to request signatures from a wallet, it also defines a Keypair
type and a KeypairSigner
type that are explicitly aware of their secret key.
The generateKeypair
, createKeypairFromSeed
and createKeypairFromSecretKey
methods of the EdDSA interface can be used to generate new Keypair
objects.
In order to use these keypairs as signers throughout your application, you can use the createSignerFromKeypair
helper method. This method will return an instance of KeypairSigner
to ensure that we can access the secret key when needed.
Note that the code snippet above is equivalent to using the generateSigner
helper method described in the previous section.
Helper functions and plugins also exist to manage keypairs.
Signing messages
The Signer
object and the EdDSA interface can be used together to sign and verify messages like so.
Signing transactions
Once we have a Signer
instance, signing a transaction or a set of transactions is as simple as calling the signTransaction
or signAllTransactions
methods.
If you need multiple signers to all sign the same transaction, you may use the signTransaction
helper method like so.
Going one step further, if you have multiple transactions that each need to be signed by one or more signers, the signAllTransactions
function can help you with that. It will even ensure that, if a signer is required to sign more than one transaction, it will use the signer.signAllTransactions
method on all of them at once.
If you are creating a Signer
manually and therefore implementing its signTransaction
method, you may want to use the addTransactionSignature
helper function to add the signature to the transaction. This will ensure the provided signature is required by the transaction and pushed at the right index of the transaction's signatures
array.
Last updated