Signers
Accounts with signing capabilities
Introduction
Signers are an abstraction that combines an account with an implementation that computes signatures on behalf of that account. No matter what actually computes the signature – a wallet app, a network API, the SubtleCrypto
API, or a userspace implementation – so long as it implements the correct signer interface for your intended purpose, you can use it with all of Kit's signer-aware functions.
Signers are designed to make it easier to build, sign, and send transactions by taking the guesswork out of which accounts' key pairs need to sign a transaction. Signer-aware APIs allow you to associate signer objects with each transaction instruction that requires them, enabling Kit's transaction planning, signing, and sending functions to automatically collect and invoke them.
Installation
Signers are included within the @solana/kit
library but you may also install them using their standalone package.
What is a signer?
All signers are wrappers around an Address
. This means that most APIs that require an Address
can be made to accept a signer. Each specific type of signer adds one or more capabilities, such as the ability to sign a message or a transaction on behalf of the account with that address. Some even add the ability to sign and send a transaction, which is common for wallets that you use in your browser or on your phone.
As you can see, this provides a consistent API regardless of how things are being signed behind the scenes. If tomorrow we need to use a browser wallet instead, we'd simply need to swap the generateKeyPairSigner()
function with the signer factory of our choice.
Types of signers
This package offers a total of five different types of signers that may be used in combination when applicable. Three of them allow us to sign transactions whereas the other two are used for regular message signing.
They are separated into three categories:
- Partial signers: Given a message or transaction, provide one or more signatures for it. These signers are not able to modify the given data which allows us to run many of them in parallel.
- Modifying signers: Can choose to modify a message or transaction before signing it with zero or more private keys. Because modifying a message or transaction invalidates any pre-existing signatures over it, modifying signers must do their work before any other signer.
- Sending signers: Given a transaction, signs it and sends it immediately to the blockchain. When applicable, the signer may also decide to modify the provided transaction before signing it. This interface accommodates wallets that simply cannot sign a transaction without sending it at the same time. This category of signers does not apply to regular messages.
Thus, we end up with the following interfaces.
Partial signers | Modifying signers | Sending signers | |
---|---|---|---|
TransactionSigner | TransactionPartialSigner | TransactionModifyingSigner | TransactionSendingSigner |
MessageSigner | MessagePartialSigner | MessageModifyingSigner | N/A |
We will go through each of these five signer interfaces and their respective characteristics in the documentation below.
Signing transactions
Partial signers
TransactionPartialSigner
is an interface that signs an array of Transactions
without modifying their content. It defines a signTransactions
function that returns a SignatureDictionary
for each provided transaction. Such signature dictionaries are expected to be merged with the existing ones if any.
Characteristics:
- Parallel. It returns a signature dictionary for each provided transaction without modifying them, making it possible for multiple partial signers to sign the same transaction in parallel.
- Flexible order. The order in which we use these signers for a given transaction doesn’t matter.
Modifying signers
TransactionModifyingSigner
is an interface that potentially modifies the provided Transactions
before signing them. E.g. this enables wallets to inject additional instructions into the transaction before signing them. For each transaction, instead of returning a SignatureDictionary
, its modifyAndSignTransactions
function returns an updated Transaction
with a potentially modified set of instructions and signature dictionary.
Characteristics:
- Sequential. Contrary to partial signers, these cannot be executed in parallel as each call can modify the provided transactions.
- First signers. For a given transaction, a modifying signer must always be used before a partial signer as the former will likely modify the transaction and thus impact the outcome of the latter.
- Potential conflicts. If more than one modifying signer is provided, the second signer may invalidate the signature of the first one. However, modifying signers may decide not to modify a transaction based on the existence of signatures for that transaction.
Sending signers
TransactionSendingSigner
is an interface that signs one or multiple transactions before sending them immediately to the blockchain. It defines a signAndSendTransactions
function that returns the transaction signature (i.e. its identifier) for each provided Transaction
. This interface is required for PDA wallets and other types of wallets that don't provide an interface for signing transactions without sending them.
Note that it is also possible for such signers to modify the provided transactions before signing and sending them. This enables use cases where the modified transactions cannot be shared with the app and thus must be sent directly.
Characteristics:
- Single signer. Since this signer also sends the provided transactions, we can only use a single
TransactionSendingSigner
for a given set of transactions. - Last signer. Trivially, that signer must also be the last one used.
- Potential conflicts. Since signers may decide to modify the given transactions before sending them, they may invalidate previous signatures. However, signers may decide not to modify a transaction based on the existence of signatures for that transaction.
- Potential confirmation. Whilst this is not required by this interface, it is also worth noting that most wallets will also wait for the transaction to be confirmed (typically with a
confirmed
commitment) before notifying the app that they are done.
Signing messages
Signable messages
SignableMessage
defines a message with any of the signatures that might have already been provided by other signers. This interface allows modifying signers to decide on whether or not they should modify the provided message depending on whether or not signatures already exist for such message. It also helps create a more consistent API by providing a structure analogous to transactions which also keep track of their signature dictionary.
You can use the createSignableMessage
function to create a SignableMessage
from a Uint8Array
or UTF-8 string. It optionally accepts a signature dictionary if the message already contains signatures.
Partial signers
MessagePartialSigner
is an interface that signs an array of SignableMessages
without modifying their content. It defines a signMessages
function that returns a SignatureDictionary
for each provided message. Such signature dictionaries are expected to be merged with the existing ones if any.
Characteristics:
- Parallel. When multiple signers sign the same message, we can perform this operation in parallel to obtain all their signatures.
- Flexible order. The order in which we use these signers for a given message doesn’t matter.
Modifying signers
MessageModifyingSigner
is an interface that potentially modifies the content of the provided SignableMessages
before signing them. E.g. this enables wallets to prefix or suffix nonces to the messages they sign. For each message, instead of returning a SignatureDictionary
, its modifyAndSignMessages
function returns its updated SignableMessage
with a potentially modified content and signature dictionary.
Characteristics:
- Sequential. Contrary to partial signers, these cannot be executed in parallel as each call can modify the content of the message.
- First signers. For a given message, a modifying signer must always be used before a partial signer as the former will likely modify the message and thus impact the outcome of the latter.
- Potential conflicts. If more than one modifying signer is provided, the second signer may invalidate the signature of the first one. However, modifying signers may decide not to modify a message based on the existence of signatures for that message.
Available signers
No-op signers
For a given address, a no-op signer can be created to offer an implementation of both the MessagePartialSigner
and TransactionPartialSigner
interfaces such that they do not sign anything. Namely, signing a transaction or a message with a NoopSigner
will return an empty SignatureDictionary
.
This signer may be useful:
- For testing purposes.
- For indicating that a given account is a signer and taking the responsibility to provide the signature for that account ourselves. For instance, if we need to send the transaction to a server that will sign it and send it for us.
Key pair signers
A key pair signer uses a CryptoKeyPair
to sign messages and transactions. It implements both the MessagePartialSigner
and TransactionPartialSigner
interfaces and keeps track of the CryptoKeyPair
instance used to sign messages and transactions.
createSignerFromKeyPair generateKeyPairSigner createKeyPairSignerFromBytes createKeyPairSignerFromPrivateKeyBytes
createSignerFromKeyPair
Creates a KeyPairSigner
from a provided Crypto KeyPair. The signMessages
and signTransactions
functions of the returned signer will use the private key of the provided key pair to sign messages and transactions. Note that both the signMessages
and signTransactions
implementations are parallelized, meaning that they will sign all provided messages and transactions in parallel.
generateKeyPairSigner
A convenience function that generates a new Crypto KeyPair and immediately creates a KeyPairSigner
from it.
createKeyPairSignerFromBytes
A convenience function that creates a new KeyPair from a 64-bytes Uint8Array
secret key and immediately creates a KeyPairSigner
from it.
createKeyPairSignerFromPrivateKeyBytes
A convenience function that creates a new KeyPair from a 32-bytes Uint8Array
private key and immediately creates a KeyPairSigner
from it.