Source: primitives/airdropkey.js

/* eslint camelcase: 'off' */

'use strict';

const assert = require('bsert');
const bio = require('bufio');
const base16 = require('bcrypto/lib/encoding/base16');
const bech32 = require('bcrypto/lib/encoding/bech32');
const BLAKE2b = require('bcrypto/lib/blake2b');
const SHA256 = require('bcrypto/lib/sha256');
const rsa = require('bcrypto/lib/rsa');
const p256 = require('bcrypto/lib/p256');
const ed25519 = require('bcrypto/lib/ed25519');
const {countLeft} = require('bcrypto/lib/encoding/util');
const Goo = require('goosig');

/*
 * Goo
 */

const goo = new Goo(Goo.RSA2048, 2, 3);

/*
 * Constants
 */

const keyTypes = {
  RSA: 0,
  GOO: 1,
  P256: 2,
  ED25519: 3,
  ADDRESS: 4
};

const keyTypesByVal = {
  [keyTypes.RSA]: 'RSA',
  [keyTypes.GOO]: 'GOO',
  [keyTypes.P256]: 'P256',
  [keyTypes.ED25519]: 'ED25519',
  [keyTypes.ADDRESS]: 'ADDRESS'
};

const EMPTY = Buffer.alloc(0);

/**
 * AirdropKey
 */

class AirdropKey extends bio.Struct {
  constructor() {
    super();
    this.type = keyTypes.RSA;
    this.n = EMPTY;
    this.e = EMPTY;
    this.C1 = EMPTY;
    this.point = EMPTY;
    this.version = 0;
    this.address = EMPTY;
    this.value = 0;
    this.sponsor = false;
    this.nonce = SHA256.zero;
    this.tweak = null;
  }

  inject(key) {
    assert(key instanceof AirdropKey);

    this.type = key.type;
    this.n = key.n;
    this.e = key.e;
    this.C1 = key.C1;
    this.point = key.point;
    this.version = key.version;
    this.address = key.address;
    this.value = key.value;
    this.sponsor = key.sponsor;
    this.nonce = key.nonce;
    this.tweak = key.tweak;

    return this;
  }

  isRSA() {
    return this.type === keyTypes.RSA;
  }

  isGoo() {
    return this.type === keyTypes.GOO;
  }

  isP256() {
    return this.type === keyTypes.P256;
  }

  isED25519() {
    return this.type === keyTypes.ED25519;
  }

  isAddress() {
    return this.type === keyTypes.ADDRESS;
  }

  isWeak() {
    if (!this.isRSA())
      return false;

    return countLeft(this.n) < 2048 - 7;
  }

  validate() {
    switch (this.type) {
      case keyTypes.RSA: {
        let key;

        try {
          key = rsa.publicKeyImport({ n: this.n, e: this.e });
        } catch (e) {
          return false;
        }

        const bits = rsa.publicKeyBits(key);

        // Allow 1024 bit RSA for now.
        // We can softfork out later.
        return bits >= 1024 && bits <= 4096;
      }

      case keyTypes.GOO: {
        return this.C1.length === goo.size;
      }

      case keyTypes.P256: {
        return p256.publicKeyVerify(this.point);
      }

      case keyTypes.ED25519: {
        return ed25519.publicKeyVerify(this.point);
      }

      case keyTypes.ADDRESS: {
        return true;
      }

      default: {
        throw new assert.AssertionError('Invalid key type.');
      }
    }
  }

  verify(msg, sig) {
    assert(Buffer.isBuffer(msg));
    assert(Buffer.isBuffer(sig));

    switch (this.type) {
      case keyTypes.RSA: {
        let key;

        try {
          key = rsa.publicKeyImport({ n: this.n, e: this.e });
        } catch (e) {
          return false;
        }

        return rsa.verify(SHA256, msg, sig, key);
      }

      case keyTypes.GOO: {
        return goo.verify(msg, sig, this.C1);
      }

      case keyTypes.P256: {
        return p256.verify(msg, sig, this.point);
      }

      case keyTypes.ED25519: {
        return ed25519.verify(msg, sig, this.point);
      }

      case keyTypes.ADDRESS: {
        return true;
      }

      default: {
        throw new assert.AssertionError('Invalid key type.');
      }
    }
  }

  hash() {
    const bw = bio.pool(this.getSize());
    this.write(bw);
    return BLAKE2b.digest(bw.render());
  }

  getSize() {
    let size = 0;

    size += 1;

    switch (this.type) {
      case keyTypes.RSA:
        assert(this.n.length <= 0xffff);
        assert(this.e.length <= 0xff);
        size += 2;
        size += this.n.length;
        size += 1;
        size += this.e.length;
        size += 32;
        break;
      case keyTypes.GOO:
        size += goo.size;
        break;
      case keyTypes.P256:
        size += 33;
        size += 32;
        break;
      case keyTypes.ED25519:
        size += 32;
        size += 32;
        break;
      case keyTypes.ADDRESS:
        size += 1;
        size += 1;
        size += this.address.length;
        size += 8;
        size += 1;
        break;
      default:
        throw new assert.AssertionError('Invalid key type.');
    }

    return size;
  }

