You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/session/apis/snode_api/signature/snodeSignatures.ts

212 lines
6.6 KiB
TypeScript

import { GroupPubkeyType, Uint8ArrayLen100, Uint8ArrayLen64 } from 'libsession_util_nodejs';
import { isEmpty } from 'lodash';
import { toFixedUint8ArrayOfLength } from '../../../../types/sqlSharedTypes';
import { getSodiumRenderer } from '../../../crypto';
import { PubKey } from '../../../types';
import { StringUtils, UserUtils } from '../../../utils';
import { fromHexToArray, fromUInt8ArrayToBase64 } from '../../../utils/String';
import { PreConditionFailed } from '../../../utils/errors';
import { GetNetworkTime } from '../getNetworkTime';
import { WithMessagesHashes, WithShortenOrExtend, WithTimestamp } from '../types';
export type SnodeSignatureResult = WithTimestamp & {
signature: string;
pubkey_ed25519: string;
pubkey: string; // this is the x25519 key of the pubkey we are doing the request to (ourself for our swarm usually)
};
async function getSnodeSignatureByHashesParams({
messagesHashes,
method,
pubkey,
}: WithMessagesHashes & {
pubkey: string;
method: 'delete';
}): Promise<
Pick<SnodeSignatureResult, 'pubkey_ed25519' | 'signature' | 'pubkey'> & {
messages: Array<string>;
}
> {
const ourEd25519Key = await UserUtils.getUserED25519KeyPair();
if (!ourEd25519Key) {
const err = `getSnodeSignatureParams "${method}": User has no getUserED25519KeyPair()`;
window.log.warn(err);
throw new Error(err);
}
const edKeyPrivBytes = fromHexToArray(ourEd25519Key?.privKey);
const verificationData = StringUtils.encode(`${method}${messagesHashes.join('')}`, 'utf8');
const message = new Uint8Array(verificationData);
const sodium = await getSodiumRenderer();
try {
const signature = sodium.crypto_sign_detached(message, edKeyPrivBytes);
const signatureBase64 = fromUInt8ArrayToBase64(signature);
return {
signature: signatureBase64,
pubkey_ed25519: ourEd25519Key.pubKey,
pubkey,
messages: messagesHashes,
};
} catch (e) {
window.log.warn('getSnodeSignatureParams failed with: ', e.message);
throw e;
}
}
type SnodeSigParamsShared = {
namespace: number | null | 'all'; // 'all' can be used to clear all namespaces (during account deletion)
method: 'retrieve' | 'store' | 'delete_all';
};
type SnodeSigParamsAdminGroup = SnodeSigParamsShared & {
groupPk: GroupPubkeyType;
privKey: Uint8ArrayLen64; // len 64
};
type SnodeSigParamsSubAccount = SnodeSigParamsShared & {
groupPk: GroupPubkeyType;
authData: Uint8ArrayLen100; // len 100
};
type SnodeSigParamsUs = SnodeSigParamsShared & {
pubKey: string;
privKey: Uint8ArrayLen64; // len 64
};
function isSigParamsForGroupAdmin(
sigParams: SnodeSigParamsAdminGroup | SnodeSigParamsUs | SnodeSigParamsSubAccount
): sigParams is SnodeSigParamsAdminGroup {
const asGr = sigParams as SnodeSigParamsAdminGroup;
return PubKey.isClosedGroupV2(asGr.groupPk) && !isEmpty(asGr.privKey);
}
function getVerificationData(params: SnodeSigParamsShared) {
const signatureTimestamp = GetNetworkTime.getNowWithNetworkOffset();
const verificationData = StringUtils.encode(
`${params.method}${params.namespace === 0 ? '' : params.namespace}${signatureTimestamp}`,
'utf8'
);
return {
toSign: new Uint8Array(verificationData),
signatureTimestamp,
};
}
async function getSnodeSignatureShared(params: SnodeSigParamsAdminGroup | SnodeSigParamsUs) {
const { signatureTimestamp, toSign } = getVerificationData(params);
try {
const sodium = await getSodiumRenderer();
const signature = sodium.crypto_sign_detached(toSign, params.privKey);
const signatureBase64 = fromUInt8ArrayToBase64(signature);
if (isSigParamsForGroupAdmin(params)) {
return {
timestamp: signatureTimestamp,
signature: signatureBase64,
pubkey: params.groupPk,
};
}
return {
timestamp: signatureTimestamp,
signature: signatureBase64,
};
} catch (e) {
window.log.warn('getSnodeShared failed with: ', e.message);
throw e;
}
}
async function getSnodeSignatureParamsUs({
method,
namespace = 0,
}: Pick<SnodeSigParamsUs, 'method' | 'namespace'>): Promise<SnodeSignatureResult> {
const ourEd25519Key = await UserUtils.getUserED25519KeyPairBytes();
const ourEd25519PubKey = await UserUtils.getUserED25519KeyPair();
if (!ourEd25519Key || !ourEd25519PubKey) {
const err = `getSnodeSignatureParams "${method}": User has no getUserED25519KeyPairBytes()`;
window.log.warn(err);
throw new Error(err);
}
const edKeyPrivBytes = ourEd25519Key.privKeyBytes;
const lengthCheckedPrivKey = toFixedUint8ArrayOfLength(edKeyPrivBytes, 64);
const sigData = await getSnodeSignatureShared({
pubKey: UserUtils.getOurPubKeyStrFromCache(),
method,
namespace,
privKey: lengthCheckedPrivKey.buffer,
});
const us = UserUtils.getOurPubKeyStrFromCache();
return {
...sigData,
pubkey_ed25519: ourEd25519PubKey.pubKey,
pubkey: us,
};
}
async function generateUpdateExpirySignature({
shortenOrExtend,
timestamp,
messagesHashes,
ed25519Privkey,
ed25519Pubkey,
}: WithMessagesHashes &
WithShortenOrExtend &
WithTimestamp & {
ed25519Privkey: Uint8Array; // len 64
ed25519Pubkey: string;
}): Promise<{ signature: string; pubkey: string }> {
// "expire" || ShortenOrExtend || expiry || messages[0] || ... || messages[N]
const verificationString = `expire${shortenOrExtend}${timestamp}${messagesHashes.join('')}`;
const verificationData = StringUtils.encode(verificationString, 'utf8');
const message = new Uint8Array(verificationData);
const sodium = await getSodiumRenderer();
const signature = sodium.crypto_sign_detached(message, ed25519Privkey);
const signatureBase64 = fromUInt8ArrayToBase64(signature);
if (isEmpty(signatureBase64) || isEmpty(ed25519Pubkey)) {
throw new Error('generateUpdateExpirySignature: failed to build signature');
}
return {
signature: signatureBase64,
pubkey: ed25519Pubkey,
};
}
async function generateUpdateExpiryOurSignature({
shortenOrExtend,
timestamp,
messagesHashes,
}: WithMessagesHashes & WithShortenOrExtend & WithTimestamp) {
const ourEd25519Key = await UserUtils.getUserED25519KeyPair();
if (!ourEd25519Key) {
const err = 'getSnodeSignatureParams "expiry": User has no getUserED25519KeyPair()';
window.log.warn(err);
throw new PreConditionFailed(err);
}
const edKeyPrivBytes = fromHexToArray(ourEd25519Key?.privKey);
return generateUpdateExpirySignature({
messagesHashes,
shortenOrExtend,
timestamp,
ed25519Privkey: toFixedUint8ArrayOfLength(edKeyPrivBytes, 64).buffer,
ed25519Pubkey: ourEd25519Key.pubKey,
});
}
export const SnodeSignature = {
getSnodeSignatureParamsUs,
getSnodeSignatureByHashesParams,
generateUpdateExpiryOurSignature,
};