UNPKG

solana-kite

Version:

The modern Solana framework for TypeScript.

277 lines (273 loc) 9.75 kB
// 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