diff --git a/packages/convert/package.json b/packages/convert/package.json index 9b0fb642..9d35095e 100644 --- a/packages/convert/package.json +++ b/packages/convert/package.json @@ -10,10 +10,12 @@ "exports": "./src/index.ts", "dependencies": { "@mtcute/core": "workspace:^", - "@fuman/utils": "https://pkg.pr.new/teidesu/fuman/@fuman/utils@6017eb4", - "@fuman/net": "https://pkg.pr.new/teidesu/fuman/@fuman/net@6017eb4" + "@fuman/utils": "https://pkg.pr.new/teidesu/fuman/@fuman/utils@b0c74cb", + "@fuman/net": "https://pkg.pr.new/teidesu/fuman/@fuman/net@b0c74cb", + "@fuman/io": "https://pkg.pr.new/teidesu/fuman/@fuman/io@b0c74cb" }, "devDependencies": { - "@mtcute/test": "workspace:^" + "@mtcute/test": "workspace:^", + "@mtcute/node": "workspace:^" } } diff --git a/packages/convert/src/index.ts b/packages/convert/src/index.ts index f989f7a8..efa91ec9 100644 --- a/packages/convert/src/index.ts +++ b/packages/convert/src/index.ts @@ -3,3 +3,4 @@ export * from './gramjs/index.js' export * from './mtkruto/index.js' export * from './pyrogram/index.js' export * from './telethon/index.js' +export * from './tdesktop/index.js' diff --git a/packages/convert/src/tdesktop/__fixtures__/multiacc/A7FDF864FBC10B77s b/packages/convert/src/tdesktop/__fixtures__/multiacc/A7FDF864FBC10B77s new file mode 100644 index 00000000..31e92220 Binary files /dev/null and b/packages/convert/src/tdesktop/__fixtures__/multiacc/A7FDF864FBC10B77s differ diff --git a/packages/convert/src/tdesktop/__fixtures__/multiacc/D877F783D5D3EF8Cs b/packages/convert/src/tdesktop/__fixtures__/multiacc/D877F783D5D3EF8Cs new file mode 100644 index 00000000..5804c37e Binary files /dev/null and b/packages/convert/src/tdesktop/__fixtures__/multiacc/D877F783D5D3EF8Cs differ diff --git a/packages/convert/src/tdesktop/__fixtures__/multiacc/key_datas b/packages/convert/src/tdesktop/__fixtures__/multiacc/key_datas new file mode 100644 index 00000000..a256631e Binary files /dev/null and b/packages/convert/src/tdesktop/__fixtures__/multiacc/key_datas differ diff --git a/packages/convert/src/tdesktop/__fixtures__/passcode/D877F783D5D3EF8Cs b/packages/convert/src/tdesktop/__fixtures__/passcode/D877F783D5D3EF8Cs new file mode 100644 index 00000000..5804c37e Binary files /dev/null and b/packages/convert/src/tdesktop/__fixtures__/passcode/D877F783D5D3EF8Cs differ diff --git a/packages/convert/src/tdesktop/__fixtures__/passcode/key_datas b/packages/convert/src/tdesktop/__fixtures__/passcode/key_datas new file mode 100644 index 00000000..fdd52a45 Binary files /dev/null and b/packages/convert/src/tdesktop/__fixtures__/passcode/key_datas differ diff --git a/packages/convert/src/tdesktop/__fixtures__/simple/D877F783D5D3EF8Cs b/packages/convert/src/tdesktop/__fixtures__/simple/D877F783D5D3EF8Cs new file mode 100644 index 00000000..5804c37e Binary files /dev/null and b/packages/convert/src/tdesktop/__fixtures__/simple/D877F783D5D3EF8Cs differ diff --git a/packages/convert/src/tdesktop/__fixtures__/simple/key_datas b/packages/convert/src/tdesktop/__fixtures__/simple/key_datas new file mode 100644 index 00000000..342588be Binary files /dev/null and b/packages/convert/src/tdesktop/__fixtures__/simple/key_datas differ diff --git a/packages/convert/src/tdesktop/__snapshots__/tdata.test.ts.snap b/packages/convert/src/tdesktop/__snapshots__/tdata.test.ts.snap new file mode 100644 index 00000000..3e51108c --- /dev/null +++ b/packages/convert/src/tdesktop/__snapshots__/tdata.test.ts.snap @@ -0,0 +1,5519 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`tdata > should read multi-account tdata 1`] = ` +{ + "auth0": { + "authKeys": [ + { + "dcId": 1, + "key": Uint8Array [ + 44, + 28, + 216, + 145, + 35, + 120, + 65, + 203, + 197, + 50, + 153, + 251, + 232, + 64, + 220, + 169, + 171, + 232, + 65, + 163, + 230, + 226, + 210, + 250, + 213, + 141, + 64, + 230, + 90, + 134, + 168, + 121, + 215, + 76, + 21, + 104, + 18, + 209, + 195, + 48, + 37, + 146, + 115, + 253, + 26, + 109, + 183, + 77, + 134, + 129, + 51, + 194, + 195, + 100, + 141, + 127, + 26, + 129, + 251, + 186, + 186, + 162, + 247, + 0, + 89, + 221, + 148, + 217, + 86, + 195, + 47, + 71, + 198, + 157, + 47, + 239, + 205, + 106, + 184, + 173, + 66, + 105, + 251, + 1, + 20, + 203, + 48, + 61, + 248, + 90, + 184, + 180, + 174, + 192, + 170, + 39, + 215, + 72, + 215, + 100, + 82, + 130, + 231, + 118, + 171, + 221, + 198, + 190, + 32, + 204, + 94, + 38, + 187, + 226, + 161, + 133, + 198, + 185, + 221, + 135, + 24, + 6, + 105, + 52, + 247, + 219, + 195, + 246, + 185, + 119, + 101, + 109, + 36, + 143, + 191, + 65, + 203, + 216, + 93, + 10, + 88, + 121, + 157, + 152, + 224, + 143, + 103, + 74, + 243, + 99, + 183, + 122, + 198, + 154, + 112, + 85, + 155, + 133, + 96, + 110, + 58, + 169, + 142, + 56, + 197, + 211, + 177, + 56, + 5, + 115, + 60, + 10, + 243, + 133, + 55, + 204, + 16, + 112, + 106, + 141, + 248, + 65, + 113, + 32, + 19, + 199, + 60, + 213, + 27, + 202, + 141, + 154, + 175, + 160, + 233, + 169, + 255, + 51, + 94, + 69, + 6, + 123, + 198, + 222, + 168, + 204, + 28, + 180, + 220, + 27, + 48, + 206, + 45, + 201, + 6, + 39, + 5, + 248, + 181, + 42, + 193, + 104, + 88, + 0, + 231, + 196, + 67, + 102, + 29, + 28, + 253, + 173, + 130, + 218, + 253, + 238, + 215, + 151, + 9, + 4, + 29, + 105, + 119, + 101, + 121, + 171, + 49, + 119, + 216, + 53, + 68, + 48, + 224, + 17, + 115, + 29, + ], + }, + { + "dcId": 2, + "key": Uint8Array [ + 73, + 77, + 244, + 157, + 119, + 147, + 12, + 196, + 245, + 200, + 23, + 94, + 249, + 171, + 93, + 102, + 187, + 25, + 155, + 172, + 1, + 115, + 72, + 228, + 40, + 135, + 83, + 253, + 179, + 49, + 175, + 230, + 185, + 194, + 224, + 112, + 178, + 125, + 77, + 8, + 38, + 143, + 21, + 14, + 134, + 31, + 138, + 169, + 128, + 113, + 1, + 191, + 178, + 12, + 87, + 83, + 8, + 45, + 103, + 118, + 67, + 111, + 20, + 213, + 188, + 26, + 239, + 59, + 8, + 238, + 26, + 179, + 28, + 90, + 210, + 243, + 23, + 141, + 159, + 136, + 61, + 146, + 129, + 13, + 190, + 189, + 241, + 213, + 104, + 201, + 18, + 27, + 202, + 124, + 214, + 60, + 243, + 225, + 238, + 173, + 70, + 255, + 207, + 139, + 148, + 177, + 88, + 227, + 72, + 88, + 173, + 216, + 119, + 231, + 110, + 129, + 155, + 35, + 151, + 121, + 133, + 203, + 37, + 251, + 226, + 0, + 118, + 173, + 15, + 201, + 85, + 101, + 198, + 56, + 142, + 110, + 50, + 21, + 19, + 10, + 35, + 255, + 253, + 215, + 107, + 92, + 28, + 208, + 0, + 51, + 55, + 81, + 48, + 247, + 183, + 24, + 213, + 52, + 94, + 114, + 204, + 198, + 110, + 247, + 102, + 103, + 156, + 13, + 241, + 1, + 89, + 131, + 226, + 142, + 93, + 185, + 83, + 181, + 86, + 67, + 15, + 168, + 39, + 253, + 124, + 203, + 251, + 110, + 102, + 163, + 92, + 231, + 29, + 171, + 62, + 72, + 159, + 233, + 251, + 185, + 9, + 59, + 234, + 172, + 133, + 226, + 112, + 82, + 23, + 151, + 158, + 85, + 66, + 124, + 254, + 138, + 81, + 141, + 223, + 212, + 205, + 107, + 97, + 153, + 126, + 228, + 45, + 179, + 26, + 79, + 26, + 76, + 241, + 194, + 176, + 202, + 153, + 201, + 241, + 43, + 249, + 82, + 101, + 154, + 93, + 161, + 247, + 221, + 244, + 147, + 109, + 45, + 216, + 215, + 218, + 52, + ], + }, + { + "dcId": 4, + "key": Uint8Array [ + 39, + 185, + 106, + 207, + 146, + 254, + 151, + 89, + 137, + 32, + 250, + 152, + 121, + 120, + 109, + 154, + 193, + 40, + 252, + 227, + 224, + 129, + 94, + 106, + 111, + 228, + 92, + 62, + 64, + 83, + 185, + 53, + 153, + 11, + 180, + 147, + 47, + 72, + 72, + 165, + 174, + 94, + 104, + 250, + 90, + 90, + 198, + 227, + 27, + 7, + 72, + 219, + 93, + 224, + 245, + 246, + 234, + 119, + 151, + 169, + 57, + 29, + 199, + 173, + 213, + 37, + 190, + 254, + 143, + 138, + 127, + 51, + 205, + 20, + 40, + 6, + 46, + 21, + 8, + 216, + 28, + 235, + 247, + 66, + 167, + 229, + 189, + 251, + 220, + 146, + 96, + 49, + 1, + 17, + 75, + 194, + 40, + 2, + 48, + 232, + 136, + 213, + 202, + 190, + 114, + 183, + 46, + 6, + 107, + 205, + 107, + 96, + 65, + 79, + 175, + 223, + 77, + 191, + 233, + 62, + 59, + 245, + 56, + 36, + 174, + 138, + 129, + 34, + 216, + 220, + 156, + 231, + 47, + 81, + 159, + 115, + 40, + 250, + 113, + 162, + 238, + 139, + 126, + 179, + 254, + 242, + 103, + 77, + 206, + 191, + 248, + 63, + 60, + 209, + 189, + 116, + 30, + 74, + 160, + 76, + 213, + 227, + 15, + 90, + 253, + 92, + 231, + 250, + 82, + 174, + 76, + 64, + 81, + 224, + 73, + 35, + 253, + 147, + 137, + 236, + 102, + 5, + 135, + 143, + 4, + 57, + 100, + 116, + 83, + 117, + 199, + 42, + 169, + 57, + 43, + 172, + 25, + 86, + 84, + 55, + 187, + 117, + 88, + 195, + 73, + 183, + 232, + 4, + 172, + 18, + 208, + 68, + 69, + 23, + 183, + 195, + 242, + 73, + 104, + 186, + 36, + 103, + 24, + 25, + 105, + 252, + 200, + 15, + 179, + 140, + 145, + 137, + 132, + 40, + 244, + 176, + 206, + 41, + 107, + 103, + 51, + 95, + 161, + 123, + 62, + 129, + 19, + 141, + 81, + 206, + 112, + 71, + 15, + 210, + 93, + 168, + ], + }, + { + "dcId": 5, + "key": Uint8Array [ + 73, + 36, + 12, + 6, + 143, + 43, + 71, + 187, + 119, + 132, + 128, + 73, + 63, + 44, + 111, + 107, + 165, + 125, + 202, + 207, + 232, + 156, + 236, + 81, + 149, + 152, + 105, + 168, + 188, + 58, + 142, + 238, + 182, + 205, + 113, + 166, + 200, + 35, + 21, + 170, + 133, + 186, + 43, + 191, + 211, + 111, + 71, + 142, + 19, + 241, + 182, + 231, + 243, + 167, + 95, + 209, + 99, + 121, + 249, + 2, + 7, + 119, + 96, + 1, + 57, + 240, + 76, + 86, + 2, + 38, + 123, + 55, + 99, + 184, + 103, + 223, + 103, + 193, + 46, + 21, + 233, + 161, + 200, + 30, + 145, + 253, + 44, + 138, + 186, + 95, + 120, + 16, + 189, + 6, + 168, + 143, + 87, + 162, + 170, + 181, + 50, + 165, + 237, + 62, + 193, + 75, + 128, + 236, + 19, + 73, + 96, + 11, + 184, + 177, + 238, + 96, + 90, + 206, + 146, + 114, + 78, + 20, + 177, + 164, + 57, + 158, + 64, + 129, + 147, + 15, + 75, + 159, + 37, + 130, + 233, + 154, + 164, + 80, + 8, + 114, + 188, + 220, + 76, + 167, + 57, + 220, + 129, + 139, + 174, + 113, + 97, + 85, + 105, + 140, + 30, + 156, + 183, + 71, + 142, + 47, + 167, + 170, + 133, + 16, + 119, + 252, + 139, + 189, + 52, + 82, + 40, + 164, + 65, + 178, + 184, + 218, + 30, + 236, + 220, + 5, + 126, + 33, + 177, + 96, + 83, + 158, + 152, + 10, + 90, + 178, + 95, + 143, + 214, + 111, + 7, + 13, + 1, + 128, + 162, + 127, + 44, + 123, + 238, + 251, + 49, + 2, + 234, + 237, + 151, + 203, + 165, + 46, + 219, + 94, + 143, + 21, + 97, + 238, + 171, + 174, + 13, + 115, + 250, + 76, + 193, + 247, + 27, + 77, + 201, + 111, + 98, + 88, + 175, + 46, + 84, + 119, + 96, + 255, + 157, + 109, + 240, + 174, + 54, + 220, + 150, + 135, + 250, + 34, + 157, + 61, + 241, + 231, + 36, + 124, + 195, + 216, + ], + }, + ], + "authKeysToDestroy": [], + "mainDcId": 1, + "userId": Long { + "high": 0, + "low": 1787945512, + "unsigned": false, + }, + }, + "auth1": { + "authKeys": [ + { + "dcId": 1, + "key": Uint8Array [ + 80, + 1, + 192, + 169, + 193, + 35, + 68, + 109, + 84, + 124, + 167, + 123, + 87, + 182, + 232, + 223, + 17, + 56, + 237, + 75, + 170, + 140, + 45, + 62, + 217, + 30, + 109, + 231, + 69, + 9, + 166, + 18, + 71, + 101, + 15, + 122, + 117, + 8, + 30, + 187, + 130, + 160, + 15, + 230, + 26, + 235, + 127, + 32, + 52, + 119, + 56, + 85, + 127, + 15, + 10, + 105, + 129, + 96, + 232, + 42, + 44, + 144, + 56, + 104, + 196, + 69, + 168, + 32, + 235, + 75, + 12, + 42, + 88, + 31, + 38, + 108, + 9, + 235, + 4, + 99, + 203, + 206, + 48, + 173, + 223, + 23, + 152, + 164, + 234, + 254, + 5, + 5, + 201, + 249, + 232, + 102, + 95, + 217, + 253, + 61, + 198, + 154, + 93, + 0, + 93, + 209, + 152, + 229, + 219, + 196, + 186, + 31, + 203, + 8, + 63, + 115, + 238, + 89, + 124, + 82, + 240, + 19, + 247, + 177, + 105, + 72, + 178, + 169, + 197, + 127, + 86, + 223, + 77, + 180, + 32, + 243, + 35, + 85, + 74, + 178, + 90, + 172, + 120, + 43, + 1, + 179, + 28, + 98, + 34, + 118, + 54, + 28, + 106, + 252, + 31, + 128, + 235, + 159, + 174, + 155, + 2, + 141, + 140, + 215, + 122, + 136, + 208, + 112, + 94, + 141, + 218, + 184, + 92, + 220, + 227, + 211, + 200, + 209, + 191, + 189, + 166, + 108, + 26, + 10, + 181, + 222, + 181, + 117, + 232, + 94, + 135, + 238, + 2, + 125, + 182, + 211, + 102, + 143, + 136, + 79, + 21, + 54, + 60, + 183, + 225, + 120, + 102, + 74, + 26, + 248, + 156, + 205, + 5, + 61, + 153, + 229, + 169, + 4, + 116, + 215, + 180, + 21, + 17, + 58, + 66, + 25, + 119, + 87, + 143, + 110, + 139, + 143, + 132, + 76, + 209, + 246, + 93, + 28, + 100, + 49, + 159, + 89, + 226, + 88, + 234, + 27, + 225, + 101, + 143, + 231, + 200, + 214, + 27, + 79, + 51, + 154, + ], + }, + { + "dcId": 2, + "key": Uint8Array [ + 130, + 148, + 91, + 227, + 227, + 77, + 248, + 101, + 245, + 102, + 224, + 127, + 214, + 165, + 64, + 53, + 156, + 169, + 101, + 230, + 237, + 80, + 0, + 6, + 0, + 117, + 92, + 208, + 2, + 167, + 104, + 74, + 40, + 6, + 209, + 36, + 56, + 200, + 160, + 69, + 224, + 116, + 86, + 14, + 73, + 202, + 198, + 197, + 201, + 175, + 254, + 204, + 229, + 25, + 24, + 118, + 52, + 23, + 220, + 191, + 152, + 168, + 97, + 68, + 36, + 246, + 105, + 238, + 203, + 171, + 6, + 70, + 126, + 248, + 61, + 118, + 224, + 36, + 116, + 216, + 4, + 175, + 220, + 14, + 66, + 252, + 32, + 252, + 201, + 41, + 238, + 30, + 56, + 105, + 250, + 238, + 62, + 149, + 146, + 139, + 159, + 240, + 214, + 23, + 186, + 53, + 28, + 39, + 119, + 146, + 62, + 139, + 126, + 163, + 87, + 101, + 226, + 173, + 110, + 202, + 173, + 140, + 231, + 147, + 94, + 168, + 87, + 117, + 71, + 156, + 193, + 160, + 64, + 53, + 187, + 162, + 206, + 132, + 101, + 119, + 108, + 103, + 61, + 203, + 250, + 218, + 137, + 111, + 140, + 177, + 29, + 3, + 95, + 213, + 115, + 191, + 212, + 221, + 103, + 70, + 246, + 182, + 164, + 97, + 158, + 55, + 230, + 230, + 153, + 9, + 153, + 62, + 94, + 43, + 60, + 135, + 20, + 156, + 107, + 133, + 249, + 161, + 81, + 222, + 131, + 8, + 214, + 80, + 252, + 70, + 120, + 70, + 134, + 99, + 67, + 133, + 155, + 134, + 148, + 129, + 211, + 39, + 248, + 240, + 223, + 241, + 221, + 192, + 37, + 251, + 83, + 96, + 104, + 35, + 3, + 188, + 171, + 219, + 152, + 245, + 125, + 15, + 152, + 107, + 108, + 32, + 82, + 109, + 166, + 230, + 127, + 60, + 211, + 28, + 208, + 169, + 165, + 229, + 81, + 15, + 91, + 106, + 155, + 85, + 166, + 203, + 135, + 100, + 226, + 64, + 110, + 133, + 116, + 157, + 56, + 50, + ], + }, + { + "dcId": 5, + "key": Uint8Array [ + 55, + 197, + 142, + 23, + 130, + 175, + 198, + 240, + 86, + 50, + 122, + 158, + 236, + 252, + 5, + 212, + 40, + 211, + 193, + 60, + 106, + 80, + 78, + 26, + 103, + 114, + 162, + 74, + 239, + 118, + 178, + 11, + 212, + 168, + 205, + 109, + 254, + 104, + 211, + 117, + 154, + 120, + 123, + 220, + 82, + 152, + 229, + 148, + 47, + 185, + 174, + 21, + 166, + 15, + 137, + 47, + 174, + 13, + 112, + 191, + 82, + 196, + 15, + 221, + 123, + 20, + 220, + 251, + 157, + 20, + 2, + 132, + 193, + 172, + 32, + 226, + 76, + 171, + 134, + 189, + 75, + 8, + 182, + 191, + 106, + 94, + 196, + 100, + 30, + 132, + 21, + 56, + 247, + 177, + 149, + 163, + 174, + 66, + 190, + 0, + 134, + 74, + 170, + 230, + 48, + 159, + 150, + 143, + 218, + 25, + 80, + 121, + 156, + 207, + 235, + 162, + 222, + 108, + 119, + 137, + 39, + 131, + 139, + 154, + 81, + 185, + 201, + 45, + 102, + 76, + 242, + 193, + 127, + 56, + 57, + 130, + 122, + 140, + 252, + 59, + 36, + 177, + 14, + 13, + 170, + 153, + 227, + 176, + 134, + 91, + 23, + 182, + 226, + 173, + 8, + 204, + 13, + 117, + 108, + 118, + 95, + 51, + 112, + 197, + 218, + 28, + 69, + 95, + 211, + 227, + 228, + 139, + 30, + 6, + 142, + 105, + 230, + 254, + 39, + 0, + 214, + 244, + 216, + 55, + 161, + 94, + 155, + 124, + 106, + 198, + 197, + 168, + 77, + 53, + 53, + 219, + 221, + 24, + 34, + 33, + 65, + 13, + 94, + 126, + 87, + 220, + 193, + 49, + 227, + 15, + 15, + 8, + 108, + 201, + 82, + 43, + 211, + 139, + 225, + 2, + 197, + 142, + 210, + 141, + 59, + 79, + 24, + 119, + 143, + 9, + 255, + 116, + 187, + 226, + 59, + 226, + 220, + 234, + 140, + 5, + 4, + 163, + 24, + 250, + 102, + 210, + 162, + 70, + 25, + 17, + 194, + 78, + 22, + 11, + 101, + 22, + ], + }, + ], + "authKeysToDestroy": [], + "mainDcId": 2, + "userId": Long { + "high": 1, + "low": 1479652362, + "unsigned": false, + }, + }, + "key": { + "active": 1, + "count": 2, + "localKey": Uint8Array [ + 120, + 76, + 23, + 84, + 165, + 8, + 147, + 169, + 22, + 88, + 113, + 223, + 177, + 195, + 146, + 155, + 109, + 145, + 99, + 223, + 104, + 160, + 2, + 130, + 226, + 64, + 17, + 129, + 197, + 118, + 10, + 114, + 253, + 65, + 105, + 81, + 88, + 112, + 155, + 152, + 140, + 176, + 150, + 34, + 144, + 47, + 13, + 33, + 133, + 110, + 105, + 28, + 96, + 36, + 181, + 83, + 205, + 203, + 161, + 95, + 21, + 165, + 102, + 26, + 137, + 6, + 153, + 92, + 134, + 147, + 249, + 24, + 203, + 85, + 137, + 2, + 131, + 47, + 159, + 212, + 3, + 91, + 52, + 156, + 35, + 121, + 24, + 93, + 51, + 161, + 225, + 22, + 152, + 190, + 246, + 74, + 146, + 49, + 184, + 152, + 151, + 188, + 133, + 235, + 75, + 190, + 199, + 26, + 39, + 108, + 60, + 103, + 51, + 229, + 26, + 27, + 34, + 162, + 68, + 121, + 162, + 222, + 228, + 71, + 89, + 139, + 4, + 161, + 213, + 11, + 194, + 173, + 217, + 153, + 63, + 212, + 110, + 244, + 159, + 189, + 146, + 33, + 155, + 38, + 2, + 80, + 144, + 171, + 155, + 210, + 242, + 120, + 226, + 247, + 70, + 116, + 20, + 155, + 155, + 137, + 53, + 8, + 147, + 220, + 189, + 72, + 193, + 96, + 170, + 219, + 81, + 114, + 78, + 212, + 217, + 93, + 211, + 250, + 135, + 177, + 232, + 41, + 131, + 35, + 26, + 77, + 214, + 115, + 2, + 119, + 4, + 167, + 219, + 161, + 21, + 81, + 123, + 82, + 56, + 244, + 36, + 142, + 250, + 12, + 182, + 206, + 109, + 175, + 254, + 138, + 69, + 253, + 177, + 93, + 212, + 250, + 38, + 192, + 141, + 62, + 250, + 173, + 34, + 103, + 95, + 100, + 147, + 217, + 93, + 130, + 119, + 30, + 36, + 101, + 17, + 162, + 166, + 138, + 11, + 189, + 38, + 174, + 254, + 191, + 123, + 160, + 106, + 165, + 74, + 72, + 42, + 226, + 47, + 242, + 214, + 35, + ], + "order": [ + 0, + 1, + ], + "version": 5008003, + }, +} +`; + +exports[`tdata > should read passcode-protected tdata 1`] = ` +{ + "auth": { + "authKeys": [ + { + "dcId": 1, + "key": Uint8Array [ + 44, + 28, + 216, + 145, + 35, + 120, + 65, + 203, + 197, + 50, + 153, + 251, + 232, + 64, + 220, + 169, + 171, + 232, + 65, + 163, + 230, + 226, + 210, + 250, + 213, + 141, + 64, + 230, + 90, + 134, + 168, + 121, + 215, + 76, + 21, + 104, + 18, + 209, + 195, + 48, + 37, + 146, + 115, + 253, + 26, + 109, + 183, + 77, + 134, + 129, + 51, + 194, + 195, + 100, + 141, + 127, + 26, + 129, + 251, + 186, + 186, + 162, + 247, + 0, + 89, + 221, + 148, + 217, + 86, + 195, + 47, + 71, + 198, + 157, + 47, + 239, + 205, + 106, + 184, + 173, + 66, + 105, + 251, + 1, + 20, + 203, + 48, + 61, + 248, + 90, + 184, + 180, + 174, + 192, + 170, + 39, + 215, + 72, + 215, + 100, + 82, + 130, + 231, + 118, + 171, + 221, + 198, + 190, + 32, + 204, + 94, + 38, + 187, + 226, + 161, + 133, + 198, + 185, + 221, + 135, + 24, + 6, + 105, + 52, + 247, + 219, + 195, + 246, + 185, + 119, + 101, + 109, + 36, + 143, + 191, + 65, + 203, + 216, + 93, + 10, + 88, + 121, + 157, + 152, + 224, + 143, + 103, + 74, + 243, + 99, + 183, + 122, + 198, + 154, + 112, + 85, + 155, + 133, + 96, + 110, + 58, + 169, + 142, + 56, + 197, + 211, + 177, + 56, + 5, + 115, + 60, + 10, + 243, + 133, + 55, + 204, + 16, + 112, + 106, + 141, + 248, + 65, + 113, + 32, + 19, + 199, + 60, + 213, + 27, + 202, + 141, + 154, + 175, + 160, + 233, + 169, + 255, + 51, + 94, + 69, + 6, + 123, + 198, + 222, + 168, + 204, + 28, + 180, + 220, + 27, + 48, + 206, + 45, + 201, + 6, + 39, + 5, + 248, + 181, + 42, + 193, + 104, + 88, + 0, + 231, + 196, + 67, + 102, + 29, + 28, + 253, + 173, + 130, + 218, + 253, + 238, + 215, + 151, + 9, + 4, + 29, + 105, + 119, + 101, + 121, + 171, + 49, + 119, + 216, + 53, + 68, + 48, + 224, + 17, + 115, + 29, + ], + }, + { + "dcId": 2, + "key": Uint8Array [ + 73, + 77, + 244, + 157, + 119, + 147, + 12, + 196, + 245, + 200, + 23, + 94, + 249, + 171, + 93, + 102, + 187, + 25, + 155, + 172, + 1, + 115, + 72, + 228, + 40, + 135, + 83, + 253, + 179, + 49, + 175, + 230, + 185, + 194, + 224, + 112, + 178, + 125, + 77, + 8, + 38, + 143, + 21, + 14, + 134, + 31, + 138, + 169, + 128, + 113, + 1, + 191, + 178, + 12, + 87, + 83, + 8, + 45, + 103, + 118, + 67, + 111, + 20, + 213, + 188, + 26, + 239, + 59, + 8, + 238, + 26, + 179, + 28, + 90, + 210, + 243, + 23, + 141, + 159, + 136, + 61, + 146, + 129, + 13, + 190, + 189, + 241, + 213, + 104, + 201, + 18, + 27, + 202, + 124, + 214, + 60, + 243, + 225, + 238, + 173, + 70, + 255, + 207, + 139, + 148, + 177, + 88, + 227, + 72, + 88, + 173, + 216, + 119, + 231, + 110, + 129, + 155, + 35, + 151, + 121, + 133, + 203, + 37, + 251, + 226, + 0, + 118, + 173, + 15, + 201, + 85, + 101, + 198, + 56, + 142, + 110, + 50, + 21, + 19, + 10, + 35, + 255, + 253, + 215, + 107, + 92, + 28, + 208, + 0, + 51, + 55, + 81, + 48, + 247, + 183, + 24, + 213, + 52, + 94, + 114, + 204, + 198, + 110, + 247, + 102, + 103, + 156, + 13, + 241, + 1, + 89, + 131, + 226, + 142, + 93, + 185, + 83, + 181, + 86, + 67, + 15, + 168, + 39, + 253, + 124, + 203, + 251, + 110, + 102, + 163, + 92, + 231, + 29, + 171, + 62, + 72, + 159, + 233, + 251, + 185, + 9, + 59, + 234, + 172, + 133, + 226, + 112, + 82, + 23, + 151, + 158, + 85, + 66, + 124, + 254, + 138, + 81, + 141, + 223, + 212, + 205, + 107, + 97, + 153, + 126, + 228, + 45, + 179, + 26, + 79, + 26, + 76, + 241, + 194, + 176, + 202, + 153, + 201, + 241, + 43, + 249, + 82, + 101, + 154, + 93, + 161, + 247, + 221, + 244, + 147, + 109, + 45, + 216, + 215, + 218, + 52, + ], + }, + { + "dcId": 4, + "key": Uint8Array [ + 39, + 185, + 106, + 207, + 146, + 254, + 151, + 89, + 137, + 32, + 250, + 152, + 121, + 120, + 109, + 154, + 193, + 40, + 252, + 227, + 224, + 129, + 94, + 106, + 111, + 228, + 92, + 62, + 64, + 83, + 185, + 53, + 153, + 11, + 180, + 147, + 47, + 72, + 72, + 165, + 174, + 94, + 104, + 250, + 90, + 90, + 198, + 227, + 27, + 7, + 72, + 219, + 93, + 224, + 245, + 246, + 234, + 119, + 151, + 169, + 57, + 29, + 199, + 173, + 213, + 37, + 190, + 254, + 143, + 138, + 127, + 51, + 205, + 20, + 40, + 6, + 46, + 21, + 8, + 216, + 28, + 235, + 247, + 66, + 167, + 229, + 189, + 251, + 220, + 146, + 96, + 49, + 1, + 17, + 75, + 194, + 40, + 2, + 48, + 232, + 136, + 213, + 202, + 190, + 114, + 183, + 46, + 6, + 107, + 205, + 107, + 96, + 65, + 79, + 175, + 223, + 77, + 191, + 233, + 62, + 59, + 245, + 56, + 36, + 174, + 138, + 129, + 34, + 216, + 220, + 156, + 231, + 47, + 81, + 159, + 115, + 40, + 250, + 113, + 162, + 238, + 139, + 126, + 179, + 254, + 242, + 103, + 77, + 206, + 191, + 248, + 63, + 60, + 209, + 189, + 116, + 30, + 74, + 160, + 76, + 213, + 227, + 15, + 90, + 253, + 92, + 231, + 250, + 82, + 174, + 76, + 64, + 81, + 224, + 73, + 35, + 253, + 147, + 137, + 236, + 102, + 5, + 135, + 143, + 4, + 57, + 100, + 116, + 83, + 117, + 199, + 42, + 169, + 57, + 43, + 172, + 25, + 86, + 84, + 55, + 187, + 117, + 88, + 195, + 73, + 183, + 232, + 4, + 172, + 18, + 208, + 68, + 69, + 23, + 183, + 195, + 242, + 73, + 104, + 186, + 36, + 103, + 24, + 25, + 105, + 252, + 200, + 15, + 179, + 140, + 145, + 137, + 132, + 40, + 244, + 176, + 206, + 41, + 107, + 103, + 51, + 95, + 161, + 123, + 62, + 129, + 19, + 141, + 81, + 206, + 112, + 71, + 15, + 210, + 93, + 168, + ], + }, + { + "dcId": 5, + "key": Uint8Array [ + 73, + 36, + 12, + 6, + 143, + 43, + 71, + 187, + 119, + 132, + 128, + 73, + 63, + 44, + 111, + 107, + 165, + 125, + 202, + 207, + 232, + 156, + 236, + 81, + 149, + 152, + 105, + 168, + 188, + 58, + 142, + 238, + 182, + 205, + 113, + 166, + 200, + 35, + 21, + 170, + 133, + 186, + 43, + 191, + 211, + 111, + 71, + 142, + 19, + 241, + 182, + 231, + 243, + 167, + 95, + 209, + 99, + 121, + 249, + 2, + 7, + 119, + 96, + 1, + 57, + 240, + 76, + 86, + 2, + 38, + 123, + 55, + 99, + 184, + 103, + 223, + 103, + 193, + 46, + 21, + 233, + 161, + 200, + 30, + 145, + 253, + 44, + 138, + 186, + 95, + 120, + 16, + 189, + 6, + 168, + 143, + 87, + 162, + 170, + 181, + 50, + 165, + 237, + 62, + 193, + 75, + 128, + 236, + 19, + 73, + 96, + 11, + 184, + 177, + 238, + 96, + 90, + 206, + 146, + 114, + 78, + 20, + 177, + 164, + 57, + 158, + 64, + 129, + 147, + 15, + 75, + 159, + 37, + 130, + 233, + 154, + 164, + 80, + 8, + 114, + 188, + 220, + 76, + 167, + 57, + 220, + 129, + 139, + 174, + 113, + 97, + 85, + 105, + 140, + 30, + 156, + 183, + 71, + 142, + 47, + 167, + 170, + 133, + 16, + 119, + 252, + 139, + 189, + 52, + 82, + 40, + 164, + 65, + 178, + 184, + 218, + 30, + 236, + 220, + 5, + 126, + 33, + 177, + 96, + 83, + 158, + 152, + 10, + 90, + 178, + 95, + 143, + 214, + 111, + 7, + 13, + 1, + 128, + 162, + 127, + 44, + 123, + 238, + 251, + 49, + 2, + 234, + 237, + 151, + 203, + 165, + 46, + 219, + 94, + 143, + 21, + 97, + 238, + 171, + 174, + 13, + 115, + 250, + 76, + 193, + 247, + 27, + 77, + 201, + 111, + 98, + 88, + 175, + 46, + 84, + 119, + 96, + 255, + 157, + 109, + 240, + 174, + 54, + 220, + 150, + 135, + 250, + 34, + 157, + 61, + 241, + 231, + 36, + 124, + 195, + 216, + ], + }, + ], + "authKeysToDestroy": [], + "mainDcId": 1, + "userId": Long { + "high": 0, + "low": 1787945512, + "unsigned": false, + }, + }, + "key": { + "active": 0, + "count": 1, + "localKey": Uint8Array [ + 120, + 76, + 23, + 84, + 165, + 8, + 147, + 169, + 22, + 88, + 113, + 223, + 177, + 195, + 146, + 155, + 109, + 145, + 99, + 223, + 104, + 160, + 2, + 130, + 226, + 64, + 17, + 129, + 197, + 118, + 10, + 114, + 253, + 65, + 105, + 81, + 88, + 112, + 155, + 152, + 140, + 176, + 150, + 34, + 144, + 47, + 13, + 33, + 133, + 110, + 105, + 28, + 96, + 36, + 181, + 83, + 205, + 203, + 161, + 95, + 21, + 165, + 102, + 26, + 137, + 6, + 153, + 92, + 134, + 147, + 249, + 24, + 203, + 85, + 137, + 2, + 131, + 47, + 159, + 212, + 3, + 91, + 52, + 156, + 35, + 121, + 24, + 93, + 51, + 161, + 225, + 22, + 152, + 190, + 246, + 74, + 146, + 49, + 184, + 152, + 151, + 188, + 133, + 235, + 75, + 190, + 199, + 26, + 39, + 108, + 60, + 103, + 51, + 229, + 26, + 27, + 34, + 162, + 68, + 121, + 162, + 222, + 228, + 71, + 89, + 139, + 4, + 161, + 213, + 11, + 194, + 173, + 217, + 153, + 63, + 212, + 110, + 244, + 159, + 189, + 146, + 33, + 155, + 38, + 2, + 80, + 144, + 171, + 155, + 210, + 242, + 120, + 226, + 247, + 70, + 116, + 20, + 155, + 155, + 137, + 53, + 8, + 147, + 220, + 189, + 72, + 193, + 96, + 170, + 219, + 81, + 114, + 78, + 212, + 217, + 93, + 211, + 250, + 135, + 177, + 232, + 41, + 131, + 35, + 26, + 77, + 214, + 115, + 2, + 119, + 4, + 167, + 219, + 161, + 21, + 81, + 123, + 82, + 56, + 244, + 36, + 142, + 250, + 12, + 182, + 206, + 109, + 175, + 254, + 138, + 69, + 253, + 177, + 93, + 212, + 250, + 38, + 192, + 141, + 62, + 250, + 173, + 34, + 103, + 95, + 100, + 147, + 217, + 93, + 130, + 119, + 30, + 36, + 101, + 17, + 162, + 166, + 138, + 11, + 189, + 38, + 174, + 254, + 191, + 123, + 160, + 106, + 165, + 74, + 72, + 42, + 226, + 47, + 242, + 214, + 35, + ], + "order": [ + 0, + ], + "version": 5008003, + }, +} +`; + +exports[`tdata > should read simple tdata 1`] = ` +{ + "auth": { + "authKeys": [ + { + "dcId": 1, + "key": Uint8Array [ + 44, + 28, + 216, + 145, + 35, + 120, + 65, + 203, + 197, + 50, + 153, + 251, + 232, + 64, + 220, + 169, + 171, + 232, + 65, + 163, + 230, + 226, + 210, + 250, + 213, + 141, + 64, + 230, + 90, + 134, + 168, + 121, + 215, + 76, + 21, + 104, + 18, + 209, + 195, + 48, + 37, + 146, + 115, + 253, + 26, + 109, + 183, + 77, + 134, + 129, + 51, + 194, + 195, + 100, + 141, + 127, + 26, + 129, + 251, + 186, + 186, + 162, + 247, + 0, + 89, + 221, + 148, + 217, + 86, + 195, + 47, + 71, + 198, + 157, + 47, + 239, + 205, + 106, + 184, + 173, + 66, + 105, + 251, + 1, + 20, + 203, + 48, + 61, + 248, + 90, + 184, + 180, + 174, + 192, + 170, + 39, + 215, + 72, + 215, + 100, + 82, + 130, + 231, + 118, + 171, + 221, + 198, + 190, + 32, + 204, + 94, + 38, + 187, + 226, + 161, + 133, + 198, + 185, + 221, + 135, + 24, + 6, + 105, + 52, + 247, + 219, + 195, + 246, + 185, + 119, + 101, + 109, + 36, + 143, + 191, + 65, + 203, + 216, + 93, + 10, + 88, + 121, + 157, + 152, + 224, + 143, + 103, + 74, + 243, + 99, + 183, + 122, + 198, + 154, + 112, + 85, + 155, + 133, + 96, + 110, + 58, + 169, + 142, + 56, + 197, + 211, + 177, + 56, + 5, + 115, + 60, + 10, + 243, + 133, + 55, + 204, + 16, + 112, + 106, + 141, + 248, + 65, + 113, + 32, + 19, + 199, + 60, + 213, + 27, + 202, + 141, + 154, + 175, + 160, + 233, + 169, + 255, + 51, + 94, + 69, + 6, + 123, + 198, + 222, + 168, + 204, + 28, + 180, + 220, + 27, + 48, + 206, + 45, + 201, + 6, + 39, + 5, + 248, + 181, + 42, + 193, + 104, + 88, + 0, + 231, + 196, + 67, + 102, + 29, + 28, + 253, + 173, + 130, + 218, + 253, + 238, + 215, + 151, + 9, + 4, + 29, + 105, + 119, + 101, + 121, + 171, + 49, + 119, + 216, + 53, + 68, + 48, + 224, + 17, + 115, + 29, + ], + }, + { + "dcId": 2, + "key": Uint8Array [ + 73, + 77, + 244, + 157, + 119, + 147, + 12, + 196, + 245, + 200, + 23, + 94, + 249, + 171, + 93, + 102, + 187, + 25, + 155, + 172, + 1, + 115, + 72, + 228, + 40, + 135, + 83, + 253, + 179, + 49, + 175, + 230, + 185, + 194, + 224, + 112, + 178, + 125, + 77, + 8, + 38, + 143, + 21, + 14, + 134, + 31, + 138, + 169, + 128, + 113, + 1, + 191, + 178, + 12, + 87, + 83, + 8, + 45, + 103, + 118, + 67, + 111, + 20, + 213, + 188, + 26, + 239, + 59, + 8, + 238, + 26, + 179, + 28, + 90, + 210, + 243, + 23, + 141, + 159, + 136, + 61, + 146, + 129, + 13, + 190, + 189, + 241, + 213, + 104, + 201, + 18, + 27, + 202, + 124, + 214, + 60, + 243, + 225, + 238, + 173, + 70, + 255, + 207, + 139, + 148, + 177, + 88, + 227, + 72, + 88, + 173, + 216, + 119, + 231, + 110, + 129, + 155, + 35, + 151, + 121, + 133, + 203, + 37, + 251, + 226, + 0, + 118, + 173, + 15, + 201, + 85, + 101, + 198, + 56, + 142, + 110, + 50, + 21, + 19, + 10, + 35, + 255, + 253, + 215, + 107, + 92, + 28, + 208, + 0, + 51, + 55, + 81, + 48, + 247, + 183, + 24, + 213, + 52, + 94, + 114, + 204, + 198, + 110, + 247, + 102, + 103, + 156, + 13, + 241, + 1, + 89, + 131, + 226, + 142, + 93, + 185, + 83, + 181, + 86, + 67, + 15, + 168, + 39, + 253, + 124, + 203, + 251, + 110, + 102, + 163, + 92, + 231, + 29, + 171, + 62, + 72, + 159, + 233, + 251, + 185, + 9, + 59, + 234, + 172, + 133, + 226, + 112, + 82, + 23, + 151, + 158, + 85, + 66, + 124, + 254, + 138, + 81, + 141, + 223, + 212, + 205, + 107, + 97, + 153, + 126, + 228, + 45, + 179, + 26, + 79, + 26, + 76, + 241, + 194, + 176, + 202, + 153, + 201, + 241, + 43, + 249, + 82, + 101, + 154, + 93, + 161, + 247, + 221, + 244, + 147, + 109, + 45, + 216, + 215, + 218, + 52, + ], + }, + { + "dcId": 4, + "key": Uint8Array [ + 39, + 185, + 106, + 207, + 146, + 254, + 151, + 89, + 137, + 32, + 250, + 152, + 121, + 120, + 109, + 154, + 193, + 40, + 252, + 227, + 224, + 129, + 94, + 106, + 111, + 228, + 92, + 62, + 64, + 83, + 185, + 53, + 153, + 11, + 180, + 147, + 47, + 72, + 72, + 165, + 174, + 94, + 104, + 250, + 90, + 90, + 198, + 227, + 27, + 7, + 72, + 219, + 93, + 224, + 245, + 246, + 234, + 119, + 151, + 169, + 57, + 29, + 199, + 173, + 213, + 37, + 190, + 254, + 143, + 138, + 127, + 51, + 205, + 20, + 40, + 6, + 46, + 21, + 8, + 216, + 28, + 235, + 247, + 66, + 167, + 229, + 189, + 251, + 220, + 146, + 96, + 49, + 1, + 17, + 75, + 194, + 40, + 2, + 48, + 232, + 136, + 213, + 202, + 190, + 114, + 183, + 46, + 6, + 107, + 205, + 107, + 96, + 65, + 79, + 175, + 223, + 77, + 191, + 233, + 62, + 59, + 245, + 56, + 36, + 174, + 138, + 129, + 34, + 216, + 220, + 156, + 231, + 47, + 81, + 159, + 115, + 40, + 250, + 113, + 162, + 238, + 139, + 126, + 179, + 254, + 242, + 103, + 77, + 206, + 191, + 248, + 63, + 60, + 209, + 189, + 116, + 30, + 74, + 160, + 76, + 213, + 227, + 15, + 90, + 253, + 92, + 231, + 250, + 82, + 174, + 76, + 64, + 81, + 224, + 73, + 35, + 253, + 147, + 137, + 236, + 102, + 5, + 135, + 143, + 4, + 57, + 100, + 116, + 83, + 117, + 199, + 42, + 169, + 57, + 43, + 172, + 25, + 86, + 84, + 55, + 187, + 117, + 88, + 195, + 73, + 183, + 232, + 4, + 172, + 18, + 208, + 68, + 69, + 23, + 183, + 195, + 242, + 73, + 104, + 186, + 36, + 103, + 24, + 25, + 105, + 252, + 200, + 15, + 179, + 140, + 145, + 137, + 132, + 40, + 244, + 176, + 206, + 41, + 107, + 103, + 51, + 95, + 161, + 123, + 62, + 129, + 19, + 141, + 81, + 206, + 112, + 71, + 15, + 210, + 93, + 168, + ], + }, + { + "dcId": 5, + "key": Uint8Array [ + 73, + 36, + 12, + 6, + 143, + 43, + 71, + 187, + 119, + 132, + 128, + 73, + 63, + 44, + 111, + 107, + 165, + 125, + 202, + 207, + 232, + 156, + 236, + 81, + 149, + 152, + 105, + 168, + 188, + 58, + 142, + 238, + 182, + 205, + 113, + 166, + 200, + 35, + 21, + 170, + 133, + 186, + 43, + 191, + 211, + 111, + 71, + 142, + 19, + 241, + 182, + 231, + 243, + 167, + 95, + 209, + 99, + 121, + 249, + 2, + 7, + 119, + 96, + 1, + 57, + 240, + 76, + 86, + 2, + 38, + 123, + 55, + 99, + 184, + 103, + 223, + 103, + 193, + 46, + 21, + 233, + 161, + 200, + 30, + 145, + 253, + 44, + 138, + 186, + 95, + 120, + 16, + 189, + 6, + 168, + 143, + 87, + 162, + 170, + 181, + 50, + 165, + 237, + 62, + 193, + 75, + 128, + 236, + 19, + 73, + 96, + 11, + 184, + 177, + 238, + 96, + 90, + 206, + 146, + 114, + 78, + 20, + 177, + 164, + 57, + 158, + 64, + 129, + 147, + 15, + 75, + 159, + 37, + 130, + 233, + 154, + 164, + 80, + 8, + 114, + 188, + 220, + 76, + 167, + 57, + 220, + 129, + 139, + 174, + 113, + 97, + 85, + 105, + 140, + 30, + 156, + 183, + 71, + 142, + 47, + 167, + 170, + 133, + 16, + 119, + 252, + 139, + 189, + 52, + 82, + 40, + 164, + 65, + 178, + 184, + 218, + 30, + 236, + 220, + 5, + 126, + 33, + 177, + 96, + 83, + 158, + 152, + 10, + 90, + 178, + 95, + 143, + 214, + 111, + 7, + 13, + 1, + 128, + 162, + 127, + 44, + 123, + 238, + 251, + 49, + 2, + 234, + 237, + 151, + 203, + 165, + 46, + 219, + 94, + 143, + 21, + 97, + 238, + 171, + 174, + 13, + 115, + 250, + 76, + 193, + 247, + 27, + 77, + 201, + 111, + 98, + 88, + 175, + 46, + 84, + 119, + 96, + 255, + 157, + 109, + 240, + 174, + 54, + 220, + 150, + 135, + 250, + 34, + 157, + 61, + 241, + 231, + 36, + 124, + 195, + 216, + ], + }, + ], + "authKeysToDestroy": [], + "mainDcId": 1, + "userId": Long { + "high": 0, + "low": 1787945512, + "unsigned": false, + }, + }, + "key": { + "active": 0, + "count": 1, + "localKey": Uint8Array [ + 120, + 76, + 23, + 84, + 165, + 8, + 147, + 169, + 22, + 88, + 113, + 223, + 177, + 195, + 146, + 155, + 109, + 145, + 99, + 223, + 104, + 160, + 2, + 130, + 226, + 64, + 17, + 129, + 197, + 118, + 10, + 114, + 253, + 65, + 105, + 81, + 88, + 112, + 155, + 152, + 140, + 176, + 150, + 34, + 144, + 47, + 13, + 33, + 133, + 110, + 105, + 28, + 96, + 36, + 181, + 83, + 205, + 203, + 161, + 95, + 21, + 165, + 102, + 26, + 137, + 6, + 153, + 92, + 134, + 147, + 249, + 24, + 203, + 85, + 137, + 2, + 131, + 47, + 159, + 212, + 3, + 91, + 52, + 156, + 35, + 121, + 24, + 93, + 51, + 161, + 225, + 22, + 152, + 190, + 246, + 74, + 146, + 49, + 184, + 152, + 151, + 188, + 133, + 235, + 75, + 190, + 199, + 26, + 39, + 108, + 60, + 103, + 51, + 229, + 26, + 27, + 34, + 162, + 68, + 121, + 162, + 222, + 228, + 71, + 89, + 139, + 4, + 161, + 213, + 11, + 194, + 173, + 217, + 153, + 63, + 212, + 110, + 244, + 159, + 189, + 146, + 33, + 155, + 38, + 2, + 80, + 144, + 171, + 155, + 210, + 242, + 120, + 226, + 247, + 70, + 116, + 20, + 155, + 155, + 137, + 53, + 8, + 147, + 220, + 189, + 72, + 193, + 96, + 170, + 219, + 81, + 114, + 78, + 212, + 217, + 93, + 211, + 250, + 135, + 177, + 232, + 41, + 131, + 35, + 26, + 77, + 214, + 115, + 2, + 119, + 4, + 167, + 219, + 161, + 21, + 81, + 123, + 82, + 56, + 244, + 36, + 142, + 250, + 12, + 182, + 206, + 109, + 175, + 254, + 138, + 69, + 253, + 177, + 93, + 212, + 250, + 38, + 192, + 141, + 62, + 250, + 173, + 34, + 103, + 95, + 100, + 147, + 217, + 93, + 130, + 119, + 30, + 36, + 101, + 17, + 162, + 166, + 138, + 11, + 189, + 38, + 174, + 254, + 191, + 123, + 160, + 106, + 165, + 74, + 72, + 42, + 226, + 47, + 242, + 214, + 35, + ], + "order": [ + 0, + ], + "version": 5008003, + }, +} +`; + +exports[`tdata > should write simple tdata 1`] = ` +Map { + "/key_datas" => Uint8Array [ + 84, + 68, + 70, + 36, + 131, + 106, + 76, + 0, + 0, + 0, + 0, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 32, + 6, + 118, + 162, + 236, + 13, + 121, + 252, + 251, + 87, + 144, + 55, + 108, + 141, + 157, + 11, + 241, + 79, + 154, + 136, + 119, + 22, + 219, + 113, + 112, + 122, + 172, + 90, + 150, + 156, + 218, + 52, + 225, + 8, + 195, + 254, + 25, + 23, + 101, + 201, + 109, + 87, + 188, + 148, + 184, + 77, + 47, + 73, + 171, + 39, + 91, + 10, + 44, + 235, + 69, + 196, + 231, + 122, + 249, + 171, + 8, + 92, + 54, + 246, + 52, + 191, + 97, + 254, + 177, + 62, + 170, + 137, + 25, + 214, + 148, + 95, + 31, + 23, + 87, + 120, + 154, + 186, + 221, + 32, + 134, + 140, + 179, + 81, + 87, + 196, + 183, + 66, + 84, + 251, + 5, + 174, + 176, + 78, + 80, + 43, + 143, + 206, + 98, + 60, + 10, + 103, + 103, + 193, + 19, + 129, + 33, + 251, + 138, + 122, + 112, + 91, + 164, + 224, + 11, + 41, + 18, + 37, + 89, + 74, + 2, + 56, + 66, + 113, + 58, + 114, + 169, + 246, + 242, + 41, + 81, + 211, + 239, + 137, + 255, + 222, + 156, + 132, + 213, + 174, + 198, + 185, + 166, + 20, + 226, + 139, + 165, + 120, + 66, + 0, + 194, + 92, + 109, + 132, + 104, + 7, + 163, + 140, + 123, + 47, + 179, + 24, + 175, + 42, + 206, + 0, + 204, + 139, + 41, + 132, + 253, + 88, + 38, + 91, + 88, + 246, + 198, + 128, + 182, + 95, + 147, + 175, + 2, + 153, + 239, + 16, + 161, + 83, + 104, + 20, + 213, + 121, + 110, + 124, + 199, + 105, + 203, + 112, + 251, + 217, + 211, + 167, + 223, + 195, + 242, + 84, + 83, + 131, + 242, + 209, + 97, + 94, + 214, + 143, + 6, + 157, + 235, + 68, + 222, + 63, + 94, + 16, + 158, + 26, + 193, + 28, + 84, + 227, + 228, + 1, + 243, + 58, + 1, + 13, + 138, + 172, + 187, + 181, + 221, + 129, + 197, + 150, + 70, + 36, + 173, + 159, + 28, + 149, + 246, + 136, + 63, + 28, + 249, + 178, + 197, + 53, + 217, + 180, + 122, + 229, + 20, + 17, + 137, + 118, + 232, + 147, + 175, + 52, + 66, + 174, + 145, + 143, + 21, + 45, + 72, + 225, + 128, + 74, + 167, + 120, + 13, + 117, + 217, + 167, + 78, + 0, + 0, + 0, + 32, + 10, + 158, + 105, + 198, + 216, + 121, + 105, + 18, + 174, + 214, + 164, + 137, + 230, + 185, + 241, + 221, + 170, + 63, + 200, + 25, + 14, + 192, + 98, + 100, + 30, + 69, + 56, + 61, + 68, + 9, + 92, + 51, + 61, + 62, + 7, + 247, + 74, + 113, + 117, + 187, + 51, + 245, + 89, + 97, + 144, + 111, + 115, + 75, + ], + "/D877F783D5D3EF8Cs" => Uint8Array [ + 84, + 68, + 70, + 36, + 131, + 106, + 76, + 0, + 0, + 0, + 1, + 64, + 71, + 149, + 63, + 211, + 151, + 188, + 117, + 77, + 154, + 161, + 98, + 253, + 57, + 149, + 142, + 35, + 117, + 185, + 155, + 26, + 62, + 142, + 31, + 76, + 110, + 212, + 228, + 161, + 29, + 202, + 73, + 2, + 88, + 55, + 22, + 68, + 63, + 143, + 186, + 251, + 102, + 42, + 120, + 116, + 90, + 150, + 102, + 160, + 176, + 102, + 2, + 165, + 250, + 50, + 4, + 108, + 124, + 112, + 243, + 241, + 159, + 216, + 29, + 193, + 108, + 61, + 158, + 117, + 32, + 106, + 84, + 8, + 1, + 228, + 136, + 127, + 13, + 82, + 112, + 241, + 150, + 181, + 25, + 125, + 35, + 29, + 91, + 231, + 48, + 58, + 161, + 187, + 16, + 188, + 145, + 1, + 249, + 204, + 251, + 222, + 215, + 158, + 125, + 199, + 39, + 248, + 110, + 219, + 20, + 26, + 137, + 179, + 21, + 226, + 69, + 11, + 181, + 58, + 214, + 44, + 139, + 60, + 78, + 96, + 22, + 73, + 3, + 17, + 240, + 250, + 205, + 72, + 28, + 132, + 229, + 158, + 172, + 88, + 244, + 228, + 83, + 65, + 205, + 75, + 76, + 64, + 29, + 251, + 140, + 205, + 237, + 46, + 7, + 136, + 31, + 85, + 50, + 207, + 89, + 246, + 9, + 155, + 46, + 147, + 147, + 19, + 4, + 186, + 74, + 90, + 207, + 96, + 120, + 120, + 132, + 107, + 134, + 167, + 87, + 182, + 18, + 58, + 132, + 99, + 247, + 127, + 79, + 224, + 39, + 123, + 231, + 183, + 213, + 111, + 125, + 181, + 155, + 144, + 165, + 166, + 173, + 223, + 83, + 6, + 195, + 18, + 217, + 74, + 146, + 191, + 121, + 14, + 29, + 135, + 81, + 253, + 180, + 252, + 147, + 37, + 131, + 22, + 203, + 200, + 29, + 65, + 92, + 95, + 28, + 15, + 122, + 191, + 47, + 168, + 156, + 225, + 64, + 31, + 204, + 196, + 252, + 124, + 56, + 58, + 44, + 243, + 209, + 214, + 10, + 214, + 73, + 135, + 181, + 155, + 117, + 183, + 94, + 67, + 217, + 196, + 97, + 110, + 1, + 73, + 121, + 162, + 148, + 8, + 9, + 55, + 203, + 89, + 132, + 97, + 7, + 150, + 225, + 10, + 17, + 138, + 255, + 82, + 240, + 64, + 98, + 119, + 150, + 191, + 47, + 62, + 255, + 54, + 214, + 0, + 178, + 58, + 252, + 42, + 120, + 212, + 244, + 181, + 11, + 207, + 0, + 206, + 253, + 235, + 243, + 23, + 222, + 188, + 165, + 199, + 240, + 98, + 30, + 88, + 211, + 254, + 190, + 122, + 211, + 58, + 86, + 214, + 85, + 43, + 206, + 163, + 135, + 153, + 122, + 109, + 92, + 137, + ], +} +`; diff --git a/packages/convert/src/tdesktop/convert.ts b/packages/convert/src/tdesktop/convert.ts new file mode 100644 index 00000000..0f77c1b6 --- /dev/null +++ b/packages/convert/src/tdesktop/convert.ts @@ -0,0 +1,73 @@ +import { type StringSessionData, readStringSession } from '@mtcute/core/utils.js' +import type { MaybeArray } from '@fuman/utils' +import { Long } from '@mtcute/core' + +import { DC_MAPPING_PROD } from '../dcs.js' + +import type { TdataOptions } from './tdata.js' +import { Tdata } from './tdata.js' +import type { InputTdKeyData } from './types.js' + +export async function convertFromTdata(tdata: Tdata | TdataOptions, accountIdx = 0): Promise { + if (!(tdata instanceof Tdata)) { + tdata = await Tdata.open(tdata) + } + + const auth = await tdata.readMtpAuthorization(accountIdx) + const authKey = auth.authKeys.find(it => it.dcId === auth.mainDcId) + if (!authKey) throw new Error('Failed to find auth key') + + return { + version: 3, + primaryDcs: DC_MAPPING_PROD[auth.mainDcId], + authKey: authKey.key, + self: { + userId: auth.userId.toNumber(), + isBot: false, + isPremium: false, + usernames: [], + }, + } +} + +export async function convertToTdata( + sessions: MaybeArray, + tdata: Tdata | TdataOptions, +): Promise { + if (!Array.isArray(sessions)) { + sessions = [sessions] + } + + if (!(tdata instanceof Tdata)) { + const keyData: InputTdKeyData = { + count: sessions.length, + order: Array.from({ length: sessions.length }, (_, i) => i), + active: 0, + } + tdata = await Tdata.create({ + keyData, + ...tdata, + }) + } + + for (let i = 0; i < sessions.length; i++) { + let session = sessions[i] + + if (typeof session === 'string') { + session = readStringSession(session) + } + + await tdata.writeMtpAuthorization({ + userId: Long.fromNumber(session.self?.userId ?? 0), + mainDcId: session.primaryDcs.main.id, + authKeys: [ + { + dcId: session.primaryDcs.main.id, + key: session.authKey, + }, + ], + authKeysToDestroy: [], + }, i) + await tdata.writeEmptyMapFile(i) + } +} diff --git a/packages/convert/src/tdesktop/docs.md b/packages/convert/src/tdesktop/docs.md new file mode 100644 index 00000000..9593b827 --- /dev/null +++ b/packages/convert/src/tdesktop/docs.md @@ -0,0 +1,55 @@ +## How it works + +### Data name + +TDesktop allows for multiple "data names", which is basically +a prefix for data storage. Default one is `data`, but you can choose +any using `-key` CLI parameter. + +### Local key + +TDesktop uses something called "local key" to encrypt most of the files. +The local key itself is stored in `key_datas`, where `data` is the default +data name. That file can be passcode-protected, in which case you will +need a correct passcode to decrypt it. + +### Encryption + +Without going too deep into details, encryption used is the same +as the one used in MTProto v1 for message encryption (see +[Telegram docs](https://core.telegram.org/mtproto/description_v1#defining-aes-key-and-initialization-vector) +for details). + +There, instead of `auth_key` a local key is used, and instead of +`msg_key` lower 16 bytes of sha1 of the contents are used. + +Before encrypting (and computing sha1), content size is prepended, and +the result is padded. + +### File naming + +To name different files, TDesktop uses 8 lower bytes of md5 hash, +with nibbles switched (i.e. `0xFE => 0xEF`). + +So, for example: + +```typescript +const filename = 'data' +const md5 = crypto.md5(filename) // 8d777f385d3dfec8815d20f7496026dc +const md5Lower = md5.slice(0, 8) // 8d777f385d3dfec8 +const result = swap8(md5Lower).toUpperHex() // D877F783D5D3EF8C +``` + +`D877F783D5D3EF8C` is the folder that is most likely present in your +`tdata` folder, which is derived simply from `data` and is used +for your first account data. + +### Multi accounts + +For second, third, etc. accounts, TDesktop appends `#2`, `#3` etc. +to the base data name respectively. + +### MTProto auth keys + +Auth keys are stored in a file named same as account data folder but with +`s` appended. So, for the first account that would be `D877F783D5D3EF8Cs`. \ No newline at end of file diff --git a/packages/convert/src/tdesktop/index.ts b/packages/convert/src/tdesktop/index.ts new file mode 100644 index 00000000..04809e2b --- /dev/null +++ b/packages/convert/src/tdesktop/index.ts @@ -0,0 +1,6 @@ +import * as qt from './qt-bundle.js' + +export { qt } +export * from './convert.js' +export * from './tdata.js' +export * from './types.js' diff --git a/packages/convert/src/tdesktop/qt-bundle.ts b/packages/convert/src/tdesktop/qt-bundle.ts new file mode 100644 index 00000000..09096c62 --- /dev/null +++ b/packages/convert/src/tdesktop/qt-bundle.ts @@ -0,0 +1,4 @@ +import * as read from './qt-reader.js' +import * as write from './qt-writer.js' + +export { read, write } diff --git a/packages/convert/src/tdesktop/qt-reader.ts b/packages/convert/src/tdesktop/qt-reader.ts new file mode 100644 index 00000000..72a53431 --- /dev/null +++ b/packages/convert/src/tdesktop/qt-reader.ts @@ -0,0 +1,35 @@ +import { Long } from '@mtcute/core' +import { type ISyncReadable, read } from '@fuman/io' +import { u8 } from '@fuman/utils' + +export function readQByteArray(readable: ISyncReadable): Uint8Array { + const length = read.uint32be(readable) + if (length === 0 || length === 0xFFFFFFFF) { + return u8.empty + } + + return read.exactly(readable, length) +} + +export function readLong(readable: ISyncReadable): Long { + const high = read.int32be(readable) + const low = read.int32be(readable) + + return new Long(low, high) +} + +export function readCharArray(readable: ISyncReadable): Uint8Array { + const buf = readQByteArray(readable) + + if (buf.length > 0) { + // drop the last byte, which is always 0 + return buf.subarray(0, buf.length - 1) + } + return buf +} + +const u16Decoder = new TextDecoder('utf-16be') +export function readQString(readable: ISyncReadable): string { + const bytes = readQByteArray(readable) + return u16Decoder.decode(bytes) +} diff --git a/packages/convert/src/tdesktop/qt-writer.ts b/packages/convert/src/tdesktop/qt-writer.ts new file mode 100644 index 00000000..8911d7fe --- /dev/null +++ b/packages/convert/src/tdesktop/qt-writer.ts @@ -0,0 +1,31 @@ +import type { ISyncWritable } from '@fuman/io' +import { write } from '@fuman/io' +import { u8 } from '@fuman/utils' +import type { Long } from '@mtcute/core' + +export function writeQByteArray(into: ISyncWritable, buf: Uint8Array): void { + write.uint32be(into, buf.length) + write.bytes(into, buf) +} + +export function writeLong(into: ISyncWritable, long: Long): void { + write.int32be(into, long.high) + write.int32be(into, long.low) +} + +export function writeCharArray(into: ISyncWritable, buf: Uint8Array): void { + const bytes = u8.alloc(buf.length + 1) + bytes.set(buf) + bytes[buf.length] = 0 + + writeQByteArray(into, bytes) +} + +export function writeQString(into: ISyncWritable, str: string): void { + const length = str.length * 2 + write.uint32be(into, length) + + for (let i = 0; i < length; i++) { + write.uint16be(into, str.charCodeAt(i)) + } +} diff --git a/packages/convert/src/tdesktop/tdata.test.ts b/packages/convert/src/tdesktop/tdata.test.ts new file mode 100644 index 00000000..ec30e7ea --- /dev/null +++ b/packages/convert/src/tdesktop/tdata.test.ts @@ -0,0 +1,106 @@ +import { fileURLToPath } from 'node:url' + +import { describe, expect, it } from 'vitest' +import { Long } from '@mtcute/core' + +import type { INodeFsLike } from '../utils/fs.js' +import { getDefaultCryptoProvider } from '../utils/crypto.js' + +import { Tdata } from './tdata.js' + +class FakeFs implements INodeFsLike { + readonly files = new Map() + + async readFile(path: string): Promise { + return this.files.get(path)! + } + + async writeFile(path: string, data: Uint8Array): Promise { + this.files.set(path, data) + } + + async stat(path: string): Promise<{ size: number, lastModified: number }> { + return { + size: this.files.get(path)!.length, + lastModified: 0, + } + } + + mkdir(): Promise { + return Promise.resolve() + } +} + +describe('tdata', () => { + it('should read simple tdata', async () => { + const tdata = await Tdata.open({ + path: fileURLToPath(new URL('./__fixtures__/simple', import.meta.url)), + }) + + const auth = await tdata.readMtpAuthorization() + + expect({ auth, key: tdata.keyData }).toMatchSnapshot() + }) + + it('should read passcode-protected tdata', async () => { + const tdata = await Tdata.open({ + path: fileURLToPath(new URL('./__fixtures__/passcode', import.meta.url)), + passcode: '123123', + }) + + const auth = await tdata.readMtpAuthorization() + + expect({ auth, key: tdata.keyData }).toMatchSnapshot() + }) + + it('should throw on invalid passcode', async () => { + await expect(Tdata.open({ + path: fileURLToPath(new URL('./__fixtures__/passcode', import.meta.url)), + passcode: '123', + })).rejects.toThrow('Failed to decrypt, invalid password?') + }) + + it('should read multi-account tdata', async () => { + const tdata = await Tdata.open({ + path: fileURLToPath(new URL('./__fixtures__/multiacc', import.meta.url)), + }) + + const auth0 = await tdata.readMtpAuthorization(0) + const auth1 = await tdata.readMtpAuthorization(1) + + expect({ auth0, auth1, key: tdata.keyData }).toMatchSnapshot() + }) + + it('should write simple tdata', async () => { + const fs = new FakeFs() + const crypto = await getDefaultCryptoProvider() + crypto.randomBytes = size => new Uint8Array(size) + + const tdata = await Tdata.create({ + path: '/', + fs, + crypto, + keyData: { + count: 1, + order: [0], + active: 0, + }, + }) + + const key = new Uint8Array(256) + key.fill(1) + await tdata.writeMtpAuthorization({ + userId: Long.fromNumber(12345678), + mainDcId: 2, + authKeys: [ + { + dcId: 2, + key, + }, + ], + authKeysToDestroy: [], + }) + + expect(fs.files).toMatchSnapshot() + }) +}) diff --git a/packages/convert/src/tdesktop/tdata.ts b/packages/convert/src/tdesktop/tdata.ts new file mode 100644 index 00000000..b5075df6 --- /dev/null +++ b/packages/convert/src/tdesktop/tdata.ts @@ -0,0 +1,494 @@ +import { dirname, join } from 'node:path/posix' + +import { Bytes, read, write } from '@fuman/io' +import type { UnsafeMutable } from '@fuman/utils' +import { typed, u8, utf8 } from '@fuman/utils' +import { createAesIgeForMessageOld } from '@mtcute/core/utils.js' +import { Long, MtUnsupportedError } from '@mtcute/core' + +import type { INodeFsLike } from '../utils/fs.js' +import { type IExtendedCryptoProvider, getDefaultCryptoProvider } from '../utils/crypto.js' + +import { readLong, readQByteArray } from './qt-reader.js' +import type { InputTdKeyData, TdAuthKey, TdKeyData, TdMtpAuthorization } from './types.js' +import { writeLong, writeQByteArray } from './qt-writer.js' + +const TDF_MAGIC = /* #__PURE__ */ utf8.encoder.encode('TDF$') +const TDF_VERSION = 5008003 +const MTP_AUTHORIZATION_BLOCK = 0x4B // see https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/storage/details/storage_settings_scheme.h +const HEX_ALPHABET = '0123456789ABCDEF' + +function toFilePart(key: Uint8Array): string { + let str = '' + // we need to swap nibbles for whatever reason + for (let i = 0; i < 8; i++) { + const b = key[i] + const low = b & 0x0F + const high = b >> 4 + str += HEX_ALPHABET[low] + HEX_ALPHABET[high] + } + return str +} + +export interface TdataOptions { + /** Full path to the tdata directory */ + path: string + + /** + * File system to use for reading/writing. + * + * @default `import('node:fs/promises')` + */ + fs?: INodeFsLike + + /** + * Crypto functions to use for encryption/decryption. + * + * @default `node:crypto`-based implementation + */ + crypto?: IExtendedCryptoProvider + + /** + * Whether to ignore TDF version mismatch. + * If set to `true`, the version will be ignored and the file will be read as is, + * however the probability of errors is higher. + */ + ignoreVersion?: boolean + + /** + * Whether the host machine has LE processor (default true, try changing in case of errors) + */ + le?: boolean + + /** + * Value of -key cli parameter. + * Defaults to `data` + */ + dataKey?: string + + /** + * Local passcode + */ + passcode?: string +} + +export class Tdata { + private constructor( + readonly options: TdataOptions, + readonly fs: INodeFsLike, + readonly crypto: IExtendedCryptoProvider, + ) {} + + readonly keyData!: TdKeyData + + static async open(options: TdataOptions): Promise { + const fs: INodeFsLike = options.fs ?? (await import('node:fs/promises') as unknown as INodeFsLike) + const crypto: IExtendedCryptoProvider = options.crypto ?? (await getDefaultCryptoProvider()) + await crypto.initialize?.() + + const tdata = new Tdata(options, fs, crypto) + ;(tdata as UnsafeMutable).keyData = await tdata.readKeyData() + + return tdata + } + + static async create(options: TdataOptions & { keyData: InputTdKeyData }): Promise { + const fs: INodeFsLike = options.fs ?? (await import('node:fs/promises') as unknown as INodeFsLike) + const crypto: IExtendedCryptoProvider = options.crypto ?? (await getDefaultCryptoProvider()) + await crypto.initialize?.() + + const tdata = new Tdata(options, fs, crypto) + const keyData: TdKeyData = { + ...options.keyData, + localKey: options.keyData.localKey ?? crypto.randomBytes(256), + version: options.keyData.version ?? TDF_VERSION, + } + ;(tdata as UnsafeMutable).keyData = keyData + + await tdata.writeKeyData(keyData) + + return tdata + } + + #readInt32(buf: Uint8Array): number { + return (this.options.le ?? true) ? read.int32le(buf) : read.int32be(buf) + } + + #writeInt32(buf: Uint8Array, val: number): Uint8Array { + if (this.options.le ?? true) { + write.int32le(buf, val) + } else { + write.int32be(buf, val) + } + + return buf + } + + getDataName(idx: number): string { + let res = this.options.dataKey ?? 'data' + + if (idx > 0) { + res += `#${idx + 1}` + } + + return res + } + + async readFile(filename: string): Promise<[number, Uint8Array]> { + const order: string[] = [] + + const modern = `${filename}s` + if (await this.fs.stat(join(this.options.path, modern))) { + order.push(modern) + } else { + const try0 = `${filename}0` + const try1 = `${filename}1` + + const try0s = await this.fs.stat(join(this.options.path, try0)) + const try1s = await this.fs.stat(join(this.options.path, try1)) + + if (try0s) { + order.push(try0) + + if (try1s) { + order.push(try1) + if (try0s.lastModified < try1s.lastModified) { + order.reverse() + } + } + } else if (try1s) { + order.push(try1) + } + } + + let lastError = 'file not found' + + for (const file of order) { + const data = await this.fs.readFile(join(this.options.path, file)) + const magic = data.subarray(0, 4) + + if (!typed.equal(magic, TDF_MAGIC)) { + lastError = 'invalid magic' + continue + } + + const versionBytes = data.subarray(4, 8) + const version = this.#readInt32(versionBytes) + if (version > TDF_VERSION && !this.options.ignoreVersion) { + lastError = `Unsupported version: ${version}` + continue + } + + const dataSize = data.length - 24 + const bytes = data.subarray(8, dataSize + 8) + + const md5 = await this.crypto.createHash('md5') + await md5.update(bytes) + await md5.update(this.#writeInt32(u8.alloc(4), dataSize)) + await md5.update(versionBytes) + await md5.update(magic) + + const hash = await md5.digest() + if (!typed.equal(hash, data.subarray(dataSize + 8))) { + lastError = 'md5 mismatch' + continue + } + + return [version, bytes] + } + + throw new Error(`failed to read ${filename}, last error: ${lastError}`) + } + + async writeFile( + filename: string, + data: Uint8Array, + mkdir = false, + ): Promise { + filename = join(this.options.path, `${filename}s`) + + const version = this.#writeInt32(u8.alloc(4), TDF_VERSION) + const dataSize = this.#writeInt32(u8.alloc(4), data.length) + const md5 = await this.crypto.createHash('md5') + await md5.update(data) + await md5.update(dataSize) + await md5.update(version) + await md5.update(TDF_MAGIC) + + if (mkdir) { + await this.fs.mkdir(dirname(filename), { recursive: true }) + } + + await this.fs.writeFile( + filename, + u8.concat([TDF_MAGIC, version, data, await md5.digest()]), + ) + } + + async createLocalKey( + salt: Uint8Array, + passcode: string = this.options.passcode ?? '', + ): Promise { + const hasher = await this.crypto.createHash('sha512') + hasher.update(salt) + hasher.update(utf8.encoder.encode(passcode)) + hasher.update(salt) + const hash = await hasher.digest() + + return this.crypto.pbkdf2( + hash, + salt, + passcode === '' ? 1 : 100000, + 256, + 'sha512', + ) + } + + async decryptLocal(encrypted: Uint8Array, key: Uint8Array): Promise { + const encryptedKey = encrypted.subarray(0, 16) + const encryptedData = encrypted.subarray(16) + + const ige = createAesIgeForMessageOld( + this.crypto, + key, + encryptedKey, + false, + ) + const decrypted = ige.decrypt(encryptedData) + + if ( + !typed.equal( + this.crypto.sha1(decrypted).subarray(0, 16), + encryptedKey, + ) + ) { + throw new Error('Failed to decrypt, invalid password?') + } + + const fullLen = encryptedData.length + const dataLen = this.#readInt32(decrypted) + + if ( + dataLen > decrypted.length + || dataLen <= fullLen - 16 + || dataLen < 4 + ) { + throw new Error('Failed to decrypt, invalid data length') + } + + return decrypted.subarray(4, dataLen) + } + + async encryptLocal(data: Uint8Array, key: Uint8Array): Promise { + const dataSize = data.length + 4 + const padding: Uint8Array + = dataSize & 0x0F + ? this.crypto.randomBytes(0x10 - (dataSize & 0x0F)) + : u8.empty + + const toEncrypt = u8.alloc(dataSize + padding.length) + this.#writeInt32(toEncrypt, dataSize) + toEncrypt.set(data, 4) + toEncrypt.set(padding, dataSize) + + const encryptedKey = this.crypto.sha1(toEncrypt).subarray(0, 16) + + const ige = createAesIgeForMessageOld( + this.crypto, + key, + encryptedKey, + false, + ) + const encryptedData = ige.encrypt(toEncrypt) + + return u8.concat2(encryptedKey, encryptedData) + } + + async readKeyData(): Promise { + const [version, data] = await this.readFile(`key_${this.options.dataKey ?? 'data'}`) + const bytes = Bytes.from(data) + + const salt = readQByteArray(bytes) + const keyEncrypted = readQByteArray(bytes) + const infoEncrypted = readQByteArray(bytes) + + const passcodeKey = await this.createLocalKey(salt) + const keyInnerData = await this.decryptLocal(keyEncrypted, passcodeKey) + const infoDecrypted = await this.decryptLocal( + infoEncrypted, + keyInnerData, + ) + const info = Bytes.from(infoDecrypted) + + const localKey = keyInnerData + const count = read.int32be(info) + const order = [...Array(count)].map(() => read.int32be(info)) + const active = read.int32be(info) + + return { + version, + localKey, + count, + order, + active, + } + } + + async writeKeyData(keyData: TdKeyData): Promise { + const info = Bytes.alloc() + write.int32be(info, keyData.count) + keyData.order.forEach(i => write.int32be(info, i)) + write.int32be(info, keyData.active) + const infoDecrypted = info.result() + + const infoEncrypted = await this.encryptLocal(infoDecrypted, keyData.localKey) + + const salt = this.crypto.randomBytes(32) + const passcodeKey = await this.createLocalKey(salt) + + const keyEncrypted = await this.encryptLocal(keyData.localKey, passcodeKey) + + const data = Bytes.alloc() + writeQByteArray(data, salt) + writeQByteArray(data, keyEncrypted) + writeQByteArray(data, infoEncrypted) + + await this.writeFile(`key_${this.options.dataKey ?? 'data'}`, data.result(), true) + } + + async computeDataNameKey(accountIdx: number): Promise { + const md5 = await this.crypto.createHash('md5') + await md5.update(utf8.encoder.encode(this.getDataName(accountIdx))) + const r = await md5.digest() + return r.subarray(0, 8) + } + + async computeDataNameKeyHex(accountIdx: number): Promise { + return toFilePart(await this.computeDataNameKey(accountIdx)) + } + + async readEncryptedFile(filename: string): Promise<[number, Uint8Array]> { + const [version, data] = await this.readFile(filename) + + const encrypted = readQByteArray(Bytes.from(data)) + const decrypted = await this.decryptLocal(encrypted, this.keyData.localKey) + + return [version, decrypted] + } + + async writeEncryptedFile( + filename: string, + data: Uint8Array, + mkdir = false, + ): Promise { + const encryptedInner = await this.encryptLocal(data, this.keyData.localKey) + + const writer = Bytes.alloc(data.length + 4) + writeQByteArray(writer, encryptedInner) + + await this.writeFile(filename, writer.result(), mkdir) + } + + async readMtpAuthorization(accountIdx: number = 0): Promise { + const [, mtpData] = await this.readEncryptedFile( + await this.computeDataNameKeyHex(accountIdx), + ) + + // nb: this is pretty much a hack that relies on the fact that + // most of the time the mtp auth data is in the first setting + // since the settings are not length-prefixed, we can't skip unknown settings, + // as we need to know their type. + // and this is very much tied to the actual tdesktop version, and would be a nightmare to maintain + let bytes = Bytes.from(mtpData) + + const header = read.int32be(bytes) + if (header !== MTP_AUTHORIZATION_BLOCK) { + throw new MtUnsupportedError(`expected first setting to be mtp auth data, got 0x${header.toString(16)}`) + } + + const mtpAuthBlock = readQByteArray(bytes) + + bytes = Bytes.from(mtpAuthBlock) + + const legacyUserId = read.int32be(bytes) + const legacyMainDcId = read.int32be(bytes) + + let userId, mainDcId + if (legacyMainDcId === -1 && legacyMainDcId === -1) { + userId = readLong(bytes) + mainDcId = read.int32be(bytes) + } else { + userId = Long.fromInt(legacyUserId) + mainDcId = legacyMainDcId + } + + function readKeys(target: TdAuthKey[]) { + const count = read.uint32be(bytes) + + for (let i = 0; i < count; i++) { + const dcId = read.int32be(bytes) + const key = read.exactly(bytes, 256) + target.push({ dcId, key }) + } + } + + const authKeys: TdAuthKey[] = [] + const authKeysToDestroy: TdAuthKey[] = [] + + readKeys(authKeys) + readKeys(authKeysToDestroy) + + return { + userId, + mainDcId, + authKeys, + authKeysToDestroy, + } + } + + async writeMtpAuthorization(auth: TdMtpAuthorization, accountIdx = 0): Promise { + const bytes = Bytes.alloc() + + // legacy user id & dc id + write.int32be(bytes, -1) + write.int32be(bytes, -1) + writeLong(bytes, auth.userId) + write.int32be(bytes, auth.mainDcId) + + function writeKeys(keys: TdAuthKey[]) { + write.uint32be(bytes, keys.length) + keys.forEach((k) => { + write.int32be(bytes, k.dcId) + write.bytes(bytes, k.key) + }) + } + + writeKeys(auth.authKeys) + writeKeys(auth.authKeysToDestroy) + + const file = Bytes.alloc() + write.int32be(file, MTP_AUTHORIZATION_BLOCK) + writeQByteArray(file, bytes.result()) + + await this.writeEncryptedFile( + await this.computeDataNameKeyHex(accountIdx), + file.result(), + ) + } + + async writeEmptyMapFile(accountIdx: number): Promise { + // without this file tdesktop will not "see" the account + // however just creating an empty file seems to be enough to make it happy + + const writer = Bytes.alloc() + writeQByteArray(writer, u8.empty) // legacySalt + writeQByteArray(writer, u8.empty) // legacyKeyEncrypted + writeQByteArray(writer, await this.encryptLocal(u8.empty, this.keyData.localKey)) + + await this.writeFile( + join(await this.computeDataNameKeyHex(accountIdx), 'map'), + writer.result(), + true, + ) + } +} diff --git a/packages/convert/src/tdesktop/types.ts b/packages/convert/src/tdesktop/types.ts new file mode 100644 index 00000000..8112783e --- /dev/null +++ b/packages/convert/src/tdesktop/types.ts @@ -0,0 +1,26 @@ +import type { Long } from '@mtcute/core' + +export interface TdAuthKey { + dcId: number + key: Uint8Array +} + +export interface TdMtpAuthorization { + userId: Long + mainDcId: number + authKeys: TdAuthKey[] + authKeysToDestroy: TdAuthKey[] +} + +export interface InputTdKeyData { + localKey?: Uint8Array + version?: number + count: number + order: number[] + active: number +} + +export interface TdKeyData extends InputTdKeyData { + version: number + localKey: Uint8Array +} diff --git a/packages/convert/src/utils/crypto.ts b/packages/convert/src/utils/crypto.ts new file mode 100644 index 00000000..7d8d0336 --- /dev/null +++ b/packages/convert/src/utils/crypto.ts @@ -0,0 +1,29 @@ +import type { MaybePromise } from '@fuman/utils' +import type { ICryptoProvider } from '@mtcute/core/utils.js' + +export interface IExtendedCryptoProvider extends ICryptoProvider { + createHash(algorithm: 'md5' | 'sha512'): MaybePromise<{ + update(data: Uint8Array): MaybePromise + digest(): MaybePromise + }> +} + +export async function getDefaultCryptoProvider(): Promise { + const crypto = /* @vite-ignore */ await import('node:crypto') + const { NodeCryptoProvider } = /* @vite-ignore */ await import('@mtcute/node/utils.js') + + return new (class extends NodeCryptoProvider implements IExtendedCryptoProvider { + createHash(algorithm: 'md5' | 'sha512') { + const hasher = crypto.createHash(algorithm) + + return { + update(data: Uint8Array) { + hasher.update(data) + }, + digest() { + return hasher.digest() as unknown as Uint8Array + }, + } + } + })() +} diff --git a/packages/convert/src/utils/fs.ts b/packages/convert/src/utils/fs.ts new file mode 100644 index 00000000..21ba9741 --- /dev/null +++ b/packages/convert/src/utils/fs.ts @@ -0,0 +1,6 @@ +export interface INodeFsLike { + readFile(path: string): Promise + writeFile(path: string, data: Uint8Array): Promise + mkdir(path: string, options?: { recursive?: boolean }): Promise + stat(path: string): Promise<{ size: number, lastModified: number }> +}