# src/publickey.js

    // @flow

    import BN from 'bn.js';
    import bs58 from 'bs58';
    import nacl from 'tweetnacl';
    import {sha256} from 'crypto-hash';
    import {Buffer} from 'buffer';

    let naclLowLevel = nacl.lowlevel;

    type PublicKeyNonce = [PublicKey, number]; // This type exists to workaround an esdoc parse error

    /**
     * Maximum length of derived pubkey seed
     */
    export const MAX_SEED_LENGTH = 32;

    /**
     * A public key
     */
    export class PublicKey {
      _bn: BN;

      /**
       * Create a new PublicKey object
       */
      constructor(value: number | string | Buffer | Uint8Array | Array<number>) {
        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`);
        }
      }

      /**
       * 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.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;
      }

      /**
       * Returns a string representation of the public key
       */
      toString(): string {
        return this.toBase58();
      }

      /**
       * Derive a public key from another key, a seed, and a program ID.
       */
      static async createWithSeed(
        fromPublicKey: PublicKey,
        seed: string,
        programId: PublicKey,
      ): Promise<PublicKey> {
        const buffer = Buffer.concat([
          fromPublicKey.toBuffer(),
          Buffer.from(seed),
          programId.toBuffer(),
        ]);
        const hash = await sha256(new Uint8Array(buffer));
        return new PublicKey(Buffer.from(hash, 'hex'));
      }

      /**
       * Derive a program address from seeds and a program ID.
       */
      static async createProgramAddress(
        seeds: Array<Buffer | Uint8Array>,
        programId: PublicKey,
      ): Promise<PublicKey> {
        let buffer = Buffer.alloc(0);
        seeds.forEach(function (seed) {
          if (seed.length > MAX_SEED_LENGTH) {
            throw new Error(`Max seed length exceeded`);
          }
          buffer = Buffer.concat([buffer, Buffer.from(seed)]);
        });
        buffer = Buffer.concat([
          buffer,
          programId.toBuffer(),
          Buffer.from('ProgramDerivedAddress'),
        ]);
        let hash = await sha256(new Uint8Array(buffer));
        let publicKeyBytes = new BN(hash, 16).toArray(null, 32);
        if (is_on_curve(publicKeyBytes)) {
          throw new Error(`Invalid seeds, address must fall off the curve`);
        }
        return new PublicKey(publicKeyBytes);
      }

      /**
       * 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 async findProgramAddress(
        seeds: Array<Buffer | Uint8Array>,
        programId: PublicKey,
      ): Promise<PublicKeyNonce> {
        let nonce = 255;
        let address;
        while (nonce != 0) {
          try {
            const seedsWithNonce = seeds.concat(Buffer.from([nonce]));
            address = await this.createProgramAddress(seedsWithNonce, programId);
          } catch (err) {
            nonce--;
            continue;
          }
          return [address, nonce];
        }
        throw new Error(`Unable to find a viable program address nonce`);
      }
    }

    // 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) {
      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, b) {
      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);
    }