UNPKG

@synet/identity

Version:

Simple and secure identity management library for Verifiable Identity

669 lines (565 loc) 17.2 kB
/** * @synet/identity - Identity Unit * * Creates and manages decentralized identities by composing multiple units: * - DID unit for decentralized identifier management * - Signer unit for cryptographic signing operations * - Key unit for public key operations * - Credential unit for verifiable credential operations * * The Identity follows Unit Architecture with props-based construction * and teaching/learning contracts for capability sharing. * * * @author Synet Team */ import { Signer, hexToPem, hexPrivateKeyToPem, generateKeyPair, } from "@synet/keys"; import { DID } from "@synet/did"; import { Credential } from "@synet/credential"; import type { SynetVerifiableCredential, BaseCredentialSubject, IdentitySubject, } from "@synet/credential"; import { Result } from "./result"; import { Unit, type UnitProps, createUnitSchema, type UnitSchema, type TeachingContract, } from "@synet/unit"; // External input to static create() export interface IdentityConfig { alias: string; did: string; publicKeyHex: string; privateKeyHex: string; kid?: string; provider?: string; credential?: SynetVerifiableCredential<BaseCredentialSubject>; metadata?: Record<string, unknown>; createdAt?: Date; } // Internal state after validation export interface IdentityProps extends UnitProps { dna: UnitSchema; alias: string; did: string; kid: string; publicKeyHex: string; privateKeyHex?: string; provider: string; credential: SynetVerifiableCredential<BaseCredentialSubject>; metadata: Record<string, unknown>; createdAt: Date; // Composed units for internal operations didUnit: DID; signerUnit: Signer; keyUnit: ReturnType<Signer["createKey"]>; credentialUnit: Credential; } export interface IIdentity { alias: string did: string kid: string publicKeyHex: string privateKeyHex?: string // Optional private key, can be used for signing provider: string // did:key | did:web credential: SynetVerifiableCredential<BaseCredentialSubject> metadata?: Record<string, unknown> createdAt: Date // Optional creation date for the vault } export interface IdentityPresent { did: string publicKeyHex: string credential: SynetVerifiableCredential<BaseCredentialSubject> } const VERSION = "1.0.1"; /** * Identity Unit - Unit Architecture implementation * Composes DID, Signer, Key, and Credential units */ export class Identity extends Unit<IdentityProps> { // MUST be protected (enables evolution) protected constructor(props: IdentityProps) { super(props); } /** * Create identity from existing data or config */ static create(config: IdentityConfig): Result<Identity> { try { if (!config.privateKeyHex || !config.publicKeyHex || !config.did || !config.alias) { return Result.fail("Required fields: alias, publicKeyHex, privateKeyHex, did"); } // 1. Prepare cryptographic material const privateKeyPEM = hexPrivateKeyToPem(config.privateKeyHex); const publicKeyPEM = hexToPem(config.publicKeyHex, "ed25519"); if (!privateKeyPEM || !publicKeyPEM) { throw new Error("Failed to convert keys to PEM format"); } // 2. Create signer const signer = Signer.create({ privateKeyPEM, publicKeyPEM, keyType: "ed25519", metadata: { name: `${config.alias || "unknown"}-signer`, }, }); if (!signer) { return Result.fail("Failed to create signer from key material"); } // 3. Create key from signer const key = signer.createKey(); if (!key) { return Result.fail("Failed to create key from signer"); } // 4. Create DID unit from public key const didUnit = DID.create({ publicKeyHex: config.publicKeyHex, keyType: "Ed25519", metadata: { alias: config.alias || "unknown", }, }); if (!didUnit) { return Result.fail("Failed to reconstruct DID"); } // 5. Create credential unit and learn from key. Key already knows how to sign from signer. const credentialUnit = Credential.create(); credentialUnit.learn([key.teach()]); const props: IdentityProps = { dna: createUnitSchema({ id: "identity", version: "1.0.0" }), alias: config.alias || "unknown", did: config.did || "did:key:unknown", kid: config.kid || config.publicKeyHex, publicKeyHex: config.publicKeyHex, privateKeyHex: config.privateKeyHex, provider: config.provider || "did:key", credential: config.credential || ({} as SynetVerifiableCredential<BaseCredentialSubject>), metadata: config.metadata || {}, createdAt: config.createdAt || new Date(), didUnit, signerUnit: signer, keyUnit: key, credentialUnit, }; return Result.success(new Identity(props)); } catch (error) { return Result.fail( error instanceof Error ? error.message : "Unknown error occurred", error instanceof Error ? error : undefined, ); } } /** * Generate a new identity with fresh cryptographic material */ static async generate(alias: string): Promise<Result<Identity>> { try { // 1. Generate cryptographic material in hex format const keyPair = generateKeyPair("ed25519", { format: "hex" }); if (!keyPair) { throw new Error("Failed to generate key pair"); } // 2. Convert hex keys to PEM for signer creation const publicKeyPEM = hexToPem(keyPair.publicKey, "ed25519"); const privateKeyPEM = hexPrivateKeyToPem(keyPair.privateKey); if (!publicKeyPEM || !privateKeyPEM) { throw new Error("Failed to convert keys to PEM format"); } // 3. Create signer from the PEM keys const signer = Signer.create({ privateKeyPEM, publicKeyPEM, keyType: "ed25519", secure: true, metadata: { name: `${alias}-signer`, }, }); if (!signer) { throw new Error("Failed to create signer from key pair"); } const key = signer.createKey(); if (!key) { throw new Error("Failed to create key from signer"); } // 4. Get public key in hex format (should match our generated key) const publicKeyHex = signer.getPublicKeyHex(); if (!publicKeyHex) { throw new Error("Failed to get public key hex"); } // 5. Use the hex private key we generated const privateKeyHex = keyPair.privateKey; // 6. Create DID from public key (reuse the PEM we already have) const didUnit = DID.create({ publicKeyHex, keyType: "Ed25519", metadata: { alias } }); if (!didUnit) { throw new Error("Failed to create DID from public key"); } // 7. Generate DID from publicKey const didString = didUnit.generateKey(); if (!didString) { throw new Error("Failed to generate DID string"); } // 8. Create credential unit and learn from key const credentialUnit = Credential.create(); credentialUnit.learn([key.teach()]); // 9. Create self-signed verifiable credential const subject: IdentitySubject = { holder: { id: didString, name: alias, }, issuedBy: { id: didString, name: alias, }, }; const credentialResult = await credentialUnit.issueCredential( subject, "IdentityCredential", didString, ); if (!credentialResult.isSuccess) { return Result.fail( `Failed to issue identity credential: ${credentialResult.errorMessage}`, ); } const credential = credentialResult.value; // 9. Build identity props const props: IdentityProps = { dna: createUnitSchema({ id: "identity", version: VERSION }), alias, did: didString, kid: publicKeyHex, publicKeyHex, privateKeyHex, provider: "did:key", credential: credential as SynetVerifiableCredential<BaseCredentialSubject>, metadata: {}, createdAt: new Date(), didUnit, signerUnit: signer, keyUnit: key, credentialUnit, }; const identity = new Identity(props); // 10. Teach identity how to generate keys, sign and create verifiable credentials identity.learn([signer.teach(), key.teach(), credentialUnit.teach()]); // 11. Full SSI Identity that can issue VCs and sign, while preserving its secrets. return Result.success(identity); } catch (error) { console.error("Failed to generate identity:", error); return Result.fail( error instanceof Error ? error.message : "Unknown error occurred", error instanceof Error ? error : undefined, ); } } // ========================================== // UNIT ARCHITECTURE REQUIRED METHODS // ========================================== whoami(): string { return `Identity Unit - ${this.props.alias} (${this.props.dna.id}@${this.props.dna.version})`; } capabilities(): string[] { return Array.from(this._capabilities.keys()); } help(): void { console.log(` [👤] Identity Unit - Decentralized Identity Management Native Capabilities: • issueCredential() - Issue verifiable credentials • sign(data) - Sign data with private key • verify(data, signature) - Verify signatures • getDid() - Get DID string • getPublicKey() - Get public key • public() - Get public identity data, without private key • present() - Present identity • toJSON() - export indentity for persistence • toDomain() - convert indentity to domain type IIdentity Getters: • alias • did • metadata • provider • publicKeyHex • privateKeyHex Composed Units: • DID Unit: ${this.props.didUnit.whoami()} • Signer Unit: ${this.props.signerUnit.whoami()} • Key Unit: ${this.props.keyUnit?.whoami() || "Key Unit (not available)"} • Credential Unit: ${this.props.credentialUnit.whoami()} Current State: • Alias: ${this.props.alias} • DID: ${this.props.did} • Provider: ${this.props.provider} • Created: ${this.props.createdAt.toISOString()} Learned Capabilities: ${this._capabilities.size} total ${Array.from(this._capabilities.keys()) .map((cap) => ` • ${cap}`) .join("\n")} `); } teach(): TeachingContract { return { unitId: this.props.dna.id, capabilities: { issueCredential: ((...args: unknown[]) => this.issueCredential( args[0] as BaseCredentialSubject, args[1] as string, args[2] as string | undefined, )) as (...args: unknown[]) => unknown, sign: ((...args: unknown[]) => this.sign(args[0] as string)) as ( ...args: unknown[] ) => unknown, verify: ((...args: unknown[]) => this.verify(args[0] as string, args[1] as string)) as ( ...args: unknown[] ) => unknown, getDid: ((...args: unknown[]) => this.getDid()) as ( ...args: unknown[] ) => unknown, getPublicKey: ((...args: unknown[]) => this.getPublicKey()) as ( ...args: unknown[] ) => unknown, }, }; } // ========================================== // NATIVE CAPABILITIES // ========================================== /** * @depricated, use generate() instead. */ async generateIdentity(alias: string): Promise<Result<Identity>> { return Identity.generate(alias); } /** * Issue and sign Verifable Credential. * @param subject * @param type * @param issuer * @returns */ async issueCredential( subject: BaseCredentialSubject, type: string, issuer?: string, ): Promise<Result<SynetVerifiableCredential<BaseCredentialSubject>>> { if (this.can("credential.issueCredential")) { return this.execute( "credential.issueCredential", subject, type, issuer || this.props.did, ); } // Graceful fallback to native capability const result = await this.props.credentialUnit.issueCredential( subject, type, issuer || this.props.did, ); // Convert the result type if (!result.isSuccess) { const errorMessage = typeof result.error === "string" ? result.error : result.error?.message || "Failed to issue credential"; return Result.fail(errorMessage); } return Result.success( result.value as SynetVerifiableCredential<BaseCredentialSubject>, ); } async sign(data: string): Promise<string> { if (this.can("signer.sign")) { return this.execute("signer.sign", data); } // Graceful fallback to native capability if (this.props.privateKeyHex) { return this.props.signerUnit.sign(data); } throw new Error( `[${this.props.dna.id}] Cannot sign - missing 'signer.sign' capability. Learn from: Signer.create().teach()`, ); } async verify(data: string, signature: string): Promise<boolean> { if (this.can("signer.verify")) { return this.execute("signer.verify", data, signature); } // Graceful fallback to native capability return this.props.signerUnit.verify(data, signature); } get publicKeyHex():string { return this.props.publicKeyHex; } get privateKeyHex():string | undefined { return this.props.privateKeyHex; } get alias():string { return this.props.alias; } get did():string { return this.props.did; } get credential():SynetVerifiableCredential<BaseCredentialSubject> { return this.props.credential; } get metadata():Record<string, unknown> { return this.props.metadata; } /** * Get identity provider */ get provider(): string { return this.props.provider; } getDid(): string { return this.props.did; } getPublicKey(): string { return this.props.publicKeyHex; } /** * @deprecated Use getPublicKey() instead */ getPublicKeyHex(): string { return this.props.publicKeyHex; } // ========================================== // UNIT ACCESS (for operations) // ========================================== didUnit(): DID { return this.props.didUnit; } /** * Get Key unit for public key operations */ keyUnit(): ReturnType<Signer["createKey"]> { return this.props.keyUnit; } /** * Get Signer unit for signing operations */ signerUnit(): Signer { return this.props.signerUnit; } /** * Get Credential unit for verifiable credential operations */ credentialUnit(): Credential { return this.props.credentialUnit; } // ========================================== // LEGACY COMPATIBILITY METHODS // ========================================== /** * Get identity alias */ getAlias(): string { return this.props.alias; } /** * Get key identifier */ getKid(): string { return this.props.kid; } /** * Get private key in hex format (if available) */ getPrivateKeyHex(): string | undefined { return this.props.privateKeyHex; } /** * Get identity provider */ getProvider(): string { return this.props.provider; } /** * Get verifiable credential */ getCredential(): SynetVerifiableCredential<BaseCredentialSubject> { return this.props.credential; } /** * Get metadata */ getMetadata(): Record<string, unknown> { return this.props.metadata; } /** * Get creation date */ getCreatedAt(): Date { return this.props.createdAt; } toJSON() { return { alias: this.props.alias, did: this.props.did, kid: this.props.kid, publicKeyHex: this.props.publicKeyHex, privateKeyHex: this.props.privateKeyHex, provider: this.props.provider, credential: this.props.credential, metadata: this.props.metadata, createdAt: this.props.createdAt, }; } /** * Convert Identity props to domain entity IIdentity * @returns * */ toDomain(): IIdentity { return { alias: this.props.alias, did: this.props.did, kid: this.props.kid, publicKeyHex: this.props.publicKeyHex, privateKeyHex: this.props.privateKeyHex, provider: this.props.provider, credential: this.props.credential, metadata: this.props.metadata, createdAt: this.props.createdAt, }; } /** * Export public identity data (no private key) */ public(): Omit<IIdentity, "privateKeyHex"> { const { privateKeyHex, ...publicData } = this.props; return publicData; } /** * Presents identity * @returns IdentityPresent { did, publicKeyHex, credential } */ present(): IdentityPresent { return { did: this.props.did, publicKeyHex: this.props.publicKeyHex, credential: this.props.credential, }; } }