2018-09-30 18:42:45 -07:00
|
|
|
import BN from 'bn.js';
|
|
|
|
import bs58 from 'bs58';
|
2021-07-08 18:01:11 -07:00
|
|
|
import {Buffer} from 'buffer';
|
2022-08-25 13:34:11 -07:00
|
|
|
import {sha256} from '@noble/hashes/sha256';
|
2018-09-30 18:42:45 -07:00
|
|
|
|
2022-08-28 12:11:34 -07:00
|
|
|
import {isOnCurve} from './utils/ed25519';
|
2022-08-11 02:10:11 -07:00
|
|
|
import {Struct, SOLANA_SCHEMA} from './utils/borsh-schema';
|
|
|
|
import {toBuffer} from './utils/to-buffer';
|
2021-03-28 20:00:56 -07:00
|
|
|
|
2020-10-13 13:40:38 -07:00
|
|
|
/**
|
|
|
|
* Maximum length of derived pubkey seed
|
|
|
|
*/
|
|
|
|
export const MAX_SEED_LENGTH = 32;
|
|
|
|
|
2022-08-14 09:19:06 -07:00
|
|
|
/**
|
|
|
|
* Size of public key in bytes
|
|
|
|
*/
|
|
|
|
export const PUBLIC_KEY_LENGTH = 32;
|
|
|
|
|
2021-09-13 14:37:18 -07:00
|
|
|
/**
|
|
|
|
* Value to be converted into public key
|
|
|
|
*/
|
|
|
|
export type PublicKeyInitData =
|
2021-05-18 10:33:06 -07:00
|
|
|
| number
|
|
|
|
| string
|
|
|
|
| Uint8Array
|
|
|
|
| Array<number>
|
|
|
|
| PublicKeyData;
|
|
|
|
|
2021-09-13 14:37:18 -07:00
|
|
|
/**
|
|
|
|
* JSON object representation of PublicKey class
|
|
|
|
*/
|
|
|
|
export type PublicKeyData = {
|
2021-05-18 10:33:06 -07:00
|
|
|
/** @internal */
|
|
|
|
_bn: BN;
|
|
|
|
};
|
|
|
|
|
|
|
|
function isPublicKeyData(value: PublicKeyInitData): value is PublicKeyData {
|
|
|
|
return (value as PublicKeyData)._bn !== undefined;
|
|
|
|
}
|
|
|
|
|
2022-09-06 20:43:22 -07:00
|
|
|
// local counter used by PublicKey.unique()
|
|
|
|
let uniquePublicKeyCounter = 1;
|
|
|
|
|
2018-09-30 18:42:45 -07:00
|
|
|
/**
|
|
|
|
* A public key
|
|
|
|
*/
|
2021-05-18 10:33:06 -07:00
|
|
|
export class PublicKey extends Struct {
|
2021-03-14 20:01:35 -07:00
|
|
|
/** @internal */
|
2018-09-30 18:42:45 -07:00
|
|
|
_bn: BN;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new PublicKey object
|
2021-03-14 20:01:35 -07:00
|
|
|
* @param value ed25519 public key as buffer or base-58 encoded string
|
2018-09-30 18:42:45 -07:00
|
|
|
*/
|
2021-05-18 10:33:06 -07:00
|
|
|
constructor(value: PublicKeyInitData) {
|
|
|
|
super({});
|
|
|
|
if (isPublicKeyData(value)) {
|
|
|
|
this._bn = value._bn;
|
2018-10-24 07:58:25 -07:00
|
|
|
} else {
|
2021-05-18 10:33:06 -07:00
|
|
|
if (typeof value === 'string') {
|
|
|
|
// assume base 58 encoding by default
|
|
|
|
const decoded = bs58.decode(value);
|
2022-08-14 09:19:06 -07:00
|
|
|
if (decoded.length != PUBLIC_KEY_LENGTH) {
|
2021-05-18 10:33:06 -07:00
|
|
|
throw new Error(`Invalid public key input`);
|
|
|
|
}
|
|
|
|
this._bn = new BN(decoded);
|
|
|
|
} else {
|
|
|
|
this._bn = new BN(value);
|
|
|
|
}
|
2018-09-30 21:27:55 -07:00
|
|
|
|
2021-05-18 10:33:06 -07:00
|
|
|
if (this._bn.byteLength() > 32) {
|
|
|
|
throw new Error(`Invalid public key input`);
|
|
|
|
}
|
2018-09-30 18:42:45 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-06 20:43:22 -07:00
|
|
|
/**
|
|
|
|
* Returns a unique PublicKey for tests and benchmarks using acounter
|
|
|
|
*/
|
|
|
|
static unique(): PublicKey {
|
|
|
|
const key = new PublicKey(uniquePublicKeyCounter);
|
|
|
|
uniquePublicKeyCounter += 1;
|
|
|
|
return new PublicKey(key.toBuffer());
|
|
|
|
}
|
|
|
|
|
2021-04-29 07:04:33 -07:00
|
|
|
/**
|
2022-10-12 19:04:22 -07:00
|
|
|
* Default public key value. The base58-encoded string representation is all ones (as seen below)
|
|
|
|
* The underlying BN number is 32 bytes that are all zeros
|
2021-04-29 07:04:33 -07:00
|
|
|
*/
|
|
|
|
static default: PublicKey = new PublicKey('11111111111111111111111111111111');
|
|
|
|
|
2018-09-30 18:42:45 -07:00
|
|
|
/**
|
|
|
|
* Checks if two publicKeys are equal
|
|
|
|
*/
|
|
|
|
equals(publicKey: PublicKey): boolean {
|
|
|
|
return this._bn.eq(publicKey._bn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the base-58 representation of the public key
|
|
|
|
*/
|
|
|
|
toBase58(): string {
|
2021-03-28 20:00:56 -07:00
|
|
|
return bs58.encode(this.toBytes());
|
|
|
|
}
|
|
|
|
|
2021-12-20 13:16:32 -08:00
|
|
|
toJSON(): string {
|
|
|
|
return this.toBase58();
|
|
|
|
}
|
|
|
|
|
2021-03-28 20:00:56 -07:00
|
|
|
/**
|
|
|
|
* Return the byte array representation of the public key
|
|
|
|
*/
|
|
|
|
toBytes(): Uint8Array {
|
|
|
|
return this.toBuffer();
|
2018-09-30 18:42:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-09-30 22:02:31 -07:00
|
|
|
* Return the Buffer representation of the public key
|
2018-09-30 18:42:45 -07:00
|
|
|
*/
|
|
|
|
toBuffer(): Buffer {
|
2018-09-30 20:40:59 -07:00
|
|
|
const b = this._bn.toArrayLike(Buffer);
|
2022-08-14 09:19:06 -07:00
|
|
|
if (b.length === PUBLIC_KEY_LENGTH) {
|
2018-09-30 18:42:45 -07:00
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
2018-10-02 21:01:58 -07:00
|
|
|
const zeroPad = Buffer.alloc(32);
|
2018-10-23 12:25:02 -07:00
|
|
|
b.copy(zeroPad, 32 - b.length);
|
2018-09-30 18:42:45 -07:00
|
|
|
return zeroPad;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-14 20:01:35 -07:00
|
|
|
* Return the base-58 representation of the public key
|
2018-09-30 18:42:45 -07:00
|
|
|
*/
|
|
|
|
toString(): string {
|
|
|
|
return this.toBase58();
|
|
|
|
}
|
2019-12-20 15:14:37 -08:00
|
|
|
|
|
|
|
/**
|
2020-05-26 17:46:49 -07:00
|
|
|
* Derive a public key from another key, a seed, and a program ID.
|
2021-07-07 19:23:36 -07:00
|
|
|
* The program ID will also serve as the owner of the public key, giving
|
|
|
|
* it permission to write data to the account.
|
2019-12-20 15:14:37 -08:00
|
|
|
*/
|
2021-10-21 18:27:50 -07:00
|
|
|
/* eslint-disable require-await */
|
2020-03-16 02:44:12 -07:00
|
|
|
static async createWithSeed(
|
2019-12-23 10:46:49 -08:00
|
|
|
fromPublicKey: PublicKey,
|
|
|
|
seed: string,
|
|
|
|
programId: PublicKey,
|
2020-03-16 02:44:12 -07:00
|
|
|
): Promise<PublicKey> {
|
2019-12-23 10:46:49 -08:00
|
|
|
const buffer = Buffer.concat([
|
|
|
|
fromPublicKey.toBuffer(),
|
|
|
|
Buffer.from(seed),
|
|
|
|
programId.toBuffer(),
|
|
|
|
]);
|
2022-08-25 13:34:11 -07:00
|
|
|
const publicKeyBytes = sha256(buffer);
|
|
|
|
return new PublicKey(publicKeyBytes);
|
2019-12-20 15:14:37 -08:00
|
|
|
}
|
2020-05-26 17:46:49 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Derive a program address from seeds and a program ID.
|
|
|
|
*/
|
2021-10-21 18:27:50 -07:00
|
|
|
/* eslint-disable require-await */
|
2022-04-18 07:17:00 -07:00
|
|
|
static createProgramAddressSync(
|
2020-06-29 17:05:05 -07:00
|
|
|
seeds: Array<Buffer | Uint8Array>,
|
2020-05-26 17:46:49 -07:00
|
|
|
programId: PublicKey,
|
2022-04-18 07:17:00 -07:00
|
|
|
): PublicKey {
|
2020-05-26 17:46:49 -07:00
|
|
|
let buffer = Buffer.alloc(0);
|
|
|
|
seeds.forEach(function (seed) {
|
2020-10-13 13:40:38 -07:00
|
|
|
if (seed.length > MAX_SEED_LENGTH) {
|
2021-05-10 15:55:51 -07:00
|
|
|
throw new TypeError(`Max seed length exceeded`);
|
2020-10-13 13:40:38 -07:00
|
|
|
}
|
2021-03-28 20:00:56 -07:00
|
|
|
buffer = Buffer.concat([buffer, toBuffer(seed)]);
|
2020-05-26 17:46:49 -07:00
|
|
|
});
|
|
|
|
buffer = Buffer.concat([
|
|
|
|
buffer,
|
|
|
|
programId.toBuffer(),
|
|
|
|
Buffer.from('ProgramDerivedAddress'),
|
|
|
|
]);
|
2022-08-25 13:34:11 -07:00
|
|
|
const publicKeyBytes = sha256(buffer);
|
2022-08-28 12:11:34 -07:00
|
|
|
if (isOnCurve(publicKeyBytes)) {
|
2020-08-06 07:10:54 -07:00
|
|
|
throw new Error(`Invalid seeds, address must fall off the curve`);
|
|
|
|
}
|
|
|
|
return new PublicKey(publicKeyBytes);
|
2020-05-26 17:46:49 -07:00
|
|
|
}
|
2020-08-06 07:10:54 -07:00
|
|
|
|
2022-04-18 07:17:00 -07:00
|
|
|
/**
|
|
|
|
* Async version of createProgramAddressSync
|
|
|
|
* For backwards compatibility
|
|
|
|
*/
|
|
|
|
/* eslint-disable require-await */
|
|
|
|
static async createProgramAddress(
|
|
|
|
seeds: Array<Buffer | Uint8Array>,
|
|
|
|
programId: PublicKey,
|
|
|
|
): Promise<PublicKey> {
|
|
|
|
return this.createProgramAddressSync(seeds, programId);
|
|
|
|
}
|
|
|
|
|
2020-08-06 07:10:54 -07:00
|
|
|
/**
|
|
|
|
* Find a valid program address
|
|
|
|
*
|
|
|
|
* Valid program addresses must fall off the ed25519 curve. This function
|
|
|
|
* iterates a nonce until it finds one that when combined with the seeds
|
|
|
|
* results in a valid program address.
|
|
|
|
*/
|
2022-04-18 07:17:00 -07:00
|
|
|
static findProgramAddressSync(
|
2020-08-06 07:10:54 -07:00
|
|
|
seeds: Array<Buffer | Uint8Array>,
|
|
|
|
programId: PublicKey,
|
2022-04-18 07:17:00 -07:00
|
|
|
): [PublicKey, number] {
|
2020-08-06 07:10:54 -07:00
|
|
|
let nonce = 255;
|
|
|
|
let address;
|
|
|
|
while (nonce != 0) {
|
|
|
|
try {
|
|
|
|
const seedsWithNonce = seeds.concat(Buffer.from([nonce]));
|
2022-04-18 07:17:00 -07:00
|
|
|
address = this.createProgramAddressSync(seedsWithNonce, programId);
|
2020-08-06 07:10:54 -07:00
|
|
|
} catch (err) {
|
2021-05-10 15:55:51 -07:00
|
|
|
if (err instanceof TypeError) {
|
|
|
|
throw err;
|
|
|
|
}
|
2020-08-06 07:10:54 -07:00
|
|
|
nonce--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return [address, nonce];
|
|
|
|
}
|
|
|
|
throw new Error(`Unable to find a viable program address nonce`);
|
|
|
|
}
|
2021-04-27 01:33:10 -07:00
|
|
|
|
2022-04-18 07:17:00 -07:00
|
|
|
/**
|
|
|
|
* Async version of findProgramAddressSync
|
|
|
|
* For backwards compatibility
|
|
|
|
*/
|
|
|
|
static async findProgramAddress(
|
|
|
|
seeds: Array<Buffer | Uint8Array>,
|
|
|
|
programId: PublicKey,
|
|
|
|
): Promise<[PublicKey, number]> {
|
|
|
|
return this.findProgramAddressSync(seeds, programId);
|
|
|
|
}
|
|
|
|
|
2021-04-27 01:33:10 -07:00
|
|
|
/**
|
|
|
|
* Check that a pubkey is on the ed25519 curve.
|
|
|
|
*/
|
2022-04-25 05:42:41 -07:00
|
|
|
static isOnCurve(pubkeyData: PublicKeyInitData): boolean {
|
|
|
|
const pubkey = new PublicKey(pubkeyData);
|
2022-08-28 12:11:34 -07:00
|
|
|
return isOnCurve(pubkey.toBytes());
|
2021-04-27 01:33:10 -07:00
|
|
|
}
|
2020-08-06 07:10:54 -07:00
|
|
|
}
|
|
|
|
|
2021-05-18 10:33:06 -07:00
|
|
|
SOLANA_SCHEMA.set(PublicKey, {
|
|
|
|
kind: 'struct',
|
|
|
|
fields: [['_bn', 'u256']],
|
|
|
|
});
|