Try   HackMD

Mina Workshop

Prerrequisitos

Cada tutorial ha sido probado con las últimas versiones de:

  • zkApp CLI
  • o1js (o1js se incluye automáticamente al crear un proyecto utilizando zkApp CLI).
  • Otras dependencias según se indica.

Instalar zkApp CLI

Para instalar zkApp CLI:

​​​​$ npm install -g zkapp-cli

Para confirmar la instalación exitosa:

​​​​$ zk --version

Dependencias

Para utilizar zkApp CLI y o1js, tu entorno requiere:

  • NodeJS v16 en adelante (o NodeJS v14 usando --experimental-wasm-threads)
  • NPM v6 en adelante
  • Git v2 en adelante

Usa un gestor de paquetes para instalar las versiones requeridas y actualizar versiones anteriores si es necesario. Los gestores de paquetes para los entornos compatibles son:

En Linux, podrías necesitar instalar una versión reciente de Node.js utilizando NodeSource. Usa deb o rpm tal y como es recomendado por el projecto Node.js.

Para verificar tus versiones instaladas, usa npm -v, node -v y git -v.

Tutorial 1: Hola Mundo

Este tutorial de Hola Mundo te ayuda a comenzar con o1js, zkApps y la programación con pruebas de conocimiento cero.

En este tutorial paso a paso, aprenderás a codificar una zkApp de principio a fin.

Harás lo siguiente:

  • Escribir un contrato inteligente básico que almacene un número como estado on-chain.
  • La lógica del contrato permite que este número sea reemplazado solo por su cuadrado; por ejemplo, 3 -> 9 -> 81, y así sucesivamente.
  • Crear un proyecto usando Mina zkApp CLI
  • Escribir el código de tu contrato inteligente
  • Utilizar una blockchain local de Mina para interactuar con tu contrato inteligente.

Los tutoriales posteriores introducen más conceptos y patrones.

El código fuente completo para este tutorial se proporciona en el directorio examples/zkapps/01-hello-world en GitHub. Mientras estés allí, dale una estrella al repositorio /docs2 para que otros desarrolladores zk puedan aprender a construir una zkApp!

Prerrequisitos

Asegúrate de que tu entorno cumpla con los prerrequisitos que se mencionan al inicio de este documento.

En particular, asegúrate de tener instalado el zkApp CLI:

$ npm install -g zkapp-cli

Crear un nuevo proyecto

Ahora que tienes las herramientas instaladas, puedes comenzar a construir tu aplicación.

  1. Crea o cambia a un directorio donde tengas privilegios de escritura.
  2. Ahora, crea un proyecto usando el comando zk project:
​​$ zk project 01-hello-world

El comando zk project tiene la capacidad de crear la interfaz de usuario para tu proyecto. Para este tutorial, selecciona none:

? Create an accompanying UI project too? …
  next
  svelte
  nuxt
  empty
❯ none

La salida esperada es:

✔ Create an accompanying UI project too? · none
✔ UI: Set up project
✔ Initialize Git repo
✔ Set up project
✔ NPM install
✔ NPM build contract
✔ Set project name
✔ Git init commit

Success!

Next steps:
  cd 01-hello-world
  git remote add origin <your-repo-url>
  git push -u origin main

El comando zk project crea el directorio 01-hello-world que contiene la estructura para tu proyecto, incluyendo herramientas como la herramienta de formateo de código Prettier, la herramienta de análisis de código estático ESLint y el framework de pruebas de JavaScript Jest.

  1. Cambia al directorio 01-hello-world y lista los contenidos:
$ cd 01-hello-world
$ ls

La salida muestra estos resultados:

LICENSE
README.md
babel.config.cjs
build
config.json
jest-resolver.cjs
jest.config.js
keys
node_modules
package-lock.json
package.json
src
tsconfig.json

Para este tutorial, ejecutarás comandos desde la raíz del directorio 01-hello-world mientras trabajas en el directorio src en archivos que contienen el código TypeScript para el contrato inteligente. Cada vez que realices actualizaciones, y luego compiles o despliegues, el código TypeScript se compila en JavaScript en el directorio build.

Preparar el proyecto

Comienza eliminando los archivos predeterminados que vienen con el nuevo proyecto.

  1. Para eliminar los archivos antiguos:

  $ rm src/Add.ts
  $ rm src/Add.test.ts
  $ rm src/interact.ts
  
  1. Ahora, crea los nuevos archivos para tu proyecto:

  $ zk file src/Square
  $ touch src/main.ts
  
  • El comando zk file creó los archivos src/Square.ts y src/Square.test.ts.
  • Sin embargo, este tutorial no incluye escribir pruebas, por lo que simplemente usarás el archivo main.ts como un script para interactuar con el contrato inteligente y observar cómo funciona.

En tutoriales posteriores, aprenderás a interactuar con un contrato inteligente desde el navegador, como lo haría un usuario típico.

  1. Ahora, abre src/index.ts en un editor de texto y cámbialo para que se vea así:

  1 import { Square } from './Square.js';
  2
  3 export { Square };
  

El archivo src/index.ts contiene todas las exportaciones que deseas hacer disponibles para su consumo desde fuera de tu proyecto de contrato inteligente, como desde una UI.

Escribir el Contrato Inteligente zkApp

¡Ahora, la parte divertida! Escribe tu contrato inteligente en el archivo src/Square.ts.

Los números de línea se proporcionan para mayor comodidad. Una versión final del contrato inteligente se proporciona en el archivo de ejemplo Square.ts.

