From dbee74db7a4404ae7578d89db4a6137f38eac231 Mon Sep 17 00:00:00 2001 From: Jack May Date: Fri, 31 Jul 2020 16:29:52 -0700 Subject: [PATCH] fix: land program addresses off-curve --- web3.js/module.flow.js | 5 ++ web3.js/src/publickey.js | 113 ++++++++++++++++++++++++++++++++- web3.js/test/publickey.test.js | 28 ++++++-- 3 files changed, 139 insertions(+), 7 deletions(-) diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 8d2cc9b8463baa..40e7b5dd2f1e4c 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -15,6 +15,7 @@ import * as BufferLayout from 'buffer-layout'; declare module '@solana/web3.js' { // === src/publickey.js === + declare export type PublicKeyNonce = [PublicKey, number]; declare export class PublicKey { constructor( value: number | string | Buffer | Uint8Array | Array, @@ -28,6 +29,10 @@ declare module '@solana/web3.js' { seeds: Array, programId: PublicKey, ): Promise; + static findProgramAddress( + seeds: Array, + programId: PublicKey, + ): Promise; equals(publickey: PublicKey): boolean; toBase58(): string; toBuffer(): Buffer; diff --git a/web3.js/src/publickey.js b/web3.js/src/publickey.js index bd1d0fda211957..9c1e89382c1eb7 100644 --- a/web3.js/src/publickey.js +++ b/web3.js/src/publickey.js @@ -2,8 +2,14 @@ import BN from 'bn.js'; import bs58 from 'bs58'; +import nacl from 'tweetnacl'; 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 */ @@ -104,7 +110,110 @@ export class PublicKey { Buffer.from('ProgramDerivedAddress'), ]); let hash = await sha256(new Uint8Array(buffer)); - hash = await sha256(new Uint8Array(new BN(hash, 16).toBuffer())); - return new PublicKey('0x' + hash); + let publicKeyBytes = new BN(hash, 16).toBuffer(); + 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, + programId: PublicKey, + ): Promise { + 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); } diff --git a/web3.js/test/publickey.test.js b/web3.js/test/publickey.test.js index bfa638adc1fef9..fbf60462f17f2b 100644 --- a/web3.js/test/publickey.test.js +++ b/web3.js/test/publickey.test.js @@ -240,12 +240,12 @@ test('createProgramAddress', async () => { ); let programAddress = await PublicKey.createProgramAddress( - [Buffer.from('', 'utf8')], + [Buffer.from('', 'utf8'), Buffer.from([1])], programId, ); expect( programAddress.equals( - new PublicKey('CsdSsqp6Upkh2qajhZMBM8xT4GAyDNSmcV37g4pN8rsc'), + new PublicKey('3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT'), ), ).toBe(true); @@ -255,7 +255,7 @@ test('createProgramAddress', async () => { ); expect( programAddress.equals( - new PublicKey('A8mYnN8Pfx7Nn6f8RoQgsPNtAGAWmmKSBCDfyDvE6sXF'), + new PublicKey('7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7'), ), ).toBe(true); @@ -265,7 +265,7 @@ test('createProgramAddress', async () => { ); expect( programAddress.equals( - new PublicKey('CawYq8Rmj4JRR992wVnGEFUjMEkmtmcFgEL4iS1qPczu'), + new PublicKey('HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds'), ), ).toBe(true); @@ -275,7 +275,7 @@ test('createProgramAddress', async () => { ); expect( programAddress.equals( - new PublicKey('4ak7qJacCKMAGP8xJtDkg2VYZh5QKExa71ijMDjZGQyb'), + new PublicKey('GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K'), ), ).toBe(true); @@ -285,3 +285,21 @@ test('createProgramAddress', async () => { ); 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); +});