Add ONS Lookup capability

This commit is contained in:
Johnathan Ross 2021-06-11 19:18:27 +10:00
parent 9dbbd57255
commit d1c2d23270
2 changed files with 206 additions and 22 deletions

View File

@ -9,6 +9,7 @@ import statistics
import string
import requests
import time
import base64
from base64 import b32encode, b16decode
from werkzeug.routing import BaseConverter
from pygments import highlight
@ -17,7 +18,9 @@ from pygments.formatters import HtmlFormatter
import subprocess
import qrcode
from io import BytesIO
import pysodium
import nacl.encoding
import nacl.hash
import config
import local_config
from lmq import FutureJSON, lmq_connection
@ -414,6 +417,109 @@ def block_with_txs_req(lmq, oxend, hash_or_height, **kwargs):
return FutureJSON(lmq, oxend, 'rpc.get_block', cache_key='single', args=args, **kwargs)
def ons_info(lmq, oxend, name,ons_type,**kwargs):
if ons_type == 2:
name=name+'.loki'
name_hash = nacl.hash.blake2b(name.encode(), encoder = nacl.encoding.Base64Encoder)
return FutureJSON(lmq, oxend, 'rpc.ons_names_to_owners', args={
"entries": [{'name_hash':name_hash.decode('ascii'),'types':[ons_type]}]})
@app.route('/ons/<string:name>')
@app.route('/ons/<string:name>/<int:more_details>')
def show_ons(name, more_details=False):
name = name.lower()
lmq, oxend = lmq_connection()
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
if len(name) > 64 or not all(c.isalnum() or c in '_-' for c in name):
return flask.render_template('not_found.html',
info=info.get(),
type='bad_search',
id=name,
)
ons_types = {'session':0,'wallet':1,'lokinet':2}
ons_data = {'name':name}
SESSION_ENCRYPTED_LENGTH = 146 # If the encrypted value is not of expected character
WALLET_ENCRYPTED_LENGTH = 210 # length it is of HF15 and before.
LOKINET_ENCRYPTED_LENGTH = 144 # The user must update their session mapping.
for ons_type in ons_types:
onsinfo = ons_info(lmq, oxend, name, ons_types[ons_type]).get()
if 'entries' in onsinfo:
onsinfo = onsinfo['entries'][0]
ons_data[ons_type] = onsinfo
if len(onsinfo['encrypted_value']) in [SESSION_ENCRYPTED_LENGTH, WALLET_ENCRYPTED_LENGTH, LOKINET_ENCRYPTED_LENGTH]:
# RPC returns encrypted_value as ciphertext and nonce concatenated.
# The nonce is the last 48 characters of the encrypted value and the remainder of characters is the encrypted_value.
nonce_received = onsinfo['encrypted_value'][-48:]
nonce = bytes.fromhex(nonce_received)
# The ciphertext is the encrypted_value with the nonce taken away.
ciphertext = bytes.fromhex(onsinfo['encrypted_value'][:-48])
# If ons type is lokinet we need to add .loki to the name before hashing.
if ons_types[ons_type] == 2:
name+='.loki'
# Calculate the blake2b hash of the lower-case full name
name_hash = nacl.hash.blake2b(name.encode(),encoder = nacl.encoding.RawEncoder)
# Decryption key: another blake2b hash, but this time a keyed blake2b hash where the first hash is the key
decryption_key = nacl.hash.blake2b(name.encode(), key=name_hash, encoder = nacl.encoding.RawEncoder)
# XChaCha20+Poly1305 decryption
val = pysodium.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext=ciphertext, ad=b'', nonce=nonce, key=decryption_key)
# lokinet check
print(ons_types[ons_type])
if ons_types[ons_type] == 2:
# val will currently be the raw lokinet ed25519 pubkey (32 bytes). We can convert it to the more
# common lokinet address (which is the same value but encoded in z-base-32) and convert the bytes to
# a string:
val = b32encode(val).decode()
# Python's regular base32 uses a different alphabet, so translate from base32 to z-base-32:
val = val.translate(str.maketrans("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
"ybndrfg8ejkmcpqxot1uwisza345h769"))
# Base32 is also padded with '=', which isn't used in z-base-32:
val = val.rstrip('=')
# Finally slap ".loki" on the end:
val += ".loki"
ons_data[ons_type]['mapping'] = val
else:
ons_data[ons_type]['mapping'] = val.hex()
else:
# Encryption involves a much more expensive argon2-based calculation for HF15 registrations.
# Owners should be notified they should update to the new encryption format.
ons_data[ons_type] = ons_info(lmq, oxend, name,ons_types[ons_type]).get()['entries'][0]
ons_data[ons_type]['mapping'] = 'Owner needs to update their ID for mapping info.'
else:
# If returned with no data from the RPC
ons_data[ons_type] = True
if more_details:
formatter = HtmlFormatter(cssclass="syntax-highlight", style="paraiso-dark")
more_details = {
'details_css': formatter.get_style_defs('.syntax-highlight'),
'details_html': highlight(json.dumps(ons_data, indent="\t"), JsonLexer(), formatter),
}
else:
more_details = {}
return flask.render_template('ons.html',
info=info.get(),
ons=ons_data,
**more_details,
)
@app.route('/sn/<hex64:pubkey>')
@app.route('/sn/<hex64:pubkey>/<int:more_details>')
@ -687,12 +793,12 @@ def show_quorums():
base32z_dict = 'ybndrfg8ejkmcpqxot1uwisza345h769'
base32z_map = {base32z_dict[i]: i for i in range(len(base32z_dict))}
@app.route('/search')
def search():
lmq, oxend = lmq_connection()
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
val = (flask.request.args.get('value') or '').strip()
if val and len(val) < 10 and val.isdigit(): # Block height
return flask.redirect(flask.url_for('show_block', height=val), code=301)
@ -704,34 +810,43 @@ def search():
v >>= 4
val = "{:64x}".format(v)
if len(val) == 64:
# Initiate all the lookups at once, then redirect to whichever one responds affirmatively
snreq = sn_req(lmq, oxend, val)
blreq = block_header_req(lmq, oxend, val, fail_okay=True)
txreq = tx_req(lmq, oxend, [val])
sn = snreq.get()
if sn and 'service_node_states' in sn and sn['service_node_states']:
return flask.redirect(flask.url_for('show_sn', pubkey=val), code=301)
bl = blreq.get()
if bl and 'block_header' in bl and bl['block_header']:
return flask.redirect(flask.url_for('show_block', hash=val), code=301)
tx = txreq.get()
if tx and 'txs' in tx and tx['txs']:
return flask.redirect(flask.url_for('show_tx', txid=val), code=301)
# ONS can be of length 64 however with txids, and sn pubkey's being of length 64
# I have removed it from the possible searches.
if len(val) < 64 and all(c.isalnum() or c in '_-' for c in val):
return flask.redirect(flask.url_for('show_ons', name=val), code=301)
elif not val or len(val) != 64 or any(c not in string.hexdigits for c in val):
return flask.render_template('not_found.html',
info=info.get(),
type='bad_search',
id=val,
)
# Initiate all the lookups at once, then redirect to whichever one responds affirmatively
snreq = sn_req(lmq, oxend, val)
blreq = block_header_req(lmq, oxend, val, fail_okay=True)
txreq = tx_req(lmq, oxend, [val])
sn = snreq.get()
if sn and 'service_node_states' in sn and sn['service_node_states']:
return flask.redirect(flask.url_for('show_sn', pubkey=val), code=301)
bl = blreq.get()
if bl and 'block_header' in bl and bl['block_header']:
return flask.redirect(flask.url_for('show_block', hash=val), code=301)
tx = txreq.get()
if tx and 'txs' in tx and tx['txs']:
return flask.redirect(flask.url_for('show_tx', txid=val), code=301)
info=info.get(),
type='bad_search',
id=val,
)
return flask.render_template('not_found.html',
info=info.get(),
type='search',
type='bad_search',
id=val,
)
@app.route('/api/networkinfo')
def api_networkinfo():
lmq, oxend = lmq_connection()

