From 0719513f388538d758a3d26d488a076b5d204031 Mon Sep 17 00:00:00 2001 From: David Sterry Date: Fri, 10 Mar 2017 11:37:11 -0800 Subject: [PATCH 1/9] add api and blockexplorer config options, enable links to escrow addresses --- rein/cli.py | 16 ++++++++++++---- rein/html/job.html | 20 +++++++++++++++----- rein/lib/transaction.py | 10 ++++++---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/rein/cli.py b/rein/cli.py index c37c970..6491f0e 100644 --- a/rein/cli.py +++ b/rein/cli.py @@ -1205,13 +1205,13 @@ def status(multi, identity, jobid): @cli.command() @click.argument('key', required=True) -@click.argument('value', required=True) +@click.argument('value', required=False, default=None) def config(key, value): """ Set configuration variable. Parses true/false, on/off, and passes anything else unaltered to the db. """ - keys = ['testnet', 'tor', 'debug', 'fee'] + keys = ['testnet', 'tor', 'debug', 'fee', 'blockexplorer', 'api'] if key not in keys: click.echo("Invalid config setting. Try one of " + ', '.join(keys)) return @@ -1220,9 +1220,11 @@ def config(key, value): PersistConfig.set(rein, key, 'true') elif value and value.lower() in ['off', 'false', 'disabled']: PersistConfig.set(rein, key, 'false') - else: + elif value: PersistConfig.set(rein, key, value) + click.echo(PersistConfig.get(rein, key)) + # leave specific config commands in for backwards compatibility, remove in 0.4 @cli.command() @@ -1279,11 +1281,14 @@ def debug(debug): def init(multi, identity): log = rein.get_log() + if multi: rein.set_multiuser() + if rein.has_no_account(): click.echo("Please run setup.") return sys.exit(1) + user = get_user(rein, identity, True) key = pubkey(user.dkey) urls = Bucket.get_urls(rein) @@ -1305,12 +1310,14 @@ def is_int(s): except ValueError: return False + def is_tags(s): if re.search(r'[^a-z0-9 ,]', s.lower()): return False else: return True + def get_user(rein, identity, enrolled): if rein.multi and identity: rein.user = rein.session.query(User).filter( @@ -2173,7 +2180,8 @@ def job_info_page(jobid): urls=urls, state=state, found=found, - fee=PersistConfig.get(rein, 'fee', 0.00025), + fee=PersistConfig.get(rein, 'fee', 0.001), + explorer=PersistConfig.get(rein, 'explorer', 'https://blockexplorer.com'), unique=unique_documents, job=combined, mediator_fee_btc=mediator_fee_btc) diff --git a/rein/html/job.html b/rein/html/job.html index 36e3c7d..71e25f6 100644 --- a/rein/html/job.html +++ b/rein/html/job.html @@ -43,16 +43,26 @@

Job Detail

