nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
January 04, 2021, 03:28:50 PM Last edit: January 06, 2021, 03:28:14 PM by nixed |
|
I have a couple questions about the way the coin history and wallet work.
1. If I receive comb to a stealth address that's already been swept, the wallet realize this and automatically drop the comb in my main wallet, or should I never reuse a stealth address like this?
2. Related to the previous, if Bob sends comb to Cindy, and then Alice sends comb to Bob's old wallet, can she just give the coin history to Cindy and have it transfer into Cindy's wallet? I realize this is an odd process that would most likely never occur organically, but it's interesting to think about.
EDIT: Looking into the way that the wallet files are store, it seems like the answer to the first question is yes, it would work to receive at previously swept addresses, however I'm still not sure about the second question's answer.
|
|
|
|
haircombint
Newbie
Offline
Activity: 4
Merit: 0
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 01, 2021, 11:23:45 PM |
|
Made a telegram for COMB for those interested. t.me/comb_talk
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 06, 2021, 12:02:46 AM |
|
I finally had some time to work on a GUI over the past week. RakeCOMB v0.1.0 can be found here: https://github.com/nixed18/RakeCOMBCurrent Functionality: - Save and Import wallets - Receive transfers - Browse accounts and stealth addresses - Generate new accounts - Incomplete commits validation - Safe shutdown Place the exe and then pck in the same folder as your .dat files so the Import can detect them. Transaction initiation and TX History browsing are planned for the next version. Let me know if you have any thoughts. Also, if anybody can identify what font Natasha used in her first post's infographic, I'd appreciate it
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 07, 2021, 01:40:31 AM |
|
RakeCOMB v0.2.0 is out. https://github.com/nixed18/RakeCOMB/releases/tag/v0.2.0Changes: Add the ability to perform transactions and view previous transaction information. There are some modifications to how RakeCOMB performs when compared with HaircombCore; RakeCOMB prevents you from using a previously burnt address as the Change Address for future transactions. I can't think why this wouldn't be appropriate, but if I've missed something, let me know.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 12, 2021, 03:31:54 PM |
|
I've been trying to figure out exactly how the deciders and contracts function mechanistically. Below I've included a diagram of my current understanding of the structure of the two, which I believe is correct. (This is of a non-stacked object, hence the "000...000") ![](https://ip.bitcointalk.org/?u=https%3A%2F%2Fi.imgur.com%2FnyvGjXA.png&t=665&c=fyAzM-Z7Ivh8RA) I'm stuck on how the actual mechanism works. I don't understand how Natasha managed to sign multiple contracts with the same decider. This is even more perplexing when you look at her post in which she states: - I will publish a file with say 1000 deciders in it. This will mean I could theoretically decide 1000 comb trade contracts This makes me think you can decide more contracts than 1 using the same decider, so long as you decide them all simultaneously. But I don't understand how that would work. My assumption is that as the decider, you're committing 2 numbers to the BTC chain; the hashchain CCW() results for both of the long decider numbers. But unless you where structuring the associated merkle trees in such a manner that the same two commits would trigger all of them simultaneously to respond the way you wanted, I don't see how that's possible. My other assumption was that, looking at the image I posted, the short decider is either SHA256(000...000 + N_1 + N_2), or just SHA256(N_1 + N_2). Another thing I don't understand is the need for the merkle tree. The wallet only lets you input 2 addresses for each contract; the Forward Address and the Rollback address. Is this just a limitation of the current wallet design, which can be expanded upon to include all 65536 destinations in a single contract?
|
|
|
|
watashi-kokoto
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 14, 2021, 08:57:49 PM |
|
There is an unanswered question about Alice spending coins before she received them, it comes as a surprise to an ordinary Bitcoiner, that you can even do this (and it definitely looks like a money creation from thin air problem at first sight).
The scenario is spending a 0 COMB key and paying out say 1 COMB to your friend using the liquidity stack output and then later (optionally) funding that 0 COMB key with 1 COMB.
Why there is no pay button for 0 COMB keys?
because it seems pointless spending 0 COMB. You can access the underlying URL manually and then you can then spend 0 COMB keys.
Why can't I pay out 2 COMB from a 1 COMB key? Or pay out 1 COMB from a 0 COMB key?
This is a safeguard to prevent users from accidentally having their money stuck in a pending liquidity stack.
If users can bypass these checks, why there is not a money creation from thin air problem at the consensus layer?
Because haircomb money must be created from comb-base only. If you spend a 0 COMB key paying out a 1 COMB to your friend, he sees that there is no comb-base associated that filled the original wallet with 1 COMB in the history given to the friend. In effect the friend will see no incoming funds at all until the comb rules are full-filled. They will be full-filled when the history is merged with another history paying the reuqired funds from comb-base. It does not matter who merges the histories, or in what order the histories were made, it only matters that the final merged history gets posted to the final recipient somehow.
So yes, in effect, you can spend funds before you have them. But they will only become available to the final party only at the moment they get paid to you and the histories get merged and posted to the final party.
It's pretty uninteresting (to be honest), so let's change the topic to comb trades.
Preliminaries: Comb trades use 16-level-65536-leaf merkle tree as a building block. They also use a decider as a building block. Merkle tree is well understood, but I need to explain decider.
It is a postquantum signature scheme to sign a small number 0-65535.
Basically you roll 2 256bit true random numbers, and you save them carefully on disk. That is the decider private key. That private key can also be stored in-wallet.
From the private key you calculate two hash chains of 65535 hashes. Then you hash 3 values one of which is just 256bit zero. This is a chaining value to be explained later. Currently you can assume it will be zero. The other two values are the tips of the hash chains.
So you hash the 3 values and you get a "decider public key" also known as "short decider." You can give the short decider to other people. It is like an address except it isn't, it is hexadecimal 256 bit number but uses different alphabet G-V
The user received a short decider from the decider person. The user wants to make the contract address.
To make the contract address just hash "short decider" CAT "merkle root".
WHAT MERKLE LEAVES TO USE
There is a trick, instead of providing 65536 different addresses as the 65536 leaves wich are useless from the business perspective the user uses just a "forward" address and a "rollback" address repeatedly. These kinds of binary contracts with exactly 2 outcomes are important for all kinds of trades.
So to fill the 65536 leaves of the merkle tree the user uses a pattern of just 2 addresses. There are 16 useful patterns. They are:
1. rollback (1 time), forward (1 time), rollback (1 time), forward (1 time), ... (total 65536 times) 2. rollback, rollback (2 times), forward, forward (2 times), ... (total 65536 times) 3. rollback, rollback, rollback, rollback (4 times), forward, forward, forward, forward (4 times)... (total 65536 times) ... ... ... 16. rollback (32768 times), forward (32768 times) (total 65536 times)
The counter-party also does the same calculation to arrive at the exactly same merkle root. And because both parties trust the decider person, they both use the decider person's "short decider", to arrive and cross-check the exact contract address.
These patterns each have the additional property, that, if you are flipping just 1 bit, exactly one comb-trade-bit's outcome is changing.
So imagine you sign the 16bit number to be "1" instead of "0" what happens? Only in the pattern 1 the outcome (the leaf address) changed from rollback to forward. Other contracts remain unaffected (remain "rollback").
This is why 16 comb trade bits exist, this is how 1 decider is able to control up to 16 binary comb trades between 32 trading parties, to be settled at the same time.
Obviously you can simply use 1 decider for 1 comb trade. Using 1 decider for 16 contracts is just an optimalization to squeeze more commerce into Bitcoin's tiny block space.
You (the decider person) can sign using the decider just you would with a haircomb, that is, you commit-to from the two hash-chains a commitments of two values at the depths that sums to 65535. If you fail to do this, you will burn the key, e.g. the scheme is one-time-only.
Then you (or somebody) tips the two commitments using 330 sats.
When there are 6+ confirmations you can reveal the "signature" also known as a "long decider"
The signature is just a signed value n (16-bit) CAT value at depth n CAT value at depth 65535-n in some encoding.
The user adds the proof that the decider had signed a number n together with a proof of n-th merkle branch to their wallet. The wallet accepts the combined proof as a coin history entry and moves the funds from the contract address to the merkle leaf address.
The leaf address can be again one of the 3 address types e.g. haircomb public key/liquidity stack entry/contract address.
But the leaf address can also be another merkle root, this is the chained merkle tree scenario in which the decider is not hashed with the 256 bit zero but with another chained short decider to form a linked list of deciders.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 14, 2021, 10:44:11 PM Last edit: February 19, 2021, 03:49:06 PM by nixed |
|
There is an unanswered question about Alice spending coins before she received them, it comes as a surprise to an ordinary Bitcoiner, that you can even do this (and it definitely looks like a money creation from thin air problem at first sight).
The scenario is spending a 0 COMB key and paying out say 1 COMB to your friend using the liquidity stack output and then later (optionally) funding that 0 COMB key with 1 COMB.
Why there is no pay button for 0 COMB keys?
because it seems pointless spending 0 COMB. You can access the underlying URL manually and then you can then spend 0 COMB keys.
Why can't I pay out 2 COMB from a 1 COMB key? Or pay out 1 COMB from a 0 COMB key?
This is a safeguard to prevent users from accidentally having their money stuck in a pending liquidity stack.
If users can bypass these checks, why there is not a money creation from thin air problem at the consensus layer?
Because haircomb money must be created from comb-base only. If you spend a 0 COMB key paying out a 1 COMB to your friend, he sees that there is no comb-base associated that filled the original wallet with 1 COMB in the history given to the friend. In effect the friend will see no incoming funds at all until the comb rules are full-filled. They will be full-filled when the history is merged with another history paying the reuqired funds from comb-base. It does not matter who merges the histories, or in what order the histories were made, it only matters that the final merged history gets posted to the final recipient somehow.
So yes, in effect, you can spend funds before you have them. But they will only become available to the final party only at the moment they get paid to you and the histories get merged and posted to the final party.
Ok this makes sense and lines up with what I understood; the wallet is essentially building the plumbing of the wallet on every boot and spawning the coins in their claimed addresses, then seeing where they end up. Pre-connecting B->C before A->B won't show any funds in C, because there weren't any in B. Once you connect A->B, then they'll show in C. It's pretty uninteresting (to be honest), so let's change the topic to comb trades.
Preliminaries: Comb trades use 16-level-65536-leaf merkle tree as a building block. They also use a decider as a building block. Merkle tree is well understood, but I need to explain decider.
It is a postquantum signature scheme to sign a small number 0-65535.
Basically you roll 2 256bit true random numbers, and you save them carefully on disk. That is the decider private key. That private key can also be stored in-wallet.
From the private key you calculate two hash chains of 65535 hashes. Then you hash 3 values one of which is just 256bit zero. This is a chaining value to be explained later. Currently you can assume it will be zero. The other two values are the tips of the hash chains.
So you hash the 3 values and you get a "decider public key" also known as "short decider." You can give the short decider to other people. It is like an address except it isn't, it is hexadecimal 256 bit number but uses different alphabet G-V
The user received a short decider from the decider person. The user wants to make the contract address.
To make the contract address just hash "short decider" CAT "merkle root".
This makes sense, it's what I understood from the whitepaper, just like a normal transaction. WHAT MERKLE LEAVES TO USE
There is a trick, instead of providing 65536 different addresses as the 65536 leaves wich are useless from the business perspective the user uses just a "forward" address and a "rollback" address repeatedly. These kinds of binary contracts with exactly 2 outcomes are important for all kinds of trades.
So to fill the 65536 leaves of the merkle tree the user uses a pattern of just 2 addresses. There are 16 useful patterns. They are:
1. rollback (1 time), forward (1 time), rollback (1 time), forward (1 time), ... (total 65536 times) 2. rollback, rollback (2 times), forward, forward (2 times), ... (total 65536 times) 3. rollback, rollback, rollback, rollback (4 times), forward, forward, forward, forward (4 times)... (total 65536 times) ... ... ... 16. rollback (32768 times), forward (32768 times) (total 65536 times)
The counter-party also does the same calculation to arrive at the exactly same merkle root. And because both parties trust the decider person, they both use the decider person's "short decider", to arrive and cross-check the exact contract address.
These patterns each have the additional property, that, if you are flipping just 1 bit, exactly one comb-trade-bit's outcome is changing.
Holy crap. So imagine you sign the 16bit number to be "1" instead of "0" what happens? Only in the pattern 1 the outcome (the leaf address) changed from rollback to forward. Other contracts remain unaffected (remain "rollback").
This is why 16 comb trade bits exist, this is how 1 decider is able to control up to 16 binary comb trades between 32 trading parties, to be settled at the same time.
Obviously you can simply use 1 decider for 1 comb trade. Using 1 decider for 16 contracts is just an optimalization to squeeze more commerce into Bitcoin's tiny block space. So essentially the infrastructure exists to combine an large amount of 3rd-party trades into a single operation. That's awesome. You (the decider person) can sign using the decider just you would with a haircomb, that is, you commit-to from the two hash-chains a commitments of two values at the depths that sums to 65535. If you fail to do this, you will burn the key, e.g. the scheme is one-time-only.
Then you (or somebody) tips the two commitments using 330 sats.
When there are 6+ confirmations you can reveal the "signature" also known as a "long decider"
The signature is just a signed value n (16-bit) CAT value at depth n CAT value at depth 65535-n in some encoding.
Ok so the long decider isn't just the hashchain heads of the short decider then, that makes sense. Do you think that the signed value n needed to be included in the signature? Couldn't you determine that value based on the other 2 signature values? I guess that it doesn't really matter if you include the signed value N, but it saves computing power on the wallet side, so it makes sense to include it. The user adds the proof that the decider had signed a number n together with a proof of n-th merkle branch to their wallet. The wallet accepts the combined proof as a coin history entry and moves the funds from the contract address to the merkle leaf address.
The leaf address can be again one of the 3 address types e.g. haircomb public key/liquidity stack entry/contract address.
But the leaf address can also be another merkle root, this is the chained merkle tree scenario in which the decider is not hashed with the 256 bit zero but with another chained short decider to form a linked list of deciders. To extend the amount of contracts that can be signed exponentially, while only increasing the amount of commitments needed linearly? If I'm understanding that correctly, it's hilarious and amazing. EDIT: I may be wrong on the above statement? I need to spend more time visualizing the expansion process, it's difficult to do. My gut thought now though is that it would just double the amount of contracts available, but if that's the case then why would you even bother stacking trees like that? EDIT2: Thinking about it more it makes sense that it's exponential. A tree with 4 tips can hold 2 patterns, a tree with 16 tips can hold 4, if the pattern threshold is just the square root of the tip count (not sqrt, po2 I guess) then my initial assumption was correct. That's crazy.One question and then I'll walk through an applied example to make sure I'm not missing anything. The following quote: To make the contract address just hash "short decider" CAT "merkle root". If the decider person wanted to sign multiple contracts at the same time, then the user wouldn't be the person choosing the Merkle Tree pattern, correct? It would have to be the decider person, to make sure that all the contracts they were deciding were all on different bits, right? Okay so walkthrough time: Alice wants to send COMB to Bob, and Cindy wants to send COMB to Dan. Xavier will be the decider person. Alice, Bob, Cindy, and Dan all give Xavier their addresses; Alice=Rollback_1, Bob=Forward_1, Cindy=Rollback_2, Dan=Rollback_2. As there are only 2 contracts being decided, there's no need for nested Merkle Trees, so Xavier generates the two Decider private keys and then gives the Short_Decider (000...000 CAT hashchain_tip_1 CAT hashchaintip_2) Alice, Bob, Cindy, and Dan. He also generates 2 Merkle Trees, one for Alice and Bob's contract (Tree_AB) and one for Cindy and Dan's contract (Tree_CD), and gives them to their respective participants. They then each generate the COMB Address that their contract's Sender will deposit COMB into, for Alice and Bob it's SHA256(Short_Decider CAT Tree_AB) and for Cindy and Dan its SHA256(Short_Decider CAT Tree_CD). Tree_AB is using pattern 1 (01010101...) and Tree_CD is using pattern 2 (00110011...) Alice and Cindy both deposit their COMB into their respective contract addresses, eager to get their shitcoins. I'll go over each of the possibilities below. (Using N min = 0, max = 65535: 1. Bob and Dan both deposit their shitcoins, as requested: Xavier signs N=3, Tree_AB = 010 (1)... and Tree_CD = 001 (1)..., COMB goes to Forward_1 and Forward_2 2. Bob loses his shitcoin wallet keys, but Dan makes his shitcoin deposit: Xavier signs N=2, Tree_AB = 01 (0)1... and Tree_CD = 00 (1)1..., COMB goes to Rollback_1, Forward_2 3. Bob deposits his shitcoins, but Dan accidently sends all his shitcoins to a scammer: Xavier signs N=1, Tree_AB = 0 (1)01... and Tree_CD = 0 (0)11..., COMB goes to Forward_1, Rollback_2 4. Bob and Dan both listen to Elon Musk and buy DOGE instead: Xavier signs N=0, Tree_AB = (0)101... and Tree_CD = (0)011..., COMB goes to Rollback_1, Rollback_2 Is this correct? P.S. It's good to hear from you again, thank you ![Smiley](https://bitcointalk.org/Smileys/default/smiley.gif)
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 18, 2021, 08:51:22 AM Last edit: February 18, 2021, 07:05:31 PM by nixed |
|
I just learned about the centralized wallets. Natasha is a genius. Giant economic pumps, driving comb through them to run the payment system, with people using deciders as their wallets. But Watashi, my friend, I realized you are wondrously wrong about something! instead of providing 65536 different addresses as the 65536 leaves wich are useless from the business perspective This is true if you are a person who is deciding a contract for other people. But what if you are a person deciding a contract for YOURSELF???Rather than send the funds from the pump to a decider with only 2 options(1 back to the Pump and 1 back to your private wallet) you could have a contract with 65536 options! You could include multiple different pump options, in case the one you normally used happened to rugpull, or even just go down temporarily for some reason. You could have multiple leaves with different liquidity stack combinations of payments you might have to make in the future (this one is a bit of a stretch, but it works well for repeated, static cost events, stuff like going to a physical therapist). But then I realized something else. These pumps can be automated! They can be completely automated, through the use of decider contracts!!! A pump would have a certain threshold of funds that it could accommodate, for example lets say a specific Pump can accommodate 10000 COMB. This Pump would have to keep 10000 COMB on it to maintain operation. A Client would then approach the Pump and tell it that they wanted to send X COMB to a location, via the Pump. They would also approach a third party, also an automated system, to facilitate the contract as the Contractor. The Contractor would communicate with the Pump and the Client, and build a contract that would send the stored funds either to the Pump, or back to the Client. The Client pays the COMB into the contract. Once the contract has been funded, the Pump then runs its next cycle, and pays the same amount of COMB that's in the contract to the address the Client wanted it sent to. Once the Contractor gets the transaction history from the Pump and verifies that the funds have in fact gone to the specified location, it Decides the contract in favour of the Pump. If the Client doesn't pay the contract, the Pump just doesn't send the COMB to the address. If the Pump doesn't send the comb to the address, the Contractor decides in favour of the client, and the funds go to the clients backup address!!! The Pump can only accept the amount of COMB in contacts per cycle that it has on hand, as it's required to prepay all the contracts before they'd be settled in the Pump's favour. Let me know if I've fucked anything up. I think this'll work. The integration with the BTC chain means that you wouldn't even have to rely on an oracle system I think. Let me know if I've gone full schizo here lol. EDIT: I can't make up my mind about which sounds better, Pump or Turbine. Pump is more apt mechanically, but, as it was pointed out to me, isn't great in a financial connotation. Turbine also suggests automation, whereas Pump does not, and Turbine sounds grandiose and cool.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 19, 2021, 03:54:48 PM Last edit: February 20, 2021, 12:57:53 AM by nixed |
|
I've been thinking a lot about how to remove a 3rd party from COMB/COMB swaps, and while I can get close, I can't figure out a way to solve one final issue.
The methodology I've been using relies on adding a new object type to the programming of COMB. Right now I'm calling them Witnesses. The way a Witness would function is similar to a Decider/Contract, however rather than trigger based off of the Decider signing a number to determine the destination of the funds, a Witness would trigger off of witnessing another event take place. Let me explain via example.
Alice and Bob want to perform a COMB for COMB swap. Alice first creates a Decider/Contract (dc_1) where she is the decider, and the contract addresses are: forward = bob_2, rollback = alice_2. Alice pays her COMB into dc_1, and then sends Bob the transaction history proving that the Decider/Contract is valid and that she has funded it. Bob then creates a Witness. I haven't figured out the exact syntax yet, but it would need to include a forward address, a rollback address, and the object address that it was witnessing. In this case those details would be forward = alice_2, rollback = bob_2, and witnessing = dc_1. Bob then pays his COMB into witness_1. Bob sends his transaction history to Alice. Alice then sees that a witness_1 is both funded and is witnessing dc_1. She now decides dc_1 as FORWARD.
When she decides dc_1, she commits the sign number to the BTC chain in the form of the two bc1 addresses. If I'm not missing anything, the Witness will be able to see this, and verify, using the information that Alice has, that dc_1 has been signed FORWARD, and will ALSO act as if it has been signed forward. This will release the funds on witness_1 into alice_2. Then Alice sends the TX history to Bob, and once he has it he'll have enough information to verify how the Decider was signed, and he'll get the funds out of the Decider.
There's a problem though. What if Alice is an asshole, and decides to just not give Bob the TX history after she's signed the Decider and gotten the COMB out of the Witness? She doesn't actually gain anything extra from this, but a malicious actor could screw over their counter-party by dong this.
This is the problem I don't have a good solution to. However, there may bee some financial trickery that can get "around" this problem. I'll make a post a little later once I've fleshed it out. Also keep in mind I have no idea of the feasibility or mathematically implications of including more objects into the Haircomb code. If I've missed any potential problems here, let me know.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 19, 2021, 11:24:10 PM Last edit: February 20, 2021, 12:57:27 AM by nixed |
|
The idea I had before to circumvent the problem didn't pan out, but I think I have a different solution. Rather than use a Decider/Contract and a Witness, you would use two Paired Witnesses. The Paired Witnesses would also utilize a hash based lock and key, where SHA256(key) = lock.
A Paired Witness would be constructed using the following steps.
1. pwit_id = SHA256(forward, rollback, lock)
2. address = SHA256(pwit_id, target)
In place of the target you would fill in the other Witness ID in the pairing, i.e. pwit1_add = SHA256(pwit1_id, pwit2_id), pwit2_add = SHA256(pwit2_id, pwit1_id)
The information that both parties would get, in addition to the transaction info proving the counterparty's deposit of funds into the counterparty's generated pwit_address, would be the information making up the counterparty's pwit_address, i.e. forward, rollback, lock, and target. A user could then confirm that the counterparty had deposited funds into the address that was Paired with their Witness. In order to unlock a Witness, the Witness's Key must be committed to the BTC chain, as well as the key to the other Witness that it has been paired with. Due to the simplicity of the lock, it would be easy to determine the key just by looking at the committed P2WSH addresses on the BTC chain if the counterparty decided not to send you the TX information proving that they had committed the Key. And said counterparty MUST commit the key, because otherwise their funds remain in the Witness.
Currently the structure doesn't use the rollback address, however it can be used if you give either party a method of terminating the swap. The first method that comes to mind would be using another lock and key, and detecting which one came first on the BTC chain, but there are certainly others, and I haven't thought about which would be better.
Let me know if something I've described wouldn't work.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 20, 2021, 04:32:07 PM Last edit: February 20, 2021, 06:03:43 PM by nixed |
|
I've combined the above three posts into an idea. If this works, it would reduce the cost of transacting on the BTC chain from 21 transactions down to (21+X)/X, where X is the amount of transactions being batched together. It would also be done in a completely trustless manner, without the use of third parties. First I will establish the two new COMB objects that are required to make this work. Note: It is possible to accomplish the same structure of motion in the current system, however it costs (21+2X)/X, and relies on trusting a 3rd party. Objects Required -- Witness A Witness would operate similarly to the previously described Witnesses. This Witness would act as a Paired Witness, but rather than being paired up with another, corresponded Witness, it would be targeting something called a Lockbox. The information a Witness would require is a forward address, a rollback address, and the target address (the address of the Lockbox it is Witnessing) -- Lockbox A Lockbox will operate similarly to the previously described Paired Witness, in the manner that it uses the same Lock/Key system that the Paired Witness uses. You can think of it as essentially a mini Decider; rather than 65355 destinations, a Lockbox has 2. Because the Lockbox only has 2, we can sign the Lockbox using a single commit. The Lockbox would be made up of a forward address, a rollback address, two locks, a YES lock, a NO lock, the height of the BTC block at which the Lockbox becomes valid, and optionally the height of a BTC block that the Lockbox expires on. To unlock a Lockbox, the controller would commit one of the two keys to the BTC chain. If the controller commits both Keys, the Key that appears on the first BTC block will be considered valid. If they both appear on the same block, then YES lock will be considered valid. If a Key appears on the blockchain before the stated start_block, it will be discounted, and only the other Key will be a valid commit. If an expiry block is specified and subsequently reached without any keys being committed, it will act as a NO key sign. Walkthrough A Client wishes to send funds using a turbine. They send their funds to a Lockbox, the address of which is built from SHA256(turbine_1, client_2, yes_lock, no_lock, start_height, expiry_height) . The Client then sends their transaction history to the Turbine, proving that their funds have been deposited into lockbox_1. First, if the current BTC height is greater than the stated start_height, the Turbine most check each of the previous blocks between then and now, to confirm that a Key has not already been signed during this time. The Turbine then creates a Witness, built from SHA256(client_2, turbine_2, lockbox_1). The Turbine deposits the same amount of funds into witness_1 that the Client has deposited into lockbox_1, and sends the Client the information proving it. Once this has been establish on the Client's end, the Client commits lockbox_1's YES key to the BTC chain. Due to the ease of calculation, whether the Client ends up being a good guy and sharing the information with the Turbine is irrelevant, the Turbine should be able to deduce the the Lockbox's status simply by trying P2WSH addresses from the BTC chain until one fits the lock, and then trying the rest of the Keys on the block until it can confirm that 1 or two keys were committed on this block, and can move forwards from there. Here is a diagram of the process: https://i.imgur.com/TR67aWN.pngYou may say that this does not reduce costs, because a Client must pay to put their funds into the Lockbox. However, this only needs to happen once, on initial Turbine cycle entry. They can specify a Turbine address, and no matter how many cycles go by before they initiate a contract with the Turbine, the funds will always flow to the currently active Turbine wallet. When a Client initiates a contract with a Turbine, they can specify that they want the destination of their funds to be a Liquidity Stack, the OUT address being a payment they wish to make, but the CHANGE address being directed to ANOTHER Lockbox. The Client should also specify a Merkle tree as the output of their Lockboxes, one that has options to go to other Turbines in case there's a problem with this one in the future, or to a private wallet as a final backup measure. By chaining Lockboxes together, after the initial transaction from a Private Wallet to a Lockbox, each additional transaction will only require one BTC commit, so long as the Client continues to go Lockbox->Turbine->Lockbox->Turbine. Each cycle that they participate in, they have the opportunity to make payments. One hiccup in the scheme is that a Client will no longer be able to receive funds or claim COMB on their old, private wallets. They will have to give other parties the address of the current lockbox they are waiting in, and once they initiate an atomic swap with a Turbine they will have to begin receiving funds into the new Lockbox address. Another potential weakness, albeit an odd one, is if a malicious Client decides to suicide-bomb the Turbine. If the Client deposits X funds into a Lockbox with no expiry height, and the Turbine then deposits X funds into a Witness targeting that Lockbox, the Client has the option of just never signed either key. This will cause both the Turbine and the Client to lose access to X funds. The Turbine can avoid this possibility however, if it decides to only accept contracts with people who's funds are in a Lockbox that has a defined expiry height. I don't know if this will work yet, because there are areas of operation that I am not currently educated enough on to understand. Will this require too much processing? The amount can be reduced by storing the Key's address with the history of the lockbox in the wallet, once the operation has been concluded, however future wallets would need to confirm that the other key was not signed in between the start height of the contract and the height of the committed Key. I also don't know enough about the processing of COMB objects internally, i.e. why Decider/Contracts use different number system. I'm assuming it's because they are not supposed to have COMB deposited into them, and so it differentiates them from addresses that ae supposed to have COMB deposited into them, but I'm not sure, so it's a variable I have to consider. If, however, this WOULD work, it would open up a ton of possibilities I think. The fact that you could have multiple Witnesses all witnessing a single Lockbox seems like it would be a VERY powerful tool, though I have not thought about how it could be used yet. If anybody can confirm or deny that this would work, I would appreciate it.
|
|
|
|
watashi-kokoto
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 22, 2021, 12:52:01 PM |
|
If hard fork is an option, I think the same basic goal of reducing the 21 addresses in many cases can be reached differently:
Hot cash address.
Hot cash address can be SHA calculated from a secret fallback address and an enough decider to sign 16 bits.
Hot cash address operation.
If the user signs 16 bit signed number zero the system flows the coins from the hot cash address to the fallback.
If the user signs a non zero value n: the money will flow to the n-th address that was commited to on chain after the fallback address was commited to on chain. (or before if n is negative)
Hot cash paying
1. the user who owns funds on a hot cash address commits the fallback address to the chain to start the relative offset period. 2. the user commits the liquidity stack commitment where the user wishes to pay. .. wait 6 confirmations ... 3. the user subtracts the relative positions of the two previous commitments: n = position2 - position1, and signs n, if n fits in a 16 bit signed int. .. wait 6 confirmations ... 4. funds are now paid to the liquidity stack.
This would cost 4 addresses funded per hot cash cycle.
Possible problems: - the user does not wait 6 confirmations & attacker manipulates the relative order - result: all funds stolen - solution: wait longer - the attacker manipulates the order to make the distance |n| greater than 32767 - by inserting your 2 commitments into their transaction(s) that confirm before yours and in which the values are more apart - result: transaction does not go through - solution: sign 0 to go to fallback / replace by fee outbid the attacker
The advantage could then be that the user needs not to approach another player in the form of turbine, can do everything purely alone.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 22, 2021, 06:01:57 PM Last edit: February 23, 2021, 03:50:56 AM by nixed |
|
That's a pretty cool way to de-couple destinations from their direct address, using block order. It's slower than Turbines (unless my walkthrough is incorrect), and more expensive, but the fact that you can do it solo and aren't merging TX histories with everybody in the cycle is a huge upside, not to mention the potential problems of a Turbine going down in an ecosystem that's still building to multiple Turbine options. The fact that no infrastructure is required is huge. What do you think about centralized wallets in general, hard fork or not? I feel like if they're an inevitability due to the convenience and 0 TX fees, not everybody cares about security and privacy, and that it makes sense to try and make options for them that are as secure and convenient as possible.
Looking at the current extrapolation of Haircomb with no forks, if you agree that Turbines would be inevitable then we're looking at
a) 4 commits per semi-safe transaction (My_Funds->My_Decider(Store Here), My_Funds->Oracle_Decider, Turbine_Funds->My_Destination, Oracle_Decider_Funds->Turbine.) b) 2 commits per less-safe transaction (My_Funds->Oracle_Decider(Store Here), Turbine_Funds->My_Destination, Oracle_Decider_Funds->Turbine.) c) 0 commits per not-safe transaction (Permanent Turbine Storage).
So the core ecosystem would require trust to participate on a frequent basis. I think that forks make sense in this case, introducing other options could go a long way.
Do you think there's a way that, if forked, we could update Haircomb without needing to fork again? The problem is ofc that a wallet needs to process every transaction in the chain, so backwards compatibility can't work like BTC, but do you think there's a way to circumnavigate it? I have some ideas but they aren't mature yet.
EDIT: Thinking about it more, I really can't stress enough the benefit of 0 infrastructure. Turbines will take AGES to get up and running properly, and they'll rely extremely heavily on the surrounding environment. Hot Cashes Addresses will be way easier to implement, and helping bridge the gap to the point that Turbines are actually working will be enormous. And even then, if you value your independence the extra fee may very well be worth it, depending on the price.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 24, 2021, 04:06:59 AM Last edit: February 24, 2021, 07:42:35 PM by nixed Merited by watashi-kokoto (8) |
|
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.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 24, 2021, 04:26:42 PM Last edit: February 24, 2021, 07:42:45 PM by nixed |
|
Some more thoughts: 1. I just used whitespace as syntax because that's what I'm used to, it makes more sense to use brackets IMO. 2. Modifying the AMOUNT from within an object's run() is moronic, that's a function that needs to be 100% under the control of the Haircomb Client; you'd PayTo(dest, amount) from object_A, and then the dest object would get its AMOUNT credited by Haircomb and then run. 3. I think it makes the most sense to modify all inherent variables using a native function. Modified Liquidity Stack example: build(int256, int256, int64) run(int64){ if (AMOUNT > struct[2]){ execute([struct[0], struct[1], struct[2]]) } }
execute(args){ PayTo(args[1], args[2]) SetActive(FALSE) ConnectTo(args[0]) } I think it makes sense to include the amount that was paid to the Object as an argument in run(), as it opens the doors for contracts that could be based on the size of an individual payment. While it's impossible to actually calculate the cost of committing a contract yet as there's a lot to be worked out with what information the interpreter would need or not need, looking at the Liquidity Stack code you can get a guess of 69 commitments total.
|
|
|
|
watashi-kokoto
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 25, 2021, 12:58:21 PM |
|
GetCommitsByHeight(height, height): returns all unseen P2WSH in between the two heights GetCommitsByIndex(index, index): returns all unseen P2WSH in between the two indices. These two are BAD. Too slow - if someone requests the complete range in a buggy contract to make a wallet killer contract. You must instead ask the prover to give you a specific example commitment as a free variable (that you would otherwise be searching using the for loop) and check the height or position of it. This is why I think that for loops should be banned. Making time/height locks in general should be banned as they cannot be enforced by consensus in principle due to slippery slope. Another bug that can't occur is balance instability. No matter in what order you load the history, you should see the same account balances. Therefore implementing custom (possibly wrong) liquidity stacks should be banned. Also some kind of bugs in the contract can't be tolerated - such as double spending. One tiny double spend hole could collapse the entire currency. Thus making a custom haircomb (possibly wrong) primitive too should be banned. That said I am hopeful that the general idea will be good in the end. Something like this perhaps. Merkle transaction: ARGCOUNT = 21 ADDRESS = SHA256(SHORTDECIDER(ARG[1...3]) CAT ARG[21] ) CONDITION = ENTRENCHED_DECIDER(ARG[2...3]) && ARG[21] == MERKLE_ROOT(DECIDER_SIGNS(ARG[2...3]), ARG[4...20]) CONNECT_TO = ARG[1] == 0 ? ARG[20] : MERKLE(ARG[1], ARG[20]) Hot cash: ARGCOUNT = 4 ADDRESS = SHA256(ARG[1...3]) CONDITION = ENTRENCHED_DECIDER(ARG[2...3]) && (DECIDER_SIGNS(ARG[2...3]) == 0 || ((ENTRENCHED(ARG[1]) && ENTRENCHED(ARG[4]) && DECIDER_SIGNS(ARG[2...3]) == (CHAINPOSITON(ARG[4]) - CHAINPOSITON(ARG[1]))))) CONNECT_TO = DECIDER_SIGNS(ARG[2...3]) == 0 ? ARG[1] : ARG[4] Some kind of lock: ARGCOUNT = 4 ADDRESS = SHA256(ARG[1...4] ) CONDITION = ENTRENCHED(ARG[1]) || ENTRENCHED(ARG[2]) CONNECT_TO = (ENTRENCHED(ARG[1]) && ENTRENCHED(ARG[2])) ? (CHAINHEIGHT(ARG[1]) < CHAINHEIGHT(ARG[2]) ? ARG[3] : ARG[4] ) : (ENTRENCHED(ARG[1]) ? ARG[3] : ARG[4] )
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 25, 2021, 04:26:29 PM Last edit: February 25, 2021, 11:22:33 PM by nixed |
|
GetCommitsByHeight(height, height): returns all unseen P2WSH in between the two heights GetCommitsByIndex(index, index): returns all unseen P2WSH in between the two indices. These two are BAD. Too slow - if someone requests the complete range in a buggy contract to make a wallet killer contract. You must instead ask the prover to give you a specific example commitment as a free variable (that you would otherwise be searching using the for loop) and check the height or position of it. This is why I think that for loops should be banned. Oh crap, yeah that's garbage. Making time/height locks in general should be banned as they cannot be enforced by consensus in principle due to slippery slope.
I'm not sure what you mean by this. My understanding was that the Haircomb client would have to be able to tell what block a commitment was on, in order to spawn the COMB to 1 address per block. Is this incorrect? Does the client just get the BTC to tell it which address was the highest in the block, and then discard that information? I think I'm missing your point entirely here. Another bug that can't occur is balance instability. No matter in what order you load the history, you should see the same account balances. Therefore implementing custom (possibly wrong) liquidity stacks should be banned. This is what I meant by atemporal vs temporal. How does the basic Haircomb client handle liquidity stacks then? I haven't been able to figure it out. I would've phrased the code in an atemporal way if I had known how to fit LStacks in with that methodology. A shell could be written to process in a temporal way though, I think. You'd load all the objects into the shell at once, and then loop through over and over until the funds couldn't trickle any more. That seems like garbage though. Also some kind of bugs in the contract can't be tolerated - such as double spending. One tiny double spend hole could collapse the entire currency. Thus making a custom haircomb (possibly wrong) primitive too should be banned. So you think it's too risky? There are too many bug possibilities? The way I had it laid out to prevent double spending was to run everything through a native shell within the Haircomb client that would do all the processing and currency motion. I didn't see how you'd be able to write a contract that could cause a double spend in that instance, the shell would know the balances of each object within it, because it did all the math to determine them based on the COMB spawns. If an object requests a transaction that doesn't mathematically make any sense to do, then the shell won't do it, and will break with an error. I was hoping keeping everything completely compartmentalized would work in this regard. Hell, with an atemporal system, the objects wouldn't even be able to request fund transfer at all; it'd just be them connecting to each other. Then the shell spawns the COMB and calculates the trickle. That said I am hopeful that the general idea will be good in the end. Something like this perhaps. Merkle transaction: ARGCOUNT = 21 ADDRESS = SHA256(SHORTDECIDER(ARG[1...3]) CAT ARG[21] ) CONDITION = ENTRENCHED_DECIDER(ARG[2...3]) && ARG[21] == MERKLE_ROOT(DECIDER_SIGNS(ARG[2...3]), ARG[4...20]) CONNECT_TO = ARG[1] == 0 ? ARG[20] : MERKLE(ARG[1], ARG[20]) Hot cash: ARGCOUNT = 4 ADDRESS = SHA256(ARG[1...3]) CONDITION = ENTRENCHED_DECIDER(ARG[2...3]) && (DECIDER_SIGNS(ARG[2...3]) == 0 || ((ENTRENCHED(ARG[1]) && ENTRENCHED(ARG[4]) && DECIDER_SIGNS(ARG[2...3]) == (CHAINPOSITON(ARG[4]) - CHAINPOSITON(ARG[1]))))) CONNECT_TO = DECIDER_SIGNS(ARG[2...3]) == 0 ? ARG[1] : ARG[4] Some kind of lock: ARGCOUNT = 4 ADDRESS = SHA256(ARG[1...4] ) CONDITION = ENTRENCHED(ARG[1]) || ENTRENCHED(ARG[2]) CONNECT_TO = (ENTRENCHED(ARG[1]) && ENTRENCHED(ARG[2])) ? (CHAINHEIGHT(ARG[1]) < CHAINHEIGHT(ARG[2]) ? ARG[3] : ARG[4] ) : (ENTRENCHED(ARG[1]) ? ARG[3] : ARG[4] ) So how do you see nested objects and returns working? Or do you not? My hope was that it'd be possible to take previously committed contracts and include them in your new contract by reference. Maybe I'm misunderstanding your code. I'm interpreting ENTRENCHED(ARG) as a function to check if the ARG is an address that has been committed on the chain. Ergo CHAINHEIGHT() is a function to check the height of an address that has been committed. These make sense to be included as native functions. What I don't understand is your use of ENTRENCHED_DECIDER(), whether it's being called as a native function or as a reference to a custom, previously committed function. EDIT: After going over it a few more times it looks more like you're using ENTRECHED_DECIDER() as a reference to custom object, I was just thrown off at first. Something I realized is that we can (I think) implement multi-sig transactions via a Locking mechanism. The syntax you have listed would need to be broken down into a more basic TRUE/FALSE return object as a lock, but it can then be combined with another object. I'll try to type it up, but my syntax might be borked, so no judgement lol. I also have no idea what the way to phrase RETURN would be, so I'm just going to label it as RETURN = for now. So the lock would be like # Either Key_NO (1) or Key_YES (2) committed # if both committed: if Key_NO has a smaller index than Key_YES (aka it's higher) return FALSE, else return TRUE # else if just Key_NO is commited, return FALSE, else return TRUE
ARGCOUNT = 2 ADDRESS = SHA256(ARG[1, 2] ) CONDITION = ENTRENCHED(ARG[1]) || ENTRENCHED(ARG[2]) RETURN = (ENTRENCHED(ARG[1]) && ENTRENCHED(ARG[2])) ? (CHAINHEIGHT(ARG[1]) < CHAINHEIGHT(ARG[2]) ? FALSE : TRUE ) : (ENTRENCHED(ARG[1]) ? FALSE : TRUE ) You could inject this into something like a Hot Cash contract, to create a Multi-Sig Hot Cash contract. The Hot Cash must both be signed normally, and have the lock signed as yes. If the lock is signed as no at any point, whether the Hot Cash has been signed or not, the funds go to the Fallback Address. You can add on multiple Locks too, if any of them are signed No then fallback, if half of them are signed No, w/e, it's up to you to use the contract that fits your needs. I'm having a little trouble deciphering the exact semantics of the Hot Cash code here, but when I figure it out I'll post a Multi-Sig version. Breaking the Lock up like this also, by happenstance, would allow it to act as a CHAINHEIGHT comparison object lol. You wouldn't code it into a future contract, you could just use ENTRENCHED_LOCK(ARG1, ARG2) ? STUFF : OTHER_STUFF. If you're worried about the temporal bit, just remember that the child contracts wouldn't be listed in the TX history as a separate object. They'd get loaded and processed at the same time as their parent. What do you think about assigning variables for use within an object? You could have contracts that return multiple variables that way. Or do you think that that's being lazy? EDIT2: I just realized the problem with the Lock script is that it doesn't use the bruteforce method to allow another party to open it even if they aren't given the TX history. This means it can't be included as a sub-object of another object, at least not as is, because in order to prove that you'd payed the funds into the contract you'd have to give the counterparty the keys to the lock so they could build the contract address and confirm you'd payed to it. It can't even be used as a mini-decider, because again, in order to prove to the counterparty that the address you payed into was indeed a Lock, you'd need to give them the keys to it in order for them to build the address, so they could just commit said keys for whatever answer they wanted. Could we do range pulls if there was a hard range that the client would accept? So if you went to make a Lock contract, and tried to specify a range of activation that was further than the hard limit, the client would just say no. And if someone hacked together a TX history that had a Lock with a range that was too long, the receiving client would just refuse to process it. If you ran all requests for a range through a client based function, then you could hard cap the range pull, couldn't you? EDIT3: And I realized again that you could just make a TX history with a bunch of range pulls in a row or something. Alright, I guess there'll have to be a different way to atomic swap.
|
|
|
|
watashi-kokoto
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 25, 2021, 09:44:27 PM |
|
slippery slope for decentralized time-locks1. suppose you have invisible but time-locked 1 COMB that goes to your address "A" in year 2100 2. you mod your wallet to see 1 TIMELOCK_COMB in address A 3. other impatient users likewise mod their wallet to see their own TIMELOCK_COMB 4. now the group of impatient users can start trading TIMELOCK_COMB among themselves 5. TIMELOCK_COMB accepted by all just like a normal COMB. 6. 1 TIMELOCK_COMB = 1 COMB 7. most users just mod their wallet and rename TIMELOCK_COMB to COMB. Timelock no longer exists. Now certainly time-locks are possible but only if you agree the funds will be nuked if spent prematurely. That does not sound like a safe primitive. Another way to have time-locks is to have a central party to generate liquidity stacks to a provided addresses used as CHANGE and using true random number as OUT, and zero as TRESHOLD, and the central party just reveals the OUT later (at the specific time). balance stability proof - very boring ![Grin](https://bitcointalk.org/Smileys/default/grin.gif) no matter in what order you're adding spends and stacks, if you flow the money after every new add, after you're done adding those edges, the trickling will converge at the same final balances. 1. For no stacks, it's fairy simple. There's no way to split money so money can only merge or stay separated or flow in loops which cause it to self destruct or the money just reaches the final destinations along the payment edges (once you add all your edges - no matter in what order). 2. For every stack that got triggered we can just imagine there is no stack but replace the stack with an additional combbase creating it's threshold in the OUT address, plus a "minus threshold" special combbase in SOURCE, plus a normal SOURCE -> CHANGE payment edge. Then the rough proof from step 1 applies. 3. For every stack that did not get triggered, well, there is no edge. The STACK SOURCE just acts like a final destination for funds. Lastly:I'm sorry for just using argument numbers in my code. I could've used relevant variable names. If you wanna pay Alice 5 bux from your contract you simply CONNECT_TO liquidity stack SHA256(CHANGE CAT ALICE CAT 5 BUX). This way you can "invoke" any number of subcontracts by CONNECT_TO their respective addresses or a stack of them.
|
|
|
|
nixed
Jr. Member
Offline
Activity: 76
Merit: 8
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 25, 2021, 10:03:42 PM Last edit: February 26, 2021, 12:38:45 AM by nixed |
|
slippery slope for decentralized time-locks1. suppose you have invisible but time-locked 1 COMB that goes to your address "A" in year 2100 2. you mod your wallet to see 1 TIMELOCK_COMB in address A 3. other impatient users likewise mod their wallet to see their own TIMELOCK_COMB 4. now the group of impatient users can start trading TIMELOCK_COMB among themselves 5. TIMELOCK_COMB accepted by all just like a normal COMB. 6. 1 TIMELOCK_COMB = 1 COMB 7. most users just mod their wallet and rename TIMELOCK_COMB to COMB. Timelock no longer exists. Now certainly time-locks are possible but only if you agree the funds will be nuked if spent prematurely. That does not sound like a safe primitive. Another way to have time-locks is to have a central party to generate liquidity stacks to a provided addresses used as CHANGE and using true random number as OUT, and zero as TRESHOLD, and the central party just reveals the OUT later (at the specific time). balance stability proof - very boring ![Grin](https://bitcointalk.org/Smileys/default/grin.gif) This makes sense. I guess they'd just forge P2WSH sigs in their wallets, and then replace them with real ones as the real ones were created. That's actually pretty funny lol. no matter in what order you're adding spends and stacks, if you flow the money after every new add, after you're done adding those edges, the trickling will converge at the same final balances.
1. For no stacks, it's fairy simple. There's no way to split money so money can only merge or stay separated or flow in loops which cause it to self destruct or the money just reaches the final destinations along the payment edges (once you add all your edges - no matter in what order).
2. For every stack that got triggered we can just imagine there is no stack but replace the stack with an additional combbase creating it's threshold in the OUT address, plus a "minus threshold" special combbase in SOURCE, plus a normal SOURCE -> CHANGE payment edge. Then the rough proof from step 1 applies.
3. For every stack that did not get triggered, well, there is no edge. The STACK SOURCE just acts like a final destination for funds.
I still don't understand, this all sounds like temporal phrasing. To me "did not" implies you let the funds converge there, and see what happens, then response accordingly. If you load a random liquidity stack in the middle of the contract and then never come back to check it after loading the other pieces, how would you know whether it got enough funds to trigger? If this is the methodology that's used, then the proposal in my first post seems like it would still fit in with this; you load all the pieces in their correct placements, then spawn COMB in a spawn stack. Every time COMB enters an object, it would trigger that object's run(). I don't see how that's different from this description. Lastly: I'm sorry for just using argument numbers in my code. I could've used relevant variable names.
If you wanna pay Alice 5 bux from your contract you simply CONNECT_TO liquidity stack SHA256(CHANGE CAT ALICE CAT 5 BUX).
This way you can "invoke" any number of subcontracts by CONNECT_TO their respective addresses or a stack of them.
Would you be willing to re-label the example Hot Cash using specific terminology for the ARGs that would be used? Even if it's just adding an ARG1 = X, ARG2 = y, etc. at the top. EDIT: Also, by sub-contracts I didn't mean further down the chain, I meant nested within the main contract. I detailed some examples of use-cases of these, in order to reduce the amount of commits you need to store the code on the BTC chain, or to do stuff like Multi-Sig. EDIT2: The thing that I can't wrap my head around about your syntax is that there's no separated build and run phase. I can't figure out how someone is supposed to verify that funds have been paid into an address that is composed of what the payer SAYS it is. Your syntax only has the option to completely execute on an object and have its funds go somewhere; what if I want to verify that something will happen if I do something else? I'd want to be able to import information about the payment to an object, without receiving the information that'd trigger the object. I don't see how this can be accomplished simply with the way you've laid things out.
|
|
|
|
watashi-kokoto
|
![](https://bitcointalk.org/Themes/custom1/images/post/xx.gif) |
February 26, 2021, 11:23:33 AM |
|
Buggy primitive: coin halverrun(int64){ AMOUNT += run[0] execute(DESTINATION1, FLOOR(AMOUNT/2), DESTINATION2, CEILING(AMOUNT/2)) }
execute(args){ PayTo(args[1], args[2]) PayTo(args[3], args[4]) } Proof that this coin halver is buggy (there is no final balance stability):1. Load first history entry that pays 0.0000001 COMB to coin halver 2. Load another history entry that pays 0.0000001 COMB to coin halver 3. Load coin halver 4. ![Huh](https://bitcointalk.org/Smileys/default/huh.gif) DESTINATION 1 = 0.0000001 COMB DESTINATION 2 = 0.0000001 COMB 1. Load first history entry that pays 0.0000001 COMB to coin halver 2. Load coin halver 3. Load another history entry that pays 0.0000001 COMB to coin halver 4. ![Huh](https://bitcointalk.org/Smileys/default/huh.gif) DESTINATION 1 = 0.0000000 COMB DESTINATION 2 = 0.0000002 COMB Now it is not true that only the inventor of a buggy primitive loses money, what's worse that the inventor will defraud others. In this case by sending history 1 to the DESTINATION 1 user and by sending history 2 to the DESTINATION 2 user. Collectively they will see more money than there is. This is why we need to be proving final balance stability and other virtuous properties about every new primitive added to the currency. We need to prove that (except on chain reorgs): a. CONDITION can change to being TRUE only after being FALSE. (Or CONDITION be TRUE all the time) b. if CONDITION is TRUE, CONNECT_TO address calculated cannot suddenly change. This means it must be impractical to come up with two DISTINCT argument vectors so that ADDRESS 1 == ADDRESS 2 and CONDITION 1 == CONDITION 2 == TRUE and CONNECT_TO 1 != CONNECT_TO 2Fixed the hot cash.ARGCOUNT = 6 256bit ARG[1] = Fallback address 256bit ARG[2] = Value at the end of the hashing sequence (to sign with the left leg of the decider) 256bit ARG[3] = Value at the end of the hashing sequence (to sign with the right leg of the decider) 256bit ARG[4] = Destination address.. A free variable (not hashed to the address) 256bit ARG[5] = Value needed to be commited on chain, a free variable (to sign with the left leg of the decider) 256bit ARG[6] = Value needed to be commited on chain, a free variable (to sign with the right leg of the decider)
ADDRESS = SHA256(ARG[1] CAT ARG[2] CAT ARG[3]) CONDITION = ENTRENCHED(ARG[5]) && ENTRENCHED(ARG[6]) && (IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES(ARG[2...3], 0, ARG[5...6]) || ((ENTRENCHED(ARG[1]) && ENTRENCHED(ARG[4]) && IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES(ARG[2...3], (CHAINPOSITON(ARG[4]) - CHAINPOSITON(ARG[1]), ARG[5...6]))))) CONNECT_TO = IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES(ARG[2...3], 0, ARG[5...6]) ? ARG[1] : ARG[4] a. assume TRUE == IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES(ARG[2...3], 0, ARG[5...6]). CONDITION = ENTRENCHED(ARG[5]) && ENTRENCHED(ARG[6]) the CONDITION is just TRUE because those preimages ARG[5], ARG[6] needed to be entrenched assume FALSE == IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES(ARG[2...3], 0, ARG[5...6]) CONDITION = ENTRENCHED(ARG[5]) && ENTRENCHED(ARG[6]) && (((ENTRENCHED(ARG[1]) && ENTRENCHED(ARG[4]) && IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES(ARG[2...3], (CHAINPOSITON(ARG[4]) - CHAINPOSITON(ARG[1]), ARG[5...6]))))) only 16bit numbers are signeable by the decider therefore to make the CONDITION TRUE: - CHAINPOSITON(ARG[4]) - CHAINPOSITON(ARG[1]) must fit into 16bit int. - CONDITION forces all of the values ARG[5], ARG[6], ARG[1], ARG[4] to be entrenched in order to become TRUE - this turns them into CONSTANTS - this turns the CONDITION into CONSTANTLY TRUE. b. the CONNECT_TO can only change if IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES changes. by ADDRESS1 == ADDRESS2 we have ARG1[2...3] == ARG2[2...3] this forces ARG1[5...6] == ARG2[5...6] by 0 being a constant: therefore IF_DECIDER_SIGNS_VALUE_USING_PREIMAGES cannot change. therefore CONNECT_TO cannot change. Guess this primitive ![Grin](https://bitcointalk.org/Smileys/default/grin.gif) ARGCOUNT = 3 256bit ARG[1] = ALICE_ADDRESS 256bit ARG[2] = BOB_ADDRESS 256bit ARG[3] = ARBITRARY 256bit number ADDRESS = SHA256(ARG[1] CAT ARG[2] CAT ARG[3]) CONDITION = TRUE CONNECT_TO = SHA256( SHA256( SHA256(ARG[1] CAT ARG[2] CAT (ARG[3]+1)) CAT ARG[1] CAT 0.00000001 COMB ) CAT ARG[2] CAT 0.00000001 COMB ) a. CONDITION is always TRUE (end of proof) b. by contradiction: ADDR1 != ADDR2 || CONNECT_TO1 == CONNECT_TO2 if ADDR1 != ADDR2: then by SHA256 collision resistance ARGS1 == ARGS2: but ARGS1 != ARGS2 if we want to prove something if CONNECTTO1 == CONNECTTO2: then by SHA256 collision resistance ARGS1 == ARGS2: but ARGS1 != ARGS2 if we want to prove something and so: ADDR1 == ADDR2 && CONNECT_TO1 != CONNECT_TO2 and therefore: CONNECT_TO1 != CONNECT_TO2 EDIT1: added code blocks.
|
|
|
|
|