69
templates/ons.html Normal file
View File

@ -0,0 +1,69 @@
{% extends "_basic.html" %}
{% block content %}
<div class="sn-details Wrapper">
<div class="sn-details-main">
<div class="details">
<h2>Oxen Name Server Lookup <b>"{{ons.name}}"</b></h2>
<div class="TitleUnderliner"></div>
<h2><label>Session</label> </h2>
<div class="TitleUnderliner"></div>
{%if ons.session == True%}
<h4 style="margin:5px"><label>Available:</label> Yes</h4>
{%else%}
<h4 style="margin:5px"><label>Available:</label> No</h4>
<h4 style="margin:5px"><label>Owner:</label> {{ons.session.owner}}</h4>
<h4 style="margin:5px"><label>TX Hash:</label> <a href="/tx/{{ons.session.txid}}">{{ons.session.txid}}</a></h4>
<h4 style="margin:5px"><label>Update Height:</label> <a href="/block/{{ons.session.update_height}}">{{ons.session.update_height}}</a></h4>
<h4 style="margin:5px"><label>Name Hash:</label> {{ons.session.name_hash}}</h4>
<h4 style="margin:5px"><label>Mapping:</label> {{ons.session.mapping}}</h4>
{%endif%}
<h2><label>Wallet</label></h2>
<div class="TitleUnderliner"></div>
{%if ons.wallet == True%}
{%if ons.session == True%}
<h4 style="margin:5px"><label>Available:</label> Yes</h4>
{%else%}
<h4 style="margin:5px"><label>Available:</label> Only available for above Session ID Owner</h4>
{%endif%}
{%else%}
<h4 style="margin:5px"><label>Available:</label> No</h4>
<h4 style="margin:5px"><label>Owner:</label> {{ons.wallet.owner}}</h4>
<h4 style="margin:5px"><label>TX Hash:</label> <a href="/tx/{{ons.wallet.txid}}">{{ons.wallet.txid}}</a></h4>
<h4 style="margin:5px"><label>Update Height:</label> <a href="/block/{{ons.wallet.update_height}}">{{ons.wallet.update_height}}</a></h4>
<h4 style="margin:5px"><label>Name Hash:</label> {{ons.wallet.name_hash}}</h4>
<h4 style="margin:5px"><label>Mapping:</label> {{ons.wallet.mapping}}</h4>
{%endif%}
<h2><label>Lokinet</label></h2>
<div class="TitleUnderliner"></div>
{%if ons.lokinet == True%}
<h4 style="margin:5px"><label>Available:</label> Yes</h4>
{%else%}
<h4 style="margin:5px"><label>Available:</label> No</h4>
<h4 style="margin:5px"><label>Owner:</label> {{ons.lokinet.owner}}</h4>
<h4 style="margin:5px"><label>TX Hash:</label> <a href="/tx/{{ons.session.txid}}">{{ons.lokinet.txid}}</a></h4>
<h4 style="margin:5px"><label>Update Height:</label> <a href="/block/{{ons.lokinet.update_height}}">{{ons.lokinet.update_height}}</a></h4>
<h4 style="margin:5px"><label>Expiration Height:</label> {{ons.lokinet.expiration_height}}</h4>
<h4 style="margin:5px"><label>Name Hash:</label> {{ons.lokinet.name_hash}}</h4>
<h4 style="margin:5px"><label>Mapping:</label> {{ons.lokinet.mapping}}</h4>
{%endif%}
</div>
</div>
{%if details_html%}
<style type="text/css">
{{details_css | safe}}
</style>
<div class="TitleDivider" id="more_details"></div>
{{details_html | safe}}
{%else%}
<h5>
<a href="/ons/{{ons.name}}/1#more_details">Show raw details</a>
</h5>
{%endif%}
</div>
{% endblock %}