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';
|
2020-08-06 07:10:54 -07:00
|
|
|
import nacl from 'tweetnacl';
|
2021-10-25 08:51:24 -07:00
|
|
|
import {sha256} from '@ethersproject/sha2';
|
2018-09-30 18:42:45 -07:00
|
|
|
|
2021-05-18 10:33:06 -07:00
|
|
|
import {Struct, SOLANA_SCHEMA} from './util/borsh-schema';
|
2021-03-28 20:00:56 -07:00
|
|
|
import {toBuffer} from './util/to-buffer';
|
|
|
|
|
2020-10-13 13:40:38 -07:00
|
|
|
/**
|
|
|
|
* Maximum length of derived pubkey seed
|
|
|
|
*/
|
|
|
|
export const MAX_SEED_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
|
|
|
|
| Buffer
|
|
|
|
| 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;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
if (decoded.length != 32) {
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-29 07:04:33 -07:00
|
|
|
/**
|
|
|
|
* Default public key value. (All zeros)
|
|
|
|
*/
|
|
|
|
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);
|
2018-09-30 18:42:45 -07:00
|
|
|
if (b.length === 32) {
|
|
|
|
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(),
|
|
|
|
]);
|
2021-10-25 08:51:24 -07:00
|
|
|
const hash = sha256(new Uint8Array(buffer)).slice(2);
|
2020-08-14 00:02:00 -07:00
|
|
|
return new PublicKey(Buffer.from(hash, 'hex'));
|
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'),
|
|
|
|
]);
|
2021-10-25 08:51:24 -07:00
|
|
|
let hash = sha256(new Uint8Array(buffer)).slice(2);
|
2021-03-14 19:42:53 -07:00
|
|
|
let publicKeyBytes = new BN(hash, 16).toArray(undefined, 32);
|
2020-08-06 07:10:54 -07:00
|
|
|
if (is_on_curve(publicKeyBytes)) {
|
|
|
|
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);
|
|
|
|
return is_on_curve(pubkey.toBytes()) == 1;
|
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']],
|
|
|
|
});
|
|
|
|
|
2021-03-14 20:01:35 -07:00
|
|
|
// @ts-ignore
|
|
|
|
let naclLowLevel = nacl.lowlevel;
|
|
|
|
|
2020-08-06 07:10:54 -07:00
|
|
|
// Check that a pubkey is on the curve.
|
|
|
|
// This function and its dependents were sourced from:
|
|
|
|
// https://github.com/dchest/tweetnacl-js/blob/f1ec050ceae0861f34280e62498b1d3ed9c350c6/nacl.js#L792
|
2021-03-14 20:01:35 -07:00
|
|
|
function is_on_curve(p: any) {
|
2020-08-06 07:10:54 -07:00
|
|
|
var r = [
|
|
|
|
naclLowLevel.gf(),
|
|
|
|
naclLowLevel.gf(),
|
|
|
|
naclLowLevel.gf(),
|
|
|
|
naclLowLevel.gf(),
|
|
|
|
];
|
|
|
|
|
|
|
|
var t = naclLowLevel.gf(),
|
|
|
|
chk = naclLowLevel.gf(),
|
|
|
|
num = naclLowLevel.gf(),
|
|
|
|
den = naclLowLevel.gf(),
|
|
|
|
den2 = naclLowLevel.gf(),
|
|
|
|
den4 = naclLowLevel.gf(),
|
|
|
|
den6 = naclLowLevel.gf();
|
|
|
|
|
|
|
|
naclLowLevel.set25519(r[2], gf1);
|
|
|
|
naclLowLevel.unpack25519(r[1], p);
|
|
|
|
naclLowLevel.S(num, r[1]);
|
|
|
|
naclLowLevel.M(den, num, naclLowLevel.D);
|
|
|
|
naclLowLevel.Z(num, num, r[2]);
|
|
|
|
naclLowLevel.A(den, r[2], den);
|
|
|
|
|
|
|
|
naclLowLevel.S(den2, den);
|
|
|
|
naclLowLevel.S(den4, den2);
|
|
|
|
naclLowLevel.M(den6, den4, den2);
|
|
|
|
naclLowLevel.M(t, den6, num);
|
|
|
|
naclLowLevel.M(t, t, den);
|
|
|
|
|
|
|
|
naclLowLevel.pow2523(t, t);
|
|
|
|
naclLowLevel.M(t, t, num);
|
|
|
|
naclLowLevel.M(t, t, den);
|
|
|
|
naclLowLevel.M(t, t, den);
|
|
|
|
naclLowLevel.M(r[0], t, den);
|
|
|
|
|
|
|
|
naclLowLevel.S(chk, r[0]);
|
|
|
|
naclLowLevel.M(chk, chk, den);
|
|
|
|
if (neq25519(chk, num)) naclLowLevel.M(r[0], r[0], I);
|
|
|
|
|
|
|
|
naclLowLevel.S(chk, r[0]);
|
|
|
|
naclLowLevel.M(chk, chk, den);
|
|
|
|
if (neq25519(chk, num)) return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
let gf1 = naclLowLevel.gf([1]);
|
|
|
|
let I = naclLowLevel.gf([
|
2021-05-24 20:53:16 -07:00
|
|
|
0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7,
|
|
|
|
0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83,
|
2020-08-06 07:10:54 -07:00
|
|
|
]);
|
2021-03-14 20:01:35 -07:00
|
|
|
function neq25519(a: any, b: any) {
|
2020-08-06 07:10:54 -07:00
|
|
|
var c = new Uint8Array(32),
|
|
|
|
d = new Uint8Array(32);
|
|
|
|
naclLowLevel.pack25519(c, a);
|
|
|
|
naclLowLevel.pack25519(d, b);
|
|
|
|
return naclLowLevel.crypto_verify_32(c, 0, d, 0);
|
2018-09-30 18:42:45 -07:00
|
|
|
}
|