* [bitcoindev] Censorship and Privacy in Chaumian ecash implementations
@ 2024-12-21 16:58 /dev /fd0
2024-12-21 21:52 ` 'conduition' via Bitcoin Development Mailing List
0 siblings, 1 reply; 3+ messages in thread
From: /dev /fd0 @ 2024-12-21 16:58 UTC (permalink / raw)
To: Bitcoin Development Mailing List
[-- Attachment #1.1: Type: text/plain, Size: 9476 bytes --]
Hi Bitcoin Developers,
This post is about a myth and some misleading things shared about ecash. It
is possible to censor specific users and none of the ecash implementation
are censorship resistant.
# Censorship Methods in Cashu
There are 2 ways to censor individual users in cashu:
1. P2PK
2. Authentication
## P2PK
Ecash tokens issued by cashu mints can be locked using public keys so that
it can only be redeemed by the user who owns the private key for it. This
links ecash with specific public keys and most implementation use nostr
keys for it. Most users are doxxed on nostr so they can be censored based
on their identity. Even if its linked to an anon they can be censored based
on their posts.
You can find relevant code snippets in [conditions.py][1] if using nutshell
for mint:
```python
class LedgerSpendingConditions:
def _verify_p2pk_spending_conditions(self, proof, secret):
if SecretKind(secret.kind) != SecretKind.P2PK:
return True
p2pk_secret = P2PKSecret.from_secret(secret)
pubkeys = [p2pk_secret.data] +
p2pk_secret.tags.get_tag_all("pubkeys")
if p2pk_secret.locktime and p2pk_secret.locktime < time.time():
refund_pubkeys = p2pk_secret.tags.get_tag_all("refund")
if not refund_pubkeys:
return True
return self._verify_secret_signatures(
proof, refund_pubkeys, proof.p2pksigs, 1
)
return self._verify_secret_signatures(
proof, pubkeys, proof.p2pksigs, p2pk_secret.n_sigs
)
def _verify_htlc_spending_conditions(self, proof, secret):
if SecretKind(secret.kind) != SecretKind.HTLC:
return True
htlc_secret = HTLCSecret.from_secret(secret)
if htlc_secret.locktime and htlc_secret.locktime < time.time():
refund_pubkeys = htlc_secret.tags.get_tag_all("refund")
if refund_pubkeys:
return self._verify_secret_signatures(
proof, refund_pubkeys, proof.p2pksigs, 1
)
return True
assert proof.htlcpreimage, TransactionError("no HTLC preimage
provided")
if not hashlib.sha256(bytes.fromhex(proof.htlcpreimage)).digest()
== bytes.fromhex(htlc_secret.data):
raise TransactionError("HTLC preimage does not match.")
hashlock_pubkeys = htlc_secret.tags.get_tag_all("pubkeys")
if not hashlock_pubkeys:
return True
return self._verify_secret_signatures(
proof, hashlock_pubkeys, proof.htlcsigs or [],
htlc_secret.n_sigs
)
def _verify_secret_signatures(self, proof, pubkeys, signatures,
n_sigs_required=1):
assert len(set(pubkeys)) == len(pubkeys), "pubkeys must be unique."
if not signatures:
raise TransactionError("no signatures in proof.")
if len(set(signatures)) != len(signatures):
raise TransactionError("signatures must be unique.")
n_sigs_required = n_sigs_required or 1
assert n_sigs_required > 0, "n_sigs must be positive."
assert len(signatures) >= n_sigs_required, f"not enough signatures
provided: {len(signatures)} < {n_sigs_required}."
n_valid_sigs_per_output = 0
for input_sig in signatures:
for pubkey in pubkeys:
if verify_schnorr_signature(
message=proof.secret.encode("utf-8"),
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
signature=bytes.fromhex(input_sig),
):
n_valid_sigs_per_output += 1
assert n_valid_sigs_per_output, "no valid signature provided for
input."
assert n_valid_sigs_per_output >= n_sigs_required, f"signature
threshold not met. {n_valid_sigs_per_output} < {n_sigs_required}."
return True
def _verify_input_spending_conditions(self, proof):
try:
secret = Secret.deserialize(proof.secret)
except Exception:
return True
if SecretKind(secret.kind) == SecretKind.P2PK:
return self._verify_p2pk_spending_conditions(proof, secret)
if SecretKind(secret.kind) == SecretKind.HTLC:
return self._verify_htlc_spending_conditions(proof, secret)
return True
def _verify_output_p2pk_spending_conditions(self, proofs, outputs):
try:
secrets_generic = [Secret.deserialize(p.secret) for p in proofs]
p2pk_secrets = [P2PKSecret.from_secret(secret) for secret in
secrets_generic]
except Exception:
return True
if not all([SecretKind(secret.kind) == SecretKind.P2PK for secret
in p2pk_secrets]):
return True
if not all([secret.sigflag == SigFlags.SIG_ALL for secret in
p2pk_secrets]):
return True
pubkeys_per_proof = [
[p2pk_secret.data] + p2pk_secret.tags.get_tag_all("pubkeys")
for p2pk_secret in p2pk_secrets
]
n_sigs_per_proof = [p2pk_secret.n_sigs for p2pk_secret in
p2pk_secrets]
for p2pk_secret in p2pk_secrets:
if p2pk_secret.locktime and p2pk_secret.locktime < time.time():
refund_pubkeys = p2pk_secret.tags.get_tag_all("refund")
if refund_pubkeys:
pubkeys_per_proof.append(refund_pubkeys)
n_sigs_per_proof.append(1)
if not pubkeys_per_proof:
return True
assert len({tuple(pubs_output) for pubs_output in
pubkeys_per_proof}) == 1, "pubkeys in all proofs must match."
assert len(set(n_sigs_per_proof)) == 1, "n_sigs in all proofs must
match."
pubkeys = pubkeys_per_proof[0]
n_sigs = n_sigs_per_proof[0] or 1
for output in outputs:
p2pksigs = output.p2pksigs
assert p2pksigs, "no signatures in output."
assert len(set(p2pksigs)) == len(p2pksigs), "duplicate
signatures in output."
n_valid_sigs_per_output = 0
for sig in p2pksigs:
for pubkey in pubkeys:
if verify_schnorr_signature(
message=bytes.fromhex(output.B_),
pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
signature=bytes.fromhex(sig),
):
n_valid_sigs_per_output += 1
assert n_valid_sigs_per_output, "no valid signature provided
for output."
assert n_valid_sigs_per_output >= n_sigs, f"signature threshold
not met. {n_valid_sigs_per_output} < {n_sigs}."
return True
def _verify_output_spending_conditions(self, proofs, outputs):
return self._verify_output_p2pk_spending_conditions(proofs, outputs)
```
## Authentication
Mints can enforce authentication at some point and do KYC for mint, melt,
swap etc. Users who refuse to KYC will not be able to use or redeem their
ecash tokens. Some of the KYCed users can be censored based on their
identity. This would also affect privacy.
gandlaf21 agrees this is possible however it is still marketed as
censorship resistant.
There was some discussion about it in a pull request and supertestnet also
shared his thoughts: https://github.com/bitcoinlayers/bitcoinlayers/pull/164
This whole debate started in May 2024 when cashu's twitter account
[announced][2] that they are considering adding an authentication in the
protocol as it is requested by regulated entities.
The authentication mechanism is shared in this [pull request][3] which
links each user with linkingkey and it will compromise privacy:
```
POST https://bob.com/v1/auth
Post Data:
{
action:"mint",
k1:"8278e1a48e61c261916791dabb6af760488e4f01932e11fe7054f59754e3de6e"
signature:c568f78e4b234a5f7d8c3b2a679e48d1234567890abcdef
linkingKey:7345786068584cd33000582ba87a9ddf77db5377c67910ab59d7e9a5f44
}
Response:
HTTP/1.1 200 OK
{
"access_token": "9876543210fedcba",
"token_type": "Bearer",
"expires_in": 3600
}
```
This pull request was closed last week and a new authentication mechanism
is proposed: https://github.com/cashubtc/nuts/pull/198
It uses clear and blind auth but users can still be censored with KYC based
on their identity. You can understand the details from this [comment][4].
## Conclusion
The authentication mechanisms shared above are not the only way mints can
restrict users as there is nothing in the protocol that stops mints from
using a custom authentication.
Introducing KYC in protocol is against freedom and privacy. These custodial
solutions might end up being another compliant ecash implementation like
[GNU Taler][5]. This would also make it easier for government agencies to
target other mints that do not comply.
[1]: https://github.com/cashubtc/nutshell/blob/main/cashu/mint/conditions.py
[2]: https://x.com/CashuBTC/status/1791001643019809146
[3]: https://github.com/cashubtc/nuts/pull/106
[4]: https://github.com/cashubtc/nuts/pull/198#issuecomment-2508706328
[5]: https://taler.net/en/index.html
/dev/fd0
floppy disk guy
--
You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups•com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/27b19012-20da-46a7-8a84-f90e0070aa77n%40googlegroups.com.
[-- Attachment #1.2: Type: text/html, Size: 11329 bytes --]
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [bitcoindev] Censorship and Privacy in Chaumian ecash implementations
2024-12-21 16:58 [bitcoindev] Censorship and Privacy in Chaumian ecash implementations /dev /fd0
@ 2024-12-21 21:52 ` 'conduition' via Bitcoin Development Mailing List
2024-12-21 23:03 ` /dev /fd0
0 siblings, 1 reply; 3+ messages in thread
From: 'conduition' via Bitcoin Development Mailing List @ 2024-12-21 21:52 UTC (permalink / raw)
To: /dev /fd0; +Cc: Bitcoin Development Mailing List
[-- Attachment #1.1.1: Type: text/plain, Size: 11948 bytes --]
Hi fd0,
For P2PK, the solution there seems deadly obvious: Just don't use raw unobfuscated Nostr keys to receive ecash. Tweak it, or use a new random key. This is a non-issue.
As for authentication systems, yes of course KYC-backed authentication would allow censorship, but that is an optional spec which mints aren't compelled to implement or use, even once the spec is finished. AFAIK no mint implementations have this system in code yet, so it's not even a feature at this point: just a proposed NUT document, sitting in draft status. Your assertion that "none of the ecash implementation are censorship resistant" is a blatantly false statement bereft of fact or depth.
Even if we fast forward several years when perhaps some ecash mint implementations do implement the authentication spec as a fully-formed feature ready to go... If a mint starts enforcing KYC, or any other badness like censoring by IP address, users are free to switch to a different non-KYC mint instance. At worst they might lose the money they already stored with the compromised mint, which is always a risk to keep in mind with ecash.
Your argument seems to be that introducing an opt-in feature spec like external authentication is inherently bad because it encourages/enables KYC in the first place, and that ecash devs should refuse to even standardize any protocol which enables KYC.
Well I have hard news for you: Governments don't care about whether compliance is "easy" or "standardized". If uncle sam wants your mint to enforce KYC, he won't balk just because the mint's code doesn't provide an easy way to do it. He'll give you a deadline to enforce KYC on your users, and if you can't prove you're compliant by then, you can kiss your business goodbye.
When faced with this choice, the ecash mint runner can either shut down their mint, or add KYC. Without an authentication system in place already, the mint runner would have to implement it all themselves to enforce KYC and stay in business. So ecash devs have proposed standardized auth systems which would give mint runners the option of knuckling under to KYC. I agree it would suck to be put to that choice, but giving them the freedom to choose is important and valid, and in no way compromises the integrity of other non-KYC mints who perhaps operate freely in more enlightened jurisdictions.
If it's not part of the NUT standards, and mint runners need it, then someone will just fork off and add it themselves. Better to pre-empt that and keep the standards from fragmenting - that's the point of standards.
-c
On Saturday, December 21st, 2024 at 9:58 AM, /dev /fd0 <alicexbtong@gmail•com> wrote:
> Hi Bitcoin Developers,
>
> This post is about a myth and some misleading things shared about ecash. It is possible to censor specific users and none of the ecash implementation are censorship resistant.
>
> # Censorship Methods in Cashu
>
> There are 2 ways to censor individual users in cashu:
>
> 1. P2PK
> 2. Authentication
>
> ## P2PK
>
> Ecash tokens issued by cashu mints can be locked using public keys so that it can only be redeemed by the user who owns the private key for it. This links ecash with specific public keys and most implementation use nostr keys for it. Most users are doxxed on nostr so they can be censored based on their identity. Even if its linked to an anon they can be censored based on their posts.
>
> You can find relevant code snippets in [conditions.py][1] if using nutshell for mint:
>
> ```python
> class LedgerSpendingConditions:
> def _verify_p2pk_spending_conditions(self, proof, secret):
> if SecretKind(secret.kind) != SecretKind.P2PK:
> return True
> p2pk_secret = P2PKSecret.from_secret(secret)
> pubkeys = [p2pk_secret.data] + p2pk_secret.tags.get_tag_all("pubkeys")
> if p2pk_secret.locktime and p2pk_secret.locktime < time.time():
> refund_pubkeys = p2pk_secret.tags.get_tag_all("refund")
> if not refund_pubkeys:
> return True
> return self._verify_secret_signatures(
> proof, refund_pubkeys, proof.p2pksigs, 1
> )
> return self._verify_secret_signatures(
> proof, pubkeys, proof.p2pksigs, p2pk_secret.n_sigs
> )
>
> def _verify_htlc_spending_conditions(self, proof, secret):
> if SecretKind(secret.kind) != SecretKind.HTLC:
> return True
> htlc_secret = HTLCSecret.from_secret(secret)
> if htlc_secret.locktime and htlc_secret.locktime < time.time():
> refund_pubkeys = htlc_secret.tags.get_tag_all("refund")
> if refund_pubkeys:
> return self._verify_secret_signatures(
> proof, refund_pubkeys, proof.p2pksigs, 1
> )
> return True
> assert proof.htlcpreimage, TransactionError("no HTLC preimage provided")
> if not hashlib.sha256(bytes.fromhex(proof.htlcpreimage)).digest() == bytes.fromhex(htlc_secret.data):
> raise TransactionError("HTLC preimage does not match.")
> hashlock_pubkeys = htlc_secret.tags.get_tag_all("pubkeys")
> if not hashlock_pubkeys:
> return True
> return self._verify_secret_signatures(
> proof, hashlock_pubkeys, proof.htlcsigs or [], htlc_secret.n_sigs
> )
>
> def _verify_secret_signatures(self, proof, pubkeys, signatures, n_sigs_required=1):
> assert len(set(pubkeys)) == len(pubkeys), "pubkeys must be unique."
> if not signatures:
> raise TransactionError("no signatures in proof.")
> if len(set(signatures)) != len(signatures):
> raise TransactionError("signatures must be unique.")
> n_sigs_required = n_sigs_required or 1
> assert n_sigs_required > 0, "n_sigs must be positive."
> assert len(signatures) >= n_sigs_required, f"not enough signatures provided: {len(signatures)} < {n_sigs_required}."
> n_valid_sigs_per_output = 0
> for input_sig in signatures:
> for pubkey in pubkeys:
> if verify_schnorr_signature(
> message=proof.secret.encode("utf-8"),
> pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
> signature=bytes.fromhex(input_sig),
> ):
> n_valid_sigs_per_output += 1
> assert n_valid_sigs_per_output, "no valid signature provided for input."
> assert n_valid_sigs_per_output >= n_sigs_required, f"signature threshold not met. {n_valid_sigs_per_output} < {n_sigs_required}."
> return True
>
> def _verify_input_spending_conditions(self, proof):
> try:
> secret = Secret.deserialize(proof.secret)
> except Exception:
> return True
> if SecretKind(secret.kind) == SecretKind.P2PK:
> return self._verify_p2pk_spending_conditions(proof, secret)
> if SecretKind(secret.kind) == SecretKind.HTLC:
> return self._verify_htlc_spending_conditions(proof, secret)
> return True
>
> def _verify_output_p2pk_spending_conditions(self, proofs, outputs):
> try:
> secrets_generic = [Secret.deserialize(p.secret) for p in proofs]
> p2pk_secrets = [P2PKSecret.from_secret(secret) for secret in secrets_generic]
> except Exception:
> return True
> if not all([SecretKind(secret.kind) == SecretKind.P2PK for secret in p2pk_secrets]):
> return True
> if not all([secret.sigflag == SigFlags.SIG_ALL for secret in p2pk_secrets]):
> return True
> pubkeys_per_proof = [
> [p2pk_secret.data] + p2pk_secret.tags.get_tag_all("pubkeys")
> for p2pk_secret in p2pk_secrets
> ]
> n_sigs_per_proof = [p2pk_secret.n_sigs for p2pk_secret in p2pk_secrets]
> for p2pk_secret in p2pk_secrets:
> if p2pk_secret.locktime and p2pk_secret.locktime < time.time():
> refund_pubkeys = p2pk_secret.tags.get_tag_all("refund")
> if refund_pubkeys:
> pubkeys_per_proof.append(refund_pubkeys)
> n_sigs_per_proof.append(1)
> if not pubkeys_per_proof:
> return True
> assert len({tuple(pubs_output) for pubs_output in pubkeys_per_proof}) == 1, "pubkeys in all proofs must match."
> assert len(set(n_sigs_per_proof)) == 1, "n_sigs in all proofs must match."
> pubkeys = pubkeys_per_proof[0]
> n_sigs = n_sigs_per_proof[0] or 1
> for output in outputs:
> p2pksigs = output.p2pksigs
> assert p2pksigs, "no signatures in output."
> assert len(set(p2pksigs)) == len(p2pksigs), "duplicate signatures in output."
> n_valid_sigs_per_output = 0
> for sig in p2pksigs:
> for pubkey in pubkeys:
> if verify_schnorr_signature(
> message=bytes.fromhex(output.B_),
> pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
> signature=bytes.fromhex(sig),
> ):
> n_valid_sigs_per_output += 1
> assert n_valid_sigs_per_output, "no valid signature provided for output."
> assert n_valid_sigs_per_output >= n_sigs, f"signature threshold not met. {n_valid_sigs_per_output} < {n_sigs}."
> return True
>
> def _verify_output_spending_conditions(self, proofs, outputs):
> return self._verify_output_p2pk_spending_conditions(proofs, outputs)
> ```
>
> ## Authentication
>
> Mints can enforce authentication at some point and do KYC for mint, melt, swap etc. Users who refuse to KYC will not be able to use or redeem their ecash tokens. Some of the KYCed users can be censored based on their identity. This would also affect privacy.
>
> gandlaf21 agrees this is possible however it is still marketed as censorship resistant.
>
> There was some discussion about it in a pull request and supertestnet also shared his thoughts: https://github.com/bitcoinlayers/bitcoinlayers/pull/164
>
> This whole debate started in May 2024 when cashu's twitter account [announced][2] that they are considering adding an authentication in the protocol as it is requested by regulated entities.
>
> The authentication mechanism is shared in this [pull request][3] which links each user with linkingkey and it will compromise privacy:
>
> ```
> POST https://bob.com/v1/auth
>
> Post Data:
> {
> action:"mint",
> k1:"8278e1a48e61c261916791dabb6af760488e4f01932e11fe7054f59754e3de6e"
> signature:c568f78e4b234a5f7d8c3b2a679e48d1234567890abcdef
> linkingKey:7345786068584cd33000582ba87a9ddf77db5377c67910ab59d7e9a5f44
> }
>
> Response:
>
> HTTP/1.1 200 OK
>
> {
> "access_token": "9876543210fedcba",
> "token_type": "Bearer",
> "expires_in": 3600
> }
> ```
>
> This pull request was closed last week and a new authentication mechanism is proposed: https://github.com/cashubtc/nuts/pull/198
>
> It uses clear and blind auth but users can still be censored with KYC based on their identity. You can understand the details from this [comment][4].
>
> ## Conclusion
>
> The authentication mechanisms shared above are not the only way mints can restrict users as there is nothing in the protocol that stops mints from using a custom authentication.
>
> Introducing KYC in protocol is against freedom and privacy. These custodial solutions might end up being another compliant ecash implementation like [GNU Taler][5]. This would also make it easier for government agencies to target other mints that do not comply.
>
> [1]: https://github.com/cashubtc/nutshell/blob/main/cashu/mint/conditions.py
> [2]: https://x.com/CashuBTC/status/1791001643019809146
> [3]: https://github.com/cashubtc/nuts/pull/106
> [4]: https://github.com/cashubtc/nuts/pull/198#issuecomment-2508706328
> [5]: https://taler.net/en/index.html
>
> /dev/fd0
> floppy disk guy
>
> --
> You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups•com.
> To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/27b19012-20da-46a7-8a84-f90e0070aa77n%40googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups•com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/-LMvaPkFoIOkgwJOch3qo7y_ueGgiOSJWqdu0gpv3wSHTunca6AB14V-ZiR4IoDcvIkPTdoQeiy_JigGwl0ei2VpBj2tFyK-GFeE2gXZzXE%3D%40proton.me.
[-- Attachment #1.1.2.1: Type: text/html, Size: 15355 bytes --]
[-- Attachment #1.2: publickey - conduition@proton.me - 0x474891AD.asc --]
[-- Type: application/pgp-keys, Size: 649 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 249 bytes --]
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [bitcoindev] Censorship and Privacy in Chaumian ecash implementations
2024-12-21 21:52 ` 'conduition' via Bitcoin Development Mailing List
@ 2024-12-21 23:03 ` /dev /fd0
0 siblings, 0 replies; 3+ messages in thread
From: /dev /fd0 @ 2024-12-21 23:03 UTC (permalink / raw)
To: Bitcoin Development Mailing List
[-- Attachment #1.1: Type: text/plain, Size: 14859 bytes --]
Hi conduition,
> For P2PK, the solution there seems deadly obvious: Just don't use raw
unobfuscated Nostr keys to receive ecash. Tweak it, or use a new random
key. This is a non-issue
This is an issue at present. I hope you fix it as you seem to be
[involved][1] in cashu development. The workarounds that you suggested will
still allow a **specific** key to be censored.
> As for authentication systems, yes of course KYC-backed authentication
would allow censorship, but that is an optional spec which mints aren't
compelled to implement or use, even once the spec is finished. AFAIK no
mint implementations have this system in code yet, so it's not even a
feature at this point: just a proposed NUT document, sitting in draft
status. Your assertion that "none of the ecash implementation are
censorship resistant" is a blatantly false statement bereft of fact or
depth.
Being optional does not matter. It is **possible** for a mint to use
authentication which is the whole point of this mailing list post. It is
true that none of the ecash implementations are censorship resistant. Just
because censorship isn't implemented yet by mints does not mean the
protocol is censorship resistant.
The false statements exist in cashu docs with misleading things about
censorship resistance.
> At worst they might lose the money they already stored with the
compromised mint, which is always a risk to keep in mind with ecash.
This is already described in my post.
> So ecash devs have proposed standardized auth systems which would give
mint runners the *option* of knuckling under to KYC. I agree it would suck
to be put to that choice, but giving them the freedom to choose is
important and valid, and in no way compromises the integrity of other
non-KYC mints who perhaps operate freely in more enlightened jurisdictions.
Standardizing KYC in the protocol is the opposite of freedom and privacy.
Jurisdictions do not matter if a mint is large enough to be attacked by the
government agencies. There are enough examples for it.
> If it's not part of the NUT standards, and mint runners need it, then
someone will just fork off and add it themselves. Better to pre-empt that
and keep the standards from fragmenting - that's the point of standards.
There are ways to avoid any kind of authentication and KYC in the protocol.
If someone still forks then it won't remain cashu protocol. However, I
won't waste my time explaining it because the goal seem to be helping
regulated entities to use cashu.
[1]: https://conduition.io/cryptography/ecash-dlc/
/dev/fd0
floppy disk guy
On Sunday, December 22, 2024 at 3:34:17 AM UTC+5:30 conduition wrote:
> Hi fd0,
>
> For P2PK, the solution there seems deadly obvious: Just don't use raw
> unobfuscated Nostr keys to receive ecash. Tweak it, or use a new random
> key. This is a non-issue.
>
> As for authentication systems, yes of course KYC-backed authentication
> would allow censorship, but that is an optional spec which mints aren't
> compelled to implement or use, even once the spec is finished. AFAIK no
> mint implementations have this system in code yet, so it's not even a
> feature at this point: just a proposed NUT document, sitting in draft
> status. Your assertion that "none of the ecash implementation are
> censorship resistant" is a blatantly false statement bereft of fact or
> depth.
>
> Even if we fast forward several years when perhaps *some* ecash mint
> implementations do implement the authentication spec as a fully-formed
> feature ready to go... If a mint starts enforcing KYC, or any other badness
> like censoring by IP address, users are free to switch to a different
> non-KYC mint instance. At worst they might lose the money they already
> stored with the compromised mint, which is always a risk to keep in mind
> with ecash.
>
> Your argument seems to be that introducing an opt-in feature spec like
> external authentication is inherently bad because it encourages/enables KYC
> in the first place, and that ecash devs should refuse to even standardize
> any protocol which enables KYC.
>
> Well I have hard news for you: Governments don't care about whether
> compliance is "easy" or "standardized". If uncle sam wants your mint to
> enforce KYC, he won't balk just because the mint's code doesn't provide an
> easy way to do it. He'll give you a deadline to enforce KYC on your users,
> and if you can't prove you're compliant by then, you can kiss your business
> goodbye.
>
> When faced with this choice, the ecash mint runner can either shut down
> their mint, or add KYC. Without an authentication system in place already,
> the mint runner would have to implement it all themselves to enforce KYC
> and stay in business. So ecash devs have proposed standardized auth systems
> which would give mint runners the *option* of knuckling under to KYC. I
> agree it would suck to be put to that choice, but giving them the freedom
> to choose is important and valid, and in no way compromises the integrity
> of other non-KYC mints who perhaps operate freely in more enlightened
> jurisdictions.
>
> If it's not part of the NUT standards, and mint runners need it, then
> someone will just fork off and add it themselves. Better to pre-empt that
> and keep the standards from fragmenting - that's the point of standards.
>
> -c
> On Saturday, December 21st, 2024 at 9:58 AM, /dev /fd0 <alice...@gmail•com>
> wrote:
>
> Hi Bitcoin Developers,
>
> This post is about a myth and some misleading things shared about ecash.
> It is possible to censor specific users and none of the ecash
> implementation are censorship resistant.
>
> # Censorship Methods in Cashu
>
> There are 2 ways to censor individual users in cashu:
>
> 1. P2PK
> 2. Authentication
>
> ## P2PK
>
> Ecash tokens issued by cashu mints can be locked using public keys so that
> it can only be redeemed by the user who owns the private key for it. This
> links ecash with specific public keys and most implementation use nostr
> keys for it. Most users are doxxed on nostr so they can be censored based
> on their identity. Even if its linked to an anon they can be censored based
> on their posts.
>
> You can find relevant code snippets in [conditions.py][1] if using
> nutshell for mint:
>
> ```python
> class LedgerSpendingConditions:
> def _verify_p2pk_spending_conditions(self, proof, secret):
> if SecretKind(secret.kind) != SecretKind.P2PK:
> return True
> p2pk_secret = P2PKSecret.from_secret(secret)
> pubkeys = [p2pk_secret.data] + p2pk_secret.tags.get_tag_all("pubkeys")
> if p2pk_secret.locktime and p2pk_secret.locktime < time.time():
> refund_pubkeys = p2pk_secret.tags.get_tag_all("refund")
> if not refund_pubkeys:
> return True
> return self._verify_secret_signatures(
> proof, refund_pubkeys, proof.p2pksigs, 1
> )
> return self._verify_secret_signatures(
> proof, pubkeys, proof.p2pksigs, p2pk_secret.n_sigs
> )
>
> def _verify_htlc_spending_conditions(self, proof, secret):
> if SecretKind(secret.kind) != SecretKind.HTLC:
> return True
> htlc_secret = HTLCSecret.from_secret(secret)
> if htlc_secret.locktime and htlc_secret.locktime < time.time():
> refund_pubkeys = htlc_secret.tags.get_tag_all("refund")
> if refund_pubkeys:
> return self._verify_secret_signatures(
> proof, refund_pubkeys, proof.p2pksigs, 1
> )
> return True
> assert proof.htlcpreimage, TransactionError("no HTLC preimage provided")
> if not hashlib.sha256(bytes.fromhex(proof.htlcpreimage)).digest() ==
> bytes.fromhex(htlc_secret.data):
> raise TransactionError("HTLC preimage does not match.")
> hashlock_pubkeys = htlc_secret.tags.get_tag_all("pubkeys")
> if not hashlock_pubkeys:
> return True
> return self._verify_secret_signatures(
> proof, hashlock_pubkeys, proof.htlcsigs or [], htlc_secret.n_sigs
> )
>
> def _verify_secret_signatures(self, proof, pubkeys, signatures,
> n_sigs_required=1):
> assert len(set(pubkeys)) == len(pubkeys), "pubkeys must be unique."
> if not signatures:
> raise TransactionError("no signatures in proof.")
> if len(set(signatures)) != len(signatures):
> raise TransactionError("signatures must be unique.")
> n_sigs_required = n_sigs_required or 1
> assert n_sigs_required > 0, "n_sigs must be positive."
> assert len(signatures) >= n_sigs_required, f"not enough signatures
> provided: {len(signatures)} < {n_sigs_required}."
> n_valid_sigs_per_output = 0
> for input_sig in signatures:
> for pubkey in pubkeys:
> if verify_schnorr_signature(
> message=proof.secret.encode("utf-8"),
> pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
> signature=bytes.fromhex(input_sig),
> ):
> n_valid_sigs_per_output += 1
> assert n_valid_sigs_per_output, "no valid signature provided for input."
> assert n_valid_sigs_per_output >= n_sigs_required, f"signature threshold
> not met. {n_valid_sigs_per_output} < {n_sigs_required}."
> return True
>
> def _verify_input_spending_conditions(self, proof):
> try:
> secret = Secret.deserialize(proof.secret)
> except Exception:
> return True
> if SecretKind(secret.kind) == SecretKind.P2PK:
> return self._verify_p2pk_spending_conditions(proof, secret)
> if SecretKind(secret.kind) == SecretKind.HTLC:
> return self._verify_htlc_spending_conditions(proof, secret)
> return True
>
> def _verify_output_p2pk_spending_conditions(self, proofs, outputs):
> try:
> secrets_generic = [Secret.deserialize(p.secret) for p in proofs]
> p2pk_secrets = [P2PKSecret.from_secret(secret) for secret in
> secrets_generic]
> except Exception:
> return True
> if not all([SecretKind(secret.kind) == SecretKind.P2PK for secret in
> p2pk_secrets]):
> return True
> if not all([secret.sigflag == SigFlags.SIG_ALL for secret in
> p2pk_secrets]):
> return True
> pubkeys_per_proof = [
> [p2pk_secret.data] + p2pk_secret.tags.get_tag_all("pubkeys")
> for p2pk_secret in p2pk_secrets
> ]
> n_sigs_per_proof = [p2pk_secret.n_sigs for p2pk_secret in p2pk_secrets]
> for p2pk_secret in p2pk_secrets:
> if p2pk_secret.locktime and p2pk_secret.locktime < time.time():
> refund_pubkeys = p2pk_secret.tags.get_tag_all("refund")
> if refund_pubkeys:
> pubkeys_per_proof.append(refund_pubkeys)
> n_sigs_per_proof.append(1)
> if not pubkeys_per_proof:
> return True
> assert len({tuple(pubs_output) for pubs_output in pubkeys_per_proof}) ==
> 1, "pubkeys in all proofs must match."
> assert len(set(n_sigs_per_proof)) == 1, "n_sigs in all proofs must match."
> pubkeys = pubkeys_per_proof[0]
> n_sigs = n_sigs_per_proof[0] or 1
> for output in outputs:
> p2pksigs = output.p2pksigs
> assert p2pksigs, "no signatures in output."
> assert len(set(p2pksigs)) == len(p2pksigs), "duplicate signatures in
> output."
> n_valid_sigs_per_output = 0
> for sig in p2pksigs:
> for pubkey in pubkeys:
> if verify_schnorr_signature(
> message=bytes.fromhex(output.B_),
> pubkey=PublicKey(bytes.fromhex(pubkey), raw=True),
> signature=bytes.fromhex(sig),
> ):
> n_valid_sigs_per_output += 1
> assert n_valid_sigs_per_output, "no valid signature provided for output."
> assert n_valid_sigs_per_output >= n_sigs, f"signature threshold not met.
> {n_valid_sigs_per_output} < {n_sigs}."
> return True
>
> def _verify_output_spending_conditions(self, proofs, outputs):
> return self._verify_output_p2pk_spending_conditions(proofs, outputs)
> ```
>
> ## Authentication
>
> Mints can enforce authentication at some point and do KYC for mint, melt,
> swap etc. Users who refuse to KYC will not be able to use or redeem their
> ecash tokens. Some of the KYCed users can be censored based on their
> identity. This would also affect privacy.
>
> gandlaf21 agrees this is possible however it is still marketed as
> censorship resistant.
>
> There was some discussion about it in a pull request and supertestnet also
> shared his thoughts:
> https://github.com/bitcoinlayers/bitcoinlayers/pull/164
>
> This whole debate started in May 2024 when cashu's twitter account
> [announced][2] that they are considering adding an authentication in the
> protocol as it is requested by regulated entities.
>
> The authentication mechanism is shared in this [pull request][3] which
> links each user with linkingkey and it will compromise privacy:
>
> ```
> POST https://bob.com/v1/auth
>
> Post Data:
> {
> action:"mint",
> k1:"8278e1a48e61c261916791dabb6af760488e4f01932e11fe7054f59754e3de6e"
> signature:c568f78e4b234a5f7d8c3b2a679e48d1234567890abcdef
> linkingKey:7345786068584cd33000582ba87a9ddf77db5377c67910ab59d7e9a5f44
> }
>
> Response:
>
> HTTP/1.1 200 OK
>
> {
> "access_token": "9876543210fedcba",
> "token_type": "Bearer",
> "expires_in": 3600
> }
> ```
>
> This pull request was closed last week and a new authentication mechanism
> is proposed: https://github.com/cashubtc/nuts/pull/198
>
> It uses clear and blind auth but users can still be censored with KYC
> based on their identity. You can understand the details from this
> [comment][4].
>
> ## Conclusion
>
> The authentication mechanisms shared above are not the only way mints can
> restrict users as there is nothing in the protocol that stops mints from
> using a custom authentication.
>
> Introducing KYC in protocol is against freedom and privacy. These
> custodial solutions might end up being another compliant ecash
> implementation like [GNU Taler][5]. This would also make it easier for
> government agencies to target other mints that do not comply.
>
> [1]:
> https://github.com/cashubtc/nutshell/blob/main/cashu/mint/conditions.py
> [2]: https://x.com/CashuBTC/status/1791001643019809146
> [3]: https://github.com/cashubtc/nuts/pull/106
> [4]: https://github.com/cashubtc/nuts/pull/198#issuecomment-2508706328
> [5]: https://taler.net/en/index.html
>
> /dev/fd0
> floppy disk guy
>
> --
> You received this message because you are subscribed to the Google Groups
> "Bitcoin Development Mailing List" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to bitcoindev+...@googlegroups•com.
> To view this discussion visit
> https://groups.google.com/d/msgid/bitcoindev/27b19012-20da-46a7-8a84-f90e0070aa77n%40googlegroups.com
> .
>
>
>
--
You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups•com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/51eb0cc8-c9e0-4a50-9928-461f5f13264en%40googlegroups.com.
[-- Attachment #1.2: Type: text/html, Size: 21506 bytes --]
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2024-12-21 23:26 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-12-21 16:58 [bitcoindev] Censorship and Privacy in Chaumian ecash implementations /dev /fd0
2024-12-21 21:52 ` 'conduition' via Bitcoin Development Mailing List
2024-12-21 23:03 ` /dev /fd0
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox