예제 #1
0
    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)
예제 #2
0
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
예제 #3
0
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']
예제 #4
0
 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)
예제 #5
0
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
예제 #6
0
    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
예제 #7
0
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
예제 #8
0
 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)
예제 #9
0
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.
    """
예제 #10
0
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))
예제 #11
0
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