solana/web3.js/src/publickey.js

223 lines
5.3 KiB
JavaScript
Raw Normal View History

2018-09-30 18:42:45 -07:00
// @flow
import BN from 'bn.js';
import bs58 from 'bs58';
import nacl from 'tweetnacl';
2020-03-16 02:44:12 -07:00
import {sha256} from 'crypto-hash';
import {Buffer} from 'buffer';
2018-09-30 18:42:45 -07:00
let naclLowLevel = nacl.lowlevel;
type PublicKeyNonce = [PublicKey, number]; // This type exists to workaround an esdoc parse error
/**
* Maximum length of derived pubkey seed
*/
export const MAX_SEED_LENGTH = 32;
2018-09-30 18:42:45 -07:00
/**
* A public key
*/
export class PublicKey {
_bn: BN;
/**
* Create a new PublicKey object
*/
constructor(value: number | string | Buffer | Uint8Array | Array<number>) {
if (typeof value === 'string') {
2020-08-14 00:02:00 -07:00
// assume base 58 encoding by default
const decoded = bs58.decode(value);
if (decoded.length != 32) {
throw new Error(`Invalid public key input`);
}
2020-08-14 00:02:00 -07:00
this._bn = new BN(decoded);
} else {
this._bn = new BN(value);
2018-09-30 18:42:45 -07:00
}
2018-09-30 18:42:45 -07:00
if (this._bn.byteLength() > 32) {
throw new Error(`Invalid public key input`);
}
}
/**
* Checks if two publicKeys are equal
*/
equals(publicKey: PublicKey): boolean {
return this._bn.eq(publicKey._bn);
}
/**
* Return the base-58 representation of the public key
*/
toBase58(): string {
return bs58.encode(this.toBuffer());
}
/**
2018-09-30 22:02:31 -07:00
* Return the Buffer representation of the public key
2018-09-30 18:42:45 -07:00
*/
toBuffer(): Buffer {
2018-09-30 20:40:59 -07:00
const b = this._bn.toArrayLike(Buffer);
2018-09-30 18:42:45 -07:00
if (b.length === 32) {
return b;
}
const zeroPad = Buffer.alloc(32);
b.copy(zeroPad, 32 - b.length);
2018-09-30 18:42:45 -07:00
return zeroPad;
}
/**
* Returns a string representation of the public key
*/
toString(): string {
return this.toBase58();
}
/**
2020-05-26 17:46:49 -07:00
* Derive a public key from another key, a seed, and a program ID.
*/
2020-03-16 02:44:12 -07:00
static async createWithSeed(
fromPublicKey: PublicKey,
seed: string,
programId: PublicKey,
2020-03-16 02:44:12 -07:00
): Promise<PublicKey> {
const buffer = Buffer.concat([
fromPublicKey.toBuffer(),
Buffer.from(seed),
programId.toBuffer(),
]);
2020-03-16 02:44:12 -07:00
const hash = await sha256(new Uint8Array(buffer));
2020-08-14 00:02:00 -07:00
return new PublicKey(Buffer.from(hash, 'hex'));
}
2020-05-26 17:46:49 -07:00
/**
* Derive a program address from seeds and a program ID.
*/
static async createProgramAddress(
seeds: Array<Buffer | Uint8Array>,
2020-05-26 17:46:49 -07:00
programId: PublicKey,
): Promise<PublicKey> {
let buffer = Buffer.alloc(0);
seeds.forEach(function (seed) {
if (seed.length > MAX_SEED_LENGTH) {
throw new Error(`Max seed length exceeded`);
}
2020-05-26 17:46:49 -07:00
buffer = Buffer.concat([buffer, Buffer.from(seed)]);
});
buffer = Buffer.concat([
buffer,
programId.toBuffer(),
Buffer.from('ProgramDerivedAddress'),
]);
let hash = await sha256(new Uint8Array(buffer));
2021-03-14 19:42:53 -07:00
let publicKeyBytes = new BN(hash, 16).toArray(undefined, 32);
if (is_on_curve(publicKeyBytes)) {
throw new Error(`Invalid seeds, address must fall off the curve`);
}
return new PublicKey(publicKeyBytes);
2020-05-26 17:46:49 -07:00
}
/**
* 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);
2018-09-30 18:42:45 -07:00
}