  write(bw) {
    bw.writeU8(this.type);

    switch (this.type) {
      case keyTypes.RSA:
        bw.writeU16(this.n.length);
        bw.writeBytes(this.n);
        bw.writeU8(this.e.length);
        bw.writeBytes(this.e);
        bw.writeBytes(this.nonce);
        break;
      case keyTypes.GOO:
        bw.writeBytes(this.C1);
        break;
      case keyTypes.P256:
      case keyTypes.ED25519:
        bw.writeBytes(this.point);
        bw.writeBytes(this.nonce);
        break;
      case keyTypes.ADDRESS:
        bw.writeU8(this.version);
        bw.writeU8(this.address.length);
        bw.writeBytes(this.address);
        bw.writeU64(this.value);
        bw.writeU8(this.sponsor ? 1 : 0);
        break;
      default:
        throw new assert.AssertionError('Invalid key type.');
    }

    return bw;
  }

  read(br) {
    this.type = br.readU8();

    switch (this.type) {
      case keyTypes.RSA: {
        this.n = br.readBytes(br.readU16());
        this.e = br.readBytes(br.readU8());
        this.nonce = br.readBytes(32);
        break;
      }

      case keyTypes.GOO: {
        this.C1 = br.readBytes(goo.size);
        break;
      }

      case keyTypes.P256: {
        this.point = br.readBytes(33);
        this.nonce = br.readBytes(32);
        break;
      }

      case keyTypes.ED25519: {
        this.point = br.readBytes(32);
        this.nonce = br.readBytes(32);
        break;
      }

      case keyTypes.ADDRESS: {
        this.version = br.readU8();
        this.address = br.readBytes(br.readU8());
        this.value = br.readU64();
        this.sponsor = br.readU8() === 1;
        break;
      }

      default: {
        throw new Error('Unknown key type.');
      }
    }

    return this;
  }

  fromAddress(addr, value, sponsor = false) {
    assert(typeof addr === 'string');
    assert(Number.isSafeInteger(value) && value >= 0);
    assert(typeof sponsor === 'boolean');

    const [hrp, version, hash] = bech32.decode(addr);

    assert(hrp === 'hs' || hrp === 'ts' || hrp === 'rs');
    assert(version === 0);
    assert(hash.length === 20 || hash.length === 32);

    this.type = keyTypes.ADDRESS;
    this.version = version;
    this.address = hash;
    this.value = value;
    this.sponsor = sponsor;

    return this;
  }

  getJSON() {
    return {
      type: keyTypesByVal[this.type] || 'UNKNOWN',
      n: this.n.length > 0
        ? this.n.toString('hex')
        : undefined,
      e: this.e.length > 0
        ? this.e.toString('hex')
        : undefined,
      C1: this.C1.length > 0
        ? this.C1.toString('hex')
        : undefined,
      point: this.point.length > 0
        ? this.point.toString('hex')
        : undefined,
      version: this.address.length > 0
        ? this.version
        : undefined,
      address: this.address.length > 0
        ? this.address.toString('hex')
        : undefined,
      value: this.value || undefined,
      sponsor: this.value
        ? this.sponsor
        : undefined,
      nonce: !this.isGoo() && !this.isAddress()
        ? this.nonce.toString('hex')
        : undefined
    };
  }

  fromJSON(json) {
    assert(json && typeof json === 'object');
    assert(typeof json.type === 'string');
    assert(keyTypes.hasOwnProperty(json.type));

    this.type = keyTypes[json.type];

    switch (this.type) {
      case keyTypes.RSA: {
        this.n = base16.decode(json.n);
        this.e = base16.decode(json.e);
        this.nonce = base16.decode(json.nonce, 32);
        break;
      }

      case keyTypes.GOO: {
        this.C1 = base16.decode(json.C1);
        break;
      }

      case keyTypes.P256: {
        this.point = base16.decode(json.point, 33);
        this.nonce = base16.decode(json.nonce, 32);
        break;
      }

      case keyTypes.ED25519: {
        this.point = base16.decode(json.point, 32);
        this.nonce = base16.decode(json.nonce, 32);
        break;
      }

      case keyTypes.ADDRESS: {
        assert((json.version & 0xff) === json.version);
        assert(Number.isSafeInteger(json.value) && json.value >= 0);
        assert(typeof json.sponsor === 'boolean');
        this.version = json.version;
        this.address = base16.decode(json.address);
        this.value = json.value;
        this.sponsor = json.sponsor;
        break;
      }

      default: {
        throw new Error('Unknown key type.');
      }
    }

    return this;
  }

  static fromAddress(addr, value, sponsor) {
    return new this().fromAddress(addr, value, sponsor);
  }
}

/*
 * Static
 */

AirdropKey.keyTypes = keyTypes;
AirdropKey.keyTypesByVal = keyTypesByVal;

/*
 * Expose
 */

module.exports = AirdropKey;