feat: add subaccount auth

pull/2963/head
Audric Ackermann 2 years ago
parent d7608c42b6
commit b8876ebbfe

@ -56,6 +56,9 @@ module.exports = {
// it helps readability to put public API at top,
'no-use-before-define': 'off',
// we need them with code in WIP sometimes, and it doesn't do any harm
'no-useless-return': 'off',
// useful for unused or internal fields
'no-underscore-dangle': 'off',

@ -40,7 +40,6 @@ function onClickOnMessageInnerContainer(event: React.MouseEvent<HTMLDivElement>)
// User clicked on message body
const target = event.target as HTMLDivElement;
if (target.className === 'text-selectable' || window.contextMenuShown) {
// eslint-disable-next-line no-useless-return
return;
}
}

@ -34,6 +34,7 @@ const StyledMemberListNoContacts = styled.div`
const StyledGroupMemberListContainer = styled.div`
padding: 2px 0px;
width: 100%;
min-height: 40px;
max-height: 400px;
overflow-y: auto;
border-top: 1px solid var(--border-color);

@ -75,7 +75,7 @@ export async function removeAllClosedGroupEncryptionKeyPairs(groupPubKey: string
await Data.removeAllClosedGroupEncryptionKeyPairs(groupPubKey);
}
export async function handleClosedGroupControlMessage(
export async function handleLegacyClosedGroupControlMessage(
envelope: EnvelopePlus,
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage
) {

@ -11,7 +11,7 @@ import { ConversationModel } from '../models/conversation';
import { ConvoHub } from '../session/conversations';
import { PubKey } from '../session/types';
import { StringUtils, UserUtils } from '../session/utils';
import { handleClosedGroupControlMessage } from './closedGroups';
import { handleLegacyClosedGroupControlMessage } from './closedGroups';
import { handleMessageJob, toRegularMessage } from './queuedJob';
import { ConversationTypeEnum } from '../models/conversationAttributes';
@ -25,6 +25,7 @@ import { isUsFromCache } from '../session/utils/User';
import { Action, Reaction } from '../types/Reaction';
import { toLogFormat } from '../types/attachments/Errors';
import { Reactions } from '../util/reactions';
import { GroupV2Receiver } from './groupv2/handleGroupV2Message';
function cleanAttachment(attachment: any) {
return {
@ -43,7 +44,7 @@ function cleanAttachments(decrypted: SignalService.DataMessage) {
// Here we go from binary to string/base64 in all AttachmentPointer digest/key fields
// we do not care about group on Session
// we do not care about group on Session Desktop
decrypted.group = null;
@ -150,7 +151,6 @@ export function cleanIncomingDataMessage(
* * envelope.source is our pubkey (our other device has the same pubkey as us)
* * dataMessage.syncTarget is either the group public key OR the private conversation this message is about.
*/
export async function handleSwarmDataMessage(
envelope: EnvelopePlus,
sentAtTimestamp: number,
@ -161,9 +161,20 @@ export async function handleSwarmDataMessage(
window.log.info('handleSwarmDataMessage');
const cleanDataMessage = cleanIncomingDataMessage(rawDataMessage, envelope);
// we handle group updates from our other devices in handleClosedGroupControlMessage()
if (cleanDataMessage.groupUpdateMessage) {
await GroupV2Receiver.handleGroupUpdateMessage({
envelopeTimestamp: sentAtTimestamp,
updateMessage: rawDataMessage.groupUpdateMessage as SignalService.GroupUpdateMessage,
});
// Groups update should always be able to be decrypted as we get the keys before trying to decrypt them.
// If decryption failed once, it will keep failing, so no need to keep it in the cache.
await removeFromCache({ id: envelope.id });
return;
}
// we handle legacy group updates from our other devices in handleLegacyClosedGroupControlMessage()
if (cleanDataMessage.closedGroupControlMessage) {
await handleClosedGroupControlMessage(
await handleLegacyClosedGroupControlMessage(
envelope,
cleanDataMessage.closedGroupControlMessage as SignalService.DataMessage.ClosedGroupControlMessage
);

@ -0,0 +1,90 @@
import { isEmpty } from 'lodash';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { HexString } from '../../node/hexStrings';
import { SignalService } from '../../protobuf';
import { getSwarmPollingInstance } from '../../session/apis/snode_api';
import { ConvoHub } from '../../session/conversations';
import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils';
import { LibSessionUtil } from '../../session/utils/libsession/libsession_utils';
import { toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes';
import {
MetaGroupWrapperActions,
UserGroupsWrapperActions,
} from '../../webworker/workers/browser/libsession_worker_interface';
type WithEnvelopeTimestamp = { envelopeTimestamp: number };
type GroupInviteDetails = {
inviteMessage: SignalService.GroupUpdateInviteMessage;
} & WithEnvelopeTimestamp;
type GroupUpdateDetails = {
updateMessage: SignalService.GroupUpdateMessage;
} & WithEnvelopeTimestamp;
async function handleGroupInviteMessage({ inviteMessage, envelopeTimestamp }: GroupInviteDetails) {
if (!PubKey.isClosedGroupV2(inviteMessage.groupSessionId)) {
// invite to a group which has not a 03 prefix, we can just drop it.
return;
}
// TODO verify sig invite adminSignature
const convo = await ConvoHub.use().getOrCreateAndWait(
inviteMessage.groupSessionId,
ConversationTypeEnum.GROUPV2
);
convo.set({
active_at: envelopeTimestamp,
didApproveMe: true,
});
if (inviteMessage.name && isEmpty(convo.getRealSessionUsername())) {
convo.set({
displayNameInProfile: inviteMessage.name,
});
}
await convo.commit();
let found = await UserGroupsWrapperActions.getGroup(inviteMessage.groupSessionId);
if (!found) {
found = {
authData: null,
joinedAtSeconds: Date.now(),
name: inviteMessage.name,
priority: 0,
pubkeyHex: inviteMessage.groupSessionId,
secretKey: null,
};
}
// not sure if we should drop it, or set it again? They should be the same anyway
found.authData = inviteMessage.memberAuthData;
const userEd25519Secretkey = (await UserUtils.getUserED25519KeyPairBytes()).privKeyBytes;
await UserGroupsWrapperActions.setGroup(found);
await MetaGroupWrapperActions.init(inviteMessage.groupSessionId, {
metaDumped: null,
groupEd25519Secretkey: null,
userEd25519Secretkey: toFixedUint8ArrayOfLength(userEd25519Secretkey, 64).buffer,
groupEd25519Pubkey: toFixedUint8ArrayOfLength(
HexString.fromHexString(inviteMessage.groupSessionId.slice(2)),
32
).buffer,
});
await LibSessionUtil.saveDumpsToDb(UserUtils.getOurPubKeyStrFromCache());
// TODO use the pending so we actually don't start polling here unless it is not in the pending state.
// once everything is ready, start polling using that authData to get the keys, members, details of that group, and its messages.
getSwarmPollingInstance().addGroupId(inviteMessage.groupSessionId);
}
async function handleGroupUpdateMessage(args: GroupUpdateDetails) {
if (args.updateMessage.inviteMessage) {
await handleGroupInviteMessage({
inviteMessage: args.updateMessage.inviteMessage as SignalService.GroupUpdateInviteMessage,
...args,
});
return;
}
}
export const GroupV2Receiver = { handleGroupUpdateMessage };

@ -8,8 +8,8 @@ import { ed25519Str } from '../../onions/onionPath';
import { StringUtils, UserUtils } from '../../utils';
import { fromBase64ToArray, fromHexToArray } from '../../utils/String';
import { doSnodeBatchRequest } from './batchRequest';
import { SnodeSignature } from './signature/snodeSignatures';
import { getSwarmFor } from './snodePool';
import { SnodeSignature } from './snodeSignatures';
export const ERROR_CODE_NO_CONNECT = 'ENETUNREACH: No network connection.';

@ -49,6 +49,16 @@ export type RetrieveGroupAdminSubRequestType = WithRetrieveMethod & {
params: {
signature: string;
namespace: SnodeNamespacesGroup;
} & RetrieveAlwaysNeeded &
WithMaxCountSize;
};
export type RetrieveGroupSubAccountSubRequestType = WithRetrieveMethod & {
params: {
namespace: SnodeNamespacesGroup;
signature: string;
subaccount: string;
subaccount_sig: string;
} & RetrieveAlwaysNeeded &
WithMaxCountSize &
WithPubkeyAsGroupPubkey;
@ -59,7 +69,8 @@ export type RetrieveSubRequestType =
| RetrievePubkeySubRequestType
| RetrieveGroupAdminSubRequestType
| UpdateExpiryOnNodeUserSubRequest
| UpdateExpiryOnNodeGroupSubRequest;
| UpdateExpiryOnNodeGroupSubRequest
| RetrieveGroupSubAccountSubRequestType;
/**
* OXEND_REQUESTS
@ -91,7 +102,7 @@ export type GetServiceNodesSubRequest = {
};
};
export type StoreOnNodeParams = {
type StoreOnNodeNormalParams = {
pubkey: string;
ttl: number;
timestamp: number;
@ -102,6 +113,19 @@ export type StoreOnNodeParams = {
pubkey_ed25519?: string;
};
type StoreOnNodeSubAccountParams = Pick<
StoreOnNodeNormalParams,
'data' | 'namespace' | 'ttl' | 'timestamp'
> & {
pubkey: GroupPubkeyType;
subaccount: string;
subaccount_sig: string;
namespace: SnodeNamespaces.ClosedGroupMessages; // this can only be this one, subaccounts holder can not post to something else atm
signature: string; // signature is mandatory for subaccount
};
export type StoreOnNodeParams = StoreOnNodeNormalParams | StoreOnNodeSubAccountParams;
export type StoreOnNodeParamsNoSig = Pick<
StoreOnNodeParams,
'pubkey' | 'ttl' | 'timestamp' | 'ttl' | 'namespace'
@ -131,7 +155,10 @@ type StoreOnNodeUserConfig = StoreOnNodeShared & {
export type StoreOnNodeData = StoreOnNodeGroupConfig | StoreOnNodeUserConfig;
export type StoreOnNodeSubRequest = { method: 'store'; params: StoreOnNodeParams };
export type StoreOnNodeSubRequest = {
method: 'store';
params: StoreOnNodeParams | StoreOnNodeSubAccountParams;
};
export type NetworkTimeSubRequest = { method: 'info'; params: object };
type DeleteSigParameters = {

@ -8,8 +8,8 @@ import { EmptySwarmError } from '../../utils/errors';
import { UpdateExpireNodeUserParams } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime';
import { SnodeSignature } from './signature/snodeSignatures';
import { getSwarmFor } from './snodePool';
import { SnodeSignature } from './snodeSignatures';
async function verifySignature({
pubkey,

@ -1,5 +1,5 @@
import { isEmpty, isNil, omit } from 'lodash';
import { GroupPubkeyType } from 'libsession_util_nodejs';
import { omit } from 'lodash';
import { Snode } from '../../../data/data';
import { updateIsOnline } from '../../../state/ducks/onion';
import { doSnodeBatchRequest } from './batchRequest';
@ -12,14 +12,15 @@ import { PubKey } from '../../types';
import { UserUtils } from '../../utils';
import {
RetrieveGroupAdminSubRequestType,
RetrieveGroupSubAccountSubRequestType,
RetrieveLegacyClosedGroupSubRequestType,
RetrieveSubRequestType,
UpdateExpiryOnNodeGroupSubRequest,
UpdateExpiryOnNodeUserSubRequest,
} from './SnodeRequestTypes';
import { SnodeSignature } from './snodeSignatures';
import { SnodeGroupSignature } from './signature/groupSignature';
import { SnodeSignature } from './signature/snodeSignatures';
import { RetrieveMessagesResultsBatched, RetrieveMessagesResultsContent } from './types';
import { PreConditionFailed } from '../../utils/errors';
type RetrieveParams = {
pubkey: string;
@ -102,28 +103,23 @@ async function retrieveRequestForGroup({
throw new Error(`retrieveRequestForGroup: not a groupNamespace: ${namespace}`);
}
const group = await UserGroupsWrapperActions.getGroup(groupPk);
const groupSecretKey = group?.secretKey;
if (isNil(groupSecretKey) || isEmpty(groupSecretKey)) {
throw new PreConditionFailed(
`retrieveRequestForGroup: failed to find group admin secret key in wrapper`
);
}
const signatureBuilt = await SnodeSignature.getSnodeGroupSignatureParams({
...retrieveParam,
const sigResult = await SnodeGroupSignature.getSnodeGroupSignature({
method: 'retrieve',
namespace,
method: 'retrieve' as const,
groupPk,
groupIdentityPrivKey: groupSecretKey,
group,
});
const retrieveGroup = {
const retrieveParamsGroup:
| RetrieveGroupSubAccountSubRequestType
| RetrieveGroupAdminSubRequestType = {
method: 'retrieve',
params: {
...retrieveParam,
...signatureBuilt,
...sigResult,
namespace,
};
const retrieveParamsGroup: RetrieveGroupAdminSubRequestType = {
method: 'retrieve' as const,
params: retrieveGroup,
},
};
return retrieveParamsGroup;
@ -188,17 +184,12 @@ async function buildRetrieveRequest(
retrieveRequestsParams.push(expireParams);
} else if (PubKey.isClosedGroupV2(pubkey)) {
const group = await UserGroupsWrapperActions.getGroup(pubkey);
if (!group || !group.secretKey || isEmpty(group.secretKey)) {
throw new PreConditionFailed(
'generateUpdateExpiryGroupSignature only handles when the group is in the userwrapper currently and we have the adminkey'
);
}
const signResult = await SnodeSignature.generateUpdateExpiryGroupSignature({
const signResult = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
shortenOrExtend: '',
timestamp: expiry,
messagesHashes: configHashesToBump,
groupPk: pubkey,
groupPrivKey: group.secretKey,
group,
});
const expireParams: UpdateExpiryOnNodeGroupSubRequest = {
@ -206,7 +197,7 @@ async function buildRetrieveRequest(
params: {
messages: configHashesToBump,
expiry,
signature: signResult.signature,
...signResult,
pubkey,
},
};

@ -0,0 +1,225 @@
import {
GroupMemberGet,
GroupPubkeyType,
Uint8ArrayLen100,
Uint8ArrayLen64,
UserGroupsGet,
} from 'libsession_util_nodejs';
import { compact, isEmpty } from 'lodash';
import { MetaGroupWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface';
import { getSodiumRenderer } from '../../../crypto/MessageEncrypter';
import { GroupUpdateInviteMessage } from '../../../messages/outgoing/controlMessage/group_v2/to_user/GroupUpdateInviteMessage';
import { StringUtils, UserUtils } from '../../../utils';
import { fromUInt8ArrayToBase64 } from '../../../utils/String';
import { PreConditionFailed } from '../../../utils/errors';
import { GetNetworkTime } from '../getNetworkTime';
import { SnodeNamespacesGroup } from '../namespaces';
import { WithMessagesHashes, WithShortenOrExtend, WithTimestamp } from '../types';
import { SignatureShared } from './signatureShared';
import { SnodeSignatureResult } from './snodeSignatures';
async function getGroupInvitesMessages({
groupName,
membersFromWrapper,
secretKey,
groupPk,
}: {
membersFromWrapper: Array<GroupMemberGet>;
groupName: string;
secretKey: Uint8ArrayLen64; // len 64
groupPk: GroupPubkeyType;
}) {
const sodium = await getSodiumRenderer();
const timestamp = GetNetworkTime.getNowWithNetworkOffset();
const inviteDetails = compact(
await Promise.all(
membersFromWrapper.map(async ({ pubkeyHex: member }) => {
if (UserUtils.isUsFromCache(member)) {
return null;
}
const tosign = `INVITE${member}${timestamp}`;
// Note: as the signature is built with the timestamp here, we cannot override the timestamp later on the sending pipeline
const adminSignature = sodium.crypto_sign_detached(tosign, secretKey);
const memberAuthData = await MetaGroupWrapperActions.makeSwarmSubAccount(groupPk, member);
const invite = new GroupUpdateInviteMessage({
groupName,
groupPk,
timestamp,
adminSignature,
memberAuthData,
});
return { member, invite };
})
)
);
return inviteDetails;
}
type ParamsShared = {
groupPk: GroupPubkeyType;
namespace: SnodeNamespacesGroup;
method: 'retrieve' | 'store';
};
type SigParamsAdmin = ParamsShared & {
groupIdentityPrivKey: Uint8ArrayLen64;
};
type SigParamsSubaccount = ParamsShared & {
authData: Uint8ArrayLen100;
};
export type SigResultAdmin = Pick<SnodeSignatureResult, 'signature' | 'timestamp'> & {
pubkey: GroupPubkeyType; // this is the 03 pubkey of the corresponding group
};
export type SigResultSubAccount = SigResultAdmin & {
subaccount: string;
subaccount_sig: string;
};
async function getSnodeGroupSignatureParams(params: SigParamsAdmin): Promise<SigResultAdmin>;
async function getSnodeGroupSignatureParams(
params: SigParamsSubaccount
): Promise<SigResultSubAccount>;
async function getSnodeGroupSignatureParams(
params: SigParamsAdmin | SigParamsSubaccount
): Promise<SigResultSubAccount | SigResultAdmin> {
if ('groupIdentityPrivKey' in params) {
return getSnodeGroupAdminSignatureParams(params);
}
return getSnodeGroupSubAccountSignatureParams(params);
}
async function getSnodeGroupSubAccountSignatureParams(
params: SigParamsSubaccount
): Promise<SigResultSubAccount> {
const { signatureTimestamp, toSign } =
SignatureShared.getVerificationDataForStoreRetrieve(params);
const sigResult = await MetaGroupWrapperActions.swarmSubaccountSign(
params.groupPk,
toSign,
params.authData
);
return {
...sigResult,
timestamp: signatureTimestamp,
pubkey: params.groupPk,
};
}
async function getSnodeGroupAdminSignatureParams(params: SigParamsAdmin): Promise<SigResultAdmin> {
const sigData = await SignatureShared.getSnodeSignatureShared({
pubKey: params.groupPk,
method: params.method,
namespace: params.namespace,
privKey: params.groupIdentityPrivKey,
});
return { ...sigData, pubkey: params.groupPk };
}
type GroupDetailsNeededForSignature = Pick<UserGroupsGet, 'pubkeyHex' | 'authData' | 'secretKey'>;
async function getSnodeGroupSignature({
group,
method,
namespace,
}: {
group: GroupDetailsNeededForSignature | null;
method: 'store' | 'retrieve';
namespace: SnodeNamespacesGroup;
}) {
if (!group) {
throw new Error(`getSnodeGroupSignature: did not find group in wrapper`);
}
const { pubkeyHex: groupPk, secretKey, authData } = group;
const groupSecretKey = secretKey && !isEmpty(secretKey) ? secretKey : null;
const groupAuthData = authData && !isEmpty(authData) ? authData : null;
if (groupSecretKey) {
return getSnodeGroupSignatureParams({
method,
namespace,
groupPk,
groupIdentityPrivKey: groupSecretKey,
});
}
if (groupAuthData) {
const subAccountSign = await getSnodeGroupSignatureParams({
groupPk,
method,
namespace,
authData: groupAuthData,
});
return subAccountSign;
}
throw new Error(`getSnodeGroupSignature: needs either groupSecretKey or authData`);
}
// this is kind of duplicated with `generateUpdateExpirySignature`, but needs to use the authData when secretKey is not available
async function generateUpdateExpiryGroupSignature({
shortenOrExtend,
timestamp,
messagesHashes,
group,
}: WithMessagesHashes &
WithShortenOrExtend &
WithTimestamp & {
group: GroupDetailsNeededForSignature | null;
}) {
if (!group || isEmpty(group.pubkeyHex)) {
throw new PreConditionFailed('generateUpdateExpiryGroupSignature groupPk is empty');
}
// "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);
if (!group) {
throw new Error('generateUpdateExpiryGroupSignature group was not found');
}
const { pubkeyHex: groupPk, secretKey, authData } = group;
const groupSecretKey = secretKey && !isEmpty(secretKey) ? secretKey : null;
const groupAuthData = authData && !isEmpty(authData) ? authData : null;
if (!groupSecretKey && !groupAuthData) {
throw new Error(`retrieveRequestForGroup: needs either groupSecretKey or authData`);
}
const sodium = await getSodiumRenderer();
const shared = { timestamp, pubkey: groupPk };
if (groupSecretKey) {
return {
signature: fromUInt8ArrayToBase64(sodium.crypto_sign_detached(message, groupSecretKey)),
...shared,
};
}
if (groupAuthData) {
const subaccountSign = await MetaGroupWrapperActions.swarmSubaccountSign(
groupPk,
message,
groupAuthData
);
return {
...subaccountSign,
...shared,
};
}
throw new Error(`generateUpdateExpiryGroupSignature: needs either groupSecretKey or authData`);
}
export const SnodeGroupSignature = {
generateUpdateExpiryGroupSignature,
getGroupInvitesMessages,
getSnodeGroupSignature,
};

@ -0,0 +1,75 @@
import { GroupPubkeyType, Uint8ArrayLen100, Uint8ArrayLen64 } from 'libsession_util_nodejs';
import { isEmpty } from 'lodash';
import { getSodiumRenderer } from '../../../crypto';
import { PubKey } from '../../../types';
import { StringUtils } from '../../../utils';
import { fromUInt8ArrayToBase64 } from '../../../utils/String';
import { GetNetworkTime } from '../getNetworkTime';
export type SnodeSigParamsShared = {
namespace: number | null | 'all'; // 'all' can be used to clear all namespaces (during account deletion)
method: 'retrieve' | 'store' | 'delete_all';
};
export type SnodeSigParamsAdminGroup = SnodeSigParamsShared & {
groupPk: GroupPubkeyType;
privKey: Uint8ArrayLen64; // len 64
};
export type SnodeSigParamsSubAccount = SnodeSigParamsShared & {
groupPk: GroupPubkeyType;
authData: Uint8ArrayLen100; // len 100
};
export type SnodeSigParamsUs = SnodeSigParamsShared & {
pubKey: string;
privKey: Uint8ArrayLen64; // len 64
};
function getVerificationDataForStoreRetrieve(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,
};
}
function isSigParamsForGroupAdmin(
sigParams: SnodeSigParamsAdminGroup | SnodeSigParamsUs | SnodeSigParamsSubAccount
): sigParams is SnodeSigParamsAdminGroup {
const asGr = sigParams as SnodeSigParamsAdminGroup;
return PubKey.isClosedGroupV2(asGr.groupPk) && !isEmpty(asGr.privKey);
}
async function getSnodeSignatureShared(params: SnodeSigParamsAdminGroup | SnodeSigParamsUs) {
const { signatureTimestamp, toSign } = getVerificationDataForStoreRetrieve(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;
}
}
export const SignatureShared = {
getSnodeSignatureShared,
getVerificationDataForStoreRetrieve,
};

@ -1,15 +1,13 @@
import { GroupPubkeyType } from 'libsession_util_nodejs';
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 { SnodeNamespacesGroup } from './namespaces';
type WithTimestamp = { timestamp: number };
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;
@ -17,14 +15,6 @@ export type SnodeSignatureResult = WithTimestamp & {
pubkey: string; // this is the x25519 key of the pubkey we are doing the request to (ourself for our swarm usually)
};
type ShortenOrExtend = 'extend' | 'shorten' | '';
type WithShortenOrExtend = { shortenOrExtend: ShortenOrExtend };
type WithMessagesHashes = { messagesHashes: Array<string> };
export type SnodeGroupSignatureResult = Pick<SnodeSignatureResult, 'signature' | 'timestamp'> & {
pubkey: GroupPubkeyType; // this is the 03 pubkey of the corresponding group
};
async function getSnodeSignatureByHashesParams({
messagesHashes,
method,
@ -72,30 +62,44 @@ type SnodeSigParamsShared = {
type SnodeSigParamsAdminGroup = SnodeSigParamsShared & {
groupPk: GroupPubkeyType;
privKey: Uint8Array; // len 64
privKey: Uint8ArrayLen64; // len 64
};
type SnodeSigParamsSubAccount = SnodeSigParamsShared & {
groupPk: GroupPubkeyType;
authData: Uint8ArrayLen100; // len 100
};
type SnodeSigParamsUs = SnodeSigParamsShared & {
pubKey: string;
privKey: Uint8Array; // len 64
privKey: Uint8ArrayLen64; // len 64
};
function isSigParamsForGroupAdmin(
sigParams: SnodeSigParamsAdminGroup | SnodeSigParamsUs
sigParams: SnodeSigParamsAdminGroup | SnodeSigParamsUs | SnodeSigParamsSubAccount
): sigParams is SnodeSigParamsAdminGroup {
const asGr = sigParams as SnodeSigParamsAdminGroup;
return PubKey.isClosedGroupV2(asGr.groupPk) && !!asGr.privKey;
return PubKey.isClosedGroupV2(asGr.groupPk) && !isEmpty(asGr.privKey);
}
async function getSnodeSignatureShared(params: SnodeSigParamsAdminGroup | SnodeSigParamsUs) {
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 message = new Uint8Array(verificationData);
const sodium = await getSodiumRenderer();
const signature = sodium.crypto_sign_detached(message, params.privKey);
const signature = sodium.crypto_sign_detached(toSign, params.privKey);
const signatureBase64 = fromUInt8ArrayToBase64(signature);
if (isSigParamsForGroupAdmin(params)) {
return {
@ -145,26 +149,6 @@ async function getSnodeSignatureParamsUs({
};
}
async function getSnodeGroupSignatureParams({
groupIdentityPrivKey,
groupPk,
method,
namespace,
}: {
groupPk: GroupPubkeyType;
groupIdentityPrivKey: Uint8Array; // len 64
namespace: SnodeNamespacesGroup;
method: 'retrieve' | 'store';
}): Promise<SnodeGroupSignatureResult> {
const sigData = await getSnodeSignatureShared({
pubKey: groupPk,
method,
namespace,
privKey: groupIdentityPrivKey,
});
return { ...sigData, pubkey: groupPk };
}
async function generateUpdateExpirySignature({
shortenOrExtend,
timestamp,
@ -220,38 +204,8 @@ async function generateUpdateExpiryOurSignature({
ed25519Pubkey: ourEd25519Key.pubKey,
});
}
async function generateUpdateExpiryGroupSignature({
shortenOrExtend,
timestamp,
messagesHashes,
groupPrivKey,
groupPk,
}: WithMessagesHashes &
WithShortenOrExtend &
WithTimestamp & {
groupPk: GroupPubkeyType;
groupPrivKey: Uint8Array; // len 64
}) {
if (isEmpty(groupPrivKey) || isEmpty(groupPk)) {
throw new PreConditionFailed(
'generateUpdateExpiryGroupSignature groupPrivKey or groupPk is empty'
);
}
return generateUpdateExpirySignature({
messagesHashes,
shortenOrExtend,
timestamp,
ed25519Privkey: groupPrivKey,
ed25519Pubkey: groupPk,
});
}
export const SnodeSignature = {
getSnodeSignatureParamsUs,
getSnodeGroupSignatureParams,
getSnodeSignatureByHashesParams,
generateUpdateExpiryOurSignature,
generateUpdateExpiryGroupSignature,
};

@ -25,3 +25,8 @@ export type RetrieveRequestResult = {
};
export type RetrieveMessagesResultsBatched = Array<RetrieveRequestResult>;
export type WithTimestamp = { timestamp: number };
export type ShortenOrExtend = 'extend' | 'shorten' | '';
export type WithShortenOrExtend = { shortenOrExtend: ShortenOrExtend };
export type WithMessagesHashes = { messagesHashes: Array<string> };

@ -1,51 +0,0 @@
import { GroupMemberGet, GroupPubkeyType, Uint8ArrayLen64 } from 'libsession_util_nodejs';
import { compact } from 'lodash';
import { MetaGroupWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
import { GetNetworkTime } from '../../apis/snode_api/getNetworkTime';
import { GroupUpdateInviteMessage } from '../../messages/outgoing/controlMessage/group_v2/to_user/GroupUpdateInviteMessage';
import { UserUtils } from '../../utils';
import { getSodiumRenderer } from '../MessageEncrypter';
export async function getGroupInvitesMessages({
groupName,
membersFromWrapper,
secretKey,
groupPk,
}: {
membersFromWrapper: Array<GroupMemberGet>;
groupName: string;
secretKey: Uint8ArrayLen64; // len 64
groupPk: GroupPubkeyType;
}) {
const sodium = await getSodiumRenderer();
const timestamp = GetNetworkTime.getNowWithNetworkOffset();
const inviteDetails = compact(
await Promise.all(
membersFromWrapper.map(async ({ pubkeyHex: member }) => {
if (UserUtils.isUsFromCache(member)) {
return null;
}
const tosign = `INVITE${member}${timestamp}`;
// Note: as the signature is built with the timestamp here, we cannot override the timestamp later on the sending pipeline
const adminSignature = sodium.crypto_sign_detached(tosign, secretKey);
console.info(`before makeSwarmSubAccount ${groupPk}:${member}`);
const memberAuthData = await MetaGroupWrapperActions.makeSwarmSubAccount(groupPk, member);
debugger;
console.info(`after makeSwarmSubAccount ${groupPk}:${member}`);
const invite = new GroupUpdateInviteMessage({
groupName,
groupPk,
timestamp,
adminSignature,
memberAuthData,
});
return { member, invite };
})
)
);
return inviteDetails;
}

@ -3,7 +3,7 @@
import { AbortController } from 'abort-controller';
import ByteBuffer from 'bytebuffer';
import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import _, { isEmpty, isNil, sample, toNumber } from 'lodash';
import _, { isEmpty, sample, toNumber } from 'lodash';
import pRetry from 'p-retry';
import { Data } from '../../data/data';
import { SignalService } from '../../protobuf';
@ -22,8 +22,13 @@ import {
} from '../apis/snode_api/SnodeRequestTypes';
import { GetNetworkTime } from '../apis/snode_api/getNetworkTime';
import { SnodeNamespace, SnodeNamespaces } from '../apis/snode_api/namespaces';
import {
SigResultAdmin,
SigResultSubAccount,
SnodeGroupSignature,
} from '../apis/snode_api/signature/groupSignature';
import { SnodeSignature, SnodeSignatureResult } from '../apis/snode_api/signature/snodeSignatures';
import { getSwarmFor } from '../apis/snode_api/snodePool';
import { SnodeSignature } from '../apis/snode_api/snodeSignatures';
import { SnodeAPIStore } from '../apis/snode_api/storeMessage';
import { ConvoHub } from '../conversations';
import { MessageEncrypter } from '../crypto';
@ -184,14 +189,20 @@ async function send(
);
}
async function getSignatureParamsFromNamespace(item: StoreOnNodeParamsNoSig, destination: string) {
async function getSignatureParamsFromNamespace(
item: StoreOnNodeParamsNoSig,
destination: string
): Promise<SigResultSubAccount | SigResultAdmin | SnodeSignatureResult | object> {
const store = 'store' as const;
if (SnodeNamespace.isUserConfigNamespace(item.namespace)) {
const ourPrivKey = (await UserUtils.getUserED25519KeyPairBytes())?.privKeyBytes;
if (!ourPrivKey) {
throw new Error('sendMessagesDataToSnode UserUtils.getUserED25519KeyPairBytes is empty');
throw new Error(
'getSignatureParamsFromNamespace UserUtils.getUserED25519KeyPairBytes is empty'
);
}
return SnodeSignature.getSnodeSignatureParamsUs({
method: 'store' as const,
method: store,
namespace: item.namespace,
});
}
@ -201,20 +212,18 @@ async function getSignatureParamsFromNamespace(item: StoreOnNodeParamsNoSig, des
item.namespace === SnodeNamespaces.ClosedGroupMessages
) {
if (!PubKey.isClosedGroupV2(destination)) {
throw new Error('sendMessagesDataToSnode: groupconfig namespace required a 03 pubkey');
}
const group = await UserGroupsWrapperActions.getGroup(destination);
const groupSecretKey = group?.secretKey; // TODO we will need to the user auth at some point
if (isNil(groupSecretKey) || isEmpty(groupSecretKey)) {
throw new Error(`sendMessagesDataToSnode: failed to find group admin secret key in wrapper`);
throw new Error(
'getSignatureParamsFromNamespace: groupconfig namespace required a 03 pubkey'
);
}
return SnodeSignature.getSnodeGroupSignatureParams({
method: 'store' as const,
const found = await UserGroupsWrapperActions.getGroup(destination);
return SnodeGroupSignature.getSnodeGroupSignature({
method: store,
namespace: item.namespace,
groupPk: destination,
groupIdentityPrivKey: groupSecretKey,
group: found,
});
}
// no signature required for this namespace/pubkey combo
return {};
}

@ -1,4 +1,3 @@
/* eslint-disable no-useless-return */
/* eslint-disable consistent-return */
/* eslint-disable no-promise-executor-return */

@ -83,7 +83,7 @@ export async function getUserED25519KeyPair(): Promise<HexKeyPair | undefined> {
return undefined;
}
export const getUserED25519KeyPairBytes = async (): Promise<ByteKeyPair | undefined> => {
export const getUserED25519KeyPairBytes = async (): Promise<ByteKeyPair> => {
// 'identityKey' keeps the ed25519KeyPair under a ed25519KeyPair field.
// it is only set if the user migrated to the ed25519 way of generating a key
const item = await UserUtils.getIdentityKeyPair();
@ -96,7 +96,7 @@ export const getUserED25519KeyPairBytes = async (): Promise<ByteKeyPair | undefi
privKeyBytes,
};
}
return undefined;
throw new Error('getUserED25519KeyPairBytes: user has no keypair');
};
export function getOurProfile(): LokiProfile | undefined {

@ -13,8 +13,8 @@ import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { HexString } from '../../node/hexStrings';
import { getSwarmPollingInstance } from '../../session/apis/snode_api';
import { SnodeNamespaces } from '../../session/apis/snode_api/namespaces';
import { SnodeGroupSignature } from '../../session/apis/snode_api/signature/groupSignature';
import { ConvoHub } from '../../session/conversations';
import { getGroupInvitesMessages } from '../../session/crypto/group/groupSignature';
import { getMessageQueue } from '../../session/sending';
import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils';
@ -130,6 +130,9 @@ const initNewGroupInWrapper = createAsyncThunk(
);
}
// now that we've added members to the group, make sure to make a full key rotation
// to include them and marks the corresponding wrappers as dirty
await MetaGroupWrapperActions.keyRekey(groupPk);
const convo = await ConvoHub.use().getOrCreateAndWait(groupPk, ConversationTypeEnum.GROUPV2);
await convo.setIsApproved(true, false);
@ -148,7 +151,7 @@ const initNewGroupInWrapper = createAsyncThunk(
await openConversationWithMessages({ conversationKey: groupPk, messageId: null });
// everything is setup for this group, we now need to send the invites to every members, privately and asynchronously, and gracefully handle errors with toasts.
const inviteDetails = await getGroupInvitesMessages({
const inviteDetails = await SnodeGroupSignature.getGroupInvitesMessages({
groupName,
membersFromWrapper,
secretKey: groupSecretKey,
@ -161,7 +164,6 @@ const initNewGroupInWrapper = createAsyncThunk(
namespace: SnodeNamespaces.Default,
pubkey: PubKey.cast(detail.member),
});
console.log(`sending invite message to ${detail.member}`);
});
return { groupPk: newGroup.pubkeyHex, infos, members: membersFromWrapper };

@ -5,7 +5,8 @@ import { HexString } from '../../../../node/hexStrings';
import { getSodiumNode } from '../../../../node/sodiumNode';
import { GetNetworkTime } from '../../../../session/apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../../../../session/apis/snode_api/namespaces';
import { SnodeSignature } from '../../../../session/apis/snode_api/snodeSignatures';
import { SnodeGroupSignature } from '../../../../session/apis/snode_api/signature/groupSignature';
import { SnodeSignature } from '../../../../session/apis/snode_api/signature/snodeSignatures';
import { concatUInt8Array } from '../../../../session/crypto';
import { UserUtils } from '../../../../session/utils';
import { fromBase64ToArray, fromHexToArray } from '../../../../session/utils/String';
@ -47,18 +48,21 @@ describe('SnodeSignature', () => {
Sinon.restore();
});
describe('getSnodeGroupSignatureParams', () => {
describe('getSnodeGroupAdminSignatureParams', () => {
beforeEach(() => {
Sinon.stub(GetNetworkTime, 'getNowWithNetworkOffset').returns(hardcodedTimestamp);
});
describe('retrieve', () => {
it('retrieve namespace ClosedGroupInfo', async () => {
const ret = await SnodeSignature.getSnodeGroupSignatureParams({
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
method: 'retrieve',
namespace: SnodeNamespaces.ClosedGroupInfo,
groupIdentityPrivKey: privKeyUint,
groupPk: validGroupPk,
group: {
authData: null,
pubkeyHex: validGroupPk,
secretKey: privKeyUint,
},
});
expect(ret.pubkey).to.be.eq(validGroupPk);
@ -68,11 +72,14 @@ describe('SnodeSignature', () => {
});
it('retrieve namespace ClosedGroupKeys', async () => {
const ret = await SnodeSignature.getSnodeGroupSignatureParams({
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
method: 'retrieve',
namespace: SnodeNamespaces.ClosedGroupKeys,
groupIdentityPrivKey: privKeyUint,
groupPk: validGroupPk,
group: {
authData: null,
pubkeyHex: validGroupPk,
secretKey: privKeyUint,
},
});
expect(ret.pubkey).to.be.eq(validGroupPk);
@ -83,11 +90,14 @@ describe('SnodeSignature', () => {
});
it('retrieve namespace ClosedGroupMessages', async () => {
const ret = await SnodeSignature.getSnodeGroupSignatureParams({
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
method: 'retrieve',
namespace: SnodeNamespaces.ClosedGroupMessages,
groupIdentityPrivKey: privKeyUint,
groupPk: validGroupPk,
group: {
authData: null,
pubkeyHex: validGroupPk,
secretKey: privKeyUint,
},
});
expect(ret.pubkey).to.be.eq(validGroupPk);
@ -99,11 +109,14 @@ describe('SnodeSignature', () => {
describe('store', () => {
it('store namespace ClosedGroupInfo', async () => {
const ret = await SnodeSignature.getSnodeGroupSignatureParams({
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
method: 'store',
namespace: SnodeNamespaces.ClosedGroupInfo,
groupIdentityPrivKey: privKeyUint,
groupPk: validGroupPk,
group: {
authData: null,
pubkeyHex: validGroupPk,
secretKey: privKeyUint,
},
});
expect(ret.pubkey).to.be.eq(validGroupPk);
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
@ -113,11 +126,14 @@ describe('SnodeSignature', () => {
});
it('store namespace ClosedGroupKeys', async () => {
const ret = await SnodeSignature.getSnodeGroupSignatureParams({
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
method: 'store',
namespace: SnodeNamespaces.ClosedGroupKeys,
groupIdentityPrivKey: privKeyUint,
groupPk: validGroupPk,
group: {
authData: null,
pubkeyHex: validGroupPk,
secretKey: privKeyUint,
},
});
expect(ret.pubkey).to.be.eq(validGroupPk);
@ -127,11 +143,14 @@ describe('SnodeSignature', () => {
});
it('store namespace ClosedGroupMessages', async () => {
const ret = await SnodeSignature.getSnodeGroupSignatureParams({
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
method: 'store',
namespace: SnodeNamespaces.ClosedGroupMessages,
groupIdentityPrivKey: privKeyUint,
groupPk: validGroupPk,
group: {
authData: null,
pubkeyHex: validGroupPk,
secretKey: privKeyUint,
},
});
expect(ret.pubkey).to.be.eq(validGroupPk);
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
@ -141,12 +160,104 @@ describe('SnodeSignature', () => {
});
});
// describe('getSnodeGroupSubAccountSignatureParams', () => {
// beforeEach(() => {
// Sinon.stub(GetNetworkTime, 'getNowWithNetworkOffset').returns(hardcodedTimestamp);
// });
// describe('retrieve', () => {
// it('retrieve namespace ClosedGroupInfo', async () => {
// const ret = await SnodeSignature.getSnodeGroupSignatureParams({
// method: 'retrieve',
// namespace: SnodeNamespaces.ClosedGroupInfo,
// groupPk: validGroupPk,
// groupIdentityPrivKey: privKeyUint,
// });
// expect(ret.pubkey).to.be.eq(validGroupPk);
// expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
// const verificationData = `retrieve${SnodeNamespaces.ClosedGroupInfo}${hardcodedTimestamp}`;
// await verifySig(ret, verificationData);
// });
// it('retrieve namespace ClosedGroupKeys', async () => {
// const ret = await SnodeSignature.getSnodeGroupSignatureParams({
// method: 'retrieve',
// namespace: SnodeNamespaces.ClosedGroupKeys,
// groupIdentityPrivKey: privKeyUint,
// groupPk: validGroupPk,
// });
// expect(ret.pubkey).to.be.eq(validGroupPk);
// expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
// const verificationData = `retrieve${SnodeNamespaces.ClosedGroupKeys}${hardcodedTimestamp}`;
// await verifySig(ret, verificationData);
// });
// it('retrieve namespace ClosedGroupMessages', async () => {
// const ret = await SnodeSignature.getSnodeGroupSignatureParams({
// method: 'retrieve',
// namespace: SnodeNamespaces.ClosedGroupMessages,
// groupIdentityPrivKey: privKeyUint,
// groupPk: validGroupPk,
// });
// expect(ret.pubkey).to.be.eq(validGroupPk);
// expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
// const verificationData = `retrieve${SnodeNamespaces.ClosedGroupMessages}${hardcodedTimestamp}`;
// await verifySig(ret, verificationData);
// });
// });
// describe('store', () => {
// it('store namespace ClosedGroupInfo', async () => {
// const ret = await SnodeSignature.getSnodeGroupSignatureParams({
// method: 'store',
// namespace: SnodeNamespaces.ClosedGroupInfo,
// groupIdentityPrivKey: privKeyUint,
// groupPk: validGroupPk,
// });
// expect(ret.pubkey).to.be.eq(validGroupPk);
// expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
// const verificationData = `store${SnodeNamespaces.ClosedGroupInfo}${hardcodedTimestamp}`;
// await verifySig(ret, verificationData);
// });
// it('store namespace ClosedGroupKeys', async () => {
// const ret = await SnodeSignature.getSnodeGroupSubAccountSignatureParams({
// method: 'store',
// namespace: SnodeNamespaces.ClosedGroupKeys,
// groupIdentityPrivKey: privKeyUint,
// groupPk: validGroupPk,
// });
// expect(ret.pubkey).to.be.eq(validGroupPk);
// expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
// const verificationData = `store${SnodeNamespaces.ClosedGroupKeys}${hardcodedTimestamp}`;
// await verifySig(ret, verificationData);
// });
// it('store namespace ClosedGroupMessages', async () => {
// const ret = await SnodeSignature.getSnodeGroupSubAccountSignatureParams({
// method: 'store',
// namespace: SnodeNamespaces.ClosedGroupMessages,
// groupPk: validGroupPk,
// });
// expect(ret.groupPk).to.be.eq(validGroupPk);
// expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
// const verificationData = `store${SnodeNamespaces.ClosedGroupMessages}${hardcodedTimestamp}`;
// await verifySig(ret, verificationData);
// });
// });
// });
describe('generateUpdateExpiryGroupSignature', () => {
it('throws if groupPk not given', async () => {
const func = async () => {
return SnodeSignature.generateUpdateExpiryGroupSignature({
groupPk: null as any,
groupPrivKey: privKeyUint,
return SnodeGroupSignature.generateUpdateExpiryGroupSignature({
group: { pubkeyHex: null as any, secretKey: privKeyUint, authData: null },
messagesHashes: ['[;p['],
shortenOrExtend: '',
timestamp: hardcodedTimestamp,
@ -159,9 +270,13 @@ describe('SnodeSignature', () => {
it('throws if groupPrivKey is empty', async () => {
const func = async () => {
return SnodeSignature.generateUpdateExpiryGroupSignature({
groupPk: validGroupPk,
groupPrivKey: new Uint8Array() as any,
return SnodeGroupSignature.generateUpdateExpiryGroupSignature({
group: {
pubkeyHex: validGroupPk as any,
secretKey: new Uint8Array() as any,
authData: null,
},
messagesHashes: ['[;p['],
shortenOrExtend: '',
timestamp: hardcodedTimestamp,
@ -176,9 +291,8 @@ describe('SnodeSignature', () => {
const hashes = ['hash4321', 'hash4221'];
const timestamp = hardcodedTimestamp;
const shortenOrExtend = '';
const ret = await SnodeSignature.generateUpdateExpiryGroupSignature({
groupPk: validGroupPk,
groupPrivKey: privKeyUint,
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
messagesHashes: hashes,
shortenOrExtend: '',
timestamp,
@ -194,9 +308,8 @@ describe('SnodeSignature', () => {
const hashes = ['hash4321', 'hash4221'];
const timestamp = hardcodedTimestamp;
const shortenOrExtend = '';
const ret = await SnodeSignature.generateUpdateExpiryGroupSignature({
groupPk: validGroupPk,
groupPrivKey: privKeyUint,
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
messagesHashes: hashes,
shortenOrExtend: '',
timestamp,
@ -213,9 +326,8 @@ describe('SnodeSignature', () => {
const hashes = ['hash4321', 'hash4221'];
const timestamp = hardcodedTimestamp;
const shortenOrExtend = '';
const ret = await SnodeSignature.generateUpdateExpiryGroupSignature({
groupPk: validGroupPk,
groupPrivKey: privKeyUint,
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
messagesHashes: hashes,
shortenOrExtend: '',
timestamp,
@ -234,9 +346,8 @@ describe('SnodeSignature', () => {
const hashes = ['hash4321', 'hash4221'];
const timestamp = hardcodedTimestamp;
const shortenOrExtend = '';
const ret = await SnodeSignature.generateUpdateExpiryGroupSignature({
groupPk: validGroupPk,
groupPrivKey: privKeyUint,
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
messagesHashes: hashes,
shortenOrExtend: '',
timestamp,

@ -13,6 +13,7 @@ import {
MetaGroupWrapperActionsCalls,
ProfilePicture,
PubkeyType,
Uint8ArrayLen100,
UserConfigWrapperActionsCalls,
UserGroupsSet,
UserGroupsWrapperActionsCalls,
@ -505,6 +506,17 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = {
'makeSwarmSubAccount',
memberPubkeyHex,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['makeSwarmSubAccount']>>,
swarmSubaccountSign: async (
groupPk: GroupPubkeyType,
message: Uint8Array,
authData: Uint8ArrayLen100
) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'swarmSubaccountSign',
message,
authData,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['swarmSubaccountSign']>>,
};
export const callLibSessionWorker = async (

Loading…
Cancel
Save