Source: lib/lns.js

const blake2bUtils = require('./blake2b.js')
const lib = require('./lib.js')
const _sodium = require('libsodium-wrappers')

/**
 * Loki Name Service Utilities
 * @module lns
 * @exports {object} exports.getNameSafe
 * @exports {object} exports.getNameFast
 * @author Ryan Tharp
 * @license ISC
 */

async function getSodium() {
  await _sodium.ready
  return _sodium
}

// good candiate of a primitive
/**
 * Lookup LNS name against three snode and get agreement
 * @param {String} lnsName what name to look up
 * @return {Promise<String>} pubkey (SessionID) it points to
 */
async function getNameSafe(lnsName) {
  const requests = [...Array(3).keys()]
  const list = await Promise.all(requests.map(idx => getNameFast(lnsName)))
  if (list.every(v => v === list[0])) {
    return list[0]
  }
}

/**
 * Lookup LNS name against one snode
 * @param {String} lnsName what name to look up
 * @return {Promise<String>} pubkey (SessionID) it points to
 */
async function getNameFast(lnsName) {
  const nameBuf = Buffer.from(lnsName)
  const uArr = blake2bUtils.blake2b(nameBuf, undefined, 32)
  const hash64 = Buffer.from(uArr).toString('base64')
  const snodeUrl = await lib.getRandomSnode()
  console.log('asking', snodeUrl, 'about', lnsName)
  const res = await lib.jsonrpc(snodeUrl, 'get_lns_mapping', {
    name_hash: hash64
  })
  console.log('decoding', lnsName, 'response from', snodeUrl)
  // FIXME: handle network failures better...
  /*
[
  {
    backup_owner: '',
    encrypted_value: '61b77686bbb8bed9074386598d6e18e0a9cca8098c1dd4b643a643cabfbf133ef9f0cdd01adf252bf18eba378f6de003d8',
    entry_index: 0,
    name_hash: 'CFm/zhpmu+SVenlQLPED6xzTja5L3ncc1KLw/+ewrUk=',
    owner: 'LBtSHQi85YEGRj1y87dHYRfEQPFwHGCRv3ceqesuSo3PKXW1LhHcFLCUSMHJ9hdRejcJRiQHX1WzdWsdBRRGJzA8QBMY27J',
    prev_txid: '',
    register_height: 497549,
    txid: 'bd1b9ca44ae541b277cdf62811012823911365c28fe978f38c39115a2b747e5a',
    type: 0,
    update_height: 497549
  }
]
  */
  //console.log('res', res.result.entries)
  if (!res || !res.result || !res.result.entries) {
    console.warn('lib:::lns::getName - Error retrieving', res)
    return
  }
  if (res.result.entries.length !== 1) {
    console.warn('lib:::lns::getName - Too many entries', res.result.entries)
    return
  }
  const obj = res.result.entries[0]
  const sodium = await getSodium()
  console.log('obj.encrypted_value.length', obj.encrypted_value.length)

  // old 7.x heavy encryption (xsalsa20-poly1305/argon2)

  // salt is all 0s (16 bytes)
  const salt = new Uint8Array(sodium.crypto_pwhash_SALTBYTES)
  //console.log(lnsName, 'salt', salt)

  // these are different
  //console.log(lnsName, 'encrypted_value', obj.encrypted_value)

  //const cipherTextBuf = Buffer.from(obj.encrypted_value, 'hex')
  // make sure it's uint8array
  //const cipherText = new Uint8Array(cipherTextBuf.buffer, cipherTextBuf.byteOffset, cipherTextBuf.byteLength);
  const cipherText = sodium.from_hex(obj.encrypted_value)
  //console.log(lnsName, 'cipherText', cipherText) // 49 bytes...
  // try to decrypt
  try {
    const key = sodium.crypto_pwhash(
      sodium.crypto_secretbox_KEYBYTES,
      lnsName, // key
      salt,
      sodium.crypto_pwhash_OPSLIMIT_MODERATE,
      sodium.crypto_pwhash_MEMLIMIT_MODERATE,
      sodium.crypto_pwhash_ALG_ARGON2ID13
    )

    // nonce should be all 0s (24 bytes)
    const nonce = new Uint8Array(sodium.crypto_secretbox_NONCEBYTES)
    //console.log(lnsName, 'nonce', nonce)

    /*
    const nonce = nonceAndCipherText.slice(0, sodium.crypto_secretbox_NONCEBYTES)
    const cipherText = nonceAndCipherText.slice(sodium.crypto_secretbox_NONCEBYTES)
    */
    // this is uint8
    const decryptedVal = sodium.crypto_secretbox_open_easy(cipherText, nonce, key)
    // convert back to hex (it includes the 05 prefix since that's usually put into the LNS record)
    console.log('Decoded', lnsName, 'from', snodeUrl)
    return Buffer.from(decryptedVal).toString('hex')
    // 053b6b764388cd6c4d38ae0b3e7492a8ecf0076e270c013bb5693d973045f45254 will be a common response
    // means they haven't set their session id yet...
  } catch (err) {
    console.error('lib:::lns::getName - decryption err', err)
  }
}

module.exports = {
  getNameSafe: getNameSafe,
  getNameFast: getNameFast,
}