Difference between revisions of "Pay server"
| Line 5: | Line 5: | ||
#payserver adds input funding + change output, and signs them, sends back to client | #payserver adds input funding + change output, and signs them, sends back to client | ||
#client finally signs their input, and broadcasts | #client finally signs their input, and broadcasts | ||
| − | If we want payserver to handle the broadcast itself right after signing for funding, we would need to adapt the flow to this (but maybe this would introduce concurrency issues since no signing on first ping would not activate _pendingUtxos array in payserver purse): | + | If we want payserver to handle the broadcast itself right after signing for funding, we would need to adapt the flow to this (but maybe this would introduce concurrency issues since no signing on first ping would not activate _pendingUtxos array in payserver purse, so we would need to add a new management for reserved utxos + timeout, and this would add one more round-trip so 200-800 ms of latency): |
#client builds, no funding, no sign, sends to payserver | #client builds, no funding, no sign, sends to payserver | ||
#payserver adds input funding + change output, no signing, sends back to client | #payserver adds input funding + change output, no signing, sends back to client | ||
#client signs their input, sends to payserver again | #client signs their input, sends to payserver again | ||
| − | #payserver signs their funding input + | + | #payserver signs their funding input + broadcast (ex with /finalize endpoint) |
| − | + | note: the only reason i'm considering the 2nd flow is bc i thought it might reduce race conditions, but since every run instance already manages its own internal queue for utxos & signing, it seems useless. Just make sure to have a different purse on each server (for ex if you have multiple payservers). | |
<p>'''Pourquoi pay() doit avoir lieu avant les autres signatures'''</p> | <p>'''Pourquoi pay() doit avoir lieu avant les autres signatures'''</p> | ||
<p>'''Engagement de signature''' : Les signatures Bitcoin hachent l’intégralité de la structure de la transaction (inputs, outputs, montants, scripts). Modifier quoi que ce soit après la signature invalide les signatures (le hash change).</p> | <p>'''Engagement de signature''' : Les signatures Bitcoin hachent l’intégralité de la structure de la transaction (inputs, outputs, montants, scripts). Modifier quoi que ce soit après la signature invalide les signatures (le hash change).</p> | ||
Revision as of 20:54, 20 January 2026
Signing Flow
Right now the flow is:
- client builds tx with no funding, doesn't sign anything, sends to payserver
- payserver adds input funding + change output, and signs them, sends back to client
- client finally signs their input, and broadcasts
If we want payserver to handle the broadcast itself right after signing for funding, we would need to adapt the flow to this (but maybe this would introduce concurrency issues since no signing on first ping would not activate _pendingUtxos array in payserver purse, so we would need to add a new management for reserved utxos + timeout, and this would add one more round-trip so 200-800 ms of latency):
- client builds, no funding, no sign, sends to payserver
- payserver adds input funding + change output, no signing, sends back to client
- client signs their input, sends to payserver again
- payserver signs their funding input + broadcast (ex with /finalize endpoint)
note: the only reason i'm considering the 2nd flow is bc i thought it might reduce race conditions, but since every run instance already manages its own internal queue for utxos & signing, it seems useless. Just make sure to have a different purse on each server (for ex if you have multiple payservers).
Pourquoi pay() doit avoir lieu avant les autres signatures
Engagement de signature : Les signatures Bitcoin hachent l’intégralité de la structure de la transaction (inputs, outputs, montants, scripts). Modifier quoi que ce soit après la signature invalide les signatures (le hash change).
Dans les transactions multi-parties :
La fonction pay() modifie la transaction (elle ajoute des inputs/outputs pour les frais) → elle doit donc intervenir avant les signatures finales sur ces nouvelles parties.
Si une partie signe en premier, puis que pay() ajoute des inputs → les signatures sont cassées (elles s’étaient engagées sur l’ancien hash).
SIGHASH_ANYONECANPAY permettrait théoriquement au client de signer avant le pay(), mais voici pourquoi Run n’utilise pas SIGHASH_ANYONECANPAY dans son code par défaut :
Le protocole Run (avant RunCraft) utilise exclusivement SIGHASH_ALL pour garantir l’intégrité des jigs.
Sécurité et engagement :
SIGHASH_ANYONECANPAY permet à n’importe qui d’ajouter des inputs plus tard sans casser les signatures existantes. C’est très utile pour des transactions collaboratives comme les atomic swaps ou les coinjoins, mais c’est risqué pour le système de jigs de Run.
Les jigs/tokens sont stateful (propriété, méthodes, références dans OP_RETURN, etc.), donc Run veut un engagement total sur la transaction pour empêcher toute manipulation.
Si un client signait avec ANYONECANPAY, un PayServer malveillant (ou un intermédiaire) pourrait ajouter des inputs/outputs malveillants (ex. : voler des fonds).
SIGHASH_ALL garantit que le signataire accepte exactement la forme de la transaction telle qu’elle est au moment de la signature.# Markdown syntax guide