Esta parte del tutorial te guía a través del código del contrato inteligente Square ya completado en el archivo de ejemplo src/Square.ts.

Copiar el ejemplo

Copia el siguiente código en el archivo src/Square.ts:

import { 
  Field, 
  SmartContract, 
  state, 
  State, 
  method 
} from 'o1js';

export class Square extends SmartContract {
  @state(Field) num = State<Field>();

  init() {
    super.init();
    this.num.set(Field(3));
  }

  @method update(square: Field) {
    const currentState = this.num.get();
    this.num.assertEquals(currentState);
    square.assertEquals(currentState.mul(currentState));
    this.num.set(square);
  }
}

Ahora estás listo para revisar las importaciones en el contrato inteligente.

Importaciones

La declaración import trae otros paquetes y dependencias para usar en tu contrato inteligente.

Todas las funciones utilizadas dentro de un contrato inteligente deben operar con tipos de datos compatibles con o1js: tipos Field y otros tipos construidos sobre tipos Field.

import { 
  Field, 
  SmartContract, 
  state, 
  State, 
  method 
} from 'o1js';

Estos elementos son:

  • Field: El tipo de número nativo en o1js. Puedes pensar en los elementos Field como enteros sin signo. Los elementos Field son el tipo más básico en o1js. Todos los demás tipos compatibles con o1js se construyen sobre elementos Field.
  • SmartContract: La clase que crea contratos inteligentes zkApp.
  • state: Un decorador de conveniencia utilizado en contratos inteligentes zkApp para crear referencias al estado almacenado on-chain en una cuenta zkApp.
  • State: Una clase utilizada en contratos inteligentes zkApp para crear un estado almacenado on-chain en una cuenta zkApp.
  • method: Un decorador de conveniencia utilizado en contratos inteligentes zkApp para crear métodos de contrato inteligente como funciones. Los métodos que utilizan este decorador son los puntos de entrada del usuario final para interactuar con un contrato inteligente.

Clase de contrato inteligente

Ahora, revisa el contrato inteligente en el archivo src/Square.ts.

El contrato inteligente llamado Square tiene un elemento de estado on-chain llamado num de tipo Field como se define en el siguiente código:

export class Square extends SmartContract {
  @state(Field) num = State<Field>();
}

Los zkApps pueden tener hasta ocho campos de estado on-chain. Cada campo almacena hasta 32 bytes (técnicamente, 31.875 bytes o 255 bits) de datos arbitrarios. Un tutorial posterior cubre opciones para el estado off-chain.

Ahora, este código agrega el método init para configurar el estado inicial del contrato inteligente al implementarlo:

export class Square extends SmartContract {
  @state(Field) num = State<Field>();

  init() {
    super.init();
    this.num.set(Field(3));
  }

Dado que estás extendiendo SmartContract, que tiene su propia inicialización, llamar a super.init() invoca esta función en la clase base.

Luego, this.num.set(Field(3)) inicializa el estado on-chain num a un valor de 3.

Opcionalmente, puedes especificar permisos. Consulta setPermissions en la documentación de referencia de o1js.

Finalmente, este código agrega la función update():

  @method update(square: Field) {
    const currentState = this.num.get();
    this.num.assertEquals(currentState);
    square.assertEquals(currentState.mul(currentState));
    this.num.set(square);
  }

El nombre de la función update es arbitrario, pero tiene sentido para este ejemplo. Observa cómo se usa el decorador @method porque está destinado a ser invocado por usuarios finales utilizando una interfaz de usuario zkApp o, como en este caso, el script main.ts.

Este método contiene la lógica por la cual los usuarios finales pueden actualizar el estado on-chain de la cuenta zkApp.

Una cuenta zkApp es una cuenta en la blockchain de Mina donde se implementa un contrato inteligente zkApp. Una cuenta zkApp tiene asociada una clave de verificación.

En este ejemplo, el código especifica:

  • Si el usuario proporciona un número (por ejemplo, 9) al método update() que es el cuadrado del estado on-chain existente denominado num (por ejemplo, 3), entonces actualiza el valor de num almacenado on-chain al valor proporcionado (en este caso, 9).
  • Si el usuario proporciona un número que no cumple con estas condiciones, no podrá generar una prueba ni actualizar el estado on-chain.

Estas condiciones de actualización se logran utilizando afirmaciones dentro del método. Cuando un usuario invoca un método en un contrato inteligente, todas las afirmaciones deben ser verdaderas para generar la prueba de conocimiento cero de ese contrato inteligente. La red Mina acepta la transacción y actualiza el estado on-chain solo si la prueba adjunta es válida. Esta afirmación es cómo puedes lograr un comportamiento predecible en un modelo de ejecución off-chain.

Observa que se utilizan los métodos get() y set() para recuperar y establecer el estado on-chain.

