Skip to content
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

feat: pseudonyms supports in presentation #19

Merged
merged 2 commits into from
Jun 5, 2023

Conversation

nicobao
Copy link
Contributor

@nicobao nicobao commented May 10, 2023

Closes #18

Things I am not sure about and would particularly love your help with:

  • does the statement calculated from Statement.attributeBoundPseudonym and sent in the proofSpec with the presentation already contain the pseudonym? If I understand well, it creates a Pedersen commitment but does not reveal the pseudonym? It is critical to me because in Privency protocol, it is sometimes necessary to keep pseudonyms private (full protocol description is here: https://github.com/privency/poc/tree/main/vc-flow#poc-to-prove-how-to-use-anoncred-for-counting-votes )
  • how do I prove that the revealed pseudonym I have put somewhere in the presentation sperc is the one that correspond to the attached proof?
  • where to put the pseudonym I want to reveal in the presentation spec? do I have to put it in the credentials themselves to be able to prove equality between the pseudonym and another attribute afterwards? But if the pseudonym is calculated from multiple credential attributes, in which credentials should I put it in? Can I provide this kind of equality proof if I don't present the pseudonym in a credential but outside, like with attributeEqualities (option I chose for now)?
  • am I supposed to decode the pseudonym I want to reveal or should I keep it encoded? What about the scope? I suggested to use the scope as a dictionary key, so it must be a string or a number.

Known lacking elements (should be implemented before PR is ready)

  • no support for plain pseudonym (no attribute-bound)
  • secret is mandatory and it shouldn't be for attribute-bound pseudonyms
  • no unit tests

@lovesh
Copy link
Member

lovesh commented May 11, 2023

Hi. Thanks for the PR.

does the statement calculated from Statement.attributeBoundPseudonym and sent in the proofSpec with the presentation already contain the pseudonym?

Yes. But generally, the proofSpec (object of class ProofSpecG1) isn't meant to be sent with the presentation but should be created by the verifier. So the prover sends the pseudonym but not the statement as it's expected that the verifier will know the commitment key independently of the prover.

If I understand well, it creates a Pedersen commitment but does not reveal the pseudonym

The Pedersen commitment is the pseudonym as seen here.

it is sometimes necessary to keep pseudonyms private (full protocol description is here: https://github.com/privency/poc/tree/main/vc-flow#poc-to-prove-how-to-use-anoncred-for-counting-votes )

Can you tell me specifically where you need a pseudonym in the above diagram and why do you need them to be private?

how do I prove that the revealed pseudonym I have put somewhere in the presentation sperc is the one that correspond to the attached proof

The proof itself is proving that you know the secret key of the pseudonym. And pseudonyms are not yet integrated in the presentation spec but otherwise you can see this test.

where to put the pseudonym I want to reveal in the presentation spec? do I have to put it in the credentials themselves to be able to prove equality between the pseudonym and another attribute afterwards? But if the pseudonym is calculated from multiple credential attributes, in which credentials should I put it in? Can I provide this kind of equality proof if I don't present the pseudonym in a credential but outside, like with attributeEqualities (option I chose for now)?

Pseudonyms not bound to any attribute should definitely be outside of any credentials and be a top-level key like attributeEqualities. And as you mentioned for bound attributes, they might be bound to attributes across credentials so better to keep them at the top level as well.

am I supposed to decode the pseudonym I want to reveal or should I keep it encoded? What about the scope? I suggested using the scope as a dictionary key, so it must be a string or a number.

The crypto logic needs bytearrays but while sending them in presentation, you should encode it to base64. Similarly for scope, it needs to be bytearray for crypto logic but in a presentation in can be kept as a string.

secret is mandatory and it shouldn't be for attribute-bound pseudonyms

Have you seen this note?

@nicobao
Copy link
Contributor Author

nicobao commented May 11, 2023

Hi. Thanks for the PR.

Thanks for the quick response 🙏

does the statement calculated from Statement.attributeBoundPseudonym and sent in the proofSpec with the presentation already contain the pseudonym?

Yes. But generally, the proofSpec (object of class ProofSpecG1) isn't meant to be sent with the presentation but should be created by the verifier. So the prover sends the pseudonym but not the statement as it's expected that the verifier will know the commitment key independently of the prover.

Ok, so that mean the pseudonym cannot be kept private... Is it hard to change that?

If I understand well, it creates a Pedersen commitment but does not reveal the pseudonym

The Pedersen commitment is the pseudonym as seen here.

it is sometimes necessary to keep pseudonyms private (full protocol description is here: https://github.com/privency/poc/tree/main/vc-flow#poc-to-prove-how-to-use-anoncred-for-counting-votes )

Can you tell me specifically where you need a pseudonym in the above diagram and why do you need them to be private?

At the first step, registration, users send a hash of their university ID (hash_ID) and a hash of a secret (hash_secret) to Privency, that is kept in a database associated with the Oauth2 account.

Then Privency sends users a VC containing the Hash ID and the Hash Secret called the registration VC.

Whenever users want to vote they need to provide :

  • another hash calculated from the same secret that serves as unique private identifier for the vote. Two option here to know that it's the same secret.
    • Either you prove you can recalculate the hash secret, without presenting the hash secret. Why keeping the hash_secret private from Privency? Because otherwise, we'd know exactly which Oauth2 account voted what, and we want to avoid that to preserve privacy.
    • Or the secret used to calculate the hash_secret was blind-signed together with other attributes in the registration VC. So we just expect a hash from the secret attribute.
  • proof that users still hold a valid unrevoked university ID that was the same ID as the one used during registration. Two options :
    • prove the university ID contained in the university VC still hashes to the hash ID contained in the registration VC, without revealing the hash ID.
    • another option, like above, is to have blind-signed the university ID in the registration VC and simply prove equality between this one and the original university VC (which is an unrevoked credential).

So, two options :

  • either I can prove that a pseudonym I calculated is equal to a private attribute I own, and not reveal the pseudonym
  • or I need to prove that the pseudonym that was calculated used a secret or an ID extracted from a private attribute, and then blind this very same secret/ID inside the registration VC that Privency signs and issues.

What do you think make the most sense between the two options, what's the easiest? If I need to go for option 2/, could you give me some clue as to which primitive in dock I can use for blind-signing? (I see there is a PR about coconut that you're working on which I may need...)

how do I prove that the revealed pseudonym I have put somewhere in the presentation sperc is the one that correspond to the attached proof

The proof itself is proving that you know the secret key of the pseudonym. And pseudonyms are not yet integrated in the presentation spec but otherwise you can see this test.

Yes, I noticed, and it's exactly the purpose of this PR: integrate pseudonyms in the presentation spec. How do I prove the order of presentation? Imagine the verifier requests many pseudonyms, how do the prover prove which corresponds to what in the presentation?

where to put the pseudonym I want to reveal in the presentation spec? do I have to put it in the credentials themselves to be able to prove equality between the pseudonym and another attribute afterwards? But if the pseudonym is calculated from multiple credential attributes, in which credentials should I put it in? Can I provide this kind of equality proof if I don't present the pseudonym in a credential but outside, like with attributeEqualities (option I chose for now)?

Pseudonyms not bound to any attribute should definitely be outside of any credentials and be a top-level key like attributeEqualities. And as you mentioned for bound attributes, they might be bound to attributes across credentials so better to keep them at the top level as well.

Good, that's what I did!

am I supposed to decode the pseudonym I want to reveal or should I keep it encoded? What about the scope? I suggested using the scope as a dictionary key, so it must be a string or a number.

The crypto logic needs bytearrays but while sending them in presentation, you should encode it to base64. Similarly for scope, it needs to be bytearray for crypto logic but in a presentation in can be kept as a string.

Ok good, that's what I did!

secret is mandatory and it shouldn't be for attribute-bound pseudonyms

Have you seen this note?

Hmm yes but I think there are tests in pseudonym.spec.ts that do not require secret for attributeBoundPseudonym. What I mean is that my current implementation in presentation does not allow attributeBoundPseudonym to not have a secret, while it is implemented in the layer below.

@lovesh
Copy link
Member

lovesh commented May 11, 2023

Ok, so that mean the pseudonym cannot be kept private... Is it hard to change that?

Yes, support needs to be built in the Rust library for this, and since the pseudonym itself is a commitment, proving its knowledge will likely require pairings which are expensive so lets try to avoid them.

Whenever users want to vote they need to provide :
another hash calculated from the same secret that serves as unique private identifier for the vote. Two option here to know that it's the same secret.
Either you prove you can recalculate the hash secret, without presenting the hash secret. Why keeping the hash_secret private from Privency? Because otherwise, we'd know exactly which Oauth2 account voted what, and we want to avoid that to preserve privacy.
Or the secret used to calculate the hash_secret was blind-signed together with other attributes in the registration VC. So we just expect a hash from the secret attribute.

Yes, let use bling signing. While requesting registration VC, the user blinds the secret (not its hash) so that the issuer is not aware of it but the VC still contains the secret as an attribute. Now during voting (presentation), the user shares an attribute-bound pseudonym where the attribute is the secret. Create it without the "secretKey" like this.

proof that users still hold a valid unrevoked university ID that was the same ID as the one used during registration

This can be simply achieved by revoking registration VC. Shouldn't need blinding or any equality proof.

How do I prove the order of presentation? Imagine the verifier requests many pseudonyms, how do the prover prove which corresponds to what in the presentation?

Have 2 keys boundedPseudonyms and unboundedPseudonyms and each will be an array like below

unboundedPseudonyms: [
    {commitKey: <a reference to a commitment key so that verifier can fetch it, we use `paramId`, pseudonym: <encoded value>},
    {commitKey: <a ref.... },
],
boundedPseudonyms: [
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: [
            {credIdx: <index of credential in PresentationSpecification.credentials>, attribute: <full attribute name>},
            {credIdx: <index of cred.. }
        ]
    },
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: [
            {credIdx: <index of credential in PresentationSpecification.credentials>, attribute: <full attribute name>},
            {credIdx: <index of cred.. }
        ]
    }
]

What do you think?

Hmm yes but I think there are tests in pseudonym.spec.ts that do not require secret for attributeBoundPseudonym. What I mean is that my current implementation in the presentation does not allow attributeBoundPseudonym to not have a secret, while it is implemented in the layer below.

Some of them do but in your case its not needed.

@nicobao
Copy link
Contributor Author

nicobao commented May 14, 2023

Ok, so that mean the pseudonym cannot be kept private... Is it hard to change that?

Yes, support needs to be built in the Rust library for this, and since the pseudonym itself is a commitment, proving its knowledge will likely require pairings which are expensive so lets try to avoid them.

👍

Whenever users want to vote they need to provide :
another hash calculated from the same secret that serves as unique private identifier for the vote. Two option here to know that it's the same secret.
Either you prove you can recalculate the hash secret, without presenting the hash secret. Why keeping the hash_secret private from Privency? Because otherwise, we'd know exactly which Oauth2 account voted what, and we want to avoid that to preserve privacy.
Or the secret used to calculate the hash_secret was blind-signed together with other attributes in the registration VC. So we just expect a hash from the secret attribute.

Yes, let use bling signing. While requesting registration VC, the user blinds the secret (not its hash) so that the issuer is not aware of it but the VC still contains the secret as an attribute. Now during voting (presentation), the user shares an attribute-bound pseudonym where the attribute is the secret. Create it without the "secretKey" like this.

👍

How to prove that the secretKey that is blind-signed is the same that was used to create the hash?
Do Privency need to issue a transitory VC with the random blinded-secret, and then request from the prover a hash from the secret attribute in the VC Privency just issued, and the very same secret attribute - but blinded? Or can this transitory VC be avoided?

proof that users still hold a valid unrevoked university ID that was the same ID as the one used during registration

This can be simply achieved by revoking registration VC. Shouldn't need blinding or any equality proof.

Hmm, I don't think so. Allow me to explain my reasoning. There are two VC:

  • the university VC issued by the university
  • the registration VC issued by Privency

In the university VC, there is a unique university ID per university member.
When issuing the registration VC, Privency blind-signs the very same ID in registration VC.
Privency uses a hash of this university ID to bound it to a Oauth2/FIDO2 account. This is necessary to prevent university members from creating plenty of accounts to vote multiple times.

Between Privency registration and voting, the university VC could be revoked, but Privency wouldn't know and wouldn't know what registration VC to revoke anyway, because the university ID is a private information between the Holder and the University, and Privency only knows a hash of the ID, and this is by design because we want to give as few information to Privency as possible. Privency should not alone be able to know who registered.

If the university ID is long enough and truly cryptographically random, the only attack vector for Privency to know who registered would be for Privency to collude with the University and try all the hash of university ID to match them with Privency database. It's unfortunately impossible to make it more private, but we think this situation is acceptable. The most important is that no matter what, it is cryptographically impossible to know who voted what so long as the secretKey is strong enough and kept secret by the Holder.

Now, when voting, Privency needs to verify that:

  • the university VC is valid and has not been revoked (easy)
  • the registration VC is valid and has not been revoked (easy)
  • the university ID in the registration VC is equal to the university ID in the university VC

I need an equality check for my use-case, and blind-sign the university ID, not just the secret.

How do I prove the order of presentation? Imagine the verifier requests many pseudonyms, how do the prover prove which corresponds to what in the presentation?

Have 2 keys boundedPseudonyms and unboundedPseudonyms and each will be an array like below

unboundedPseudonyms: [
    {commitKey: <a reference to a commitment key so that verifier can fetch it, we use `paramId`, pseudonym: <encoded value>},
    {commitKey: <a ref.... },
],
boundedPseudonyms: [
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: [
            {credIdx: <index of credential in PresentationSpecification.credentials>, attribute: <full attribute name>},
            {credIdx: <index of cred.. }
        ]
    },
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: [
            {credIdx: <index of credential in PresentationSpecification.credentials>, attribute: <full attribute name>},
            {credIdx: <index of cred.. }
        ]
    }
]

What do you think?

It looks good. For the attributes field, I'd go for the following:

unboundedPseudonyms: [
    {commitKey: <a reference to a commitment key so that verifier can fetch it, we use `paramId`, pseudonym: <encoded value>},
    {commitKey: <a ref.... },
],
boundedPseudonyms: [
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: { // key is credential index
            1: [<full attribute name1>, <full attribute name2>,...],
            2: [<full attribute name3>, <full attribute name4>,...],
        }
    },
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: {
            1: [<full attribute name1>, <full attribute name2>,...],
            2: [<full attribute name3>, <full attribute name4>,...],
        }
    }
]

But I'll do whatever you prefer!
My understanding is that the commitKeys are baseforSecretKey (if defined) and basesForAttributes for boundedPseudonyms and simply base for unboundedPseudonyms. Am I correct?

There is still something that I don't understand though. How does the verifier verifies that the pseudonym he extracts from the spec above corresponds to the Statements and Witnesses sent by the prover in proofSpec? Is there something to implement in Presentation.verify function?

Hmm yes but I think there are tests in pseudonym.spec.ts that do not require secret for attributeBoundPseudonym. What I mean is that my current implementation in the presentation does not allow attributeBoundPseudonym to not have a secret, while it is implemented in the layer below.

Some of them do but in your case its not needed.

Thank you again for your help 🙏

@lovesh
Copy link
Member

lovesh commented May 15, 2023

How to prove that the secretKey that is blind-signed is the same that was used to create the hash?

The "hash" should serve as a pseudonym, right? The don't hash but commit to the attribute (which the psedonym is). Adding witness equality like this https://github.com/docknetwork/crypto-wasm-ts/blob/master/tests/composite-proofs/pseudonyms.spec.ts#L313 will ensure that above condition is met. It will generate an equality proof between the attribute the secret of the psedonym. From now on, i will assume whenever you say hash of attribute, you mean a an attribute bound pseudonym so please correct me when you really need a hash.

can this transitory VC be avoided?

Yes, no need of this.

In the university VC, there is a unique university ID per university member.
When issuing the registration VC, Privency blind-signs the very same ID in registration VC.
Privency uses a hash of this university ID to bound it to a Oauth2/FIDO2 account. This is necessary to prevent university members from creating plenty of accounts to vote multiple times.

👍

Between Privency registration and voting, the university VC could be revoked, but Privency wouldn't know and wouldn't know what registration VC to revoke anyway, because the university ID is a private information between the Holder and the University, and Privency only knows a hash of the ID

Whats the revocation mechanism of the university VC? Accumulators? If yes then during voting, the user could prove that his university VC isn't revoked without revealing his university id. If the revocation mechanism is through an expiry date, then the user could do a range proof on the expiry date.

the university ID in the registration VC is equal to the university ID in the university VC
I need an equality check for my use-case, and blind-sign the university ID, not just the secret.

This is doable easily, right?

For the attributes field, I'd go for the following:

unboundedPseudonyms: [
    {commitKey: <a reference to a commitment key so that verifier can fetch it, we use `paramId`, pseudonym: <encoded value>},
    {commitKey: <a ref.... },
],
boundedPseudonyms: [
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: { // key is credential index
            1: [<full attribute name1>, <full attribute name2>,...],
            2: [<full attribute name3>, <full attribute name4>,...],
        }
    },
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: {
            1: [<full attribute name1>, <full attribute name2>,...],
            2: [<full attribute name3>, <full attribute name4>,...],
        }
    }
]

Thats better. Lets use this.

My understanding is that the commitKeys are baseforSecretKey (if defined) and basesForAttributes for boundedPseudonyms and simply base for unboundedPseudonyms. Am I correct?

Correct.

How does the verifier verifies that the pseudonym he extracts from the spec above corresponds to the Statements and Witnesses sent by the prover in proofSpec? Is there something to implement in Presentation.verify function?

The provided proof proves that user knows secret/attribute (and the attribute is same in case of bound) behind psedonym. So in Presentation.verify, construct Statement.pseudonyms, Statement.attributeBoundPseudonym and WitnessEqualityMetaStatement accordingly.

@nicobao nicobao force-pushed the feat-pseudonyms-presentation branch 2 times, most recently from 604890f to 7d03de4 Compare May 22, 2023 16:39
@nicobao
Copy link
Contributor Author

nicobao commented May 22, 2023

How to prove that the secretKey that is blind-signed is the same that was used to create the hash?

The "hash" should serve as a pseudonym, right? The don't hash but commit to the attribute (which the psedonym is). Adding witness equality like this https://github.com/docknetwork/crypto-wasm-ts/blob/master/tests/composite-proofs/pseudonyms.spec.ts#L313 will ensure that above condition is met. It will generate an equality proof between the attribute the secret of the psedonym. From now on, i will assume whenever you say hash of attribute, you mean a an attribute bound pseudonym so please correct me when you really need a hash.

Yes, sorry for some reasons I keep associating the pseudonym with a hash, but it's a pedersen commitment, not a hash.

The thing I am wondering is how to send the blinded secretKey together with the proof that the secretKey is the same that was used to present the pseudonym? At this point, the secretKet is not yet part of a credential, but the witness equality you pointed seems to only work with attributes included in existing credentials. Am I mistaken?

can this transitory VC be avoided?

Yes, no need of this.

👍

In the university VC, there is a unique university ID per university member.
When issuing the registration VC, Privency blind-signs the very same ID in registration VC.
Privency uses a hash of this university ID to bound it to a Oauth2/FIDO2 account. This is necessary to prevent university members from creating plenty of accounts to vote multiple times.

+1

Between Privency registration and voting, the university VC could be revoked, but Privency wouldn't know and wouldn't know what registration VC to revoke anyway, because the university ID is a private information between the Holder and the University, and Privency only knows a hash of the ID

Whats the revocation mechanism of the university VC? Accumulators? If yes then during voting, the user could prove that his university VC isn't revoked without revealing his university id. If the revocation mechanism is through an expiry date, then the user could do a range proof on the expiry date.

Technically the university VC must be a BBS+ VC for the proofs to work.
As of the revocation, the only requirement is to be privacy-preserving. I wonder what's the classic revocation mechanism in case issuers use did:web?
I am wondering if there is any way to present a BBS+ VC when the university VC originally issued is another type of VC (AnonCred with CL signature or basic JWT/JSON-LD VC for example)?

the university ID in the registration VC is equal to the university ID in the university VC
I need an equality check for my use-case, and blind-sign the university ID, not just the secret.

This is doable easily, right?

Yes!

For the attributes field, I'd go for the following:

unboundedPseudonyms: [
    {commitKey: <a reference to a commitment key so that verifier can fetch it, we use `paramId`, pseudonym: <encoded value>},
    {commitKey: <a ref.... },
],
boundedPseudonyms: [
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: { // key is credential index
            1: [<full attribute name1>, <full attribute name2>,...],
            2: [<full attribute name3>, <full attribute name4>,...],
        }
    },
    {
        commitKey: <a ref....>,  
        pseudonym: <encoded value>,
        attributes: {
            1: [<full attribute name1>, <full attribute name2>,...],
            2: [<full attribute name3>, <full attribute name4>,...],
        }
    }
]

Thats better. Lets use this.

For the boundedPseudonyms, I finallly went for:

boundedPseudonyms: {
    // this unique key is calculated by doing:
    // basesForAttributes.map(b => b.toString()).join('')+basesForSecretKey.toString()
    // (@see source code)
    // I find this handy for picking the right pseudonym on the verifier side
    // verifier does not need to do "boundedPseudoyms.find(...)" to get the right pseudonym, which is O(n), 
    // they can just get the value directly from the map, which is O(1)
    // and if necessary it's still possible to iterate through the pseudonyms by doing: "boundedPseudonyms.entries()"
    "unique_key_1":
          {
              commitKey: <a ref....>,  
              pseudonym: <encoded value>,
              attributes: { // key is credential index
                  1: [<full attribute name1>, <full attribute name2>,...],
                  2: [<full attribute name3>, <full attribute name4>,...],
              }
          },
   "unique_key_2":
          {
              commitKey: <a ref....>,  
              pseudonym: <encoded value>,
              attributes: {
                  1: [<full attribute name1>, <full attribute name2>,...],
                  2: [<full attribute name3>, <full attribute name4>,...],
              }
          }
}

I'd do something similar with the unboundedPseudonyms.

My understanding is that the commitKeys are baseforSecretKey (if defined) and basesForAttributes for boundedPseudonyms and simply base for unboundedPseudonyms. Am I correct?

Correct.

Good.

How does the verifier verifies that the pseudonym he extracts from the spec above corresponds to the Statements and Witnesses sent by the prover in proofSpec? Is there something to implement in Presentation.verify function?

The provided proof proves that user knows secret/attribute (and the attribute is same in case of bound) behind psedonym. So in Presentation.verify, construct Statement.pseudonyms, Statement.attributeBoundPseudonym and WitnessEqualityMetaStatement accordingly.

Makes sense. I implemented what you suggested, but I still get an error in the unit test that check whether the verification goes well:

I don't understand what's wrong... I have put some comments in the code.
Could you help me?

I almost finished the PR for the boundedPseudonyms, then I'll do unboundedPseudonyms and add some more tests and it will be ready for full review.

@lovesh
Copy link
Member

lovesh commented May 23, 2023

The thing I am wondering is how to send the blinded secretKey together with the proof that the secretKey is the same that was used to present the pseudonym? At this point, the secretKet is not yet part of a credential, but the witness equality you pointed seems to only work with attributes included in existing credentials.

This is regarding the request for registration VC? Then the secretKey is already part of the university VC. The flow i had in mind was-

  1. User requests a university VC which contains the secretKey as an attribute but is blinded (hidden) from issuer.
  2. User requests a registration VC by sharing a presentation from university VC, the pseudonym created from secretKey and proof that secretKey in pseudonym is equal to the one in university VC

As of the revocation, the only requirement is to be privacy-preserving. I wonder what's the classic revocation mechanism in case issuers use did:web?

The revocation mechanism used is orthogonal to the DID method.

I am wondering if there is any way to present a BBS+ VC when the university VC originally issued is another type of VC (AnonCred with CL signature or basic JWT/JSON-LD VC for example)?

The CL sigs I have seen use RSA and with different group size than the one we use with elliptic curves so proving attribute equality would be difficult and we don't support that even in the Rust library.

For the boundedPseudonyms, I finallly went for:
// this unique key is calculated by doing:
// basesForAttributes.map(b => b.toString()).join('')+basesForSecretKey.toString()

This can have collisions for the key when the same bases are used to create many pseudonyms from different credentials. The pseudonym itself being the key is better as they are required to be unique (in a scope) and have a negligible chance of collision.

I implemented what you suggested, but I still get an error in the unit test that check whether the verification goes well:

Will check. Can you please merge the latest master branch in your PR? Most of the new changes were part of this PR. These bring in support for additional schemes but should not impact your logic much.

@nicobao nicobao force-pushed the feat-pseudonyms-presentation branch from 7d03de4 to 705d57a Compare May 23, 2023 13:29
@nicobao
Copy link
Contributor Author

nicobao commented May 23, 2023

The thing I am wondering is how to send the blinded secretKey together with the proof that the secretKey is the same that was used to present the pseudonym? At this point, the secretKet is not yet part of a credential, but the witness equality you pointed seems to only work with attributes included in existing credentials.

This is regarding the request for registration VC? Then the secretKey is already part of the university VC. The flow i had in mind was-

1. User requests a `university VC` which contains the `secretKey` as an attribute but is blinded (hidden) from issuer.

2. User requests a `registration VC` by sharing a presentation from `university VC`, the pseudonym created from `secretKey` and proof that `secretKey` in pseudonym is equal to the one in  `university VC`

It cannot work for two reasons:

  • we can't expect to control what's the university VC look like
  • there should be 1 secret per poll, so that if you lose it you only lose the possibility to change your vote for the polls that you already registered on, but you can still vote in other polls. Also, if we were using 1 secret per university VC that means we'd need to expect the university to keep track of secret pseudonym and refuse issuing new VC to users in case they lost their secret... Otherwise their vote could be counted twice after they revoked and got a new VC with a new secret. It would be unrealistic to expect the university to manage such complexity.

There are two use-cases for our product:

  1. we are the university VC issuer
  2. we are not the university issuer - in that case we have no control over the VC initially issued by the university

At the beginning we will ship case 1/ - but the goal of the project is really 2/: we want to support any existing VCs - with as little limitations as possible.
Because the goal is 2/ I don't want to make a design for 1/ that does not work for 2/.

My idea would be the following:

  • the only expectation from the university VC issuer is to issue an attribute that represents a unique identifier per holder in the VC. Preferentially a cryptographically random identifier.
  • on user starting the registration process to a specific poll, we request the proof of ownership of a university VC, then two options:
    • if it's a BBS+ VC, then move on to the next step
    • if it's not, then blind all the attributes from the VC, send it to us and we issue a new BBS+ VC that's BBS+. It would be even better if the wallet itself could do that internally - sending the new BBS+ VC with a proof of ownership of the other one... Is that step possible? How so? I am not sure right now (see below).
  • we request the user to send a blinded secret
  • we send a poll VC to the user, that contains information about the poll, and the blinded secret (I was wondering whether this transitory step is necessary and it seems it is)
  • then the user can request a registration VC and we proceed like discussed except the secret equality is associated with the poll VC just issued

That seems to work fine to me, so long as the university VC type is BBS+. Not so sure otherwise...

As of the revocation, the only requirement is to be privacy-preserving. I wonder what's the classic revocation mechanism in case issuers use did:web?

The revocation mechanism used is orthogonal to the DID method.

As of the poll VC and registration VC, users need to trust us anyway, so I am thinking we could use a status list. The list would be limited to the people who've already registered to the poll, so it should be manageable in terms of performance. We would present that list to the holder anytime they need to present a non-revocation proof. What's the state in the art here? Is there a better option?

As of the university VC, I am not sure which method is best yet. I want to support all the methods anyway.

I am wondering if there is any way to present a BBS+ VC when the university VC originally issued is another type of VC (AnonCred with CL signature or basic JWT/JSON-LD VC for example)?

The CL sigs I have seen use RSA and with different group size than the one we use with elliptic curves so proving attribute equality would be difficult and we don't support that even in the Rust library.

Hmm... So, regarding my suggestion above, that means:

  • proving ownership of a non-BBS+ university VC should be doable
  • blinding non-BBS+ university VC attributes should be doable
  • but proving equality between those blinded attributes and the ones in the non-BBS+ VC is NOT doable

Which would mean we cannot support non-BBS+ university VC - am I correct?

For the boundedPseudonyms, I finallly went for:
// this unique key is calculated by doing:
// basesForAttributes.map(b => b.toString()).join('')+basesForSecretKey.toString()

This can have collisions for the key when the same bases are used to create many pseudonyms from different credentials. The pseudonym itself being the key is better as they are required to be unique (in a scope) and have a negligible chance of collision.

Oh right, I didn't realize that! Changing this now.

I implemented what you suggested, but I still get an error in the unit test that check whether the verification goes well:

Will check. Can you please merge the latest master branch in your PR? Most of the new changes were part of this PR. These bring in support for additional schemes but should not impact your logic much.

I just rebased my branch, it is up to date! Thanks for checking.

@lovesh
Copy link
Member

lovesh commented May 24, 2023

It cannot work for two reasons:
we can't expect to control what's the university VC look like
there should be 1 secret per poll, so that if you lose it you only lose the possibility to change your vote for the polls that you already registered on, but you can still vote in other polls. Also, if we were using 1 secret per university VC that means we'd need to expect the university to keep track of secret pseudonym and refuse issuing new VC to users in case they lost their secret... Otherwise their vote could be counted twice after they revoked and got a new VC with a new secret. It would be unrealistic to expect the university to manage such complexity.

Sorry, i forgot about these details. So the flow should be this

  1. User requests a university VC which contains the student id attribute called sId and no attribute is blinded.
  2. User requests a registration VC by sharing a presentation from university VC. The user blinds 2 attributes, sId and secretKey and proves that blinded sId is equal to the sId attribute from university VC.
  3. While participating in the poll, the user creates a pseudonym bounded to 2 attributes, sId and secretKey of above registration VC

Does the above work?

if it's not, then blind all the attributes from the VC, send it to us and we issue a new BBS+ VC that's BBS+. It would be even better if the wallet itself could do that internally - sending the new BBS+ VC with a proof of ownership of the other one... Is that step possible? How so? I am not sure right now (see below).

If the university VC is not of a supported scheme like BBS+, PS, BBS, above won't be possible at the moment and would be a significant effort even if we restrict the signature scheme of university VC to EdDSA, ECDSA. We will need to use a zk-SNARK to prove that you know a EdDSA signature and the attributes and that blinding has been done correctly and all this will be proved inside the snark circuit making it quite expensive.

we send a poll VC to the user, that contains information about the poll, and the blinded secret (I was wondering whether this transitory step is necessary and it seems it is)

Yes, you don't need it.

As of the poll VC and registration VC, users need to trust us anyway, so I am thinking we could use a status list. The list would be limited to the people who've already registered to the poll, so it should be manageable in terms of performance.

The problem with the status list isn't performance but the lack of privacy because the verifier will learn statusListIndex and know which "registered user" it is. If that is acceptable then using status list is fine.

Is there a better option?

Better in terms of?

proving ownership of a non-BBS+ university VC should be doable
blinding non-BBS+ university VC attributes should be doable
but proving equality between those blinded attributes and the ones in the non-BBS+ VC is NOT doable

I can't answer the question without knowing which sig scheme is being used in university VC but it's quite expensive and not supported atm for EdDSA, ECDSA.

I just rebased my branch, it is up to date! Thanks for checking.

Thanks

@nicobao
Copy link
Contributor Author

nicobao commented May 24, 2023

It cannot work for two reasons:
we can't expect to control what's the university VC look like
there should be 1 secret per poll, so that if you lose it you only lose the possibility to change your vote for the polls that you already registered on, but you can still vote in other polls. Also, if we were using 1 secret per university VC that means we'd need to expect the university to keep track of secret pseudonym and refuse issuing new VC to users in case they lost their secret... Otherwise their vote could be counted twice after they revoked and got a new VC with a new secret. It would be unrealistic to expect the university to manage such complexity.

Sorry, i forgot about these details. So the flow should be this

1. User requests a `university VC` which contains the student id attribute called `sId` and no attribute is blinded.

2. User requests a `registration VC` by sharing a presentation from `university VC`. The user blinds 2 attributes, `sId` and `secretKey` and proves that blinded `sId` is equal to the `sId` attribute from `university VC`.

That's right but before the registration VC is sent, we need to record certain information.

Our product needs to keep track of an anonymized version of secretKey and sId per poll and per user. A new OAuth2 account should not be allowed to bind the same sId to his account and be allowed to vote in the poll as the same university member again - unless the first account is unregistered. Similarly, after revocation and request of a new registration VC, the user should provide the same secret, otherwise he'd be able to vote twice.

My original idea for that was for our product to request two pseudonyms before sending the registration VC:

  • one created from secretKey + a poll/user-specific nonce - let's call it psecretKey
  • one created from sId + another poll/user-specific nonce - let's call it psId

Nonce is useful to add randomness, especially for sId, that may not be as cryptographically secure as we'd want.
Then we'd keep these pseudonyms in a database bounded to the OAuth2 user. The nonces also have to be kept in database. This way, if only psId leaks but not the nonce, the University won't be able to link psId with sId and know who registered.

3. While participating in the poll, the user creates a pseudonym bounded to 2 attributes, `sId` and `secretKey` of above `registration VC`

I need to add a third attribute to this bounded pseudonym: yet another poll-specific nonce.
Otherwise, if users reuse the same secretKey, the same pseudonym would be used for all the polls created for that university, and we'd want to avoid that.

Does the above work?

if it's not, then blind all the attributes from the VC, send it to us and we issue a new BBS+ VC that's BBS+. It would be even better if the wallet itself could do that internally - sending the new BBS+ VC with a proof of ownership of the other one... Is that step possible? How so? I am not sure right now (see below).

If the university VC is not of a supported scheme like BBS+, PS, BBS, above won't be possible at the moment and would be a significant effort even if we restrict the signature scheme of university VC to EdDSA, ECDSA. We will need to use a zk-SNARK to prove that you know a EdDSA signature and the attributes and that blinding has been done correctly and all this will be proved inside the snark circuit making it quite expensive.

Alright, I can live with that restriction. BBS+ is where the community is heading anyway.

we send a poll VC to the user, that contains information about the poll, and the blinded secret (I was wondering whether this transitory step is necessary and it seems it is)

Yes, you don't need it.

Even with these nonces and psecretKey/psId?

As of the poll VC and registration VC, users need to trust us anyway, so I am thinking we could use a status list. The list would be limited to the people who've already registered to the poll, so it should be manageable in terms of performance.

The problem with the status list isn't performance but the lack of privacy because the verifier will learn statusListIndex and know which "registered user" it is. If that is acceptable then using status list is fine.

The "registered user" could be yet another ID that is unique to each registration VC and generated by Privency on registration. We can call the attribute rId. It would only be used for the purpose of revocation.

This value would never be used for voting or anything else and should be kept private by the holder. Yes the status list made of rIds is public, but so long as the holder keeps their own rId private, and we don't publicly associate this rId with psId, nobody can ever know which psId registered to the votes except us. Privency already knows which psId have registered anyway, and users need to trust us to keep this information private, so I think that's acceptable, considering we would still need to collude with the University to know people's real identity (the sId).

The most important for the product is that it should be cryptographically infeasible to know who voted what - so long as the secretKey is kept private by the voter, even if Privency and the University collude or if there is a data leak.

Is there a better option?

Better in terms of?

My requirements for the revocation mechanism of registration VC are:

  • private (to the level exposed above)
  • cheap
  • scalable and performant
  • widely adopted

The non-requirements for the revocation mechanism:

  • the kind of decentralization and high-availability that a blockchain brings - Privency already acts as an issuer so there is a minimum level of trust needed. Also, the registration VCs that Privency issue are not particularly long-lasting, and they are only created for a specific purpose contrary to institutional VCs.

I am wondering if there is another revocation mechanism I simply missed that's already widely adopted?
I am thinking of rolling my own implementation but if there is already something available, that would be better...
Do Dock wallet support status lists already?

proving ownership of a non-BBS+ university VC should be doable
blinding non-BBS+ university VC attributes should be doable
but proving equality between those blinded attributes and the ones in the non-BBS+ VC is NOT doable

I can't answer the question without knowing which sig scheme is being used in university VC but it's quite expensive and not supported atm for EdDSA, ECDSA.

I just rebased my branch, it is up to date! Thanks for checking.

Thanks

I thank you! Love all the support you're providing!
Btw, I will be at DICE Europe in Zurich next month, I would love to see you there!

@nicobao nicobao force-pushed the feat-pseudonyms-presentation branch 2 times, most recently from 864f2ee to cf8d217 Compare May 25, 2023 08:48
@nicobao
Copy link
Contributor Author

nicobao commented May 25, 2023

I just rebased and changed the "key" in the boundedPseudonyms presented Map to the decoded pseudonym instead of (basesForAttributes+basesForSecretKey).

@lovesh
Copy link
Member

lovesh commented May 25, 2023

My original idea for that was for our product to request two pseudonyms before sending the registration VC:
one created from secretKey + a poll/user-specific nonce - let's call it psecretKey
one created from sId + another poll/user-specific nonce - let's call it psId

There is one additional thing that you need when using the nonce. The same user can register multiple times by using different nonces so it needs to be proven that the sId used in each psId is different. The naive approach (and the only one i can think of now) is for the registration service to publish these psIds and during registration the user proves that his sId is not used in any psIds. The crypto logic would be proving the sId in his pseudonym psId = G*sId + H*r is different from each existing pseudonym like G*sId1 + H*r1, G*sId2 + H*r2, ..., where the rs are nonces and G and H are public bases for commitment. But this is not practical (beyond few hundred pseudonyms) as each inequality requires a separate proof. With this you won't need registration VC as you can directly use the above strategy for the poll. However we don't support any specific kind of inequlity proofs and you have to use the zk-SNARK and our Circom integration.
But if you can live with this limitation that pseudonym psId can be brute forced then having 2 pseudonyms as above (without nonce) makes sense.
However the best approach for now would be to introduce another credential thats issued to a user only once by Privency (simialar to your transitory VC). Before a user can request registration VC, it uses the university VC in a presentation revealing his sId to get a blinded VC where user chooses a random value with sufficient entropy, say 256-bits, called masterSecretKey, blinds it and request a Privency VC which has the masterSecretKey as an attribute. Now on each request of registration VC, user creates a pseudonym bound to masterSecretKey without any nonce. But even this has the limitation that a user has the same pseudonym during different registration phases and is correlated though you can't tie it to any sId.

I need to add a third attribute to this bounded pseudonym: yet another poll-specific nonce.
Otherwise, if users reuse the same secretKey, the same pseudonym would be used for all the polls created for that university, and we'd want to avoid that.

This will cause the same problem as above. I think its reasonable to expect users to generate a different secretKey for each poll.

The "registered user" could be yet another ID that is unique to each registration VC and generated by Privency on registration. We can call the attribute rId. It would only be used for the purpose of revocation.

Privency will know which "registered user" corresponds to which masterSecretKey. If thats acceptable then you can take this route.

I am wondering if there is another revocation mechanism I simply missed that's already widely adopted?

The 2 already mentioned seem the most appropriate. There is a transparent construction of accumulators but I haven't looked at it much yet.

@nicobao
Copy link
Contributor Author

nicobao commented May 26, 2023

My original idea for that was for our product to request two pseudonyms before sending the registration VC:
one created from secretKey + a poll/user-specific nonce - let's call it psecretKey
one created from sId + another poll/user-specific nonce - let's call it psId

There is one additional thing that you need when using the nonce. The same user can register multiple times by using different nonces so it needs to be proven that the sId used in each psId is different. The naive approach (and the only one i can think of now) is for the registration service to publish these psIds and during registration the user proves that his sId is not used in any psIds. The crypto logic would be proving the sId in his pseudonym psId = G*sId + H*r is different from each existing pseudonym like G*sId1 + H*r1, G*sId2 + H*r2, ..., where the rs are nonces and G and H are public bases for commitment. But this is not practical (beyond few hundred pseudonyms) as each inequality requires a separate proof. With this you won't need registration VC as you can directly use the above strategy for the poll. However we don't support any specific kind of inequlity proofs and you have to use the zk-SNARK and our Circom integration. But if you can live with this limitation that pseudonym psId can be brute forced then having 2 pseudonyms as above (without nonce) makes sense. However the best approach for now would be to introduce another credential thats issued to a user only once by Privency (simialar to your transitory VC). Before a user can request registration VC, it uses the university VC in a presentation revealing his sId to get a blinded VC where user chooses a random value with sufficient entropy, say 256-bits, called masterSecretKey, blinds it and request a Privency VC which has the masterSecretKey as an attribute. Now on each request of registration VC, user creates a pseudonym bound to masterSecretKey without any nonce. But even this has the limitation that a user has the same pseudonym during different registration phases and is correlated though you can't tie it to any sId.

Hmm true. Interesting approach. I'd want to look into that after the first product is out.
I think Privency could generate and enforce a nonce that's poll-specific, not user/transaction-specific. By sending it in a Verifiable Credential pre-registration.

Three nonces:

  • nonce for sId registration to psId
  • nonce for secretKey registration to psecretKey
  • nonce for voting, only used during voting in the specific poll, never used during registration.

Advantages of doing so:

  • Using the nonce for registering sId to a poll means generating two different psId for two different polls: that would help to prevent Privency from knowing that the same underlying sId associated with a unique psId registered to two different polls. Of course brute-force attacks are still possible if sId is not cryptographically secure, as Privency knows the nonce.
  • Using the nonce for secretKey leads to two different psecretKey for each poll, meaning like for psId that we can't correlate them. Of course, we could also expect the user to generate two different secretKey for each poll. SecretKey are likely to be secured, as it will be automatically generated so brute-force attacks are not an issue. This nonce is the least useful one.
  • Using the nonce for the vote is the most important as the voting data is public. Having a nonce for each vote results in each user having a unique pseudonym for each poll. For a third-party viewer, that means every poll have a brand new set of unique pseudonyms.

Though I admit, using these nonces is not critical for the first version of the product, especially if it is assumed that users use a different secretKey for every poll, which we can make the default.

I need to add a third attribute to this bounded pseudonym: yet another poll-specific nonce.
Otherwise, if users reuse the same secretKey, the same pseudonym would be used for all the polls created for that university, and we'd want to avoid that.

This will cause the same problem as above. I think its reasonable to expect users to generate a different secretKey for each poll.

The "registered user" could be yet another ID that is unique to each registration VC and generated by Privency on registration. We can call the attribute rId. It would only be used for the purpose of revocation.

Privency will know which "registered user" corresponds to which masterSecretKey. If thats acceptable then you can take this route.

I am wondering if there is another revocation mechanism I simply missed that's already widely adopted?

The 2 already mentioned seem the most appropriate. There is a transparent construction of accumulators but I haven't looked at it much yet.

Can I use accumulators without a ledger? I am not very familiar how it works.

I just added support for unboundedPseudonyms.

I am still not sure why the test does not pass (the "verify" function on the verifier side). I get Verifying proof returned error BBSPlusError(FirstSchnorrVerificationFailed). I guess that's because of the encoding/decoding method I used for passing around pseudonyms and bases (new TextDecoder() and new TextEncoder()), but I am not sure what else to use.

@nicobao nicobao force-pushed the feat-pseudonyms-presentation branch 2 times, most recently from 1934a80 to 5dcb622 Compare May 26, 2023 10:52
tests/anonymous-credentials/presentation.spec.ts Outdated Show resolved Hide resolved
src/anonymous-credentials/presentation.ts Outdated Show resolved Hide resolved
src/Pseudonym.ts Outdated Show resolved Hide resolved
@lovesh
Copy link
Member

lovesh commented May 26, 2023

I think Privency could generate and enforce a nonce that's poll-specific, not user/transaction-specific. By sending it in a Verifiable Credential pre-registration.

With this pre-registration VC, Privency would which student is willing to participate in the poll.

Three nonces:

Like i mentioned before, if you are using the inequality proof, you can have just one poll specific pseudonym and thus only 1 nonce.

Using the nonce for registering sId to a poll means generating two different psId for two different polls: that would help to prevent Privency from knowing that the same underlying sId associated with a unique psId registered to two different polls. Of course brute-force attacks are still possible if sId is not cryptographically secure, as Privency knows the nonce.

I have been meaning that Privency does not know the nonce. Because knowing the nonce makes brute forcing possible, eg psId = G*sId + H*nonce now Privency can compute G*sId as psId - H*nonce and brute-force for sId

Though I admit, using these nonces is not critical for the first version of the product, especially if it is assumed that users use a different secretKey for every poll, which we can make the default.

So for the first version, having the pseudonym psId without a nonce is acceptable?

Can I use accumulators without a ledger? I am not very familiar how it works.

Yes. Infact this library does not assume a ledger. You can read more about accumulators here and here.

I am still not sure why the test does not pass

Added comments. Please don't force push for now. We can squash the commits later.

@nicobao
Copy link
Contributor Author

nicobao commented May 26, 2023

I think Privency could generate and enforce a nonce that's poll-specific, not user/transaction-specific. By sending it in a Verifiable Credential pre-registration.

With this pre-registration VC, Privency would which student is willing to participate in the poll.

Yes, Privency will know which psId registered to the poll with the 1st solution (with or without the Privency-known nonces I suggested). But not necessarily which student! Privency would still to be able to brute-force the sId, and depending on its strengh it may not be possible. If sId is weak, Privency would still need to have the desire to brute-force the psId, so there's trust. Then of course there is a risk of data leak. In that case, bad actors would still need to have access to the University DB as the sId records are usually not public information. It is not perfect, but again it is only for registration and not voting.

Three nonces:

Like i mentioned before, if you are using the inequality proof, you can have just one poll specific pseudonym and thus only 1 nonce.

If the inequality proof solution can achieve good performance, it is a superior solution to the one I proposed. But I can't afford diving into Circom right now. (I am very fortunate you guys implemented the Pseudonym functionality already 👍 .) That's a great candidate for v2.

Using the nonce for registering sId to a poll means generating two different psId for two different polls: that would help to prevent Privency from knowing that the same underlying sId associated with a unique psId registered to two different polls. Of course brute-force attacks are still possible if sId is not cryptographically secure, as Privency knows the nonce.

I have been meaning that Privency does not know the nonce. Because knowing the nonce makes brute forcing possible, eg psId = G*sId + H*nonce now Privency can compute G*sId as psId - H*nonce and brute-force for sId

Yes, you're right, let's forget about the nonce for generating psId. The nonce for voting is not necessary if I make sure on the holder side that the secretKey registered by voters are different for each poll.
(Besides, if some voter want their votes to be correlatable, why not allowing it? They're just anonymous votes that's never linkable with psId or sId anyway).

Though I admit, using these nonces is not critical for the first version of the product, especially if it is assumed that users use a different secretKey for every poll, which we can make the default.

So for the first version, having the pseudonym psId without a nonce is acceptable?

Definitely yes. In the first version of the product, I will have a personal relationship with "the University" and I may be able to convince them to issue a cryptographically secure sId for each student.
And even if I don't, the product would still offer a good security model, considering nobody would now who voted what.

Also, there is another functionality where we handle the issuing of credentials for the University (or any group). In that case, I will have total freedom to issue a cryptographically secure sId in the VCs.

Can I use accumulators without a ledger? I am not very familiar how it works.

Yes. Infact this library does not assume a ledger. You can read more about accumulators here and here.

Nice! I will look into that. Thanks for the links.

I am still not sure why the test does not pass

Added comments. Please don't force push for now. We can squash the commits later.

Thank you! Looking at it now! OK, I won't force push.

@nicobao
Copy link
Contributor Author

nicobao commented May 26, 2023

Here is the summary of how I would do the flow after our discussions.
Maybe I misunderstood some of your remarks, happy to get feedback.

Registration to a specific poll:

  1. User logs in to Privency via OAuth2/WebAuthn
  2. User starts the registration process for a specific poll
  3. Before Privency issues anything, Privency needs to verify that sId has never been registered or has been registered by the same user.
    1. the user sends psId from the University VC he holds. Privency keeps record of psId.
    2. Two possibilities:
      • If it's a first-time registration, Privency checks if psId is already used by another account. If yes, then stop, else continue
      • If the user had already registered (like from another device/wallet), Privency checks if the presented psId is different from the psId already associated with the user. If yes, then stop, else continue.
  4. User blinds sId and sends it to Privency. Privency temporarily records this blinded sId to issue sId VC and registration VC.
  5. Privency issues a sId VC to the user containing the sId, the sIdCommitKey and the poll ID as attributes
  6. User provides psId calculated from sId and sIdCommitKey attributes in sId VC
  7. If psId does not match with the one previously recorded, stop there, else continue. This step is necessary to make sure the blinded sId the user sent to Privency is the same as the one in the University VC he holds.
  8. User sends blinded secretKey to Privency. The secretKey is a cryptographically random value that's automatically generated on the wallet side for every poll. Privency temporarily records the blinded secretKey (for the purpose of issuing secret VC and registration VC).
  9. Privency issues a secret VC containing as attribute the poll ID, secretKey and secretKeyCommitKey.
  10. User presents psecretKey, a pseudonym bounded to secret VC attribute secretKey using attribute secretKeyCommitKey as base. Privency records this pseudonym.
  11. Privency checks the validity of the secretKey:
    1. If it's a first-time registration, do nothing and continue.
    2. Else, Privency checks whether psecretKey is different from the one that was already associated with the user. If there is a difference, stop, else continue.
  12. Privency issues a registration VC that contains secretKey and sId (using the blinded versions previously recorded), as well as secretKeyCommitKey, sIdCommitKey and the poll ID as attributes.
  13. The registration VC will be used for voting in this specific poll (identified via the poll ID).

User is voting:

  1. user does not need to be logged in
  2. user provide non-revocation proofs for holding registration VC and university VC
  3. user provides proof of equality that sId in his registration VC correponds to the one in his university VC
  4. Privency sends off-band a baseForSecretKey and basesForAttributes
  5. User verifies that baseForSecretKey!=secretKeyCommitKey and basesForAttributes!=sIdCommitKey where secretKeyCommitKey and sIdCommitKey are extracted from registration VC. If OK, then continue, then user don't accept.
  6. User sends a Pseudonym called pvote bounded to attributes secretKey and sId from the registration VC (pvote=basesForAttributes*sId + baseForSecretKey*secretKey). This pseudonym will be used as unique identifier for the vote. Explanation:
    • Even though Privency knows psId and psecretKey such that psId=sIdCommitKey*sId and psecretKey=secretKeyCommitKey*secretKey, pvote uses both sId and secretKey, and those values are kept secret from Privency.
    • To make sure, pvote is not correlatable to the OAuth2 account, voters made sure that secretKeyCommitKey!=baseForSecretKey and sIdCommitKey!=basesForAttributes - essentially that the bases are different. Otherwise, it would have been trivial to try all the accounts' psecretKey until we get one that matches with psecretKey=pvote - psId. Also, because psId is possibly brute-forceable to sId, we could expect attackers to guess baseForSecretKey*secretKey=pvote - basesForAttributes*sId. Because baseForSecretKey!=secretKeyCommitKey, it still wouldn't be possible to correlate the vote with the user account.
    • Therefore, so long as secretKey is kept private by the voter, Privency cannot correlate pvote with the OAuth2 account bound to psId, and Privency cannot know who voted what.

Note:

  • we could possibly ensure the secretKey is secure enough on the verifier side, like by enforcing a proof when providing the blinded values.
  • I omitted the classic VC-validity and non-revocation proof checks for the sake of simplicity
  • on issuing a registration VC to the same user for the second time, we need to make sure the user uses the same secretKey, otherwise he would end up having a different Pseudonym for the vote, and therefore be able to vote twice. By enforcing the same secretKey, we make sure that when he votes again, his previous vote will simply be modified.
  • on issuing a registration VC to a user, we make sure the sId is not already in use, because otherwise the same person identified by the sId could create multiple OAUth2 accounts to vote multiple times.
  • if after having registered once, a user loses his registration VC and also forgot his secretKey, then he will never be able to vote (or change his vote) in the registered poll.
  • if psId is succesfully brute-forced to guess sId, and the same baseForSecretKey are used for all the polls, then the anonymous votes from the same user could be correlated between different polls using the formula: baseForSecretKey*secretKey=pvote - basesForAttributes*sId. This would not endanger voter's privacy so it is not critical. And there are easy ways for voters to make sure the baseForSecretKey is different everytime. We could also add a second secretKey to the loop. It would annihilate the problem completely.

lovesh added a commit that referenced this pull request May 28, 2023
Signed-off-by: lovesh <[email protected]>
@lovesh
Copy link
Member

lovesh commented May 28, 2023

If the university VC has sId, I don't see the need for sId VC. User can send a pseudonym bound to attribute sId while presenting university VC with the scope being the poll Id

  1. If psId does not match with the one previously recorded, stop there, else continue. This step is necessary to make sure the blinded sId the user sent to Privency is the same as the one in the University VC he holds.

Does this step happen during the request of registration VC or even before that?

  1. Privency issues a secret VC containing as attribute the poll ID, secretKey and secretKeyCommitKey.

secret VC is not needed as secret will be present in registration VC.

  1. User presents psecretKey, a pseudonym bounded to secret VC attribute secretKey using attribute secretKeyCommitKey as base. Privency records this pseudonym.

This isn't needed because a pseudonym will be submitted later during voting anyway.

  1. Privency issues a registration VC that contains secretKey and sId (using the blinded versions previously recorded), as well as secretKeyCommitKey, sIdCommitKey and the poll ID as attributes.

secretKeyCommitKey, sIdCommitKeydon't need to be attributes. You can have a convention that they are derived frompollId`

  1. Privency sends off-band a baseForSecretKey and basesForAttributes.
    User verifies that baseForSecretKey!=secretKeyCommitKey and basesForAttributes!=sIdCommitKey

Privency can avoid sending these bases and thus the check as well.

on issuing a registration VC to the same user for the second time, we need to make sure the user uses the same secretKey, otherwise he would end up having a different Pseudonym for the vote, and therefore be able to vote twice.

Why would it be issued the second time for the same poll?

Have added a test here for this use-case. There are comments in the test to explain my thinking. signed1 is for university VC and signed2 is for registration VC. user-id is what you call sId (sorry for the naming, was using existing test data)

@nicobao
Copy link
Contributor Author

nicobao commented May 30, 2023

I just pushed the changes you requested, except for the reference to the bases (responded to your comments).
The tests passes now!

If the university VC has sId, I don't see the need for sId VC. User can send a pseudonym bound to attribute sId while presenting university VC with the scope being the poll Id

The need is to make sure the sId that will be put in registration VC was actually taken from university VC.
The sId VC is just a way I found to work-around that problem. I don't know how to send a blinded sId together with a proof that it was taken from university VC, without having this intermediary sId VC.

  1. If psId does not match with the one previously recorded, stop there, else continue. This step is necessary to make sure the blinded sId the user sent to Privency is the same as the one in the University VC he holds.

Does this step happen during the request of registration VC or even before that?

During the same request flow. This step happens before registration VC is issued, to verify eligibility.

  1. Privency issues a secret VC containing as attribute the poll ID, secretKey and secretKeyCommitKey.

secret VC is not needed as secret will be present in registration VC.

secret VC is a work-around to the following problem:

  • i want to send a blinded secret together with a Pseudonym and a proof that this Pseudonym was calculated from this blinded secret.

I don't know how to do that without the secret VC intermediary.

  1. User presents psecretKey, a pseudonym bounded to secret VC attribute secretKey using attribute secretKeyCommitKey as base. Privency records this pseudonym.

This isn't needed because a pseudonym will be submitted later during voting anyway.

The pseudonym submitted during voting is not the same as the one that is recorded.
The pseudonym that is recorded is there to make sure users will always use the same secretKey.
For example, let's say you registered with a secretKey on your phone, and then you use your laptop to register again. Or you lost your registration VC, so you request its revocation, and then you request a new one. We need to make sure the same secretKey is used, otherwise they could vote twice. Or would it be possible for Privency in that case, to extract the blinded secret that was used the first time from the original registration VC? How so?

The Pseudonym that is used for voting should be unlinkable with the Pseudonym that was recorded during registration:

  • the pseudonyms that are recorded during registration are:
    • an attribute-based pseudonym based on sId. The sId attribute will end up in registration VC.
    • a pseudonym based on a secretKey. The secretKey attribute will end up in registration VC.
  • the Pseudonym that is used for voting is an attribute-based voting that uses secretKey and sId in registration VC and different bases. Please can you confirm that using different bases make this pseudonym unlikable with registration pseudonyms described above?
  1. Privency issues a registration VC that contains secretKey and sId (using the blinded versions previously recorded), as well as secretKeyCommitKey, sIdCommitKey and the poll ID as attributes.

secretKeyCommitKey, sIdCommitKeydon't need to be attributes. You can have a convention that they are derived frompollId`

Good point. Having different bases is key to ensure privacy though. Somehow the frontend should verify that at all times.

  1. Privency sends off-band a baseForSecretKey and basesForAttributes.
    User verifies that baseForSecretKey!=secretKeyCommitKey and basesForAttributes!=sIdCommitKey

Privency can avoid sending these bases and thus the check as well.

Yea, I see.

on issuing a registration VC to the same user for the second time, we need to make sure the user uses the same secretKey, otherwise he would end up having a different Pseudonym for the vote, and therefore be able to vote twice.

Why would it be issued the second time for the same poll?

Two different devices. (Though VC could be sync between them. I know Findy Agency does that in the Hyperledger realm. Does that exist for Dock wallet?). Or the user lost the registration VC, and requests a new one. It must be the same secretKey otherwise if he votes again to the same poll, it would count twice (Privency has no way no know if people voted after registration, and Privency allows for users to change their vote over time.)

Have added a test here for this use-case. There are comments in the test to explain my thinking. signed1 is for university VC and signed2 is for registration VC. user-id is what you call sId (sorry for the naming, was using existing test data)

Thank you for doing the unit test for my use-case!
Will look at it in more details asap.

@nicobao
Copy link
Contributor Author

nicobao commented May 31, 2023

In your test, I don't understand this part.
How does witnessEq.addWitnessRef(statementIndex: number, witnessIndex: number) really work?
How is witnessId chosen? Why is the index different between BBS+ and BBS?
Could we make a similar witness equality to prove that a blinded secret is the same that was used to generate a presented pseudonym?

Not understanding these witness equalities is why I was suggesting using sId VC and secret VC.

@lovesh
Copy link
Member

lovesh commented May 31, 2023

The need is to make sure the sId that will be put in registration VC was actually taken from university VC.
The sId VC is just a way I found to work-around that problem. I don't know how to send a blinded sId together with a proof that it was taken from university VC, without having this intermediary sId VC.

The test i shared does that. Its the equality enforced here.

i want to send a blinded secret together with a Pseudonym and a proof that this Pseudonym was calculated from this blinded secret.

See this equality. I am creating the pseudonym from both sId and secret though but its the same idea.

For example, let's say you registered with a secretKey on your phone, and then you use your laptop to register again. Or you lost your registration VC, so you request its revocation, and then you request a new one. We need to make sure the same secretKey is used, otherwise they could vote twice.

To get a registration VC, user has to submit a pseudonym to its sId and Privency can check if this pseudonym has requested a VC before or not. This is the one i am referring to. And the base here can be poll specific to ensure that requests across polls cant be correlated. In case of revocation, the VC won't be valid so any action using the VC should be rejected. So no compulsion to use the secret key. I didn't show revocation in test but you could use attribute registration-id for it.

The Pseudonym that is used for voting should be unlinkable with the Pseudonym that was recorded during registration

They are unlinkable (in the test).

a pseudonym based on a secretKey. The secretKey attribute will end up in registration VC.

This shouldn't be needed.

the Pseudonym that is used for voting is an attribute-based voting that uses secretKey and sId in registration VC and different bases. Please can you confirm that using different bases make this pseudonym unlikable with registration pseudonyms described above?

Yes.

@lovesh
Copy link
Member

lovesh commented May 31, 2023

How does witnessEq.addWitnessRef(statementIndex: number, witnessIndex: number) really work?

I assume you've read this so I won't describe the terminology. Witness equality (currently) is for proving the equality of messages in a Pedersen commitment without revealing them and uses a sigma protocol (Schnoor proof of knowledge). Eg. for 2 commitments C1 and C2 with C1 = G1 * m1 + G2 * m2 + G3 * m3 + G4 * m4 and C2 = H1 * n1 + H2 * n2 + H3 * n3, i can prove that m2 equals n3. The witness refs i will add are (0, 1) and (1, 2). 0 in (0,1) refers to C1 as its the 0th statement (0-based indexing) and 1 refers to m2. Similarly C2 is the 1st statement and n3 has index 2 in it. Psuedonym is of the form G1 * a1 + G2 * a2 where a1 and a2 are attributes. When requesting blind sigs, the blinded attributes are given as H1* a1 + H2 * a2 + .... Signatures also contain attributes in this form H1* a1 + H2 * a2 + ... (there are other details for signature but the idea is same).

How is witnessId chosen?

Its the index of the attribute in the above form.

Why is the index different between BBS+ and BBS?

BBS does not have a random value, BBS+ does, and thus the offset. The abstraction I will have here will hide these details.

Could we make a similar witness equality to prove that a blinded secret is the same that was used to generate a presented pseudonym?

Yes.

@nicobao
Copy link
Contributor Author

nicobao commented May 31, 2023

The need is to make sure the sId that will be put in registration VC was actually taken from university VC.
The sId VC is just a way I found to work-around that problem. I don't know how to send a blinded sId together with a proof that it was taken from university VC, without having this intermediary sId VC.

The test i shared does that. Its the equality enforced here.

👍

i want to send a blinded secret together with a Pseudonym and a proof that this Pseudonym was calculated from this blinded secret.

See this equality. I am creating the pseudonym from both sId and secret though but its the same idea.

👍

For example, let's say you registered with a secretKey on your phone, and then you use your laptop to register again. Or you lost your registration VC, so you request its revocation, and then you request a new one. We need to make sure the same secretKey is used, otherwise they could vote twice.

To get a registration VC, user has to submit a pseudonym to its sId and Privency can check if this pseudonym has requested a VC before or not. This is the one i am referring to. And the base here can be poll specific to ensure that requests across polls cant be correlated. In case of revocation, the VC won't be valid so any action using the VC should be rejected. So no compulsion to use the secret key. I didn't show revocation in test but you could use attribute registration-id for it.

"In case of revocation, the VC won't be valid so any action using the VC should be rejected"
=> yes but what if the action has already taken place? Say someone voted before, with their valid registration VC when it was valid, before it was revoked. They used the secret for the vote, through a poll-specific unique identifier.

Say they later revoke the registration VC. OK, the potential thief cannot vote with it again. And the old vote can still count, it is fine.
And then I issue a new registration VC, with a different secret, to the same user.
On attempting to vote to the same poll the user already voted on, the user generates a new Pseudonym, that will act as a new identifier for the poll, identifier which is totally different and unlikable with the identifier the user used originally with the now-revoked registration VC. I will therefore have to count the vote twice, instead of modifying the old vote.

That is why, in my view Privency must re-issue the same secret in the registration VC. Or is there something that I am missing?

The Pseudonym that is used for voting should be unlinkable with the Pseudonym that was recorded during registration

They are unlinkable (in the test).

👍

the Pseudonym that is used for voting is an attribute-based voting that uses secretKey and sId in registration VC and different bases. Please can you confirm that using different bases make this pseudonym unlikable with registration pseudonyms described above?

Yes.

👍

Thanks for the explanation on witness equality, it helps.

For your comment on the bases in the spec, how do you want to proceed? Apart from that, I think the PR is ready on my side.

I am trying to see how to interact with Dock Wallet as a verifier or an issuer, and I am not sure how to proceed. Which protocol is the wallet using? DIDcomm? What should I do to use the new Pseudonym functionality in Dock Wallet (request a Pseudonym)?
In general, what's the technique to request a presentation in Dock Wallet (like the "presentation request" functionality in Hyperledger Aries)?

@lovesh
Copy link
Member

lovesh commented Jun 1, 2023

And then I issue a new registration VC, with a different secret, to the same user.

Privency (issuer) will know if the same user is requesting a registration VC who was given earlier and which was revoked so it can deny the request. Is that behavior not acceptable?

For your #19 (comment), how do you want to proceed? Apart from that, I think the PR is ready on my side.

Sorry about that, missed 2 comments. Replied now.

Dock wallet does not support pseudonyms or most predicates in this repo yet.

@nicobao
Copy link
Contributor Author

nicobao commented Jun 1, 2023

And then I issue a new registration VC, with a different secret, to the same user.

Privency (issuer) will know if the same user is requesting a registration VC who was given earlier and which was revoked so it can deny the request. Is that behavior not acceptable?

Not really, because this behavior would mean that once revoked, the user can never request a new registration VC again, and hence never vote to the poll (again - or not, we cannot know).
By enforcing the same secret on issuing a new registration VC to the same user, the user can still vote on the poll, so long as he remembers the secret.

But I think with your tremendous help and the unit test you wrote, I can implement this functionality now!
Thanks a lot!

For your #19 (comment), how do you want to proceed? Apart from that, I think the PR is ready on my side.

Sorry about that, missed 2 comments. Replied now.

Looking at it now.

Dock wallet does not support pseudonyms or most predicates in this repo yet.

I am choosing between:

  • helping you add support for it in Dock Wallet => is it possible, and where would I need to start?
  • creating my own wallet, eventually by combining https://github.com/uport-project/veramo with crypto-wasm-ts

@nicobao nicobao marked this pull request as ready for review June 1, 2023 18:17
@nicobao
Copy link
Contributor Author

nicobao commented Jun 1, 2023

I switched my PR from "DRAFT" to "READY".
Let me know when/if I need to squash commits.

@nicobao
Copy link
Contributor Author

nicobao commented Jun 1, 2023

Just changed from hex to base64 as you suggested.

Btw I noticed some linting issue on files unrelated to my changes when running yarn pretty:

	modified:   src/anonymous-credentials/credential-builder.ts
	modified:   src/anonymous-credentials/types-and-consts.ts
	modified:   src/anonymous-credentials/util.ts
	modified:   src/bbs-plus/params.ts
	modified:   src/index.ts
	modified:   src/ps/keys.ts
	modified:   src/ps/params.ts
	modified:   src/ps/signature.ts
	modified:   src/types.ts

Should I push it here?

src/Pseudonym.ts Outdated Show resolved Hide resolved
src/Pseudonym.ts Outdated Show resolved Hide resolved
src/Pseudonym.ts Outdated Show resolved Hide resolved
src/Pseudonym.ts Outdated Show resolved Hide resolved
src/anonymous-credentials/presentation-builder.ts Outdated Show resolved Hide resolved
src/anonymous-credentials/presentation-builder.ts Outdated Show resolved Hide resolved
src/anonymous-credentials/presentation-builder.ts Outdated Show resolved Hide resolved
src/anonymous-credentials/presentation-builder.ts Outdated Show resolved Hide resolved
src/anonymous-credentials/presentation-specification.ts Outdated Show resolved Hide resolved
const unboundedPseudonym = this.unboundedPseudonyms[i];
const pseudonym = Pseudonym.new(unboundedPseudonym.baseForSecretKey, unboundedPseudonym.secretKey);
const decodedBaseForSecretKey = PseudonymBases.decodeBaseForSecretKey(unboundedPseudonym.baseForSecretKey);
const decodedPseudonym = PseudonymBases.decode(pseudonym.value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly misleading as pseudonym is not a base. Better to define encode, decode on Pseudonym. Use them above as well instead of pseudonym.base64

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it weird to have Pseudonym.encode(value) and the decode equivalent when both AttributeBoundPseudonym and Pseudonym derive from BytearrayWrapper. The encode and decode static functions I use for AttributeBoundPseudonym are taken from Pseudonym in a very arbitrary way.
I'd find it more appropriate to use (static OR not) functions from BytearrayWrapper.
Though I think it is just a question of style, I'll do whatever you prefer.
I just implemented it in Pseudonym like you mentioned.

@lovesh
Copy link
Member

lovesh commented Jun 2, 2023

I can implement this functionality now

I will submit support for blind issuance in the anoncreds abstraction soon so you won't have to rely on lower level objects.

helping you add support for it in Dock Wallet => is it possible, and where would I need to start?

The wallet is closed source but I have asked if they can grant you access.

Btw I noticed some linting issue on files unrelated to my changes ...

Sure. Please merge the latest master as well.

@nicobao nicobao force-pushed the feat-pseudonyms-presentation branch from 70674e0 to a4a8178 Compare June 2, 2023 14:48
@nicobao nicobao force-pushed the feat-pseudonyms-presentation branch from 0032477 to 63aeffa Compare June 2, 2023 17:52
@nicobao
Copy link
Contributor Author

nicobao commented Jun 2, 2023

I can implement this functionality now

I will submit support for blind issuance in the anoncreds abstraction soon so you won't have to rely on lower level objects.

👍

helping you add support for it in Dock Wallet => is it possible, and where would I need to start?

The wallet is closed source but I have asked if they can grant you access.

Would be cool!

Btw I noticed some linting issue on files unrelated to my changes ...

Sure. Please merge the latest master as well.

I just pushed the changes you requested, including the linting.
I rebased and fixup the branch, so to avoid issues, you should git pull --rebase instead of using standard git pull if you want to commit changes to the branch directly.

@lovesh lovesh merged commit 8dd7dbf into docknetwork:master Jun 5, 2023
@nicobao nicobao deleted the feat-pseudonyms-presentation branch June 6, 2023 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add pseudonyms support for presentation
2 participants