Skip to content

Instantly share code, notes, and snippets.

@jspilman
Last active December 15, 2015 16:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jspilman/5287991 to your computer and use it in GitHub Desktop.
Save jspilman/5287991 to your computer and use it in GitHub Desktop.

Secure Wallet Extensions through Payment Protocol

The first rule of fight club is... 'Never trust a private key to a third party.'

Jeremy Spilman (jeremy@taplink.co)
April 2, 2013
DRAFT v0.3

Forward

The proposed Payment Protocol supports the ability for a third party service to request and confirm payments from your wallet. This is a huge step forward for Bitcoin, and I believe marks the beginning of a new era of usability, without too large an increase in complexity of the underlying wallet. This proposal is my attempt to contribute to the development of the Payment Protocol, and I welcome all constructive feedback and criticism. I'm a relative new-comer to Bitcoin (as they say on radio, "long time first time") and I've taken care to properly research How Things Work before posting this, so I hope you find this constructive.

Thanks for reading, and looking forward to Bitcoin 2013 in San Jose on May 17th!

Introduction

The following proposal suggests 3 lower level operations which I believe should be supported by the Payment Protocol, which would significantly increase its flexibility for use in current and as-yet-unsupported future applications. Specifically, my proposal would enable what I call 'wallet secured' 3rd party applications, or more eloquently 'Secure Wallet Extensions'.

Secure Wallet Extensions

A 'wallet secured' service is any interaction involving a third-party which meets the following 3 criteria:

  1. The owner of the wallet maintains full control over their funds even if the service disappears
  2. The service has no control over the funds without a signature from one of the user's private key
  3. The service never sees a private key from the user's wallet

Bitcoin users who want to keep their coins should only ever use third party services which are, at a minimum, 'wallet secured'. Use anything less? I would call it "pre-donating your coins at some unknown time in the future."

Today there are huge challenges to providing a 'wallet secured' web service which is safe, easy, and efficient to use. By 'safe' I mean, not prone to user errors which cause coins to be effectively 'destroyed'. I believe that providing the following 3 operations through the Payment Protocol will make it possible to provide innovative 'wallet secured' services that are both safe and "user friendly".

Proposed Operations

Wallets generate transactions by choosing inputs, outputs (scriptPubKeys), and signing. But a wallet, depending on its feature set, will not always know which inputs, or which outputs, should be used in order to achieve a desired effect.

However, every possible action that a wallet may perform on the blockchain could be managed safely and efficiently from outside that wallet, as long as you have an interface which can perform the following functions:

  1. ask a wallet for one or more pubkeys -- returns Set(pubkey)
  2. ask a wallet for a 'payment plan' - given an amount, returns Set(txid:index)
  3. ask a wallet to sign a given rawtransaction

Exposing these basic operation through the Payment Protocol would open up an avenue for innovative new 'wallet secured' services that can effectively extend any wallet's functionality. This is what I mean by 'Secure Wallet Extensions'.

Regarding #2 the payment plan is like doing a 'listunspent' but returning a set of {txid:index ...} for enough addresses to cover the requested amount. The total amount of BTC in the set of txid:index must be >= the requested amount.

Regarding #3 which I assume will be the most controversial, the alternative is to extend the parameters of PaymentRequest, and I propose two new parameters below.

Use Case #1 - Multisignature Transactions

The current draft Payment Protocol allows directing a payment to a multisig redeemScript, or a P2SH address, but does not assist with actually retrieving the necessary public keys from the interested parties to create the multi-sig script in the first place.

It is also ambiguous, if not impossible, how to create a PaymentRequest which would spend from a specific multi-sig txid:index.

Obtaining Public Keys

A multi-sig scriptPubKey or redeemScript needs a pubkey from each possible signer. For the use case of releasing a portion of escrowed funds, and returning the remainder back to escrow, you would want additional pubkeys from each party to use for the funds being returned to escrow (to avoid reusing the same pubkeys for the change output). Deterministic public keys help alleviate this problem, but the initial setup and sharing of public keys remains an open problem for Payment Protocol.

Sharing public keys should not be left to copy & paste. Although we can verify whether the X coordinate in the public key data corresponds to a point on the curve, with a diverse ecosystem of wallet software, walking users through the process of even simply generating and retrieving a public key from their wallet is non-trivial. In the case of BIP32, you also have the share the 32 byte chain code.