  • Un contrato inteligente recupera el estado de la cuenta on-chain cuando se invoca por primera vez si existe al menos un get() dentro de él.
  • De manera similar, usar set() cambia la transacción para indicar que los cambios en este estado on-chain particular se actualizan solo cuando la transacción es recibida por la red Mina si contiene una autorización válida (generalmente, una autorización válida es una prueba).

La lógica también utiliza el método .mul() para la multiplicación de los valores almacenados en tipos Field. Puedes ver todos los métodos disponibles en la documentación de referencia de o1js.

Recuerda que las funciones en tu contrato inteligente deben operar con tipos de datos compatibles con o1js: tipos Field y otros tipos construidos sobre tipos Field. Debido a que un contrato inteligente es realmente un circuito de conocimiento cero, las funciones de paquetes NPM arbitrarios funcionan dentro de un contrato inteligente solo si las funciones que proporciona el contrato operan con tipos de datos compatibles con o1js.

Importante, los datos pasados como entrada a un método de contrato inteligente en o1js son privados y nunca son vistos por la red.

También puedes almacenar datos públicamente on chain cuando sea necesario, como num en este ejemplo. Un tutorial posterior cubre un ejemplo que implementa la privacidad.

Felicidades, has revisado el código completo del contrato inteligente.

Interactuar con un contrato inteligente

A continuación, escribirás un script que interactúa con tu contrato inteligente. Para tu referencia, el archivo de ejemplo completo se proporciona en main.ts. Sigue estos pasos para construir el archivo main.ts para que puedas interactuar con el contrato inteligente.

Importaciones

Para este tutorial, la declaración import trae elementos de o1js que utilizas para interactuar con tu contrato inteligente.

  1. Copia las siguientes líneas del archivo de ejemplo main.ts en el archivo src/main.ts:
import { Square } from './Square.js';
import {
  Field,
  Mina,
  PrivateKey,
  AccountUpdate,
} from 'o1js';

Estos elementos importados son:

  • Field: El mismo tipo de entero sin signo de o1js que aprendiste anteriormente.
  • Mina: Una blockchain local de Mina para desplegar el contrato inteligente para que puedas interactuar con él como lo haría un usuario.
  • PrivateKey: Una clase con funciones para manipular claves privadas.
  • AccountUpdate: Una clase que genera una estructura de datos que puede actualizar cuentas zkApp.

Blockchain Local

Usar una blockchain local acelera el desarrollo y prueba el comportamiento de tu contrato inteligente localmente. Los tutoriales posteriores cubren cómo desplegar un zkApp en redes Mina en vivo.

Para inicializar tu blockchain local, agrega el siguiente código a src/main.ts:

const useProof = false;

const Local = Mina.LocalBlockchain({ proofsEnabled: useProof });
Mina.setActiveInstance(Local);
const { privateKey: deployerKey, publicKey: deployerAccount } = Local.testAccounts[0];
const { privateKey: senderKey, publicKey: senderAccount } = Local.testAccounts[1];

Esta blockchain local también proporciona cuentas prefinanciadas. Estas líneas crean cuentas de prueba locales con MINA de prueba para usar en este tutorial:

const { privateKey: deployerKey, publicKey: deployerAccount } = Local.testAccounts[0];
const { privateKey: senderKey, publicKey: senderAccount } = Local.testAccounts[1]; 

Construir y ejecutar el contrato inteligente

Ahora que el contrato inteligente Square está completo, estos comandos ejecutan tu proyecto como una blockchain local.

Para compilar el código TypeScript en JavaScript:

$ npm run build

Para ejecutar el código JavaScript:

$ node build/src/main.js

Tienes la opción de combinar estos comandos en una sola línea:

npm run build && node build/src/main.js
  • El comando npm run build crea código JavaScript en el directorio build.
  • El operador && enlaza dos comandos juntos. El segundo comando se ejecuta solo si el primer comando es exitoso.
  • El comando node build/src/main.js ejecuta el código en src/main.ts.

Inicializar tu contrato inteligente

Para inicializar tu contrato inteligente, vamos a agregar más código al archivo src/main.ts.

Todos los contratos inteligentes que creas con la CLI de zkApp utilizan un código similar:

