Source: covenants/bitfield.js

'use strict';

const assert = require('bsert');
const AirdropProof = require('../primitives/airdropproof');
const {TREE_LEAVES} = AirdropProof;

/**
 * Field
 */

class Field {
  constructor(size = 0) {
    assert((size >>> 0) === size);

    this.size = size;
    this.field = Buffer.alloc((size + 7) >>> 3, 0x00);
    this.dirty = false;
  }

  set(i, val) {
    assert((i >>> 0) === i);
    assert(i < this.size);
    assert((val >>> 0) === val);
    assert(val === 0 || val === 1);

    if (val)
      this.field[i >>> 3] |= 1 << (7 - (i & 7));
    else
      this.field[i >>> 3] &= ~(1 << (7 - (i & 7)));

    this.dirty = true;

    return this;
  }

  get(i) {
    assert((i >>> 0) === i);

    if (i >= this.size)
      return 1;

    return (this.field[i >>> 3] >> (7 - (i & 7))) & 1;
  }

  isSpent(i) {
    return Boolean(this.get(i));
  }

  spend(i) {
    return this.set(i, 1);
  }

  unspend(i) {
    return this.set(i, 0);
  }

  encode() {
    this.dirty = false;
    return this.field;
  }

  decode(data) {
    assert(Buffer.isBuffer(data));
    this.field = data;
    this.dirty = false;
    return this;
  }

  static decode(size, data) {
    return new this(size).decode(data);
  }
}

/**
 * BitField
 */

class BitField extends Field {
  constructor() {
    super(TREE_LEAVES);
  }

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

/**
 * BitView
 */

class BitView {
  constructor() {
    this.bits = new Map();
  }

  spend(field, tx) {
    assert(field instanceof Field);
    assert(tx && tx.isCoinbase());

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

      assert(output && witness.items.length === 1);

      const {covenant} = output;

      if (!covenant.isNone())
        continue;

      const proof = AirdropProof.decode(witness.items[0]);
      const index = proof.position();

      if (!this.bits.has(index))
        this.bits.set(index, field.get(index));

      if (this.bits.get(index) !== 0)
        return false;

      this.bits.set(index, 1);
    }

    return true;
  }

  undo(tx) {
    assert(tx && tx.isCoinbase());

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

      assert(output && witness.items.length === 1);

      const {covenant} = output;

      if (!covenant.isNone())
        continue;

      const proof = AirdropProof.decode(witness.items[0]);
      const index = proof.position();

      this.bits.set(index, 0);
    }

    return this;
  }

  commit(field) {
    assert(field instanceof Field);

    for (const [bit, spent] of this.bits)
      field.set(bit, spent);

    return field.dirty;
  }
}

/*
 * Expose
 */

exports.Field = Field;
exports.BitField = BitField;
exports.BitView = BitView;