/zencashjs

Dead simple and easy to use JavaScript library for Horizen

Primary LanguageJavaScriptMIT LicenseMIT

zencashjs build status

A javascript library for Node.js. Used in the development of Sphere and other wallets. Implements all non-computationally intensive operations.

address.js

  • Create private keys, public keys, and addresses
  • Transform addresses / keys into different formats (WIF)

transaction.js

  • Create and sign (non-shielded) transactions (1 input & multiple inputs)
  • Transaction serialization / deserialization

message.js

  • Sign and verify messages

address.js

  • Create secret key and transform to a spending key, paying key, or transmission key
  • Viewing key is not yet supported

Address / keys prefixes for both mainnet and testnet in config.js.

Inspired by pybitcointools

Installation

npm install zencashjs

Usage

See examples below

Migrating from 1.x to 2.x

Version 2 brings sidechain support and compatibility with Zendoo. Most functionality and example usage remains identical to Version 1, with the exception of the changes listed below:

zencashjs.transaction.deserializeTx

This function now takes a third parameter envPubKeyHash: string

function deserializeTx (
   hexStr: string,
   withPrevScriptPubKey: boolean = false,
   envPubKeyHash: string = zconfig.mainnet.pubKeyHash
): TXOBJ {}

This parameter envPubKeyHash is a constant hashed value representing the environment (mainnet/testnet). These constants are stored on the zencashjs.config object.

For example,

// Testnet
const testnetTxHex = 'fcff...';

zencashjs.transaction.deserializeTx(
  testnetTxHex,
  false,
  zencashjs.config.testnet.pubKeyHash
);


// Mainnet
const mainnetTxHex = 'fcff...';

zencashjs.transaction.deserializeTx(
  mainnetTxHex,
  false,
  zencashjs.config.mainnet.pubKeyHash // Could be omitted, as it defaults to mainnet
);

zencashjs.transaction.createRawTx

This function now takes a fifth optional parameter vft to be used for a forward transfer transaction

function createRawTx (
   history: HISTORY[],
   recipients: RECIPIENTS[],
   blockHeight: number,
   blockHash: string,
   vft: TXOBJ.vft_ccout
): TXOBJ {}

Example forward transfer transaction

// Sending 10 ZEN from address 'ztphoWCQmyJVuNq2L3SLnRgy2Lw5i5a7hxL' to address '8d8137d57eee250bdd0302fcad05243276ba059556165517c3d919331cd5bdc8' on sidechain with sidechain id '1a4d5813b260d0cb456c649b005840e1a1eb6eb2e0f98f3af7d201ea1e95d0b8'
var blockHeight = 937649
var blockHash = '0002d980065e2bb502a302905ed81229aa467a9502b527a491d7978b970b1ae5'

var txobj = zencashjs.transaction.createRawTx(
  [{
      txid: '087faec93611add81dcf0ec31df934248e63c4abde21ee81d65584c7396feb2b', vout: 0,
      scriptPubKey: ''
  }],
  [{address: 'ztphoWCQmyJVuNq2L3SLnRgy2Lw5i5a7hxL', satoshis: 297799952794}],
  blockHeight,
  blockHash,
  [{
    scid: "1a4d5813b260d0cb456c649b005840e1a1eb6eb2e0f98f3af7d201ea1e95d0b8",
    n: 0,
    value: 1000000000,
    address: "8d8137d57eee250bdd0302fcad05243276ba059556165517c3d919331cd5bdc8",
    mcReturnAddress: "ztphoWCQmyJVuNq2L3SLnRgy2Lw5i5a7hxL"
  }]
)
// { locktime: 0,
//   version: -4,
//   ins:
//    [ { output: { hash: '087faec93611add81dcf0ec31df934248e63c4abde21ee81d65584c7396feb2b', vout: 0 },
//        script: '',
//        prevScriptPubKey: '',
//        sequence: 'ffffffff' } ],
//   outs:
//    [ { script:
//         '76a914ec6039c0505e74b8f74fb1e22b77da64d30ce6b388ac20e51a0b978b97d791a427b502957a46aa2912d85e9002a302b52b5e0680d9020003b14e0eb4',
//        satoshis: 297799952794 } ],
//   vft_ccout:
//    [ { scid:
//         '1a4d5813b260d0cb456c649b005840e1a1eb6eb2e0f98f3af7d201ea1e95d0b8',
//        n: 0,
//        value: 1000000000,
//        address:
//         '8d8137d57eee250bdd0302fcad05243276ba059556165517c3d919331cd5bdc8',
//        mcReturnAddress: 'ztphoWCQmyJVuNq2L3SLnRgy2Lw5i5a7hxL' } ],
//   vsc_ccout: [],
//   vcsw_ccin: [],
//   vmbtr_out: [] }