Paying into a P2SH

P2SH may be used to conceal the actual requirements to spend the output. In some cases this is desired, and the current Payment Protocol supports all those use cases.

In other cases, P2SH is used to simplify the sending transaction, perhaps as a way to shift TxFees, perhaps as a way to increase anonymity (maybe even security) in the blockchain, by not disclosing pubKeys until after the output is spent. In either case, there are valid reasons where you would want to use P2SH while your counterparties would also require knowledge of the redeemScript. The current PaymentRequest does not include an option for providing the redeemScript when paying to a P2SH, so it does not currently support these use cases.

Adding the redeemScript to the user's keychain ('addmultisigaddress') allows the wallet to confirm for the user a few very important things, for example:

  1. This P2SH pays to an N-of-M multisig
  2. I can currently sign for X of the required signatures
  3. Counterparties must sign for Y of the required signatures
  4. (I can)/(I cannot) spend the outputs with just my signatures, (at least X other signatures are required)

A wallet may choose to show a dialog enumerating the points above as part of approving a PaymentRequest, which I personally believe would be pretty helpful. But that's only possible with P2SH if you can also send the redeemScript.

Spending from a Multisig Input

A wallet will generally contain multiple keypairs which control various amounts of funds ('listunspent'). Some of these txs may be encumbered with a multi-sig requirement, and although not strictly required, multi-sigs tend to require sigs from multiple different wallets.

A PaymentRequest currently only contains an output (scriptPubKey) and an amount. If the desired transaction is to pay from from a specific 2-of-2 multisig address, the wallet needs to be told which source of funds to use to fund the payment request. It must be possible to explicitly state the source of funds, as a set of txid:index, and then the wallet can (try to) sign for those inputs and return the result. If the payment is from a P2SH address, and the wallet doesn't have the redeemScript, that would need to be provided as well.

The wallet needs to be particularly careful to inform the user about what source(s) of funds are being used, so that they cannot be tricked into sending private funds along to a merchant, when they think they are 'releasing' specific escrowed funds.

It should NOT be encumbent upon users to pre-select a funding account before or during a PaymentRequest. Likewise, a PaymentRequest which does specify a txid:index MUST not allow the user to specify an alternate source of funds -- the only choice should be to reject the request, or fail to sign if no corresponding key is availble.

Therefore, if not an API to signraw, then in this case you would need at least an optional parameter to PaymentRequest which I'll call 'Inputs' which is a set of txid:index, and optionally, a scriptSig for each input.

Use Case #2 - Two Party Escrow with Security Deposit

Overview

An escrow with security deposit requires funds from the payer as well as the payee to be paid into the 2-of-2 multisig, to increase the incentive for the payee to reach an amicable resolution in the case of disputes. Basically it gives the seller some 'skin in the game' so they have some incentive to negotiate in the case where a buyer believes they are due a full refund.

Note: There are certainly other ways for a seller to put 'skin in the game', but none (IMO) so direct and obviously compelling as a security deposit in the escrow.

Funding

To fund a P2SH with a 2-of-2 redeemScipt where the funds must come from two different wallets is a tricky proposition. To construct a transaction which funds the escrow ONLY if all funds are provided by both parties, we need the following:

  1. A pubkey each from Alice and Bob used to create the redeemScript
  2. List(txid:index) from Alice to retrieve funds, and change address to return change
  3. List(txid:index) from Bob to retrieve funds, and change address to return change
  4. Sign a rawtransaction

We have tx.ins from Alice and Bob (potentially multiple for each), we have tx.outs sending correct change back to Alice and Bob, and then finally the tx.out for the P2SH itself.

A security deposit cannot be achieved by two separate payment requests under the current protocol. Alice would refuse to sign a transaction that doesn't also include the required security deposit from Bob!

You could use PaymentRequest to make such a payment, with some minor limitations. You would need to provide Bob's inputs as 'Inputs' and Bob's change as one of the outputs, along with the P2SH output. Alice would add inputs equal to total outputs minus total inputs. In this case 'Inputs' becomes a subset of the required inputs, easily determined since sum of inputs would be less than sum of outputs.

You need to provide all of Bob's payment plan to Alice ahead of time in order for Alice to use 'SIGHASH_ALL' on the amount of change that would need to be sent back to Bob. Unfortunately it's impossible to use 'SIGHASH_SINGLE' in this case, because each signature would actually need to cover, at a minimum, two of the outputs (the amount going to escrow, and the amount going to their own change).

