Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: land program addresses off-curve #11355

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions web3.js/module.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>,
Expand All @@ -28,6 +29,10 @@ declare module '@solana/web3.js' {
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): Promise<PublicKey>;
static findProgramAddress(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): Promise<PublicKeyNonce>;
equals(publickey: PublicKey): boolean;
toBase58(): string;
toBuffer(): Buffer;
Expand Down
113 changes: 111 additions & 2 deletions web3.js/src/publickey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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<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);
}
28 changes: 23 additions & 5 deletions web3.js/test/publickey.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -255,7 +255,7 @@ test('createProgramAddress', async () => {
);
expect(
programAddress.equals(
new PublicKey('A8mYnN8Pfx7Nn6f8RoQgsPNtAGAWmmKSBCDfyDvE6sXF'),
new PublicKey('7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7'),
),
).toBe(true);

Expand All @@ -265,7 +265,7 @@ test('createProgramAddress', async () => {
);
expect(
programAddress.equals(
new PublicKey('CawYq8Rmj4JRR992wVnGEFUjMEkmtmcFgEL4iS1qPczu'),
new PublicKey('HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds'),
),
).toBe(true);

Expand All @@ -275,7 +275,7 @@ test('createProgramAddress', async () => {
);
expect(
programAddress.equals(
new PublicKey('4ak7qJacCKMAGP8xJtDkg2VYZh5QKExa71ijMDjZGQyb'),
new PublicKey('GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K'),
),
).toBe(true);

Expand All @@ -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);
});