Difference between revisions of "Pay server"
| Line 16: | Line 16: | ||
#client signs their input, sends to payserver again | #client signs their input, sends to payserver again | ||
#payserver signs their funding input + broadcast (ex with /finalize endpoint) | #payserver signs their funding input + broadcast (ex with /finalize endpoint) | ||
| − | + | == '''Pourquoi pay() doit avoir lieu avant les autres signatures''' == | |
| − | <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><p>Dans les transactions multi-parties :<br>La fonction <code>pay()</code> modifie la transaction (elle ajoute des inputs/outputs pour les frais) → elle '''doit''' donc intervenir '''avant''' les signatures finales sur ces nouvelles parties.</p><p>Si une partie signe en premier, puis que <code>pay()</code> ajoute des inputs → les signatures sont cassées (elles s’étaient engagées sur l’ancien hash).</p> |
| − | <p>Dans les transactions multi-parties :<br>La fonction <code>pay()</code> modifie la transaction (elle ajoute des inputs/outputs pour les frais) → elle '''doit''' donc intervenir '''avant''' les signatures finales sur ces nouvelles parties.</p> | + | === Pourquoi ne pas utiliser '''SIGHASH_ANYONECANPAY ?''' === |
| − | <p>Si une partie signe en premier, puis que <code>pay()</code> ajoute des inputs → les signatures sont cassées (elles s’étaient engagées sur l’ancien hash).</p> | + | <p>'''SIGHASH_ANYONECANPAY''' permettrait théoriquement au client de signer '''avant''' le <code>pay()</code>, mais voici pourquoi '''Run n’utilise pas SIGHASH_ANYONECANPAY''' dans son code par défaut :</p><p>Le protocole '''Run''' (avant RunCraft) utilise exclusivement '''SIGHASH_ALL''' pour garantir l’intégrité des jigs.</p><p>'''Sécurité et engagement''' :<br>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.</p><p>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.</p><p>Si un client signait avec ANYONECANPAY, un PayServer malveillant (ou un intermédiaire) pourrait ajouter des inputs/outputs malveillants (ex. : voler des fonds).</p><p>'''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</p> |
| − | + | ||
| − | <p>'''SIGHASH_ANYONECANPAY''' permettrait théoriquement au client de signer '''avant''' le <code>pay()</code>, mais voici pourquoi '''Run n’utilise pas SIGHASH_ANYONECANPAY''' dans son code par défaut :</p> | + | |
| − | <p>Le protocole '''Run''' (avant RunCraft) utilise exclusivement '''SIGHASH_ALL''' pour garantir l’intégrité des jigs.</p> | + | |
| − | <p>'''Sécurité et engagement''' :<br>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.</p> | + | ___ ACTUALLY this might not be exact because:<p>It depends on which base sighash flag you combine ANYONECANPAY with — ANYONECANPAY only changes the '''input''' commitment, not the output commitment. The output behavior is governed by the base type (ALL / SINGLE / NONE):</p> |
| − | <p>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.</p> | + | *<p>'''SIGHASH_ALL | ANYONECANPAY''' — all outputs (including your output0 with the OP_RETURN) are hashed into the sighash. Your signature commits to that exact OP_RETURN, so a counterparty changing it would invalidate your signature. Only the input list is left open (others can add more inputs).</p> |
| − | <p>Si un client signait avec ANYONECANPAY, un PayServer malveillant (ou un intermédiaire) pourrait ajouter des inputs/outputs malveillants (ex. : voler des fonds).</p> | + | *<p>'''SIGHASH_SINGLE | ANYONECANPAY''' — only the output at the ''same index'' as the input you're signing is committed. So if your input is at index 0, then yes, output0 (your OP_RETURN) is locked and can't be changed. But if your input is at a different index than 0, output0 is '''not''' committed at all and the other party is free to modify it.</p> |
| − | <p>'''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</p> | + | *<p>'''SIGHASH_NONE | ANYONECANPAY''' — no outputs are committed. Anyone can change any output, including output0, without invalidating your signature.</p> |
| + | <p>So: if you want to guarantee output0's OP_RETURN can't be tampered with by the next party, use '''SIGHASH_ALL | ANYONECANPAY''' (safest, commits everything except letting others add inputs), or make sure your signed input is at index 0 and use '''SIGHASH_SINGLE | ANYONECANPAY'''. Anything with SIGHASH_NONE, or a SINGLE where your input index ≠ 0, leaves output0 open to modification.</p><p>One gotcha with SIGHASH_SINGLE: if there's no output at the same index as your input, the "SIGHASH_SINGLE bug" applies in legacy Bitcoin (signs the value <code>0000...0001</code>) — BSV script interpreters may or may not preserve this quirk, worth testing against whatever node/library you're using.</p> | ||
Latest revision as of 18:48, 22 September 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 (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)
note: the only reason i'm considering this 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).
New flow would be:
- 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)
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).
Pourquoi ne pas utiliser SIGHASH_ANYONECANPAY ?
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
___ ACTUALLY this might not be exact because:
It depends on which base sighash flag you combine ANYONECANPAY with — ANYONECANPAY only changes the input commitment, not the output commitment. The output behavior is governed by the base type (ALL / SINGLE / NONE):
SIGHASH_ALL | ANYONECANPAY — all outputs (including your output0 with the OP_RETURN) are hashed into the sighash. Your signature commits to that exact OP_RETURN, so a counterparty changing it would invalidate your signature. Only the input list is left open (others can add more inputs).
SIGHASH_SINGLE | ANYONECANPAY — only the output at the same index as the input you're signing is committed. So if your input is at index 0, then yes, output0 (your OP_RETURN) is locked and can't be changed. But if your input is at a different index than 0, output0 is not committed at all and the other party is free to modify it.
SIGHASH_NONE | ANYONECANPAY — no outputs are committed. Anyone can change any output, including output0, without invalidating your signature.
So: if you want to guarantee output0's OP_RETURN can't be tampered with by the next party, use SIGHASH_ALL | ANYONECANPAY (safest, commits everything except letting others add inputs), or make sure your signed input is at index 0 and use SIGHASH_SINGLE | ANYONECANPAY. Anything with SIGHASH_NONE, or a SINGLE where your input index ≠ 0, leaves output0 open to modification.
One gotcha with SIGHASH_SINGLE: if there's no output at the same index as your input, the "SIGHASH_SINGLE bug" applies in legacy Bitcoin (signs the value 0000...0001) — BSV script interpreters may or may not preserve this quirk, worth testing against whatever node/library you're using.