Open eukreign opened 6 years ago
We had a discussion on this topic today. Notes: Additional Requirements:
Open Questions
I wanted to add to this discussion the BIP70 requirements:
And additional requirements from feedback on that:
In your above proposal, you suggest that claims providing exchange rates represent a "single integer". I was picturing a table, something like this: LBC/USD: 0.0001 LBC/EUR: 0.0002 ... I think real numbers are necessary, and I'm not sure what I gain by having each currency base pair in its own claim. Well, I suppose what you gain is the ability to store the data on the blockchain without the currency identifier (e.g. "USD"). I think someone viewing the blockchain would want the currency ID in visible, though.
The reason for the single integer per claim is to keep it simple in the lbrycrd implementation. If you have tables then this means a lot of extra work in validating it and figuring out how to deal with edge cases. If you have one claim per exchange rate and the payload is an integer then you don't have to parse anything and validation is super simple.
Table approach would require adding a new data format specification that needs to be documented and implemented and tested, etc. Using an integer as payload doesn't require any of that.
Another problem with table is that now you have to reference not only the claim containing the table but also which exchange rate in that table should be used.
26 bytes per buy OP puts a maximum of 80k buys per 2MB block (leaving a little extra for other OPs). If blocks continue at one every 2.5 minutes, that gives us 500-ish buy transactions per second. We have a few different payment models:
If you had 100M users (40M less than Netflix) and they each streamed one new piece of content per day, that would lead to 1100 new streams per second. Hence, option 3 is not feasible with this plan.
<negotiation_signature>
from the buy transaction?1. Can we increase block size?
Yes, but there are negative consequences.
- Can we reduce time between blocks? Not much.
- Can you paste the TX example that you used to come up with the 26 bytes? Looking at it again I realized it was 26 bytes for OP_PRICE_PAID, not OP_BUY_CLAIM. The latter seems to take 150+ bytes?
- Would it be worth dropping some features to reduce size? For example, dropping the
<negotiation_signature>
from the buy transaction? Each sig or hash we can drop would definitely increase the throughput.
This seems largely reasonable to me.
I think this comment by @BrannonKing is one of the biggest problems/downsides, but this problem would presumably be roughly the same under the current model. So while this is a very important problem, unless the additional space required for this proposal is a significant difference, I would say it's not a reason to consider a solution along these lines.
I also think that since any price negotiation of even moderate complexity will have to happen off-chain, we should not be particularly concerned with sell/receive scripts. If these add any significant complexity to the design vs. simply allowing fixed prices, I'd propose excluding these from the first iteration.
I don't think the purchase price/transaction is as important as recording the right to access the content.
What about something like this:
It's Craig Wright, but relevant “Storing IP on the Blockchain” by Craig Wright (Bitcoin SV is the original Bitcoin.) https://link.medium.com/qfr4a3mtPT
For this to work as you spec'd it, each publish would need to be unique for every user, which doesn't sound feasible.
Sharing your wallet or decryption key could still lead to sharing/stealing content.
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
andSUPPORT
transaction types:META_OP_CODE <value_1> <value_n>... OP_HASH160 <redeem_script_hash> 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 <claim_id> OP_CLAIM_INTEGER_VALUE 10 OP_MUL OP_GREATERTHANOREQUAL OP_VERIFY
Will replace
<claim_id> OP_CLAIM_INTEGER_VALUE
with the integer payload of the referenced claim and then multiply that by10
to provide the publisher's dynamic LBC price.Variables & Terms Used
<claim_id>
txid:nout
of the first TX which created the claim. When used inOP_CLAIM_INTEGER_VALUE
it points to a claim containing an integer as the payload.<claim_version>
claim_id
, for subsequent claim updates it will be a hash of thetxid:nout
of the TX containing the specific claim update.<sell_script>
<receive_script>
receive_script
is specifically the script to which purchase payments are supposed to go (can be made optional by seller).<sell_id>
txid:nout
of the sell script.negotiation_signature
<sell_script>
.<owner_pubkey_hash>
<price>
SELL
script and used byOP_PRICECHECK
opcode.<redeem_script_hash>
OP_PRICE_PAID
OP_PRICE_PAID
replaces the op code with the value of theBUY
output, allowing theSELL
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<claim_id>
, 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<claim_id>
being sold and a<sell_script>
which programmatically defines the requirements for aBUY
to be accepted and also the<receive_script>
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 <claim_id> <sell_script> <receive_script> OP_2DROP OP_2DROP OP_HASH160 <redeem_script_hash> OP_EQUAL
The embedded
<sell_script>
could be the following serialized script:OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL OP_VERIFY
Once this
SELL
output is on the blockchain, buyers can start makingBUY
outputs referencing this SELL.OP_BUY_CLAIM
BUY
transactions contain a<sell_id>
referencing theSELL
which will validate thisBUY
and then the various values forming the proof of purchase<claim_id>
,<claim_version>
,<owner_pubkey_hash>
and<negotiation_signature>
:OP_BUY_CLAIM <sell_id> <claim_id> <claim_version> <owner_pubkey_hash> <negotiation_signature> OP_2DROP OP_2DROP OP_2DROP OP_HASH160 <receive_script_hash> 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<owner_pubkey_hash>
belongs to redeemer).Validation & Buying
Lbrycrd would implement two processing stages, one for processing the initial
SELL
transaction and then each of theBUY
transaction referencing thatSELL
.Validating OP_SELL_CLAIM
txid:nout
of the SELL output and place it into a lookup ofSELL
s.Validating OP_BUY_CLAIM
<sell_id>
in lookup table. If not found, reject. (Can't buy something that's not for sale.)<claim_id>
,<sell_script>
and<receive_script>
from theSELL
output.Executing Sale
In order to validate and add the proof of purchase to the blockchain, lbrycrd has to gather both the
SELL
script and theBUY
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
andBUY
transactions do not vary in structure and the rest of this proposal uses the structure defined earlier. The<sell_script>
does change and is up to the merchant to define the requirements for a successful sale, the rest of this section uses the following<sell_script>
:OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL OP_VERIFY
With that out of the way let's construct the proof of purchase script:
<claim_id>
from bothBUY
andSELL
scripts and an equality check.<claim_id> <claim_id> OP_EQUALVERIFY
BUY
script, skip<sell_id>
and<claim_id>
(already added) and add all of the other values.<claim_version> <owner_pubkey_hash> <negotiation_signature>
<receive_script>
fromSELL
.<receive_script>
BUY
script.OP_BUY_CLAIM <sell_id> <claim_id> <claim_version> <owner_pubkey_hash> <negotiation_signature> OP_2DROP OP_2DROP OP_2DROP OP_HASH160 <receive_script_hash> OP_EQUAL
<sell_script>
OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL OP_VERIFY
All of the above parts concatenated together results in the following final script:
<claim_id> <claim_id> OP_EQUALVERIFY <claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script> OP_BUY_CLAIM <sell_id> <claim_id> <claim_version> <owner_pubkey_hash> <negotiation_signature> OP_2DROP OP_2DROP OP_2DROP OP_HASH160 <receive_script_hash> OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> 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):
<claim_id> <claim_id> OP_EQUALVERIFY <claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script> OP_BUY_CLAIM <sell_id> <claim_id> <claim_version> <owner_pubkey_hash> <negotiation_signature> OP_2DROP OP_2DROP OP_2DROP OP_HASH160 <receive_script_hash> OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
claim_id
s onto the stack.<claim_id> <claim_id>
OP_EQUALVERIFY <claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script> OP_BUY_CLAIM <sell_id> <claim_id> <claim_version> <owner_pubkey_hash> <negotiation_signature> OP_2DROP OP_2DROP OP_2DROP OP_HASH160 <receive_script_hash> OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
<claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script> OP_BUY_CLAIM <sell_id> <claim_id> <claim_version> <owner_pubkey_hash> <negotiation_signature> OP_2DROP OP_2DROP OP_2DROP OP_HASH160 <receive_script_hash> OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
<claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script> OP_BUY_CLAIM <sell_id> <claim_id> <claim_version> <owner_pubkey_hash> <negotiation_signature>
OP_2DROP OP_2DROP OP_2DROP OP_HASH160 <receive_script_hash> OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
<claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script>
OP_HASH160 <receive_script_hash> OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
<receive_script>
-><receive_script_hash>
(converts it in-place).<claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script_hash>
<receive_script_hash> OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
<receive_script_hash>
onto stack for comparison.<claim_version> <owner_pubkey_hash> <negotiation_signature> <receive_script_hash> <receive_script_hash>
OP_EQUAL OP_VERIFY OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
OP_EQUAL
and then drop them if they are equal withOP_VERIFY
. This tells us that the BUY payment went to the correct scripthash.<claim_version> <owner_pubkey_hash> <negotiation_signature>
OP_DROP OP_DROP OP_DROP OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
<claim_version>
or the<owner_pubkey_hash>
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<negotiation_signature>
to validate it further. For this example we just ignore/drop all three values.OP_PRICE_PAID <price> OP_GREATERTHANOREQUAL
100000000
<price> OP_GREATERTHANOREQUAL
<price>
onto the stack.100000000 <price>
OP_GREATERTHANOREQUAL
true
If the final outcome is
true
then theBUY
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 theOP_CLAIM_INTEGER_VALUE
op code. For this script, the buy value is220000000
, the<claim_id>
points to a claim with payload of110000000
and the<price>
is2
.OP_PRICE_PAID <claim_id> OP_CLAIM_INTEGER_VALUE <price> OP_MUL OP_GREATERTHANOREQUAL
220000000
<claim_id> OP_CLAIM_INTEGER_VALUE <price> OP_MUL OP_GREATERTHANOREQUAL
<claim_id>
.220000000 110000000
<price> OP_MUL OP_GREATERTHANOREQUAL
<price>
onto the stack.220000000 110000000 2
OP_MUL OP_GREATERTHANOREQUAL
220000000 220000000
OP_GREATERTHANOREQUAL
true