Source: primitives/abstractblock.js

/*!
 * abstractblock.js - abstract block object for hsd
 * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
 * https://github.com/handshake-org/hsd
 */

'use strict';

const assert = require('bsert');
const BLAKE2b = require('bcrypto/lib/blake2b');
const SHA3 = require('bcrypto/lib/sha3');
const bio = require('bufio');
const InvItem = require('./invitem');
const consensus = require('../protocol/consensus');
const util = require('../utils/util');

/** @typedef {import('../types').Hash} Hash */
/** @typedef {import('../types').BufioWriter} BufioWriter */

/**
 * Abstract Block
 * The class which all block-like objects inherit from.
 * @alias module:primitives.AbstractBlock
 * @abstract
 * @property {Number} version
 * @property {Hash} prevBlock
 * @property {Hash} merkleRoot
 * @property {Number} time
 * @property {Number} bits
 * @property {Number} nonce
 */

class AbstractBlock extends bio.Struct {
  /**
   * Create an abstract block.
   * @constructor
   */

  constructor() {
    super();

    this.version = 0;
    this.prevBlock = consensus.ZERO_HASH;
    this.merkleRoot = consensus.ZERO_HASH;
    this.witnessRoot = consensus.ZERO_HASH;
    this.treeRoot = consensus.ZERO_HASH;
    this.reservedRoot = consensus.ZERO_HASH;
    this.time = 0;
    this.bits = 0;
    this.nonce = 0;
    this.extraNonce = consensus.ZERO_NONCE;
    this.mask = consensus.ZERO_HASH;

    this.mutable = false;

    /** @type {Buffer?} */
    this._hash = null;
    /** @type {Buffer?} */
    this._maskHash = null;
  }

  /**
   * Inject properties from options object.
   * @param {Object} options
   */

  parseOptions(options) {
    assert(options, 'Block data is required.');
    assert((options.version >>> 0) === options.version);
    assert(Buffer.isBuffer(options.prevBlock));
    assert(Buffer.isBuffer(options.merkleRoot));
    assert(Buffer.isBuffer(options.witnessRoot));
    assert(Buffer.isBuffer(options.treeRoot));
    assert(Buffer.isBuffer(options.reservedRoot));
    assert(util.isU64(options.time));
    assert((options.bits >>> 0) === options.bits);
    assert((options.nonce >>> 0) === options.nonce);
    assert(Buffer.isBuffer(options.extraNonce)
      && options.extraNonce.length === consensus.NONCE_SIZE);
    assert(Buffer.isBuffer(options.mask));

    this.version = options.version;
    this.prevBlock = options.prevBlock;
    this.merkleRoot = options.merkleRoot;
    this.witnessRoot = options.witnessRoot;
    this.treeRoot = options.treeRoot;
    this.reservedRoot = options.reservedRoot;
    this.time = options.time;
    this.bits = options.bits;
    this.nonce = options.nonce;
    this.extraNonce = options.extraNonce;
    this.mask = options.mask;

    if (options.mutable != null) {
      assert(typeof options.mutable === 'boolean');
      this.mutable = options.mutable;
    }

    return this;
  }

  /**
   * Inject properties from json object.
   * @param {Object} json
   */

  parseJSON(json) {
    assert(json, 'Block data is required.');
    assert((json.version >>> 0) === json.version);
    assert((json.time >>> 0) === json.time);
    assert((json.bits >>> 0) === json.bits);
    assert((json.nonce >>> 0) === json.nonce);

    this.version = json.version;
    this.prevBlock = util.parseHex(json.prevBlock, 32);
    this.merkleRoot = util.parseHex(json.merkleRoot, 32);
    this.witnessRoot = util.parseHex(json.witnessRoot, 32);
    this.treeRoot = util.parseHex(json.treeRoot, 32);
    this.reservedRoot = util.parseHex(json.reservedRoot, 32);
    this.time = json.time;
    this.bits = json.bits;
    this.nonce = json.nonce;
    this.extraNonce = util.parseHex(json.extraNonce, consensus.NONCE_SIZE);
    this.mask = util.parseHex(json.mask, 32);

    return this;
  }

  /**
   * Test whether the block is a memblock.
   * @returns {Boolean}
   */

  isMemory() {
    return false;
  }

  /**
   * Clear any cached values (abstract).
   */

  _refresh() {
    this._hash = null;
    this._maskHash = null;
    return this;
  }

  /**
   * Clear any cached values.
   */

  refresh() {
    return this._refresh();
  }

  /**
   * Hash the block header.
   * @returns {Hash}
   */

  hash() {
    if (this.mutable)
      return this.powHash();

    if (!this._hash)
      this._hash = this.powHash();

    return this._hash;
  }

  /**
   * Hash the block header.
   * @returns {String}
   */

