A Step-by-Step Guide to Developing Bitcoin Smart Contracts

We show the complete workflow of the design, development, testing, deployment, and invocation process of smart contracts in sCrypt.


  • P2PKH is the most popular type of transactions in the bitcoin network used to sending bitcoins from one to another, which is necessary for beginners to understand
  • By implementing this classic transaction type, one can more intuitively understand the capabilities and usage of Script/sCrypt

What’s P2PKH?


It’s unlocking script is:

<Signature> <Public Key>

Receiving of P2PKH

Spending of P2PKH

  • The original public key that the above public key hash value is calculated from;
  • A Signature calculated by using the private key corresponding to the original public key.

After constructing the unlocking script and placing it in a transaction, Bob broadcasts the transaction.

Verification of P2PKH

  • Concatenate the unlocking script with the locking script to form the following verification Script:
<Signature> <Public Key> OP_DUP OP_HASH160 <Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
  • Use the Bitcoin Virtual Machine to execute this verification Script to check whether the execution result is valid. There are two critical checks in the verification process:
  1. Verify that the hash, which can be calculate by the public key information provided in the unlocking script, is equal to the hash provided in the locking script. If it passes, the public key is indeed the recipient address of the previous transaction. (It is equivalent to verifying that the receiving address of the previous transfer is Bob’s bank account number)
  2. Verify that the signature provided in the unlocking script matches the public key information. If it passes, it means that Alice does have control over the private key corresponding to this public key.

If the verification passes, it proves that Alice does own and can control the bitcoins, and the miner will record the new spending transaction on chain. This is the main process and principle of P2PKH type transactions.

In summary, our goal for contract design is also very clear: to implement a sCrypt contract that is equivalent to the function of P2PKH.


A sample project is provided for anyone to quickly learn how to develop and test sCrypt contracts. It is a good place to start. First clone the project locally, using the command:

git clone git@github.com:sCrypt-Inc/boilerplate.git

Actually, the P2PKH contract we wanted is included in the project. So let us look directly at the code at contracts/p2pkh.scrypt.

Contract DemoP2PKH

The main body of the contract includes:

  • A contract variable pubKeyHash of type Ripemd160, corresponding to the previous P2PKH locking script <Public Key Hash> ;
  • Constructorconstructor, used to initialize the contract variable;
  • A custom public function named unlock which has two parameter with type Sig and PubKey, corresponding to the previous P2PKH unlocking script<Signature> <Public Key>.The implementation logic also corresponds to the P2PKH validation described earlier.

It is obvious that the sCrypt implementation is much easier to learn and write than previous scripts. And the more complex the contractual logic, the more obvious the advantage of using sCrypt is.

Unit testing

Anyone familiar with JavaScript might immediately recognize that this is a pure JS test file based on the MOCHA + Chai framework. Let’s take a closer look at this test case.

  • First Import sCrypt’s Javascript/Typescript library scrypttest:
const { buildContractClass, bsv } = require('scrypttest');
  • Use the function buildContractClass to get the class object reflected into Javascript of the contract DemoP2PKH:
const DemoP2PKH = buildContractClass(path.join(__dirname, '../../contracts/p2pkh.scrypt'), tx, inputIndex, inputSatoshis)
  • Use arguments (that is, the hex format of the public key hash) to instantiate the contract:
demo = new DemoP2PKH(toHex(pkh))
  • Test the public method of the contract instance and expect it to succeed:
sig = signTx(tx, privateKey, demo.getLockingScript())
expect(demo.unlock(toHex(sig), toHex(publicKey))).to.equal(true);
  • Expect it to fail if the signature cannot be verified because the wrong private key is used:
sig = signTx(tx, privateKey2, demo.getLockingScript())
expect(demo.unlock(toHex(sig), toHex(publicKey))).to.equal(false);

Before running the test, we need to run npm install in the root directory of the project to ensure that the dependencies have been successfully installed. Then right click the test file in VS Code editor and select “Run sCrypt Test” ; The result is shown in the “Output” view.


A sample debug configuration for the DemoP2PKH contract can be found in the file .vscode/launch.json :


The key parameters are:

  • program: the contract file for this debug configuration.
  • constructorParams: the contract’s constructor argument list, separated by comma.
  • entryMethod: specifies the public function name to be debugged.
  • entryMethodParams: specify the actual argument list of the public function to be debugged, also separated by comma.
  • txContext: specifies context information about the current transaction at debugging time, where:
  1. txContext.hex: the hex format of the transaction, can be signed or unsigned.
  2. txContext.inputIndex: the input index corresponding to the contract-locked UTXO to be spent.
  3. txContext.inputSatoshis: The amount of bitcoins corresponding to the contract-locked UTXO to be spent, in unit of satoshi.

So how does one obtain these arguments in general? Looking back at the previous test file, you can see that there are several log statements commented out:

This log is exactly what you need for the debug configuration, and you can get it for other contracts in a similar way.

With the configuration in place, you can use the “F5” shortcut to start code debugging. See the previous article and the official VS Code documentation for details of what the debugger does and how to use it.

Deploy and Call the Contract


New privKey generated for testnet: cMtFUvwk43MwBoWs15fU15jWmQEk27yJJjEkWotmPjHHRuXU9qGq
With address: moJnB7AND5TW8suRmdHPbY6knpfE1uJ15n
You could fund the address on testnet & use the privKey to complete the test

Because the code requires two things to work:

  1. A private key on the test network is required;
  2. The private key has sufficient balance in the corresponding address for testing.

If you already have such a private key, you can find and modify the following line of code in privateKey.js (using a private key in WIF format instead of a empty character) :

const privKey = ''

Of course, you can also use the private key in the output above, but first you need to get the test coin for the address in the output, such as on this site.

Expected Result

Contract Deployed Successfully! TxId: bc929f1dddc6652896c7c162314e2651fbcd26495bd1ccf9568219e22fea2fb8
Contract Method Called Successfully! TxId: ce2dba497065d33c1e07bf710ad94e9600c6413e053b4abec2bd8562aea3dc20

The above result shows that the contract deployment and call have been successful, and you can go to a blockchain browser to see the corresponding transaction details (using the txid in the output results) .

The full code is available atdeployments/p2pkh.js.


Let’s take a look at the specific implementation of contract deployment and invocation.

Contract Deployment

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

2. Set the script for the output to be the locking script above:


3. Transaction Signature:


4. Broadcast transaction to the bitcoin network:

const deployTxId = await sendTx(deployTx)

Contract Call

const spendAmount = amountInContract / 10
const methodCallTx = createUnlockingTx(deployTxId, amountInContract, p2pkh.lockingScript.toASM(), spendAmount)

2. Get a signature for this transaction:

const sig = signTx(methodCallTx, privateKey, p2pkh.lockingScript.toASM(), amountInContract)

3. Get the unlocking script corresponding to the contract method call.

const unlockingScript = p2pkh.unlock(new Sig(toHex(sig)), new PubKey(toHex(publicKey))).toScript()

4. Set the corresponding input script to the above unlocking script:


5. Send transaction to the network:

const methodCallTxId = await sendTx(methodCallTx)


Translated from article by Yiqiang Wang with help of Hongfeng Zheng.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store

sCrypt (https://scrypt.io) is a company with a mission to provide integrated on-chain smart contracting solutions on Bitcoin Satoshi Vision