Introducción al contrato inteligente de Bitcoin (4) - Capítulo práctico del contrato sCrypt - Contrato P2PKH

El último artículo presentó principalmente las funciones relevantes de la herramienta de desarrollo del lenguaje sCrypt, el complemento sCrypt Visual Studio Code . Ahora vamos a comenzar y experimentar el proceso completo de diseño, desarrollo, prueba, implementación e invocación del contrato sCrypt.

diseño

El primer paso para construir cualquier contrato inteligente es completar un diseño a partir de la idea. Aquí elegimos contratar un tipo de transacción común ( P2PKH ) en la red Bitcoin en sCrypt . Hay dos razones principales para usar este proceso como ejemplo:

  1. P2PKH es actualmente el tipo de transacción más importante en la red Bitcoin, y es necesario que los principiantes lo entiendan;
  2. Al contratar este tipo de transacción clásico, puede comprender de manera más intuitiva las capacidades y el uso de sCrypt;

¿Qué es P2PKH?

El nombre completo de P2PKH es Pay To Public Key Hash, que es el tipo de transacción más común en la red Bitcoin y se utiliza para realizar la función de transferencia.

Su script de bloqueo es:

OP_DUP OP_HASH160 <Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG

Su script de desbloqueo es:

<Signature> <Public Key>

Tomemos el ejemplo descrito en el primer artículo de esta serie para explicar su principio e implementación.

Recibir P2PKH

Si alguien quiere transferirme bitcoin, primero necesito decirle el valor hash de mi clave pública (es decir, mi dirección bitcoin, que es equivalente a mi número de tarjeta bancaria), y luego la otra parte usa este valor para construir un script de bloqueo P2PKH (aquí registrado como LS-1) y envía la transacción al minero, y el minero registrará la transacción en la cadena después de la verificación.

Costo P2PKH

Ahora, cuando quiero gastar este bitcoin, necesito proporcionar dos piezas de información para construir el script de desbloqueo:

  • Información de clave pública original (el valor hash de clave pública anterior es calculado por él 1 );
  • La información de la firma de transacción 2 calculada utilizando la clave privada correspondiente a la clave pública original ;

Después de construir la secuencia de comandos de desbloqueo, use el valor hash de clave pública del beneficiario para construir una nueva secuencia de comandos de bloqueo y, finalmente, transmita la transacción.

Verificación P2PKH

Cuando un minero recibe mi nueva transacción, necesita verificar su legalidad, lo que involucra principalmente dos pasos:

  1. Conecte el script de desbloqueo con el script de bloqueo en UTXO (el LS-1 mencionado anteriormente) para formar un script de verificación completo:

    <Signature> <Public Key> OP_DUP OP_HASH160 <Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG

  2. Utilice la máquina virtual para ejecutar este script de verificación para comprobar si el resultado de la ejecución es válido. De hecho, hay dos comprobaciones más críticas en el proceso de verificación:

    2.1 Verifique si la información de clave pública proporcionada en el script de desbloqueo puede calcular el valor hash de la clave pública en el script de bloqueo. Si pasa, significa que la clave pública es efectivamente la dirección del destinatario de la transacción anterior (equivalente a verificar que la dirección del destinatario de la transferencia anterior es mi número de tarjeta bancaria);

    2.2 Verifique que la firma proporcionada en el script de desbloqueo sea coherente con la información de la clave pública. Si pasa, significa que tengo derecho a controlar la clave privada correspondiente a esta clave pública (equivalente a verificar que tengo la contraseña para este número de tarjeta bancaria);

Si se aprueba la verificación de legalidad, demuestra que soy dueño y puedo controlar el bitcoin, y el minero registrará la nueva transacción de gasto en la cadena. Este es el proceso principal y el principio de las transacciones de tipo P2PKH.

En resumen, nuestro objetivo para el diseño de contratos también es muy claro: lograr un contrato sCrypt que sea completamente equivalente a la función P2PKH.

Desarrollo

Una vez que tengamos las ideas de diseño y los objetivos, podemos hacerlo. Primero que nada, por supuesto, instale el complemento sCrypt en VS Code ( como se describe en el artículo anterior ).

sCrypt proporciona un proyecto de muestra para que todos aprendan y desarrollen contratos de prueba rápidamente. Este es un buen punto de partida, también comenzamos desde aquí, primero clonamos el proyecto al local, usamos el comando:

git clone [email protected]:scrypt-sv/boilerplate.git

De hecho, este proyecto ya contiene el contrato P2PKH que queremos, así que mira el código directamente (el archivo es contracts/p2pkh.scrypt):

contract DemoP2PKH {
    
    
  Ripemd160 pubKeyHash;

  constructor(Ripemd160 pubKeyHash) {
    
    
    this.pubKeyHash = pubKeyHash;
  }

  public function unlock(Sig sig, PubKey pubKey) {
    
    
      require(hash160(pubKey) == this.pubKeyHash);
      require(checkSig(sig, pubKey));
  }
}

El contrato también es muy simple, el cuerpo principal incluye:

  • Un tipo Ripemd160de variables de atributo pubKeyHash. Corresponde al script de bloqueo P2PKH anterior <Public Key Hash>;
  • Constructor constructor. Se utiliza para completar la inicialización de las variables de atributo;
  • Una costumbre llamada unlockfunción pública. Tipo de parámetro respectivamente Sigy PubKeycorrespondiente al script de desbloqueo P2PKH anterior <Signature>y <Public Key>; para lograr una lógica correspondiente a hablar en la verificación P2PKH frontal.

En comparación con el script de verificación anterior en forma de script, creo que la mayoría de mis amigos estarán de acuerdo en que el código sCrypt es más fácil de aprender y escribir. Y cuanto más compleja es la función de la lógica del contrato, más obvias se pueden reflejar las ventajas de sCrypt.

prueba de unidad

Con el código, el siguiente paso es verificar si su función está implementada correctamente, en este momento el método convencional es agregar algunas pruebas unitarias. El archivo de prueba para el contrato anterior es el tests/js/p2pkh.scrypttest.jssiguiente:

const path = require('path');
const {
    
     expect } = require('chai');
const {
    
     buildContractClass, bsv } = require('scrypttest');

/**
 * an example test for contract containing signature verification
 */
const {
    
     inputIndex, inputSatoshis, tx, signTx, toHex } = require('../testHelper');

const privateKey = new bsv.PrivateKey.fromRandom('testnet')
const publicKey = privateKey.publicKey
const pkh = bsv.crypto.Hash.sha256ripemd160(publicKey.toBuffer())
const privateKey2 = new bsv.PrivateKey.fromRandom('testnet')

describe('Test sCrypt contract DemoP2PKH In Javascript', () => {
    
    
  let demo
  let sig

  before(() => {
    
    
    const DemoP2PKH = buildContractClass(path.join(__dirname, '../../contracts/p2pkh.scrypt'), tx, inputIndex, inputSatoshis)
    demo = new DemoP2PKH(toHex(pkh))
  });

  it('signature check should succeed when right private key signs', () => {
    
    
    sig = signTx(tx, privateKey, demo.getLockingScript())
    expect(demo.unlock(toHex(sig), toHex(publicKey))).to.equal(true);
    /*
     * print out parameters used in debugger, see ""../.vscode/launch.json" for an example
      console.log(toHex(pkh))
      console.log(toHex(sig))
      console.log(toHex(publicKey))
      console.log(tx.uncheckedSerialize())
    */
  });

  it('signature check should fail when wrong private key signs', () => {
    
    
    sig = signTx(tx, privateKey2, demo.getLockingScript())
    expect(demo.unlock(toHex(sig), toHex(publicKey))).to.equal(false);
  });
});

Los amigos familiares de Javascript pueden buscar para identificar que esto se basa en mocha + chaiun marco de prueba de archivos JS puro. Echemos un vistazo más de cerca a este caso de prueba.

En primer lugar importar el scrypttest función de la biblioteca de prueba Javascript / Letra de imprenta de Scrypt :

const { buildContractClass, bsv } = require('scrypttest');

