Skip to content
This repository has been archived by the owner on May 13, 2022. It is now read-only.

initial blacklist edits #328

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/bitcoin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
from bitcoin.transaction import *
from bitcoin.deterministic import *
from bitcoin.bci import *
from bitcoin.podle import *

100 changes: 100 additions & 0 deletions lib/bitcoin/podle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#Proof Of Discrete Logarithm Equivalence
#For algorithm steps, see https://gist.github.com/AdamISZ/9cbba5e9408d23813ca8
import secp256k1
import os
from py2specials import *
from py3specials import *

N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
dummy_pub = secp256k1.PublicKey()

'''NUMS - an alternate basepoint on the secp256k1 curve
For background (taken from https://github.com/AdamISZ/ConfidentialTransactionsDoc/blob/master/essayonCT.pdf)
>>> import bitcoin as btc
>>> import os
>>>H_x = int(sha256(btc.encode_pubkey(btc.G,'hex').decode('hex')).hexdigest(),16)
>>> H_x
36444060476547731421425013472121489344383018981262552973668657287772036414144L
>>> H_y = pow(int(H_x*H_x*H_x + 7), int((btc.P+1)//4), int(btc.P))
>>> H_y
93254584761608041185240733468443117438813272608612929589951789286136240436011L
>>> H = (H_x, H_y)
'''
J_raw = '0350929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0'
J = secp256k1.PublicKey(safe_from_hex(J_raw), raw=True)

def getP2(priv):
priv_raw = priv.private_key
return J.tweak_mul(priv_raw)

def generate_podle(priv):
'''Given a raw private key, in hex format,
construct a commitment sha256(P2), which is
the hash of the value x*J, where x is the private
key as a raw scalar, and J is a NUMS alternative
basepoint on the Elliptic Curve. Also construct
a signature (s,e) of Schnorr type, which will serve
as a zero knowledge proof that the private key of P2
is the same as the private key of P (=x*G).
Signature is constructed as:
s = k + x*e
where k is a standard 32 byte nonce and:
e = sha256(k*G || k*J || P || P2)
'''
if len(priv)==66 and priv[-2:]=='01':
priv = priv[:-2]
priv = secp256k1.PrivateKey(safe_from_hex(priv))
P = priv.pubkey
k = os.urandom(32)
KG = secp256k1.PrivateKey(k).pubkey
KJ = J.tweak_mul(k)
P2 = getP2(priv)
commitment = hashlib.sha256(P2.serialize()).digest()
e = hashlib.sha256(''.join([x.serialize() for x in [KG, KJ, P, P2]])).digest()
k_int = decode(k, 256)
priv_int = decode(priv.private_key, 256)
e_int = decode(e, 256)
sig_int = (k_int + priv_int*e_int) % N
sig = encode(sig_int, 256, minlen=32)
P2hex, chex, shex, ehex = [safe_hexlify(x) for x in [P2.serialize(),commitment, sig, e]]
return {'P2':P2hex, 'commit': chex, 'sig': shex, 'e':ehex}

def verify_podle(Pser, P2ser, sig, e, commitment):
Pser, P2ser, sig, e, commitment = [safe_from_hex(x) for x in [Pser, P2ser, sig, e, commitment]]
#check 1: Hash(P2ser) =?= commitment
if not hashlib.sha256(P2ser).digest() == commitment:
return False
sig_priv = secp256k1.PrivateKey(sig,raw=True)
sG = sig_priv.pubkey
sJ = J.tweak_mul(sig)
P = secp256k1.PublicKey(Pser, raw=True)
P2 = secp256k1.PublicKey(P2ser, raw=True)
e_int = decode(e, 256)
minus_e = encode(-e_int % N, 256, minlen=32)
minus_e_P = P.tweak_mul(minus_e)
minus_e_P2 = P2.tweak_mul(minus_e)
KG = dummy_pub.combine([sG.public_key, minus_e_P.public_key])
KJ = dummy_pub.combine([sJ.public_key, minus_e_P2.public_key])
KGser = secp256k1.PublicKey(KG).serialize()
KJser = secp256k1.PublicKey(KJ).serialize()
#check 2: e =?= H(K_G || K_J || P || P2)
e_check = hashlib.sha256(KGser + KJser + Pser + P2ser).digest()
if not e_check == e:
return False
return True

