Ruta práctica: activos digitales en la operación

Artículo 6 de 12

50%

completado al llegar aquí

Sistemas distribuidosNota de investigación28 min de lectura

Firma operativa: BitGo Express, firmantes externos y co-firmantes

Qué componente nuestro toca una llave cuando el sistema firma solo: BitGo Express, firmante externo, Advanced Wallets y el API Co-Signer de Fireblocks.

Son las tres de la madrugada y un cliente pide retirar 40 000 USDC. La política de tesorería del módulo de wallets dice que ese monto sale de la wallet hot sin aprobación humana. Nadie abre una app ni pulsa un botón, y aun así la transacción tiene que salir firmada. Alguna pieza de software nuestra descifra una llave, firma y entrega la firma. ¿Cuál es, en qué máquina corre y quién más puede pedirle que firme?

Esa es la pregunta de este módulo, y aplica sobre todo al proyecto Middleware Blockchain (custodia y wallets con Fireblocks y BitGo). El módulo de custodia decidió quién responde por los activos; aquí bajamos al despliegue: qué proceso toca qué llave, en qué zona de red vive y qué controles lo rodean. El resultado es un diagrama de despliegue que el equipo de seguridad pueda revisar componente por componente.

Brújula de lectura

Actor, evidencia y estado

En firma operativa lo que se audita es un proceso: dónde corre, qué llave descifra y quién puede llamarlo.

Actor

Servicio firmante y co-firmante del proveedor

Nuestro proceso aporta la firma con la llave de usuario; el proveedor añade la suya tras evaluar sus políticas.

Evidencia

Diagrama de despliegue y registros de firma

Zona de red, llamantes permitidos, transporte y ubicación de cada llave, versionados junto a la infraestructura.

Estado

La llave no sale de nuestra red

Ningún componente expone la llave en claro fuera de la zona donde se firma.

Quién firma cuando nadie pulsa un botón

Un firmante es el componente que usa una llave privada (o un fragmento de llave) para producir una firma. Un co-firmante es otro firmante que aporta una segunda firma o un segundo fragmento para completar el umbral, como en el esquema 2 de 3 del módulo de tipos de firma. Lo que garantiza una firma y por qué reduce lo que hay que confiar está explicado en firmas criptográficas y reducción de nodos.

En BitGo, toda wallet en la cadena tiene tres llaves: la de usuario, la de respaldo y la de BitGo. Una transacción normal completa el 2 de 3 con la llave de usuario y la de BitGo; la de respaldo solo entra en una recuperación. Por tanto, la pregunta "quién firma" se reduce a "quién guarda la llave de usuario".

Si la wallet es de autocustodia, la llave de usuario es nuestra: BitGo solo recibe la llave de usuario cifrada y la llave pública de respaldo. Entonces algún proceso nuestro debe descifrarla y firmar. Si la wallet es de custodia, el cliente no firma: lo hace BitGo con la llave de usuario, tras una verificación por video con un operador de BitGo Bank & Trust. En ese caso no hay firmante nuestro que desplegar.

Dos términos más. Un HSM (hardware security module) es un dispositivo que genera y usa llaves sin dejarlas salir en claro. Un KMS (key management service) es un servicio, a menudo de la nube, que cifra, descifra y custodia llaves bajo control de acceso. Ambos aparecen cuando la llave de usuario no debe vivir en un archivo del servidor que firma.

BitGo Express: un proxy que firma dentro de nuestra red

BitGo Express es un servidor REST que corremos nosotros, en nuestro centro de datos o en nuestra nube. Hace dos trabajos: reenvía (proxy) las llamadas a la API de BitGo y realiza las operaciones del lado del cliente con nuestras llaves, como firmar parcialmente una transacción antes de enviarla. Según BitGo, las llaves privadas nunca salen de nuestra red y BitGo nunca las ve. Existe para equipos que trabajan en un lenguaje sin SDK oficial de BitGo o que prefieren un servicio independiente del lenguaje.

