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' {
|
||||
// === src/publickey.js ===
|
||||
declare export type PublicKeyNonce = [PublicKey, number];
|
||||
declare export class PublicKey {
|
||||
constructor(
|
||||
value: number | string | Buffer | Uint8Array | Array<number>,
|
||||
@ -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;
|
||||
|
@ -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<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(
|
||||
[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);
|
||||
});
|
||||
|
Reference in New Issue
Block a user