Keypairs y Direcciones
La identidad de Solana se basa en keypairs Ed25519. La clave pública es la dirección de la cuenta; la clave secreta autoriza las transacciones. Comprender los roles de firma previene errores de autorización y fugas de claves.
Receta
Tarjeta de referencia rápida - lista para copiar y pegar.
solana-keygen new --outfile ./keypair.json --no-bip39-passphrase
solana-keygen pubkey ./keypair.jsonimport { createKeyPairSignerFromBytes, address } from "@solana/kit";
import { readFileSync } from "node:fs";
const secret = Uint8Array.from(JSON.parse(readFileSync("./keypair.json", "utf8")));
const signer = await createKeyPairSignerFromBytes(secret);
console.log(signer.address);Cuándo usar esto:
- Creación de billeteras para desarrollo y pruebas
- Carga de firmantes en constructores de transacciones del lado del servidor
- Verificación de que una meta de cuenta esté marcada como
isSigner: true - Derivación de PDAs (direcciones sin claves secretas) para cuentas propiedad del programa
- Validación de cadenas de direcciones base58 antes de llamadas RPC
Ejemplo de Trabajo
import {
createSolanaRpc,
createKeyPairSignerFromBytes,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
signTransactionMessageWithSigners,
getSignatureFromTransaction,
pipe,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
import { readFileSync } from "node:fs";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
// Cargar firmante desde archivo keypair
const secret = Uint8Array.from(
JSON.parse(readFileSync(process.env.HOME + "/.config/solana/id.json", "utf8")),
);
const feePayer = await createKeyPairSignerFromBytes(secret);
const recipient = await createKeyPairSignerFromBytes(
crypto.getRandomValues(new Uint8Array(64)),
);
const { value: blockhash } = await rpc.getLatestBlockhash().send();
const message = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayer(feePayer.address, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
(m) =>
appendTransactionMessageInstruction(
getTransferSolInstruction({
source: feePayer.address,
destination: recipient.address,
amount: 1_000_000n,
}),
m,
),
);
const signed = await signTransactionMessageWithSigners(message, [feePayer]);
const signature = getSignatureFromTransaction(signed);
console.log("Signature:", signature);Lo que esto demuestra:
- Los archivos Keypair almacenan un array de 64 bytes: 32 bytes de semilla + 32 bytes de clave pública
- El pagador de tarifas debe firmar porque autoriza la deducción de lamports
createKeyPairSignerFromBytesenvuelve el keypair como un firmante de Kit- El destinatario no firma; solo la autoridad de la cuenta de origen firma las transferencias
Análisis Profundo
Cómo Funciona
- Ed25519 produce una clave pública de 32 bytes a partir de una semilla de 32 bytes
- Solana almacena la clave secreta de 64 bytes (semilla + pubkey) en archivos JSON de keypair
- Las transacciones listan cuentas con flags
isSigneryisWritable - El runtime verifica las firmas Ed25519 contra los firmantes declarados antes de la ejecución
Roles de Firmante
| Rol | ¿Firma? | Cuenta Típica |
|---|---|---|
| Pagador de tarifas | Sí | Primer firmante; paga la tarifa de transacción |
| Autoridad | Sí | Propietario de lamports o cuenta de tokens |
| Programa | No | Cuenta ejecutable invocada por la instrucción |
| PDA | No (el programa firma a través de semillas) | Dirección derivada sin clave privada |
Notas de Rust
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct UpdateData<'info> {
#[account(mut, signer)]
pub authority: Signer<'info>,
#[account(mut, has_one = authority)]
pub data_account: Account<'info, MyData>,
}- La restricción
Signer<'info>verifica que la cuenta firmó la transacción - Los PDAs usan
invoke_signedcon semillas en lugar de una clave privada
Errores Comunes
- Incluir keypair.json en git - drenaje instantáneo de fondos en mainnet. Solución: usar
.gitignore, variables de entorno o billeteras de hardware para producción. - Falta el flag
isSigner- transacción rechazada en tiempo de ejecución. Solución: marcar cada cuenta firmante como firmante en las cuentas de la instrucción. - Asumir que pubkey es igual a PDA - los PDAs están fuera de la curva y no tienen clave secreta. Solución: derivar PDAs con
find_program_addressy dejar que el programa firme. - Usar el keypair incorrecto para el cluster - el keypair funciona en todas partes pero las cuentas son específicas del cluster. Solución: separar archivos de keypair de desarrollo y producción.
- Confusión entre 64 bytes y 32 bytes - los archivos keypair de Solana son de 64 bytes, las semillas son de 32. Solución: usar
solana-keygeno helpers de Kit, no segmentación de bytes en bruto. - Pagador de tarifas no es el primer firmante - algunos clientes requieren el pagador de tarifas en el índice 0. Solución: establecer el pagador de tarifas primero en la lista de cuentas.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
CLI solana-keygen | Billeteras rápidas de desarrollo | Gestión de claves de producción |
| Billetera de hardware (Ledger) | Firma en Mainnet | Firma automatizada de servidor con alto rendimiento |
Firmantes de @solana/kit | Construcción de transacciones en TypeScript | Lógica de programa en cadena |
| PDAs con semillas | Cuentas controladas por programa | Billeteras de usuario |
Preguntas Frecuentes
¿Qué algoritmo utiliza Solana para las firmas?
Ed25519. Las claves públicas son de 32 bytes, codificadas en base58 para visualización.
¿Qué contiene un archivo JSON de keypair de Solana?
Un array JSON de 64 bytes: la semilla secreta de 32 bytes concatenada con la clave pública de 32 bytes.
¿Es una clave pública lo mismo que una dirección?
Sí. Los términos son intercambiables: la clave pública Ed25519 de 32 bytes codificada en base58.
¿Pueden dos keypairs diferentes compartir una dirección?
No. Cada keypair válido se mapea a exactamente una clave pública/dirección.
¿Qué es una Dirección Derivada de Programa (PDA)?
Una dirección derivada de semillas y un ID de programa. No tiene clave privada; el programa firma por ella a través de invoke_signed.
¿Cuántos firmantes puede tener una transacción?
Hasta 16 firmas por transacción en el formato actual. El pagador de tarifas es siempre uno de ellos.
¿El destinatario firma una transferencia de SOL?
No. Solo la autoridad de la cuenta de origen (y el pagador de tarifas) deben firmar.
¿Cómo valido una cadena de dirección base58?
import { address } from "@solana/kit";
try {
address("invalid!");
} catch {
console.log("Invalid address");
}¿Puedo recuperar un keypair solo con la clave pública?
No. La clave secreta no se puede derivar de la clave pública (función unidireccional).
¿Cuál es la dirección del Programa del Sistema?
11111111111111111111111111111111 - maneja la creación de cuentas y las transferencias de SOL.
¿Las aplicaciones del lado del servidor deben usar el mismo keypair que el predeterminado de la CLI?
No. Utiliza keypairs de servicio dedicados con saldos mínimos financiados y controles de acceso estrictos.
¿Cómo firma una billetera de hardware?
La clave privada nunca sale del dispositivo. La aplicación envía el mensaje de transacción; el dispositivo devuelve la firma.
Relacionado
- Conceptos Básicos de Solana - ejemplo de generación de keypair
- Cómo Fluye una Transacción - dónde encaja la firma
- Billeteras y Exploradores - herramientas de billetera
Versiones de Stack: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, y LiteSVM 0.6.x.