solana-kite
Version:
The modern Solana framework for TypeScript.
277 lines (273 loc) • 9.75 kB
JavaScript
// src/lib/keypair.ts
import {
createKeyPairSignerFromBytes,
createKeyPairSignerFromPrivateKeyBytes
} from "@solana/kit";
import { assertKeyGenerationIsAvailable } from "@solana/assertions";
// src/lib/constants.ts
import { lamports, address as toAddress } from "@solana/kit";
var NOT_FOUND = -1;
var TOKEN_PROGRAM = toAddress("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
var TOKEN_EXTENSIONS_PROGRAM = toAddress("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
var ASSOCIATED_TOKEN_PROGRAM = toAddress("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
var SOL = 1000000000n;
var SECONDS = 1e3;
var DEFAULT_AIRDROP_AMOUNT = lamports(1n * SOL);
var DEFAULT_MINIMUM_BALANCE = lamports(500000000n);
var DEFAULT_ENV_KEYPAIR_VARIABLE_NAME = "PRIVATE_KEY";
var DEFAULT_TRANSACTION_RETRIES = 4;
var DEFAULT_TRANSACTION_TIMEOUT = 15 * SECONDS;
var KEYPAIR_LENGTH = 64;
var KEYPAIR_PUBLIC_KEY_OFFSET = 32;
var DEFAULT_FILEPATH = "~/.config/solana/id.json";
var BASE58_CHARACTER_SET = /^[1-9A-HJ-NP-Za-km-z]+$/;
var PKCS_8_PREFIX = new Uint8Array([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
var PKCS_8_PREFIX_LENGTH = PKCS_8_PREFIX.length;
var GRIND_COMPLEXITY_THRESHOLD = 5;
var DISCRIMINATOR_SIZE = 8;
var PUBLIC_KEY_SIZE = 32;
var LENGTH_FIELD_SIZE = 4;
// src/lib/crypto.ts
import { getBase58Decoder, getBase58Encoder } from "@solana/kit";
var exportKey = crypto.subtle.exportKey.bind(crypto.subtle);
var modInverse = (a, m) => {
let [old_r, r] = [a, m];
let [old_s, s] = [1n, 0n];
let [old_t, t] = [0n, 1n];
while (r !== 0n) {
const quotient = old_r / r;
[old_r, r] = [r, old_r - quotient * r];
[old_s, s] = [s, old_s - quotient * s];
[old_t, t] = [t, old_t - quotient * t];
}
return old_s < 0n ? old_s + m : old_s;
};
var P = 2n ** 255n - 19n;
var D = -121665n * modInverse(121666n, P) % P;
var isOnEd25519Curve = (publicKeyBytes) => {
try {
let y = 0n;
for (let i = 0; i < 32; i++) {
y |= BigInt(publicKeyBytes[i] & (i === 31 ? 127 : 255)) << 8n * BigInt(i);
}
if (y >= P) return false;
const y2 = y * y % P;
const u = (y2 - 1n + P) % P;
const v = (D * y2 + 1n) % P;
if (v === 0n) return false;
const x2 = u * modInverse(v, P) % P;
return isQuadraticResidue(x2, P);
} catch {
return false;
}
};
var isQuadraticResidue = (a, p) => {
if (a === 0n) return true;
const exponent = (p - 1n) / 2n;
return modPow(a, exponent, p) === 1n;
};
var modPow = (base, exponent, modulus) => {
let result = 1n;
base = base % modulus;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = result * base % modulus;
}
exponent = exponent >> 1n;
base = base * base % modulus;
}
return result;
};
var exportRawPrivateKeyBytes = async (privateKey) => {
if (!privateKey.extractable) {
throw new Error("Private key is not extractable");
}
const pkcs8Bytes = await exportKey("pkcs8", privateKey);
const rawPrivateKeyBytes = pkcs8Bytes.slice(PKCS_8_PREFIX_LENGTH);
return new Uint8Array(rawPrivateKeyBytes);
};
var exportRawPublicKeyBytes = async (publicKey) => {
const rawPublicKeyBytes = await exportKey("raw", publicKey);
return new Uint8Array(rawPublicKeyBytes);
};
var getBase58AddressFromPublicKey = async (publicKey) => {
const base58Decoder = getBase58Decoder();
const publicKeyBytes = await exportRawPublicKeyBytes(publicKey);
const publicKeyString = base58Decoder.decode(publicKeyBytes);
return publicKeyString;
};
var checkIfAddressIsPublicKey = async (address) => {
const base58Encoder = getBase58Encoder();
try {
let publicKeyBytes;
if (typeof address === "string") {
publicKeyBytes = base58Encoder.encode(address);
} else if (address instanceof Uint8Array) {
publicKeyBytes = address;
} else if (typeof address === "object" && address !== null && typeof address.toString === "function") {
publicKeyBytes = base58Encoder.encode(address.toString());
} else {
return false;
}
if (publicKeyBytes.length !== 32) {
return false;
}
if (publicKeyBytes.every((b) => b === 0)) {
return false;
}
if (!isOnEd25519Curve(publicKeyBytes)) {
return false;
}
try {
await crypto.subtle.importKey(
"raw",
publicKeyBytes,
{
name: "Ed25519",
namedCurve: "Ed25519"
},
false,
["verify"]
);
} catch (error) {
return false;
}
return true;
} catch (error) {
return false;
}
};
// src/lib/keypair.ts
var ALLOW_EXTRACTABLE_PRIVATE_KEY_MESSAGE = "yes I understand the risk of extractable private keys and will delete this keypair shortly after saving it to a file";
var grindKeyPair = async (options) => {
await assertKeyGenerationIsAvailable();
const allowExtractablePrivateKey = options.isPrivateKeyExtractable === ALLOW_EXTRACTABLE_PRIVATE_KEY_MESSAGE;
if (options.prefix && !BASE58_CHARACTER_SET.test(options.prefix)) {
throw new Error("Prefix must contain only base58 characters.");
}
if (options.suffix && !BASE58_CHARACTER_SET.test(options.suffix)) {
throw new Error("Suffix must contain only base58 characters.");
}
const keypairGrindComplexity = (options.prefix?.length || 0) + (options.suffix?.length || 0);
if (keypairGrindComplexity > GRIND_COMPLEXITY_THRESHOLD) {
console.warn(
`Generating a keyPair with a prefix and suffix of ${keypairGrindComplexity} characters, this may take some time.`
);
}
let counter = 0;
while (true) {
counter++;
if (!options.silenceGrindProgress && counter % 1e5 === 0) {
console.log(`Keypair grind tries: ${counter}`);
}
const keyPair = await crypto.subtle.generateKey(
// Algorithm. Native implementation status: https://github.com/WICG/webcrypto-secure-curves/issues/20
"Ed25519",
// Allows the private key to be exported (eg for saving it to a file) - public key is always extractable see https://wicg.github.io/webcrypto-secure-curves/#ed25519-operations
allowExtractablePrivateKey,
// Allowed uses
["sign", "verify"]
);
const publicKeyString = await getBase58AddressFromPublicKey(keyPair.publicKey);
if (!options.prefix && !options.suffix) {
return keyPair;
}
const matchesPrefix = options.prefix ? publicKeyString.startsWith(options.prefix) : true;
const matchesSuffix = options.suffix ? publicKeyString.endsWith(options.suffix) : true;
if (matchesPrefix && matchesSuffix) {
return keyPair;
}
continue;
}
};
var createJSONFromKeyPairSigner = async (keyPairSigner) => {
const rawPrivateKeyBytes = await exportRawPrivateKeyBytes(keyPairSigner.keyPair.privateKey);
const rawPublicKeyBytes = await exportRawPublicKeyBytes(keyPairSigner.keyPair.publicKey);
const combinedUint8Array = new Uint8Array(KEYPAIR_LENGTH);
combinedUint8Array.set(new Uint8Array(rawPrivateKeyBytes), 0);
combinedUint8Array.set(new Uint8Array(rawPublicKeyBytes), KEYPAIR_PUBLIC_KEY_OFFSET);
return JSON.stringify(Array.from(combinedUint8Array));
};
var loadWalletFromFile = async (filepath) => {
const path = await import("path");
const { readFile } = await import("fs/promises");
if (!filepath) {
filepath = DEFAULT_FILEPATH;
}
if (filepath[0] === "~") {
const home = process.env.HOME || null;
if (home) {
filepath = path.join(home, filepath.slice(1));
}
}
let fileContents;
try {
const fileContentsBuffer = await readFile(filepath);
fileContents = fileContentsBuffer.toString();
} catch (thrownObject) {
throw new Error(`Could not read keyPair from file at '${filepath}'`);
}
try {
const parsedFileContents = Uint8Array.from(JSON.parse(fileContents));
return createKeyPairSignerFromBytes(parsedFileContents);
} catch (thrownObject) {
const error = thrownObject;
if (!error.message.includes("Unexpected token")) {
throw error;
}
throw new Error(`Invalid secret key file at '${filepath}'!`);
}
};
var loadWalletFromEnvironment = (variableName) => {
const privateKeyString = process.env[variableName];
if (!privateKeyString) {
throw new Error(`Please set '${variableName}' in environment.`);
}
try {
let decodedPrivateKey = Uint8Array.from(JSON.parse(privateKeyString));
return createKeyPairSignerFromBytes(decodedPrivateKey);
} catch (error) {
throw new Error(`Invalid private key in environment variable '${variableName}'!`);
}
};
var addKeyPairSignerToEnvFile = async (keyPairSigner, variableName, envFileName) => {
const { appendFile } = await import("fs/promises");
if (!envFileName) {
envFileName = ".env";
}
const existingSecretKey = process.env[variableName];
if (existingSecretKey) {
throw new Error(`'${variableName}' already exists in env file.`);
}
const privateKeyString = await createJSONFromKeyPairSigner(keyPairSigner);
await appendFile(envFileName, `
# Solana Address: ${keyPairSigner.address}
${variableName}=${privateKeyString}`);
};
var checkAddressMatchesPrivateKey = async (address, privateKey) => {
const temporarySigner = await createKeyPairSignerFromPrivateKeyBytes(privateKey);
return temporarySigner.address === address;
};
export {
NOT_FOUND,
TOKEN_PROGRAM,
TOKEN_EXTENSIONS_PROGRAM,
ASSOCIATED_TOKEN_PROGRAM,
SOL,
SECONDS,
DEFAULT_AIRDROP_AMOUNT,
DEFAULT_ENV_KEYPAIR_VARIABLE_NAME,
DEFAULT_TRANSACTION_RETRIES,
DEFAULT_TRANSACTION_TIMEOUT,
DISCRIMINATOR_SIZE,
PUBLIC_KEY_SIZE,
LENGTH_FIELD_SIZE,
checkIfAddressIsPublicKey,
grindKeyPair,
createJSONFromKeyPairSigner,
loadWalletFromFile,
loadWalletFromEnvironment,
addKeyPairSignerToEnvFile,
checkAddressMatchesPrivateKey
};
//# sourceMappingURL=chunk-D5BJ3YJK.js.map