Initial commit
This commit is contained in:
commit
14e656379c
105
namekrs.py
Normal file
105
namekrs.py
Normal 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
156
timestamper.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user