Start with...

     Inputs:
        X BTC from Bob {txid:index ...}  
     Outputs:  
        5 BTC to Escrow P2SH with RedeemScript
        X' BTC to Bob (change)

Then Alice's wallet would internally pick her addresses to pay from, add the inputs, add an output if needed to return any change back to her, and sign. The Payment response sent to the receipt_url would include the partially signed transaction. We could then create a new PaymentRequest which includes Alice's signatures in the scriptSig of Alice's input(s), and send it off to Bob.

The 'minor limitation' I mentioned before, is that if you're letting Alice's wallet pick her inputs and automatically handle any change, then Alice's wallet also ends up choosing how much fees to pay. Bob may refuse to sign if Alice skimps on fees by the time he see the tx. It might be nice for Alice & Bob to be able to negotiate the fee amount ahead of time, and spec them into the PaymentRequest.

Again, all this is transparent if the PaymentRequest can just take in rawtransaction, and give back rawtransaction -- essentially a signrawtransaction operation.

New Message Types

We need separate message flows to support getting pubKeys (deterministic or otherwise), and getting a payment plan. I think they would look like this under the proposed framework...

Public Key Request

        message Pubkey {
            required bytes pubkey = 1
            optional bytes chaincode = 2
        }

        message PubkeyRequestDetails {
            required uint16 type = 1
            optional uint16 count = 2 [default = 1]
            required string callback_url = 3
            required uint64 time = 3;
            optional uint64 expires = 4;
            optional string memo = 5
        }
        
        message PubkeyRequest { 
            optional uint32 pubkey_request_version = 1 [default = 1];
            optional string pki_type = 2 [default = "none"];
            optional bytes pki_data = 3;
            required bytes serialized_pubkey_request = 4;
            optional bytes signature = 5;            
        }
        
        message PubkeyResponse {
            repeated Pubkey keys = 2;
        }

The request includes public key type (0 = 33 byte compressed standard pubkey, 1 = determinstic), and count - a number of Pubkeys to return.

Each 'Pubkey' in the response includes a 33 byte 'pubkey' (0x02 or 0x03 + X).

  • If it is a standard type, then chaincode is not set.
  • If it is deterministic pubkey;
    • pubkey is public key corresponding to an m/x/0 external wallet chain
    • chaincode is 32 bytes
    • public keys are then generated by using HMAC-SHA512(chain, pub || i), starting with i=0

The particular 'x' used to select the wallet chain is chosen internally by the wallet, and not disclosed in the response. The wallet would also keep i2 = 0, in other words, use the external chain.

Question: Do we want to allow a public key request to indicate that the keys will be used for change, in which case the wallet would return a wallet chain from m/x/1 ? However BIP32 calls this an "internal" wallet chain.

I think you would also ignore the 'count' field if type is deterministic.

The callback url would typically be some base URL plus a chunk of random data to uniquely identify and secure the response. The callback url must not be guessable by an attacker, or else they could try to provide their own pubkeys to the callback.

I'm not sure if we really need time and expires for this use case.

Payment Plan Request

        message Input {
            required uint16 vout = 1
            required bytes txid = 2
        }

        message InputRequestDetails { 
            required uint64 amount = 1
            required string callback_url = 2
            required uint64 time = 3;
            optional uint64 expires = 4;
            optional string memo = 5
        }

        message InputRequest { 
            optional uint32 input_request_version = 1 [default = 1];
            optional string pki_type = 2 [default = "none"];
            optional bytes pki_data = 3;
            required bytes serialized_input_request = 4;
            optional bytes signature = 5;            
        }
        
        message InputResponse {
            repeated Input inputs = 2;
        }

The request includes the amount which must be covered by the inputs, and a callback url. I'm not sure if we really need time or expires for this use case.

Payment Request - New Parameters

The Input Type which was defined above:

        message Input {
            required uint16 vout = 1
            required bytes txid = 2
        }

Add Optional 'redeemScript' to Output:

        message Output {
            optional uint64 amount = 1 [default = 0];
            optional bytes script = 2;
            optional bytes redeemScript = 3
        }