if __name__ == '__main__':

for i in range(10000):
priv = os.urandom(32)
Priv = secp256k1.PrivateKey(priv)
Pser = safe_hexlify(Priv.pubkey.serialize())
podle_sig = generate_podle(safe_hexlify(priv))
P2ser, s, e, commitment = (podle_sig['P2'], podle_sig['sig'],
podle_sig['e'], podle_sig['commit'])
if not verify_podle(Pser, P2ser, s, e, commitment):
print 'failed to verify'




2 changes: 1 addition & 1 deletion lib/bitcoin/secp256k1.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import hashlib

import binascii
from _libsecp256k1 import ffi, lib
import _noncefunc

Expand Down
30 changes: 29 additions & 1 deletion lib/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
config_location = 'joinmarket.cfg'
# FIXME: Add rpc_* options here in the future!
required_options = {'BLOCKCHAIN':['blockchain_source', 'network'],
'MESSAGING':['host','channel','port']}
'MESSAGING':['host','channel','port'],
'LIMITS':['taker_utxo_retries']}

defaultconfig =\
"""
Expand Down Expand Up @@ -188,6 +189,33 @@ def debug_dump_object(obj, skip_fields=[]):
else:
debug(str(v))

def check_utxo_blacklist(utxo):
#write/read the file here; could be inferior
#performance-wise; should cache. TODO
#there also needs to be formatting error checking here,
#but not a high priority TODO
blacklist = {}
if os.path.isfile("logs/blacklist"):
with open("logs/blacklist","rb") as f:
blacklist_lines = f.readlines()
else:
blacklist_lines = []
for bl in blacklist_lines:
ut, ct = bl.split(',')
ut = ut.strip()
ct = int(ct.strip())
blacklist[ut]=ct
if utxo in blacklist.keys():
blacklist[utxo] += 1
if blacklist[utxo] >= config.getint("LIMITS","taker_utxo_retries"):
return False
else:
blacklist[utxo]=1
with open("logs/blacklist","wb") as f:
for k,v in blacklist.iteritems():
f.write(k+' , '+str(v)+'\n')
return True

def select_gradual(unspent, value):
'''
UTXO selection algorithm for gradual dust reduction
Expand Down
32 changes: 27 additions & 5 deletions lib/irc.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,18 @@ def request_orderbook(self):
self.__pubmsg(COMMAND_PREFIX + 'orderbook')

#Taker callbacks
def fill_orders(self, nickoid_dict, cj_amount, taker_pubkey):
def fill_orders(self, nickoid_dict, cj_amount, taker_pubkey, commitment=None):
for c, oid in nickoid_dict.iteritems():
msg = str(oid) + ' ' + str(cj_amount) + ' ' + taker_pubkey
if commitment:
msg += ' '+commitment
self.__privmsg(c, 'fill', msg)

def send_auth(self, nick, pubkey, sig):
message = pubkey + ' ' + sig
def send_auth(self, nick, pubkey, sig, utxo=None, P2=None, s=None, e=None):
fields = [pubkey, sig]
if all([utxo, P2, s, e]):
fields += [utxo, P2, s, e]
message = ' '.join(fields)
self.__privmsg(nick, 'auth', message)

def send_tx(self, nick_list, txhex):
Expand Down Expand Up @@ -240,18 +245,35 @@ def __on_privmsg(self, nick, message):
oid = int(chunks[1])
amount = int(chunks[2])
taker_pk = chunks[3]
if len(chunks)>4:
commit = chunks[4]
else:
commit = None
except (ValueError, IndexError) as e:
self.send_error(nick, str(e))
if self.on_order_fill:
self.on_order_fill(nick, oid, amount, taker_pk)
self.on_order_fill(nick, oid, amount,
taker_pk, commit)
elif chunks[0] == 'auth':
try:
i_utxo_pubkey = chunks[1]
btc_sig = chunks[2]
if len(chunks)>3:
i_utxo = chunks[3]
p2 = chunks[4]
sig = chunks[5]
e_val = chunks[6]
else:
i_utxo = None
p2 = None
sig = None
e_val = None
except (ValueError, IndexError) as e:
self.send_error(nick, str(e))
if self.on_seen_auth:
self.on_seen_auth(nick, i_utxo_pubkey, btc_sig)
self.on_seen_auth(nick, i_utxo_pubkey,
btc_sig, i_utxo,
p2, sig, e_val)
elif chunks[0] == 'tx':
b64tx = chunks[1]
try:
Expand Down
44 changes: 34 additions & 10 deletions lib/maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,32 @@ def __init__(self, maker, nick, oid, amount, taker_pk):
# orders to find out which addresses you use
self.maker.msgchan.send_pubkey(nick, self.kp.hex_pk())

def auth_counterparty(self, nick, i_utxo_pubkey, btc_sig):
def auth_counterparty(self, nick, i_utxo_pubkey, btc_sig, i_utxo, p2, s, e):
self.i_utxo_pubkey = i_utxo_pubkey

#check the btc signature of the encryption pubkey
if not btc.ecdsa_verify(self.taker_pk, btc_sig, binascii.unhexlify(self.i_utxo_pubkey)):
print 'signature didnt match pubkey and message'
common.debug("signature from nick: " + nick + "didnt match pubkey and message")
return False

if all([i_utxo, p2, s, e]):
#check the validity of the proof of discrete log equivalence
if not btc.verify_podle(self.i_utxo_pubkey, p2, s, e, self.maker.commit):
common.debug("PODLE verification failed; counterparty utxo is not verified.")
return False
#finally, check that the proffered utxo is real,
#and corresponds to the pubkey
res = common.bc_interface.query_utxo_set([i_utxo])
if len(res) != 1:
common.debug("Input utxo: "+str(i_utxo)+" from nick: "+nick+" is not valid.")
return False
real_utxo = res[0]
if real_utxo['address'] != btc.pubkey_to_address(i_utxo_pubkey, common.get_p2pk_vbyte()):
return False
#TODO: could add check for coin age and value here
#(need to edit query_utxo_set if we want coin age)

#authorisation of taker passed
#(but input utxo pubkey is checked in verify_unsigned_tx).
#Send auth request to taker
#TODO the next 2 lines are a little inefficient.
btc_key = self.maker.wallet.get_key_from_addr(self.cj_addr)
btc_pub = btc.privtopub(btc_key, True)
btc_sig = btc.ecdsa_sign(self.kp.hex_pk(), binascii.unhexlify(btc_key))
Expand Down Expand Up @@ -189,22 +205,30 @@ def get_crypto_box_from_nick(self, nick):
def on_orderbook_requested(self, nick):
self.msgchan.announce_orders(self.orderlist, nick)

def on_order_fill(self, nick, oid, amount, taker_pubkey):
def on_order_fill(self, nick, oid, amount, taker_pubkey, commit = None):
if nick in self.active_orders and self.active_orders[nick] != None:
self.active_orders[nick] = None
debug('had a partially filled order but starting over now')
if commit:
self.commit = commit
if not common.check_utxo_blacklist(self.commit):
common.debug("Taker utxo commitment is in blacklist, having been used "\
+ common.config.get("LIMITS","taker_utxo_retries")+ " times, rejecting.")
return

self.wallet_unspent_lock.acquire()
try:
self.active_orders[nick] = CoinJoinOrder(self, nick, oid, amount, taker_pubkey)
finally:
self.wallet_unspent_lock.release()

def on_seen_auth(self, nick, pubkey, sig):
def on_seen_auth(self, nick, pubkey, sig, i_utxo, p2, s, e_val):
if nick not in self.active_orders or self.active_orders[nick] == None:
self.msgchan.send_error(nick, 'No open order from this nick')
self.active_orders[nick].auth_counterparty(nick, pubkey, sig)
#TODO if auth_counterparty returns false, remove this order from active_orders
# and send an error
if not self.active_orders[nick].auth_counterparty(nick, pubkey,
sig, i_utxo, p2, s, e_val):
self.active_orders[nick]=None
self.msgchan.send_error(nick, "Authorisation failed.")

def on_seen_tx(self, nick, txhex):
if nick not in self.active_orders or self.active_orders[nick] == None:
Expand Down
4 changes: 2 additions & 2 deletions lib/message_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def register_taker_callbacks(self, on_error=None, on_pubkey=None, on_ioauth=None
self.on_pubkey = on_pubkey
self.on_ioauth = on_ioauth
self.on_sig = on_sig
def fill_orders(self, nickoid_dict, cj_amount, taker_pubkey): pass
def send_auth(self, nick, pubkey, sig): pass
def fill_orders(self, nickoid_dict, cj_amount, taker_pubkey, commitment=None): pass
def send_auth(self, nick, pubkey, sig, utxo=None, P2=None, s=None, e=None): pass
def send_tx(self, nick_list, txhex): pass
def push_tx(self, nick, txhex): pass

Expand Down
24 changes: 14 additions & 10 deletions lib/taker.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,27 @@ def __init__(self, msgchan, wallet, db, cj_amount, orders, input_utxos, my_cj_ad
#create DH keypair on the fly for this Tx object
self.kp = enc_wrapper.init_keypair()
self.crypto_boxes = {}
self.msgchan.fill_orders(self.active_orders, self.cj_amount, self.kp.hex_pk())
if not self.auth_addr:
self.auth_addr = self.input_utxos.itervalues().next()['address']
self.auth_utxo = self.input_utxos.iterkeys().next()
self.auth_priv = self.wallet.get_key_from_addr(self.auth_addr)
self.podle = btc.generate_podle(self.auth_priv)
debug("Generated PoDLE: "+pprint.pformat(self.podle))

self.msgchan.fill_orders(self.active_orders, self.cj_amount,
self.kp.hex_pk(), self.podle['commit'])

def start_encryption(self, nick, maker_pk):
if nick not in self.active_orders.keys():
debug("Counterparty not part of this transaction. Ignoring")
return
self.crypto_boxes[nick] = [maker_pk, enc_wrapper.as_init_encryption(\
self.kp, enc_wrapper.init_pubkey(maker_pk))]
#send authorisation request
if self.auth_addr:
my_btc_addr = self.auth_addr
else:
my_btc_addr = self.input_utxos.itervalues().next()['address']
my_btc_priv = self.wallet.get_key_from_addr(my_btc_addr)
my_btc_pub = btc.privtopub(my_btc_priv, True)
my_btc_sig = btc.ecdsa_sign(self.kp.hex_pk(), binascii.unhexlify(my_btc_priv))
self.msgchan.send_auth(nick, my_btc_pub, my_btc_sig)
my_btc_pub = btc.privtopub(self.auth_priv, True)
my_btc_sig = btc.ecdsa_sign(self.kp.hex_pk(), binascii.unhexlify(self.auth_priv))
self.msgchan.send_auth(nick, my_btc_pub, my_btc_sig,
str(self.auth_utxo), str(self.podle['P2']),
str(self.podle['sig']), str(self.podle['e']))

def auth_counterparty(self, nick, btc_sig, cj_pub):
'''Validate the counterpartys claim to own the btc
Expand Down
Loading