Initial commit

This commit is contained in:
Piotr Dobrowolski 2018-06-17 11:06:55 +02:00
commit 14e656379c
2 changed files with 261 additions and 0 deletions

105
namekrs.py Normal file
View File

@ -0,0 +1,105 @@
import datetime
import json
import argparse
import logging
import requests
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
from timestamper import rpcurl_from_config
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s')
class NamecoinStore(object):
identity = None
def __init__(self, url='http://127.0.0.1:8336', identity=None):
self.client = AuthServiceProxy(url)
self.identity = identity
def set(self, key, value):
name = self.identity.format(key)
data = json.dumps(value)
reg_txid, nonce = self.client.name_new(name)
logging.debug('reg_txid, nonce: %r %r', reg_txid, nonce)
txid = self.client.name_firstupdate(name, nonce, reg_txid, data)
logging.debug('txid: %r', txid)
return txid
def get(self, key):
try:
hist = self.client.name_history(self.identity.format(key))
except JSONRPCException as exc:
# FIXME maybe?
if str(exc).split(': ')[0] == '-4':
logging.exception('No record found')
return None
raise
last_tx = self.client.gettransaction(hist[-1]['txid'])
return {
'history': hist,
'last_tx': last_tx
}
def bind_parser(p):
def deco(func):
p.set_defaults(func=func)
return func
return deco
parser = argparse.ArgumentParser()
parser.add_argument('--rpc', help='namecoind RPC URL (defaults to data obtained from ~/.namecoin.conf)')
sub = parser.add_subparsers()
verify_parser = sub.add_parser('verify', help='lookup KRS database')
verify_parser.add_argument('id', help='lookup ID')
@bind_parser(verify_parser)
def verify(args, store):
print(store.get(args.id))
publish_parser = sub.add_parser('publish', help='publish mojepanstwo.pl data')
publish_parser.add_argument('--limit', type=int, help='limit number of records', default=20)
publish_parser.add_argument('--page', type=int, help='start with specified page', default=1)
publish_parser.add_argument('-n', '--dry-run', action='store_true', help='only parse data')
@bind_parser(publish_parser)
def publish(args, store):
output = {}
data = requests.get('https://api-v3.mojepanstwo.pl/dane/krs_podmioty.json', {
'limit': args.limit,
'page': args.page
}).json()
logging.info('Total objects: %d, selected: %d', data['Count'], len(data['Dataobject']))
for obj in data['Dataobject']:
if obj['data']['krs_podmioty.nip'] == '0':
logging.info('Ignoring %s (no NIP/VATID)', obj['mp_url'])
continue
k = obj['data']['krs_podmioty.nip']
output[k] = obj['mp_url']
logging.info('%s => %r', k, output[k])
if args.dry_run:
logging.info('Dry run, not publishing')
return
for k, v in output.items():
logging.info('Publishing %s => %r', k, v)
store.set(k, v)
def main():
args = parser.parse_args()
store = NamecoinStore(
args.rpc or rpcurl_from_config('namecoin', ''),
'krs-test/{}')
args.func(args, store)
if __name__ == "__main__":
main()

156
timestamper.py Normal file
View File

@ -0,0 +1,156 @@
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
from datetime import datetime
import json
import hashlib
import sys
import platform
import os
class Timestamper(object):
"""Basic timestamping interface"""
def verify(self, digest):
"""Checks if digest (hash) is present in selected blockchain and returns
its timestamp and (unique) transaction hash/id in which it has been
announced. Returns None if it has not been found."""
raise NotImplemented
def publish(self, digest):
"""Publishes digest onto selected blockchain and returns transaction
hash/id (which should match with one reported by verify call)"""
raise NotImplemented
def hash_file(self, fd):
"""Returns sha256 hash of provided open fd."""
filehash = hashlib.sha256()
for chunk in iter(lambda: fd.read(8192), b""):
filehash.update(chunk)
return filehash.hexdigest()
def verify_file(self, fd):
"""Verifies file from open fd using sha256."""
return self.verify(self.hash_file(fd))
def publish_file(self, fd):
"""Publishes file from open fd using sha256."""
return self.publish(self.hash_file(fd))
class NamecoinTimestamper(Timestamper):
"""Simple Namecoin proof-of-existence timestamper implementation based on
poe/ identity prefix.
"""
IDENTITY = 'poe/{}'
def __init__(self, url='http://127.0.0.1:8336'):
self.client = AuthServiceProxy(url)
def verify(self, digest):
"""Namecoin poe/ verification implementation"""
try:
hist = self.client.name_history(self.IDENTITY.format(digest))
except JSONRPCException as exc:
# FIXME maybe?
if str(exc).split(': ')[0] == '-4':
return None
raise
txid = hist[0]['txid']
tx = self.client.gettransaction(txid)
return {
'txid': txid,
'timestamp': datetime.utcfromtimestamp(tx['time']),
}
def publish(self, digest):
"""Namecoin poe/ publishing implementation"""
name = self.IDENTITY.format(digest)
reg_txid, nonce = self.client.name_new(name)
txid = self.client.name_firstupdate(name, nonce, reg_txid, json.dumps({
'ver': 0,
}))
return txid
def parse_bitcoin_conf(fd):
"""Returns dict from bitcoin.conf-like configuration file from open fd"""
conf = {}
for l in fd:
l = l.strip()
if not l.startswith('#') and '=' in l:
key, value = l.split('=', 1)
conf[key] = value
return conf
def coin_config_path(coin):
"""Returns bitcoin.conf-like configuration path for provided coin"""
paths = {
# FIXME use proper AppData path
'Windows': '~\AppData\Roaming\{0}\{0}.conf',
'Darwin': '~/Library/Application Support/{0}/{0}.conf',
# Fallback path (Linux, FreeBSD...)
None: '~/.{0}/{0}.conf',
}
path = paths.get(platform.system(), paths[None])
return os.path.expanduser(path.format(coin))
def rpcurl_from_config(coin, default=None, config_path=None):
"""Returns RPC URL loaded from bitcoin.conf-like configuration of desired
currency"""
config_path = config_path or coin_config_path(coin)
cookie_path = os.path.join(os.path.dirname(config_path), '.cookie')
credentials = ''
try:
with open(config_path) as fd:
conf = parse_bitcoin_conf(fd)
if 'rpcpassword' in conf:
# Password authentication
credentials = '{rpcuser}:{rpcpassword}'.format(**conf)
elif os.path.exists(cookie_path):
# Cookie authentication
with open(cookie_path) as cfd:
credentials = cfd.read().decode('utf-8').strip() \
.replace('/', '%2F')
else:
return default
return 'http://{0}@127.0.0.1:{1}/' \
.format(credentials, conf.get('rpcport', 8336))
except:
return default
def main():
ts = NamecoinTimestamper(rpcurl_from_config('namecoin', 'http://127.0.0.1:8336/'))
for f in sys.argv[1:]:
with open(f) as fd:
timestamp = ts.verify_file(fd)
if timestamp:
print('File {} found at: {}'.format(f, timestamp['timestamp']))
else:
print('File {} not found'.format(f))
if __name__ == "__main__":
main()