Add Optional 'Inputs' to Payment Details: (can I use 'repeated' here to mean '0 or more'?)

        message PaymentDetails {
            optional string network = 1 [default = "main"];
            repeated Output outputs = 2;
            repeated Input inputs = 3
            required uint64 time = 4;
            optional uint64 expires = 5;
            optional bool single_use = 6 [default = true];
            optional string memo = 7;
            optional string receipt_url = 8;
            optional bytes merchant_data = 9;
        }

The only problem left from the use cases above is of course, you can make it easy for Alice to sign, but then you're back to a manual process to get that signature to Bob. So let's just add one last thing...

Add optional 'scriptSig' to Input:

        message Input {
            required uint16 vout = 1
            required bytes txid = 2
            optional bytes scriptSig = 3
        }

At this point, we've just effectively implemented 99% of signraw... :-)

Payment and PaymentAck

The 'Payment' message includes 'repeated bytes tranaction' which is described as "One or more valid, signed Bitcoin transactions that fully pay the PaymentRequest".

This requirement should be relaxed -- transaction bytes may contain partially signed transactions.

It would be good to be able to signal the merchants intent to continue gathering signatures and ultimately transmit the transaction, since returning 'accepted = false' and then later transmitting the transaction is counterintuitive, and would probably result in the wallet giving the user the wrong impression, but returning 'accepted = true' implies the transaction has been sent, which is not right either.

There may be some benefit to adding an additional boolean to PaymentAck so the merchant can return something like 'partialAccept = true' to acknowledge that signatures were received, and payment will be transmitted once remaining signatures have been collected.

Payment Plans (And Thoughts on HASHTYPE)

I don't really like the idea of asking the user for 'payment plans' because it's sounds too much like asking for a payment, but you can't support some desireable transactions without a way to basically 'listunspent' through an API.

Ultimately it's a tricky problem brought on by an inflexible SIG_HASHTYPE. When Alice signs, all she cares is that the total output to escrow includes all expected funds (hers and Bobs), and that she gets the right amount of change. It's hard to encode this logic within a single byte of SIG_HASHTYPE in a way that's not total hackery.

The only thing I can come up with is a new HASHTYPE type that would work similar to SIGHASH_SINGLE but it would also cover any unmatched outputs. In other words, the signature for tx.in[i] includes output at tx.out[i], and ALSO includes any outputs where 'index' >= tx.ins.length.

Each subsequent signer inserts their tx.ins and tx.outs at the front of the array, keeping the shared output at the end of the tx.outs[tx.outs.length-1]. If any signer has more inputs than outputs, they need to insert 'placeholder' outputs with an amount of 0, so that they maintain the same number of outputs to keep things lined up. If an output has a tx.out.value of 0, then it's NOT covered by the signature.

A wallet would know implicitly when they must use the new hashtype -- any time the sum of outputs is greater than the sum of inputs, it's an obvious sign that someone else will be adding inputs later, so the wallet would use this new hashtype, and include it as the last byte in the signatures that it adds. If you're signing an otherwise complete transaction (inputs == outputs) the wallet would use SIGHASH_ALL, which works fine in that case.

Is it worth all the damn complexity just so multiple payers can independently sign for a group payment with less a priori knowledge? Well, I guess that would come down to what's the net positive benefit (or cost) to Bitcoin economy for having that feature. I'm in no position to judge, but such a BIP could be in theory be written, discussed, and voted on. There's the added cost in terms of storage in the placeholder transactions, but no added CPU cost for the feature. Mostly there's the code complexity to handle a new SIGHASH code, and all associated costs of developing, testing, and deploying that code. In short, I wouldn't expect to see this anytime soon, and hence would very much like to see a standardized way to essentially 'listunspent for >= X BTC' through a URI.

Bulk Operations

It's an implementation detail whether multiple operations can be 'sequenced' or 'bulk executed' through the Payment Protocol, but I believe an option should exist. For example, Bob signing 100 transactions with private keys at the end of the day. If we want users to keep their keys private from the 3rd party services, and we absolutely do, we can't expect merchants to click through these transactions one-by-one.

@gavinandresen
Copy link

You're a step ahead of the payment protocol.

See https://gist.github.com/gavinandresen/4039433 for my thoughts on multidevice wallets; I need to focus on getting the payment protocol done, then the next big issue is solving the messaging issue (how do Alice and Bob coordinate if they are not both online at the same time?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment