@iden3/js-iden3-core
Version:
Low level API to create and manipulate iden3 Claims.
190 lines (189 loc) • 6.92 kB
JavaScript
import { Id } from '../id';
import { Blockchain, Constants, DidMethodByte, DidMethodNetwork, DidMethod, NetworkId } from '../constants';
import { BytesHelper } from '../elemBytes';
import { DIDNetworkFlag, findBlockchainForDIDMethodByValue, findDIDMethodByValue, findNetworkIDForDIDMethodByValue } from './did-helper';
import { Parser } from './did-parser';
import { sha256 } from '@iden3/js-crypto';
import { encoder } from '../utils';
// DID Decentralized Identifiers (DIDs)
// https://w3c.github.io/did-core/#did-syntax
export class DID {
constructor(d) {
this.method = '';
this.id = '';
this.idStrings = [];
this.params = [];
this.path = '';
this.pathSegments = [];
this.query = '';
this.fragment = '';
if (d) {
Object.assign(this, d);
}
}
isUrl() {
return (this.params.length > 0 ||
!!this.path ||
this.pathSegments.length > 0 ||
!!this.query ||
!!this.fragment);
}
string() {
const buff = ['did:'];
if (this.method) {
buff.push(`${this.method}:`);
}
else {
return '';
}
if (this.id) {
buff.push(this.id);
}
else if (this.idStrings.length) {
buff.push(this.idStrings.join(':'));
}
else {
return '';
}
if (this.params.length) {
for (const param of this.params) {
const p = param.toString();
if (p) {
buff.push(`;${p}`);
}
else {
return '';
}
}
}
if (this.path) {
buff.push(`/${this.path}`);
}
else if (this.pathSegments.length) {
buff.push(`/${this.pathSegments.join('/')}`);
}
if (this.query) {
buff.push(`?${this.query}`);
}
if (this.fragment) {
buff.push(`#${this.fragment}`);
}
return buff.join('');
}
toJSON() {
return this.string();
}
static parse(s) {
const parser = new Parser(s);
let parserState = parser.checkLength();
while (parserState) {
parserState = parserState();
}
parser.out.id = parser.out.idStrings.join(':');
parser.out.path = parser.out.pathSegments.join('/');
return new DID(parser.out);
}
static decodePartsFromId(id) {
const method = findDIDMethodByValue(id.bytes[0]);
const blockchain = findBlockchainForDIDMethodByValue(method, id.bytes[1]);
const networkId = findNetworkIDForDIDMethodByValue(method, id.bytes[1]);
return { method, blockchain, networkId };
}
static networkIdFromId(id) {
return DID.throwIfDIDUnsupported(id).networkId;
}
static methodFromId(id) {
return DID.throwIfDIDUnsupported(id).method;
}
static blockchainFromId(id) {
return DID.throwIfDIDUnsupported(id).blockchain;
}
static throwIfDIDUnsupported(id) {
const { method, blockchain, networkId } = DID.decodePartsFromId(id);
if (DID.isUnsupported(method, blockchain, networkId)) {
throw new Error(`${Constants.ERRORS.UNKNOWN_DID_METHOD.message}: unsupported DID`);
}
return { method, blockchain, networkId };
}
// DIDGenesisFromIdenState calculates the genesis ID from an Identity State and returns it as DID
static newFromIdenState(typ, state) {
const id = Id.idGenesisFromIdenState(typ, state);
return DID.parseFromId(id);
}
// NewDID creates a new *w3c.DID from the type and the genesis
static new(typ, genesis) {
return DID.parseFromId(new Id(typ, genesis));
}
// ParseDIDFromID returns DID from ID
static parseFromId(id) {
if (!BytesHelper.checkChecksum(id.bytes)) {
throw new Error(`${Constants.ERRORS.UNSUPPORTED_ID.message}: invalid checksum`);
}
const { method, blockchain, networkId } = DID.throwIfDIDUnsupported(id);
const didParts = [Constants.DID.DID_SCHEMA, method.toString(), blockchain.toString()];
if (networkId) {
didParts.push(networkId.toString());
}
didParts.push(id.string());
const didString = didParts.join(':');
const did = DID.parse(didString);
return did;
}
static idFromDID(did) {
let id;
try {
id = DID.getIdFromDID(did);
}
catch (error) {
if (error.message === Constants.ERRORS.UNKNOWN_DID_METHOD.message) {
return DID.idFromUnsupportedDID(did);
}
throw error;
}
return id;
}
static isUnsupported(method, blockchain, networkId) {
return (method == DidMethod.Other &&
blockchain == Blockchain.Unknown &&
networkId == NetworkId.Unknown);
}
static idFromUnsupportedDID(did) {
const hash = sha256(encoder.encode(did.string()));
const genesis = new Uint8Array(27);
const idSlice = hash.slice(hash.length - Constants.GENESIS_LENGTH);
for (let i = 0; i < genesis.length; i++) {
genesis[i] = idSlice[i] ?? 0;
}
const flg = new DIDNetworkFlag(Blockchain.Unknown, NetworkId.Unknown);
const tp = Uint8Array.from([
DidMethodByte[DidMethod.Other],
DidMethodNetwork[DidMethod.Other][flg.toString()]
]);
return new Id(tp, genesis);
}
static getIdFromDID(did) {
const method = did.method;
const methodByte = DidMethodByte[method];
if (!methodByte || method === DidMethod.Other) {
throw Constants.ERRORS.UNKNOWN_DID_METHOD;
}
if (did.idStrings.length > 3 || did.idStrings.length < 2) {
throw new Error(`${Constants.ERRORS.INCORRECT_DID}: unexpected number of ID strings`);
}
const id = Id.fromString(did.idStrings[did.idStrings.length - 1]);
if (!BytesHelper.checkChecksum(id.bytes)) {
throw new Error(`${Constants.ERRORS.INCORRECT_DID}: incorrect ID checksum`);
}
const { method: method2, blockchain, networkId } = DID.decodePartsFromId(id);
if (method2.toString() !== method.toString()) {
throw new Error(`${Constants.ERRORS.INCORRECT_DID}: methods in Id and DID are different`);
}
if (blockchain.toString() !== did.idStrings[0]) {
throw new Error(`${Constants.ERRORS.INCORRECT_DID}: blockchains in ID and DID are different`);
}
if (did.idStrings.length > 2 && networkId.toString() != did.idStrings[1]) {
throw new Error(`${Constants.ERRORS.INCORRECT_DID}: networkIDs in Id and DID are different`);
}
return id;
}
}