-
Notifications
You must be signed in to change notification settings - Fork 126
fidelity bonds (proof of concept) #1120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There are two primary benefits of using P2PKH over P2PK for outputs (when not wrapped in a P2SH):
Given that, I would argue that it is generally strictly worse to wrap a P2PKH in a P2SH because:
One case where wrapped P2PKH can make sense is if the redeem script must be revealed publicly prior to redemption, but the public key(s) it refers to are not needed until redemption time. However, in this particular case, as you noted, the public key is required for the DEX instance(s) to validate the bond. That implies both the redeem script and the public key must be revealed to those DEX instance(s) via an out-of-band mechanism, and therefore wrapped P2PKH would provide no additional security in that regard. The redeem script and public keys will only be known to the originator and the DEX instance(s) that are validating them, so as long as neither reveals them publicly, the public keys will also only known to them. |
Thanks for providing that input. It confirms what I had concluded that there is actually nothing to be gained from wrapping p2pkh in p2sh (assuming server can't be trusted to keep the pubkey secret). I had started with p2pk instead of p2pkh and will revert it for the reasons you cited, but also because it simplifies part of the validation (direct pubkey comparision after RecoverCompact, rather than having to hash160 (Decred's way!) the pubkey).
I think this is what even made me consider wrapping p2pkh, but at the end of the day, I don't think "trusting" the server to not reveal the pubkey is smart. Plus in a mesh config, it's likely going to be necessary to have it propagated between DEX nodes. |
Right. I didn't explicitly state it, but what I also meant to imply by that final statement is that the server needs the public key to validate things anyway, so if they are going to reveal the public keys by way of the P2PK redeem script, they can just reveal the public keys directly too. It's the same trust model regardless. |
Just did a challenging rebase after HD keys and stdaddr PRs were merged. I've not retested with this however, so this is a more "drafty" than before. It's probably broken at the moment. The
Also, |
Superseded by #1480 |
Summary
Status: Working. Tests need fixing. Some frontend issues to address.
See TODOs.
Replaces PR #1017, tossing out 2,306 loc for a better solution.
This PR replaces the registration fee, which was paid to an address provided by the server operator, with a time-locked fidelity bond, which is redeemable by the user who posted the bond after a certain time. This creates a time cost to use DCRDEX instead of a monetary cost.
The DEX considers a user's bond as active when the bond script's
lockTime
(when it can be redeemed by the user) is at leastbondExpiry
in the future. That is, the bond remains locked for a period even after it becomes inactive for DEX purposes.This also introduces an account tier system. Previously, paying the registration fee would mark the account paid, and it could be closed permanently if the user's violations exceed a certain score (binary and permanent). Now, posting a bond increases a user's tier, which offsets violations and in future work will also scale user order limits. When a bond expires, their tier is reduced. To trade, a user must have a tier > 0.
Bond Transaction Structure
There must be at least two outputs: the bond output (P2SH) and an account commitment (nulldata).
See:
dex/networks/dcr.ExtractBondDetails
client/asset/dcr.(*ExchangeWallet).{MakeBondTx,RefundBond}
server/assset/dcr.ParseBondTx
Time-locked Bond (output 0)
Output 0 of the fidelity bond transaction must be a P2SH output paying to the bond script.
The redeem script looks similar to the refund path of an atomic swap script:
<locktime[4]> OP_CHECKLOCKTIMEVERIFY OP_DROP <pubkey[33]> OP_CHECKSIG
The above uses P2PK, and we can do P2PKH instead, but since part of validation requires the pubkey we probably don't gain anything from this:
<locktime[4]> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash[20]> OP_EQUALVERIFY OP_CHECKSIG
I've tested both formats, and I will probably switch to the non-hashed version because validation becomes even more asset-specific given hash160 is not a standard (e.g. ripemd160 of either sha256 for Bitcoin or blake256 for Decred).
Server validates that this P2SH output actually pays to the provided redeem script.
The pubkey referenced by the script need not be controlled by the user's wallet, but in this implementation it comes from their wallet. The client could even use their account pubkey in the script, but for security reasons it is best not to reuse pubkeys in this bond outputs, which are unspent on the blockchain for long periods.
The
lockTime
must be after a bond expiry time, thelockTimeThreshold
. A bond becomes expired for a DEX account when duration untillockTime
is less than abondExpiry
duration, which is on the order of a month. This, a user should create a bond with alockTime
at leastbondExpiry
in the future, but a practicallockTime
would be more like 2xbondExpiry
in the future so the account is active on the DEX forbondExpiry
.User must demonstrate ownership of the locked funds: sign something with private key corresponding to the pubkey in the TL redeemscript.
DEX Account commitment (output 1)
The account commitment OP_RETURN output should reference the account ID that corresponds to the account pubkey in the
postbond
request.Having it in the raw like this allows the txn alone to identify the account, and without requiring the bond output pay to the account's private keys.
Protocol
New
postbond
route replacesregister
, but with no fee address and instead a txn with time-locked output (P2SH) and a DEX account commitment output (OP_RETURN).postbond
need not be used only for new accounts. Existing accounts may top-up / supplement their bond.The
config
response should be consulted for bond requirements, including: expiry time, supported bond assets, amounts, and required confirmations.The user may submit an initial
postbond
prior to broadcasting the bond transaction request containing the raw unsigned transaction for validation. The bond script to which the P2SH bond output pays must also be provided in thepostbond
request for validation by the DEX (correct script structure and lock time). The payload:Server must use provided account pubkey to verify signed
postbond
request, otherwise the user could be spamming or putting up a bond for a bogus account or someone else's account.Further validation is described in subsequent sections of this PR description.
The response payload:
After validation of the provided data, the DEX attempts to locate the bond transaction on the asset network:
PostBondResult
is sent immediately withConfs=-1
set. The bond is NOT stored in the DB. This was a courtesy pre-validation. The user should then broadcast the transaction, wait for the required number of confirmations, and sendpostbond
again.bondconfirmed
notification is sent.When the client receives the initial successful response prior to broadcast, they should then broadcast the bond txn, wait for the required confirmations, and resubmit
postbond
.Server must record all known bond txns and their amounts and expiry times. There is no explicit invalidation of bonds due to conduct, and instead a user's tier is a balance between bond "strength" (a function of the active bond total amounts) and their conduct score.
TODOs
Auto add bond in
client.Core
. There are comments in the code regarding an option to automatically postbond when previous bonds are about to expire on DEX. There are questions pertaining to timing, UI, and UX. Presently this PR requires using dexcctl to call theaddbond
RPC (or a direct http API call to the endpoint of the same name, which is created for said UI plans).Bond amount. It's pretty clear the bond amount has to be at least an order of magnitude more than the previous registration fee since you get it back, and it's only an opportunity cost having it locked up. I'm thinking like 5 DCR per tier (the bond increment).
Related to bond amount, the violation weights can use rebalancing. Specifically, increase weight for "no swap as taker" (currently 11, increase to 18 or 20) and "no redeem as maker" (currently 7, increase to 12 to 14), the violations that lock counterparty funds (20hr / 8 hr, respectively). Maybe even higher.
Make the tier system scale up order size limits. This was planned outside of fidelity bond work, in terms of conduct score, but it's more intuitive in terms of tier, which is a function of both account bond level and score.
Scale penalty (score change) with amount locked due to violation (e.g. lots/10 * violation score). This was planned regardless of fidelity bonds, but it dovetails nicely with the tier system.
Open Questions
What problems would be solved and/or create by using
OP_CHECKSEQUENCEVERIFY
(relative time lock) instead ofOP_CHECKLOCKTIMEVERIFY
?When and if accounts go away, would output 1 commit to a magic DEX network key instead of account ID?
Is this transaction structure and account commitment output sufficient to prevent txns from being dual purposed for other services with similar TL output structure?