zencashjs.transaction.serializeTx(txobj)
// fcffffff012beb6f39c78455d681ee21deabc4638e2434f91dc30ecf1dd8ad1136c9ae7f080000000000ffffffff019aa94256450000003f76a914ec6039c0505e74b8f74fb1e22b77da64d30ce6b388ac20e51a0b978b97d791a427b502957a46aa2912d85e9002a302b52b5e0680d9020003b14e0eb400000100ca9a3b00000000c8bdd51c3319d9c3175516569505ba76322405adfc0203dd0b25ee7ed537818db8d0951eea01d2f73a8ff9e0b26eeba1e14058009b646c45cbd060b213584d1aec6039c0505e74b8f74fb1e22b77da64d30ce6b30000000000

New Transaction Types

Zendoo introduces new transaction types related to sidechains. Therefore, deserializeTx will return extra fields if applicable. See the Zendoo Upgrade Guide for more details on these transaction types.

See also the updated TX_OBJ type declaration.

Note: serializeTx and createRawTx do not yet support all the new types (only forward transfers are supported), but will in future releases.

Version 1

Example usage (Transparent address)

var zencashjs = require('zencashjs')

var priv = zencashjs.address.mkPrivKey('chris p. bacon, defender of the guardians')
// 2c3a48576fe6e8a466e78cd2957c9dc62128135540bbea0685d7c4a23ea35a6c

var privWIF = zencashjs.address.privKeyToWIF(priv)
// 5J9mKPd531Tk4A73kKp4iowoi6EvhEp8QSMAVzrZhuzZkdpYbK8

var pubKey = zencashjs.address.privKeyToPubKey(priv, true) // generate compressed pubKey
// 038a789e0910b6aa314f63d2cc666bd44fa4b71d7397cb5466902dc594c1a0a0d2

var zAddr = zencashjs.address.pubKeyToAddr(pubKey)
// znnjppzJG7ajT7f6Vp1AD6SjgcXBVPA2E6c

// It is imperative that the block used for bip115BlockHeight and bip115BlockHash has a sufficient number of
// confirmations (recommded values: 150 to 600 blocks older than current BLOCKHEIGHT). If the block used for
// the replay protection should get orphaned the transaction will be unspendable for at least 52596 blocks.
// For details on the replay protection please see: https://github.com/bitcoin/bips/blob/master/bip-0115.mediawiki

// To create and sign a raw transaction at BLOCKHEIGHT with BIP115BLOCKHEIGHT and BIP115BLOCKHASH
const blockHeight = 450150 // Example of current BLOCKHEIGHT
const bip115BlockHeight = blockHeight - 150 // Chaintip - 150 blocks, the block used for the replay protection needs a sufficient number of confirmations
const bip115BlockHash = '0000000007844e62d471b966cc5926bd92e56a27d2c6a6ac8f90d34e11d3028d' // Blockhash of block 450000

// If it is unfeasable to get the current BLOCKHEIGHT or transactions are to be signed completely offline
// use hard coded values for BIP115BLOCKHEIGHT and BIP115BLOCKHASH that are at least 52596 blocks older than the
// current BLOCKHEIGHT. For example:
const bip115BlockHeight = 142091
const bip115BlockHash = '00000001cf4e27ce1dd8028408ed0a48edd445ba388170c9468ba0d42fff3052'

var txobj = zencashjs.transaction.createRawTx(
  [{
      txid: '196173ec34d22a52cc689a21d01dd33b633671cbe1141e7e66240c7f3b4ccf7b', vout: 0,
      scriptPubKey: '76a914da46f44467949ac9321b16402c32bbeede5e3e5f88ac20ebd78933082d25d56a47d471ee5d57793454cf3d2787f77c21f9964b02000000034f2902b4'
  }],
  [{address: 'znkz4JE6Y4m8xWoo4ryTnpxwBT5F7vFDgNf', satoshis: 100000}],
  bip115BlockHeight,
  bip115BlockHash
)

