Discussion on best practices for building anyone-can-spend automata contracts and covenants

Introduction

BitcoinScript and the Bitcoin VM enables a powerful set of stack-based logic to control how (or when) value is spent. In addition, higher-level scripting languages (CashScript & spedn) lower the technical bar for writing contracts that transpile to BitcoinScript. However, the reach of this technology remains relatively limited to a small set of blockchain application developers in the UTXO space, and while it makes sense to develop a complex contract (or set of contracts) for a large application or service (Anyhedge/Jedex), there is a large swath of bespoke financial transactions people typically conduct with a single trusted party (themselves, people they give money to), with zero or very limited & arguments at the time of execution.

Some of this core-traditional market demand is addressed by Electron-cash plugins. (Such as the Last Will Plugin by @Licho , or the Hodl Plugin by Pat.)

However, this approach leads to a number of UX challenges:

  • To create or claim the script, users must install both electron-cash and the plugin.
  • The relevant user(s) must remember the contract existence and execute the unlocking script.
  • In addition to seed phrases, the plugin must store, or derive, the data necessary to unlock the contract, or this data must be transmitted to the receiving parties.

In addition to these issues for simple cases, the actual way people “hard-code” the spending of value with soft-finance contracts and wills is usually a bit more complex than simply hodling, or willing everything to one heir.

Entities often divide value in shares or lays. Parents want to divide in equal fractions. People want equal payments over time. Or, they want to gift money in powerful ways far into the future.

People want to “mine” bitcoin in the browser, and someone will always want free bitcoin for doing little or nothing.

So rather than create a dozen plugins, or large bespoke contracts for everyone, perhaps there’s a better way, today…

Re: Introspection; Hello, Automata!

Since the introduction of introspection, unlocking script has access to both total value and individual input/output values in BitcoinScript. So some of the previous pain points can be resolved by:

  • Publishing the parameters of a “well known” script
  • Accounting for a small fee available to the party (anybody) that executes the script.

The idea being, if the ability to create the unlocking script is well known and incentivized, an economy can develop for the execution of a set of scripts by anyone, for a fee.

  • Users receiving funds may not actually have to call the script.
  • Users can publish the unlocking script, and someone may want to execute it.
  • Since some scripts may not require access to private keys, the security requirements for the user are greatly reduced, as most of the risk is off-loaded to the contract. Which is to say, it may be appropriate to use in a sketchy context, like a web-browser.

In the example of a time lock script, if a time locking script were written to leave some small fee (say 1500 sats) as extra output, then someone could call that script to claim the fee. The funding transaction, or any transaction, could publish the serialized parameters referring to the script in an OP_RETURN, and some party could be paid to track and execute it in a timely manner. Failing that, the original user would have a record (on-chain) and could call the contract over a web page, service or cli, without installing desktop software or plugins (because it doesn’t need keys).

With these simple contracts, more complex functionality could be emulated by chaining the output of contracts into others. In the case of a Will contract, it’s quite possible that someone has more than one heir. It also isn’t always appropriate to give people of a certain age an entire inheritance, or to give an inheritance all at once.

A simpler example: just divide input(s) in half*

For a concrete example of an anyone-can-spend contract, suppose we implement the above contract to divide any input across two addresses, mostly equally.

First, lets account for both the mining fee and the executor fee with an executorAllowance. This is the total amount outputs are reduced by, and it is presumed that the executor has accounted for the required miner fees out of their own end. Without loops, it’s expedient to have the divisor hardcoded as input to the contract. The function execute() takes no inputs and simply assures the receipts are correct, and the amounts exceed half the initial total, minus the executorAllowance.