Usando la función de utilidad buildContractClasspara obtener el DemoP2PKHobjeto de clase de contrato reflejado en Javascript:

const DemoP2PKH = buildContractClass(path.join(__dirname, '../../contracts/p2pkh.scrypt'), tx, inputIndex, inputSatoshis)

Utilice parámetros de inicialización (es decir, el formato hexadecimal del hash de clave pública) para crear una instancia de la clase de contrato:

demo = new DemoP2PKH(toHex(pkh))

El método público de la instancia del contrato de prueba, cuando debería tener éxito:

sig = signTx(tx, privateKey, demo.getLockingScript())
expect(demo.unlock(toHex(sig), toHex(publicKey))).to.equal(true);

O cuando debería fallar (la firma no se puede verificar porque se usa la clave privada incorrecta):

sig = signTx(tx, privateKey2, demo.getLockingScript())
expect(demo.unlock(toHex(sig), toHex(publicKey))).to.equal(false);

Antes de ejecutar las pruebas, necesitamos el directorio raíz de la ejecución del proyecto npm installpara asegurarnos de que las pruebas dependen de la instalación exitosa; justo después de este archivo de prueba, seleccione "Ejecutar prueba sCrypt" en el editor de código VS; ejecute los resultados en la vista "SALIDA" Ver.

Depurar

Solo la prueba unitaria anterior no es suficiente, porque cuando la prueba única falla, solo podemos obtener el resultado final y no hay más información interna que nos ayude a resolver el problema del código del contrato en sí. En este momento, debe utilizar la función de depuración del complemento sCrypt.

En .vscode/launch.jsonse pueden encontrar elementos de configuración de depuración para el archivo de contrato DemoP2PKH:

{
    "type": "scrypt",
    "request": "launch",
    "name": "Debug P2PKH",
    "program": "${workspaceFolder}/contracts/p2pkh.scrypt",
    "constructorParams": "Ripemd160(b'2bc7163e0085b0bcd4e0efd1c537537053aa13f2')",
    "entryMethod": "unlock",
    "entryMethodParams": "Sig(b'30440220729d3935d496e5a708a6a1d4c61dcdd1bebae6f0e0b63b9b9eb1b7616cdbbc2b02203b58cdde0133a6e90d921ecee6ecafca7000a13a3e38673810b4c6badd8d952041'), PubKey(b'03613fa845ad3fe1ef4fe9bbf0b50a1cb5219dd30a0c4e3e4e46fb218313af9220')",
    "txContext": {
        "hex": "01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a40000000000ffffffff0000000000",
        "inputIndex": 0,
        "inputSatoshis": 100000
    }
}

Explique los parámetros clave:

  • program: Especifique el archivo de contrato para la ejecución específica de la configuración;
  • constructorParams: Especifique la lista de parámetros del constructor del contrato. Si hay más de uno, use una coma para conectarse; además, si el contrato no tiene un constructor mostrado, el compilador generará automáticamente un constructor predeterminado, por lo que sus propiedades deben ser utilizado como constructor en orden. La lista de parámetros se pasa.
  • entryMethod: Especifique el nombre de la función pública que se depurará;
  • entryMethodParams: Especifique la lista de parámetros real de la función pública a depurar, y si hay más de uno, use una coma para conectarse;
  • txContext: Especifique la información de contexto relevante de la transacción actual durante la depuración, donde:
    • hex: El formato hexadecimal de la transacción puede estar firmado (transacción firmada) o sin firmar (transacción sin firmar);
    • inputIndex: El número de serie de entrada correspondiente al UTXO que se gastará y bloqueará por contrato;
    • inputSatoshis: La cantidad de bitcoins en UTXO que se gastarán y bloquearán por contrato, en satoshis;

Nota : constructorParamsy entryMethodParamslos parámetros correspondientes deben ser consistentes con los (sub) tipos de parámetros de contrato, y deben ser sintaxis sCrypt. De lo contrario, se informará un error para indicar problemas de parámetros al iniciar la depuración.

Entonces, ¿cómo se obtienen generalmente los parámetros anteriores? Mirando hacia atrás en el archivo de prueba anterior, puede encontrar que hay varias salidas de línea de comando comentadas:

/*
 * print out parameters used in debugger, see ""../.vscode/launch.json" for an example
  console.log(toHex(pkh))
  console.log(toHex(sig))
  console.log(toHex(publicKey))
  console.log(tx.uncheckedSerialize())
*/

Estos resultados son exactamente los parámetros necesarios para la configuración de depuración y, de manera similar, otros contratos también se pueden usar de manera similar para obtener lo que necesitan.

Después de la configuración adecuada, puede utilizar el acceso directo "F5" para iniciar la depuración del código. Las funciones específicas y el uso del depurador también se pueden encontrar en el artículo anterior y en la documentación oficial de VS Code.

Implementar y llamar a testnet

Antes de utilizar el contrato en un entorno de producción, el desarrollador debe realizar las pruebas necesarias en Testnet para asegurarse de que el código del contrato cumple con las expectativas. Por ejemplo, en este artículo, puede usar el comando en el directorio raíz del proyecto node tests/testnet/p2pkh.jspara ejecutar.

Listo para trabajar

Cuando ejecutamos el archivo por primera vez, veremos una salida similar a esta:

Nueva clave privKey generada para testnet: cMtFUvwk43MwBoWs15fU15jWmQEk27yJJjEkWotmPjHHRuXU9qGq
Con dirección: moJnB7AND5TW8suRmdHPbY6knpfE1uJ15n
Puede completar la dirección en testnet y usar la prueba

Porque hay dos requisitos previos para el funcionamiento normal del código:

  1. Necesita una clave privada en la red de prueba;
  2. Hay suficiente BSV (al menos 10000+ satoshis) para probar en la dirección correspondiente a la clave privada;

Si ya tiene dicha clave privada, puede buscar y modificar la siguiente línea de código (use la clave privada en formato WIF en lugar de caracteres vacíos):

const privKey = ''

Por supuesto, también puede usar directamente la clave privada en el resultado de salida anterior, pero debe obtener la moneda de prueba para la dirección en el resultado de salida (por ejemplo , recójalo en este sitio web ).

resultado de la operación

Después de realizar los preparativos antes mencionados, puede ejecutar este caso de uso nuevamente. En circunstancias normales, puede ver el siguiente resultado:

¡El contrato se implementó correctamente! TxId: bc929f1dddc6652896c7c162314e2651fbcd26495bd1ccf9568219e22fea2fb8 ¡
Método de contrato llamado exitosamente! TxId: ce2dba497065d33c1e07bf710ad94e9600c6413e053b4abec2bd8562aea3dc20

Los resultados anteriores muestran que la implementación del contrato y la llamada han sido exitosas, puede ir al navegador de cadena de bloques BSV para ver los detalles de la transacción correspondiente (use el TxId en el resultado de salida para consultar).

Descripción del código

En el tests/testnet/p2pkh.jsarchivo, puede ver el código completo:

const path = require('path')
const {
    
     exit } = require('process')

const {
    
    
  buildContractClass,
  showError,
  bsv
} = require('scrypttest')

const {
    
    
  toHex,
  createLockingTx,
  createUnlockingTx,
  signTx,
  sendTx
} = require('../testHelper')

function getUnlockingScript(method, sig, publicKey) {
    
    
  if (method === 'unlock') {
    
    
    return toHex(sig) + ' ' + toHex(publicKey)
  }
}

