lbryio / lbryio/proposals

[Proof of Purchase] New buy/sell ops

Open
#6 10 comments 0 reactions 0 assignees View on GitHub
preliminary
Dominant language
No language data
Stars
1
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Initial issue and discussion: https://github.com/lbryio/lbrycrd/issues/184

# Problem Statement
One of the core features of LBRY is buying and selling content. There needs to be first-class support for this activity at the blockchain level so that anyone building services that stream paid content, provide digital rights management or otherwise make purchsed conent available to users can quickly and efficiently verify that a specific user (public key) has access to a specific piece of content (claim_id and optionally claim_version).

# Requirements

- First Class Support: Determining proof of purchase should not be done by convention but rather there should be one obvious and undisputable way that the blockchain records a purchase tx containing the necessary metada to validate the purchase.

- Efficient: A purchase TXO should have all of the metadata necessary for a content gatekeeper to validate that a specific user (public key) has access to a specific piece of content (claim_id).

- Synchronized: Making a purchase on one device should automatically reflect on all of the users other devices.

- Recoverable: Recovering wallet from a seed should also recover all purchases.

# Use Cases

- Content creator can list their content for sale at a specific price.

- Buyer can purchase content at or above the specific price.

- Content creator and a specific buyer or class of buyers can negotiate an agreement offchain wherby the content creator will list their content for sale at a specific price or no price locked by a signature check. When buyer decides to purchase they would make the transaction, send it to seller to be signed, then broadcast it to the blockchain. If the signature matches, buy is allowed and the proof of purchase is recorded on the blockchain.

- Content creator can cancel their sale (abandon the UTXO representing the sale), all future buys will be rejected by the blockchain.

- Buyer can purchase content to be redeemed by someone else.

- Proof of purchase owner can produce a reference to the TXO detailing their proof of purchase (txid:nout) and verify they are the owner of it (they have the public/private keys referenced by pubkey hash in TXO) and then gain access to the purchased content.

- Associate price with an exchange rate.

# Solution No. 1

Introduce four new opcodes to the lbrycrd scripting language which together address all of the requirements:

`OP_SELL_CLAIM` = 0xb8
Meta data opcode representing a claim being available for sale.
`OP_BUY_CLAIM` = 0xb9
Meta data opcode, when accepted into blockchain, represents a proof of purchase.
`OP_PRICE_PAID` = 0xb0
New value opcode which pops the amount paid onto the stack.
`OP_CLAIM_INTEGER_VALUE` = 0xb1
New value opcode which pops the payload of a referenced claim onto the stack.

The two new metada opcodes each form new transaction types following the same structural pattern as the existing `CLAIM`, `UPDATE` and `SUPPORT` transaction types:

`META_OP_CODE ... OP_HASH160 OP_EQUAL`

The new `OP_PRICE_PAID` opcode works in conjunction with a comparison opcode to verify that the price paid is correct, eg:

`OP_PRICE_PAID 1000000 OP_GREATERTHANOREQUAL OP_VERIFY`

Will check that the buyer provided at least 1000000 dewies in the purchase output.

The new `OP_CLAIM_INTEGER_VALUE` opcode works as a multiplier for the base price, eg:

`OP_PRICE_PAID OP_CLAIM_INTEGER_VALUE 10 OP_MUL OP_GREATERTHANOREQUAL OP_VERIFY`

Will replace ` OP_CLAIM_INTEGER_VALUE` with the integer payload of the referenced claim and then multiply that by `10` to provide the publisher's dynamic LBC price.

## Variables & Terms Used

Term | Definition
---:|---
`` | Hash of the `txid:nout` of the first TX which created the claim. When used in `OP_CLAIM_INTEGER_VALUE` it points to a claim containing an integer as the payload.
`` | For a claim with no updates this will equal the `claim_id`, for subsequent claim updates it will be a hash of the `txid:nout` of the TX containing the specific claim update.
`` | Lbrycrd script defining the sales contract requiremnets a buyer must meet in order for the proof of purchase to be recorded on the blockchain.
`` | Standard bitcoin pay-to-script-hash script which is hash160'd and becomes the payment address, in this proposal the `receive_script` is specifically the script to which purchase payments are supposed to go (can be made optional by seller).
`` | Hash of the `txid:nout` of the sell script.
`negotiation_signature` | (optional) Signature of the buyer transaction by the seller, verified by the ``.
`` | When presenting the proof of purchase to download content, the content gate keeper will verify ownership using this public key hash.
`` | Price in dewies attached to the `SELL` script and used by `OP_PRICECHECK` opcode.
`` | Ignore for the purpose of this proposal, it's just the standard pay-to-script-hash stuff which allow spending the various meta UTXOs (aka "abandoning").

## OP_PRICE_PAID

`OP_PRICE_PAID` replaces the op code with the value of the `BUY` output, allowing the `SELL` script to verify that the buyer has provided enough LBC to complete the purchase.

## OP_CLAIM_INTEGER_VALUE

`OP_CLAIM_INTEGER_VALUE` requires a single argument, a ``, which must point at a claim where the payload of the claim is a simple integer. This supports using a claim as an exchange rate provider.

## OP_SELL_CLAIM

`SELL` transactions contain a `` being sold and a `` which programmatically defines the requirements for a `BUY` to be accepted and also the `` in order to be able to validate that purchases are sent to the correct scripthash (address).

When the content creator wants to sell something, they might create the following output script:

`OP_SELL_CLAIM OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL`

The embedded `` could be the following serialized script:

`OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL OP_VERIFY`

Once this `SELL` output is on the blockchain, buyers can start making `BUY` outputs referencing this SELL.

## OP_BUY_CLAIM

`BUY` transactions contain a `` referencing the `SELL` which will validate this `BUY` and then the various values forming the proof of purchase ``, ``, `` and ``:

`OP_BUY_CLAIM OP_2DROP OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL`

If lbrycrd recieves this TX and validates it to be correct (described later) it means the sale has been successful and now this `BUY` output is the proof of purchase which can be redeemed when attempting to download content (along with proof that the `` belongs to redeemer).

## Validation & Buying

Lbrycrd would implement two processing stages, one for processing the initial `SELL` transaction and then each of the `BUY` transaction referencing that `SELL`.

### Validating OP_SELL_CLAIM

1. Find the associated claim. If not found, reject. (Can't sell claim which doesn't exist.)
1. Check if the address holding the claim (or latest update of the claim) is also an address in one of the inputs to the current SELL transaction. If not, reject. (Can't sell claim that's not yours.)
1. Hash the `txid:nout` of the SELL output and place it into a lookup of `SELL`s.
1. Place transaction in mempool.

### Validating OP_BUY_CLAIM

1. Lookup the `` in lookup table. If not found, reject. (Can't buy something that's not for sale.)
1. Extract the ``, `` and `` from the `SELL` output.
1. Concatenate the various script parts and variables into a final validation script (described later in *Executing Sale*).
1. Execute script in lbrycrd script interpreter. If output is false, reject. (Buyer is not paying to the correct address, doesn't have the correct amount or some other reason.)
1. Place transaction in mempool.

### Executing Sale

In order to validate and add the proof of purchase to the blockchain, lbrycrd has to gather both the `SELL` script and the `BUY` script and concatenate them in a specific way, then execute the script and finally if the result is true allow the TX and if the result is false reject it as invalid.

The standard `SELL` and `BUY` transactions do not vary in structure and the rest of this proposal uses the structure defined earlier. The `` does change and is up to the merchant to define the requirements for a successful sale, the rest of this section uses the following ``:

`OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL OP_VERIFY`

With that out of the way let's construct the proof of purchase script:

Step | Part
---:|---
Add the `` from both `BUY` and `SELL` scripts and an equality check. | ` OP_EQUALVERIFY`
From the `BUY` script, skip `` and `` (already added) and add all of the other values. | ` `
Add the `` from `SELL`. | ``
Entire `BUY` script. | `OP_BUY_CLAIM OP_2DROP OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL`
Deserialize and add the `` | `OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL OP_VERIFY`

All of the above parts concatenated together results in the following final script:

` OP_EQUALVERIFY OP_BUY_CLAIM OP_2DROP OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`

Lbrycrd script interpreter would now execute the script as follows (assume the value of the output is 1 LBC and the price of the claim is also 1 LBC):

Stack | Script
---:|---
[empty] | ` OP_EQUALVERIFY OP_BUY_CLAIM OP_2DROP OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 1 | Push the buy and sell `claim_id`s onto the stack.
` ` | `OP_EQUALVERIFY OP_BUY_CLAIM OP_2DROP OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 2 | Compare them, then fail if not equal, pop both values off stack if equal.
[empty] | ` OP_BUY_CLAIM OP_2DROP OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 3 | Push next values onto stack.
` OP_BUY_CLAIM ` | `OP_2DROP OP_2DROP OP_2DROP OP_HASH160 OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 4 | Perform all of the drops.
` ` | `OP_HASH160 OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 5 | Hash160 the `` -> `` (converts it in-place).
` ` | ` OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 6 | Push the other `` onto stack for comparison.
` ` | `OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 7 | Compare the two hashes with `OP_EQUAL` and then drop them if they are equal with `OP_VERIFY`. *This tells us that the BUY payment went to the correct scripthash.*
` ` | `OP_DROP OP_DROP OP_DROP OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 8 | This is where a merchant can check the `` or the `` if they only want to sell a specific version of the claim or if they only want to sell to specific pub key holder. If they are doing offchain negotiation they can do something with the `` to validate it further. For this example we just ignore/drop all three values.
[empty] | `OP_PRICE_PAID OP_GREATERTHANOREQUAL`
step 9 | Push the price of the output onto the stack.
`100000000` | ` OP_GREATERTHANOREQUAL`
step 10 | Push `` onto the stack.
`100000000 ` | `OP_GREATERTHANOREQUAL`
step 11 | Price paid is 1 LBC and price expected is 1 LBC therefore the greater than or equal to check passes.
`true` | [empty]

If the final outcome is `true` then the `BUY` is recorded onto the blockchain and is now a valid proof of purchase.

Adding exchange rate support via `OP_CLAIM_INTEGER_VALUE` is also straightforward; to avoid too much repetition we'll re-use the previous execution steps but pick up after step 8 with a sale script that includes the `OP_CLAIM_INTEGER_VALUE` op code. For this script, the buy value is `220000000`, the `` points to a claim with payload of `110000000` and the `` is `2`.

Stack | Script
---:|---
[empty] | `OP_PRICE_PAID OP_CLAIM_INTEGER_VALUE OP_MUL OP_GREATERTHANOREQUAL`
step 9 | Push the price of the output onto the stack.
`220000000` | ` OP_CLAIM_INTEGER_VALUE OP_MUL OP_GREATERTHANOREQUAL`
step 10 | Push the payload of the claim pointed to by ``.
`220000000 110000000` | ` OP_MUL OP_GREATERTHANOREQUAL`
step 11 | Push `` onto the stack.
`220000000 110000000 2` | `OP_MUL OP_GREATERTHANOREQUAL`
step 12 | Multiply the exchange rate by the sell price.
`220000000 220000000` | `OP_GREATERTHANOREQUAL`
step 12 | Price paid is 2.20 LBC and price expected is 2.20 LBC therefore the greater than or equal to check passes.
`true` | [empty]

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with Solution No. 1, then read the Validation & Buying and Executing Sale sections to understand the proposed opcodes and transaction flow. The payload names no repository files or tests, and completion would require defining and implementing the proposal across the blockchain and script-validation paths.

Written by the indexing model from the issue text.

Assessment

Domain
blockchain
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.