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.

npm install @solana/signers

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.

import {
    ,
    ,
    ,
    ,
    ,
    ,
} from '@solana/kit';
 
// Generate a key pair signer.
const  = await ();
.; // The address of the account
 
// Sign one or multiple messages.
const  = ('Hello world!');
const [] = await .([]);
 
// Sign to pay fees for a transaction message
const  = (
    ({ : 0 }),
    () => (, ),
    // Add instructions, lifetime, etc.
);
const  = await ();

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.

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.

const : <'1234..5678'> = {
    : ('1234..5678'),
    : async (): <[]> => {
        // My custom signing logic.
    },
};

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.

const : <'1234..5678'> = {
    : ('1234..5678'),
    : async < extends >(: []): <[]> => {
        // My custom signing logic.
    },
};

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.

const : <'1234..5678'> = {
    : ('1234..5678'),
    : async (: []): <[]> => {
        // My custom signing logic.
    },
};

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.

type  = {
    : ;
    : ; // Record<Address, SignatureBytes>
};

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.

import { , ,  } from '@solana/kit';
// ---cut-before--
const  = (new ([1, 2, 3]));
const  = ('Hello world!');
const  = ('Hello world!', {
    [('1234..5678')]: new ([1, 2, 3]) as ,
});

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.

const : <'1234..5678'> = {
    : ('1234..5678'),
    : async (: []): <[]> => {
        // My custom signing logic.
    },
};

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.

const : <'1234..5678'> = {
    : ('1234..5678'),
    : async (: []): <[]> => {
        // My custom signing logic.
    },
};

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.
import {  } from '@solana/kit';
 
const  = (('1234..5678'));
const [] = await .([]); // <- Empty signature dictionary.
const [] = await .([]); // <- Empty signature dictionary.

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

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.

import { , ,  } from '@solana/kit';
 
const : CryptoKeyPair = await ();
const :  = await ();

generateKeyPairSigner

A convenience function that generates a new Crypto KeyPair and immediately creates a KeyPairSigner from it.

import {  } from '@solana/kit';
 
const  = await ();

createKeyPairSignerFromBytes

A convenience function that creates a new KeyPair from a 64-bytes Uint8Array secret key and immediately creates a KeyPairSigner from it.

import  from 'fs';
import {  } from '@solana/kit';
 
// Get bytes from local keypair file.
const  = .('~/.config/solana/id.json');
const  = new (.(.()));
 
// Create a KeyPairSigner from the bytes.
const  = await ();

createKeyPairSignerFromPrivateKeyBytes

A convenience function that creates a new KeyPair from a 32-bytes Uint8Array private key and immediately creates a KeyPairSigner from it.

import { ,  } from '@solana/kit';
 
const  = ().('Hello, World!');
const  = new (await ..('SHA-256', ));
 
const  = await ();

On this page