Kit

Key pairs

Sign and verify messages and transactions using Ed25519 keys

Introduction

Kit uses the primitives built-in to JavaScript’s Web Crypto API to perform cryptography. This keeps your applications small, and confers to them the security and performance characteristics of the runtime's native cryptography functions.

Ed25519 keys created or imported using the native APIs are compatible with all of Kit's cryptographic features. Kit also offers several helpers to generate, import, and transform key material.

Looking for Signers instead?

Keys are a low-level primitive. While it's important to know how they work, in a Solana application it's often more appropriate to deal with key material in terms of the accounts and wallets that sign a transaction or message. The Signers API offers an ergonomic way to build and sign transactions using accounts and their associated keys.

Installation

Key management functions are included within the @solana/kit library but you may also install them using their standalone package.

npm install @solana/keys

Not all runtimes support the cryptography required by Solana

When deploying your application to a JavaScript runtime that lacks support for the Ed25519 digital signature algorithm, import our polyfill before invoking any operations that create or make use of CryptoKey objects.

What is a key pair?

A key pair is a data type composed of 256-bits of random data (the private key) and a Cartesian point (the public key) on the curve that is used to cryptographically sign and verify Solana transactions. The interface definition of CryptoKeyPair is:

interface CryptoKeyPair {
    privateKey: CryptoKey;
    publicKey: CryptoKey;
}

Together, these keys represent an address on Solana and its owner. The 256-bit address is derived from the coordinates of the public key itself.

What is a key?

A key is an object native to the JavaScript runtime that supports the CryptoKey interface. You can use a CryptoKey with the native SubtleCrypto API to sign messages and to verify signatures. Kit builds on these capabilities to enable the use of CryptoKey objects to sign and verify Solana transactions and off-chain messages.

Managing keys

Generating new keys

You can use the native SubtleCrypto#generateKey API, or the generateKeyPair() helper to create a new random key pair.

const  = await ..(
  /* algorithm */ { : 'Ed25519' },
  /* extractable */ false,
  /* usages */ ['sign', 'verify'],
);

This is useful in cases where you need to sign for the creation of a new account with a random address, using an ephemeral key pair that can be discarded after the account is created and assigned to a program.

Importing a key

You can create a CryptoKeyPair using the 64 bytes of a pre-generated key. This is possible to do with the native SubtleCrypto#importKey API, but it is more convenient to use the helpers in Kit.

import {  } from '@solana/kit';
const  = await (
    new ([
        /* 32 bytes representing the private key */
        /* 32 bytes representing the public key */
    ]),
);

In cases where you have only the 32 bytes representing the private key but not its associated public key, you can use a different function that will automatically derive the public key from the private key.

import {  } from '@solana/kit';
const  = await (
    new ([
        /* 32 bytes representing the private key */
    ]),
);

Storing keys

CryptoKey objects can be stored locally by runtimes that support the IndexedDB API.

Given an instance of an IDBDatabase with an object store called 'MyKeyPairStore':

const  = await new <IDBDatabase>((, ) => {
    const  = .('MyDatabase', 1);
    . = () => {
        const  = (. as IDBOpenDBRequest).;
        .('MyKeyPairStore');
    };
    . = () => {
        ((. as IDBOpenDBRequest).);
    };
    . = () => {
        ((. as IDBOpenDBRequest).);
    };
});

You can store a key pair like this:

const  = .('MyKeyPairStore', 'readwrite');
const  = .('MyKeyPairStore');
await new <void>((, ) => {
    const  = .(, 'myStoredKey');
    . = () => ();
    . = () => (.);
});

And then later retrieve it like this:

const  = .('MyKeyPairStore', 'readonly');
const  = .('MyKeyPairStore');
const  = await new <CryptoKeyPair>((, ) => {
    const  = .('myStoredKey');
    . = () => {
        if (.) {
            (.);
        } else {
            (new ('Key not found'));
        }
    };
    . = () => (.);
});

Keys stored in IndexedDB are local to the host, are subject to its storage limits and eviction criteria, and can not be accessed from a domain other than the one that stored them when the host is a web browser. Keys that are evicted from storage or erased by the user can generally not be recovered.

Exporting a key

You can obtain the 32 bytes of any public key like this:

const  = new (await ..('raw', .));

Exporting private key material requires a specially constructed CryptoKey with its extractable property set to true.

const  = await ..(
    /* algorithm */ { : 'Ed25519' },
    /* extractable */ true,
    /* usages */ ['sign', 'verify'],
);

You can then use that CryptoKey with the SubtleCrypto#exportKey API. The last 32 bytes of a PKCS#8 export of the key are the private key bytes.

const  = await ..('pkcs8', .);
const  = new (, . - 32, 32);

Security warning

Exporting private key material in JavaScript is not recommended, and you should take extreme care when doing so. Private key bytes exported through these APIs are vulnerable to theft (eg. by code running in your JavaScript sandbox) and accidental logging (eg. to the console or to a third-party logger).

Using private keys

A private key is a CryptoKey whose type property is set to 'private' and whose usages property includes 'sign'.

Private keys can be used by their owner to produce a digital signature of a specific message. You can think of a signature as representing the key owner's approval of, agreement to, or endorsement of the message. Private key owners must never share the key with anyone.

Signing messages

Any data that you can serialize as a Uint8Array can be signed with a CryptoKey.

import { ,  } from '@solana/kit';
const  = ().('🎉');
const  = ().(0xdeadbeef);
const  = new ([1, 2, 3]);

Supply a message and a private key to the signBytes() function to obtain a 64-byte digital signature.

import { ,  } from '@solana/kit';
const  = ().('The meeting is at 6:00pm');
const  = await (., );
.();
 
Uint8Array(64) [79, 43, 140, 65, 177, 35, 169, 61, 62, 228, 190, 202, 205, 143, 4, 35, 83, 228, 47, 76, 68, 62, 125, 140, 21, 102, 182, 105, 24, 238, 67, 40, 179, 255, 247, 136, 95, 119, 46, 244, 44, 224, 100, 111, 68, 110, 189, 224, 159, 144, 197, 181, 210, 132, 101, 226, 120, 200, 0, 102, 104, 65, 216, 3]

The same message and private key might produce a different signature every time you call this function. Some runtimes produce randomized signatures as per draft-irtf-cfrg-det-sigs-with-noise while others produce deterministic signatures as per RFC 8032.

Signing transactions

In Kit, private keys are used to digitally sign transactions on behalf of account owners, to approve spending, transfers, and modifications to account data on the blockchain.

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

In practice, it's rare to sign transactions like this. Typically, you create a transaction message in your application, specify which accounts are required to sign it using the Signers API, and then call signTransactionMessageWithSigners to turn it into a signed Transaction.

Using public keys

A public key is a CryptoKey whose type property is set to 'public' and whose usages property includes 'verify'.

Solana uses public keys to verify that modifications to account data and balances through transactions are approved of by those who hold the private keys required to authorize such modifications. Kit generally only uses public keys to derive the address of their associated accounts, but it can also use public keys to enable your programs to verify arbitrary messages.

Verifying signatures

The public key associated with a given private key can be used by anyone to verify that a message was signed by the holder of the associated private key, as described above. The verification function requires the original message, a digital signature, and the public key associated with the owner who is believed to have produced that signature. Successful verification implies that the contents of the message signed by the owner are identical to the contents of the message that was given to the verification function. Key owners are free to share their public key with anyone they would like to grant the ability to verify their digital signatures.

If someone sends us a message and signature that they claim to be from the example above, we could use Bob's public key to verify that the signature was in fact the one produced by Bob and that the message was not modified in transit.

import { ,  } from '@solana/kit';
const  = await (
    ,
    ,
    ().('The meeting is at 6:00pm'),
);
if (!) {
    throw new (
        'Either the message was modified, the signature was not produced by Bob, or both',
    );
}

Verification protects equally against false claims of who produced the signature as it does against false claims about what message was signed. Both of these will return false;

import { ,  } from '@solana/kit';
// False claim that Bob signed the message when it was in fact Mallory
await (
    ,
    ,
    ().('The meeting is at 6:00pm'),
);
// False claim about the contents of the message
await (
    ,
    ,
    ().('The meeting is at 6:00am'),
);

Deriving addresses

The Solana address associated with any public key can be derived using the getAddressFromPublicKey() function.

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

Signatures

The SignatureBytes type represents a 64-byte Ed25519 signature, and the Signature type represents such a signature as a base58-encoded string.

When you acquire a string that you expect to be a base58-encoded signature (eg. of a transaction) from an untrusted network API or user input you can assert it is in fact a base58-encoded byte array of sufficient length using the assertIsSignature() function.

import {  } from '@solana/kit';
 
// Imagine a function that asserts whether a user-supplied signature is valid or not.
function () {
    // We know only that what the user typed conforms to the `string` type.
    const : string = .;
    try {
        // If this type assertion function doesn't throw, then
        // Typescript will upcast `signature` to `Signature`.
        ();
        // At this point, `signature` is a `Signature` that can be used with the RPC.
        const {
            : [],
        } = await .([]).();
    } catch () {
        // `signature` turned out not to be a base58-encoded signature
    }
}

Similarly, you can use the isSignature() type guard. It will return true if the input string conforms to the Signature type and will refine the type for use in your program from that point onward. This method does not throw in the opposite case.

import {  } from '@solana/kit';
 
if (()) {
    // At this point, `signature` has been refined to a
    // `Signature` that can be used with the RPC.
    const {
        : [],
    } = await .([]).();
    ();
} else {
    (`${} is not a transaction signature`);
}

The signature() helper combines asserting that a string is an Ed25519 signature with coercing it to the Signature type. It's best used with untrusted input.

import { signature } from '@solana/keys';
 
const signature = signature(userSuppliedSignature);
const {
    value: [status],
} = await rpc.getSignatureStatuses([signature]).send();

Runtime Support

All major JavaScript runtimes support the Ed25519 digital signature algorithm required by Solana.

RuntimeMin versionSince
Desktop browsersChromev137May 2025
Edgev137May 2025
Firefoxv130Sep 2024
Safariv17Sep 2023
Mobile browsersAndroid browserv137May 2025
Firefox for Androidv139May 2025
Mobile SafariiOS 17Sep 2023
Server runtimesBunv1.2.6Mar 2025
Cloudflare workerdv1.20230419.0Apr 2023
Denov1.26.1Oct 2022
Node.jsv18.4.0Jun 2022
Vercel Edgev2.3.0May 2023

For additional up-to-date details on runtimes' implementation status, visit https://github.com/WICG/webcrypto-secure-curves/issues/20.

Ed25519 Polyfill

To use keys in a runtime without Ed25519 digital signature algorithm support, install the following polyfill.

npm install @solana/webcrypto-ed25519-polyfill

Then import and install it before invoking any operations that create or make use of CryptoKey objects.

import {  } from '@solana/webcrypto-ed25519-polyfill';
 
// Calling this will shim methods on `SubtleCrypto`, adding Ed25519 support.
();
 
// Now you can do this, in environments that do not otherwise support Ed25519.
const  = await ..({ : 'Ed25519' }, false, ['sign']);

Wherever you call install(), make sure the call is made only once, and before any key operation requiring Ed25519 support is performed.

Security warning

Because the polyfill's implementation of Ed25519 key generation exists in userspace, it can't guarantee that the keys you generate with it are non-exportable. Untrusted code running in your JavaScript context may still be able to gain access to and/or exfiltrate secret key material.

Storing polyfilled keys in IndexedDB

Native CryptoKeys can be stored in IndexedDB, but the keys created by this polyfill can not. This is because, unlike native CryptoKeys, our polyfilled key objects can not implement the structured clone algorithm.

On this page