pragma cashscript >= 0.7.0;

  contract Divide(
      // allowance for party executing the contract
      int executorAllowance,
      // number of outputs receiving payout
      int divisor,

      // for each beneficiary, take the LockingBytecode as input
      bytes r0LockingBytecode,
      bytes r1LockingBytecode
  ) {
      function execute() {  // no args!

        // distributes to each output in order
        require(tx.outputs[0].lockingBytecode == r0LockingBytecode);
        require(tx.outputs[1].lockingBytecode == r1LockingBytecode);

        // Get the total value of inputs
        int currentValue = tx.inputs[this.activeInputIndex].value;

        // Total value paid to beneficiaries, minus executor allowance
        int distributedValue = currentValue - executorAllowance;

        // Value paid to each beneficiary
        int distribution = distributedValue / divisor;

        // each output must be greater or equal to the distribution amount
        require(tx.outputs[0].value >= distribution);
        require(tx.outputs[1].value >= distribution);
      }
  }

So the trick here is that outputs beyond the first two are unrestricted. So anyone who submitted a transaction could claim the 1200 sats, minus the miner fee. An executor could call the CashScript function above and pay themselves by tacking on their address and desired payment to the third output, like so:

    // ... instantiate the CashScript contract, from artifact, parameters, provider, 
    // ... estimate fee, calculate installment, etc. 
    // ...
    let fn = divideContract.functions["execute"]();
    let to = []
    to.push({ to: heirs[0].getDepositAddress(), amount: installment  })
    to.push({ to: heirs[1].getDepositAddress(), amount: installment  })
    
    to.push(
      { to: executor.getDepositAddress(), amount: exFee - estimatedMinerFee - 2 }
    )
    
    await fn
      .to(to)
      .withoutChange()
      .send();

Note the above example does not strictly require the outputs be equal, nor exactly the minimum share.

Serialized examples: divide by four

In the case of a Divide contract among four inputs instead of two, the contract may be serialized for a keystore database (in a string of delimited text) as follows:

D,     // Type (Divide)
1,     // version
1200,  // executor/miner fee
a914cb75efdfd51ba11b81f2c7986dca8c3b78174f2487, // receipt 1
a9144ea5ce167a9656a601790eabca1e1165b1de9f1487, // receipt 2
a91496e199d7ea23fb779f5764b97196824002ef811a87 ,// receipt 3
a91405b471c045278ddc670a19415a47005929eb37c987, // receipt 4
a9141538fb59b073fbad92490eae961a12d542872f9a87  // contract lockingBytecode

As an OP_RETURN, the same announced contract might look like so:

6a           // 106 OP_RETURN
04 7574786f  // random protocol identifier
01 44        // Divide
01 01        // Version 1
02 b004      // 1200 sats allowance
17 a914cb75efdfd51ba11b81f2c7986dca8c3b78174f2487 // r0
17 a9144ea5ce167a9656a601790eabca1e1165b1de9f1487 // r1
17 a91496e199d7ea23fb779f5764b97196824002ef811a87 // r2
17 a91405b471c045278ddc670a19415a47005929eb37c987 // r3
17 a9141538fb59b073fbad92490eae961a12d542872f9a87 // check, locking bytecode

An interested party could check the unlocking bytecode here and execute the contract by dividing inputs (if any are available) while paying themselves the executor fee.

Complex functionality by chaining simple contracts

As a typical example of what someone may want, suppose instead of one heir, there are two heirs (age 12 and 15) who may, or may not, be very good with money, (we can’t tell yet). While it should be possible to create a set of Locking and Will contracts to pay them out over time in the absence of the contract funder, that may get somewhat tedious.

Let’s approximate the functionality of a Will and some Trusts with BitcoinScript. Here’s what we use instead:

  • A Will contract, which:
    • pays to itself or the funder if signed by the funder signature in some timeframe
    • or pays to a divide contract.
  • The Divide contract splits any input equally (roughly) among two addresses,
  • One is a Lock contract for 8 years, the other Locks for 5 years.
  • Each Lock contract then pays a Perpetuity, (or Annuity), which distributes funds to an heirs address (at a later time) over some interval of about 15 years.

The same functionality could be approximated by using ~30 Will contracts paying ~30 Lock contracts, but it’s simpler to use 1 Will, 1 Divide, 2 Time Locks and 2 Time Distribution contracts.

In pseudo yml code, as follows:

Will:
    exFee: 3000                             // 3k fee, after expiration 
    period: 24000                           // six months
    signature: <pk, sig>                    // principal's keys
    payTo:  
        Divide:
            fee: 3000                       // 3k fee
            payTo:                          // dividing remainder
                - Lock:
                    fee: 3000               // 3k fee
                    period: 384000          // 8 years
                    payTo:
                        Annuity: 
                            fee: 2000       // 2k fee, for
                            period: 4000    // monthly payments
                            amount: 100000  // of 100k
                            payTo: lock1    // to heir 1
                - Lock: 
                    fee: 3000               // 3k fee, for
                    period: 240000          // Lock for 5 years
                    payTo:              
                        Annuity:
                            fee: 2000        // 2k fee, for
                            period: 4000     // monthly payments
                            amount: 100000:  // of 100k
                            payTo: lock2            // to heir 2

Considerations & Observations

With a set of small scripts using introspection (that are reviewed and thoroughly tested) a larger userbase could gain access to more complex functionality with more reliability than if they had to commission or implement a single large script and test it on their own dime/time.

However, there are some new considerations for the safety and generally tidiness that should probably have wider consideration here.

Collision Attacks

Due to the possibility of collision attacks discussed previously here:

at this juncture, it should be assumed 1) that contracts that accept parameters for unlocking inputs are vulnerable to collision attacks, 2) that the output unlocking bytecode will be public and long-lasting, as well as 3) all data to redeem the script.

Without something like CHIP-2022-05 Pay-to-Script-Hash-32 P2SH32, the functionality of these anyone-can-spend contracts is (and should be) deliberately limited for security reasons.

Therefore:

  • Variables passed to unlocking script should be minimized to the extent possible, ideally zero.
  • Spending conditions should be limited (instead) to the value, number and locking bytecode of transaction inputs and outputs.

Announcing Contracts

Bitcoin Cash sub-protocols have used a four-byte prefix in OP_RETURN identifiers in the past. So known sets of contracts/covenants can be developed to utilize specific OP_RETURN prefixes.

In addition, as a postfix to the OP_RETURN:

  • It may be very expedient for users calling various contracts, (both as a checksum and to check balances) to have the locking bytecode of the contract (P2SH) in the contract announcement. It allows the “would be” caller to determine:
    • If the contract is funded, without instantiating the contract.
    • If they instantiated a funded contract correctly on their side

As some controversy in the memo/member protocol(s) has shown, it may be best to assume that someone will extend the protocol in ways initial developers don’t wish to accommodate, so it might be best not to get married to a random four byte code.

On announcing contracts v. covenants:

  • A Contract with fixed parameters only needs to be announced once,
  • A Covenant with changing parameters, can be made self-announcing. However:
    • If a four byte OP_RETURN space for a protocol is “claimed”, self-announcing covenants shouldn’t share space with fixed contracts. Because covenants may be announced every block, where covenants announcements are used once and discarded.

P2PKH v. PSH v. LockingBytecode

  • If contracts are to be composable, they should always favor asserting inputs & outputs as lockingBytecode over a PublicKeyHash or PublicScriptHash.
  • i.e. If a contract (P2SH) address is passed as output to another contract which enforces or assumes P2PKH outputs, it bricks those inputs forever.

OP_Return length

  • Presently, OP_RETURN is limited to 223 bytes, so if lockingBytecodes are stored in the contract announcement, the number of addresses involved is limited to five (or four plus the contract lockingBytecode shortcut/checksum).
  • Current OP_RETURN size shouldn’t be a limitation with current signature sizes. I.e. If inputs should, be divided by 4, they can be divided to 64 in three more steps―it is not worth a consensus change.

