fix: land program addresses off-curve (#11355)
This commit is contained in:
		@@ -15,6 +15,7 @@ import * as BufferLayout from 'buffer-layout';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
declare module '@solana/web3.js' {
 | 
					declare module '@solana/web3.js' {
 | 
				
			||||||
  // === src/publickey.js ===
 | 
					  // === src/publickey.js ===
 | 
				
			||||||
 | 
					  declare export type PublicKeyNonce = [PublicKey, number];
 | 
				
			||||||
  declare export class PublicKey {
 | 
					  declare export class PublicKey {
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
      value: number | string | Buffer | Uint8Array | Array<number>,
 | 
					      value: number | string | Buffer | Uint8Array | Array<number>,
 | 
				
			||||||
@@ -28,6 +29,10 @@ declare module '@solana/web3.js' {
 | 
				
			|||||||
      seeds: Array<Buffer | Uint8Array>,
 | 
					      seeds: Array<Buffer | Uint8Array>,
 | 
				
			||||||
      programId: PublicKey,
 | 
					      programId: PublicKey,
 | 
				
			||||||
    ): Promise<PublicKey>;
 | 
					    ): Promise<PublicKey>;
 | 
				
			||||||
 | 
					    static findProgramAddress(
 | 
				
			||||||
 | 
					      seeds: Array<Buffer | Uint8Array>,
 | 
				
			||||||
 | 
					      programId: PublicKey,
 | 
				
			||||||
 | 
					    ): Promise<PublicKeyNonce>;
 | 
				
			||||||
    equals(publickey: PublicKey): boolean;
 | 
					    equals(publickey: PublicKey): boolean;
 | 
				
			||||||
    toBase58(): string;
 | 
					    toBase58(): string;
 | 
				
			||||||
    toBuffer(): Buffer;
 | 
					    toBuffer(): Buffer;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import BN from 'bn.js';
 | 
					import BN from 'bn.js';
 | 
				
			||||||
import bs58 from 'bs58';
 | 
					import bs58 from 'bs58';
 | 
				
			||||||
 | 
					import nacl from 'tweetnacl';
 | 
				
			||||||
import {sha256} from 'crypto-hash';
 | 
					import {sha256} from 'crypto-hash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//$FlowFixMe
 | 
				
			||||||
 | 
					let naclLowLevel = nacl.lowlevel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PublicKeyNonce = [PublicKey, number]; // This type exists to workaround an esdoc parse error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A public key
 | 
					 * A public key
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -104,7 +110,110 @@ export class PublicKey {
 | 
				
			|||||||
      Buffer.from('ProgramDerivedAddress'),
 | 
					      Buffer.from('ProgramDerivedAddress'),
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
    let hash = await sha256(new Uint8Array(buffer));
 | 
					    let hash = await sha256(new Uint8Array(buffer));
 | 
				
			||||||
    hash = await sha256(new Uint8Array(new BN(hash, 16).toBuffer()));
 | 
					    let publicKeyBytes = new BN(hash, 16).toBuffer();
 | 
				
			||||||
    return new PublicKey('0x' + hash);
 | 
					    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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -240,12 +240,12 @@ test('createProgramAddress', async () => {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let programAddress = await PublicKey.createProgramAddress(
 | 
					  let programAddress = await PublicKey.createProgramAddress(
 | 
				
			||||||
    [Buffer.from('', 'utf8')],
 | 
					    [Buffer.from('', 'utf8'), Buffer.from([1])],
 | 
				
			||||||
    programId,
 | 
					    programId,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  expect(
 | 
					  expect(
 | 
				
			||||||
    programAddress.equals(
 | 
					    programAddress.equals(
 | 
				
			||||||
      new PublicKey('CsdSsqp6Upkh2qajhZMBM8xT4GAyDNSmcV37g4pN8rsc'),
 | 
					      new PublicKey('3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT'),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  ).toBe(true);
 | 
					  ).toBe(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -255,7 +255,7 @@ test('createProgramAddress', async () => {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
  expect(
 | 
					  expect(
 | 
				
			||||||
    programAddress.equals(
 | 
					    programAddress.equals(
 | 
				
			||||||
      new PublicKey('A8mYnN8Pfx7Nn6f8RoQgsPNtAGAWmmKSBCDfyDvE6sXF'),
 | 
					      new PublicKey('7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7'),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  ).toBe(true);
 | 
					  ).toBe(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -265,7 +265,7 @@ test('createProgramAddress', async () => {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
  expect(
 | 
					  expect(
 | 
				
			||||||
    programAddress.equals(
 | 
					    programAddress.equals(
 | 
				
			||||||
      new PublicKey('CawYq8Rmj4JRR992wVnGEFUjMEkmtmcFgEL4iS1qPczu'),
 | 
					      new PublicKey('HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds'),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  ).toBe(true);
 | 
					  ).toBe(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -275,7 +275,7 @@ test('createProgramAddress', async () => {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
  expect(
 | 
					  expect(
 | 
				
			||||||
    programAddress.equals(
 | 
					    programAddress.equals(
 | 
				
			||||||
      new PublicKey('4ak7qJacCKMAGP8xJtDkg2VYZh5QKExa71ijMDjZGQyb'),
 | 
					      new PublicKey('GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K'),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  ).toBe(true);
 | 
					  ).toBe(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -285,3 +285,21 @@ test('createProgramAddress', async () => {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
  expect(programAddress.equals(programAddress2)).toBe(false);
 | 
					  expect(programAddress.equals(programAddress2)).toBe(false);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('findProgramAddress', async () => {
 | 
				
			||||||
 | 
					  const programId = new PublicKey(
 | 
				
			||||||
 | 
					    'BPFLoader1111111111111111111111111111111111',
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  let [programAddress, nonce] = await PublicKey.findProgramAddress(
 | 
				
			||||||
 | 
					    [Buffer.from('', 'utf8')],
 | 
				
			||||||
 | 
					    programId,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  expect(
 | 
				
			||||||
 | 
					    programAddress.equals(
 | 
				
			||||||
 | 
					      await PublicKey.createProgramAddress(
 | 
				
			||||||
 | 
					        [Buffer.from('', 'utf8'), Buffer.from([nonce])],
 | 
				
			||||||
 | 
					        programId,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					  ).toBe(true);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user