  hashHex() {
    return this.hash().toString('hex');
  }

  /**
   * Get header size.
   * @returns {Number}
   */

  sizeHead() {
    return consensus.HEADER_SIZE;
  }

  /**
   * Serialize the block headers.
   * @returns {Buffer}
   */

  toHead() {
    const size = this.sizeHead();
    return this.writeHead(bio.write(size)).render();
  }

  /**
   * Inject properties from serialized data.
   * @param {Buffer} data
   * @returns {this}
   */

  fromHead(data) {
    return this.readHead(bio.read(data));
  }

  /**
   * Retrieve deterministically random padding.
   * @param {Number} size
   * @returns {Buffer}
   */

  padding(size) {
    assert((size >>> 0) === size);

    const pad = Buffer.alloc(size);

    for (let i = 0; i < size; i++)
      pad[i] = this.prevBlock[i % 32] ^ this.treeRoot[i % 32];

    return pad;
  }

  /**
   * Serialize subheader for proof.
   * @returns {Buffer}
   */

  toSubhead() {
    const bw = bio.write(128);

    // The subheader contains miner-mutable
    // and less essential data (that is,
    // less essential for SPV resolvers).
    bw.writeBytes(this.extraNonce);
    bw.writeHash(this.reservedRoot);
    bw.writeHash(this.witnessRoot);
    bw.writeHash(this.merkleRoot);
    bw.writeU32(this.version);
    bw.writeU32(this.bits);

    // Exactly one blake2b block (128 bytes).
    assert(bw.offset === BLAKE2b.blockSize);

    return bw.render();
  }

  /**
   * Compute subheader hash.
   * @returns {Buffer}
   */

  subHash() {
    return BLAKE2b.digest(this.toSubhead());
  }

  /**
   * Compute xor bytes hash.
   * @returns {Buffer}
   */

  maskHash() {
    if (this._maskHash != null)
      return this._maskHash;

    // Hash with previous block in case a pool wants
    // to re-use the same mask for the next block!
    return BLAKE2b.multi(this.prevBlock, this.mask);
  }

  /**
   * Compute commitment hash.
   * @returns {Buffer}
   */

  commitHash() {
    // Note for mining pools: do not send
    // the mask itself to individual miners.
    return BLAKE2b.multi(this.subHash(), this.maskHash());
  }

  /**
   * Serialize preheader.
   * @returns {Buffer}
   */

  toPrehead() {
    const bw = bio.write(128);

    // The preheader contains only the truly
    // essential data. This optimizes for
    // SPV resolvers, who may only need
    // access to the tree root as well as
    // the ability to validate the PoW.
    //
    // Note that we don't consider the
    // target commitment "essential" as
    // the pow can still be validated
    // contextually without it.
    //
    // Furthermore, the preheader does not
    // contain any miner malleable data
    // aside from the timestamp and nonce.
    //
    // Any malleable data is contained
    // within the commitment hash. Miners
    // are penalized for updating this
    // data, as it will cost them two
    // rounds of hashing.
    //
    // We could drop the padding here and
    // just use a 20 byte blake2 hash for
    // the xor bytes (which seems much
    // cleaner), but this is insecure due
    // to the following attack:
    // todo - explain attack.
    //
    // The position of the nonce and
    // timestamp intentionally provide
    // incentives to keep the timestamp
    // up-to-date.
    //
    // The first 8 bytes of this block
    // of data can be treated as a uint64
    // and incremented as such. If more
    // than a second has passed since
    // the last timestamp update, a miner
    // can simply let the nonce overflow
    // into the timestamp.
    bw.writeU32(this.nonce);
    bw.writeU64(this.time);
    bw.writeBytes(this.padding(20));
    bw.writeHash(this.prevBlock);
    bw.writeHash(this.treeRoot);
    bw.writeHash(this.commitHash());

    // Exactly one blake2b block (128 bytes).
    assert(bw.offset === BLAKE2b.blockSize);

    return bw.render();
  }

  /**
   * Calculate share hash.
   * @returns {Hash}
   */

  shareHash() {
    const data = this.toPrehead();

    // 128 bytes (output as BLAKE2b-512).
    const left = BLAKE2b.digest(data, 64);

    // 128 + 8 = 136 bytes.
    const right = SHA3.multi(data, this.padding(8));

    // 64 + 32 + 32 = 128 bytes.
    return BLAKE2b.multi(left, this.padding(32), right);
  }

  /**
   * Calculate PoW hash.
   * @returns {Buffer}
   */

  powHash() {
    const hash = this.shareHash();

    // XOR the PoW hash with arbitrary bytes.
    // This can optionally be used by mining
    // pools to mitigate block withholding
    // attacks. Idea from Kevin Pan:
    //
    // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-October/015163.html
    //
    // The goal here is to allow a pool to
    // deny individual miners the ability
    // to recognize whether they have found
    // a block, but still allow them to
    // recognize a share.
    //
    // Example:
    //
    //   Network target:
    //   00000000 00000000 10000000 ...
    //
    //   Share target:
    //   00000000 10000000 00000000 ...
    //
    //   Mask:
    //   00000000 01010101 10000000 ...
    //
    // The mask bytes are hidden from the
    // individual miner, but known to the
    // pool, and precommitted to in the
    // block header (i.e. hashed).
    //
    // Following our example further:
    //
    //   Miner share:
    //   00000000 01010101 00000000 ...
    //
    //   PoW hash (after XOR):
    //   00000000 00000000 10000000 ...
    //
    // At this point, the miner has found
    // a block, but this is unknown to
    // him or her as they do not have
    // access to the mask bytes directly.
    for (let i = 0; i < 32; i++)
      hash[i] ^= this.mask[i];

    return hash;
  }

  /**
   * Serialize the block headers.
   * @param {BufioWriter} bw
   * @returns {BufioWriter}
   */

  writeHead(bw) {
    // Preheader.
    bw.writeU32(this.nonce);
    bw.writeU64(this.time);
    bw.writeHash(this.prevBlock);
    bw.writeHash(this.treeRoot);

    // Subheader.
    bw.writeBytes(this.extraNonce);
    bw.writeHash(this.reservedRoot);
    bw.writeHash(this.witnessRoot);
    bw.writeHash(this.merkleRoot);
    bw.writeU32(this.version);
    bw.writeU32(this.bits);

    // Mask.
    bw.writeBytes(this.mask);

    return bw;
  }

  /**
   * Parse the block headers.
   * @param {bio.BufferReader} br
   */

  readHead(br) {
    // Preheader.
    this.nonce = br.readU32();
    this.time = br.readU64();
    this.prevBlock = br.readHash();
    this.treeRoot = br.readHash();

    // Subheader.
    this.extraNonce = br.readBytes(consensus.NONCE_SIZE);
    this.reservedRoot = br.readHash();
    this.witnessRoot = br.readHash();
    this.merkleRoot = br.readHash();
    this.version = br.readU32();
    this.bits = br.readU32();

    // Mask.
    this.mask = br.readBytes(32);

    return this;
  }

  /**
   * Encode to miner serialization.
   * @returns {Buffer}
   */

  toMiner() {
    const bw = bio.write(128 + 128);

    // Preheader.
    bw.writeU32(this.nonce);
    bw.writeU64(this.time);
    bw.writeBytes(this.padding(20));
    bw.writeHash(this.prevBlock);
    bw.writeHash(this.treeRoot);

    // Replace commitment hash with mask hash.
    bw.writeHash(this.maskHash());

    // Subheader.
    bw.writeBytes(this.extraNonce);
    bw.writeHash(this.reservedRoot);
    bw.writeHash(this.witnessRoot);
    bw.writeHash(this.merkleRoot);
    bw.writeU32(this.version);
    bw.writeU32(this.bits);

    return bw.render();
  }

  /**
   * Decode from miner serialization.
   * @param {Buffer} data
   */

  fromMiner(data) {
    const br = bio.read(data);

    // Preheader.
    this.nonce = br.readU32();
    this.time = br.readU64();

    const padding = br.readBytes(20);

    this.prevBlock = br.readHash();
    this.treeRoot = br.readHash();

    assert(padding.equals(this.padding(20)));

    // Note: mask _hash_.
    this._maskHash = br.readHash();

    // Subheader.
    this.extraNonce = br.readBytes(consensus.NONCE_SIZE);
    this.reservedRoot = br.readHash();
    this.witnessRoot = br.readHash();
    this.merkleRoot = br.readHash();
    this.version = br.readU32();
    this.bits = br.readU32();

    // Mask (unknown).
    this.mask = Buffer.alloc(32, 0x00);

    return this;
  }

  /**
   * Verify the block.
   * @returns {Boolean}
   */

  verify() {
    if (!this.verifyPOW())
      return false;

    if (!this.verifyBody())
      return false;

    return true;
  }

  /**
   * Verify proof-of-work.
   * @returns {Boolean}
   */

  verifyPOW() {
    return consensus.verifyPOW(this.hash(), this.bits);
  }

  /**
   * Verify the block.
   * @returns {Boolean}
   */

  verifyBody() {
    throw new Error('Abstract method.');
  }

  /**
   * Convert the block to an inv item.
   * @returns {InvItem}
   */

  toInv() {
    return new InvItem(InvItem.types.BLOCK, this.hash());
  }

  /**
   * Decode from miner serialization.
   * @param {Buffer} data
   */

  static fromMiner(data) {
    return new this().fromMiner(data);
  }
}

/*
 * Expose
 */

module.exports = AbstractBlock;