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,
}