async function main() {
    
    
  try {
    
    
    // private key on testnet in WIF
    const privKey = 'cVWvTt4tVqCHgSchQpUHch7EHcDbfXeYZnYbuqXYxpPbXQWPtrxV'
    if (!privKey) {
    
    
      const newPrivKey = new bsv.PrivateKey.fromRandom('testnet')
      console.log('New privKey generated for testnet: ' + newPrivKey.toWIF())
      console.log('With address: ' + newPrivKey.toAddress())
      console.log('You could fund the address on testnet & use the privKey to complete the test') // for example get bsv from: https://faucet.bitcoincloud.net/
      exit(1)
    }
    const privateKey = new bsv.PrivateKey.fromWIF(privKey)
    const publicKey = privateKey.publicKey

    // Initialize contract
    const P2PKH = buildContractClass(path.join(__dirname, '../../contracts/p2pkh.scrypt'))
    const publicKeyHash = bsv.crypto.Hash.sha256ripemd160(publicKey.toBuffer())
    const p2pkh = new P2PKH(toHex(publicKeyHash))

    // deploy contract on testnet
    const amountInContract = 10000
    const deployTx = await createLockingTx(privateKey.toAddress(), amountInContract)
    const lockingScript = p2pkh.getLockingScript()
    deployTx.outputs[0].setScript(bsv.Script.fromASM(lockingScript))
    deployTx.sign(privateKey)
    const deployTxId = await sendTx(deployTx)
    console.log('Contract Deployed Successfully! TxId: ', deployTxId)

    // call contract method on testnet
    const spendAmount = amountInContract / 10
    const methodCallTx = createUnlockingTx(deployTxId, amountInContract, lockingScript, spendAmount, privateKey.toAddress())
    const sig = signTx(methodCallTx, privateKey, lockingScript, amountInContract)
    const unlockingScript = getUnlockingScript('unlock', sig, publicKey)
    methodCallTx.inputs[0].setScript(bsv.Script.fromASM(unlockingScript))
    const methodCallTxId = await sendTx(methodCallTx)
    console.log('Contract Method Called Successfully! TxId: ', methodCallTxId)

  } catch (error) {
    
    
    console.log('Failed on testnet')
    showError(error)
  }
}

main()

Para facilitar la comprensión de todos, echemos un vistazo a la implementación específica de la implementación y la invocación de contratos.

  • Despliegue de contrato:
  1. Cree una nueva transacción bloqueada:

    const deployTx = await createLockingTx(privateKey.toAddress(), amountInContract)

  2. Obtenga el script de bloqueo correspondiente al contrato:

    const lockingScript = p2pkh.getLockingScript()

  3. Configure el script correspondiente a la salida del script de bloqueo anterior:

    deployTx.outputs[0].setScript(bsv.Script.fromASM(lockingScript))

  4. Firma de la transacción:

    deployTx.sign(privateKey)

  5. Envíe la transacción al nodo de servicio:

    const deployTxId = await sendTx(deployTx)

  • Convocatoria de contrato:
  1. Cree una nueva transacción de desbloqueo:

    const methodCallTx = createUnlockingTx(deployTxId, amountInContract, lockingScript, spendAmount, privateKey.toAddress())

  2. Obtenga la firma para esta transacción:

    const sig = signTx(methodCallTx, privateKey, lockingScript, amountInContract)

  3. Obtenga el script de desbloqueo correspondiente a la llamada al método de contrato:

    const unlockingScript = getUnlockingScript('unlock', sig, publicKey)

  4. Configure el script correspondiente a la entrada del script de desbloqueo anterior;

    methodCallTx.inputs[0].setScript(bsv.Script.fromASM(unlockingScript))

  5. Envíe la transacción al nodo de servicio:

    const methodCallTxId = await sendTx(methodCallTx)

Nota : La implementación y la implementación de llamadas de diferentes contratos variarán, pero el proceso general es similar a este ejemplo.

Observaciones finales

Dicho esto, la serie de Introducción a los contratos inteligentes de Bitcoin también ha terminado. Espero que de esta manera, los amigos interesados ​​puedan aprender más y participar en el desarrollo de contratos inteligentes, y crear más posibilidades con la tecnología blockchain. Continúe prestando atención, gracias :)

apéndice


  1. Método de cálculo de hash de clave pública: primero calcule el valor hash SHA256 de la clave pública y luego calcule el valor hash RIPEMD160 del resultado anterior para obtener un valor hash de clave pública de 20 bytes. ↩︎

  2. Para una introducción más detallada de la firma de transacciones (Firma), consulte este documento . ↩︎

Supongo que te gusta

Origin blog.csdn.net/freedomhero/article/details/107235041
Recomendado
Clasificación