@cheqd/sdk
Version:
A TypeScript SDK built with CosmJS to interact with the cheqd network ledger
369 lines • 15.7 kB
JavaScript
import { VerificationMethods, MethodSpecificIdAlgo, CheqdNetwork, ServiceType, } from './types.js';
import { fromString, toString } from 'uint8arrays';
import { bases } from 'multiformats/basics';
import { base64ToBytes } from 'did-jwt';
import { generateKeyPair, generateKeyPairFromSeed } from '@stablelib/ed25519';
import { DirectSecp256k1HdWallet, DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
import { EnglishMnemonic as _, sha256 } from '@cosmjs/crypto';
import { rawSecp256k1PubkeyToRawAddress } from '@cosmjs/amino';
import pkg from 'secp256k1';
import { v4 } from 'uuid';
import { VerificationMethod as ProtoVerificationMethod, Service as ProtoService, MsgCreateDidDocPayload, MsgDeactivateDidDocPayload, } from '@cheqd/ts-proto/cheqd/did/v2/index.js';
import { contexts, DIDModule } from './modules/did.js';
import { MsgCreateResourcePayload } from '@cheqd/ts-proto/cheqd/resource/v2/index.js';
import { toBech32 } from '@cosmjs/encoding';
import { StargateClient } from '@cosmjs/stargate';
import { backOff } from 'exponential-backoff';
export const TImportableEd25519Key = {
isValid(key) {
return (typeof key === 'object' &&
key !== null &&
typeof key.publicKeyHex === 'string' &&
isHex(key.publicKeyHex) &&
typeof key.privateKeyHex === 'string' &&
isHex(key.privateKeyHex) &&
typeof key.kid === 'string' &&
key.type === 'Ed25519');
},
};
const MULTICODEC_ED25519_HEADER = new Uint8Array([0xed, 0x01]);
export function isEqualKeyValuePair(kv1, kv2) {
return kv1.every((item, index) => item.key === kv2[index].key && item.value === kv2[index].value);
}
export class EnglishMnemonic extends _ {
static _mnemonicMatcher = /^[a-z]+( [a-z]+)*$/;
}
export function createSignInputsFromImportableEd25519Key(key, verificationMethod) {
if (!TImportableEd25519Key.isValid(key))
throw new Error(`Key validation failed. Expected ${Object.values(TImportableEd25519Key).join(', ')}`);
const publicKey = fromString(key.publicKeyHex, 'hex');
for (const method of verificationMethod) {
switch (method.type) {
case VerificationMethods.Ed255192020:
const publicKeyMultibase = toMultibaseRaw(publicKey);
if (method.publicKeyMultibase === publicKeyMultibase) {
return {
verificationMethodId: method.id,
privateKeyHex: key.privateKeyHex,
};
}
case VerificationMethods.Ed255192018:
const publicKeyBase58 = bases['base58btc'].encode(publicKey).slice(1);
if (method.publicKeyBase58 === publicKeyBase58) {
return {
verificationMethodId: method.id,
privateKeyHex: key.privateKeyHex,
};
}
case VerificationMethods.JWK:
const publicKeyJwk = {
crv: 'Ed25519',
kty: 'OKP',
x: toString(publicKey, 'base64url'),
};
if (JSON.stringify(method.publicKeyJwk) === JSON.stringify(publicKeyJwk)) {
return {
verificationMethodId: method.id,
privateKeyHex: key.privateKeyHex,
};
}
}
throw new Error(`Unsupported verification method type: ${method.type}. Expected one of: ${Object.values(VerificationMethods).join(', ')}`);
}
throw new Error(`No verification method type provided. Expected one of: ${Object.values(VerificationMethods).join(', ')}`);
}
export function createKeyPairRaw(seed) {
return seed ? generateKeyPairFromSeed(fromString(seed)) : generateKeyPair();
}
export function createKeyPairBase64(seed) {
const keyPair = seed ? generateKeyPairFromSeed(fromString(seed)) : generateKeyPair();
return {
publicKey: toString(keyPair.publicKey, 'base64'),
privateKey: toString(keyPair.secretKey, 'base64'),
};
}
export function createKeyPairHex(seed) {
const keyPair = createKeyPairRaw(seed);
return {
publicKey: toString(keyPair.publicKey, 'hex'),
privateKey: toString(keyPair.secretKey, 'hex'),
};
}
export function createVerificationKeys(publicKey, algo, keyFragment, network = CheqdNetwork.Testnet, methodSpecificId, didUrl) {
if (isHex(publicKey)) {
publicKey = toString(fromString(publicKey, 'hex'), 'base64');
}
else if (!isBase64(publicKey)) {
throw new Error('publicKey validation failed. PublicKey should be in base64 or hex format');
}
switch (algo) {
case MethodSpecificIdAlgo.Base58:
methodSpecificId ||= bases['base58btc'].encode(base64ToBytes(publicKey));
didUrl ||= `did:cheqd:${network}:${bases['base58btc']
.encode(sha256(base64ToBytes(publicKey)).slice(0, 16))
.slice(1)}`;
return {
methodSpecificId,
didUrl,
keyId: `${didUrl}#${keyFragment}`,
publicKey,
};
case MethodSpecificIdAlgo.Uuid:
methodSpecificId ||= v4();
didUrl ||= `did:cheqd:${network}:${methodSpecificId}`;
return {
methodSpecificId,
didUrl,
keyId: `${didUrl}#${keyFragment}`,
publicKey,
};
}
}
export function createDidVerificationMethod(verificationMethodTypes, verificationKeys) {
return (verificationMethodTypes.map((type, _) => {
switch (type) {
case VerificationMethods.Ed255192020:
return {
id: verificationKeys[_].keyId,
type,
controller: verificationKeys[_].didUrl,
publicKeyMultibase: toMultibaseRaw(base64ToBytes(verificationKeys[_].publicKey)),
};
case VerificationMethods.Ed255192018:
return {
id: verificationKeys[_].keyId,
type,
controller: verificationKeys[_].didUrl,
publicKeyBase58: bases['base58btc']
.encode(base64ToBytes(verificationKeys[_].publicKey))
.slice(1),
};
case VerificationMethods.JWK:
return {
id: verificationKeys[_].keyId,
type,
controller: verificationKeys[_].didUrl,
publicKeyJwk: {
crv: 'Ed25519',
kty: 'OKP',
x: toString(fromString(verificationKeys[_].publicKey, 'base64pad'), 'base64url'),
},
};
}
}) ?? []);
}
export function createDidPayload(verificationMethods, verificationKeys, controller = []) {
if (!verificationMethods || verificationMethods.length === 0)
throw new Error('No verification methods provided');
if (!verificationKeys || verificationKeys.length === 0)
throw new Error('No verification keys provided');
const did = verificationKeys[0].didUrl;
return {
id: did,
controller: controller.length ? controller : Array.from(new Set(verificationKeys.map((key) => key.didUrl))),
verificationMethod: verificationMethods,
authentication: verificationKeys.map((key) => key.keyId),
};
}
export function validateSpecCompliantPayload(didDocument) {
// id is required, validated on both compile and runtime
if (!didDocument?.id)
return { valid: false, error: 'id is required' };
// verificationMethod is required
if (!didDocument?.verificationMethod)
return { valid: false, error: 'verificationMethod is required' };
// verificationMethod must be an array
if (!Array.isArray(didDocument?.verificationMethod))
return { valid: false, error: 'verificationMethod must be an array' };
// verificationMethod types must be supported
const protoVerificationMethod = didDocument.verificationMethod.map((vm) => {
switch (vm?.type) {
case VerificationMethods.Ed255192020:
if (!vm.publicKeyMultibase)
throw new Error('publicKeyMultibase is required');
return ProtoVerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: VerificationMethods.Ed255192020,
verificationMaterial: vm.publicKeyMultibase,
});
case VerificationMethods.JWK:
if (!vm.publicKeyJwk)
throw new Error('publicKeyJwk is required');
return ProtoVerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: VerificationMethods.JWK,
verificationMaterial: JSON.stringify(vm.publicKeyJwk),
});
case VerificationMethods.Ed255192018:
if (!vm.publicKeyBase58)
throw new Error('publicKeyBase58 is required');
return ProtoVerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: VerificationMethods.Ed255192018,
verificationMaterial: vm.publicKeyBase58,
});
default:
throw new Error(`Unsupported verificationMethod type: ${vm?.type}`);
}
});
const protoService = normalizeService(didDocument);
return { valid: true, protobufVerificationMethod: protoVerificationMethod, protobufService: protoService };
}
export function createCosmosPayerWallet(cosmosPayerSeed) {
return EnglishMnemonic._mnemonicMatcher.test(cosmosPayerSeed)
? DirectSecp256k1HdWallet.fromMnemonic(cosmosPayerSeed, { prefix: 'cheqd' })
: DirectSecp256k1Wallet.fromKey(fromString(cosmosPayerSeed.replace(/^0x/, ''), 'hex'), 'cheqd');
}
export function toMultibaseRaw(key) {
const multibase = new Uint8Array(MULTICODEC_ED25519_HEADER.length + key.length);
multibase.set(MULTICODEC_ED25519_HEADER);
multibase.set(key, MULTICODEC_ED25519_HEADER.length);
return bases['base58btc'].encode(multibase);
}
export async function createMsgCreateDidDocPayloadToSign(didPayload, versionId) {
const { protobufVerificationMethod, protobufService } = await DIDModule.validateSpecCompliantPayload(didPayload);
return MsgCreateDidDocPayload.encode(MsgCreateDidDocPayload.fromPartial({
context: didPayload?.['@context'],
id: didPayload.id,
controller: didPayload.controller,
verificationMethod: protobufVerificationMethod,
authentication: didPayload.authentication,
assertionMethod: didPayload.assertionMethod,
capabilityInvocation: didPayload.capabilityInvocation,
capabilityDelegation: didPayload.capabilityDelegation,
keyAgreement: didPayload.keyAgreement,
service: protobufService,
alsoKnownAs: didPayload.alsoKnownAs,
versionId,
})).finish();
}
export const createMsgUpdateDidDocPayloadToSign = createMsgCreateDidDocPayloadToSign;
export function createMsgDeactivateDidDocPayloadToSign(didPayload, versionId) {
return MsgDeactivateDidDocPayload.encode(MsgDeactivateDidDocPayload.fromPartial({
id: didPayload.id,
versionId,
})).finish();
}
export function createMsgResourcePayloadToSign(payload) {
return MsgCreateResourcePayload.encode(MsgCreateResourcePayload.fromPartial(payload)).finish();
}
export function getCosmosAccount(publicKeyHex) {
const { publicKeyConvert } = pkg;
return toBech32('cheqd', rawSecp256k1PubkeyToRawAddress(publicKeyConvert(fromString(publicKeyHex, 'hex'), true)));
}
export async function checkBalance(address, rpcAddress) {
const client = await StargateClient.connect(rpcAddress);
return await client.getAllBalances(address);
}
export function isJSON(input) {
if (typeof input !== 'string')
return false;
try {
JSON.parse(input);
return true;
}
catch (e) {
return false;
}
}
export const DefaultBackoffOptions = {
jitter: 'full',
timeMultiple: 1,
delayFirstAttempt: false,
maxDelay: 100,
startingDelay: 100,
numOfAttempts: 3,
};
export async function retry(fn, options) {
// set default options
if (!options) {
options = DefaultBackoffOptions;
}
else {
// overwrite defaults with user supplied options
options = { ...DefaultBackoffOptions, ...options };
}
let result;
try {
result = await backOff(fn, options);
}
catch (e) {
console.error(e);
}
return result;
}
function isBase64(str) {
// Quick pattern check to filter obvious non-base64 strings
const base64Pattern = /^[A-Za-z0-9+/]*={0,3}$/;
if (!base64Pattern.test(str)) {
return false;
}
try {
return toString(fromString(str, 'base64'), 'base64') === str;
}
catch (e) {
return false;
}
}
function isHex(str) {
// Quick pattern check to filter obvious non-hex strings
const hexPattern = /^[0-9a-fA-F]*$/;
if (!hexPattern.test(str)) {
return false;
}
try {
return toString(fromString(str, 'hex'), 'hex') === str;
}
catch {
return false;
}
}
export function normalizeAuthentication(didDocument) {
if (!didDocument.authentication)
throw new Error('Invalid DID Document: Authentication section is required in DID Document');
const authArray = Array.isArray(didDocument.authentication)
? didDocument.authentication
: [didDocument.authentication];
return authArray.map((a) => (typeof a === 'string' ? a : a.id));
}
export function normalizeController(didDocument) {
if (!didDocument.controller)
return [didDocument.id];
return Array.isArray(didDocument.controller) ? didDocument.controller : [didDocument.controller];
}
export function normalizeService(didDocument) {
return didDocument.service?.map((s) => {
return ProtoService.fromPartial({
id: s?.id,
serviceType: s?.type,
serviceEndpoint: s ? (Array.isArray(s.serviceEndpoint) ? s.serviceEndpoint : [s.serviceEndpoint]) : [],
...(s?.recipientKeys && { recipientKeys: s.recipientKeys }),
...(s?.routingKeys && { routingKeys: s.routingKeys }),
...(s?.accept && { accept: s.accept }),
...(s?.priority !== undefined && { priority: s.priority }),
});
});
}
export function denormalizeService(didDocument) {
return didDocument.service.map((s) => {
if (s.serviceType === ServiceType.LinkedDomains)
didDocument.context = [...didDocument.context, contexts.LinkedDomainsContext];
return {
id: s.id,
type: s.serviceType,
serviceEndpoint: Array.isArray(s.serviceEndpoint)
? s.serviceEndpoint.length === 1
? s.serviceEndpoint[0]
: s.serviceEndpoint
: s?.serviceEndpoint,
...(s.recipientKeys && { recipientKeys: s.recipientKeys }),
...(s.routingKeys && { routingKeys: s.routingKeys }),
...(s.accept && { accept: s.accept }),
...(s.priority !== undefined && { priority: s.priority }),
};
});
}
//# sourceMappingURL=utils.js.map