Skip to content

Commit

Permalink
Credentials with blinded revocation status and top level attributes
Browse files Browse the repository at this point in the history
Signed-off-by: lovesh <[email protected]>
  • Loading branch information
lovesh committed Apr 26, 2024
1 parent 2d48f89 commit f23c8e0
Show file tree
Hide file tree
Showing 20 changed files with 621 additions and 138 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docknetwork/crypto-wasm-ts",
"version": "0.59.0",
"version": "0.60.0",
"description": "Typescript abstractions over Dock's Rust crypto library's WASM wrapper",
"homepage": "https://github.com/docknetwork/crypto-wasm-ts",
"main": "lib/index.js",
Expand Down
62 changes: 60 additions & 2 deletions src/accumulator/witness-update-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
publicInfoForKBUniversalNonMemWitnessUpdate, publicInfoForKBUniversalNonMemWitnessUpdateOnDomainExtension
} from 'crypto-wasm-new';
import { BytearrayWrapper } from '../bytearray-wrapper';
import { jsonObjToUint8Array } from '../util';
import { fromLeToBigInt, jsonObjToUint8Array } from '../util';
import { KBUniversalAccumulatorValue } from './kb-universal-accumulator';
import { AccumulatorSecretKey } from './params-and-keys';

Expand All @@ -17,7 +17,7 @@ export class WitnessUpdateInfo extends BytearrayWrapper {
}

/**
* Public info published by the accumulator manager used to update witnesses after several additions and removals.
* Public info published by the VB accumulator manager used to update witnesses after several additions and removals.
*/
export class VBWitnessUpdateInfo extends WitnessUpdateInfo {
fromJSON(json: string): VBWitnessUpdateInfo {
Expand Down Expand Up @@ -71,6 +71,9 @@ export class KBUniversalMembershipWitnessUpdateInfo extends WitnessUpdateInfo {
}
}

/**
* Public info published by the KB universal accumulator manager used to update non-membership witnesses after several additions and removals.
*/
export class KBUniversalNonMembershipWitnessUpdateInfo extends WitnessUpdateInfo {
fromJSON(json: string): KBUniversalNonMembershipWitnessUpdateInfo {
return new KBUniversalNonMembershipWitnessUpdateInfo(jsonObjToUint8Array(json));
Expand Down Expand Up @@ -119,3 +122,58 @@ export class KBUniversalNonMembershipWitnessUpdateInfo extends WitnessUpdateInfo
);
}
}

/**
* Public info published by the KB universal accumulator manager used to update membership and non-membership witnesses after several additions and removals.
*/
export class KBUniversalWitnessUpdateInfo {
readonly mem?: KBUniversalMembershipWitnessUpdateInfo;
readonly nonMem?: KBUniversalNonMembershipWitnessUpdateInfo;
// Maximum size in bytes of the membership witness update info can be `2^maxByteSize`
static readonly maxByteSize = 4;
static readonly maxLength = BigInt(1) << BigInt(32);

constructor(mem?: KBUniversalMembershipWitnessUpdateInfo, nonMem?: KBUniversalNonMembershipWitnessUpdateInfo) {
this.mem = mem;
this.nonMem = nonMem;
}

/**
* Returns a bytearray containing both the membership and non-membership witness update info. The first `maxByteSize` byte
* of the result contains the byte size of the membership witness update info. This is followed by the bytes of membership
* witness update info, followed by the bytes of non-membership witness update info.
*/
toBytes(): Uint8Array {
const memLength = this.mem ? this.mem.value.length : 0;
if (memLength > KBUniversalWitnessUpdateInfo.maxLength) {
throw new Error(`Cannot support sizes greater than ${KBUniversalWitnessUpdateInfo.maxLength}`)
}
const buf = Buffer.allocUnsafe(KBUniversalWitnessUpdateInfo.maxByteSize + memLength + (this.nonMem ? this.nonMem.value.length : 0));
// Write the byte size of membership witness update info in the first `maxByteSize` bytes in little-endian format
buf.writeUIntLE(memLength, 0, KBUniversalWitnessUpdateInfo.maxByteSize);
const merged = new Uint8Array(buf);
if (this.mem) {
merged.set(this.mem.value, KBUniversalWitnessUpdateInfo.maxByteSize);
}
if (this.nonMem) {
merged.set(this.nonMem.value, KBUniversalWitnessUpdateInfo.maxByteSize + memLength);
}
return merged;
}

/**
* Creates `KBUniversalWitnessUpdateInfo` from its byte representation
* @param bytes - This is the result of `KBUniversalWitnessUpdateInfo.toBytes`
*/
static fromBytes(bytes: Uint8Array): KBUniversalWitnessUpdateInfo {
const memLength = fromLeToBigInt(bytes, KBUniversalWitnessUpdateInfo.maxByteSize);
if (memLength > KBUniversalWitnessUpdateInfo.maxLength) {
throw new Error(`Cannot support sizes greater than ${KBUniversalWitnessUpdateInfo.maxLength}`)
}
// Create the update info if non-zero byte size found
const mem = memLength > 0 ? new KBUniversalMembershipWitnessUpdateInfo(bytes.slice(KBUniversalWitnessUpdateInfo.maxByteSize, KBUniversalWitnessUpdateInfo.maxByteSize + Number(memLength))) : undefined;
const nonMemVal = bytes.slice(KBUniversalWitnessUpdateInfo.maxByteSize + Number(memLength));
const nonMem = nonMemVal.length > 0 ? new KBUniversalNonMembershipWitnessUpdateInfo(nonMemVal) : undefined;
return new KBUniversalWitnessUpdateInfo(mem, nonMem);
}
}
24 changes: 21 additions & 3 deletions src/anonymous-credentials/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ A credential always contains a schema as one of the attribute (inline, not a ref

A [CredentialBuilder](./credential-builder.ts) is used to build a credential by setting various attributes and
then signed using the issuer's secret key resulting in a [Credential](./credential.ts) which can then be verified using the
public key of the issuer. A credential might have a status field indicating whether the credential can be revoked or not. Currently only 1
mechanism is supported and that is accumulator but the status property is oblivious to that.
public key of the issuer. A credential might have a `status` field indicating whether the credential can be revoked or not. Currently only 1
mechanism is supported and that is accumulator but the s`tatus` property is oblivious to that.

See these [tests](../../tests/anonymous-credentials/credential.spec.ts) for examples of credential issuance, verification and (de)serialization.

Expand Down Expand Up @@ -53,7 +53,25 @@ The user while requesting such a credential might need to prove the possession o

See these [tests](../../tests/anonymous-credentials/blind-issuance.spec.ts) for examples of using these predicates.


## KVAC Credentials

KVAC stands for Keyed-Verification Anonymous Credentials. Verifying them requires the secret key of the signer (issuer) or a proof
given by the signer unlike regular credentials where signer's public key is required. For presentations created from KVAC,
they can't be "fully" verified without the signer's secret key. These presentations can be considered to be composed of 2 parts,
one which can be verified without the secret key and the other which needs the secret key. The latter is what we call [DelegatedProof](./delegated-proof.ts)
as it will be delegated to the signer to verify. The expected usage of KVAC presentations is for the verifier to verify the part
that does not require the usage of secret key and send the other part to the signer to verify thus making the verifications always
require the signer. This is useful when the signer wants to be aware of anytime its issued credential is used, eg. charging for it.
Note that the `DelegatedProof` does not contain any revealed attributes or predicates or unique ids so the signer cannot
learn any identifying information from it. Not all kinds of credentials support this capability and currently only
[BDDT16Credential](./credential.ts) supports it. Note that it does not support the `verify` method which expects signer's
public key but instead supports `verifyUsingValidityProof` method which requires a [BDDT16MacProofOfValidity](../bddt16-mac/mac.ts)
that can be created by the signer. Unlike regular credentials, `BDDT16Credential` contain a [MAC](https://en.wikipedia.org/wiki/Message_authentication_code)
and not a signature which require the secret key for verification.

This principle also applies to credential revocation (`status` field) where the revocation status can only be checked by the issuer.

See these [tests](../../tests/anonymous-credentials/delegated-proofs.spec.ts) for examples.

*Note that lot of classes mentioned above are abstract as this project supports multiple signature schemes.*

Expand Down
33 changes: 30 additions & 3 deletions src/anonymous-credentials/blinded-credential-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CredentialBuilderCommon } from './credential-builder-common';
import { IBlindCredentialRequest } from './presentation-specification';
import {
BBS_PLUS_SIGNATURE_PARAMS_LABEL_BYTES,
BBS_SIGNATURE_PARAMS_LABEL_BYTES,
BBS_SIGNATURE_PARAMS_LABEL_BYTES, STATUS_STR,
BDDT16_MAC_PARAMS_LABEL_BYTES
} from './types-and-consts';
import { BBSCredential, BBSPlusCredential, BDDT16Credential } from './credential';
Expand All @@ -18,7 +18,7 @@ import { BDDT16BlindMac, BDDT16MacParams, BDDT16MacSecretKey } from '../bddt16-m
export abstract class BlindedCredentialBuilder extends CredentialBuilderCommon {
// NOTE: Follows semver and must be updated accordingly when the logic of this class changes or the
// underlying crypto changes.
static VERSION = '0.2.0';
static VERSION = '0.3.0';

blindedCredReq: IBlindCredentialRequest;

Expand All @@ -31,13 +31,37 @@ export abstract class BlindedCredentialBuilder extends CredentialBuilderCommon {
protected getTotalAttributesAndEncodedKnownAttributes(): [number, Map<number, Uint8Array>] {
const schema = this.schema as CredentialSchema;
const flattenedSchema = schema.flatten();
const knownAttributes = this.serializeForSigning();
let knownAttributes = this.serializeForSigning();
if (this.blindedCredReq.unBlindedAttributes !== undefined) {
if (typeof this.blindedCredReq.unBlindedAttributes !== 'object') {
throw new Error(`Unblinded attributes were supposed to an object but found ${this.blindedCredReq.unBlindedAttributes}`)
}
knownAttributes = {...knownAttributes, ...this.blindedCredReq.unBlindedAttributes};
}
const encodedAttributes = new Map<number, Uint8Array>();
Object.entries(schema.encoder.encodeMessageObjectAsObject(knownAttributes)).forEach(([name, value]) => {
encodedAttributes.set(flattenedSchema[0].indexOf(name), value);
});
return [flattenedSchema[0].length, encodedAttributes];
}

protected processUnBlindedAttributes() {
if (this.blindedCredReq.unBlindedAttributes !== undefined) {
if (typeof this.blindedCredReq.unBlindedAttributes !== 'object') {
throw new Error(`Unblinded attributes were supposed to an object but found ${this.blindedCredReq.unBlindedAttributes}`)
}
for (const [name, value] of Object.entries(this.blindedCredReq.unBlindedAttributes)) {
if (name === STATUS_STR) {
if (this.credStatus !== undefined) {
throw new Error('credStatus was set by the signer when it was provided in request as well')
}
this.credStatus = value;
} else {
throw new Error(`Unsupported for blinded attribute ${name}`);
}
}
}
}
}

export class BBSBlindedCredentialBuilder extends BlindedCredentialBuilder {
Expand All @@ -58,6 +82,7 @@ export class BBSBlindedCredentialBuilder extends BlindedCredentialBuilder {
const [totalAttrs, encodedAttrs] = this.getTotalAttributesAndEncodedKnownAttributes();
const params = BBSSignatureParams.getSigParamsOfRequiredSize(totalAttrs, sigParams);
const sig = BBSBlindSignature.generate(this.blindedCredReq.commitment, encodedAttrs, secretKey, params, false);
this.processUnBlindedAttributes();
return new BBSBlindedCredential(
this.version,
this.schema as CredentialSchema,
Expand Down Expand Up @@ -94,6 +119,7 @@ export class BBSPlusBlindedCredentialBuilder extends BlindedCredentialBuilder {
params,
false
);
this.processUnBlindedAttributes();
return new BBSPlusBlindedCredential(
this.version,
this.schema as CredentialSchema,
Expand Down Expand Up @@ -124,6 +150,7 @@ export class BDDT16BlindedCredentialBuilder extends BlindedCredentialBuilder {
const [totalAttrs, encodedAttrs] = this.getTotalAttributesAndEncodedKnownAttributes();
const params = BDDT16MacParams.getMacParamsOfRequiredSize(totalAttrs, sigParams);
const sig = BDDT16BlindMac.generate(this.blindedCredReq.commitment, encodedAttrs, secretKey, params, false);
this.processUnBlindedAttributes();
return new BDDT16BlindedCredential(
this.version,
this.schema as CredentialSchema,
Expand Down
Loading

0 comments on commit f23c8e0

Please sign in to comment.