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. diff --git a/rein/cli.py b/rein/cli.py index 0411411..e5f28e6 100644 --- a/rein/cli.py +++ b/rein/cli.py @@ -1202,12 +1202,13 @@ def status(multi, identity, jobid): else: click.echo("Job id not found") + def config_common(key, value): """ Set configuration variable. Parses true/false, on/off, and passes anything else unaltered to the db. """ - keys = ['testnet', 'tor', 'debug', 'fee', 'trust_score'] + keys = ['testnet', 'tor', 'debug', 'fee', 'blockexplorer', 'api', 'trust_score'] if key not in keys: click.echo("Invalid config setting. Try one of " + ', '.join(keys)) return @@ -1216,9 +1217,12 @@ def config_common(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)) + + @cli.command() @click.argument('key', required=True) @click.argument('value', required=True) @@ -1286,11 +1290,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) @@ -1312,12 +1319,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( @@ -1542,11 +1551,16 @@ 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): display_trust_score = PersistConfig.get(rein, 'trust_score', False) 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, display_trust_score=display_trust_score) + return render_template("profile.html", + user=user, + user_rated=get_user_name(log, url, user, rein, msin), + msin=msin, + ratings=ratings, + display_trust_score=display_trust_score) @app.route('/hide', methods=['POST']) def hide(): @@ -1652,8 +1666,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, '{}{}%{}{}{}'.\ - 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, @@ -1775,8 +1792,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'], @@ -2195,6 +2213,14 @@ 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 '' + + 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, @@ -2203,9 +2229,13 @@ 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=explorer, 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) @@ -2363,9 +2393,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'], @@ -2400,6 +2434,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 36e3c7d..0e50e14 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:17] }}
{{ job['Job creator delegate address'][17:] }}
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:17] }}
{{ job['Mediator delegate address'][17:] }}
+ {% 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:17] }}
{{ job['Worker delegate address'][17:] }}
@@ -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/html/ratings.html b/rein/html/profile.html similarity index 63% rename from rein/html/ratings.html rename to rein/html/profile.html index fd3807b..755de2d 100644 --- a/rein/html/ratings.html +++ b/rein/html/profile.html @@ -6,18 +6,24 @@
-

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 }}

+

- {% if display_trust_score %} + {% if display_trust_score %} - {% else %} + {% else %}

- {% endif %} + {% endif %}

@@ -31,7 +37,7 @@ - + {% endfor %} @@ -39,4 +45,4 @@

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/rein/html/rate.html b/rein/html/rate.html index 36b8aa9..a6a715a 100644 --- a/rein/html/rate.html +++ b/rein/html/rate.html @@ -19,12 +19,7 @@

Submit rating

-
{{ form.job_id(class="form-control", data_toggle="tooltip", title="Unique job identifier", readonly=true) }}
- {{ render_error(form.job_id) }} -
- -
-
+
@@ -36,13 +31,14 @@

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.job_id(class="form-control", data_toggle="tooltip", title="Unique job identifier", readonly=true) }}
+ {{ render_error(form.job_id) }}
-
+ +
@@ -53,6 +49,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") }} diff --git a/rein/lib/rating.py b/rein/lib/rating.py index e485575..0e54add 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']) } 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 = "" 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')
{{ rating.rating_value|safe }} {{ rating.comments }}{{ rating.rated_by_name }} - {{ rating.rated_by_rating|safe }}{{ rating.rated_by_name }} - {{ rating.rated_by_rating|safe }}