/* eslint-disable camelcase */
import { u8aToHex } from '@polkadot/util';
import { createDidSig, typedHexDID } from '../utils/did';
import { getDidNonce } from '../utils/misc';
/**
* Class with logic for public keys and corresponding setup parameters.
* This logic is common in offchain signatures modules and accumulator
*/
export default class WithParamsAndPublicKeys {
/**
* Builds module-specific params from the provided value.
*/
static buildParams(params) {
return params;
}
/**
* Create object to add new parameters on chain
* @param bytes
* @param curveType
* @param label
* @returns {{}}
*/
static prepareAddParameters(bytes, curveType = undefined, label = undefined) {
const params = {};
if (bytes === undefined) {
throw new Error('bytes must be provided');
} else {
params.bytes = bytes;
}
if (curveType === undefined) {
params.curveType = 'Bls12381';
} else if (curveType === 'Bls12381') {
params.curveType = curveType;
} else {
throw new Error(`Invalid curve type ${curveType}`);
}
params.label = label;
return params;
}
/**
* Create object to add new public key on chain
* @param bytes
* @param curveType
* @param paramsRef - Provide if this public key was created using params present on chain.
* @returns {{}}
*/
static prepareAddPublicKey(
api,
bytes,
curveType = undefined,
paramsRef = undefined,
) {
const publicKey = {};
if (bytes === undefined) {
throw new Error('bytes must be provided');
} else {
publicKey.bytes = bytes;
}
if (curveType === undefined) {
publicKey.curveType = 'Bls12381';
} else if (curveType === 'Bls12381') {
publicKey.curveType = curveType;
} else {
throw new Error(`Invalid curve type ${curveType}`);
}
publicKey.paramsRef = paramsRef !== undefined
? WithParamsAndPublicKeys.parseRef(api, paramsRef)
: undefined;
return publicKey;
}
/**
* Parse a reference to a param or a public key. A reference uniquely identifies a param or a public key and is a pair
* of a DID and a positive number starting from 1.
* @param ref
* @returns {any[]}
*/
static parseRef(api, ref) {
const parsed = new Array(2);
if (
!(typeof ref === 'object' && ref instanceof Array && ref.length === 2)
) {
throw new Error('Reference should be an array of 2 items');
}
try {
parsed[0] = typedHexDID(api, ref[0]);
} catch (e) {
throw new Error(
`First item of reference should be a DID but was ${ref[0]}`,
);
}
if (typeof ref[1] !== 'number') {
throw new Error(
`Second item of reference should be a number but was ${ref[1]}`,
);
}
// eslint-disable-next-line prefer-destructuring
parsed[1] = ref[1];
return parsed;
}
/**
* Create transaction to add new BBS+ params.
* @param params - The BBS+ params to add.
* @param signerDid - Signer of the payload
* @param keyPair - Signer's keypair
* @param signingKeyRef - Reference to the keypair used by the signer. This will be used by the verifier (node) to fetch the public key for verification
* @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
* @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
* using this
* @returns {Promise<*>}
*/
async createAddParamsTx(
params,
signerDid,
signingKeyRef,
{ nonce = undefined, didModule = undefined },
) {
const offchainParams = this.constructor.buildParams(params);
const hexDid = typedHexDID(this.api, signerDid);
const [addParams, signature] = await this.createSignedAddParams(
offchainParams,
hexDid,
signingKeyRef,
{ nonce, didModule },
);
return this.module.addParams(addParams, signature);
}
/**
* Create transaction to remove existing BBS+ params.
* @param index - Index to uniquely identify BBS+ params
* @param signerDid - Signer of the payload
* @param keyPair - Signer's keypair
* @param signingKeyRef - Reference to the keypair used by the signer. This will be used by the verifier (node) to fetch the public key for verification
* @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
* @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
* using this
* @returns {Promise<*>}
*/
async removeParamsTx(
index,
signerDid,
signingKeyRef,
{ nonce = undefined, didModule = undefined },
) {
const hexDid = typedHexDID(this.api, signerDid);
const [removeParams, signature] = await this.createSignedRemoveParams(
index,
hexDid,
signingKeyRef,
{ nonce, didModule },
);
return this.module.removeParams(removeParams, signature);
}
/**
* Add new signature params.
* @param param - The signature params to add.
* @param signerDid - Signer of the payload
* @param keyPair - Signer's keypair
* @param signingKeyRef - Reference to the keypair used by the signer. This will be used by the verifier (node) to fetch the public key for verification
* @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
* @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
* using this
* @param waitForFinalization
* @param params
* @returns {Promise<*>}
*/
async addParams(
param,
signerDid,
signingKeyRef,
{ nonce = undefined, didModule = undefined },
waitForFinalization = true,
params = {},
) {
const tx = await this.createAddParamsTx(param, signerDid, signingKeyRef, {
nonce,
didModule,
});
return this.signAndSend(tx, waitForFinalization, params);
}
/**
* Remove existing BBS+ params.
* @param index - Index to uniquely identify BBS+ params
* @param signerDid - Signer of the blob
* @param keyPair - Signer's keypair
* @param signingKeyRef - Reference to the keypair used by the signer. This will be used by the verifier (node) to fetch the public key for verification
* @param nonce - The nonce to be used for sending this transaction. If not provided then `didModule` must be provided.
* @param didModule - Reference to the DID module. If nonce is not provided then the next nonce for the DID is fetched by
* using this
* @param waitForFinalization
* @param params
* @returns {Promise<*>}
*/
async removeParams(
index,
signerDid,
signingKeyRef,
{ nonce = undefined, didModule = undefined },
waitForFinalization = true,
params = {},
) {
const tx = await this.removeParamsTx(index, signerDid, signingKeyRef, {
nonce,
didModule,
});
return this.signAndSend(tx, waitForFinalization, params);
}
async createSignedAddParams(
params,
hexDid,
signingKeyRef,
{ nonce = undefined, didModule = undefined },
) {
// eslint-disable-next-line no-param-reassign
nonce = await getDidNonce(hexDid, nonce, didModule);
const addParams = { params, nonce };
const signature = this.signAddParams(signingKeyRef, addParams);
const didSig = createDidSig(hexDid, signingKeyRef, signature);
return [addParams, didSig];
}
async createSignedRemoveParams(
index,
hexDid,
signingKeyRef,
{ nonce = undefined, didModule = undefined },
) {
// eslint-disable-next-line no-param-reassign
nonce = await getDidNonce(hexDid, nonce, didModule);
const removeParams = { paramsRef: [hexDid, index], nonce };
const signature = this.signRemoveParams(signingKeyRef, removeParams);
const didSig = createDidSig(hexDid, signingKeyRef, signature);
return [removeParams, didSig];
}
// eslint-disable-next-line no-unused-vars
async queryParamsFromChain(hexDid, counter) {
throw new Error('Extending class should implement queryParamsFromChain');
}
// eslint-disable-next-line no-unused-vars
async queryPublicKeyFromChain(hexDid, keyId) {
throw new Error('Extending class should implement queryPublicKeyFromChain');
}
async getParams(did, counter) {
const hexId = typedHexDID(this.api, did);
return this.getParamsByHexDid(hexId, counter);
}
/**
*
* @param did
* @param keyId
* @param withParams - If true, return the params referenced by the public key. It will throw an error if paramsRef is null
* or params were not found on chain which can happen if they were deleted after this public key was added.
* @returns {Promise<{bytes: string}|null>}
*/
async getPublicKey(did, keyId, withParams = false) {
const hexId = typedHexDID(this.api, did);
return this.getPublicKeyByHexDid(hexId, keyId, withParams);
}
async getParamsByHexDid(hexDid, counter) {
const resp = await this.queryParamsFromChain(hexDid, counter);
if (resp) {
return this.createParamsObjFromChainResponse(resp);
}
return null;
}
async getPublicKeyByHexDid(hexDid, keyId, withParams = false) {
const resp = await this.queryPublicKeyFromChain(hexDid, keyId);
if (resp) {
const pkObj = WithParamsAndPublicKeys.createPublicKeyObjFromChainResponse(
this.api,
resp,
);
if (withParams) {
if (pkObj.paramsRef === null) {
throw new Error('No reference to parameters for the public key');
} else {
const params = await this.getParamsByHexDid(
pkObj.paramsRef[0],
pkObj.paramsRef[1],
);
if (params === null) {
throw new Error(
`Parameters with reference (${pkObj.paramsRef[0]}, ${pkObj.paramsRef[1]}) not found on chain`,
);
}
pkObj.params = params;
}
}
return pkObj;
}
return null;
}
/**
* Format an object received from the chain as a params object with keys `bytes`, `label` and `curveType`.
* @param params
* @returns {{bytes: string}}
*/
createParamsObjFromChainResponse(params) {
const paramsObj = {
bytes: u8aToHex(params.bytes),
};
if (params.curveType.isBls12381) {
paramsObj.curveType = 'Bls12381';
}
if (params.label.isSome) {
paramsObj.label = u8aToHex(params.label.unwrap());
} else {
paramsObj.label = null;
}
return paramsObj;
}
/**
* Format an object received from the chain as a public key object with keys `bytes`, ` paramsRef` and `curveType`.
* @param pk
* @returns {{bytes: string}}
*/
static createPublicKeyObjFromChainResponse(api, pk) {
const pkObj = {
bytes: u8aToHex(pk.bytes),
};
if (pk.curveType.isBls12381) {
pkObj.curveType = 'Bls12381';
}
if (pk.paramsRef.isSome) {
const pr = pk.paramsRef.unwrap();
pkObj.paramsRef = [typedHexDID(api, pr[0]), pr[1].toNumber()];
} else {
pkObj.paramsRef = null;
}
pkObj.participantId = null;
return pkObj;
}
// eslint-disable-next-line no-unused-vars
signAddParams(keyPair, params) {
throw new Error('Extending class should implement signAddParams');
}
// eslint-disable-next-line no-unused-vars
signRemoveParams(keyPair, ref) {
throw new Error('Extending class should implement signRemoveParams');
}
}