La figura sigue el retiro de las tres de la madrugada paso a paso. Nuestra app pide el envío a Express (1). Express pide a BitGo que construya la transacción y la recibe sin firmar (2). Express descifra la llave de usuario con la passphrase de la wallet y firma (3, 4). La transacción semifirmada viaja a BitGo (5), que evalúa sus políticas y co-firma con su llave (6) antes de difundirla (7).

Figura 1

Retiro firmado con BitGo Express

Fuentes:

[3][2]
Retiro firmado con BitGo Express
Cómo leer esta figura

Dos zonas delimitadas con línea discontinua. A la izquierda, Nuestra red, con el rótulo la llave de usuario nunca sale de aquí, contiene tres cajas: Nuestra app (servicio de retiros), BitGo Express (proxy de la API, TLS, puerto propio) y debajo Firma local (llave de usuario cifrada más passphrase, o firmante externo). A la derecha, BitGo, con el rótulo guarda solo su propia llave, contiene API de BitGo arriba y Políticas y llave BitGo abajo. Fuera de ambas zonas, a la derecha, la Red blockchain. Pasos: 1, la app pide el envío a Express por localhost o TLS; 2, Express y la API construyen la transacción, flecha doble; 3, Express pide la firma parcial a Firma local; 4, Firma local devuelve la transacción; 5, Express envía la transacción semifirmada a la API; 6, la API pasa a Políticas y llave BitGo, que co-firma 2 de 3; 7, BitGo difunde a la red.

Visual original de TrautsLab. Nuestra app llama a BitGo Express dentro de nuestra red; Express construye la transacción con la API de BitGo, la firma localmente con la llave de usuario y envía la transacción semifirmada; BitGo evalúa sus políticas, co-firma con su llave y la difunde. La llave de usuario no cruza la frontera de nuestra red.

Para el código que llama a Express, el cambio es mínimo: las llamadas son idénticas a las de la API directa, pero apuntan a localhost:3080 en lugar del host de BitGo. Para retiros simples, Express construye, firma y envía en una sola llamada; si hace falta más control, hay rutas separadas para cada paso.

Una advertencia que evita un malentendido frecuente: en BitGo, aprobar una transacción no aplica una firma. Las aprobaciones humanas del módulo de custodia ocurren en BitGo; la firma con la llave de usuario ocurre en Express. Son dos controles distintos y el diagrama de despliegue debe mostrar ambos.

Configurar Express para producción: puerto, interfaz y TLS

Cuatro parámetros deciden quién puede hablar con Express. El puerto por defecto es 3080 (BITGO_PORT) y la interfaz por defecto es localhost (BITGO_BIND); para escuchar en todas las interfaces hay que poner 0.0.0.0. Si quien llama vive en el mismo host, Express puede escuchar en un socket IPC, que se protege con permisos y dueño de archivo como cualquier otro archivo.

El tercero es TLS. Cuando Express apunta al entorno de producción y escucha en interfaces externas, debe arrancar con TLS (keyPath y crtPath); si no, falla al iniciar. El cuarto es NODE_ENV=production, que apaga información de depuración que podría filtrar datos del sistema. BitGo recomienda correrlo con su contenedor Docker oficial. El comando de producción que publica tiene esta forma:

Bash
# Comando de la documentación de BitGo (ilustrativo: rutas y puerto son de ejemplo)
docker run --platform linux/amd64 -it -e NODE_ENV=production \
  --volume /path/to/certs:/private -p 4000:4000 \
  bitgo/express:latest -p 4000 -k /private/cert.key -c /private/cert.crt -e prod

La salida a internet tiene dos detalles que chocan con redes corporativas. Por defecto, Express solo acepta certificados de un conjunto propio de autoridades raíz para los dominios de BitGo; detrás de un proxy que inspecciona TLS, la variable NODE_EXTRA_CA_CERTS se ignora para esos dominios. Express sí puede salir por un proxy indicado en BITGO_USE_PROXY. En la práctica, el proxy corporativo debe dejar pasar el tráfico a BitGo sin inspeccionarlo.

Por último, algunas operaciones solo existen en Express. La creación de Wallet Safes y de sus wallets hijas ejecuta ceremonias de llave locales: deben llamarse a través de Express, no de la API de BitGo, y la passphrase nunca sale del host de Express.