Cleanup the dust as you go

  • If a contract is not configured to utilize fractional satoshis and an ever decreasing DUST_LIMIT, it should NOT leave unspendable outputs on the contracts.
  • Ephemeral contracts should be constructed with a dust collection mechanism and leave zero dust when funds are exhausted, even if those inputs are far below thresholds for the contract.

Dust attacks, selective inputs

  • It will be trivial with a small amount of bitcoin to dust a contract with tiny inputs such that the transaction fee required will exceed the allowance given, so software around protocols might consider enabling UTXO selection and dust collection by default, from launch.
  • Likewise, it should always be assumed, when writing contracts, that the executor may call either all inputs, individual inputs, or some mix of both.
    • As an example: a perpetuity paying daily into a two party divide contract may either be executed as inputs as a lump sum, or as 356 times on individual inputs for the year. If the daily input amount is low relative to the fee on the divide contract, the difference between calling individual inputs and one lump input could be significant.

Insecurity on the network

With the nature of an anyone-can-spend contract, or the unrestricted output component, any party receiving the unlocking transaction may choose to replace the anyone-can-spend output with their own. Alternatively, the mempool can be monitored for anyone-can-spend transactions and any attempt to claim it may be “raced” as a double spend.

In the latter case, a party who put in the effort to query, store and execute transactions in a timely manner could likely be thwarted by someone simply trying to double spend their small reward.

Finally, a miner who implemented software to spend all known anyone-can-outputs in blocks they mine would virtually eliminate any incentive for others to attempt to profit from submitting transactions (in blocks they mine with their hash power).

For the initial funder and ultimate recipient of the contract funds, it’s immaterial whether the reward for executing the contract goes to someone who diligently submitted it, a double-spend cheater, or a sat pinching miner. The end result, that the contract is executed, is the same regardless of the party taking a small fee.

Malleability

Similar to the above venerability to the executor outputs, it may be theoretically possible to generate outputs after the fact which are indistinguishable from the original using immutable checks using block headers, but are in fact not the original executor, rather a different address. However, at the present time, with such low fees, it seems unlikely that generating collisions would be profitable, and every spend of a UTXO would add to the cost of creating a spurious output and having it accepted on the network.

Conclusion

The above topic outlines one view of how to go about enabling complex financial transactions from a set of well-known anyone-can-spend contracts. However these contracts may be disruptive to the network, have unconsidered security considerations or long term maintenance overhead that hasn’t been considered.

So the general idea and some early pain-points are laid out to begin a broader discussion.

2 Likes

Some notes:

  • With introspection you can easily offload the variable part of a contract to a “sidecar” output, so that “main” address will be constant, I use that pattern here. Caveat is that the contract then becomes 2-output contract, which complicates interacting with it since software would have to be designed for that. CashTokens will further simplify this because they’ll let you drop some checks and ID+commitment will be proof enough that the sidecar code is executed against correct inputs/outputs.

  • Once activated CashTokens will make it possible to extract the variable part into the NFT “commitment” field, making it possible to separate contract state from contract code, while keeping it all in 1 output.

  • Using introspection it is possible to emulate OP_EVAL (example), make more complex multi-output contracts, and work around the per-input Script VM limits, but you still can’t do operations that require a bigger program “memory” (like, you still can’t compute a hash of a blob bigger than 520b, and we don’t have lower level hash functions that would let us continue hashing from some intermediate state)

3 Likes

I like the idea of hiding functions (like a Will replacement or refund) in a sidecar.

With the simple patterns above, (lock, divide, etc) I’m finding most “one job” KISS contracts fit nicely in current Script limits, but it’s actually the OP_RETURN limit that determines how complex a setup could be recorded on chain with all the predefined variables. And that may get worse, if P2SH32 locking bytecode is longer.

As is, without CT, I think there are a lot of powerful contracts that are really quite small.

1 Like

You can’t execute it if it lives in OP_RETURN. We don’t have the “real” OP_EVAL. To get executed it must come as “sidecar” P2SH where redeem script will be provided by a data push in unlocking script, which means your app needs to worry about preparing the output and then referencing it inside the TX which authenticates the input against the hash in the “main” contract.

