import BN from 'bn.js'; import bs58 from 'bs58'; import {Buffer} from 'buffer'; import nacl from 'tweetnacl'; import {sha256} from '@ethersproject/sha2'; import {Struct, SOLANA_SCHEMA} from './util/borsh-schema'; import {toBuffer} from './util/to-buffer'; /** * Maximum length of derived pubkey seed */ export const MAX_SEED_LENGTH = 32; /** * Value to be converted into public key */ export type PublicKeyInitData = | number | string | Buffer | Uint8Array | Array | PublicKeyData; /** * JSON object representation of PublicKey class */ export type PublicKeyData = { /** @internal */ _bn: BN; }; function isPublicKeyData(value: PublicKeyInitData): value is PublicKeyData { return (value as PublicKeyData)._bn !== undefined; } /** * A public key */ export class PublicKey extends Struct { /** @internal */ _bn: BN; /** * Create a new PublicKey object * @param value ed25519 public key as buffer or base-58 encoded string */ constructor(value: PublicKeyInitData) { super({}); if (isPublicKeyData(value)) { this._bn = value._bn; } else { 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); } if (this._bn.byteLength() > 32) { throw new Error(`Invalid public key input`); } } } /** * Default public key value. (All zeros) */ static default: PublicKey = new PublicKey('11111111111111111111111111111111'); /** * 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 { return bs58.encode(this.toBytes()); } toJSON(): string { return this.toBase58(); } /** * Return the byte array representation of the public key */ toBytes(): Uint8Array { return this.toBuffer(); } /** * Return the Buffer representation of the public key */ toBuffer(): Buffer { const b = this._bn.toArrayLike(Buffer); if (b.length === 32) { return b; } const zeroPad = Buffer.alloc(32); b.copy(zeroPad, 32 - b.length); return zeroPad; } /** * Return the base-58 representation of the public key */ toString(): string { return this.toBase58(); } /** * Derive a public key from another key, a seed, and a program ID. * The program ID will also serve as the owner of the public key, giving * it permission to write data to the account. */ /* eslint-disable require-await */ static async createWithSeed( fromPublicKey: PublicKey, seed: string, programId: PublicKey, ): Promise { const buffer = Buffer.concat([ fromPublicKey.toBuffer(), Buffer.from(seed), programId.toBuffer(), ]); const hash = sha256(new Uint8Array(buffer)).slice(2); return new PublicKey(Buffer.from(hash, 'hex')); } /** * Derive a program address from seeds and a program ID. */ /* eslint-disable require-await */ static createProgramAddressSync( seeds: Array, programId: PublicKey, ): PublicKey { let buffer = Buffer.alloc(0); seeds.forEach(function (seed) { if (seed.length > MAX_SEED_LENGTH) { throw new TypeError(`Max seed length exceeded`); } buffer = Buffer.concat([buffer, toBuffer(seed)]); }); buffer = Buffer.concat([ buffer, programId.toBuffer(), Buffer.from('ProgramDerivedAddress'), ]); let hash = sha256(new Uint8Array(buffer)).slice(2); let publicKeyBytes = new BN(hash, 16).toArray(undefined, 32); if (is_on_curve(publicKeyBytes)) { throw new Error(`Invalid seeds, address must fall off the curve`); } return new PublicKey(publicKeyBytes); } /** * Async version of createProgramAddressSync * For backwards compatibility */ /* eslint-disable require-await */ static async createProgramAddress( seeds: Array, programId: PublicKey, ): Promise { return this.createProgramAddressSync(seeds, programId); } /** * 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. */ static findProgramAddressSync( seeds: Array, programId: PublicKey, ): [PublicKey, number] { let nonce = 255; let address; while (nonce != 0) { try { const seedsWithNonce = seeds.concat(Buffer.from([nonce])); address = this.createProgramAddressSync(seedsWithNonce, programId); } catch (err) { if (err instanceof TypeError) { throw err; } nonce--; continue; } return [address, nonce]; } throw new Error(`Unable to find a viable program address nonce`); } /** * Async version of findProgramAddressSync * For backwards compatibility */ static async findProgramAddress( seeds: Array, programId: PublicKey, ): Promise<[PublicKey, number]> { return this.findProgramAddressSync(seeds, programId); } /** * Check that a pubkey is on the ed25519 curve. */ static isOnCurve(pubkeyData: PublicKeyInitData): boolean { const pubkey = new PublicKey(pubkeyData); return is_on_curve(pubkey.toBytes()) == 1; } } SOLANA_SCHEMA.set(PublicKey, { kind: 'struct', fields: [['_bn', 'u256']], }); // @ts-ignore let naclLowLevel = nacl.lowlevel; // 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 function is_on_curve(p: any) { 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([ 0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83, ]); function neq25519(a: any, b: any) { 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); }