El firmante externo: separar el proxy de las llaves

En el despliegue simple, el mismo proceso Express hace de proxy, sale a internet y guarda las llaves cifradas con sus passphrases. El modo de firmante externo separa esas funciones en dos instancias. La instancia de proxy apunta, con externalSignerUrl, a una segunda instancia que corre en una máquina aislada y es la única que guarda las llaves de usuario y firma.

La instancia firmante arranca con --signerMode y --signerFileSystemPath, que apunta al archivo con las llaves privadas cifradas. Las passphrases le llegan como variables de entorno con el formato WALLET_<walletId>_PASSPHRASE. Eso convierte la gestión de secretos del firmante en un punto de diseño: de dónde salen esas variables, quién puede leerlas y cómo se rotan.

Hay un límite documentado: las wallets MPC con ECDSA no son compatibles con el modo de firmante externo. La página de retiros, en cambio, dice que la ruta de firma MPC admite el modo de firmante externo en un entorno aislado. Leídas juntas, sugieren que el modo externo cubre multifirma y MPC con EdDSA, pero no MPC con ECDSA, que es el caso de Ethereum y de los tokens ERC-20 como USDC. Esa lectura es nuestra y queda a validar con el proveedor.

La segunda regla afecta a la alta disponibilidad. Cada signerFileSystemPath debe pertenecer a un solo proceso firmante, porque ese proceso mantiene un registro de fragmentos ya consumidos; compartirlo entre réplicas puede perder registros que protegen contra repeticiones. Escalar el firmante no es replicar un contenedor detrás de un balanceador: cuántas instancias y con qué archivos es otro punto a validar con BitGo.

Advanced Wallets: llaves en nuestro HSM o KMS

Las Advanced Wallets son wallets hot de autocustodia en las que la llave de usuario y la de respaldo se gestionan con nuestro propio HSM o KMS, en nuestra región o jurisdicción. Usan dos servidores Express dedicados, y BitGo sigue gestionando la llave BitGo.

El primero es el Master BitGo Express (MBE): un Express normal, fuera de la zona segura, que orquesta las operaciones entre la API de BitGo y el segundo servidor, y que necesita salida a app.bitgo.com. El segundo es el Advanced Wallet Manager (AWM): un servidor ligero, aislado en una subred interna sin acceso a internet, que genera llaves, firma transacciones multifirma y ejecuta recuperaciones.

El AWM usa mTLS (TLS con autenticación mutua: cada lado presenta un certificado) y solo acepta conexiones entrantes del MBE y salientes hacia nuestro KMS. La integración con el HSM o KMS es trabajo nuestro: BitGo publica la especificación de la API del proveedor de llaves, pero no la implementa. El esfuerzo de ingeniería cambia de "operar un contenedor" a "construir y certificar un servicio de llaves".

La documentación menciona firma multifirma en el AWM; si las Advanced Wallets cubren también wallets MPC, y para qué activos, es un punto a validar con el proveedor. Elegir entre Advanced Wallets y Express con firmante externo es una decisión conjunta del equipo de seguridad y de BitGo, no de este capítulo.

Cuándo no hace falta un firmante nuestro

La forma más simple de no operar un firmante es no tener llave que firmar. En las wallets de custodia de BitGo, que son siempre frías, BitGo firma con la llave de usuario después de la verificación por video; tiene un SLA de 24 horas para completar retiros, y se pueden configurar políticas que eliminen la verificación por video en condiciones definidas por el cliente. Para esas wallets basta la API REST, sin Express.

El Go Account, la cuenta ómnibus de BitGo con libro fuera de la cadena, tiene una ruta de envío en Express. Las páginas de BitGo no coinciden sobre si usa una sola llave o es una multifirma de custodia, así que su necesidad de firmante es un punto a validar.

Para el proyecto que usa stablecoins con Circle Mint, la cuenta Mint es un servicio de Circle y el capítulo de USDC y Circle Mint no documenta ninguna llave nuestra en ese flujo. En cuanto los USDC salen a una wallet de autocustodia propia, este capítulo vuelve a aplicar.

Matriz de decisión

Qué firmante necesita cada wallet

Reglas de decisión rápida

  • ¿Wallet de custodia de BitGo?

    BitGo guarda las tres llaves y firma tras verificación por video.

    → Sin firmante nuestro: solo API REST

  • ¿Autocustodia hot con volumen moderado?

    Llave de usuario cifrada y passphrase bajo nuestro control.

    → Express en subred privada, TLS y llamantes limitados

  • ¿Autocustodia que exige aislar la llave del proxy?

    Multifirma o MPC EdDSA; seguridad pide máquina sin internet.

    → Express con firmante externo

  • ¿Wallet MPC ECDSA (Ethereum, ERC-20)?

    El modo de firmante externo no la soporta.

    → Express en red; alternativa a validar con BitGo

  • ¿La regulación exige llaves en HSM propio?

    Llaves de usuario y respaldo en nuestra jurisdicción.

    → Advanced Wallets (MBE + AWM con mTLS)

Primera pasada para el inventario; cada fila se confirma con el tipo de wallet contratado.

Dónde se coloca el firmante y quién puede llamarlo

Volvamos al retiro de las tres de la madrugada, pero ahora con un atacante dentro de la red interna. Si Express escucha en 0.0.0.0 sin restricciones, cualquier proceso que lo alcance y tenga un token de acceso de BitGo puede pedir un envío. La primera línea de defensa no es criptográfica: es quién puede abrir una conexión con el firmante.

Cuatro reglas cubren la mayoría de los casos. Primera: Express vive en una subred privada, sin entrada desde internet, y solo el servicio de retiros puede llamarlo, por política de red y con TLS o un socket IPC. Segunda: la única salida permitida es hacia los dominios de BitGo, directa o por el proxy de BITGO_USE_PROXY. Tercera: las passphrases salen de un gestor de secretos, nunca del repositorio ni de la imagen. Cuarta: la llave de respaldo no vive en ningún host de Express.

La cuarta regla tiene una razón concreta. Las políticas de BitGo solo se aplican a transacciones en las que participa la llave BitGo; una recuperación con llave de usuario y llave de respaldo las omite. Un host que guarde las dos llaves puede mover fondos sin pasar por ningún límite. El ciclo de vida de llaves ya pedía guardar el respaldo aparte; aquí se vuelve una regla de despliegue.

Las políticas de BitGo también ofrecen un control complementario: una regla de tipo webhook que decide según la respuesta de un servicio nuestro. Eso permite que la lógica de negocio (límites por cliente, screening) diga la última palabra antes de que BitGo co-firme, sin que esa lógica tenga acceso a ninguna llave.

Cómo lo resuelven Fireblocks, BitGo y Kaleido

El equivalente de Fireblocks es el API Co-Signer. Lo alojamos en nuestro entorno, en una máquina con enclave: un entorno de ejecución aislado que protege código y datos incluso de usuarios privilegiados del propio host. Corre sobre Intel SGX, AWS Nitro o Google Cloud Confidential Space. Se empareja con usuarios de API: para un Signer o Admin guarda un fragmento MPC y firma junto a dos co-firmantes en la nube de Fireblocks; para un Approver guarda una llave de aprobación y solo aprueba.

La diferencia de red es importante. El Co-Signer consulta al SaaS de Fireblocks si hay solicitudes pendientes; no espera llamadas entrantes. Nuestra lógica entra por un Callback Handler, un servidor HTTPS propio que aprueba, rechaza o pide reintento; si no responde en 30 segundos, la solicitud falla, y el handler puede aprobar pero no puede crear una firma. Sin handler configurado, el Co-Signer firma o aprueba todo lo que le llega para ese usuario.

Kaleido declara que no genera ni guarda llaves: se crean en el keystore del cliente. En su modelo híbrido, Kaleido opera la plataforma y nosotros alojamos el Zero Trust Verifier junto a nuestro HSM, donde se evalúan las políticas antes de cada firma. Circle y Tether no entran en esta comparación: son emisores de stablecoins, y la firma de nuestras wallets la resuelven los proveedores anteriores.

CriterioBitGo ExpressBitGo Advanced WalletsFireblocks API Co-SignerKaleido (híbrido)
Qué alojamosExpress (y firmante externo opcional)MBE y AWMCo-Signer y, opcional, Callback HandlerZero Trust Verifier junto al HSM
Qué llave tocaLlave de usuario o fragmento MPC, cifradosUsuario y respaldo, vía nuestro HSM o KMSFragmento MPC o llave de aprobación por usuario de APILlaves en nuestro keystore
Dirección de redNuestra app llama a Express; Express sale a BitGoMBE sale a BitGo; AWM sin internet, mTLSEl Co-Signer consulta al SaaSA validar con el proveedor
AislamientoContenedor, TLS, CA fijadasSubred aislada y HSM o KMSEnclave SGX, Nitro o Confidential SpaceHSM del cliente
Lógica de negocioRegla de webhook en políticasRegla de webhook en políticasCallback Handler (30 s)Políticas Rego en el verificador
EscalaUn proceso por archivo de firmanteA validar con el proveedorUn Co-Signer por cada 2,5 TPSA validar con el proveedor

Fuentes de la tabla: Express, Advanced Wallets, políticas de BitGo, Co-Signer y Kaleido. Las firmas por umbral en que se apoya el MPC se explican en firmas umbral y agregadas.

Decisiones que el equipo debe cerrar

Lista de verificación

Decisiones del módulo 6

0 de 7 revisados

Entregable: diagrama de despliegue del firmante

El entregable es un diagrama de despliegue acompañado de una tabla que lo hace auditable. El diagrama dibuja zonas de red y flechas con su sentido; la tabla dice, para cada componente, qué llave toca y qué controles lo rodean. Las filas de ejemplo son ilustrativas y corresponden al patrón Express con firmante externo.

ComponenteZona de redQuién lo llamaQué llave tocaDónde vive la llaveTransporteSalidaRéplicasEvidencia
Servicio de retirosAplicaciónLibro internoNinguna—mTLS internoSolo a ExpressNRegistro de solicitudes
BitGo Express (proxy)Subred privadaSolo servicio de retirosNinguna (token de acceso)—TLSDominios de BitGoNRegistro de llamadas
Firmante externoZona aisladaSolo Express (proxy)Llave de usuarioArchivo cifrado y passphrase en gestor de secretosTLSNinguna1 por archivoRegistro de firmas
Llave de respaldoFuera de líneaProcedimiento de recuperaciónLlave de respaldoBóveda o HSM aparte———Acta de custodia
BitGo (proveedor)ExternaExpressLlave BitGoBitGoTLS——Registro de políticas
Texto
# Esqueleto del diagrama (ilustrativo)
[Zona aplicación]   servicio-retiros --(mTLS)--> [Subred privada] express-proxy --(TLS, salida)--> api.bitgo
[Subred privada]    express-proxy --(TLS)--> [Zona aislada] firmante-externo (llave de usuario)
[Fuera de línea]    llave de respaldo (sin ruta de red)
Reglas: entrada a express solo desde servicio-retiros; salida de firmante: ninguna

Una revisión rápida del diagrama hace tres preguntas: ¿hay alguna flecha que llegue al firmante desde algo distinto del servicio autorizado?, ¿hay alguna caja que guarde dos llaves de la misma wallet?, ¿hay alguna llave en claro fuera de su zona? Si alguna respuesta es sí, el diseño vuelve a la mesa.

Con el firmante desplegado, el siguiente paso es decidir qué fondos dejamos entrar y salir: AML y KYT, el módulo 7.

Referencias

Bibliografía académica

Formato IEEENumerado para ingeniería, sistemas y protocolos.
  1. [1]

    BitGo, "BitGo Wallet Types," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/wallet-types

    docsID: BITGO-WALLET-TYPESAbrir fuente (abre en otra pestaña)

    Tres llaves (usuario, respaldo, BitGo); custodia fría firmada por BitGo tras verificación por video y SLA de 24 horas; Go Account ómnibus; MPC como TSS.

  2. [2]

    BitGo, "Wallets Overview," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/wallets-overview

    docsID: BITGO-WALLETS-OVERVIEWAbrir fuente (abre en otra pestaña)

    Firma normal 2 de 3 con llave de usuario y llave BitGo; en autocustodia BitGo recibe solo la llave de usuario cifrada y la llave pública de respaldo.

  3. [3]

    BitGo, "Install BitGo Express," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/get-started-express-install

    Servidor local que hace de proxy de la API REST; firma parcial con llaves propias que nunca salen de nuestra red; Docker recomendado; puerto 3080.

  4. [4]

    BitGo, "BitGo Express README," BitGoJS repository (modules/express). [Online]. Available: https://raw.githubusercontent.com/BitGo/BitGoJS/master/modules/express/README.md

    docsID: BITGO-EXPRESS-READMEAbrir fuente (abre en otra pestaña)

    TLS obligatorio en producción con interfaces externas, NODE_ENV=production, CA fijadas para dominios de BitGo, proxy de salida, regla de réplicas del firmante y ceremonias locales de Wallet Safes.

  5. [5]

    BitGo, "Withdraw Overview," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/withdraw-overview

    Construir, firmar y enviar en una llamada con Express; rutas granulares; firma MPC en red o con firmante externo; aprobar no es firmar.

  6. [6]

    BitGo, "Express Configuration Reference," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/get-started-express-reference

    Variables BITGO_PORT (3080) y BITGO_BIND (localhost); socket IPC protegido por permisos de archivo.

  7. [7]

    BitGo, "Run Express in Production," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/get-started-express-production

    docsID: BITGO-EXPRESS-PRODAbrir fuente (abre en otra pestaña)

    Comando docker de producción con certificado y llave TLS y NODE_ENV=production.

  8. [8]

    BitGo, "External-Signer Mode," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/wallets-external-signer-mode

    Segunda instancia de Express en una máquina aislada con las llaves de usuario; externalSignerUrl, signerMode, WALLET_<walletId>_PASSPHRASE; ECDSA MPC no compatible.

  9. [9]

    BitGo, "Set Up Advanced Wallets Infrastructure," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/advanced-wallets-set-up-infrastructure

    Master BitGo Express (MBE) y Advanced Wallet Manager (AWM) en subred aislada sin internet, mTLS, HSM o KMS propio e integración del key provider a cargo del cliente.

  10. [10]

    BitGo, "Policies Overview," BitGo Developer Portal. [Online]. Available: https://developers.bitgo.com/docs/policies-overview

    Tipos de regla, incluida la de webhook; las políticas solo aplican a transacciones con la llave BitGo y la recuperación con usuario y respaldo las omite.

  11. [11]

    Fireblocks, "API Co-signers Architecture Overview," Fireblocks Developer Docs. [Online]. Available: https://developers.fireblocks.com/cosigners/overview/cosigner-architecture-overview

    Co-signer alojado por el cliente en enclaves SGX, Nitro o Confidential Space; consulta al SaaS las solicitudes pendientes; fragmento MPC o llave de aprobación por usuario de API; un co-signer por cada 2,5 TPS.

  12. [12]

    Fireblocks, "Setup API Co-signer Callback Handler," Fireblocks Developer Docs. [Online]. Available: https://developers.fireblocks.com/cosigners/callback-handler/create-api-co-signer-callback-handler

    Servidor HTTPS propio que aprueba, rechaza o pide reintento; 30 segundos de plazo; autenticación por llave pública con JWT, certificados o híbrida; no crea firmas.

  13. [13]

    Kaleido, "Kaleido Custody," Kaleido. [Online]. Available: https://www.kaleido.io/product/custody

    Llaves creadas en el keystore del cliente; Remote Signing Module y Zero Trust Verifier junto al HSM; políticas Rego antes de firmar; SaaS, autoalojado o híbrido.

Fuentes primarias, papers seminales y especificaciones técnicas citadas en esta investigación.

Rutas vecinas

Si este tema te abrió hambre, sigue por aquí.

Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.

Sigue leyendo

Ideas conectadas con este artículo

Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.

Los comentarios se cargan al final de la lectura para no meter peso antes de tiempo.