For data storage, you can also use a side-car input and have a full 520b blob. Plenty of options: https://www.researchgate.net/publication/345644650_Data_Insertion_in_Bitcoin's_Blockchain

I’m beginning to feel this could have been an Applications Layer Informational CHIP.

It’s interesting to see all the ways people stored data in the early days, but I think OP_RETURN is the best (and polite) place for the case described above. I’m proposing it could be used to say “This is a time Lock Contract (version #) and it can be sent to this address after X blocks.” where that contract lives in application software external to the network that a user can submit transactions from.

However, since the thing we’re storing is the unlocking script, it’s actually really efficient to put that on the blockchain. It should also be possible to say, “use the real transaction X as an unlocking template, popping the old parameters and substituting these instead”, with an OP_CODE protocol. Then if someone wants to create a different set of automata contracts, it should be possible to step them forward from more generic software if everyone can see, “this is the template transaction, and these are the different parameters”.

1 Like

Hey @tom , following up on the telegram discussion I saw from this morning…

As mentioned in the intro above, there are a number of usability issues with a wallet specific Will plugin. In addition, if heirs must submit the inheritance transaction(s), then the on-boarding issues are extended to a much broader range of people―people that may not be as interested in running special wallets or installing plugins.

While an integrated wallet feature would be better, if the person setting up the Will uses an anyone-can-spend contract, and publishes the parameters, then the heirs may use any wallet they chose without installing a plugin or submitting the payout transaction.

Using a relative timelock, someone could setup a function with the following parameters

Will(int period, bytes32 person, bytes32 heir, int executorFee)

with three functions:

revoke(sig s){...}
renew(sig s){...} //&
inherit(){...}

where inherit automatically could be executed by anyone after the allotted time period has elapsed.

Similar to the cashscript example:

Or your spec:

The reason a Will hasn’t been implemented in unspent.app already is 1) that concern about someone generating address collisions for a large value public contract to “steal” the funds headed to a p2sh20 address.

And 2) none of the v1 contracts transactions are signed in anyway with private keys stored in the browser, because it seemed like a bad idea.

So if someone were to be willing to put their heirs inheritance on a contract, it might be nice to renew or revoke it with private keys from a secure wallet, i.e. one written in c++ maybe.


In your spec, the part the jumped out to me was the “extender” role, which (if a new party) would introduce someone that may essentially hold the funds ransom by continually extending the contract. This wouldn’t be a risk if the extender was only ever the primary party.

1 Like

its honestly just a layman friendly explanation of the idea. Allowing people that don’t understand most of crypto / blockchain to understand what value it adds.

This is likely the biggest reason why we don’t see smart money or scripts used more broadly. The people making the wallets and designing the UX are not the same people that understand or even create the useful scripts.

1 Like

The generic design is called in my document a “timevault”. The one specific implementation of it is the inheritance (or last will) contract. In the specific inheritance contract you want to make sure that the ‘extender’ role is fulfilled by the same person as the one that is the owner. The doc specifically states this.

I can imagine that there will be other usages of the timelock concept where this is not the case. For instance a crypto-based lock for your rental car where the extender is someone confirming you paid the rent.

So in an abstract sense, a p2pkh transaction is also a contract, as are multisig addresses. They’ve been well defined, so wallets implement them and they see broader adoption.

With p2sh, it could be anything. But if there were a catalogue of well-defined scripts, and an OP_RETURN tag to decipher/execute them, then those scripts might also see broader adoption.

Anyhedge is an example of a more advanced but versioned and vetted contract, that multiple wallets can implement.

If all wallets agree on a good “Will” or “Escrow” contract, then everyone could use it without reinventing the wheel. If someone asked about a “dead man” functionality, anyone in the community could say: “This is the latest Will contract, it’s been vetted, everyone uses it.” rather than say, “Well, you can do this in a particular wallet, it was designed by one person, and no one really knows the usage or if it’s safe.”

So with the Unspent.app contracts, they all have the common feature that anyone can execute some unlocking path, and the parameters can be serialized to an OP_Return (if the user chooses to do so).

Would you be interested in a Will contract where the heirs could be paid by anyone after the time-frame expired? So someone could do the high value functions in an app like flowee, but if they died, then the heirs would likely just be paid automatically by someone spending the any-one-can spend path?

1 Like

Just to give a high-level explanation of the “anyone-can-execute” mechanism.
Licho’s contract has the beneficiary “pull” his money from the contract UTXO by himself: the contract requires beneficiary’s signature + checks that the timer has expired.
The “anyone-can-execute” version would kinda be a “push” contract, after the timer expires anyone could spend the UTXO to “push” the money held with it into the pre-determined P2PKH address. Instead of contract requiring a signature from the beneficiary, it only requires (using introspection opcodes) that the TX sends all the money to the correct beneficiary address. Anyone can do the sending - but they can’t change the target address, so money can’t be stolen. The contract can leave out a little “executor allowance”: instead of requiring that the exact same amount is passed on to the beneficiary, it can require a slightly smaller amount so the executor can direct the difference to himself - by adding another output to “capture” the allowance.
From the point-of-view of beneficiary: the money one day magically appears in his P2PKH wallet. The TX would’ve been constructed by either a miner or anyone else “hunting” for these “anyone-can-execute” UTXOs.

1 Like

To add to this, the privacy implication is that the parameters of the contract must be known for “anyone” to release the contract. So the principal would have to publish the parameters for it to be released by “anyone”.

So in the case of a Will, any supposed heir would be able to view the parameters of the Will with a little effort. Or not much effort if someone made a slick UI for it.

However, the parameters could also be kept secret and either published at a later date (by a meat space executor) or used by an heir to release the funds.

Normally the contents of a Will are private.

1 Like

The p2sh concept was invented to make complex scripts fit in an address. Allowing you to use existing p2pkh infrastructure to make complex payments. A sideeffect is that the script is kept secret, which is both an advantage (secrecy) and a disadvantage (more complex wallet backups).

I’d like to explore possibilities in the Flowee Pay wallet, indeed. One way may be to change bchn to accept non-standard scripts into its mempool, because after so many years of using p2sh which provides those exact same scripts to be run on the VM, there is no protection it actually provides for the node or miners. This would store more scripts on-chain and make the UX of something like timevaults much better. Rationale: there are up to 3 people involved and unlocking a p2sh is not a case of just a private key from a HD wallet-seed, you need the script too. And thats just bad UX.

Absolutely, bitcoin (cash) scripts are like javascript in a browser, they are not going to be kept secret for very long anyway. Why not make something like NPM for them. Makes total sense to me.

I’m indeed collecting usecases for a wallet that is more than a wallet. Which is why I called it “Flowee Pay” instead. Implementations of the concept are also very much needed for this.

Idealy we get something better than NPM, though. Which has no quality control, no incompatibility checks between versions and no way to compre competing implementations of the same concept.

Exciting times :slight_smile:

1 Like

While I’m still trying to pick them up… BitAuth Authentication Templates seem like a much more robust and flexible format as opposed to a CashScript contract.

There are variables, state and essentially other data about calling the contracts which aren’t contained or in the scope of CashScript when it was made.

The spec is here:

https://ide.bitauth.com/authentication-template-v0.schema.json

In the future, it may be possible to extend CashScript with some kind of markup to enable a 1:1 translation or interoperability with Auth Templates, but that seems like an issue that could be solved later.

In addition, if a set of contracts were done in CashScript, the reproducibility in time would be at the whims of the transpiler. So if a feature was removed from CashScirpt, transpilation to Bytecode may no longer work. Additionally, transpilation may be different if an optimization is made in CashScript. There also may be more optimal or direct ways to encode something in bytecode directly that CashScript doesn’t support.

The format would be JSON, so it would be more programing language agnostic than a set of contracts in CashScript.

1 Like

Here’s something to think about: in some cases it could be useful to reveal the redeem script in same TX that creates the UTXO. We could define a prefix for this, and it’d just be followed by a single push. Due to the limit it would be usable only for shorter scripts, but still.

We have a defined way to specify OP_RETURN protocols, it’s simply:

OP_RETURN <protocol_ID> <reveal_type> <data1> <data2> ... <dataN>

i.e. the 1st push is 4 bytes of protocol ID, and protocol-specific data follows.

How about we define protocol_ID = 0x50325348 which is ASCII encoding of the string “P2SH”, or 0x70327368 for small caps.
Then, we associate the OP_RETURN with the P2SH output coming after it. For bigger scripts, the OP_RETURN could be pushing an index of an input index which would reveal the script as input data push.

There’s something similar already in use, @2qx 's Unspent Phi hosts the redeem script template, and the OP_RETURN just pushes the contract-specific parameters, quoting a talk on Telegram:

Unspent Phi is using 04 7574786f, (“utxo”)
It’s protocol, contract type (P), contract version (01), parameters, and the locking bytecode as a checksum.

So it’s basically a reference to redeem script template(s) published on Unspent Phi + the contract-specific params needed to reconstruct the hash.

There were some other thoughts I had,

  • if the same type of contract is used repeatedly, the redeem script OP_CODES could simply be a reference to a previous transaction. And only the parameters with that reference need to be published.

  • In Bitcoin Cash now, multiple OP_RETURNs are allowed on a single transaction, so it’s technically possible to store a lot of stuff in short messages as long as the protocol also handles putting them back together.

  • Finally, for a general solution, we’d want to think about the weirdest way someone might want to use a redeem script.

P2SH specification for reference

This is a link to an earlier idea from Karol with Last Will, etc contracts.

1 Like

To add more things to ponder, inspired by @bitjson 's concept of redeem_script_pattern implemented in chaingraph to make searching for contracts easier. His current implementation strips a redeem script from the data but it keeps the bytes that indicate length of push. Also, he keeps the OP_N number pushes. So, I still had to do a regex on hex representation in order to find all instances of the AnyHedge v0.11 contract. Here’s an idea for a similar approach, call it redeem_script_template:

  • replace every sequence of pushes with number of pushes encoded as a Script number push

Examples:

  • 0x5102bebe04beefbeef would be replaced with 0x53 since it encodes 3 pushes in sequence. Jason’s variant would result in 0x510204 since it preserves lengths.
  • 0x76a914{pubkey_hash}88ac (p2pkh template) would be replaced with 0x76a95188ac. Jason’s variant would result in 0x76a91488ac
  • 0x5220{pubkey}20{pubkey}20{pubkey}53ae (2 of 3 multisig) would be replaced with 55ae since it encodes 5 pushes followed by OP_CHECKMULTISIG. Jason’s variant would result in 0x5220202053ae

For scripts where variables don’t vary in size, either way is good, but there are scripts where a variable may vary in size, and with more variables and Jason’s method the possible combinations will result in many distinct patterns for pretty much the same template: {some executable code}{some number of pushes}{some executable code}{some number of pushes}…

So, if we compress the pushes down to just their number, we can match all variations. Then, we could hash the template to get a redeem script fingerprint, an unique reference to a template, and build a database of those or something. I think I’ll build an index of these just to see what’s been mined already :slight_smile:

I agree that would be useful (and I recall bitjson also agreeing on that 6 - 9 months ago).

What I’d like to point out here is that shipping a p2sh hash, which promises to publish the entire script on claim of the utxo, you indeed would need to duplicate the script if you want it to exist on-chain earlier.

But if you simply do not use p2sh but build a simple transaction with a simple non-standard output script, you bypass the need to duplicate the script. It will be stored on-chain inside the actual UTXO. No need to copy it in an op_return.

2 Likes