Job creator
- Name: {{ job['Job creator'] }}
Contact: {{ job['Job creator contact'] }}
Master: {{ job['Job creator master address'][0:10] }}...{{ job['Job creator master address'][-8:] }}
+ Name: {{ job['Job creator'] }}
+ Contact: {{ job['Job creator contact'] }}
+ Direct payment address:
+ {{ job['Job creator delegate address'][0:15] }}
{{ job['Job creator delegate address'][15:] }}
Mediator
- Name: {{ job['Mediator'] }}
Contact: {{ job['Mediator contact'] }}
Master: {{ job['Mediator master address'][0:10] }}...{{ job['Mediator master address'][-8:] }}
+ Name: {{ job['Mediator'] }}
+ Contact: {{ job['Mediator contact'] }}
+ Direct payment address:
+ {{ job['Mediator delegate address'][0:15] }}
{{ job['Mediator delegate address'][15:] }}
+ {% if 'Worker' in job %}
Worker
- Name: {{ job['Worker'] }}
Contact: {{ job['Worker contact'] }}
Master: {{ job['Worker master address'][0:10] }}...{{ job['Worker master address'][-8:] }} + Name: {{ job['Worker'] }}
+ Contact: {{ job['Worker contact'] }}
+ Direct payment address:
+ {{ job['Worker delegate address'][0:15] }}
{{ job['Worker delegate address'][15:] }}
@@ -65,13 +75,13 @@
Payments
- Primary escrow: {{ job['Primary escrow address'] }} should be funded with {{ job['Bid amount (BTC)'] }} BTC plus {{ fee }} for the closing transaction fee. + Primary escrow: {{ job['Primary escrow address'] }} should be funded with {{ job['Bid amount (BTC)'] }} BTC plus {{ fee }} for the closing transaction fee.
- Mediator escrow: {{ job['Mediator escrow address'] }} should be funded with {{ mediator_fee_btc }} BTC ({{ job['Mediator fee'] }}% of the above) plus {{ fee }} for the closing transaction fee. + Mediator escrow: {{ job['Mediator escrow address'] }} should be funded with {{ mediator_fee_btc }} BTC ({{ job['Mediator fee'] }}% of the above) plus {{ fee }} for the closing transaction fee.
diff --git a/rein/lib/transaction.py b/rein/lib/transaction.py index cdfc2ca..06328b6 100644 --- a/rein/lib/transaction.py +++ b/rein/lib/transaction.py @@ -5,13 +5,15 @@ import urllib2, urllib import json from hashlib import sha256 -api = "blocktrail" #handle this from .bucket import Bucket from .io import safe_get from .persistconfig import PersistConfig import click -def unspent_txins(address,testnet): + +def unspent_txins(rein, address, testnet): + api = PersistConfig.get(rein, 'api', 'blockr') + if (api == "blockr"): if testnet: url = "https://tbtc.blockr.io/api/v1/address/unspent/"+str(address) @@ -65,7 +67,7 @@ def partial_spend_p2sh (redeemScript,rein,daddr=None,alt_amount=None,alt_daddr=N txin_redeemScript = CScript(x(redeemScript)) txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) - (txins,total_value) = unspent_txins(txin_p2sh_address,rein.testnet) + (txins,total_value) = unspent_txins(rein, txin_p2sh_address,rein.testnet) if len(txins)==0: raise ValueError('Primary escrow is empty. Please inform client to add funds.') txins_str = "" @@ -101,7 +103,7 @@ def partial_spend_p2sh_mediator (redeemScript,rein,mediator_address,mediator_sig txin_redeemScript = CScript(x(redeemScript)) txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) - (txins,total_value) = unspent_txins(txin_p2sh_address,rein.testnet) + (txins,total_value) = unspent_txins(rein, txin_p2sh_address,rein.testnet) if len(txins)==0: raise ValueError('Mediator escrow is empty. Please inform client to add funds.') txins_str = "" From 1ec3ff122c9ad9482489b9c3fcad2fbab5c2030d Mon Sep 17 00:00:00 2001 From: David Sterry Date: Fri, 10 Mar 2017 11:38:16 -0800 Subject: [PATCH 2/9] put job and user fields first when rating --- rein/html/rate.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rein/html/rate.html b/rein/html/rate.html index 36b8aa9..738a1ca 100644 --- a/rein/html/rate.html +++ b/rein/html/rate.html @@ -17,12 +17,6 @@

Submit rating

{{ form.csrf_token }} -
- -
{{ form.job_id(class="form-control", data_toggle="tooltip", title="Unique job identifier", readonly=true) }}
- {{ render_error(form.job_id) }} -
-
- -
{{ form.user_id(class="form-control", data_toggle="tooltip", title="Unique user identifier: Secure Identity Number", readonly=true) }}
- {{ render_error(form.user_id) }} + +
{{ form.job_id(class="form-control", data_toggle="tooltip", title="Unique job identifier", readonly=true) }}
+ {{ render_error(form.job_id) }}
@@ -53,6 +47,12 @@

Submit rating

+
+ +
{{ form.user_id(class="form-control", data_toggle="tooltip", title="Unique user identifier: Secure Identity Number", readonly=true) }}
+ {{ render_error(form.user_id) }} +
+
{{ form.rating(class="form-control") }} From f35ca2fb3a8b1a89c3e8b68b379c2fec6a3dcf9c Mon Sep 17 00:00:00 2001 From: David Sterry Date: Fri, 10 Mar 2017 13:03:26 -0800 Subject: [PATCH 3/9] convert ratings page to profile and link usernames most places --- rein/cli.py | 25 +++++++++++++++++++----- rein/html/job.html | 6 +++--- rein/html/{ratings.html => profile.html} | 11 ++++++++--- rein/lib/rating.py | 3 ++- 4 files changed, 33 insertions(+), 12 deletions(-) rename rein/html/{ratings.html => profile.html} (54%) diff --git a/rein/cli.py b/rein/cli.py index 6491f0e..01eacee 100644 --- a/rein/cli.py +++ b/rein/cli.py @@ -1542,10 +1542,14 @@ def rate_web(): user_jobs = get_user_jobs(rein) return render_template("rate.html", form=form, user_sin=user.msin, user=user, user_jobs=user_jobs) - @app.route('/ratings/', methods=['GET']) - def view_ratings(msin): + @app.route('/profile/', methods=['GET']) + def view_profile(msin): ratings = get_all_user_ratings(log, url, user, rein, msin) - return render_template("ratings.html", user=user, user_rated=get_user_name(log, url, user, rein, msin), msin=msin, ratings=ratings) + return render_template("profile.html", + user=user, + user_rated=get_user_name(log, url, user, rein, msin), + msin=msin, + ratings=ratings) @app.route('/hide', methods=['POST']) def hide(): @@ -1752,8 +1756,9 @@ def job_offer(): id = d[0].id bid_choices.append(( str(id), - '{}{}{}{}{}{}'.format( + '{}{}{}{}{}{}'.format( job_link(b), + worker_msin, b['Worker'], get_average_user_rating_display(log, url, user, rein, worker_msin), b['Description'], @@ -2172,6 +2177,8 @@ def job_info_page(jobid): except ValueError: mediator_fee_btc = "NaN" + worker_msin = generate_sin(combined['Worker master address']) if 'Worker master address' in combined else '' + return render_template('job.html', rein=rein, user=user, @@ -2184,6 +2191,9 @@ def job_info_page(jobid): explorer=PersistConfig.get(rein, 'explorer', 'https://blockexplorer.com'), unique=unique_documents, job=combined, + job_creator_msin=generate_sin(combined['Job creator master address']), + mediator_msin=generate_sin(combined['Mediator master address']), + worker_msin=worker_msin, mediator_fee_btc=mediator_fee_btc) @@ -2341,9 +2351,13 @@ def job_bid(): continue creator_msin = generate_sin(j['Job creator master address']) - row = '{}{}{}{}{}{}{}' + row = '{}' +\ + '{}' +\ + '{}{}{}' +\ + '{}{}' job_ids.append((j['Job ID'], row.format(j['Job ID'], j['Job name'], + creator_msin, j['Job creator'], get_average_user_rating_display(log, url, user, rein, creator_msin), j['Description'], @@ -2378,6 +2392,7 @@ def job_bid(): {'label': 'Job name', 'value_from': job}, {'label': 'Worker', 'value': user.name}, {'label': 'Worker contact', 'value': user.contact}, + {'label': 'Worker msin', 'value': user.msin}, {'label': 'Worker delegate address', 'value': user.daddr}, {'label': 'Worker master address', 'value': user.maddr}, {'label': 'Description', 'value': form.description.data}, diff --git a/rein/html/job.html b/rein/html/job.html index 71e25f6..05f608c 100644 --- a/rein/html/job.html +++ b/rein/html/job.html @@ -43,14 +43,14 @@

Job Detail

Mediator
- Name: {{ job['Mediator'] }}
+ Name: {{ job['Mediator'] }}
Contact: {{ job['Mediator contact'] }}
Direct payment address:
{{ job['Mediator delegate address'][0:15] }}
{{ job['Mediator delegate address'][15:] }}
@@ -59,7 +59,7 @@
Mediator
Worker
- Name: {{ job['Worker'] }}
+ Name: {{ job['Worker'] }}
Contact: {{ job['Worker contact'] }}
Direct payment address:
{{ job['Worker delegate address'][0:15] }}
{{ job['Worker delegate address'][15:] }}
diff --git a/rein/html/ratings.html b/rein/html/profile.html similarity index 54% rename from rein/html/ratings.html rename to rein/html/profile.html index 46b968b..ab6c686 100644 --- a/rein/html/ratings.html +++ b/rein/html/profile.html @@ -4,7 +4,12 @@
- This page displays ratings received by {{ user_rated }} (msin: {{ msin }}). +

User Profile

+ Name / handle: {{ user_rated }}
+ Contact: {{ user.contact }}
+ Master address: {{ user.maddr }}
+ Direct payment address: {{ user.daddr }}
+ SIN (?): {{ msin }}

@@ -17,7 +22,7 @@ - + {% endfor %} @@ -25,4 +30,4 @@

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/rein/lib/rating.py b/rein/lib/rating.py index bbd783d..ce153a4 100644 --- a/rein/lib/rating.py +++ b/rein/lib/rating.py @@ -149,7 +149,7 @@ def get_average_user_rating_display(log, url, user, rein, msin, cli=False): return 'Not yet rated' if not cli: - return '{} ({})'.format(rating[0], msin, rating[1]) + return '{} ({})'.format(rating[0], msin, rating[1]) return '{} Stars ({})'.format(rating[0], rating[1]) @@ -172,6 +172,7 @@ def get_all_user_ratings(log, url, user, rein, msin): { 'rating_value': '{} '.format(float(rating['Rating'])), 'comments': rating['Comments'], + 'rated_by_msin': rating['Rater msin'], 'rated_by_name': get_user_name(log, url, user, rein, rating['Rater msin']), 'rated_by_rating': get_average_user_rating_display(log, url, user, rein, rating['Rater msin']) } From 65772318062f2162c14b3a40ed807f0b58c4ef60 Mon Sep 17 00:00:00 2001 From: David Sterry Date: Fri, 10 Mar 2017 13:14:33 -0800 Subject: [PATCH 4/9] fix labels with re-ordered rating form fields --- rein/html/rate.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/rein/html/rate.html b/rein/html/rate.html index 738a1ca..a6a715a 100644 --- a/rein/html/rate.html +++ b/rein/html/rate.html @@ -18,7 +18,8 @@

Submit rating

{{ form.csrf_token }}
-
+ +
@@ -30,13 +31,14 @@

Submit rating

- -
{{ form.job_id(class="form-control", data_toggle="tooltip", title="Unique job identifier", readonly=true) }}
+
+ {{ form.job_id(class="form-control", data_toggle="tooltip", title="Unique job identifier", readonly=true) }}
{{ render_error(form.job_id) }}
-
+ +
@@ -48,8 +50,8 @@

Submit rating

- -
{{ form.user_id(class="form-control", data_toggle="tooltip", title="Unique user identifier: Secure Identity Number", readonly=true) }}
+
+ {{ form.user_id(class="form-control", data_toggle="tooltip", title="Unique user identifier: Secure Identity Number", readonly=true) }}
{{ render_error(form.user_id) }}
From 21f1cfb73a72826df441cbb99875e865f4909e94 Mon Sep 17 00:00:00 2001 From: David Sterry Date: Fri, 10 Mar 2017 13:22:45 -0800 Subject: [PATCH 5/9] link profile for mediators on post page --- rein/cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rein/cli.py b/rein/cli.py index 01eacee..51e0557 100644 --- a/rein/cli.py +++ b/rein/cli.py @@ -1633,8 +1633,11 @@ def job_post(): if m.msin in [hidden_mediator['content_identifier'] for hidden_mediator in hidden_mediator_content]: continue - mediator_maddrs.append((m.maddr, '{}
' + '
{{ rating.rating_value|safe }} {{ rating.comments }}{{ rating.rated_by_name }} - {{ rating.rated_by_rating|safe }}{{ rating.rated_by_name }} - {{ rating.rated_by_rating|safe }}
{}%{}{}{}'.\ - format(m.username, + mediator_maddrs.append((m.maddr, + '{}{}%{}{}{}'.\ + format(m.msin, + m.username, m.mediator_fee, get_average_user_rating_display(log, url, user, rein, m.msin), m.contact, From a4138fa6d0b24f5a36f45426e2ca439c55db9970 Mon Sep 17 00:00:00 2001 From: David Sterry Date: Sun, 12 Mar 2017 22:37:36 -0400 Subject: [PATCH 6/9] fix apostrophes --- doc/key-management.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/key-management.md b/doc/key-management.md index 8de0e88..fb16e56 100644 --- a/doc/key-management.md +++ b/doc/key-management.md @@ -4,7 +4,7 @@ Key Management in python-rein * Privacy - Keys involved in a transaction only need to be known by parties to that transaction. This means knowing a key that is used to sign a document or payment should not lead to the -discovery of a user's other keys. +discovery of a user''s other keys. * Multilayered security - An identity is defined by a 12-word mnemonic seed. The seed is used to generate a root BIP32 key. @@ -17,13 +17,13 @@ to use. * Root key (m) - Derived from the 12-word mnemonic seed. - * Master signing key (m/0) - This key is used to sign the primary document defining a user's identity + * Master signing key (m/0) - This key is used to sign the primary document defining a user''s identity in Rein, called an enrollment. In includes name, contact info, whether a user is willing to be a mediator in others transactions and their desired fee rate. The enrollment also includes a Secure Identity Number (SIN), generated from the master signing key. This SIN is specified on the Bitcoin wiki as Identity Protocol v1 and used by Bitrated as a unique identifier for a user. - * Delegate key (m/1'/0) - A delegate key is used for day-to-day signatures of documents like job + * Delegate key (m/1''/0) - A delegate key is used for day-to-day signatures of documents like job postings, bids, offers, disputes and is also used for controlling payments. ###Deficiencies with current implementation: @@ -34,15 +34,15 @@ to use. job), obviously not desirable when handling money. * The public part of the key used to sign documents must be made available so that others can verify - their authenticity. Since this same key is used to generate a user's payment address meaning all + their authenticity. Since this same key is used to generate a user''s payment address meaning all incoming payments and document signatures are trivially linked. ###Future directions: - * Unique public keys per escrow (m/1'/k with k > 0) - A unique key will be generated from the BIP32 tree + * Unique public keys per escrow (m/1''/k with k > 0) - A unique key will be generated from the BIP32 tree for each post or bid. This key will be used to build escrow addresses and to sign payments at conclusion of each job. - * Unique internal wallet address per job (m/0'/k) - A unique payment address will be generated from the + * Unique internal wallet address per job (m/0''/k) - A unique payment address will be generated from the BIP32 tree for each post or bid. This is where funds will be sent when payments are sent from escrow back to a client, to a mediator, or to a freelancer. From c881f9e46607e3764becb43ee7d86d8017e9582a Mon Sep 17 00:00:00 2001 From: David Sterry Date: Tue, 14 Mar 2017 12:45:12 -0400 Subject: [PATCH 7/9] split address text better --- rein/html/job.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rein/html/job.html b/rein/html/job.html index 05f608c..0e50e14 100644 --- a/rein/html/job.html +++ b/rein/html/job.html @@ -46,14 +46,14 @@
Job creator
Name: {{ job['Job creator'] }}
Contact: {{ job['Job creator contact'] }}
Direct payment address:
- {{ job['Job creator delegate address'][0:15] }}
{{ job['Job creator delegate address'][15:] }}
+ {{ job['Job creator delegate address'][0:17] }}
{{ job['Job creator delegate address'][17:] }}
Mediator
Name: {{ job['Mediator'] }}
Contact: {{ job['Mediator contact'] }}
Direct payment address:
- {{ job['Mediator delegate address'][0:15] }}
{{ job['Mediator delegate address'][15:] }}
+ {{ job['Mediator delegate address'][0:17] }}
{{ job['Mediator delegate address'][17:] }} {% if 'Worker' in job %}
@@ -62,7 +62,7 @@
Worker
Name: {{ job['Worker'] }}
Contact: {{ job['Worker contact'] }}
Direct payment address:
- {{ job['Worker delegate address'][0:15] }}
{{ job['Worker delegate address'][15:] }}
+ {{ job['Worker delegate address'][0:17] }}
{{ job['Worker delegate address'][17:] }} From c986943ee233387e0188dd48ca3ee12d92b3078e Mon Sep 17 00:00:00 2001 From: David Sterry Date: Fri, 17 Mar 2017 22:31:57 -0400 Subject: [PATCH 8/9] add ecdsa test --- tests/lib/test_bitcoinecdsa.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/lib/test_bitcoinecdsa.py diff --git a/tests/lib/test_bitcoinecdsa.py b/tests/lib/test_bitcoinecdsa.py new file mode 100644 index 0000000..1cd5128 --- /dev/null +++ b/tests/lib/test_bitcoinecdsa.py @@ -0,0 +1,17 @@ +import unittest + +from rein.lib.bitcoinecdsa import pubkey_to_address, privkey_to_address + +class TestBitcoinEcdsa(unittest.TestCase): + def test_pubkey_to_address(self): + self.assertEqual(pubkey_to_address('029fcafbe2dced6fe79865b265ea90387c5411658ca11449999d5020a9f67bb005'), + '19skaV7ZDvSe2zKXB32fcay2NzajJcRG8B') + self.assertNotEqual(pubkey_to_address('029fcafbe2dced6fe79865b265ea90387c5411658ca11449999d5020a9f67bb005'), + '1LgubGW1vZFkR79tZtuDU5jk4DqagDgUih') + + def test_privkey_to_address(self): + self.assertFalse(privkey_to_address('notaprivkey')) + self.assertEqual(privkey_to_address('KwKnhdiSmGiCkWneqyFZEDcD2NftCyqRFz7U96dq8BYudkXMCTtJ'), + '12YFs9A39J8npGUvZZ6Mune3mCwrJ6Gky1') + self.assertNotEqual(privkey_to_address('KwKnhdiSmGiCkWneqyFZEDcD2NftCyqRFz7U96dq8BYudkXMCTtJ'), + '1LgubGW1vZFkR79tZtuDU5jk4DqagDgUih') From eee3a613ec7c596fd1022caa0cdb90abfb768987 Mon Sep 17 00:00:00 2001 From: David Sterry Date: Fri, 17 Mar 2017 22:47:41 -0400 Subject: [PATCH 9/9] add default testnet explorer --- rein/cli.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rein/cli.py b/rein/cli.py index 535f634..e5f28e6 100644 --- a/rein/cli.py +++ b/rein/cli.py @@ -2215,6 +2215,12 @@ def job_info_page(jobid): worker_msin = generate_sin(combined['Worker master address']) if 'Worker master address' in combined else '' + explorer = PersistConfig.get( + rein, + 'explorer', + 'https://testnet.blockexplorer.com' if rein.testnet else 'https://blockexplorer.com' + ) + return render_template('job.html', rein=rein, user=user, @@ -2224,7 +2230,7 @@ def job_info_page(jobid): state=state, found=found, fee=PersistConfig.get(rein, 'fee', 0.001), - explorer=PersistConfig.get(rein, 'explorer', 'https://blockexplorer.com'), + explorer=explorer, unique=unique_documents, job=combined, job_creator_msin=generate_sin(combined['Job creator master address']),