NUT for DLC execution #128

Closed
conduition wants to merge 21 commits from dlcs into main
conduition commented 2024-05-27 16:14:48 +00:00 (Migrated from github.com)

Depends on https://github.com/cashubtc/nuts/pull/127

Closes #122

Adds a NUT which enables DLC execution using a cashu mint as a blind intermediary. Based on this proposal.

Depends on https://github.com/cashubtc/nuts/pull/127 Closes #122 Adds a NUT which enables DLC execution using a cashu mint as a blind intermediary. Based on [this proposal](https://conduition.io/cryptography/ecash-dlc/).
conduition (Migrated from github.com) reviewed 2024-05-27 16:25:57 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
conduition (Migrated from github.com) commented 2024-05-27 16:25:57 +00:00

I think it should be safe to use a single blinding secret b to mask every locking point, like this

Ki_ = b * Ki

This would be easier on the client, as the wallet only needs to store one blinding secret per DLC instead of n secrets. We would not be exposing any additional information to the mint, because the mint only ever sees at most one blinded locking point, and thus could not use the common factor of b for any trickery.

Can somebody check me on this?

I think it should be safe to use a single blinding secret `b` to mask every locking point, like this ``` Ki_ = b * Ki ``` This would be easier on the client, as the wallet only needs to store one blinding secret per DLC instead of `n` secrets. We would not be exposing any additional information to the mint, because the mint only ever sees at most one blinded locking point, and thus could not use the common factor of `b` for any trickery. Can somebody check me on this?
conduition (Migrated from github.com) reviewed 2024-05-29 18:31:28 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
conduition (Migrated from github.com) commented 2024-05-29 18:31:28 +00:00

This could also be a hash preimage, or the dlog of Payout.pubkey.

This could also be a hash preimage, or the dlog of `Payout.pubkey`.
a1denvalu3 (Migrated from github.com) reviewed 2024-07-18 14:06:55 +00:00
@ -0,0 +394,4 @@
"dlc_root": "2db63c93043ab646836b38292ed4fcf209ba68307427a4b2a8621e8b1daeb8ed",
"outcome": {
"k": "8e935aec5668312be8f960a5ecc3c5dd290e39985970bfd093047df7f05cc9ec",
"P": "{\"361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\":\"10000\"}"
a1denvalu3 (Migrated from github.com) commented 2024-07-18 14:06:55 +00:00

shouldn't the value here be 1 as the lone payout gets the entirety of the funding amount?

shouldn't the value here be 1 as the lone payout gets the entirety of the funding amount?
a1denvalu3 (Migrated from github.com) reviewed 2024-07-18 14:07:33 +00:00
@ -0,0 +437,4 @@
"dlc_root": "2db63c93043ab646836b38292ed4fcf209ba68307427a4b2a8621e8b1daeb8ed",
"outcome": {
"timeout": 1716777419,
"P": "{\"361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\":\"10000\"}"
a1denvalu3 (Migrated from github.com) commented 2024-07-18 14:07:32 +00:00

Also here

Also here
a1denvalu3 (Migrated from github.com) reviewed 2024-07-18 17:13:41 +00:00
@ -0,0 +220,4 @@
"registrations": [
{
"dlc_root": "2db63c93043ab646836b38292ed4fcf209ba68307427a4b2a8621e8b1daeb8ed",
"funding_amount": <int>,
a1denvalu3 (Migrated from github.com) commented 2024-07-18 17:13:41 +00:00

So all the provided inputs must be signed by keys of the same denomination? Or is just funding_amount in sats?

So all the provided inputs must be signed by keys of the same denomination? Or is just funding_amount in sats?
conduition (Migrated from github.com) reviewed 2024-07-19 05:25:20 +00:00
@ -0,0 +394,4 @@
"dlc_root": "2db63c93043ab646836b38292ed4fcf209ba68307427a4b2a8621e8b1daeb8ed",
"outcome": {
"k": "8e935aec5668312be8f960a5ecc3c5dd290e39985970bfd093047df7f05cc9ec",
"P": "{\"361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5\":\"10000\"}"
conduition (Migrated from github.com) commented 2024-07-19 05:25:20 +00:00

It can be any positive integer. Since payout values are computed by relative weights, this number can be anything if there is only one recipient in the payout weights map.

It can be any positive integer. Since payout values are computed by relative weights, this number can be anything if there is only one recipient in the payout weights map.
conduition (Migrated from github.com) reviewed 2024-07-19 05:33:54 +00:00
@ -0,0 +220,4 @@
"registrations": [
{
"dlc_root": "2db63c93043ab646836b38292ed4fcf209ba68307427a4b2a8621e8b1daeb8ed",
"funding_amount": <int>,
conduition (Migrated from github.com) commented 2024-07-19 05:33:54 +00:00

input proofs can be in any combination of denominations, as per the usual rules of swap/melt operations.

The new thing here is the funding_amount which is an explicit indicator to the mint, saying "i want to fund this DLC with exactly this much money". The value of all the input proofs will need to meet that threshold - or more if the mint charges fees. (see L254 for how fees are computed)

Note also that the threshold tag in the DLC secret is compared against the funding_amount

input proofs can be in any combination of denominations, as per the usual rules of swap/melt operations. The new thing here is the `funding_amount` which is an explicit indicator to the mint, saying "i want to fund this DLC with exactly this much money". The value of all the input proofs will need to meet that threshold - or more if the mint charges fees. (see L254 for how fees are computed) Note also that the `threshold` tag in the `DLC` secret is compared against the `funding_amount`
a1denvalu3 (Migrated from github.com) reviewed 2024-07-19 05:48:49 +00:00
@ -0,0 +220,4 @@
"registrations": [
{
"dlc_root": "2db63c93043ab646836b38292ed4fcf209ba68307427a4b2a8621e8b1daeb8ed",
"funding_amount": <int>,
a1denvalu3 (Migrated from github.com) commented 2024-07-19 05:48:49 +00:00

Yes I understand this, but how do we know what denomination the funding amount is in? I, for now, just assumed it is always sats but maybe it should be specified in each registration as an additional field

Yes I understand this, but how do we know what denomination the funding amount is in? I, for now, just assumed it is always sats but maybe it should be specified in each registration as an additional field
conduition (Migrated from github.com) reviewed 2024-07-19 06:14:39 +00:00
@ -0,0 +220,4 @@
"registrations": [
{
"dlc_root": "2db63c93043ab646836b38292ed4fcf209ba68307427a4b2a8621e8b1daeb8ed",
"funding_amount": <int>,
conduition (Migrated from github.com) commented 2024-07-19 06:14:39 +00:00

Oh sorry, I see what you mean. I forgot about multi-currency support. Yes, good idea, I added a unit field to disambiguate that for the mint. This is also something the mint needs to store (probably easier to just have different tables for DLCs funded under different units than to concretely store the unit as a string)

Oh sorry, I see what you mean. I forgot about multi-currency support. Yes, good idea, [I added a `unit` field to disambiguate that for the mint](https://github.com/cashubtc/nuts/pull/128/commits/e10d9f2b3e6cc13d2f931fb6affdbbaf8ed4ea0b). This is also something the mint needs to store (probably easier to just have different tables for DLCs funded under different `unit`s than to concretely store the unit as a string)
conduition commented 2024-07-30 15:14:00 +00:00 (Migrated from github.com)

Note to self todo:

  • Remove the atomic flag. It adds an unnecessary database-rollback requirement to the implementation and realistically very few users will need to register or resolve multiple DLCs atomically.
  • Instead of a single dedicated key for funding proof signatures, use the first key from the relevant unit's active keyset
Note to self todo: - Remove the atomic flag. It adds an unnecessary database-rollback requirement to the implementation and realistically very few users will need to register or resolve multiple DLCs atomically. - Instead of a single dedicated key for funding proof signatures, use the first key from the relevant unit's active keyset
a1denvalu3 (Migrated from github.com) reviewed 2024-08-02 17:57:11 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
a1denvalu3 (Migrated from github.com) commented 2024-08-02 17:57:11 +00:00

Can't we just do:

T = hash_to_curve(Settlement.outcome.timeout.to_bytes(4, 'big'))
P = hash_to_curve(Settlement.outcome.P.encode("utf-8"))
leaf_hash = sha256((T+P).serialize())
assert merkle_verify(dlc_root, Settlement.merkle_proof, leaf_hash)

?

Also just noticed the unix epoch is cut to 4 bytes: this will bug in ~2035 when the epoch is a number bigger than 2^32

Can't we just do: ```python T = hash_to_curve(Settlement.outcome.timeout.to_bytes(4, 'big')) P = hash_to_curve(Settlement.outcome.P.encode("utf-8")) leaf_hash = sha256((T+P).serialize()) assert merkle_verify(dlc_root, Settlement.merkle_proof, leaf_hash) ``` ? Also just noticed the unix epoch is cut to 4 bytes: this will bug in ~2035 when the epoch is a number bigger than 2^32
a1denvalu3 (Migrated from github.com) reviewed 2024-08-02 18:03:26 +00:00
@ -0,0 +423,4 @@
leaf_hash = SHA256(Settlement.outcome.k * G || Settlement.outcome.P)
assert merkle_verify(dlc_root, Settlement.merkle_proof, leaf_hash)
```
a1denvalu3 (Migrated from github.com) commented 2024-08-02 18:03:26 +00:00

see my other comment on the timeout redemption

see my other comment on the timeout redemption
conduition (Migrated from github.com) reviewed 2024-08-03 17:13:24 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
conduition (Migrated from github.com) commented 2024-08-03 17:13:24 +00:00

Also just noticed the unix epoch is cut to 4 bytes: this will bug in ~2035 when the epoch is a number bigger than 2^32

Correction: it will be a bug in 2106, since 0xFFFFFFFF is an unsigned integer. Although it probably won't matter, i'd be open to using a 64-bit encoding. Can't hurt

> Also just noticed the unix epoch is cut to 4 bytes: this will bug in ~2035 when the epoch is a number bigger than 2^32 Correction: it will be a bug in _2106,_ since 0xFFFFFFFF is an unsigned integer. Although it probably won't matter, i'd be open to using a 64-bit encoding. Can't hurt
conduition (Migrated from github.com) reviewed 2024-08-03 17:53:55 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
conduition (Migrated from github.com) commented 2024-08-03 17:53:55 +00:00

Can't we just do:

Possibly, yes, but I think my approach is faster (one fewer hash_to_curve invocations, and one less EC point addition op).

It also lends itself better to formal proofs of security: The client's goal with this procedure is to prove to the mint that Settlement.outcome.timeout and Settlement.outcome.P were committed to at registration time (and also that they are one of the payees in P).

If you define leaf_hash = sha256(hash_to_curve(t)+hash_to_curve(P)), then a formal proof of soundness for the server would need an extra assumption - namely that for any point E, it is infeasible to find two inputs x and y such that H'(x) + H'(y) = E (where H' is the hash_to_curve function). I'm not sure whether that statement can be proven. Wagner's Algorithm could possibly be applied to reduce the amount of work needed to forge a fraudulent proof. I don't think it would be possible/practical but still... it'd be safer and simpler to just do sha256(T || P) instead. That is pretty easy to prove secure.

> Can't we just do: Possibly, yes, but I think my approach is faster (one fewer `hash_to_curve` invocations, and one less EC point addition op). It also lends itself better to formal proofs of security: The client's goal with this procedure is to prove to the mint that `Settlement.outcome.timeout` and `Settlement.outcome.P` were committed to at registration time (and also that they are one of the payees in `P`). If you define `leaf_hash = sha256(hash_to_curve(t)+hash_to_curve(P))`, then a formal proof of soundness for the server would need an extra assumption - namely that for any point $E$, it is infeasible to find two inputs $x$ and $y$ such that $H'(x) + H'(y) = E$ (where $H'$ is the `hash_to_curve` function). I'm not sure whether that statement can be proven. [Wagner's Algorithm](https://conduition.io/cryptography/wagner/#Wagner%E2%80%99s-Algorithm) could _possibly_ be applied to reduce the amount of work needed to forge a fraudulent proof. I don't think it would be possible/practical but still... it'd be safer and simpler to just do `sha256(T || P)` instead. That is pretty easy to prove secure.
conduition (Migrated from github.com) reviewed 2024-08-03 17:58:41 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
conduition (Migrated from github.com) commented 2024-08-03 17:58:41 +00:00

I didn't mention this explicitly in the spec, but the reason I chose to do hash_to_curve(timeout) is because it allows implementations to strongly type the leaves of the merkle tree as tuples of (Point, String). A simpler approach, such as:

leaf_hash = sha256(Settlement.outcome.timeout.to_bytes() || Settlement.outcome.P)

...would require leaf hashes be expressed as a union of either (Time, string) or (Point, string).

I didn't mention this explicitly in the spec, but the reason I chose to do `hash_to_curve(timeout)` is because it allows implementations to strongly type the leaves of the merkle tree as tuples of `(Point, String)`. A simpler approach, such as: ``` leaf_hash = sha256(Settlement.outcome.timeout.to_bytes() || Settlement.outcome.P) ``` ...would require leaf hashes be expressed as a union of either `(Time, string)` or `(Point, string)`.
a1denvalu3 (Migrated from github.com) reviewed 2024-08-03 19:24:57 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
a1denvalu3 (Migrated from github.com) commented 2024-08-03 19:24:57 +00:00

for any point E it is infeasible to find two inputs x and y such that H'(x) + H'(y) = E

I think this is the assumption upon which the whole Chaumian e-cash scheme relies, without the addition. If you have a point E of which you know the discrete logarithm (eG = E), and you can find an input x such that H'(x) = E then you break the scheme because you can forge any proof you want.

Wagner algorithm

I am familiar with Wagner's algorithm.

Having said this, it's a non-issue. We can keep it like this. Better to have something formally provable.

> for any point $E$ it is infeasible to find two inputs $x$ and $y$ such that $H'(x) + H'(y) = E$ I think this is the assumption upon which the whole Chaumian e-cash scheme relies, without the addition. If you have a point $E$ of which you know the discrete logarithm ($eG = E$), and you can find an input $x$ such that $H'(x) = E$ then you break the scheme because you can forge any proof you want. > Wagner algorithm I am familiar with Wagner's algorithm. Having said this, it's a non-issue. We can keep it like this. Better to have something formally provable.
conduition (Migrated from github.com) reviewed 2024-08-04 16:11:43 +00:00
@ -0,0 +1,675 @@
NUT-DLC: Discreet Log Contracts
conduition (Migrated from github.com) commented 2024-08-04 16:11:42 +00:00

Updated to use a 64-bit timestamp for futureproofness

Updated to use a 64-bit timestamp for futureproofness
conduition commented 2024-08-04 18:21:25 +00:00 (Migrated from github.com)

Added the following spec changes:

Added the following spec changes: - No more 'atomic' flags https://github.com/cashubtc/nuts/pull/128/commits/ce22a20b4de9374d14f2dfb387c4564885727be6 - Timeout timestamps are now encoded as uint64 instead of uint32 https://github.com/cashubtc/nuts/pull/128/commits/b67e62a5686e468513f1f76e4181983efba5ade1 - Add recommendations for clients to avoid being scammed by replay attacks https://github.com/cashubtc/nuts/pull/128/commits/6990d591e7cc1b844a6a52c392d34c609d79cebf - Improve security of the `funding_proof` signature by the mint. It now commits to the `funding_amount` explicitly, and to the funding `unit` implicitly, by using a signing key from an active keyset instead of a dedicated key. https://github.com/cashubtc/nuts/pull/128/commits/95f47ba6f71ef2c525d06afc0df80e4d851ba7eb https://github.com/cashubtc/nuts/pull/128/commits/a86a4e8ce0b9a76ce9b242d6c2c2ab846b3e1955
a1denvalu3 (Migrated from github.com) reviewed 2024-09-14 09:38:04 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
a1denvalu3 (Migrated from github.com) commented 2024-09-14 09:38:04 +00:00

@conduition I think here it should be sum([out.amount for out in Payout.outputs]) != eligible_amount where eligible_amount is calculated as follows:

denom = sum(debts.values())
nom = debts[payout.pubkey]
eligible_amount = int(nom / denom * funding_amount)

Also, I was thinking we can discriminate based on whether the requested amount is equal or less than the eligible amount:

  • if it's equal, we drop the pubkey entry from the debts map
  • if it's less, we adjust the debts map so that the pubkey entry displays a weight value calculated such that new_eligible_amount == old_eligible_amount - requested_amount
@conduition I think here it should be `sum([out.amount for out in Payout.outputs]) != eligible_amount` where `eligible_amount` is calculated as follows: ```python denom = sum(debts.values()) nom = debts[payout.pubkey] eligible_amount = int(nom / denom * funding_amount) ``` Also, I was thinking we can discriminate based on whether the requested amount is equal or less than the eligible amount: * if it's equal, we drop the `pubkey` entry from the debts map * if it's less, we adjust the debts map so that the `pubkey` entry displays a weight value calculated such that `new_eligible_amount == old_eligible_amount - requested_amount`
conduition (Migrated from github.com) reviewed 2024-09-14 17:46:49 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
conduition (Migrated from github.com) commented 2024-09-14 17:46:48 +00:00

I think here it should be sum([out.amount for out in Payout.outputs]) != eligible_amount where eligible_amount is calculated as follows:

denom = sum(debts.values())
nom = debts[payout.pubkey]
eligible_amount = int(nom / denom * funding_amount)

Earlier in the document, we define debts as follows:

weights = json.loads(Settlement.outcome.P)
weight_sum = sum(weights.values())
debts = dict(((pubkey, funding_amount * weight // weight_sum) for pubkey, weight in weights.items()))

You can view debts as a mapping from weights[pubkey] to weights[pubkey] / sum(weights.values()) * funding_amount. Because sum((x / sum(set) for x in set)) == 1 for any set, we can assert:

sum(debts.values()) ~= funding_amount

(approximately, due to integer division rounding)

In your suggestion you have:

eligible_amount = int(debts[payout.pubkey] / sum(debts.values()) * funding_amount)

But as i just showed, sum(debts.values()) is approximately equivalent to funding_amount. Simplify your definition of eligible_amount and you'll find it's the same as debts[payout.pubkey]:

debts[payout.pubkey] / sum(debts.values()) * funding_amount
debts[payout.pubkey] / funding_amount * funding_amount
debts[payout.pubkey]
> I think here it should be `sum([out.amount for out in Payout.outputs]) != eligible_amount` where eligible_amount is calculated as follows: > > ``` > denom = sum(debts.values()) > nom = debts[payout.pubkey] > eligible_amount = int(nom / denom * funding_amount) > ``` Earlier in the document, we define `debts` as follows: ```python weights = json.loads(Settlement.outcome.P) weight_sum = sum(weights.values()) debts = dict(((pubkey, funding_amount * weight // weight_sum) for pubkey, weight in weights.items())) ``` You can view `debts` as a mapping from `weights[pubkey]` to `weights[pubkey] / sum(weights.values()) * funding_amount`. Because `sum((x / sum(set) for x in set)) == 1` for any `set`, we can assert: ```python sum(debts.values()) ~= funding_amount ``` (approximately, due to integer division rounding) In your suggestion you have: ```python eligible_amount = int(debts[payout.pubkey] / sum(debts.values()) * funding_amount) ``` But as i just showed, `sum(debts.values())` is approximately equivalent to `funding_amount`. Simplify your definition of `eligible_amount` and you'll find it's the same as `debts[payout.pubkey]`: ```python debts[payout.pubkey] / sum(debts.values()) * funding_amount debts[payout.pubkey] / funding_amount * funding_amount debts[payout.pubkey] ```
conduition (Migrated from github.com) reviewed 2024-09-14 17:47:41 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
conduition (Migrated from github.com) commented 2024-09-14 17:47:41 +00:00

Also, I was thinking we can discriminate based on whether the requested amount is equal or less than the eligible amount:

Interesting. Is there any reason (other than a computational error) why a wallet wouldn't want to claim its full payout at once?

> Also, I was thinking we can discriminate based on whether the requested amount is equal or less than the eligible amount: Interesting. Is there any reason (other than a computational error) why a wallet wouldn't want to claim its full payout at once?
a1denvalu3 (Migrated from github.com) reviewed 2024-09-14 19:09:58 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
a1denvalu3 (Migrated from github.com) commented 2024-09-14 19:09:58 +00:00

Aw, man. I think I might have misinterpreted/read too quickly that part of the spec. I thought the debts map was just the Settlement.outcome.P once it was validated (BTW that's also what I have implemented).

Any reason why it can't be?

[EDIT: forget it. much simplier your way]

Aw, man. I think I might have misinterpreted/read too quickly that part of the spec. I thought the debts map was just the `Settlement.outcome.P` once it was validated (BTW that's also what I have implemented). Any reason why it can't be? [EDIT: forget it. much simplier your way]
a1denvalu3 (Migrated from github.com) reviewed 2024-09-14 19:15:22 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
a1denvalu3 (Migrated from github.com) commented 2024-09-14 19:15:22 +00:00

Also, I was thinking we can discriminate based on whether the requested amount is equal or less than the eligible amount:

Interesting. Is there any reason (other than a computational error) why a wallet wouldn't want to claim its full payout at once?

It's just something that can happen. Do we let the mint just rug the claimant in that case? I thought it might be better not to.

> > Also, I was thinking we can discriminate based on whether the requested amount is equal or less than the eligible amount: > > Interesting. Is there any reason (other than a computational error) why a wallet wouldn't want to claim its full payout at once? It's just something that can happen. Do we let the mint just rug the claimant in that case? I thought it might be better not to.
conduition (Migrated from github.com) reviewed 2024-09-15 14:19:47 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
conduition (Migrated from github.com) commented 2024-09-15 14:19:47 +00:00

Any reason why it can't be?

Technically you could, but you'd also have to save the weight_sum. It's simpler to calculate the debts at settlement time rather than at claim time while you still have access to the full weights map.

It's just something that can happen. Do we let the mint just rug the claimant in that case? I thought it might be better not to.

If the claimant's client is buggy for some reason and won't compute the correct debt amount on its own, then a claimant could just use GET /v1/dlc/status/{dlc_root} to see the correct debts[pubkey] value and withdraw directly using a different client. But integer arithmetic is deterministic regardless of programming language, so this shouldn't happen as long as clients follow the spec.

As for specifically why i'm trying to avoid partial claims, it's because it would allow one more vector for clients to DoS the mint (by consuming space with pubkeys for cheap). This is something I was hoping to clean up once we had a working implementation we can test against.

> Any reason why it can't be? Technically you could, but you'd also have to save the `weight_sum`. It's simpler to calculate the debts at settlement time rather than at claim time while you still have access to the full `weights` map. > It's just something that can happen. Do we let the mint just rug the claimant in that case? I thought it might be better not to. If the claimant's client is buggy for some reason and won't compute the correct debt amount on its own, then a claimant could just use `GET /v1/dlc/status/{dlc_root}` to see the correct `debts[pubkey]` value and withdraw directly using a different client. But integer arithmetic is deterministic regardless of programming language, so this shouldn't happen as long as clients follow the spec. As for specifically why i'm trying to avoid partial claims, it's because it would allow one more vector for clients to DoS the mint (by consuming space with pubkeys for cheap). This is something I was hoping to clean up once we had a working implementation we can test against.
a1denvalu3 (Migrated from github.com) reviewed 2024-09-16 16:45:41 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
a1denvalu3 (Migrated from github.com) commented 2024-09-16 16:45:41 +00:00

This is something I was hoping to clean up once we had a working implementation we can test against.

Right now it should do everything. It surely has a million problems but you can test it if you want.

> This is something I was hoping to clean up once we had a working implementation we can test against. Right now it should do everything. It surely has a million problems but you can test it if you want.
a1denvalu3 (Migrated from github.com) reviewed 2024-09-16 16:48:06 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
a1denvalu3 (Migrated from github.com) commented 2024-09-16 16:48:06 +00:00

As for specifically why i'm trying to avoid partial claims, it's because it would allow one more vector for clients to DoS the mint (by consuming space with pubkeys for cheap). This is something I was hoping to clean up once we had a working implementation we can test against.

Right now if the DLC is overfunded, the mint automatically adjusts the funding amount. So one client might unknowingly provide an amount which doesn't exactly match their eligible amount, thereby failing the transaction.

> As for specifically why i'm trying to avoid partial claims, it's because it would allow one more vector for clients to DoS the mint (by consuming space with pubkeys for cheap). This is something I was hoping to clean up once we had a working implementation we can test against. Right now if the DLC is overfunded, the mint automatically adjusts the funding amount. So one client might unknowingly provide an amount which doesn't exactly match their eligible amount, thereby failing the transaction.
a1denvalu3 (Migrated from github.com) approved these changes 2024-09-16 17:25:47 +00:00
conduition (Migrated from github.com) reviewed 2024-09-17 17:00:23 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
conduition (Migrated from github.com) commented 2024-09-17 17:00:23 +00:00

So one client might unknowingly provide an amount which doesn't exactly match their eligible amount, thereby failing the transaction.

If a DLC is overfunded, all clients involved in the DLC must know about it. The funding_amount is committed to in the DlcFundingProof that the funder uses to prove DLC registration. If for some reason a funder goes silent after registering and overfunding a DLC, their peer clients can simply use GET /v1/dlc/status/{dlc_root} to see the true funding amount. This endpoint will also explicitly return the debts map once the DLC is settled, allowing faulty clients to recover gracefully if the POST /v1/dlc/payout request fails due to a mismatching output sum.

I just don't see the point in allowing partial payouts when all these options are available. It feels like an invitation to a class of "missing money" bugs in client implementations where clients withdraw some money and leave the rest on the table unknowingly. It also allows one more way to DoS the mint which we'll have to clean up later.

> So one client might unknowingly provide an amount which doesn't exactly match their eligible amount, thereby failing the transaction. If a DLC is overfunded, all clients involved in the DLC must know about it. The `funding_amount` is committed to in the `DlcFundingProof` that the funder uses to prove DLC registration. If for some reason a funder goes silent after registering and overfunding a DLC, their peer clients can simply use `GET /v1/dlc/status/{dlc_root}` to see the true funding amount. This endpoint will also explicitly return the `debts` map once the DLC is settled, allowing faulty clients to recover gracefully if the `POST /v1/dlc/payout` request fails due to a mismatching output sum. I just don't see the point in allowing partial payouts when all these options are available. It feels like an invitation to a class of "missing money" bugs in client implementations where clients withdraw some money and leave the rest on the table unknowingly. It also allows one more way to DoS the mint which we'll have to clean up later.
conduition (Migrated from github.com) reviewed 2024-09-17 17:01:18 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
conduition (Migrated from github.com) commented 2024-09-17 17:01:18 +00:00

it's your call if you'd like to implement partial withdrawals (it's your code after all), but I would recommend against it.

it's your call if you'd like to implement partial withdrawals (it's your code after all), but I would recommend against it.
a1denvalu3 (Migrated from github.com) reviewed 2024-09-21 18:47:44 +00:00
@ -0,0 +536,4 @@
1. If `Payout.dlc_root` does not correspond to any known funded DLC, return an error.
1. If `Payout.dlc_root` corresponds to a known DLC, but that DLC has not been settled, return an error.
1. If `Payout.pubkey` is not a key in the `debts` map, return an error.
1. If `sum([out.amount for out in Payout.outputs]) != debts[Payout.pubkey]`, return an error.
a1denvalu3 (Migrated from github.com) commented 2024-09-21 18:47:44 +00:00

Ok ok. I'll change it back.

Ok ok. I'll change it back.
gudnuf (Migrated from github.com) reviewed 2024-11-19 15:34:46 +00:00
@ -0,0 +37,4 @@
## Payout Structures
Payout structures are serialized dictionaries which map `xonly_pubkey -> weight`.
gudnuf (Migrated from github.com) commented 2024-10-27 10:06:48 +00:00

Can we make the public keys 33-bytes to be consistent with the rest of Cashu? @lollerfirst already implemented this with 33-byte keys.

Can we make the public keys 33-bytes to be consistent with the rest of Cashu? @lollerfirst already implemented this with 33-byte keys.
conduition (Migrated from github.com) reviewed 2024-11-23 19:31:18 +00:00
@ -0,0 +37,4 @@
## Payout Structures
Payout structures are serialized dictionaries which map `xonly_pubkey -> weight`.
conduition (Migrated from github.com) commented 2024-11-23 19:31:18 +00:00

The use of 33-byte pubkeys for BIP340 signatures was a mistake. See https://github.com/cashubtc/nuts/issues/133. New NUTs should use xonly pubkeys when validating BIP340 signatures

The use of 33-byte pubkeys for BIP340 signatures was a mistake. See https://github.com/cashubtc/nuts/issues/133. New NUTs should use xonly pubkeys when validating BIP340 signatures
thesimplekid commented 2025-05-20 14:48:48 +00:00 (Migrated from github.com)

Closing as there is no active work. Please reopen if work continues.

Closing as there is no active work. Please reopen if work continues.

Pull request closed

Sign in to join this conversation.
No description provided.