Source: key.js

import { Buffer } from 'buffer'
import { KeyPair } from 'bsv'
import cbor from 'borc'
import crypto from 'isomorphic-webcrypto'
import Recipient from './recipient.js'
import algs from './algs/index.js'

/**
 * Key class
 * 
 * Univrse Keys mirror the API of, and are compatible with, JSON JWK keys.
 */
class Key {
  /**
   * Instantiates a new Key instance of the given type and parameters.
   * 
   * @param {String} type 
   * @param {Object} params
   * @constructor
   */
  constructor(type, params) {
    this.type = type
    this.params = params
  }

  /**
   * Decodes the given CBOR encoded key into a Key instance.
   * 
   * @param {Buffer} buf
   * @constructor
   */
  static decode(buf) {
    const { kty, ...params } = cbor.decode(buf)
    return new this(kty, params)
  }

  /**
   * Securely generates a new key of the given type and paramter.
   * 
   * ## Supported key types
   * 
   * Eliptic curve keys:
   * 
   * * `type` must be 'EC'
   * * `param` must be supported curve type, from: `secp256k1`
   * 
   * Octet byte sequence:
   * 
   * * `type` must be 'oct'
   * * `param` must be supported bit length, from: `128`, `256` or `512`
   * 
   * @param {String} type 
   * @param {String|Number} param
   * @constructor
   */
  static async generate(type, param) {
    if (type.toLowerCase() === 'ec' && param === 'secp256k1') {
      // generate secp256k1 key using bsv
      const key = KeyPair.fromRandom()
      return new this('EC', {
        crv: 'secp256k1',
        d: Buffer.from(key.privKey.bn.toArray('big', 32)),
        x: Buffer.from(key.pubKey.point.x.toArray('big', 32)),
        y: Buffer.from(key.pubKey.point.y.toArray('big', 32))
      })
    } else if (type.toLowerCase() == 'oct' && [128, 256, 512].includes(param)) {
      // use generateKey() as getRandomValues() is not guaranteed to
      // run in a secure context
      const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: param }, true, ['encrypt', 'decrypt'])
      const buf = await crypto.subtle.exportKey('raw', key)
      return new this('oct', { k: Buffer.from(buf) })
    } else {
      throw 'Invalid key type or param'
    }
  }

  /**
   * Encrypts the key using the given encryption key, and returns the encrypted
   * key wrapped in a Recipient instance.
   * 
   * A headers object must be given including at least the encryption `alg`
   * value.
   * 
   * A third argument of options can be given for the relevant encryption
   * algorithm.
   * 
   * @param {Key} key encryption key
   * @param {Object} headers
   * @param {Object} opts
   * @returns {Recipient}
   */
  async encrypt(key, headers = {}, opts = {}) {
    const { alg } = headers
    const encOpts = {
      ...opts,
      ...['iv'].reduce((o, k) => { o[k] = headers[k]; return o }, {})
    }

    const { encrypted, ...newHeaders } = await algs.encrypt(alg, this.toBuffer(), key, encOpts)
    return Recipient.wrap(encrypted, { ...headers, ...newHeaders })
  }

  /**
   * Returns the Key as a CBOR encoded Buffer.
   * 
   * @returns {Buffer}
   */
  toBuffer() {
    return cbor.encode(this.toObject())
  }

  /**
   * Returns the Key as a JWK compatible object.
   * 
   * @returns {Object}
   */
  toObject() {
    return {
      ...this.params,
      kty: this.type
    }
  }

  /**
   * Returns a public key from the current key, which can be safely shared with
   * other parties.
   * 
   * Only for use with `EC` key types.
   * 
   * @returns {Key}
   */
  toPublic() {
    if (this.type === 'EC') {
      const { crv, x, y } = this.params
      return new this.constructor('EC', { crv, x, y })
    } else {
      throw `Cannot convert key type '${ this.type }' to public key`
    }
  }
}

export default Key