All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elastos.did.RootIdentity Maven / Gradle / Ivy

The newest version!
package org.elastos.did;

import static com.google.common.base.Preconditions.checkArgument;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicInteger;

import org.elastos.did.DIDStore.ConflictHandle;
import org.elastos.did.crypto.Base58;
import org.elastos.did.crypto.HDKey;
import org.elastos.did.exception.DIDAlreadyExistException;
import org.elastos.did.exception.DIDDeactivatedException;
import org.elastos.did.exception.DIDResolveException;
import org.elastos.did.exception.DIDStoreException;
import org.elastos.did.exception.MalformedDocumentException;
import org.elastos.did.exception.MnemonicException;
import org.elastos.did.exception.RootIdentityAlreadyExistException;
import org.elastos.did.exception.UnknownInternalException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.digests.MD5Digest;
import org.spongycastle.util.encoders.Hex;

/**
 * The RootIdentity is a top-level object that represents a real user who
 * owns a series of DIDs
 *
 * 

* The users could use RootIdentity object to derive a series of DIDs, * all these DIDs are managed by this root identity object. * At the same time, these DIDs are independent to the 3rd party verifiers. *

*/ public final class RootIdentity { private String mnemonic; private HDKey rootPrivateKey; private HDKey preDerivedPublicKey; private AtomicInteger index; private String id; private Metadata metadata; private static final Logger log = LoggerFactory.getLogger(RootIdentity.class); static class Metadata extends AbstractMetadata { public static final String DEFAULT_DID = "defaultDid"; private String id; /** * Construct a Metadata object with given values. * * @param id the id of the RootIdentity object * @param store the target DIDStore */ protected Metadata(String id, DIDStore store) { super(store); this.id = id; } /** * Construct a Metadata object with given values. * * @param id the id of the RootIdentity object */ protected Metadata(String id) { this(id, null); } /** * The default constructor for JSON deserializer. */ protected Metadata() { this(null); } /** * Set the RootIdentity's id that this metadata related to. * @param id */ protected void setId(String id) { this.id = id; } /** * Set the default DID of this RootIdentity. * * @param did a DID object that derived by this RootIdentity object */ protected void setDefaultDid(DID did) { put(DEFAULT_DID, did.toString()); } /** * Get the default DID of this RootIdentity. * * @return a DID that represent as the default DID */ public DID getDefaultDid() { return DID.valueOf(get(DEFAULT_DID)); } /** * Save the modified metadata to the attached store if this metadata * attached with a store. */ @Override protected void save() { if (attachedStore()) { try { getStore().storeRootIdentityMetadata(id, this); } catch (DIDStoreException ignore) { log.error("INTERNAL - error store metadata for credential {}", id); } } } } private RootIdentity(String mnemonic, String passphrase) { this.mnemonic = mnemonic; if (passphrase == null) passphrase = ""; this.rootPrivateKey = new HDKey(mnemonic, passphrase); this.preDerivedPublicKey = rootPrivateKey.derive(HDKey.PRE_DERIVED_PUBLICKEY_PATH); this.index = new AtomicInteger(0); } private RootIdentity(HDKey rootPrivateKey) { this.rootPrivateKey = rootPrivateKey; this.preDerivedPublicKey = rootPrivateKey.derive(HDKey.PRE_DERIVED_PUBLICKEY_PATH); this.index = new AtomicInteger(0); } private RootIdentity(HDKey preDerivedPublicKey, int index) { this.preDerivedPublicKey = preDerivedPublicKey; this.index = new AtomicInteger(index); } /** * Create a RootIdentity from mnemonic and an optional passphrase. * * @param mnemonic the mnemonic string * @param passphrase the extra passphrase to generate seed with the mnemonic * @param overwrite true will overwrite the identity if the identity exists * in the store, false will raise exception if the identity * exists in the store * @param store the DIDStore where to save this identity * @param storepass the password for DIDStore * @return the RootIdentity object * @throws DIDStoreException if an error occurred when accessing the store */ public static RootIdentity create(String mnemonic, String passphrase, boolean overwrite, DIDStore store, String storepass) throws DIDStoreException { checkArgument(mnemonic != null && !mnemonic.isEmpty(), "Invalid mnemonic"); checkArgument(store != null, "Invalid DID store"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); try { checkArgument(Mnemonic.checkIsValid(mnemonic), "Invalid mnemonic."); } catch (MnemonicException e) { throw new IllegalArgumentException(e); } if (passphrase == null) passphrase = ""; RootIdentity identity = new RootIdentity(mnemonic, passphrase); if (store.containsRootIdentity(identity.getId()) && !overwrite) throw new RootIdentityAlreadyExistException(identity.getId()); identity.setMetadata(new Metadata(identity.getId(), store)); store.storeRootIdentity(identity, storepass); identity.wipe(); return identity; } /** * Create a RootIdentity from mnemonic and an optional passphrase. * * @param mnemonic the mnemonic string * @param passphrase the extra passphrase to generate seed with the mnemonic * @param store the DIDStore where to save this identity * @param storepass the password for DIDStore * @return the RootIdentity object * @throws DIDStoreException if an error occurred when accessing the store */ public static RootIdentity create(String mnemonic, String passphrase, DIDStore store, String storepass) throws DIDStoreException { return create(mnemonic, passphrase, false, store, storepass); } /** * Create a RootIdentity from a root extended private key. * * @param extentedPrivateKey the root extended private key * @param overwrite true will overwrite the identity if the identity exists * in the store, false will raise exception if the identity * exists in the store * @param store the DIDStore where to save this identity * @param storepass the password for DIDStore * @return the RootIdentity object * @throws DIDStoreException if an error occurred when accessing the store */ public static RootIdentity create(String extentedPrivateKey, boolean overwrite, DIDStore store, String storepass) throws DIDStoreException { checkArgument(extentedPrivateKey != null && !extentedPrivateKey.isEmpty(), "Invalid extended private key"); checkArgument(store != null, "Invalid DID store"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); HDKey rootPrivateKey = HDKey.deserialize(Base58.decode(extentedPrivateKey)); RootIdentity identity = new RootIdentity(rootPrivateKey); if (store.containsRootIdentity(identity.getId()) && !overwrite) throw new RootIdentityAlreadyExistException(identity.getId()); identity.setMetadata(new Metadata(identity.getId(), store)); store.storeRootIdentity(identity, storepass); identity.wipe(); return identity; } /** * Create a RootIdentity from a root extended private key. * * @param extentedPrivateKey the root extended private key * @param store the DIDStore where to save this identity * @param storepass the password for DIDStore * @return the RootIdentity object * @throws DIDStoreException if an error occurred when accessing the store */ public static RootIdentity create(String extentedPrivateKey, DIDStore store, String storepass) throws DIDStoreException { return create(extentedPrivateKey, false, store, storepass); } /** * Create a public key only RootIdentity instance. * * @param preDerivedPublicKey the pre-derived extended public key * @param index current available derive index * @return the RootIdentity object */ protected static RootIdentity create(String preDerivedPublicKey, int index) { HDKey key = preDerivedPublicKey == null ? null : HDKey.deserializeBase58(preDerivedPublicKey); return new RootIdentity(key, index); } /** * Get RootIdentity Id from mnemonic and an optional passphrase. * * @param mnemonic the mnemonic string * @param passphrase the extra passphrase to generate seed with the mnemonic * @return the RootIdentity Id */ public static String getId(String mnemonic, String passphrase) { checkArgument(mnemonic != null && !mnemonic.isEmpty(), "Invalid mnemonic"); try { checkArgument(Mnemonic.checkIsValid(mnemonic), "Invalid mnemonic."); } catch (MnemonicException e) { throw new IllegalArgumentException(e); } if (passphrase == null) passphrase = ""; RootIdentity identity = new RootIdentity(mnemonic, passphrase); String id = identity.getId(); identity.wipe(); return id; } /** * Get a RootIdentity Id from a root extended private key. * * @param extentedPrivateKey the root extended private key * @return the RootIdentity Id */ public static String getId(String extentedPrivateKey) { checkArgument(extentedPrivateKey != null && !extentedPrivateKey.isEmpty(), "Invalid extended private key"); HDKey rootPrivateKey = HDKey.deserialize(Base58.decode(extentedPrivateKey)); RootIdentity identity = new RootIdentity(rootPrivateKey); String id = identity.getId(); identity.wipe(); return id; } private void wipe() { rootPrivateKey.wipe(); mnemonic = null; rootPrivateKey = null; } /** * Get the attached DIDStore instance. * * @return a DIDStore object */ protected DIDStore getStore() { return metadata.getStore(); } /** * Get the metadata object of this RootIdentity. * * @param metadata the metadata object */ protected void setMetadata(Metadata metadata) { this.metadata = metadata; } /** * Calculate the id of RootIdentity object from the pre-derived public key. * * @param key the pre-derived public key in bytes array * @return the id of RootIdentity object */ protected static String getId(byte[] key) { checkArgument(key != null && key.length > 0, "Invalid key bytes"); MD5Digest md5 = new MD5Digest(); byte[] digest = new byte[md5.getDigestSize()]; md5.update(key, 0, key.length); md5.doFinal(digest, 0); return Hex.toHexString(digest); } /** * Get the id of this RootIdentity object. * * @return the id of this RootIdentity object */ public synchronized String getId() { if (id == null) id = getId(preDerivedPublicKey.serializePublicKey()); return id; } /** * Get the alias of this RootIdentity object. * * @return the alias of this RootIdentity object, or null if not set before */ public String getAlias() { return metadata.getAlias(); } /** * Set the alias for this RootIdentity object. * * @param alias the new alias */ public void setAlias(String alias) { metadata.setAlias(alias); } /** * Set this RootIdentity as the global default identity in current DIDStore. * * @throws DIDStoreException if an error occurred when accessing the store */ public void setAsDefault() throws DIDStoreException { getStore().setDefaultRootIdentity(this); } /** * Get the default DID of this RootIdentity object. * * @return a DID object or null if not set the default DID before */ public DID getDefaultDid() { DID did = metadata.getDefaultDid(); if (did == null) did = getDid(0); return did; } /** * Set the default DID for this RootIdentity object. * *

* The default DID object should derived from this RootIdentity. *

* * @param did a DID object */ public void setDefaultDid(DID did) { metadata.setDefaultDid(did); } /** * Set the default DID for this RootIdentity object. * *

* The default DID object should derived from this RootIdentity. *

* * @param did a DID string */ public void setDefaultDid(String did) { metadata.setDefaultDid(DID.valueOf(did)); } /** * Set the default DID for this RootIdentity object. * * @param index the index of default DID derived from */ public void setDefaultDid(int index) { checkArgument(index >=0, "Invalid index"); metadata.setDefaultDid(getDid(index)); } String getMnemonic() { return mnemonic; } HDKey getRootPrivateKey() { return rootPrivateKey; } /** * Get the pre-derived public key of this RootIdentity. * * @return a HDKey object represent the public key */ protected HDKey getPreDerivedPublicKey() { return preDerivedPublicKey; } /** * Get the next available derive index of this RootIdentity. * * @return the next available derive index */ protected int getIndex() { return index.get(); } /** * Set the next available derive index for this RootIdentity. * * @param idx the next available derive index * @throws DIDStoreException if an error occurred when accessing the store */ protected void setIndex(int idx) throws DIDStoreException { index.set(idx); getStore().storeRootIdentity(this); } /** * Increase the next available derive index for this RootIdentity. * * @return the next available derive index * @throws DIDStoreException if an error occurred when accessing the store */ protected int incrementIndex() throws DIDStoreException { int idx = index.incrementAndGet(); getStore().storeRootIdentity(this); return idx; } /** * Get DID that derived from the specific index. * * @param index the derive index * @return a DID object */ public DID getDid(int index) { checkArgument(index >= 0, "Invalid index"); HDKey key = preDerivedPublicKey.derive("0/" + index); DID did = new DID(DID.METHOD, key.getAddress()); /* DIDMetadata metadata = new DIDMetadata(did, getStore()); metadata.setRootIdentityId(getId()); metadata.setIndex(index); did.setMetadata(metadata); */ return did; } static byte[] lazyCreateDidPrivateKey(DIDURL id, DIDStore store, String storepass) throws DIDStoreException { DIDDocument doc = store.loadDid(id.getDid()); if (doc == null) { log.error("INTERNAL - Missing document for DID: {}", id.getDid()); throw new DIDStoreException("Missing document for DID: " + id.getDid()); } String identity = doc.getMetadata().getRootIdentityId(); if (identity == null) return null; HDKey key = store.derive(identity, HDKey.DERIVE_PATH_PREFIX + doc.getMetadata().getIndex(), storepass); DIDDocument.PublicKey pk = doc.getPublicKey(id); if (pk == null) { log.error("INTERNAL - Invalid public key: {}", id); throw new DIDStoreException("Invalid public key: " + id); } if (!key.getPublicKeyBase58().equals(pk.getPublicKeyBase58())) { log.error("INTERNAL - Invalid DID metadata: {}", id.getDid()); throw new DIDStoreException("Invalid DID metadata: " + id.getDid()); } store.storePrivateKey(id, key.serialize(), storepass); byte[] sk = key.serialize(); key.wipe(); return sk; } /** * Create a new DID that derive from the specified index. * * @param index the derive index * @param overwrite true for overwriting the existing one, fail otherwise * @param storepass the password for DIDStore * @return the new created DIDDocument object * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newDid(int index, boolean overwrite, String storepass) throws DIDResolveException, DIDStoreException { checkArgument(index >= 0, "Invalid index"); checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); DID did = getDid(index); DIDDocument doc = getStore().loadDid(did); if (doc != null) { if (doc.isDeactivated()) throw new DIDDeactivatedException(did.toString()); if (!overwrite) throw new DIDAlreadyExistException("DID already exists in the store."); } try { doc = did.resolve(); if (doc != null) { if (doc.isDeactivated()) throw new DIDDeactivatedException(did.toString()); if (!overwrite) throw new DIDAlreadyExistException("DID already published."); } } catch (DIDResolveException e) { if (!overwrite) throw e; } log.debug("Creating new DID {} at index {}...", did.toString(), index); HDKey key = getStore().derive(getId(), HDKey.DERIVE_PATH_PREFIX + index, storepass); try { DIDURL id = new DIDURL(did, "#primary"); getStore().storePrivateKey(id, key.serialize(), storepass); DIDDocument.Builder db = new DIDDocument.Builder(did, getStore()); db.addAuthenticationKey(id, key.getPublicKeyBase58()); doc = db.seal(storepass); doc.getMetadata().setRootIdentityId(getId()); doc.getMetadata().setIndex(index); doc.getMetadata().attachStore(getStore()); getStore().storeDid(doc); return doc; } catch (MalformedDocumentException ignore) { throw new UnknownInternalException(ignore); } finally { key.wipe(); } } /** * Create a new DID that derive from the specified index. * * @param index the derive index * @param storepass the password for DIDStore * @return the new created DIDDocument object * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newDid(int index, String storepass) throws DIDResolveException, DIDStoreException { return newDid(index, false, storepass); } /** * Create a new DID from next available index. * * @param overwrite true for overwriting the existing one, fail otherwise * @param storepass the password for DIDStore * @return the new created DIDDocument object * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public synchronized DIDDocument newDid(boolean overwrite, String storepass) throws DIDResolveException, DIDStoreException { DIDDocument doc = newDid(getIndex(), overwrite, storepass); incrementIndex(); return doc; } /** * Create a new DID from next available index. * * @param storepass the password for DIDStore * @return the new created DIDDocument object * @throws DIDResolveException if an error occurred when resolving the DIDs * @throws DIDStoreException if an error occurred when accessing the store */ public DIDDocument newDid(String storepass) throws DIDResolveException, DIDStoreException { return newDid(false, storepass); } /** * Check whether this RootIdentity created from mnemonic. * * @return true if this RootIdentity created from mnemonic, false otherwise * @throws DIDStoreException if an error occurred when accessing the store */ public boolean hasMnemonic() throws DIDStoreException { return getStore().containsRootIdentityMnemonic(getId()); } /** * Export mnemonic that generated this RootIdentity object. * * @param storepass the password for DIDStore * @return the mnemonic string * @throws DIDStoreException if an error occurred when accessing the store */ public String exportMnemonic(String storepass) throws DIDStoreException { checkArgument(storepass != null && !storepass.isEmpty(), "Invalid storepass"); return getStore().exportRootIdentityMnemonic(getId(), storepass); } /** * Synchronize the specific DID from ID chain. * *

* If the ConflictHandle is not set by the developers, this method will * use the default ConflictHandle implementation: if conflict between * the chain copy and the local copy, it will keep the local copy, but * update the local metadata with the chain copy. *

* * @param index the DID derive index * @param handle an application defined handle to process the conflict * between the chain copy and the local copy * @return true if synchronized success, false if not synchronized * @throws DIDResolveException if an error occurred when resolving DID * @throws DIDStoreException if an error occurred when accessing the store */ public boolean synchronize(int index, ConflictHandle handle) throws DIDResolveException, DIDStoreException { checkArgument(index >= 0, "Invalid index"); if (handle == null) handle = DIDStore.defaultConflictHandle; DID did = getDid(index); return getStore().synchronize(did, handle, getId(), index); } /** * Synchronize the specific DID from ID chain. * * @param index the DID derive index * @return true if synchronized success, false if not synchronized * @throws DIDResolveException if an error occurred when resolving DID * @throws DIDStoreException if an error occurred when accessing the store */ public boolean synchronize(int index) throws DIDResolveException, DIDStoreException { return synchronize(index, null); } /** * Synchronize the specific DID from ID chain in asynchronous mode. * *

* If the ConflictHandle is not set by the developers, this method will * use the default ConflictHandle implementation: if conflict between * the chain copy and the local copy, it will keep the local copy, but * update the local metadata with the chain copy. *

* * @param index the DID derive index * @param handle an application defined handle to process the conflict * between the chain copy and the local copy * @return a new CompletableStage, the result is the boolean value that * indicate the synchronize result */ public CompletableFuture synchronizeAsync(int index, ConflictHandle handle) { CompletableFuture future = CompletableFuture.supplyAsync(() -> { try { return synchronize(index, handle); } catch (DIDResolveException | DIDStoreException e) { throw new CompletionException(e); } }); return future; } /** * Synchronize the specific DID from ID chain in asynchronous mode. * * @param index the DID derive index * @return a new CompletableStage, the result is the boolean value that * indicate the synchronize result */ public CompletableFuture synchronizeAsync(int index) { return synchronizeAsync(index, null); } /** * Synchronize all DIDs that derived from this RootIdentity object. * *

* If the ConflictHandle is not set by the developers, this method will * use the default ConflictHandle implementation: if conflict between * the chain copy and the local copy, it will keep the local copy, but * update the local metadata with the chain copy. *

* * @param handle an application defined handle to process the conflict * between the chain copy and the local copy * @throws DIDResolveException if an error occurred when resolving DID * @throws DIDStoreException if an error occurred when accessing the store */ public void synchronize(ConflictHandle handle) throws DIDResolveException, DIDStoreException { log.info("Synchronize root identity {}...", getId()); int lastIndex = getIndex() - 1; int blanks = 0; int i = 0; while (i < lastIndex || blanks < 20) { boolean exists = synchronize(i, handle); if (exists) { if (i > lastIndex) lastIndex = i; blanks = 0; } else { if (i > lastIndex) blanks++; } i++; } if (lastIndex >= getIndex()) setIndex(lastIndex + 1); } /** * Synchronize all DIDs that derived from this RootIdentity object. * * @throws DIDResolveException if an error occurred when resolving DID * @throws DIDStoreException if an error occurred when accessing the store */ public void synchronize() throws DIDResolveException, DIDStoreException { synchronize(null); } /** * Synchronize all DIDs that derived from this RootIdentity object in * asynchronous mode. * *

* If the ConflictHandle is not set by the developers, this method will * use the default ConflictHandle implementation: if conflict between * the chain copy and the local copy, it will keep the local copy, but * update the local metadata with the chain copy. *

* * @param handle an application defined handle to process the conflict * between the chain copy and the local copy * @return a new CompletableStage */ public CompletableFuture synchronizeAsync(ConflictHandle handle) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { synchronize(handle); } catch (DIDResolveException | DIDStoreException e) { throw new CompletionException(e); } }); return future; } /** * Synchronize all DIDs that derived from this RootIdentity object in * asynchronous mode. * * @return a new CompletableStage */ public CompletableFuture synchronizeAsync() { return synchronizeAsync(null); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy