Artículo del Blog
Autor
George Kudrayvtsev
Fecha de publicación
En el primero de una posible serie de artículos que juegan con las características únicas de la red de Stellar, discutiremos "multisig". Abreviatura de "multi-firma", esta característica, cuando se combina junto con el soporte de Stellar para una variedad de tipos de firma, habilita de forma nativa un poderoso conjunto de características. Los usuarios pueden preparar transacciones que:
En este artículo, nos adentraremos en los detalles sobre cómo es posible esto y crearemos algunas demos exploratorias pequeñas para que fluyan las ideas creativas.
Ahora, vale la pena mencionar que este tema se cubre en la documentación para desarrolladores, pero la visión general allí es bastante superficial. Aunque definitivamente es un recurso útil y de referencia, entraremos en mucho más detalle en este post sobre cómo todo encaja.
En el pasado, las familias poseían "anillos de sello" y los usaban para estampar el sello de cera en una carta para "autenticar" a su autor1. Estos anillos eran únicos por ser un objeto físico, hecho a mano y relacionado directamente con el escudo de la familia1. Todos estamos familiarizados con el garabato que ahora llamamos firmas en el mundo real; firmamos recibos al salir a comer, documentos legales que vienen con comprar una casa o conseguir un trabajo, y tarjetas de cumpleaños para nuestros seres queridos. En general, están diseñadas como una forma de prestar autenticidad a una acción.
Sin embargo, como sabe cualquiera que tuviera una racha rebelde de niño, las firmas son bastante fáciles de falsificar. Hoy en día, no ofrecen muchas garantías (y tampoco lo harían los anillos de sello, con la impresión 3D y todo). A veces pongo caras sonrientes en mis cuentas de bar, y ni el bar ni el banco parecen importarles.
Entra digital las firmas, el equivalente del mundo virtual. Y no estoy hablando de algo como HelloSign, sino de una construcción matemática rigurosa que proporciona garantías de autenticidad de manera eficiente y es imposible de falsificar.
Un esquema de firma digital tiene tres partes:
Más formalmente,
KeyGen() → (pk, sk)
Sign(sk, x) → s
Verify(pk, x, s) → sí / no
Supongamos que tengo un documento (llamémoslo X) que quiero firmar para que el mundo lo vea. Usaría mi clave secreta (llamémosla sk) y lo firmaría:
sig = Sign(sk, X)
Luego, transmitiría X, sig, y pk (mi clave pública) al mundo de alguna manera. Cualquiera puede usar los tres para asegurarse de que Verify(pk, X, sig) tenga éxito: esta es prueba indiscutible de que yo (y por "yo", queremos decir "la identidad que posee el par de claves (pk, sk)") de hecho firmó ese documento.
Las multi-firmas llevan la utilidad de las firmas digitales y la elevan a otro nivel. Uno de los esquemas de multi-firma más flexibles y poderosos se llama "m-de-n multisig". Esto significa: de n participantes, exactamente m de ellos tienen que firmar el valor para que la firma sea "válida".
Esta construcción es particularmente útil en un entorno multi-partido, donde los participantes independientes no se fían completamente entre sí (o no están completamente de acuerdo) pero necesitan coordinar una acción. Por ejemplo, podrías dar a cada uno de los Jueces del Tribunal Supremo una clave de firma y usar un esquema de multi-firma 4-de-7 para significar sus fallos.
También puedes usarlo en un entorno de un solo partido para hacer algo como la recuperación de cuenta. Genera un puñado de claves de respaldo y guárdalas en un lugar seguro, y entonces cualquier 1-de-n te permitiría usar tu cuenta incluso si pierdes tu "clave principal" secreta. Incluso podrías requerir más de una clave de respaldo para "reemplazar" la clave principal.
Hay otras blockchains que ofrecen multi-firmas de forma nativa, como Bitcoin, pero esto a menudo implica una configuración complicada y podría ser inmutable después de la configuración inicial. Algunas plataformas como Ethereum ofrecen multi-firma como una capa adicional a través de contratos inteligentes (como Gnosis), pero estas soluciones a medida pueden llevar a fallos de seguridad catastróficos como el encontrado encontrado en el contrato multi-firma de Parity.
Tener soporte nativo para multi-firmas es poderoso: significa seguridad, interoperabilidad y extensibilidad. Dado que las cuentas son ciudadanos de primera clase en Stellar y la multi-firma ha estado allí desde el Día 1, todo es fácil de usar y completamente personalizable.
Veamos los detalles de cómo funcionan las firmas en Stellar.
En Stellar, las multi-firmas se logran combinando firmantes, sus pesos, y los umbrales de operación. Deberías comenzar con esta sección de los documentos para entender los umbrales, pero en resumen, puedes pensar en una transacción como válida si la combinación lineal de firmantes y sus respectivos pesos supera el umbral necesario de la operación:
signer_1 * weight_1 + signer_2 * weight_2 + ... >= umbral
El poder proviene del hecho de que todos estos parámetros pueden ser modificados por los propios firmantes de la cuenta, y esa capacidad de modificación misma puede ser controlada por umbrales.
Cuando creas una cuenta Stellar, solo tiene un firmante—este es la llave maestra—y sin umbrales. Puedes añadir firmantes a una cuenta que tengan diferentes pesos, y los pesos de las firmas en una transacción deben superar el umbral para sus operaciones. Por ejemplo, si una cuenta tiene un umbral medio de 200 y tiene 5 firmantes con 50 de peso cada uno, necesitarías exactamente cuatro firmas para ejecutar una operación de umbral medio como un Pago.
Con soporte nativo para multisig, es realmente sencillo añadir otro firmante a una cuenta. Aprovechamos la operación SetOptions, por ejemplo.2
const sdk = require("stellar-sdk");
const newSigner = sdk.Keypair.random();
const setOptOp = sdk.Operation.setOptions({
masterWeight: 255,
signer: {
ed25519PublicKey: newSigner.publicKey(),
weight: 255,
},
});
Nota que establecemos el peso del nuevo firmante en 255 (el máximo). Esto significa que el firmante tiene "plena fuerza", por lo que tiene los mismos privilegios que la llave maestra. Podemos reducir este peso hasta 1.
Puedes ir aquí para leer más sobre eso: en resumen, de manera similar usas SetOptions. Por ejemplo, vamos a establecer los umbrales de la cuenta en 100, 200 y 255 para operaciones de umbral bajo, medio y alto, respectivamente:
const threshOp = sdk.Operation.setOptions({
lowThreshold: 100,
mediumThreshold: 200,
highThreshold: 255
});
Cada operación debe cumplir un umbral particular, y puedes usar estos para controlar qué operaciones son posibles por qué firmantes, incluyendo cuando los firmantes colaboran. Esto desbloqueará el avanzado comportamiento de consenso de múltiples partes que discutiremos más adelante.
Puede que hayas notado que especificamos un ed25519PublicKey al añadir el nuevo firmante. Si adivinaste que esto implica que hay otros tipos de firmantes, ¡adivinaste bien! Y ahí es donde entra el verdadero poder del soporte nativo de multi-firma. Los otros tipos de firmantes se describen en la entrada del glosario de Multifirma mencionado anteriormente, pero aquí hay un rápido resumen:
Las cargas firmadas son los firmantes más complicados del grupo, así que vamos a elaborar más sobre ellos. Comienzas con un par de claves, (pk, sk) y una carga P. Luego, configuras un firmante de "carga firmada" con (pk, P). Esto significa que Firma(sk, P) es una firma de transacción válida.
¡Podemos combinar multi-firmas y los diversos tipos de firmantes para hacer algunas cosas divertidas!
Los siguientes estudios de caso serán bastante irreales, pero harán el trabajo al demostrar el comportamiento interesante que puede surgir de estos bloques de construcción primitivos.
Déjame enfatizar algo aquí: Estos "estudios de caso" son solo demostraciones de funcionalidad. Por favor, no los uses como bases para nada: ni la utilidad ni la seguridad se consideraron profundamente al crear estos ejemplos de juguete.
Demostramos cómo se puede configurar multi-sig para una sola cuenta anteriormente, pero hagamos algo incluso mejor que eso. Dado que una sola cuenta puede tener múltiples firmantes, y una sola transacción puede tener múltiples cuentas firmando, podemos tener mecanismos complicados como votación por mayoría usando grupos de gobernanza independientes.
Ahora, Stellar no tiene contratos inteligentes (¡aún!), pero aún podemos simular los mecanismos de votación de un DAO usando consenso de multi-firma y multi-parte.
Nuestro DAO será un poco limitado en su funcionalidad, restringido a hacer cosas posibles con la funcionalidad nativa de Stellar: votar para expulsar miembros, hacer pagos, etc. pero será un DAO de todos modos.
Supongamos que nuestro DAO comienza con tres miembros, todos de acuerdo en financiar el DAO con 1000 lumens cada uno.
Deciden estructurar el DAO de tal manera que cada miembro (es decir, firmante) tenga peso proporcional a su contribución al DAO. Para empezar, los tres miembros tienen firmantes con peso de 85. Los fundadores también deciden que solo un voto de super-mayoría (2/3) puede ejecutar operaciones de alto umbral, mientras que todo lo demás se puede hacer con una mayoría simple:
// Supongamos que A, B, y C son los pares de claves de los miembros inaugurales.
const initDAO = [A, B, C].map((kp) => {
sdk.Operation.setOptions({
signer: {
ed25519PublicKey: kp.publicKey(),
weight: 85,
}
});
}).concat([
sdk.Operation.setOptions({
lowThreshold: 128, // mayoría simple
medThreshold: 128, // mayoría simple
highThreshold: 170, // super mayoría
masterWeight: 0,
}),
]);
Si todos (incluyendo la cuenta DAO) firman una transacción con este conjunto de operaciones, el DAO se establecerá con su estructura de votación inicial.
Stellar tiene algunas barreras de seguridad fundamentales alrededor de multi-sig:
Si tenemos un DAO "controlando" una cuenta, entonces, las “acciones” están limitadas a solo 20 votos. Ahora, George Washington puede habernos aconsejado en contra de la formación de partidos políticos, pero son inevitables en este caso: Debemos introducirlos para crear jerarquías de membresía en cascada que voten juntas para sortear estas limitaciones.
No entremos en eso aquí, sin embargo. Debería ser suficiente decir que podemos evitar la limitación teniendo miembros que sean firmantes en cuentas que son firmantes en cuentas que son... etc. con mayor autoridad en el DAO. Un poco como una democracia representativa, pero con más influencia directa en el “electorado”.
La membresía en el DAO es controlada por los miembros existentes, y todos ajustan sus pesos para los nuevos miembros apropiadamente.
Para unirse al DAO, las personas deben ofrecer una transacción especial para firmar:
floor(100 / (3000 + 100)) = 3%
de los derechos de voto, lo que significa que los pesos ahora son (82, 82, 82, 7).SetOptions(source: DAO, signer: {address, weight})
operación para cada miembro del DAO con el peso "reequilibrado" mencionado, incluidos ellos mismos.Naturalmente, deben firmar esta transacción. Si suficientes miembros firman la transacción (es decir, una super mayoría, ya que SetOptions es una operación de alto umbral), entonces la transacción se valida y el nuevo miembro pasa a formar parte del DAO.
En cualquier momento dado, ahora, el DAO puede votar colectivamente con sus firmas para permitir que la cuenta gobernante tome acción. Pueden enviar todos sus fondos a un único destino (por ejemplo, hacer una donación), votar para expulsar miembros (es decir, eliminando su firmante), emitir activos (por ejemplo, poseer un activo podría representar alguna legislación), y más.
¡Este es el poder del consenso multi-partidario en acción!
Los firmantes de transacciones pre-autorizadas permiten a las cuentas planificar comportamientos futuros.
Un ejemplo simple y útil es resolución de escrow: La cuenta de escrow podría pre-autorizar dos transacciones con el mismo número de secuencia: una que envía dinero a un destino, mientras que la otra lo envía a otro lugar. Dado que los números de secuencia deben ser únicos, se garantiza que como máximo una de las transacciones se ejecutará.
Esto también puede ser útil en descongelar suministros de activos, por ejemplo, al bloquear una cuenta que emite NFTs. Dado que bloquear cuentas (es decir, eliminar todos los firmantes) es permanente, puedes usar una transacción pre-autorizada que te permita "desbloquear" la cuenta en algún punto en el futuro. Esto podría usarse para expandir tu suministro de NFT después de una cierta fecha.
Como un ejemplo más divertido, podemos agregar algún comportamiento siniestro al "DAO" descrito anteriormente. Los tres fundadores iniciales decidieron que quieren agregar un plan de contingencia a su organización, dándoles el derecho de restablecer completamente los derechos de voto si quieren:
const builder = sdk.TransactionBuilder(daoAccount, {
networkPassphrase: sdk.Network.TESTNET,
fee: sdk.BASE_FEE
}).setTimeout(sdk.TIMEOUT_INFINITE);
initDAO.forEach(builder.addOperation);
Luego, establecen este hash de transacción como un firmante pre-autorizado en la cuenta del DAO antes de abrir la membresía:
const resetDAO = sdk.Operation.setOptions({
signer: {
preAuthTx: builder.build().hash(),
weight: 255
}
});
Por supuesto, un ciudadano responsable de DAO-Land cuestionaría por qué hay un firmante de transacción pre-autorizado misterioso en la cuenta gobernante antes de unirse, pero eso quitaría la diversión de este caso de estudio.
La idea criptográfica de un "esquema de compromiso" tiene algunas aplicaciones interesantes, y se pueden expandir a cualquier escenario humano donde necesites comprometerte de antemano sin revelar el compromiso en sí.
El esquema de compromiso más simple es uno donde revelas tu compromiso como c = H(x), donde x es "la cosa a la que te comprometiste" y H es una función hash criptográfica como SHA2, lo que hace c un "compromiso unidireccional". Esto significa que es imposible derivar x cuando solo tienes c. Por ejemplo, me dices que lanzaste una moneda y te comprometiste a un “resultado”. Entonces, puedo hacer una suposición sobre el resultado del lanzamiento, y puedes revelar a qué te comprometiste previamente sin poder engañarme sobre el resultado.
¡Eso es exactamente como las firmas hash en Stellar! Entonces, ¿qué podemos hacer con eso?
Pongámonos raros: usemos la red para hacer una plataforma de apuestas. No será completamente sin confianza, desafortunadamente, ya que los participantes aún necesitarán confiar en "La Casa", pero aún podemos divertirnos aprovechando algunos de los primitivos multi-firma de Stellar.
El juego de apuestas será un simple juego de "adivina el número", donde el número es de 4 bits de largo, permitiendo 24 = 16 valores diferentes. La cuenta de "La Casa" o "casino" C primero se compromete a un valor dándose a sí misma un firmante hash con el valor H(r || x) y un peso de 1.
Aquí, r es un valor generado aleatoriamente de 252 bits (un "nonce") y x es el valor real de 0-15 que obtendrá recompensas. La || sintaxis indica concatenación a nivel de bits, lo que simplemente significa unir r y x juntos en un solo valor de 256 bits.
Usamos un nonce aquí porque si solo usáramos x, alguien podría simplemente probar todos los valores posibles para x y aprender el valor (por ejemplo, "¿H(0) = x? No, entonces ¿H(1) = x?" y así sucesivamente). El nonce hace que el valor de entrada sea demasiado grande para forzarlo bruscamente de esa manera.
El firmante tiene un peso de 1 porque su único propósito es ser un compromiso público a x.
Ahora que el casino se ha comprometido a un valor, los participantes pueden hacer sus apuestas. Para hacerlo, los apostadores siguen un "apretón de manos" con el casino para establecer una cuenta de escrow. Involucra tres fases: dos fases de compromiso y una fase de ejecución.
Primero, el apostador B crea una cuenta de escrow E con su apuesta como saldo inicial. Luego, se compromete a una suposición particular creando un firmante hash H(s || y), donde s es un valor aleatorio diferente y y es la suposición (similar a lo que hizo el casino). Luego, crea una transacción con los siguientes efectos:
En este punto, la cuenta está lista para el fideicomiso pero sigue estando bajo el control total del participante. Esto permite que el participante se retire y conserve sus fondos hasta que el casino haga su parte en el proceso.
El participante ahora pre-firma una transacción que cederá el control al casino si el casino coloca su contraapuesta en fideicomiso. La transacción hará:
Para "pasar la antorcha" al casino, podría almacenar el hash de la transacción y su firma como entradas de datos en la cuenta de fideicomiso, pero una plataforma real simplemente usaría un canal de comunicación externo para esto.
Si el casino acepta los términos y el fideicomiso “se verifica,” añade su firma a la transacción y la envía a la red. Ahora, la cuenta de fideicomiso puede ser controlada por el casino si y solo si revela su elección comprometida, x, y si el apostador revela su suposición, y.
Aquí es donde entra la confianza: una vez que el apostador revela y, el casino tendría control total sobre la cuenta, por lo que podría barrer sus fondos.
Sin embargo, al hacerlo, hace público su propio juego sucio: no solo admite que conoce la suposición del apostador, y, también revela su propio valor oculto, x. Todos verán que si x=y, que el casino robó de la cuenta de fideicomiso. Esto reduce los incentivos para hacer trampa, ya que el casino solo puede hacer trampa una vez antes de que nadie quiera jugar más.
La fase final es donde el apostador revela su suposición. Al igual que un candidato electoral en la noche de elecciones, el apostador prepara dos transacciones: una para ganar, otra para perder.
Ambas contienen una operación de fusión de cuentas; en un caso, la fusión es hacia la cuenta del apostador, mientras que en el otro caso, la fusión es hacia la cuenta del casino. Estas transacciones se firman con s || y, revelando el nonce y la suposición del apostador. Una vez firmadas parcialmente, el casino puede elegir qué transacción firmar y enviar basándose en si la suposición del apostador es correcta o no.
El título es un poco de clickbait: no vamos a implementar un consenso de Nakamoto completo en la red. En cambio, podemos usar firmas hash para simular "recompensas de minería". Obviamente, no deberíamos incentivar a las personas a desperdiciar ciclos de CPU sin razón (por eso Stellar es una de las blockchains más ecológicas34), pero aún así es una demostración divertida.
Así es como funciona: Creamos una cuenta, la financiamos con algunos "premios" lumens, y le añadimos un firmante hash. Excepto que aquí está la parte divertida: la entrada x al firmante hash H(x) no es solo algún valor aleatorio, sino que en cambio se revela parcialmente al mundo. Por ejemplo, si revelamos los primeros 24 bytes de x al mundo (a través de un medio centralizado como Twitter, o incluso como una entrada de datos en la cuenta), quienquiera que pueda determinar los últimos 8 bytes puede obtener acceso parcial a la cuenta.
Le damos al firmante hash un peso de 200 y a la clave maestra un peso de 55. También establecemos todos los umbrales a 255, así que la única manera de que alguien pueda hacer algo con la cuenta es con ambos firmantes. Luego, revelamos una transacción T que modifica todos los umbrales a 200 que está solo firmada por el maestro. Si se envía, permitirá que el "ganador" tenga libre control sobre la cuenta y retire el premio.
Una vez que alguien ha "descifrado" los últimos 8 bytes del hash, pueden firmar la transacción anterior T, enviarla a la red, ¡y luego retirar su premio!
Como puedes ver, tener multi-firmas como una característica nativa de la red Stellar es poderoso. Es más seguro, flexible y eficiente en comparación con otras plataformas blockchain que dependen de soluciones de terceros para multi-firma. También pueden crecer en capacidades a medida que la red madura; por ejemplo, cargas de firma (que realmente no cubrimos aquí, pero son una parte importante de canales de pago) solo se lanzaron en Protocolo 19, pero obtienen todas las características multi-firma existentes del protocolo “de forma gratuita” como resultado de ser nativas.
Tener multi-firmas y una variedad de tipos de firmantes nos permite construir algunas cosas interesantes sin necesidad de contratos inteligentes completos. Por supuesto, las cosas que diseñamos aquí eran proyectos de juguete destinados a demostrar conceptos, pero eso solo resalta aún más el poder y la flexibilidad de la multi-firma en Stellar.
-----
1https://www.thehistorypress.co.uk/articles/a-brief-history-of-signet-rings/
2Nota: Por razones de brevedad, no incluyo código para verificar errores, preparar/firmar/enviar transacciones, etc. a menos que se desvíen de la norma. De lo contrario, son partes implícitas de la demostración que deberían ser autoexplicativas para cualquiera que construya en Stellar.
4 https://stellar.org/resources/sustainability-report