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