Skip to content
Merged
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
38 changes: 38 additions & 0 deletions pkg/e2ee/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,51 @@ func (m *Manager) DecryptMessageV2(msg *line.Message) (string, error) {
if err != nil {
return "", err
}

ver := 2
if vStr, ok := msg.ContentMetadata["e2eeVersion"]; ok && vStr == "1" {
ver = 1
}

if ver == 1 {
cipher, err := assembleCipherV1(msg.Chunks)

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'cipher' is assigned on line 324 from assembleCipher(msg.Chunks) but then shadowed on line 339 within the v1 block by declaring a new cipher variable with assembleCipherV1(msg.Chunks). This creates unnecessary confusion and wastes the initial computation. The first cipher assignment should be moved inside the 'else' or 'if ver == 2' block to avoid this shadowing and unnecessary computation when ver is 1.

Copilot uses AI. Check for mistakes.
if err != nil {
return "", err
}
pt, _, err := m.runner.ChannelDecryptV1(chanID, senderKeyID, receiverKeyID, base64.StdEncoding.EncodeToString(cipher))
if err != nil {
return "", err
}
return pt, nil
}

pt, _, err := m.runner.ChannelDecryptV2(chanID, msg.To, msg.From, senderKeyID, receiverKeyID, msg.ContentType, base64.StdEncoding.EncodeToString(cipher))
if err != nil {
return "", err
}
return pt, nil
}

func assembleCipherV1(chunks []string) ([]byte, error) {
if len(chunks) < 3 {
return nil, fmt.Errorf("not enough chunks")
}
decode := func(s string) ([]byte, error) { return base64.StdEncoding.DecodeString(strings.TrimSpace(s)) }
b0, err := decode(chunks[0])
if err != nil {
return nil, err
}
b1, err := decode(chunks[1])
if err != nil {
return nil, err
}
b2, err := decode(chunks[2])
if err != nil {
return nil, err
}
return append(append(b0, b1...), b2...), nil
Comment on lines +357 to +374

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assembleCipherV1 function concatenates chunks in the order [first, middle, tag] which differs from assembleCipher (v2) that reorders them to [first, tag, middle] as noted in its comment. Please verify that this difference in chunk ordering is intentional for the v1 protocol. If this is intentional, consider adding a comment explaining the chunk order for v1, similar to the comment in assembleCipher.

Copilot uses AI. Check for mistakes.
}

func (m *Manager) UnwrapGroupSharedKey(chatMid string, sharedKey *line.E2EEGroupSharedKey) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
Expand Down
15 changes: 15 additions & 0 deletions pkg/internal/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,21 @@ async function run() {
process.stdout.write(
JSON.stringify({ ciphertext: u8ToB64(ct) }) + "\n"
);
} else if (type === "channel_decrypt_v1") { // messages sent from ios LINE app use v1

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comment should capitalize "iOS" as it's a proper noun. It should read: "messages sent from iOS LINE app use v1"

Suggested change
} else if (type === "channel_decrypt_v1") { // messages sent from ios LINE app use v1
} else if (type === "channel_decrypt_v1") { // messages sent from iOS LINE app use v1

Copilot uses AI. Check for mistakes.
const {
channelId,
ciphertext,
} = query;
Comment on lines +402 to +405

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The senderKeyId and receiverKeyId parameters are extracted from the query but never used. If these parameters are not needed for v1 decryption, they should be removed from the query destructuring. If they are needed, they should be passed to the decryptV1 method or validated.

Copilot uses AI. Check for mistakes.
const chan = getChannel(toIntStrict("channelId", channelId));

const u8Cipher = b64ToU8(ciphertext);
const pt = chan.decryptV1(
u8Cipher
);
const plaintext = new TextDecoder().decode(Uint8Array.from(pt).buffer);
process.stdout.write(
JSON.stringify({ plaintext, base64: u8ToB64(pt) }) + "\n"
);
} else if (type === "channel_decrypt_v2") {
const {
channelId,
Expand Down
22 changes: 22 additions & 0 deletions pkg/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,28 @@ func (r *Runner) ChannelEncryptV2(channelID int, to, from string, senderKeyID, r
return ct, nil
}

// decrypts ciphertext with channel v1 (ios)

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should follow Go documentation conventions. It should start with the function name and use proper capitalization: "ChannelDecryptV1 decrypts ciphertext with channel v1 (iOS)." Note that "iOS" should be capitalized as it's a proper noun.

Suggested change
// decrypts ciphertext with channel v1 (ios)
// ChannelDecryptV1 decrypts ciphertext with channel v1 (iOS).

Copilot uses AI. Check for mistakes.
func (r *Runner) ChannelDecryptV1(channelID, senderKeyID, receiverKeyID int, ciphertext string) (string, string, error) {
resp, err := r.call(map[string]any{
"type": "channel_decrypt_v1",
"channelId": channelID,
"senderKeyId": senderKeyID,
"receiverKeyId": receiverKeyID,
"ciphertext": ciphertext,
})
if err != nil {
return "", "", err
}
var plaintext, base64 string
if v, ok := resp["plaintext"]; ok {
_ = json.Unmarshal(v, &plaintext)
}
if v, ok := resp["base64"]; ok {
_ = json.Unmarshal(v, &base64)
}
return plaintext, base64, nil
}

// decrypts ciphertext with channel v2
func (r *Runner) ChannelDecryptV2(channelID int, to, from string, senderKeyID, receiverKeyID, contentType int, ciphertext string) (string, string, error) {
resp, err := r.call(map[string]any{
Expand Down
Loading