  • Crear un par de llaves pública/privada; la llave pública es tu dirección y donde despliegas el zkApp
  • Crear una instancia de tu contrato inteligente Square y desplegarlo en zkAppAddress
  • Obtener el estado inicial de Square después del despliegue

Los comentarios desglosan cada etapa:

// Create a public/private key pair. The public key is your address and where you deploy the zkApp to
const zkAppPrivateKey = PrivateKey.random();
const zkAppAddress = zkAppPrivateKey.toPublicKey();

// create an instance of Square - and deploy it to zkAppAddress
const zkAppInstance = new Square(zkAppAddress);
const deployTxn = await Mina.transaction(deployerAccount, () => {
  AccountUpdate.fundNewAccount(deployerAccount);
  zkAppInstance.deploy();
});
await deployTxn.sign([deployerKey, zkAppPrivateKey]).send();

// get the initial state of Square after deployment
const num0 = zkAppInstance.num.get();
console.log('state after init:', num0.toString());

Intenta ejecutar este comando de nuevo:

$ npm run build && node build/src/main.js

La salida esperada es:

> 01-hello-world@0.1.0 build
> tsc

o1js loaded
state after init: 3
Shutting down

Actualizar tu cuenta zkApp con una transacción

Para actualizar tu cuenta zkApp local con una transacción, agrega el siguiente código al archivo src/main.ts:

const txn1 = await Mina.transaction(senderAccount, () => {
  zkAppInstance.update(Field(9));
});
await txn1.prove();
await txn1.sign([senderKey]).send();

const num1 = zkAppInstance.num.get();
console.log('state after txn1:', num1.toString());

Este código crea una nueva transacción que intenta actualizar el campo al valor 9. Debido a las reglas en la función update() que se llama en el contrato inteligente, este comando tiene éxito cuando lo ejecutas nuevamente:

$ npm run build && node build/src/main.js
...
o1js loaded
estado después de iniciar: 3
estado después de txn1: 9
Apagando

Agregar una transacción que falla

Es hora de hacer algunas pruebas. La lógica del contrato permite que el número almacenado como estado on-chain sea reemplazado solo por su cuadrado. Ahora que num está en estado 9, la actualización es posible solo con 81.

Para probar un fallo, actualiza el estado a 75 en zkAppInstance.update(Field(75)):

try {
  const txn2 = await Mina.transaction(senderAccount, () => {
    zkAppInstance.update(Field(75));
  });
  await txn2.prove();
  await txn2.sign([senderKey]).send();
} catch (ex: any) {
  console.log(ex.message);
}
const num2 = zkAppInstance.num.get();
console.log('state after txn2:', num2.toString());

Intenta ejecutar este comando de nuevo:

$ npm run build && node build/src/main.js

La salida esperada es:

$ npm run build && node build/src/main.js
...
o1js loaded
state after init: 3
state after txn1: 9
assert_equal: 75 != 81
state after txn2: 9
Shutting down

Y finalmente, asegúrate de cambiar tu archivo main.ts para incluir la actualización correcta:

const txn3 = await Mina.transaction(senderAccount, () => {
  zkAppInstance.update(Field(81));
});
await txn3.prove();
await txn3.sign([senderKey]).send();

const num3 = zkAppInstance.num.get();
console.log('state after txn3:', num3.toString());

// ----------------------------------------------------

console.log('Shutting down');

Ejecuta este comando de nuevo:

$ npm run build && node build/src/main.js

La salida esperada es:

$ npm run build && node build/src/main.js
...
o1js loaded
state after init: 3
state after txn1: 9
assert_equal: 75 != 81
state after txn2: 9
state after txn3: 81
Apagando

Sigue el tutorial

Puedes seguir este video donde el especialista en criptpgrafía, David Wong, aprende a codificar un proyecto Hola Mundo: Aprende a programar zkApps conmigo

El video se proporciona con fines educativos y utiliza versiones anteriores de zkApp CLI y o1js, por lo que hay algunas diferencias.

Conclusión

¡Felicidades! Has completado con éxito todos los pasos para construir tu primera zkApp con o1js.

Consulta el Tutorial 2: Entradas Privadas y Funciones Hash para aprender a usar entradas privadas y funciones hash con o1js.

Encuentra más tutoriales y recursos en la documentación de zkApps.

Tutorial 2: Entradas Privadas y Funciones Hash

En el tutorial Hola Mundo, construiste un contrato inteligente zkApp básico con o1js con una única variable de estado que podía actualizarse si conocías el cuadrado de ese número.

En este tutorial, aprenderás sobre entradas privadas y funciones hash.

Con un zkApp, el dispositivo local del usuario del contrato inteligente genera una o más pruebas de conocimiento cero, que luego son verificadas por la red Mina. Cada método en un contrato inteligente o1js corresponde a la construcción de una prueba.

Todas las entradas a un contrato inteligente son privadas por defecto. Las entradas nunca son vistas por la blockchain a menos que almacenes esos valores como estado on-chain en la cuenta zkApp.

En este tutorial, construirás un contrato inteligente con una pieza de estado privado que puede ser modificada si un usuario conoce el estado privado.

Prerrequisitos

Este tutorial ha sido probado con Mina zkApp CLI versión 0.9.0 y o1js 0.11.0.

Asegúrate de que tu entorno cumpla con los prerrequisitos mencionados al inicio de este documento.

Crear un proyecto

  1. Crea o cambia a un directorio donde tengas privilegios de escritura.

  2. Crea un proyecto usando el comando zk project:

    ​​​$ zk project 02-private-inputs-and-hash-functions
    

    El comando zk project tiene la capacidad de crear la interfaz de usuario para tu proyecto. Para este tutorial, selecciona none:

  ? Create an accompanying UI project too? …
    next
    svelte
    nuxt
    empty
  ❯ none

La salida esperada es:

  ✔ Create an accompanying UI project too? · none
  ✔ UI: Set up project
  ✔ Initialize Git repo
  ✔ Set up project
  ✔ NPM install
  ✔ NPM build contract
  ✔ Set project name
  ✔ Git init commit

  Success!

  Next steps:
    cd 02-private-inputs-and-hash-functions
    git remote add origin <your-repo-url>
    git push -u origin main

El comando zk project crea el directorio 02-private-inputs-and-hash-functions que contiene la estructura para tu proyecto, incluyendo herramientas como la herramienta de formateo de código Prettier, la herramienta de análisis de código estático ESLint y el framework de pruebas de JavaScript Jest.

  1. Cambia al directorio 02-private-inputs-and-hash-functions y lista los contenidos:
   $ cd 02-private-inputs-and-hash-functions
   $ ls

La salida muestra estos resultados:

   LICENSE
   README.md
   babel.config.cjs
   build
   config.json
   jest-resolver.cjs
   jest.config.js
   keys
   node_modules
   package-lock.json
   package.json
   src
   tsconfig.json

Para este tutorial, ejecutarás comandos desde la raíz del directorio 02-private-inputs-and-hash-functions mientras trabajas en el directorio src en archivos que contienen el código TypeScript para el contrato inteligente.

Cada vez que realices actualizaciones, para luego compilar o desplegar, el código TypeScript se compila en JavaScript en el directorio build.

Preparar el proyecto

Comienza eliminando los archivos predeterminados que vienen con el nuevo proyecto.

