Source: workers/packets.js

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

'use strict';

/**
 * @module workers/packets
 */

const assert = require('bsert');
const bio = require('bufio');
const Witness = require('../script/witness');
const Output = require('../primitives/output');
const MTX = require('../primitives/mtx');
const TX = require('../primitives/tx');
const KeyRing = require('../primitives/keyring');
const CoinView = require('../coins/coinview');
const ScriptError = require('../script/scripterror');
const {encoding} = bio;

/*
 * Constants
 */

const packetTypes = {
  ENV: 0,
  EVENT: 1,
  LOG: 2,
  ERROR: 3,
  ERRORRESULT: 4,
  CHECK: 5,
  CHECKRESULT: 6,
  SIGN: 7,
  SIGNRESULT: 8,
  CHECKINPUT: 9,
  CHECKINPUTRESULT: 10,
  SIGNINPUT: 11,
  SIGNINPUTRESULT: 12,
  ECVERIFY: 13,
  ECVERIFYRESULT: 14,
  ECSIGN: 15,
  ECSIGNRESULT: 16,
  MINE: 17,
  MINERESULT: 18,
  SCRYPT: 19,
  SCRYPTRESULT: 20
};

/**
 * Packet
 */

class Packet extends bio.Struct {
  constructor() {
    super();
    this.id = ++Packet.id >>> 0;
    this.cmd = -1;
  }

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

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

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

  decode(data, extra) {
    const br = bio.read(data, true);
    this.read(br, extra);
    return this;
  }
}

Packet.id = 0;

/**
 * EnvPacket
 */

class EnvPacket extends Packet {
  constructor(env) {
    super();
    this.cmd = packetTypes.ENV;
    this.env = env || {};
    this.json = JSON.stringify(this.env);
  }

  getSize() {
    return encoding.sizeVarString(this.json, 'utf8');
  }

  write(bw) {
    bw.writeVarString(this.json, 'utf8');
    return bw;
  }

  read(br) {
    this.json = br.readVarString('utf8');
    this.env = JSON.parse(this.json);
    return this;
  }
}

/**
 * EventPacket
 */

class EventPacket extends Packet {
  constructor(items) {
    super();
    this.cmd = packetTypes.EVENT;
    this.items = items || [];
    this.json = JSON.stringify(this.items);
  }

  getSize() {
    return encoding.sizeVarString(this.json, 'utf8');
  }

  write(bw) {
    bw.writeVarString(this.json, 'utf8');
    return bw;
  }

  read(br) {
    this.json = br.readVarString('utf8');
    this.items = JSON.parse(this.json);
    return this;
  }
}

/**
 * LogPacket
 */

class LogPacket extends Packet {
  constructor(text) {
    super();
    this.cmd = packetTypes.LOG;
    this.text = text || '';
  }

  getSize() {
    return encoding.sizeVarString(this.text, 'utf8');
  }

  write(bw) {
    bw.writeVarString(this.text, 'utf8');
    return bw;
  }

  read(br) {
    this.text = br.readVarString('utf8');
    return this;
  }
}

/**
 * ErrorPacket
 */

class ErrorPacket extends Packet {
  constructor(error) {
    super();
    this.cmd = packetTypes.ERROR;
    this.error = error || new Error();
  }

  getSize() {
    const err = this.error;

    let size = 0;

    size += encoding.sizeVarString(stringify(err.message), 'utf8');
    size += encoding.sizeVarString(stringify(err.stack), 'utf8');
    size += encoding.sizeVarString(stringify(err.type), 'utf8');

    switch (typeof err.code) {
      case 'number':
        size += 1;
        size += 4;
        break;
      case 'string':
        size += 1;
        size += encoding.sizeVarString(err.code, 'utf8');
        break;
      default:
        size += 1;
        break;
    }

    return size;
  }

  write(bw) {
    const err = this.error;

    bw.writeVarString(stringify(err.message), 'utf8');
    bw.writeVarString(stringify(err.stack), 'utf8');
    bw.writeVarString(stringify(err.type), 'utf8');

    switch (typeof err.code) {
      case 'number':
        bw.writeU8(2);
        bw.writeI32(err.code);
        break;
      case 'string':
        bw.writeU8(1);
        bw.writeVarString(err.code, 'utf8');
        break;
      default:
        bw.writeU8(0);
        break;
    }

    return bw;
  }

  read(br) {
    const err = this.error;

    err.message = br.readVarString('utf8');
    err.stack = br.readVarString('utf8');
    err.type = br.readVarString('utf8');

    switch (br.readU8()) {
      case 2:
        err.code = br.readI32();
        break;
      case 1:
        err.code = br.readVarString('utf8');
        break;
      default:
        err.code = null;
        break;
    }

    return this;
  }
}

/**
 * ErrorResultPacket
 */

class ErrorResultPacket extends ErrorPacket {
  constructor(error) {
    super(error);
    this.cmd = packetTypes.ERRORRESULT;
  }
}

/**
 * CheckPacket
 */

class CheckPacket extends Packet {
  constructor(tx, view, flags) {
    super();
    this.cmd = packetTypes.CHECK;
    this.tx = tx || null;
    this.view = view || null;
    this.flags = flags != null ? flags : null;
  }

  getSize() {
    return this.tx.getSize() + this.view.getSize(this.tx) + 4;
  }

  write(bw) {
    this.tx.write(bw);
    this.view.write(bw, this.tx);
    bw.writeI32(this.flags != null ? this.flags : -1);
    return bw;
  }

  read(br) {
    this.tx = TX.read(br);
    this.view = CoinView.read(br, this.tx);
    this.flags = br.readI32();

    if (this.flags === -1)
      this.flags = null;

    return this;
  }
}

/**
 * CheckResultPacket
 */

class CheckResultPacket extends Packet {
  constructor(error) {
    super();
    this.cmd = packetTypes.CHECKRESULT;
    this.error = error || null;
  }

  getSize() {
    const err = this.error;

    let size = 0;

    if (!err) {
      size += 1;
      return size;
    }

    size += 1;
    size += encoding.sizeVarString(stringify(err.message), 'utf8');
    size += encoding.sizeVarString(stringify(err.stack), 'utf8');
    size += encoding.sizeVarString(stringify(err.code), 'utf8');
    size += 1;
    size += 4;

    return size;
  }

  write(bw) {
    const err = this.error;

    if (!err) {
      bw.writeU8(0);
      return bw;
    }

    bw.writeU8(1);
    bw.writeVarString(stringify(err.message), 'utf8');
    bw.writeVarString(stringify(err.stack), 'utf8');
    bw.writeVarString(stringify(err.code), 'utf8');
    bw.writeU8(err.op === -1 ? 0xff : err.op);
    bw.writeU32(err.ip === -1 ? 0xffffffff : err.ip);

    return bw;
  }

  read(br) {
    if (br.readU8() === 0)
      return this;

    const err = new ScriptError('');

    err.message = br.readVarString('utf8');
    err.stack = br.readVarString('utf8');
    err.code = br.readVarString('utf8');
    err.op = br.readU8();
    err.ip = br.readU32();

    if (err.op === 0xff)
      err.op = -1;

    if (err.ip === 0xffffffff)
      err.ip = -1;

    this.error = err;

    return this;
  }
}

/**
 * SignPacket
 */

class SignPacket extends Packet {
  constructor(tx, rings, type) {
    super();
    this.cmd = packetTypes.SIGN;
    this.tx = tx || null;
    this.rings = rings || [];
    this.type = type != null ? type : 1;
  }

  getSize() {
    let size = 0;

    size += this.tx.getSize();
    size += this.tx.view.getSize(this.tx);
    size += encoding.sizeVarint(this.rings.length);

    for (const ring of this.rings)
      size += ring.getSize();

    size += 1;

    return size;
  }

