I've been thinking a lot about how to avoid hard forks in the future, and I might have a solution. It's too early to tell. The problem with backwards compatibility in Haircomb is that it's impossible with a system based on static smart contracts; if a client doesn't have the smart contract for a history, they can't import it. How do you provide new code to a Haircomb client, while also keeping it completely anonymous, and only ever speaking with the BTC chain??
DoThis is a theory demonstration for Do, a programming language that consists of smart contract code hidden within the BTC blockchain. This fork to introduce Do would introduce a new wallet entry /type, /contract. When a wallet runs into the /contract code, it would check to see if it currently had the contract code compiled in its database. If it did not, it would find the contract code, located on the BTC block chain, and pull and compile it. It could then process and understand the transaction. The methodology for storing Do on the BTC chain is as follows.
1. Write a Do contract. The contracts can be written using native functions that are contained within every Haircomb client, such as math functions, SHA256, or GetCommit related functions. They can also be written by referencing other custom contracts that have been created.
2. Get the contract's ID. This is created by concatenating a nonce and the script of the contract.
3. Build the commits necessary to commit the code to the BTC chain. It's easier to show an example than to explain:
ID = SHA256( NONCE if A > B: )
code commits:
SHA256(ID + if + 0)
SHA256(ID + A + 1)
SHA256(ID + > + 2)
SHA256(ID + B + 3)
SHA256(ID + DO + 4)
SHA256(ID + END + 5)
While it is obviously incomplete code, you can see the commit process in action. Concatenate the contract ID, the chunk, and the chunk index, then SHA256.
4. Once the contract commits have been generated, commit them to the BTC chain. They must be committed in index order.
5. When a Haircomb client that does not know this script receives a TX history that contains it, it searches for the location of the contract ID on the chain. This marks the beginning of the contract. Haircomb then begins checking each consecutive P2WSH address in the BTC chain, and for each one it check if the address = SHA256(ID + WORD + INDEX) The ID is the contract ID, the INDEX is the current chunk index, and the WORD is the current keyword that is being checked. It runs this script for each keyword it knows, on each P2WSH, until it finds a correct answer. In the above example it would find that SHA256(ID + if + 0) == currently tested address. Then it adds 1 to the INDEX value, and repeats. It does this until it finds the WORD END, at which point it knows the script has finished.
In order to embed contracts within other contract code, we can't reference them by ID. If we did, Haircomb would never be able to guess them, because their ID is a random 256bit number. Instead, we can reference them using a native function called BuildGetObject(index, args). Index is the index of the contract ID, and args is an array with the arguments used to build that function when the code is actually being processed (I'll explain shortly). Because all Haircomb nodes know BuildGetObject, they know that the first argument of BuildGetObject will be the index of an Object they need to get.
NOTE: During this reading, if you see a reference to another object written during build func as simply something like Object(args), know that in practice it would be committed as BuildGetObject(index, [object_args]). I'll refer to the main script as a Contract, and the thing it creates during processing as an Object. Some rules for Objects:
Objects have variable CONNECT_TO, it is an address, if any, that their funds flow into down the chain
Objects have variable ACTIVE, when active if they are payed funds they call their run(), if inactive they pay the funds to their CONNECT_TO address and skip their run() code.
Objects have an AMOUNT, which is how much COMB they currently have stored. Only relevant in some cases
Objects are built in build code by referencing their Contract ID location on chain, and are run in run code by referencing the individual instance of the built object.
If an Object finishes run() and it has a CONNECT_TO address, it automatically sends all of its funds there.
Some example of native functions might be:
SHA256(content):
returns the hash of content
GetCommitHeight(address):
returns the height of the address, if it exists. If not, return 0
GetCommitIndex(address):
returns the index of the address if it exists. If not, return 0
GetCommitsByHeight(height, height):
returns all unseen P2WSH in between the two heights
GetCommitsByIndex(index, index):
returns all unseen P2WSH in between the two indices.
PayTo(dest, amount):
Subtracts amount on funds and puts them in the destination. If the dest.AMOUNT < amount, this function will NOT fire. Not needed on
standard, 100% payments, just use CONNECT_TO
BuildGetObject(index, [args]):
Gets the ID of the references contract, and if it isn't already compiled in storage, compile it. Then build with it. Store with its index.
A bunch of Math functions (+, /, >, etc.)
Note: This next part I'm sketchy about, as I don't know if Haircomb uses temporal methodology like I have laid out here, or if it uses an algorithmic, atemporal process. If it does though, I have 0 idea how it deals with liquidity stacks then, but if I can figure it out I can easily convert this into an atemporal system. A temporal system feels like it'd be super slow, so I'm hoping I'm wrong hereWhen a wallet is processing a TX history, first it initializes the all the transactions in the history as objects. Next, it spawns the combbase. As the comb trickles into an object, it will trigger the run(x) function, where X = the amount of comb that has entered the object. Once the object's run() has completed, if the comb can trickle further it will, trigger more run(), until finally it cannot trickle any more. Then the next combase will trickle, and so on, until no comb can trickle further.
I have built some already existing and or theorized Haircomb contracts to show an example. When I use the syntax struct, I am referring to an array of values that is created directly from the build() function arguments. Struct as in structure.
1. Liquidity Stack
build(int256, int256, int64) #change_add, send_add, send_threshold)
run(int64):
AMOUNT += run[0] # Increase stored amount
if AMOUNT > struct[2]: # If stored > threshold
execute([struct[0], struct[1], struct[2], AMOUNT]) # send COMB
execute(args):
PayTo(args[1], args[2])
ACTIVE = FALSE
CONNECT_TO = args[0]
2. Hot Cash Address (Decider is built with left_object, tip1, tip2)
build(int256, Decider.build(int256, int256, int256), [int16, int256, int256]) # Fallback, decider(), longdecider, unCAT
run(int64):
AMOUNT += run[0]
if struct[1].run(struct[2]): # If signature checks out
if struct[2][0] == 0:
execute(struct[0]) # If signed 0, fallback
else:
execute(struct[2][0]) # Go forward
execute(args):
CONNECT_TO(args[0])
ACTIVE = FALSE
3. Lock
build(int256)
run(commit): # commits are sent as [commit, index]
if SHA256(run[0][0]) == struct[0]: # If key
execute([TRUE])
execute(args):
return args[0]
4. TimedLockbox
build([int256, int256], [Lock.build(keyhash), Lock.build(keyhash)], int32, int32))
run(int64):
AMOUNT += run[0]
for commit in GetCommits(struct[2], struct[3]):
if struct[1][0].run(commit[0]): # Run NO Lock with commit[0], if NO key found
for this_commit in GetCommits(commit[1], commit[1]): # Check for YES in rest of block
if struct[1][1].run(this_commit[0]): # If YES in same block
execute(struct[0][1]) # YES
else:
execute([struct[0][0]]) # NO
elif struct[1][1].run(commit[0]): # Run YES Lock with commit[0], if YES key found
if not commit[1] > struct[2]+1): # If not greater than first block
execute(struct[0][1])
else:
for this_commit in GetCommits(struct[2]+1, commit[1]-1): # Check prev height commits
if struct[1][0].run(this_commit[0]): # IF NO before
execute(struct[0][0]) # NO
else:
execute(struct[0][1] ) # YES
execute(args):
CONNECT_TO = args[0]
ACTIVE = FALSE
I'm not a good enough coder to know if this'll work yet. But it seems like the logic is there. If we can break everything down into tiny pieces, we can open up the door for completely open-sourced creativity. Anybody could make any contract they wanted to, and if they completely mess up, they'd just be losing their money. And if we did it right, there'd never need to be a fork again.
Let me know how you think this could be attacked or exploited. I don't know enough yet to know if this will really work, but thinking about what it could accomplish if it did makes me want to try.