  1. Para eliminar los archivos generados por defecto:
   $ rm src/Add.ts
   $ rm src/Add.test.ts
   $ rm src/interact.ts
  1. Ahora, crea los nuevos archivos para tu proyecto:
   $ zk file src/IncrementSecret
   $ touch src/main.ts
  • El comando zk file creó el archivo src/IncrementSecret.ts y el archivo de prueba src/IncrementSecret.test.ts.
  • Sin embargo, este tutorial no incluye escribir pruebas, por lo que simplemente usarás el archivo main.ts como un script para interactuar con el contrato inteligente y observar cómo funciona.
  1. Ahora, abre src/index.ts en un editor de texto y cámbialo para que se vea así:
   import { IncrementSecret } from './IncrementSecret.js';
   export { IncrementSecret };

El archivo src/index.ts contiene todas las exportaciones que deseas hacer disponibles para su consumo desde fuera de tu proyecto de contrato inteligente, como desde una interfaz de usuario.

Copiar el código de ejemplo

Copia el siguiente código en tu archivo IncrementSecret.ts:

import { Field, SmartContract, state, State, method, Poseidon } from 'o1js';

export class IncrementSecret extends SmartContract {
  @state(Field) x = State<Field>();

  @method initState(salt: Field, firstSecret: Field) {
    this.x.set(Poseidon.hash([salt, firstSecret]));
  }

  @method incrementSecret(salt: Field, secret: Field) {
    const x = this.x.get();
    this.x.assertEquals(x);

    Poseidon.hash([salt, secret]).assertEquals(x);
    this.x.set(Poseidon.hash([salt, secret.add(1)]));
  }
}

Copia el siguiente código en tu archivo main.ts:

import { IncrementSecret } from './IncrementSecret.js';
import {
  Field,
  Mina,
  PrivateKey,
  AccountUpdate,
} from 'o1js';


const useProof = false;

const Local = Mina.LocalBlockchain({ proofsEnabled: useProof });
Mina.setActiveInstance(Local);
const { privateKey: deployerKey, publicKey: deployerAccount } =
  Local.testAccounts[0];
const { privateKey: senderKey, publicKey: senderAccount } =
  Local.testAccounts[1];

const salt = Field.random();

// ----------------------------------------------------

// create a destination we will deploy the smart contract to
const zkAppPrivateKey = PrivateKey.random();
const zkAppAddress = zkAppPrivateKey.toPublicKey();

const zkAppInstance = new IncrementSecret(zkAppAddress);
const deployTxn = await Mina.transaction(deployerAccount, () => {
  AccountUpdate.fundNewAccount(deployerAccount);
  zkAppInstance.deploy();
  zkAppInstance.initState(salt, Field(750));
});
await deployTxn.prove();
await deployTxn.sign([deployerKey, zkAppPrivateKey]).send();

// get the initial state of IncrementSecret after deployment
const num0 = zkAppInstance.x.get();
console.log('state after init:', num0.toString());

// ----------------------------------------------------

const txn1 = await Mina.transaction(senderAccount, () => {
  zkAppInstance.incrementSecret(salt, Field(750));
});
await txn1.prove();
await txn1.sign([senderKey]).send();

const num1 = zkAppInstance.x.get();
console.log('state after txn1:', num1.toString());

Escribir el contrato inteligente

Ahora construiremos el contrato inteligente para nuestra aplicación.

Importaciones

La declaración import en el archivo IncrementSecret.ts trae otros paquetes y dependencias para usar en tu contrato inteligente.

Todas las funciones utilizadas dentro de un contrato inteligente deben operar con tipos de datos compatibles con o1js: tipos Field y otros tipos construidos sobre tipos Field.

import { Field, SmartContract, state, State, method, Poseidon } from 'o1js';

Exportaciones

El contrato inteligente llamado IncrementSecret tiene un elemento de estado on-chain llamado x de tipo Field como se define en el siguiente código:

...
export class IncrementSecret extends SmartContract {
  @state(Field) x = State<Field>();
}

Este código agrega la estructura básica para el contrato inteligente. Ya estás familiarizado con el código de importación y exportación del Tutorial Hola Mundo descrito arriba.

Estado Inicial

El método initState() está destinado a ejecutarse una vez para configurar el estado inicial en la cuenta zkApp.

...
@method initState(salt: Field, firstSecret: Field) {
  this.x.set(Poseidon.hash([ salt, firstSecret ]));
}

El método initState() acepta tu secreto y agrega un valor salt.

Estas entradas al método initState() son privadas para quien inicializa el contrato. La cuenta zkApp en el blockchain no revela cuáles son los valores firstSecret o salt en realidad.

Actualizar el Estado

Este método actualiza el estado:

...
@method incrementSecret(salt: Field, secret: Field) {
   const x = this.x.get();
   this.x.assertEquals(x);

   Poseidon.hash([ salt, secret ]).assertEquals(x);
   this.x.set(Poseidon.hash([ salt, secret.add(1) ]));
}

Mina utiliza la función de hash Poseidon que está optimizada para un rendimiento rápido dentro de los sistemas de prueba de conocimiento cero. La función de hash Poseidon toma un arreglo de Fields y devuelve un solo Field como salida.

Este contrato inteligente utiliza un número secreto y el segundo Field, salt.

El método incrementSecret() verifica que el hash del salt y el secreto sea igual al estado actual x:

  • Si es así, agrega 1 al secreto y establece x como el hash del salt y este nuevo secreto.
  • o1js crea una prueba de este hecho y una descripción JSON de las actualizaciones de estado que se harán en la cuenta zkApp, como almacenar el nuevo valor del hash.
  • Juntos, esto forma una transacción que se puede enviar a la red Mina para actualizar la cuenta zkApp.

Debido a que los contratos inteligentes zkApp se ejecutan off-chain, tu salt y secreto permanecen privados y nunca se transmiten a ningún lugar.

Solo el resultado, actualizando el estado on-chain de x a hash([ salt, secret + 1]) se revela. Debido a que el salt y el secreto no se pueden deducir de su hash, permanecen privados.

Acerca del argumento salt

El salt criptográfico añade una capa adicional de seguridad a un contrato inteligente. El argumento extra salt previene un posible ataque al contrato inteligente. Si solo usas secret, el contrato es vulnerable a ser descubierto por un atacante. Un atacante podría intentar hacer hash de secretos probables y luego verificar si el hash coincide con el hash almacenado en el contrato inteligente. Si el hash coincide, entonces el atacante sabe que ha descubierto el secreto. Este escenario es particularmente preocupante si el secreto es probable que esté dentro de un subconjunto particular de valores posibles, digamos entre 1 y 10,000. En ese caso, con solo 10,000 hashes, el atacante podría descubrir el secreto.

Agregar salt como una segunda entrada al código del contrato hace que sea más difícil para un atacante hacer ingeniería inversa y acceder al contrato. El salt hace que el contrato sea más seguro y ayuda a proteger los datos almacenados en él. Para una seguridad óptima, el salt es conocido solo por ti y suele ser aleatorio.

El archivo main.ts

El archivo src/main.ts es similar al tutorial Hola Mundo. Para una versión completa, consulta main.ts.

Para este tutorial, las partes clave a discutir son la inicialización de nuestro contrato y el uso del Poseidon hash.

La inicialización del contrato inteligente esta vez es:

...
const salt = Field.random();
...
const deployTxn = await Mina.transaction(deployerAccount, () => {
  AccountUpdate.fundNewAccount(deployerAccount);
  zkAppInstance.deploy();
  zkAppInstance.initState(salt, Field(750));
});

await deployTxn.prove();
...

Nota que el método initState() acepta el salt y el secreto. En este caso, el secreto es el número 750.

Este código crea una transacción de usuario y actualiza el estado on-chain:

...
const txn1 = await Mina.transaction(senderAccount, () => {
  zkAppInstance.incrementSecret(salt, Field(750));
});
...

Invoca la zkApp con el salt y el secreto (el número 750).

Debido a que los zkApps son ejecutados localmente, ni el secreto ni la sal son parte de la transacción.

En su lugar, la transacción incluye solamente la prueba de que la actualización fue invocada de tal forma que todas las verificaciones pasaron y que se hizo una actualización al estado on-chain x donde el valor del hash está almacenado. Luego de que la transacción es procesada por la red Mina, x tendrá el valor Poseidon.hash([ salt, Field(750).add(1)]). El salt y el secreto no son revelados.

Intenta correr main:

$ npm run build && node build/src/main.js

La salida se ve algo como así:

o1js loaded
state after init: 3116464240601550031577632290308565252747064306168758166756574536757280262269
state after txn1: 15333363135506653312218020664441564145350761288169575380089681962972642150348
Shutting down

Vamos ahora a intentar actualizar el valor del secreto con un número que no corresponde al valor on-chain. Agrega a src/main.ts el siguiente código:

const txn2 = await Mina.transaction(senderAccount, () => {
  zkAppInstance.incrementSecret(salt, Field(1234));
});
await txn2.prove();
await txn2.sign([senderKey]).send();

const num2 = zkAppInstance.x.get();
console.log('state after txn2:', num2.toString());

Deberías recibir un error similar a este:

Error: Field.assertEquals(): 3129191626806317831660800709841622162488652225442964548186218383162970769305 != 22373281554833500418354582585806200234516786908475945664577311628009557470956

Esto ocurre debido a que el número 1234 no corresponde con el valor secreto actualmente almacenado on-chain, que para este momento es 751.

Conclusión

¡Felicidades! Has construido un contrato inteligente que utiliza privacidad y funciones hash.

A continuación aprenderás cómo desplegar zkApps en una red en vivo.

Tutorial 3: Desplegar en una red en vivo

En tutoriales anteriores, aprendiste a desplegar y ejecutar transacciones en una red local.

En este tutorial, utilizarás el comando zk config para crear un alias de despliegue, solicitar fondos de tMINA para pagar las tarifas de transacción y desplegar un proyecto en una red en vivo.

Las zkApps de Mina están disponibles solo en Berkeley, la testnet pública y con todas las características de Mina. Este tutorial reutiliza el contrato Square que creaste en el Tutorial Hola Mundo.

Prerrequisitos

Este tutorial ha sido probado con la versión 0.15.0 de zkApp CLI y la versión 0.14.1 de o1js.

Asegúrate de que tu entorno cumpla con los Prerrequisitos descritos arriba.

Si tienes versiones anteriores de zkApp CLI y o1js instaladas, asegúrate de actualizar tu zkApp CLI a la última versión:

$ npm update -g zkapp-cli

Crear un proyecto

  1. Crea o cambia a un directorio donde tengas privilegios de escritura.
  2. Crea un proyecto utilizando el comando zk project:
$ zk project 03-deploying-to-a-live-network

El comando zk project tiene la capacidad de crear la interfaz de usuario para tu proyecto. Para este tutorial, selecciona none:

  ✔ Create an accompanying UI project too? · none
  ✔ UI: Set up project
  ✔ Initialize Git repo
  ✔ Set up project
  ✔ NPM install
  ✔ NPM build contract
  ✔ Set project name
  ✔ Git init commit

  Success!

  Next steps:
    cd 03-deploying-to-a-live-network
    git remote add origin <your-repo-url>
    git push -u origin main

El comando zk project crea el directorio 03-deploying-to-a-live-network que contiene la estructura para tu proyecto, incluyendo herramientas como la herramienta de formateo de código Prettier, la herramienta de análisis de código estático ESLint y el framework de pruebas de JavaScript Jest.

  1. Cambia al directorio 03-deploying-to-a-live-network.

Para este tutorial, ejecutarás comandos desde la raíz del directorio 03-deploying-to-a-live-network mientras trabajas en el directorio src en archivos que contienen el código TypeScript para el contrato inteligente.

Cada vez que realices actualizaciones, al compilar o desplegar, el código TypeScript se compila en JavaScript en el directorio build.

El comando zk project crea el directorio 03-deploying-to-a-live-network que contiene la estructura para tu proyecto, incluyendo herramientas como la herramienta de formateo de código Prettier, la herramienta de análisis de código estático ESLint y el marco de pruebas de JavaScript Jest.

  1. Cambia al directorio 03-deploying-to-a-live-network.

Para este tutorial, ejecutarás comandos desde la raíz del directorio 03-deploying-to-a-live-network mientras trabajas en el directorio src en archivos que contienen el código TypeScript para el contrato inteligente.

Cada vez que realices actualizaciones, y luego compiles o despliegues, el código TypeScript se compila en JavaScript en el directorio build.

Preparar el proyecto

Comienza eliminando los archivos predeterminados que vienen con el nuevo proyecto.

  1. Elimina los archivos generados por defecto:

    ​​​$ rm src/Add.ts
    ​​​$ rm src/Add.test.ts
    ​​​$ rm src/interact.ts
    
  2. Ahora, crea un nuevo archivo para tu contrato inteligente:

    ​​​$ zk file src/Square
    
  3. Copia los archivos src/Square.ts y src/index.ts de los archivos de ejemplo del primer tutorial 01-hello-world/src a tu directorio local 03-deploying-to-a-live-network/src. Si se solicita, reemplaza los archivos existentes.

Ahora que tu contrato inteligente está en su lugar, estás listo para desplegar tu contrato inteligente en la Testnet de Berkeley.

Mina zkApp CLI

Ya instalaste Mina zkApp CLI como parte de los prerrequisitos, por lo que tienes las herramientas para gestionar despliegues.

En algunos casos, podrías necesitar crear una cuenta personalizada para tu zkApp, por ejemplo, para desplegar un zkApp con una clave diferente a la clave de pagador de tarifas, parametrizar programáticamente un zkApp antes de inicializarlo, o crear un contrato inteligente programáticamente para usuarios como parte de una aplicación. Para más detalles, consulta Interactuar con zkApps en el lado del servidor.

Desplegar el contrato inteligente

El archivo de configuración config.json se creó automáticamente cuando creaste tu proyecto con el comando zk project. Sin embargo, el archivo de configuración generado aún no contiene el alias de despliegue.

Alias de despliegue

Los mensajes de guía del comando zk config te ayudan a crear un alias de despliegue en tu archivo config.json del proyecto.

Puedes tener uno o más alias de despliegue para tu proyecto.

Un alias de despliegue consiste en:

  • Un nombre auto-descriptivo (puede ser cualquier cosa). Utilizar patrones de nombres es útil cuando tienes más de un alias de despliegue.

  • La URL de la API GraphQL de Mina que define la red que recibe tu transacción de despliegue y la transmite a la red Mina apropiada (Testnet, Devnet, Mainnet, etc.)

  • La tarifa de transacción (en MINA) a usar al desplegar

  • Dos pares de claves:

    • Un par de claves para la cuenta zkApp. Las claves pública y privada para usar en tu aplicación se generan automáticamente en keys/berkeley.json.
    • Un par de claves para usar como cuenta pagadora de tarifas para actualizaciones y despliegues. Las claves pública y privada se almacenan en tu computadora local y se pueden usar en varios proyectos.
  • Alias de cuenta pagadora de tarifas

    • Se requiere una cuenta pagadora de tarifas, puedes elegir usar una cuenta existente o crear una nueva cuenta pagadora de tarifas.
  1. Para configurar tu despliegue, ejecuta el comando zk config y responde a las indicaciones:

    ​​​$ zk config
    

    Para este tutorial en la Testnet de Berkeley, utiliza:

    • Nombre del alias de despliegue: berkeley

      Este tutorial utiliza berkeley, pero el nombre del alias de despliegue puede ser cualquiera y no tiene que coincidir con el nombre de la red.

    • URL de la API GraphQL de Mina: https://proxy.berkeley.minaexplorer.com/graphql

    • Tarifa de transacción a utilizar al desplegar: 0.1

    • Cuenta para pagar las tarifas de transacción: Crear un nuevo par de pagadores de tarifas

  2. Cuando se te pida que elijas una cuenta para pagar las tarifas de transacción, selecciona usar una cuenta diferente:

​​Use a different account (select to see options)
  1. A continuación, selecciona crear un nuevo par de claves para el pagador de tarifas:
​​Create a new fee payer key pair
​​NOTE: the private key will be stored in plain text on this computer.
  1. Cuando se te pregunte, da un alias a tu nuevo par de claves para el pagador de tarifas. Para este tutorial, utiliza 03-deploy:
​​Create an alias for this account: viaje-al-blockchain

Tus pares de claves y el alias de despliegue se han creado:

  ✔ Create fee payer key pair at /Users/<username>/.cache/zkapp-cli/keys/viaje-al-blockchain.json
  ✔ Create zkApp key pair at keys/berkeley2.json
  ✔ Add deploy alias to config.json

  Success!

  Next steps:
    - If this is a testnet, request tMINA at:
      https://faucet.minaprotocol.com/?address=B62qqK5JgYAtmh2DHsQfUjUSKwQ6CFSPkGvyMZd19j1BUHfEJEqpKGo&?explorer=minaexplorer
    - To deploy, run: `zk deploy berkeley`
  1. Solicita fondos del Faucet de Testnet para financiar tu cuenta pagadora de tarifas.

    Sigue las indicaciones para solicitar tMINA. Para este tutorial, tu dirección MINA se rellena en el Faucet de Testnet. tMINA llega a tu dirección cuando se produce el próximo bloque (aproximadamente 3 minutos).

  2. Para desplegar tu proyecto:

    ​​​$ zk deploy
    
  3. En el mensaje interactivo, selecciona el alias de despliegue berkeley:

    ​​​   ? Which deploy alias would you like to deploy to? …
    ​​​   ❯ berkeley
    

    Se genera una clave de verificación para tu contrato inteligente (tarda de 10 a 30 segundos).

    Se muestra el proceso de despliegue:

  ✔ Build project
  ✔ Generate build.json
  ✔ Choose smart contract
    Only one smart contract exists in the project: Square
    Your config.json was updated to always use this
    smart contract when deploying to this deploy alias.
  ✔ Generate verification key (takes 10-30 sec)
  ⠋ Build transaction...(node:25066) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
  (Use `node --trace-warnings ...` to show where the warning was created)
  ✔ Build transaction
  1. Revisa y confirma los detalles de la transacción:
      ✖ Confirm to send transaction

        ┌─────────────────┬─────────────────────────────────────────────────┐
        │ Deploy Alias    │ berkeley2                                       │
        ├─────────────────┼─────────────────────────────────────────────────┤
        │ Fee-Payer Alias │ viaje-al-blockchain                             │
        ├─────────────────┼─────────────────────────────────────────────────┤
        │ URL             │ https://proxy.berkeley.minaexplorer.com/graphql │
        ├─────────────────┼─────────────────────────────────────────────────┤
        │ Smart Contract  │ Square                                          │
        └─────────────────┴─────────────────────────────────────────────────┘

Cuando se te pregunte, escribe yes para confirmar y enviar la transacción.

  ✔ Send to network

  Success! Deploy transaction sent.

  Next step:
    Your smart contract will be live (or updated)
    as soon as the transaction is included in a block:
  1. Para ver la transacción zkApp, ve a https://berkeley.minaexplorer.com/transaction/<txn-hash>, donde <txn-hash> es el hash de la transacción que se muestra en tu terminal.

    • La Transacción zkApp Pendiente se muestra hasta que la transacción se incluya en el próximo bloque.
    • La Transacción zkApp se muestra después de que la transacción se incluya en un bloque.

Éxito

Después de que la transacción se incluya en un bloque, ¡tu contrato inteligente está desplegado!

  • La cuenta Mina en esta clave pública ahora contiene la clave de verificación asociada con este contrato inteligente.

Ejecutaste el comando zk config para:

  • Crear un alias de despliegue
  • Crear un par de claves pagadoras de tarifas en /Users/<username>/.cache/zkapp-cli/keys/03deploy.json
  • Crear un par de claves zkApp en keys/berkeley.json

Solicitaste tMINA para financiar tus transacciones de despliegue. Usa el tMINA restante para seguir construyendo y probando.

Ejecutaste el comando zk deploy para:

  • Generar una clave de verificación para tu contrato inteligente
  • Añadir el alias de despliegue al config.json del proyecto

¡Felicidades!

Acerca de las Transacciones del Contrato Inteligente

Debido a que este tutorial utilizó el contrato inteligente del Tutorial 1: Hola Mundo, los permisos de editState del contrato inteligente requieren que una transacción contenga una prueba zk válida que haya sido creada por la clave privada asociada con esta cuenta zkApp.

  • Cuando un usuario interactúa con este contrato inteligente proporcionando una prueba, la prueba se genera localmente en el dispositivo del usuario y se incluye en una transacción.
  • Cuando la transacción se envía a la red, se verifica la prueba para asegurarse de que es correcta y coincide con la clave de verificación on-chain.
  • Después de que la transacción sea aceptada, la prueba y la transacción se prueban recursivamente y se agrupan en la prueba de conocimiento cero recursiva de Mina.

Cuando cambias el código del contrato inteligente, la clave de verificación asociada también cambia. Utiliza los mismos pasos para volver a desplegar el contrato inteligente.

Para un contrato inteligente típico, los permisos se establecen para permitir solo la autorización por medio de prueba.

Video

Mira este tutorial para obtener una guía paso a paso y explicaciones adicionales sobre cómo desplegar un zkApp.

Cómo crear un contrato inteligente

El video se proporciona con fines educativos y utiliza versiones anteriores de zkApp CLI y o1js, por lo que hay algunas diferencias. Este tutorial se ha probado con una versión específica de zkApp CLI y o1js.

Conclusión

¡Felicidades! Has desplegado con éxito un contrato inteligente en una red en vivo.