  write(bw) {
    this.tx.write(bw);
    this.tx.view.write(bw, this.tx);

    bw.writeVarint(this.rings.length);

    for (const ring of this.rings)
      ring.write(bw);

    bw.writeU8(this.type);

    return bw;
  }

  read(br) {
    this.tx = MTX.read(br);
    this.tx.view.read(br, this.tx);

    const count = br.readVarint();

    for (let i = 0; i < count; i++) {
      const ring = KeyRing.read(br);
      this.rings.push(ring);
    }

    this.type = br.readU8();

    return this;
  }
}

/**
 * SignResultPacket
 */

class SignResultPacket extends Packet {
  constructor(total, witness, script) {
    super();
    this.cmd = packetTypes.SIGNRESULT;
    this.total = total || 0;
    this.witness = witness || [];
  }

  fromTX(tx, total) {
    this.total = total;

    for (const input of tx.inputs)
      this.witness.push(input.witness);

    return this;
  }

  static fromTX(tx, total) {
    return new SignResultPacket().fromTX(tx, total);
  }

  getSize() {
    let size = 0;

    size += encoding.sizeVarint(this.total);
    size += encoding.sizeVarint(this.witness.length);

    for (let i = 0; i < this.witness.length; i++) {
      const witness = this.witness[i];
      size += witness.getVarSize();
    }

    return size;
  }

  write(bw) {
    bw.writeVarint(this.total);
    bw.writeVarint(this.witness.length);

    for (let i = 0; i < this.witness.length; i++)
      this.witness[i].write(bw);

    return bw;
  }

  inject(tx) {
    assert(this.witness.length === tx.inputs.length);

    for (let i = 0; i < tx.inputs.length; i++) {
      const input = tx.inputs[i];
      input.witness = this.witness[i];
    }
  }

  read(br) {
    this.total = br.readVarint();

    const count = br.readVarint();

    for (let i = 0; i < count; i++)
      this.witness.push(Witness.read(br));

    return this;
  }
}

/**
 * CheckInputPacket
 */

class CheckInputPacket extends Packet {
  constructor(tx, index, coin, flags) {
    super();
    this.cmd = packetTypes.CHECKINPUT;
    this.tx = tx || null;
    this.index = index;
    this.coin = coin || null;
    this.flags = flags != null ? flags : null;
  }

  getSize() {
    let size = 0;
    size += this.tx.getSize();
    size += encoding.sizeVarint(this.index);
    size += encoding.sizeVarint(this.coin.value);
    size += this.coin.script.getVarSize();
    size += 4;
    return size;
  }

  write(bw) {
    this.tx.write(bw);
    bw.writeVarint(this.index);
    bw.writeVarint(this.coin.value);
    this.coin.script.write(bw);
    bw.writeI32(this.flags != null ? this.flags : -1);
    return bw;
  }

  read(br) {
    this.tx = TX.read(br);
    this.index = br.readVarint();

    this.coin = new Output();
    this.coin.value = br.readVarint();
    this.coin.script.read(br);

    this.flags = br.readI32();

    if (this.flags === -1)
      this.flags = null;

    return this;
  }
}

/**
 * CheckInputResultPacket
 */

class CheckInputResultPacket extends CheckResultPacket {
  constructor(error) {
    super(error);
    this.cmd = packetTypes.CHECKINPUTRESULT;
  }
}

/**
 * SignInputPacket
 */

class SignInputPacket extends Packet {
  constructor(tx, index, coin, ring, type) {
    super();
    this.cmd = packetTypes.SIGNINPUT;
    this.tx = tx || null;
    this.index = index;
    this.coin = coin || null;
    this.ring = ring || null;
    this.type = type != null ? type : 1;
  }

  getSize() {
    let size = 0;
    size += this.tx.getSize();
    size += encoding.sizeVarint(this.index);
    size += encoding.sizeVarint(this.coin.value);
    size += this.coin.script.getVarSize();
    size += this.ring.getSize();
    size += 1;
    return size;
  }

  write(bw) {
    this.tx.write(bw);
    bw.writeVarint(this.index);
    bw.writeVarint(this.coin.value);
    this.coin.script.write(bw);
    this.ring.write(bw);
    bw.writeU8(this.type);
    return bw;
  }

  read(br) {
    this.tx = MTX.read(br);
    this.index = br.readVarint();

    this.coin = new Output();
    this.coin.value = br.readVarint();
    this.coin.script.read(br);

    this.ring = KeyRing.read(br);
    this.type = br.readU8();

    return this;
  }
}

/**
 * SignInputResultPacket
 */

class SignInputResultPacket extends Packet {
  constructor(value, witness) {
    super();
    this.cmd = packetTypes.SIGNINPUTRESULT;
    this.value = value || false;
    this.witness = witness || null;
  }

  fromTX(tx, i, value) {
    const input = tx.inputs[i];

    assert(input);

    this.value = value;
    this.witness = input.witness;

    return this;
  }

  static fromTX(tx, i, value) {
    return new SignInputResultPacket().fromTX(tx, i, value);
  }

  getSize() {
    return 1 + this.witness.getVarSize();
  }

  write(bw) {
    bw.writeU8(this.value ? 1 : 0);
    this.witness.write(bw);
    return bw;
  }

  inject(tx, i) {
    const input = tx.inputs[i];
    assert(input);
    input.witness = this.witness;
  }

  read(br) {
    this.value = br.readU8() === 1;
    this.witness = Witness.read(br);
    return this;
  }
}

/**
 * ECVerifyPacket
 */

class ECVerifyPacket extends Packet {
  constructor(msg, sig, key) {
    super();
    this.cmd = packetTypes.ECVERIFY;
    this.msg = msg || null;
    this.sig = sig || null;
    this.key = key || null;
  }

  getSize() {
    let size = 0;
    size += encoding.sizeVarBytes(this.msg);
    size += encoding.sizeVarBytes(this.sig);
    size += encoding.sizeVarBytes(this.key);
    return size;
  }

  write(bw) {
    bw.writeVarBytes(this.msg);
    bw.writeVarBytes(this.sig);
    bw.writeVarBytes(this.key);
    return bw;
  }

  read(br) {
    this.msg = br.readVarBytes();
    this.sig = br.readVarBytes();
    this.key = br.readVarBytes();
    return this;
  }
}

/**
 * ECVerifyResultPacket
 */

class ECVerifyResultPacket extends Packet {
  constructor(value) {
    super();
    this.cmd = packetTypes.ECVERIFYRESULT;
    this.value = value;
  }

  getSize() {
    return 1;
  }

  write(bw) {
    bw.writeU8(this.value ? 1 : 0);
    return bw;
  }

  read(br) {
    this.value = br.readU8() === 1;
    return this;
  }
}

/**
 * ECSignPacket
 */

class ECSignPacket extends Packet {
  constructor(msg, key) {
    super();
    this.cmd = packetTypes.ECSIGN;
    this.msg = msg || null;
    this.key = key || null;
  }

  getSize() {
    let size = 0;
    size += encoding.sizeVarBytes(this.msg);
    size += encoding.sizeVarBytes(this.key);
    return size;
  }

  write(bw) {
    bw.writeVarBytes(this.msg);
    bw.writeVarBytes(this.key);
    return bw;
  }

  read(br) {
    this.msg = br.readVarBytes();
    this.key = br.readVarBytes();
    return this;
  }
}

/**
 * ECSignResultPacket
 */

class ECSignResultPacket extends Packet {
  constructor(sig) {
    super();
    this.cmd = packetTypes.ECSIGNRESULT;
    this.sig = sig;
  }

  getSize() {
    return encoding.sizeVarBytes(this.sig);
  }

  write(bw) {
    bw.writeVarBytes(this.sig);
    return bw;
  }

  read(br) {
    this.sig = br.readVarBytes();
    return this;
  }
}

/**
 * MinePacket
 */

class MinePacket extends Packet {
  constructor(hdr, target, rounds) {
    super();
    this.cmd = packetTypes.MINE;
    this.hdr = hdr || null;
    this.target = target || null;
    this.rounds = rounds != null ? rounds : -1;
  }

  getSize() {
    return 256 + 32 + 4;
  }

  write(bw) {
    bw.writeBytes(this.hdr);
    bw.writeBytes(this.target);
    bw.writeU32(this.rounds);
    return bw;
  }

  read(br) {
    this.hdr = br.readBytes(256);
    this.target = br.readBytes(32);
    this.rounds = br.readU32();
    return this;
  }
}

/**
 * MineResultPacket
 */

class MineResultPacket extends Packet {
  constructor(nonce, solved) {
    super();
    this.cmd = packetTypes.MINERESULT;
    this.nonce = nonce || 0;
    this.solved = solved || false;
  }

  getSize() {
    return 4 + 1;
  }

  write(bw) {
    bw.writeU32(this.nonce);
    bw.writeU8(this.solved ? 1 : 0);
    return bw;
  }

  read(br) {
    this.nonce = br.readU32();
    this.solved = br.readU8() === 1;
    return this;
  }
}

/**
 * ScryptPacket
 */

class ScryptPacket extends Packet {
  constructor(passwd, salt, N, r, p, len) {
    super();
    this.cmd = packetTypes.SCRYPT;
    this.passwd = passwd || null;
    this.salt = salt || null;
    this.N = N != null ? N : -1;
    this.r = r != null ? r : -1;
    this.p = p != null ? p : -1;
    this.len = len != null ? len : -1;
  }

  getSize() {
    let size = 0;
    size += encoding.sizeVarBytes(this.passwd);
    size += encoding.sizeVarBytes(this.salt);
    size += 16;
    return size;
  }

  write(bw) {
    bw.writeVarBytes(this.passwd);
    bw.writeVarBytes(this.salt);
    bw.writeU32(this.N);
    bw.writeU32(this.r);
    bw.writeU32(this.p);
    bw.writeU32(this.len);
    return bw;
  }

  read(br) {
    this.passwd = br.readVarBytes();
    this.salt = br.readVarBytes();
    this.N = br.readU32();
    this.r = br.readU32();
    this.p = br.readU32();
    this.len = br.readU32();
    return this;
  }
}

/**
 * ScryptResultPacket
 */

class ScryptResultPacket extends Packet {
  constructor(key) {
    super();
    this.cmd = packetTypes.SCRYPTRESULT;
    this.key = key || null;
  }

  getSize() {
    return encoding.sizeVarBytes(this.key);
  }

  write(bw) {
    bw.writeVarBytes(this.key);
    return bw;
  }

  read(br) {
    this.key = br.readVarBytes();
    return this;
  }
}

/*
 * Helpers
 */

function stringify(value) {
  if (typeof value !== 'string')
    return '';
  return value;
}

/*
 * Expose
 */

exports.types = packetTypes;
exports.EnvPacket = EnvPacket;
exports.EventPacket = EventPacket;
exports.LogPacket = LogPacket;
exports.ErrorPacket = ErrorPacket;
exports.ErrorResultPacket = ErrorResultPacket;
exports.CheckPacket = CheckPacket;
exports.CheckResultPacket = CheckResultPacket;
exports.SignPacket = SignPacket;
exports.SignResultPacket = SignResultPacket;
exports.CheckInputPacket = CheckInputPacket;
exports.CheckInputResultPacket = CheckInputResultPacket;
exports.SignInputPacket = SignInputPacket;
exports.SignInputResultPacket = SignInputResultPacket;
exports.ECVerifyPacket = ECVerifyPacket;
exports.ECVerifyResultPacket = ECVerifyResultPacket;
exports.ECSignPacket = ECSignPacket;
exports.ECSignResultPacket = ECSignResultPacket;
exports.MinePacket = MinePacket;
exports.MineResultPacket = MineResultPacket;
exports.ScryptPacket = ScryptPacket;
exports.ScryptResultPacket = ScryptResultPacket;