Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
Add createKeyPairFromPrivateKeyBytes helper
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva committed Aug 5, 2024
1 parent 06734a1 commit b738776
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 2 deletions.
11 changes: 11 additions & 0 deletions .changeset/curvy-stingrays-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@solana/keys': patch
---

Add a `createKeyPairFromPrivateKeyBytes` helper that creates a keypair from the 32-byte private key bytes.

```ts
import { createKeyPairFromPrivateKeyBytes } from '@solana/keys';

const { privateKey, publicKey } = await createKeyPairFromPrivateKeyBytes(new Uint8Array([...]));
```
22 changes: 22 additions & 0 deletions packages/keys/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,28 @@ const keypairBytes = new Uint8Array(JSON.parse(keypairFile.toString()));
const { privateKey, publicKey } = await createKeyPairFromBytes(keypairBytes);
```

### `createKeyPairFromPrivateKeyBytes()`

Given a private key represented as a 32-bytes `Uint8Array`, creates an Ed25519 public/private key pair for use with other methods in this package that accept `CryptoKey` objects.

```ts
import { createKeyPairFromPrivateKeyBytes } from '@solana/keys';

const { privateKey, publicKey } = await createKeyPairFromPrivateKeyBytes(new Uint8Array([...]));
```

This can be useful when you have a private key but not the corresponding public key or when you need to derive key pairs from seeds. For instance, the following code snippet derives a key pair from the hash of a message.

```ts
import { getUtf8Encoder } from '@solana/codecs-strings';
import { createKeyPairFromPrivateKeyBytes } from '@solana/keys';

const message = getUtf8Encoder().encode('Hello, World!');
const seed = new Uint8Array(await crypto.subtle.digest('SHA-256', message));

const derivedKeypair = await createKeyPairFromPrivateKeyBytes(seed);
```

### `createPrivateKeyFromBytes()`

Given a private key represented as a 32-bytes `Uint8Array`, creates an Ed25519 private key for use with other methods in this package that accept `CryptoKey` objects.
Expand Down
35 changes: 34 additions & 1 deletion packages/keys/src/__tests__/key-pair-test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
SOLANA_ERROR__KEYS__INVALID_KEY_PAIR_BYTE_LENGTH,
SOLANA_ERROR__KEYS__INVALID_PRIVATE_KEY_BYTE_LENGTH,
SOLANA_ERROR__KEYS__PUBLIC_KEY_MUST_MATCH_PRIVATE_KEY,
SolanaError,
} from '@solana/errors';

import { createKeyPairFromBytes, generateKeyPair } from '../key-pair';
import { createKeyPairFromBytes, createKeyPairFromPrivateKeyBytes, generateKeyPair } from '../key-pair';

const MOCK_KEY_BYTES = new Uint8Array([
0xeb, 0xfa, 0x65, 0xeb, 0x93, 0xdc, 0x79, 0x15, 0x7a, 0xba, 0xde, 0xa2, 0xf7, 0x94, 0x37, 0x9d, 0xfc, 0x07, 0x1d,
Expand Down Expand Up @@ -85,4 +86,36 @@ describe('key-pair', () => {
);
});
});

describe('createKeyPairFromPrivateKeyBytes', () => {
it('creates a key pair from a 32-bytes private key', async () => {
expect.assertions(1);
const keyPair = await createKeyPairFromPrivateKeyBytes(MOCK_KEY_BYTES.slice(0, 32));
expect(keyPair).toMatchObject({
privateKey: expect.objectContaining({
[Symbol.toStringTag]: 'CryptoKey',
algorithm: { name: 'Ed25519' },
type: 'private',
}),
publicKey: expect.objectContaining({
[Symbol.toStringTag]: 'CryptoKey',
algorithm: { name: 'Ed25519' },
type: 'public',
}),
});
});
it('uses the public key associated with the provided private key bytes', async () => {
expect.assertions(1);
const keyPair = await createKeyPairFromPrivateKeyBytes(MOCK_KEY_BYTES.slice(0, 32));
const publicKeyBytes = new Uint8Array(await crypto.subtle.exportKey('raw', keyPair.publicKey));
const expectedPublicKeyBytes = MOCK_KEY_BYTES.slice(32);
expect(publicKeyBytes).toEqual(expectedPublicKeyBytes);
});
it('errors when the byte array is not 32 bytes', async () => {
expect.assertions(1);
await expect(createKeyPairFromPrivateKeyBytes(MOCK_KEY_BYTES.slice(0, 31))).rejects.toThrow(
new SolanaError(SOLANA_ERROR__KEYS__INVALID_PRIVATE_KEY_BYTE_LENGTH, { actualLength: 31 }),
);
});
});
});
7 changes: 6 additions & 1 deletion packages/keys/src/__typetests__/key-pair-typetests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ReadonlyUint8Array } from '@solana/codecs-core';

import { createKeyPairFromBytes } from '../key-pair';
import { createKeyPairFromBytes, createKeyPairFromPrivateKeyBytes } from '../key-pair';

createKeyPairFromBytes(new Uint8Array()) satisfies Promise<CryptoKeyPair>;
createKeyPairFromBytes(new Uint8Array() as ReadonlyUint8Array) satisfies Promise<CryptoKeyPair>;
createKeyPairFromBytes(new Uint8Array(), true) satisfies Promise<CryptoKeyPair>;

createKeyPairFromPrivateKeyBytes(new Uint8Array()) satisfies Promise<CryptoKeyPair>;
createKeyPairFromPrivateKeyBytes(new Uint8Array() as ReadonlyUint8Array) satisfies Promise<CryptoKeyPair>;
createKeyPairFromPrivateKeyBytes(new Uint8Array(), true) satisfies Promise<CryptoKeyPair>;
17 changes: 17 additions & 0 deletions packages/keys/src/key-pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@solana/errors';

import { createPrivateKeyFromBytes } from './private-key';
import { getPublicKeyFromPrivateKey } from './public-key';
import { signBytes, verifySignature } from './signatures';

export async function generateKeyPair(): Promise<CryptoKeyPair> {
Expand Down Expand Up @@ -41,3 +42,19 @@ export async function createKeyPairFromBytes(bytes: ReadonlyUint8Array, extracta

return { privateKey, publicKey } as CryptoKeyPair;
}

export async function createKeyPairFromPrivateKeyBytes(
bytes: ReadonlyUint8Array,
extractable: boolean = false,
): Promise<CryptoKeyPair> {
const promises = [createPrivateKeyFromBytes(bytes, true)];

if (!extractable) {
promises.push(createPrivateKeyFromBytes(bytes, false));
}

const [extractablePrivateKey, privateKey] = await Promise.all(promises);
const publicKey = await getPublicKeyFromPrivateKey(extractablePrivateKey, true);

return extractable ? { privateKey: extractablePrivateKey, publicKey } : { privateKey, publicKey };
}

0 comments on commit b738776

Please sign in to comment.