Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ Proxy
messages*.json
test.ts
TestData
wa-logs.txt
wa-logs.txt
.claude
59 changes: 49 additions & 10 deletions src/Socket/messages-recv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
let sendActiveReceipts = false;

const sendMessageAck = async ({ tag, attrs, content }: BinaryNode) => {
if (ws.readyState !== ws.OPEN) {
logger.debug({ tag, id: attrs.id }, "connection closed, skipping ack");
return;
}

const stanza: BinaryNode = {
tag: "ack",
attrs: {
Expand Down Expand Up @@ -407,20 +412,54 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
node,
"link_code_companion_reg"
);
const ref = toRequiredBuffer(
getBinaryNodeChildBuffer(
linkCodeCompanionReg,
"link_code_pairing_ref"
)
if (!linkCodeCompanionReg) {
logger.warn(
"Received link_code_companion_reg notification without node content"
);
break;
}
if (
!authState.creds.pairingCode ||
!authState.creds.pairingEphemeralKeyPair
) {
logger.warn(
"Received link_code_companion_reg but pairing credentials are missing"
);
break;
}
const refBuffer = getBinaryNodeChildBuffer(
linkCodeCompanionReg,
"link_code_pairing_ref"
);
const primaryIdentityPubBuffer = getBinaryNodeChildBuffer(
linkCodeCompanionReg,
"primary_identity_pub"
);
const primaryEphemeralPubWrappedBuffer = getBinaryNodeChildBuffer(
linkCodeCompanionReg,
"link_code_pairing_wrapped_primary_ephemeral_pub"
);
if (
!refBuffer ||
!primaryIdentityPubBuffer ||
!primaryEphemeralPubWrappedBuffer
) {
logger.warn(
{
hasRef: !!refBuffer,
hasPrimaryIdentityPub: !!primaryIdentityPubBuffer,
hasPrimaryEphemeralPubWrapped: !!primaryEphemeralPubWrappedBuffer
},
"Received incomplete link_code_companion_reg notification"
);
break;
}
const ref = toRequiredBuffer(refBuffer);
const primaryIdentityPublicKey = toRequiredBuffer(
getBinaryNodeChildBuffer(linkCodeCompanionReg, "primary_identity_pub")
primaryIdentityPubBuffer
);
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(
getBinaryNodeChildBuffer(
linkCodeCompanionReg,
"link_code_pairing_wrapped_primary_ephemeral_pub"
)
primaryEphemeralPubWrappedBuffer
);
const codePairingPublicKey = await decipherLinkPublicKey(
primaryEphemeralPublicKeyWrapped
Expand Down
85 changes: 60 additions & 25 deletions src/Socket/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
INITIAL_PREKEY_COUNT,
MIN_PREKEY_COUNT
} from "../Defaults";
import { DisconnectReason, SocketConfig } from "../Types";
import { DisconnectReason, KeyPair, SocketConfig } from "../Types";
import {
addTransactionCapability,
aesEncryptCTR,
Expand Down Expand Up @@ -40,6 +40,16 @@ import {
} from "../WABinary";
import { randomBytes } from "crypto";

// Map custom platform names to valid OS names accepted by WhatsApp
const VALID_OS_MAP: { [key: string]: string } = {
Baileys: "Windows",
baileys: "Windows"
};

const getPlatformOS = (platform: string): string => {
return VALID_OS_MAP[platform] || platform;
};

/**
* Connects to WA servers and performs:
* - simple queries (no retry mechanism, wait for connection establishment)
Expand Down Expand Up @@ -500,22 +510,33 @@ export const makeSocket = ({

const requestPairingCode = async (
phoneNumber: string,
customPairingCode?: string
customPairingCode?: string,
showPushNotification: boolean = true
): Promise<string> => {
const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5));
// Gerar novo ephemeral key pair para este pairing (igual ao Go)
const pairingEphemeralKeyPair = Curve.generateKeyPair();

if (customPairingCode && customPairingCode?.length !== 8) {
throw new Boom("Custom pairing code must be exactly 8 chars");
// Gerar o linking code (5 bytes -> crockford base32 = 8 chars)
if (customPairingCode && customPairingCode.length !== 8) {
throw new Boom("Custom pairing code must be exactly 8 characters");
}
const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5));

const jid = jidEncode(phoneNumber, "s.whatsapp.net");

// Armazenar no estado para uso posterior no pair-success
authState.creds.pairingCode = pairingCode;
authState.creds.me = {
id: jidEncode(phoneNumber, "s.whatsapp.net"),
name: "~"
};
authState.creds.pairingEphemeralKeyPair = pairingEphemeralKeyPair;
authState.creds.me = { id: jid, name: "~" };
ev.emit("creds.update", authState.creds);

await sendNode({
// Gerar a chave empacotada (salt + iv + encrypted pubkey)
const ephemeralKey = await generatePairingKey(
pairingCode,
pairingEphemeralKeyPair
);

const resp = await query({
tag: "iq",
attrs: {
to: S_WHATSAPP_NET,
Expand All @@ -527,16 +548,16 @@ export const makeSocket = ({
{
tag: "link_code_companion_reg",
attrs: {
jid: authState.creds.me.id,
jid,
stage: "companion_hello",
// eslint-disable-next-line camelcase
should_show_push_notification: "true"
should_show_push_notification: showPushNotification.toString()
},
content: [
{
tag: "link_code_pairing_wrapped_companion_ephemeral_pub",
attrs: {},
content: await generatePairingKey()
content: ephemeralKey
},
{
tag: "companion_server_auth_key_pub",
Expand All @@ -551,31 +572,45 @@ export const makeSocket = ({
{
tag: "companion_platform_display",
attrs: {},
content: `${browser[1]} (${browser[0]})`
content: `${browser[1]} (${getPlatformOS(browser[0])})`
},
{
tag: "link_code_pairing_nonce",
attrs: {},
content: "0"
content: new Uint8Array([0])
}
]
}
]
});

return authState.creds.pairingCode;
// Ler o pairing_ref da resposta (igual ao Go)
const regNode = getBinaryNodeChild(resp, "link_code_companion_reg");
const pairingRefNode = getBinaryNodeChild(regNode, "link_code_pairing_ref");

if (!pairingRefNode) {
throw new Boom("link_code_pairing_ref not found in response");
}

const pairingRef = (pairingRefNode.content as Buffer).toString("utf-8");

// Armazenar para uso posterior no pair-success
authState.creds.pairingRef = pairingRef;
ev.emit("creds.update", authState.creds);

// Formato "XXXX-XXXX" igual ao Go
return pairingCode.slice(0, 4) + "-" + pairingCode.slice(4);
};

async function generatePairingKey() {
async function generatePairingKey(
pairingCode: string,
ephemeralKeyPair: KeyPair
) {
const salt = randomBytes(32);
const randomIv = randomBytes(16);
const key = await derivePairingCodeKey(authState.creds.pairingCode!, salt);
const ciphered = aesEncryptCTR(
authState.creds.pairingEphemeralKeyPair.public,
key,
randomIv
);
return Buffer.concat([salt, randomIv, ciphered]);
const iv = randomBytes(16);
const key = await derivePairingCodeKey(pairingCode, salt);
const encryptedPubkey = aesEncryptCTR(ephemeralKeyPair.public, key, iv);
return Buffer.concat([salt, iv, encryptedPubkey]);
}

ws.on("message", onMessageRecieved);
Expand Down
13 changes: 10 additions & 3 deletions src/Types/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,26 @@ export type AuthenticationCreds = SignalCreds & {
/** number of times history & app state has been synced */
accountSyncCounter: number;
accountSettings: AccountSettings;
pairingCode: string | undefined;
registered: boolean;
pairingCode: string | undefined;
pairingRef: string | undefined;
lastPropHash: string | undefined;
routingInfo: Buffer | undefined;
additionalData?: any | undefined;
};

export type SignalDataTypeMap = {
"pre-key": KeyPair;
session: any;
"sender-key": any;
session: Uint8Array;
"sender-key": Uint8Array;
"sender-key-memory": { [jid: string]: boolean };
"contacts-tc-token": { token: Buffer; timestamp?: string };
"app-state-sync-key": proto.Message.IAppStateSyncKeyData;
"app-state-sync-version": LTHashState;
"lid-mapping": string;
"device-list": string[];
tctoken: { token: Buffer; timestamp?: string };
"identity-key": Uint8Array;
};

export type SignalDataSet = {
Expand Down
1 change: 1 addition & 0 deletions src/Utils/auth-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export const initAuthCreds = (): AuthenticationCreds => {
unarchiveChats: false
},
pairingCode: undefined,
pairingRef: undefined,
registered: false,
lastPropHash: undefined
};
Expand Down