Skip to content

Manage ethereum based identities #200

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

Merged
merged 38 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9895873
create ethereum based identities
daveroga Mar 21, 2024
5aeda0e
fix rebase
daveroga Mar 22, 2024
3e96c51
remove log
daveroga Mar 22, 2024
6f0c5ac
pass parameter for ethereum identity
daveroga Mar 22, 2024
2c375b2
update state transition ethereum identities
daveroga Mar 27, 2024
c638453
method update for testing in Mumbai deployment
daveroga Mar 27, 2024
dca0556
update auth test with integration test for ethereum identities
daveroga Mar 27, 2024
2bf7b91
restore method helper iden3
daveroga Mar 27, 2024
8ec2133
update current state when creating auth credential in ethereum identi…
daveroga Mar 28, 2024
a86992c
remove unused import
daveroga Mar 28, 2024
7e43f84
add test ethereum identity flow (not integration)
daveroga Mar 28, 2024
39a2dfa
remove only in test
daveroga Mar 28, 2024
c048e0b
update mumbai for aloy
daveroga Apr 2, 2024
b31c69b
update id test
daveroga Apr 2, 2024
a68a6dd
changes proposed from review
daveroga Apr 2, 2024
d6740c1
update tests
daveroga Apr 3, 2024
17a4bf1
suggested changes from review
daveroga Apr 3, 2024
bf4d208
throw error if not ethSigner
daveroga Apr 3, 2024
f7e784d
add transit state logic to identiy wallet
daveroga Apr 3, 2024
8819374
typo and make new function to add bjj key and transit state
daveroga Apr 3, 2024
42c2c25
fix sec256k1 private key generation less than 32 bytes
daveroga Apr 4, 2024
4a1030a
updates from reviewed comments
daveroga Apr 4, 2024
48e126a
updates from review comments
daveroga Apr 5, 2024
368fdf8
updates from review
daveroga Apr 5, 2024
a19871a
update prover parameter in identity wallet
daveroga Apr 5, 2024
bb236e3
separate creation options for createIdentity and createEthereumBasedI…
daveroga Apr 5, 2024
c7409cc
update add bjj credential and test
daveroga Apr 8, 2024
cc25463
update core claim in mtp proof and revocation
daveroga Apr 8, 2024
a6eba8a
add key rotation use case test
daveroga Apr 8, 2024
2b29963
remove only in test
daveroga Apr 8, 2024
e6f1187
add transit state after revoke credential
daveroga Apr 8, 2024
60a0c50
update query proof with v3
daveroga Apr 9, 2024
571234c
check issue credentials in issuer revocation keys
daveroga Apr 9, 2024
f6728c9
revoke user keys and check
daveroga Apr 9, 2024
a48de78
changes
daveroga Apr 10, 2024
c652718
add function to get mtp from core claim
vmidyllic Apr 10, 2024
38fad21
fix
vmidyllic Apr 10, 2024
ca6cf87
remove level of nested structure
daveroga Apr 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
publishStateGeneric: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
getGISTProof: () => {
return Promise.resolve({
root: 0n,
Expand Down
12 changes: 10 additions & 2 deletions src/credentials/status/reverse-sparse-merkle-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IStateStorage } from '../../storage';
import { CredentialStatusResolver, CredentialStatusResolveOptions } from './resolver';
import { CredentialStatus, RevocationStatus, State } from '../../verifiable';
import { VerifiableConstants, CredentialStatusType } from '../../verifiable/constants';
import { isGenesisState } from '../../utils';
import { isEthereumIdentity, isGenesisState } from '../../utils';
import { IssuerResolver } from './sparse-merkle-tree';

/**
Expand Down Expand Up @@ -173,11 +173,19 @@ export class RHSResolver implements CredentialStatusResolver {
return this.getRevocationStatusFromIssuerData(issuerDID, issuerData, genesisState);
}
const currentStateBigInt = Hash.fromHex(stateHex).bigInt();
if (!isGenesisState(issuerDID, currentStateBigInt)) {

const isEthIdentity = isEthereumIdentity(issuerDID);

if (!isEthIdentity && !isGenesisState(issuerDID, currentStateBigInt)) {
throw new Error(
`latest state not found and state parameter ${stateHex} is not genesis state`
);
}

if (isEthIdentity) {
throw new Error(`State must be published for Ethereum based identity`);
}

latestState = currentStateBigInt;
}

Expand Down
231 changes: 190 additions & 41 deletions src/identity/identity-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import {
SchemaHash
} from '@iden3/js-iden3-core';
import { poseidon, PublicKey, sha256, Signature, Hex, getRandomBytes } from '@iden3/js-crypto';
import { hashElems, ZERO_HASH } from '@iden3/js-merkletree';

import { Hash, hashElems, ZERO_HASH } from '@iden3/js-merkletree';
import { generateProfileDID, subjectPositionIndex } from './common';
import * as uuid from 'uuid';
import { JSONSchema, JsonSchemaValidator, cacheLoader } from '../schema-processor';
Expand Down Expand Up @@ -43,13 +42,14 @@ import {
TreesModel
} from '../credentials';
import { TreeState } from '../circuits';
import { byteEncoder } from '../utils';
import { buildDIDFromEthPubKey, byteEncoder } from '../utils';
import { Options } from '@iden3/js-jsonld-merklization';
import { TransactionReceipt } from 'ethers';
import {
CredentialStatusPublisherRegistry,
Iden3SmtRhsCredentialStatusPublisher
} from '../credentials/status/credential-status-publisher';
import { ProofService } from '../proof';

/**
* DID creation options
Expand All @@ -72,6 +72,9 @@ export type IdentityCreationOptions = {
};
};
seed?: Uint8Array;
keyType?: KmsKeyType;
ethSigner?: any;
proofService?: ProofService;
};

/**
Expand Down Expand Up @@ -109,7 +112,7 @@ export interface IIdentityWallet {
* adds auth BJJ credential to claims tree and generates mtp of inclusion
* based on the resulting state it provides an identifier in DID form.
*
* @param {IdentityCreationOptions} opts - default is did:iden3:polygon:mumbai** with generated key.
* @param {IdentityCreationOptions} opts - default is did:iden3:polygon:aloy** with generated key.
* @returns `Promise<{ did: DID; credential: W3CCredential }>` - returns did and Auth BJJ credential
* @public
*/
Expand Down Expand Up @@ -434,20 +437,24 @@ export class IdentityWallet implements IIdentityWallet {
async createIdentity(
opts: IdentityCreationOptions
): Promise<{ did: DID; credential: W3CCredential }> {
const tmpIdentifier = opts.seed ? uuid.v5(Hex.encode(sha256(opts.seed)), uuid.NIL) : uuid.v4();

opts.method = opts.method ?? DidMethod.Iden3;
opts.blockchain = opts.blockchain ?? Blockchain.Polygon;
opts.networkId = opts.networkId ?? NetworkId.Amoy;

await this._storage.mt.createIdentityMerkleTrees(tmpIdentifier);

opts.seed = opts.seed ?? getRandomBytes(32);

const keyId = await this._kms.createKeyFromSeed(KmsKeyType.BabyJubJub, opts.seed);
opts.keyType = opts.keyType ?? KmsKeyType.BabyJubJub;

switch (opts.keyType) {
case KmsKeyType.BabyJubJub:
return this.createBabyJubJubIdentity(opts);
case KmsKeyType.Secp256k1:
return this.createEthereumIdentity(opts);
default:
throw new Error(`Invalid KmsKeyType ${opts.keyType}`);
}
}

private async createAuthCoreClaim(
revNonce: number,
seed: Uint8Array
): Promise<{ authClaim: Claim; pubKey: PublicKey }> {
const keyId = await this._kms.createKeyFromSeed(KmsKeyType.BabyJubJub, seed);
const pubKeyHex = await this._kms.publicKey(keyId);

const pubKey = PublicKey.newFromHex(pubKeyHex);

const schemaHash = SchemaHash.authSchemaHash;
Expand All @@ -457,35 +464,23 @@ export class IdentityWallet implements IIdentityWallet {
ClaimOptions.withIndexDataInts(pubKey.p[0], pubKey.p[1]),
ClaimOptions.withRevocationNonce(BigInt(0))
);
const revNonce = opts.revocationOpts.nonce ?? 0;
authClaim.setRevocationNonce(BigInt(revNonce));

await this._storage.mt.addToMerkleTree(
tmpIdentifier,
MerkleTreeType.Claims,
authClaim.hiHv().hi,
authClaim.hiHv().hv
);
return { authClaim, pubKey };
}

private async createAuthBJJCredential(
did: DID,
pubKey: PublicKey,
authClaim: Claim,
currentState: Hash,
revocationOpts: { id: string; type: CredentialStatusType }
): Promise<W3CCredential> {
const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(
tmpIdentifier,
did.string(),
MerkleTreeType.Claims
);

const currentState = hashElems([
(await claimsTree.root()).bigInt(),
ZERO_HASH.bigInt(),
ZERO_HASH.bigInt()
]);

const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId);
const identifier = Id.idGenesisFromIdenState(didType, currentState.bigInt());
const did = DID.parseFromId(identifier);

await this._storage.mt.bindMerkleTreeToNewIdentifier(tmpIdentifier, did.string());

const schema = JSON.parse(VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON);

const authData = authClaim.getExpirationDate();
const expiration = authData ? getUnixTimestamp(authData) : 0;

Expand All @@ -500,13 +495,14 @@ export class IdentityWallet implements IIdentityWallet {
version: 0,
expiration,
revocationOpts: {
nonce: revNonce,
id: opts.revocationOpts.id.replace(/\/$/, ''),
type: opts.revocationOpts.type,
nonce: Number(authClaim.getRevocationNonce()),
id: revocationOpts.id.replace(/\/$/, ''),
type: revocationOpts.type,
issuerState: currentState.hex()
}
};

const schema = JSON.parse(VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON);
let credential: W3CCredential = new W3CCredential();
try {
credential = this._credentialWallet.createCredential(did, request, schema);
Expand Down Expand Up @@ -535,6 +531,64 @@ export class IdentityWallet implements IIdentityWallet {

credential.proof = [mtpProof];

return credential;
}

/**
*
* creates Baby JubJub based identity
*
* @param {IdentityCreationOptions} opts - options for the creation of the identity
* @returns `{did,credential>}` - returns did and Auth BJJ credential
*/
private async createBabyJubJubIdentity(
opts: IdentityCreationOptions
): Promise<{ did: DID; credential: W3CCredential }> {
const tmpIdentifier = opts.seed ? uuid.v5(Hex.encode(sha256(opts.seed)), uuid.NIL) : uuid.v4();

opts.method = opts.method ?? DidMethod.Iden3;
opts.blockchain = opts.blockchain ?? Blockchain.Polygon;
opts.networkId = opts.networkId ?? NetworkId.Aloy;
opts.seed = opts.seed ?? getRandomBytes(32);

await this._storage.mt.createIdentityMerkleTrees(tmpIdentifier);

const revNonce = opts.revocationOpts.nonce ?? 0;

const { authClaim, pubKey } = await this.createAuthCoreClaim(revNonce, opts.seed);

await this._storage.mt.addToMerkleTree(
tmpIdentifier,
MerkleTreeType.Claims,
authClaim.hiHv().hi,
authClaim.hiHv().hv
);

const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(
tmpIdentifier,
MerkleTreeType.Claims
);

const currentState = hashElems([
(await claimsTree.root()).bigInt(),
ZERO_HASH.bigInt(),
ZERO_HASH.bigInt()
]);

const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId);
const identifier = Id.idGenesisFromIdenState(didType, currentState.bigInt());
const did = DID.parseFromId(identifier);

await this._storage.mt.bindMerkleTreeToNewIdentifier(tmpIdentifier, did.string());

const credential = await this.createAuthBJJCredential(
did,
pubKey,
authClaim,
currentState,
opts.revocationOpts
);

await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, {
rhsUrl: opts.revocationOpts.id,
onChain: opts.revocationOpts.onChain
Expand All @@ -555,6 +609,101 @@ export class IdentityWallet implements IIdentityWallet {
};
}

/**
*
* creates Ethereum based identity
*
* @param {IdentityCreationOptions} opts - options for the creation of the identity
* @returns `{did,credential>}` - returns did and Auth BJJ credential
*/
private async createEthereumIdentity(
opts: IdentityCreationOptions
): Promise<{ did: DID; credential: W3CCredential }> {
opts.method = opts.method ?? DidMethod.Iden3;
opts.blockchain = opts.blockchain ?? Blockchain.Polygon;
opts.networkId = opts.networkId ?? NetworkId.Aloy;
opts.seed = opts.seed ?? getRandomBytes(32);

const proofService = opts.proofService;
const ethSigner = opts.ethSigner;

const currentState = ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential

const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId);

const keyIdEth = await this._kms.createKeyFromSeed(KmsKeyType.Secp256k1, opts.seed);
const pubKeyHexEth = (await this._kms.publicKey(keyIdEth)).slice(2); // 04 + x + y (uncompressed key)
const did = buildDIDFromEthPubKey(didType, pubKeyHexEth);

await this._storage.mt.createIdentityMerkleTrees(did.string());

await this._storage.identity.saveIdentity({
did: did.string(),
state: currentState,
isStatePublished: false,
isStateGenesis: true
});

// Add Auth BJJ credential after saving identity for Ethereum identities
const { authClaim, pubKey } = await this.createAuthCoreClaim(
opts.revocationOpts.nonce ?? 0,
opts.seed
);

await this._storage.mt.addToMerkleTree(
did.string(),
MerkleTreeType.Claims,
authClaim.hiHv().hi,
authClaim.hiHv().hv
);

const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(
did.string(),
MerkleTreeType.Claims
);

const stateAuthClaim = hashElems([
(await claimsTree.root()).bigInt(),
ZERO_HASH.bigInt(),
ZERO_HASH.bigInt()
]);

const credential = await this.createAuthBJJCredential(
did,
pubKey,
authClaim,
stateAuthClaim,
opts.revocationOpts
);

// Old tree state genesis state
const oldTreeState: TreeState = {
revocationRoot: ZERO_HASH,
claimsRoot: ZERO_HASH,
state: currentState,
rootOfRoots: ZERO_HASH
};

if (!proofService) {
throw new Error('Proof service is required to create Ethereum identities');
}

// Mandatory transit state after adding auth credential in Ethereum identities
await proofService?.transitState(did, oldTreeState, true, this._storage.states, ethSigner);

await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, {
rhsUrl: opts.revocationOpts.id,
onChain: opts.revocationOpts.onChain
});

await this._credentialWallet.save(credential);

return {
did,
credential
};
}

/** {@inheritDoc IIdentityWallet.getGenesisDIDMetadata} */
async getGenesisDIDMetadata(did: DID): Promise<{ nonce: number; genesisDID: DID }> {
// check if it is a genesis identity
Expand Down
4 changes: 2 additions & 2 deletions src/kms/key-providers/sec256k1-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class Sec256k1Provider implements IKeyProvider {
const keyPair = this._ec.keyFromPrivate(seed);
const kmsId = {
type: this.keyType,
id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false))
id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false)) // 04 + x + y (uncompressed key)
};
await this._keyStore.importKey({ alias: kmsId.id, key: keyPair.getPrivate().toString('hex') });

Expand All @@ -56,7 +56,7 @@ export class Sec256k1Provider implements IKeyProvider {
*/
async publicKey(keyId: KmsKeyId): Promise<string> {
const privateKeyHex = await this.privateKey(keyId);
return this._ec.keyFromPrivate(privateKeyHex, 'hex').getPublic().encode('hex', false);
return this._ec.keyFromPrivate(privateKeyHex, 'hex').getPublic().encode('hex', false); // 04 + x + y (uncompressed key)
}

/**
Expand Down
Loading