def __init__(self, wallet, db=None, account='default', testnet=False, blockchain=None): """Initalize the payment server. Args: wallet (.wallet.Two1WalletWrapper): a two1 wallet wrapped with payment server functionality. account (string): which account within the wallet to use (e.g. 'merchant', 'customer', 'default', etc). testnet (boolean): whether or not the server should broadcast and verify transactions against the bitcoin testnet blockchain. """ self._wallet = Two1WalletWrapper(wallet, account) self._blockchain = blockchain self._db = db if db is None: self._db = DatabaseSQLite3() if blockchain is None: self._blockchain = TwentyOneProvider(TWO1_PROVIDER_HOST)
def validate_data_provider(ctx, param, value): """ Validates the data provider sent in via the CLI. Args: ctx (Click context): Click context object. param (str): Parameter that is being validated. value (str): Parameter value. """ data_provider_params = {} if ctx.obj is None: ctx.obj = {} if value not in REQUIRED_DATA_PROVIDER_PARAMS: ctx.fail("Unknown data provider %s" % value) required = REQUIRED_DATA_PROVIDER_PARAMS[value] fail = False for r in required: if r not in ctx.params: s = r.replace('_', '-') click.echo("--%s is required to use %s." % (s, value)) fail = True else: data_provider_params[r] = ctx.params[r] if fail: ctx.fail("One or more required arguments are missing.") dp = None if value == 'insight': url = ctx.params['insight_url'] api_path = ctx.params['insight_api_path'] dp = InsightProvider(insight_host_name=url, insight_api_path=api_path) elif value == 'twentyone': dp = TwentyOneProvider() ctx.obj['data_provider'] = dp ctx.obj['data_provider_params'] = data_provider_params
class PaymentServer: """Payment channel handling. This class handles the server-side implementation of payment channels from handshake to channel close. It also implements the ability for """ # Minimum transaction fee and total payment amount (dust limit) MIN_TX_FEE = 5000 DUST_LIMIT = 546 MIN_EXP_TIME = 4 * 3600 def __init__(self, wallet, db=None, account='default', testnet=False, blockchain=None): """Initalize the payment server. Args: wallet (.wallet.Two1WalletWrapper): a two1 wallet wrapped with payment server functionality. account (string): which account within the wallet to use (e.g. 'merchant', 'customer', 'default', etc). testnet (boolean): whether or not the server should broadcast and verify transactions against the bitcoin testnet blockchain. """ self._wallet = Two1WalletWrapper(wallet, account) self._blockchain = blockchain self._db = db if db is None: self._db = DatabaseSQLite3() if blockchain is None: self._blockchain = TwentyOneProvider(TWO1_PROVIDER_HOST) def discovery(self): """Return the merchant's public key. A customer requests a public key from a merchant. This allows the customer to create a multi-signature refund transaction with both the customer and merchant's public keys. """ return self._wallet.get_public_key() def initialize_handshake(self, refund_tx): """Initialize a payment channel. The customer initializes the payment channel by providing a half-signed multi-signature refund transaction. This allows the merchant to return a fully executed refund transaction. Args: refund_tx (two1.lib.bitcoin.txn.Transaction): half-signed refund Transaction from a customer. This object is passed by reference and modified directly. Returns: (boolean): whether the handshake was successfully initialized. """ # Verify that the transaction is build correctly self._wallet.verify_half_signed_tx(refund_tx) # Verify that the lock time is an allowable amount in the future minimum_locktime = int(time.time()) + self.MIN_EXP_TIME if refund_tx.lock_time < minimum_locktime: raise TransactionVerificationError( 'Transaction locktime must be further in the future.') # Try to create the channel and verify that the deposit txid is good deposit_txid = str(refund_tx.inputs[0].outpoint) redeem_script = get_redeem_script(refund_tx) pubkeys = redeem_script.extract_multisig_redeem_info()['public_keys'] merch_pubkey = self._wallet.get_merchant_key_from_keys(pubkeys) # Sign the remaining half of the transaction self._wallet.sign_half_signed_tx(refund_tx, merch_pubkey) try: # Save the initial payment channel self._db.pc.create(refund_tx, merch_pubkey) except: raise BadTransactionError( 'That deposit has already been used to create a channel.') return True def complete_handshake(self, deposit_txid, deposit_tx): """Complete the final step in the channel handshake. The customer completes the handshake by sending the merchant the customer's signed deposit transaction, which the merchant can then broadcast to the network. The merchant responds with 200 to verify that the handshake has completed successfully. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. deposit_tx (two1.lib.bitcoin.txn.Transaction): half-signed deposit Transaction from a customer. This object is passed by reference and modified directly. Returns: (boolean): whether the handshake was successfully completed. """ try: channel = self._db.pc.lookup(deposit_txid) except: raise PaymentServerNotFoundError('Related channel not found.') # Get the refund spend address refund_tx = channel['refund_tx'] # Find the payment amount associated with the refund refund_hash160 = get_redeem_script(refund_tx).hash160() deposit_index = deposit_tx.output_index_for_address(refund_hash160) # Verify that the deposit funds the refund in our records if deposit_index is not None: deposit_amt = deposit_tx.outputs[deposit_index].value else: raise BadTransactionError('Deposit must fund refund.') # Save the deposit transaction try: self._db.pc.update_deposit(deposit_txid, deposit_tx, deposit_amt) except: raise BadTransactionError('Deposit already used.') self._db.pc.update_state(deposit_txid, 'confirming') return True def receive_payment(self, deposit_txid, payment_tx): """Receive and process a payment within the channel. The customer makes a payment in the channel by sending the merchant a half-signed payment transaction. The merchant signs the other half of the transaction and saves it in its records (but does not broadcast it or send it to the customer). The merchant responds with 200 to verify that the payment was handled successfully. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. deposit_tx (two1.lib.bitcoin.txn.Transaction): half-signed deposit Transaction from a customer. This object is passed by reference and modified directly. Returns: (boolean): whether the payment was sucessfully processed. """ # Verify that the transaction is what we expect self._wallet.verify_half_signed_tx(payment_tx) # Get channel and addresses related to the deposit try: channel = self._db.pc.lookup(deposit_txid) except: raise PaymentServerNotFoundError('Related channel not found.') # Get merchant public key information from payment channel last_pmt_amt = channel['last_payment_amount'] merch = channel['merchant_pubkey'] merch_pubkey = PublicKey.from_bytes(codecs.decode(merch, 'hex_codec')) index = payment_tx.output_index_for_address(merch_pubkey.hash160()) # Verify that the payment channel is still open if (channel['state'] != 'confirming' and channel['state'] != 'ready'): raise ChannelClosedError('Payment channel closed.') # Find the payment amount associated with the merchant address if index is None: raise BadTransactionError('Payment must pay to merchant pubkey.') # Validate that the payment is more than the last one new_pmt_amt = payment_tx.outputs[index].value if new_pmt_amt <= last_pmt_amt: raise BadTransactionError('Micropayment must be greater than 0.') # Verify that the payment channel is still open if (channel['state'] != 'confirming' and channel['state'] != 'ready'): raise ChannelClosedError('Payment channel closed.') # Verify that the transaction has adequate fees net_pmt_amount = sum([d.value for d in payment_tx.outputs]) deposit_amount = channel['amount'] if deposit_amount < net_pmt_amount + PaymentServer.MIN_TX_FEE: raise BadTransactionError('Payment must have adequate fees.') # Sign the remaining half of the transaction self._wallet.sign_half_signed_tx(payment_tx, merch_pubkey) # Update the current payment transaction self._db.pc.update_payment(deposit_txid, payment_tx, new_pmt_amt) self._db.pmt.create(deposit_txid, payment_tx, new_pmt_amt-last_pmt_amt) return True def status(self, deposit_txid): """Get a payment channel's current status. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. """ try: channel = self._db.pc.lookup(deposit_txid) except: raise PaymentServerNotFoundError('Related channel not found.') return {'status': channel['state'], 'balance': channel['last_payment_amount'], 'time_left': channel['expires_at']} def close(self, deposit_txid): """Close a payment channel. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. txid_signature (string): a signed message consisting solely of the deposit_txid to verify the authenticity of the close request. """ try: channel = self._db.pc.lookup(deposit_txid) except: raise PaymentServerNotFoundError('Related channel not found.') # Verify that there is a valid payment to close if not channel['payment_tx']: raise BadTransactionError('No payments made in channel.') # Broadcast payment transaction to the blockchain self._blockchain.broadcast_transaction(channel['payment_tx'].to_hex()) # Record the broadcast in the database self._db.pc.update_state(deposit_txid, 'closed') return str(channel['payment_tx'].hash) def redeem(self, payment_txid): """Determine the validity and amount of a payment. Args: payment_txid (string): the hash in hexadecimal of the payment transaction, often referred to as the transaction id. Returns: pmt_amount (int): value in satoshis of the incremental payment. Raises: PaymentError: reason why payment is not redeemable. """ # Verify that we have this payment transaction saved try: payment = self._db.pmt.lookup(payment_txid) except: raise PaymentServerNotFoundError('Payment not found.') # Verify that this payment exists within a channel (do we need this?) try: channel = self._db.pc.lookup(payment['deposit_txid']) except: raise PaymentServerNotFoundError('Channel not found.') # Verify that the payment channel is still open if (channel['state'] != 'confirming' and channel['state'] != 'ready'): raise ChannelClosedError('Payment channel closed.') # Verify that the most payment has not already been redeemed if payment['is_redeemed']: raise RedeemPaymentError('Payment already redeemed.') # Calculate and redeem the current payment self._db.pmt.redeem(payment_txid) return payment['amount']
def __init__(self, wallet, db=None): """Initialize payment handling for on-chain payments.""" self.db = db or OnChainSQLite3() self.address = wallet.get_payout_address() self.provider = TwentyOneProvider(TWO1_PROVIDER_HOST)
class OnChain(PaymentBase): """Making a payment on the bitcoin blockchain.""" lock = threading.Lock() http_payment_data = 'Bitcoin-Transaction' http_402_price = 'Price' http_402_address = 'Bitcoin-Address' DUST_LIMIT = 3000 # dust limit in satoshi def __init__(self, wallet, db=None): """Initialize payment handling for on-chain payments.""" self.db = db or OnChainSQLite3() self.address = wallet.get_payout_address() self.provider = TwentyOneProvider(TWO1_PROVIDER_HOST) @property def payment_headers(self): """List of headers to use for payment processing.""" return [OnChain.http_payment_data] def get_402_headers(self, price, **kwargs): """Dict of headers to return in the initial 402 response.""" return { OnChain.http_402_price: price, OnChain.http_402_address: kwargs.get('address', self.address) } def redeem_payment(self, price, request_headers, **kwargs): """Validate the transaction and broadcast it to the blockchain.""" raw_tx = request_headers[OnChain.http_payment_data] logger.debug('[BitServ] Receieved transaction: {}'.format(raw_tx)) # verify txn is above dust limit if price < OnChain.DUST_LIMIT: raise PaymentBelowDustLimitError( 'Payment amount is below dust limit ({} Satoshi)'.format( OnChain.DUST_LIMIT)) try: payment_tx = Transaction.from_hex(raw_tx) except: raise InvalidPaymentParameterError('Invalid transaction hex.') # Find the output with the merchant's address payment_index = payment_tx.output_index_for_address( kwargs.get('address', self.address)) if payment_index is None: raise InvalidPaymentParameterError('Not paid to merchant.') # Verify that the payment is made for the correct amount if payment_tx.outputs[payment_index].value != price: raise InsufficientPaymentError('Incorrect payment amount.') # Synchronize the next block of code to manage its atomicity with self.lock: # Verify that we haven't seen this transaction before if self.db.lookup(str(payment_tx.hash)): raise DuplicatePaymentError('Payment already used.') else: self.db.create(str(payment_tx.hash), price) try: # Broadcast payment to network txid = self.provider.broadcast_transaction(raw_tx) logger.debug('[BitServ] Broadcasted: ' + txid) except Exception as e: # Roll back the database entry if the broadcast fails self.db.delete(str(payment_tx.hash)) raise TransactionBroadcastError(str(e)) return True
def __init__(self, config_file=TWO1_CONFIG_FILE, config=None, create_wallet=True): if not os.path.exists(TWO1_USER_FOLDER): os.makedirs(TWO1_USER_FOLDER) self.file = path(config_file).expand().abspath() self.dir = self.file.parent self.defaults = { } # TODO: Rename this var. Those are not the defaults but the actual values. self.json_output = False # output in json # actual config. self.load() # override config variables if config: if self.verbose: self.vlog("Applied manual config.") for k, v in config: self.defaults[k] = v if self.verbose: self.vlog("\t{}={}".format(k, v)) # add wallet object if self.defaults.get('testwallet', None) == 'y': self.wallet = test_wallet.TestWallet() elif create_wallet: dp = TwentyOneProvider(TWO1_PROVIDER_HOST) wallet_path = self.defaults.get('wallet_path') if not Two1Wallet.check_wallet_file(wallet_path): # configure wallet with default options click.pause(UxString.create_wallet) wallet_options = { 'data_provider': dp, 'wallet_path': wallet_path } if not Two1Wallet.configure(wallet_options): raise click.ClickException( UxString.Error.create_wallet_failed) # Display the wallet mnemonic and tell user to back it up. # Read the wallet JSON file and extract it. with open(wallet_path, 'r') as f: wallet_config = json.load(f) mnemonic = wallet_config['master_seed'] click.pause(UxString.create_wallet_done % (mnemonic)) # Start the daemon, if: # 1. It's not already started # 2. It's using the default wallet path # 3. We're not in a virtualenv try: d = daemonizer.get_daemonizer() if Two1Wallet.is_configured() and \ wallet_path == Two1Wallet.DEFAULT_WALLET_PATH and \ not os.environ.get("VIRTUAL_ENV") and \ not d.started(): d.start() if d.started(): click.echo(UxString.wallet_daemon_started) except (OSError, DaemonizerError): pass self.wallet = Wallet(wallet_path=wallet_path, data_provider=dp) self.machine_auth = MachineAuthWallet(self.wallet) self.channel_client = PaymentChannelClient(self.wallet) else: # This branch is hit when '21 help' or '21 update' is invoked pass
class OnChain(PaymentBase): """Making a payment on the bitcoin blockchain.""" lock = threading.Lock() http_payment_data = "Bitcoin-Transaction" http_402_price = "Price" http_402_address = "Bitcoin-Address" DUST_LIMIT = 3000 # dust limit in satoshi def __init__(self, wallet, db=None): """Initialize payment handling for on-chain payments.""" self.db = db or OnChainSQLite3() self.address = wallet.get_payout_address() self.provider = TwentyOneProvider(TWO1_PROVIDER_HOST) @property def payment_headers(self): """List of headers to use for payment processing.""" return [OnChain.http_payment_data] def get_402_headers(self, price, **kwargs): """Dict of headers to return in the initial 402 response.""" return {OnChain.http_402_price: price, OnChain.http_402_address: kwargs.get("address", self.address)} def redeem_payment(self, price, request_headers, **kwargs): """Validate the transaction and broadcast it to the blockchain.""" raw_tx = request_headers[OnChain.http_payment_data] logger.debug("[BitServ] Receieved transaction: {}".format(raw_tx)) # verify txn is above dust limit if price < OnChain.DUST_LIMIT: raise PaymentBelowDustLimitError( "Payment amount is below dust limit ({} Satoshi)".format(OnChain.DUST_LIMIT) ) try: payment_tx = Transaction.from_hex(raw_tx) except: raise InvalidPaymentParameterError("Invalid transaction hex.") # Find the output with the merchant's address payment_index = payment_tx.output_index_for_address(kwargs.get("address", self.address)) if payment_index is None: raise InvalidPaymentParameterError("Not paid to merchant.") # Verify that the payment is made for the correct amount if payment_tx.outputs[payment_index].value != price: raise InsufficientPaymentError("Incorrect payment amount.") # Synchronize the next block of code to manage its atomicity with self.lock: # Verify that we haven't seen this transaction before if self.db.lookup(str(payment_tx.hash)): raise DuplicatePaymentError("Payment already used.") else: self.db.create(str(payment_tx.hash), price) try: # Broadcast payment to network txid = self.provider.broadcast_transaction(raw_tx) logger.debug("[BitServ] Broadcasted: " + txid) except Exception as e: # Roll back the database entry if the broadcast fails self.db.delete(str(payment_tx.hash)) raise TransactionBroadcastError(str(e)) return True
def __init__(self, wallet, db=None): """Initialize payment handling for on-chain payments.""" self.db = db or OnChainSQLite3() self.address = wallet.get_payout_address() self.provider = TwentyOneProvider(TWO1_PROVIDER_HOST)
from two1.lib.wallet import Wallet, Two1Wallet from two1.commands.config import TWO1_PROVIDER_HOST from two1.lib.bitserv import PaymentServer, DatabaseDjango from two1.lib.blockchain.twentyone_provider import TwentyOneProvider from two1.lib.blockchain.exceptions import DataProviderError from two1.examples.bitcoin_auth.helpers.bitcoin_auth_provider_helper import ( BitcoinAuthProvider) from .models import BitcoinToken, PaymentChannel, PaymentChannelSpend from .pricing import get_price_for_request from .exceptions import PaymentRequiredException from .exceptions import ServiceUnavailable if settings.WALLET_MNEMONIC: dp = TwentyOneProvider(TWO1_PROVIDER_HOST) wallet = Two1Wallet.import_from_mnemonic(dp, settings.WALLET_MNEMONIC) else: wallet = Wallet() payment_server = PaymentServer( wallet, DatabaseDjango(PaymentChannel, PaymentChannelSpend)) class BaseBitcoinAuthentication(BaseAuthentication): """Basic Bitcoin authentication. Attributes: bitcoin_provider_helper (BlockchainProvider): provides an api & helper methods into the blockchain. """
from two1.lib.bitcoin.crypto import PublicKey from two1.lib.bitcoin.txn import Transaction from two1.lib.wallet import Wallet from two1.lib.bitcoin.script import Script from two1.lib.blockchain.twentyone_provider import TwentyOneProvider import two1.lib.bitcoin as bitcoin provider = TwentyOneProvider() wallet = Wallet() pubkey = input("Please enter the public key that was used to create the script") tx_hex = input("Please enter the transaction hex") server_pubkey = input("Please enter server pub key") white_pubkey = input("Please enter white pub key") black_pubkey = input("Please enter black pub key") their_pubkey = PublicKey.from_bytes(pubkey) tx = Transaction.from_hex(tx_hex) private_key = wallet.get_private_for_public(their_pubkey) public_keys = [PublicKey.from_bytes(server_pubkey).compressed_bytes, PublicKey.from_bytes(white_pubkey).compressed_bytes, PublicKey.from_bytes(black_pubkey).compressed_bytes] redeem_script = Script.build_multisig_redeem(2, public_keys) for i, inp in enumerate(tx.inputs): tx.sign_input(i, bitcoin.Transaction.SIG_HASH_ALL, private_key, redeem_script) txid = provider.broadcast_transaction(tx.to_hex()) print("Transaction ID: {}".format(txid))
import io import uuid # change the receiving_key in config.py in the root folder. from config import receiving_key, SATOSHIS_PER_MINUTE # logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.ERROR) auth_app = Flask(__name__, static_folder='static') auth_app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/wifiportal21.db' db = SQLAlchemy(auth_app) K = HDPublicKey.from_b58check(receiving_key) blockchain_provider = TwentyOneProvider() cache = CacheManager() receiving_account = HDAccount(hd_key=K, name="hotspot receiving", index=0, data_provider=blockchain_provider, cache_manager=cache) SATOSHIS_PER_MBTC = 100 * 10**3 SATOSHIS_PER_BTC = 100 * 10**6 STATUS_NONE = 0 STATUS_PAYREQ = 1 STATUS_PAID = 2