// To do a NULL_DATA transaction
// var txobj = zencashjs.transaction.createRawTx(
//   [{
//       txid: '196173ec34d22a52cc689a21d01dd33b633671cbe1141e7e66240c7f3b4ccf7b', vout: 0,
//       scriptPubKey: '76a914da46f44467949ac9321b16402c32bbeede5e3e5f88ac20ebd78933082d25d56a47d471ee5d57793454cf3d2787f77c21f9964b02000000034f2902b4'
//   }],
//   [{address: 'znkz4JE6Y4m8xWoo4ryTnpxwBT5F7vFDgNf', satoshis: 99000},
//    {address: undefined, data: 'hello world', satoshis: 900}],
//   bip115BlockHeight,
//   bip115BlockHash
// )

// zencashjs.transaction.serializeTx(txobj)
// 01000000019dd5ae887ce5e354c4cabe75230a439b03e494f36c5e7726cb7385f892a304270000000000ffffffff01a0860100000000003f76a914da46f44467949ac9321b16402c32bbeede5e3e5f88ac205230ff2fd4a08b46c9708138ba45d4ed480aed088402d81dce274ecf01000000030b2b02b400000000

var tx0 = zencashjs.transaction.signTx(txobj, 0, '2c3a48576fe6e8a466e78cd2957c9dc62128135540bbea0685d7c4a23ea35a6c', true) // The final argument sets the `compressPubKey` boolean. It is `false` by default.
// zencashjs.transaction.serializeTx(tx0)
// 01000000017bcf4c3b7f0c24667e1e14e1cb7136633bd31dd0219a68cc522ad234ec736119000000008b483045022100b69baff0eb5570fd8ddda7b180463035d47eb3b1c07a808a68085fd58e9e22b102202eb3983a2137af4f8c3967b3c6c16c024ad952a712ab92b8911a8797f1864d3d0141048a789e0910b6aa314f63d2cc666bd44fa4b71d7397cb5466902dc594c1a0a0d2e4d234528ff87b83f971ab2b12cd2939ff33c7846716827a5b0e8233049d8aadffffffff01a0860100000000003f76a914da46f44467949ac9321b16402c32bbeede5e3e5f88ac205230ff2fd4a08b46c9708138ba45d4ed480aed088402d81dce274ecf01000000030b2b02b400000000

// You can now do zen-cli sendrawtransaction `SERIALIZED_TRANSACTION`

// To serialize and deserialize an unsigned raw transaction for signing on a separate machine, 'prevScriptPubKey' can be
// encoded in the raw transaction by passing 'true' as a second optional parameter to serializeTx/deserializeTx.
// Take note that the final signed transaction has to be serialized with serializeTx(tx, false).

// Default serializeTx/deserializeTx setting 'prevScriptPubKey' to '':
zencashjs.transaction.deserializeTx(zencashjs.transaction.serializeTx(txobj))
// {
//   version: 1,
//   locktime: 0,
//   ins: [
//     {
//       output: [Object],
//       script: '',
//       sequence: 'ffffffff',
//       prevScriptPubKey: ''
//     }
//   ],
//   outs: [
//     {
//       satoshis: 100000,
//       script: '76a914da46f44467949ac9321b16402c32bbeede5e3e5f88ac200206260143838b5ff52dc2eb7b4b8099d4e4c99dc3ef19794289a2cd4c10070000b4'
//     }
//   ]
// }

// serializeTx/deserializeTx with 2nd argument 'true' keeping 'prevScriptPubKey':
zencashjs.transaction.deserializeTx(zencashjs.transaction.serializeTx(txobj, true), true)
// {
//   version: 1,
//   locktime: 0,
//   ins: [
//     {
//       output: [Object],
//       script: '',
//       sequence: 'ffffffff',
//       prevScriptPubKey: '76a914da46f44467949ac9321b16402c32bbeede5e3e5f88ac20ebd78933082d25d56a47d471ee5d57793454cf3d2787f77c21f9964b02000000034f2902b4'
//     }
//   ],
//   outs: [
//     {
//       satoshis: 100000,
//       script: '76a914da46f44467949ac9321b16402c32bbeede5e3e5f88ac200206260143838b5ff52dc2eb7b4b8099d4e4c99dc3ef19794289a2cd4c10070000b4'
//     }
//   ]
// }

Example Usage (Multi-sig)

var zencashjs = require('zencashjs')

// Private keys in wallet import format
var privKeysWIF = ['L2sjwCsdZQmckKkTKGDqhKcWtbe3EU2FL4N1YHpD2SC1GhHRhqxF', 'L5bpskJWAGGWR1GA9SJkCQ2ndHkezqm8GuoWaBesrrwnsa1roSN6',
'KxvE58rxEwckkCjemDVdMDp7wzgosnyX1oyjzWmrcAVpV7EaZdSP']

// Converts Private keys in WIF to its original form
var privKeys = privKeysWIF.map((x) => zencashjs.address.WIFToPrivKey(x))
// [ 'a8c04b7209d16606532bbbd158420bd7dcde415db5ff3ec269f9c3cb327661d6', 'fa137ed90f4876b7154884caf2d889de9ea9a838e1c938380c2de25d4de613f7', '32ae14a84a5f4e0ec804daef223cc7f48412abde76f78a2d2df463bb065d6617' ]

// Get public keys (NOT address)
var pubKeys = privKeys.map((x) => zencashjs.address.privKeyToPubKey(x, true))
// [ '03519842d08ea56a635bfa8dd617b8e33f0426530d8e201107dd9a6af9493bd487', '02d3ac8c0cb7b99a26cd66269a312afe4e0a621579dfe8b33e29c597a32a616544', '02696187262f522cf1fa2c30c5cd6853c4a6c51ad5ba418abb4e3898dbc5a93d2e' ]

// Make a 2-of-3 multisig address
// NOTE: The redeemScript determines the order of your signatures for multisign
//       E.g. I made an address with pk1, pk3, pk2 for a 2-of-3 multisig
//            Valid Sigs: [pk1, pk2] OR [pk3, pk2] OR [pk1, pk3] ...
//            Invalid Sigs: [pk3, pk1], [pk2, pk1]
var redeemScript = zencashjs.address.mkMultiSigRedeemScript(pubKeys, 2, 3)
// 522103519842d08ea56a635bfa8dd617b8e33f0426530d8e201107dd9a6af9493bd4872102d3ac8c0cb7b99a26cd66269a312afe4e0a621579dfe8b33e29c597a32a6165442102696187262f522cf1fa2c30c5cd6853c4a6c51ad5ba418abb4e3898dbc5a93d2e53ae

var multiSigAddress = zencashjs.address.multiSigRSToAddress(redeemScript)
// zsmSCni8GXoCdTGqUfn26QJVGh6rpaFs17T

// It is imperative that the block used for bip115BlockHeight and bip115BlockHash has a sufficient number of
// confirmations (recommded values: 150 to 600 blocks older than current BLOCKHEIGHT). If the block used for
// the replay protection should get orphaned the transaction will be unspendable for at least 52596 blocks.
// For details on the replay protection please see: https://github.com/bitcoin/bips/blob/master/bip-0115.mediawiki

// To create and sign a raw transaction at BLOCKHEIGHT with BIP115BLOCKHEIGHT and BIP115BLOCKHASH
const blockHeight = 450150 // Example of current BLOCKHEIGHT
const bip115BlockHeight = blockHeight - 150 // Chaintip - 150 blocks, the block used for the replay protection needs a sufficient number of confirmations
const bip115BlockHash = '0000000007844e62d471b966cc5926bd92e56a27d2c6a6ac8f90d34e11d3028d' // Blockhash of block 450000

// If it is unfeasable to get the current BLOCKHEIGHT or transactions are to be signed completely offline
// use hard coded values for BIP115BLOCKHEIGHT and BIP115BLOCKHASH that are at least 52596 blocks older than the
// current BLOCKHEIGHT. For example:
const bip115BlockHeight = 142091
const bip115BlockHash = '00000001cf4e27ce1dd8028408ed0a48edd445ba388170c9468ba0d42fff3052'

var txobj = zencashjs.transaction.createRawTx(
  [{
      txid: 'f5f324064de9caab9353674c59f1c3987ca997bf5882a41a722686883e089581', vout: 0,
      scriptPubKey: '' // Don't need script pub key since we'll be using redeemScript to sign
  }],
  [{address: 'zneng6nRqTrqTKfjYAqXT86HWtk96ftPjtX', satoshis: 10000}],
  bip115BlockHeight,
  bip115BlockHash
)

// Prepare our signatures for mutli-sig
var sig1 = zencashjs.transaction.multiSign(txobj, 0, privKeys[0], redeemScript)
// 3045022100c65ec438dc13028b1328a0f8426e1970ef202cba168772fe9d91d141e3020413022021b038c2098c29014aa7feef1624c3d9e4035ca960791f3bbe256df9f008038d01

var sig2 = zencashjs.transaction.multiSign(txobj, 0, privKeys[1], redeemScript)
// 3045022100db1f423fe11bf06c9c97692e8086f5743653cad289e3a1c085ae656847ffb9d10220063c103d8c7c54597b055106ab70a45a2254c63435b64375a966c002f85d141901

// NOTE: If you wanna send the tx to someone to get their signature, you can serialize the txObj and send it over in bytes, they can also deserialize it: e.g.
// var txBytes = zencashjs.transaction.serializeTx(txobj)
// var txObj = zencashjs.transaction.deserializeTx(txBytes)

// Apply the signatures to the transaction object
var tx0 = zencashjs.transaction.applyMultiSignatures(txobj, 0, [sig1, sig2], redeemScript)

// Serialize the transaction
var serializedTx = zencashjs.transaction.serializeTx(tx0)

// You can now send the serializedTx using the RPC command sendrawtransaction or through an API like insight

Example usage (Private address)

var zencashjs = require('zencashjs')

var z_secretKey = zencashjs.zaddress.mkZSecretKey('Z pigs likes to snooze. ZZZZ')
// 0c10a61a669bc4a51000c4c74ff58c151912889891089f7bae5e4994a73af7a8

// Spending key (this is what you import into your wallet)
var spendingKey = zencashjs.zaddress.zSecretKeyToSpendingKey(z_secretKey)
// SKxtHJsneoLByrwME9Nh4cd4AvYLNK9jJkAnB3AHNW794udD1qpx

// Paying key
var a_pk = zencashjs.zaddress.zSecretKeyToPayingKey(z_secretKey)
// 927a3819627247c0dd39102ec5449fc6fc952e242aad08615df9f26718912e27

// Transmission key
var pk_enc = zencashjs.zaddress.zSecretKeyToTransmissionKey(z_secretKey)
// 22d666c34ababacf6a9a4a752cc7870b505b64e85638aa45d23ac32992397960

var Zaddress = zencashjs.zaddress.mkZAddress(a_pk, pk_enc)
// zcTPZR8Hqz2ZcStwMJju9L4VBHW7YWmNyL6tDAT4eVmzmxLaG7h4QmqUXfmrjz8twizH4piDGiRYJRZ1bhHhT5gFL6TKsQZ

Example usage (Sign/Verify message)

var zencashjs = require('zencashjs')

var priv = zencashjs.address.mkPrivKey('chris p. bacon, defender of the guardians')
// 2c3a48576fe6e8a466e78cd2957c9dc62128135540bbea0685d7c4a23ea35a6c

var privWIF = zencashjs.address.privKeyToWIF(priv)
// 5J9mKPd531Tk4A73kKp4iowoi6EvhEp8QSMAVzrZhuzZkdpYbK8

var pubKey = zencashjs.address.privKeyToPubKey(priv, true) // generate compressed pubKey
// 038a789e0910b6aa314f63d2cc666bd44fa4b71d7397cb5466902dc594c1a0a0d2

var zAddr = zencashjs.address.pubKeyToAddr(pubKey)
// znnjppzJG7ajT7f6Vp1AD6SjgcXBVPA2E6c

var message = 'ciao'

/**
 * Will sign a message with a given zen private key.
 *
 * @param {string} message - The message to be signed
 * @param {string} privateKey - A private key
 * @param {Boolean} compressed
 * @returns {Buffer} The signature
 */
var signature = zencashjs.message.sign(message, priv, true)
// signature.toString('base64')
// IC7SPUlfyjKjG7hrrPWc6WTsm79ksjHAYXSFmjlGwAL9SSvAIvpYmOm3U5CzG2k0QBxDYuzpltw+UAET3J8e7Gg=

/**
 * Validate a signature against a given zend address.
 *
 * @param {String} message - the message to verify
 * @param {String} zenAddress - A zen address
 * @param {String|Buffer} signature - A base64 encoded compact signature
 * @returns {Boolean} true if the signature is valid
 */
var verification = zencashjs.message.verify(message, zAddr, signature)
 // true

Development guide (more to come..)

# src is where the source code resides.
# lib is where the transpiled code resides in.
# edit src if you wanna make a PR
git clone https://github.com/HorizenOfficial/zencashjs.git
cd zencashjs
npm install
npm run [dev | build | test]