move gotd fork into repo. (#111)

- update to latest telegram layer
- remove some references to fields in tg.Entities that don't exist in
the schema
- originally added here:
https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e
  - referenced here
-
https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3
-
https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
This commit is contained in:
Adam Van Ymeren
2025-06-27 20:03:37 -07:00
committed by GitHub
parent 0952df0244
commit 7a04f298d2
19264 changed files with 1539697 additions and 84 deletions
+118
View File
@@ -0,0 +1,118 @@
package srp
import (
"crypto/sha256"
"crypto/sha512"
"math/big"
"github.com/go-faster/errors"
"golang.org/x/crypto/pbkdf2"
)
// Hash computes user password hash using parameters from server.
//
// See https://core.telegram.org/api/srp#checking-the-password-with-srp.
func (s SRP) Hash(password, srpB, random []byte, i Input) (Answer, error) {
p := s.bigFromBytes(i.P)
if err := checkInput(i.G, p); err != nil {
return Answer{}, errors.Wrap(err, "validate algo")
}
g := big.NewInt(int64(i.G))
// It is safe to use FillBytes directly because we know that 64-bit G always smaller than
// 256-bit destination array.
var gBytes [256]byte
g.FillBytes(gBytes[:])
// random 2048-bit number a
a := s.bigFromBytes(random)
// `g_a = pow(g, a) mod p`
ga, ok := s.pad256FromBig(s.bigExp(g, a, p))
if !ok {
return Answer{}, errors.New("g_a is too big")
}
// `g_b = srp_B`
gb := s.pad256(srpB)
// `u = H(g_a | g_b)`
u := s.bigFromBytes(s.hash(ga[:], gb[:]))
// `x = PH2(password, salt1, salt2)`
// `v = pow(g, x) mod p`
x, v := s.computeXV(password, i.Salt1, i.Salt2, g, p)
// `k = (k * v) mod p`
k := s.bigFromBytes(s.hash(i.P, gBytes[:]))
// `k_v = (k * v) % p`
kv := k.Mul(k, v).Mod(k, p)
// `t = (g_b - k_v) % p`
t := s.bigFromBytes(srpB)
if t.Sub(t, kv).Cmp(big.NewInt(0)) == -1 {
t.Add(t, p)
}
// `s_a = pow(t, a + u * x) mod p`
sa, ok := s.pad256FromBig(s.bigExp(t, u.Mul(u, x).Add(u, a), p))
if !ok {
return Answer{}, errors.New("s_a is too big")
}
// `k_a = H(s_a)`
ka := sha256.Sum256(sa[:])
// `M1 = H(H(p) xor H(g) | H2(salt1) | H2(salt2) | g_a | g_b | k_a)`
xorHpHg := xor32(sha256.Sum256(i.P), sha256.Sum256(gBytes[:]))
M1 := s.hash(
xorHpHg[:],
s.hash(i.Salt1),
s.hash(i.Salt2),
ga[:],
gb[:],
ka[:],
)
return Answer{
A: ga[:],
M1: M1,
}, nil
}
// The main hashing function H is sha256:
//
// H(data) := sha256(data)
func (s SRP) hash(data ...[]byte) []byte {
h := sha256.New()
for i := range data {
h.Write(data[i])
}
return h.Sum(nil)
}
// The salting hashing function SH is defined as follows:
//
// SH(data, salt) := H(salt | data | salt)
func (s SRP) saltHash(data, salt []byte) []byte {
return s.hash(salt, data, salt)
}
// The primary password hashing function is defined as follows:
//
// PH1(password, salt1, salt2) := SH(SH(password, salt1), salt2)
func (s SRP) primary(password, salt1, salt2 []byte) []byte {
return s.saltHash(s.saltHash(password, salt1), salt2)
}
// The secondary password hashing function is defined as follows:
//
// PH2(password, salt1, salt2) := SH(pbkdf2(sha512, PH1(password, salt1, salt2), salt1, 100000), salt2)
func (s SRP) secondary(password, salt1, salt2 []byte) []byte {
return s.saltHash(s.pbkdf2(s.primary(password, salt1, salt2), salt1, 100000), salt2)
}
func (s SRP) pbkdf2(ph1, salt1 []byte, n int) []byte {
return pbkdf2.Key(ph1, salt1, n, 64, sha512.New)
}
+54
View File
@@ -0,0 +1,54 @@
package srp
import (
"io"
"math/big"
"github.com/go-faster/errors"
)
// computeXV computes following numbers
//
// `x = PH2(password, salt1, salt2)`
// `v = pow(g, x) mod p`
//
// TDLib uses terms `clientSalt` for `salt1` and `serverSalt` for `salt2`.
func (s SRP) computeXV(password, clientSalt, serverSalt []byte, g, p *big.Int) (x, v *big.Int) {
// `x = PH2(password, salt1, salt2)`
x = new(big.Int).SetBytes(s.secondary(password, clientSalt, serverSalt))
// `v = pow(g, x) mod p`
v = new(big.Int).Exp(g, x, p)
return x, v
}
// NewHash computes new user password hash using parameters from server.
//
// See https://core.telegram.org/api/srp#setting-a-new-2fa-password.
//
// TDLib implementation:
// See https://github.com/tdlib/td/blob/fa8feefed70d64271945e9d5fd010b957d93c8cd/td/telegram/PasswordManager.cpp#L57.
//
// TDesktop implementation:
// See https://github.com/telegramdesktop/tdesktop/blob/v3.4.8/Telegram/SourceFiles/core/core_cloud_password.cpp#L68.
func (s SRP) NewHash(password []byte, i Input) (hash, newSalt []byte, _ error) {
// Generate a new new_password_hash using the KDF algorithm specified in the new_settings,
// just append 32 sufficiently random bytes to the salt1, first. Proceed as for checking passwords with SRP,
// just stop at the generation of the v parameter, and use it as new_password_hash:
p := new(big.Int).SetBytes(i.P)
if err := checkInput(i.G, p); err != nil {
return nil, nil, errors.Wrap(err, "validate algo")
}
// Make a copy.
newClientSalt := append([]byte(nil), i.Salt1...)
newClientSalt = append(newClientSalt, make([]byte, 32)...)
// ... append 32 sufficiently random bytes to the salt1 ...
if _, err := io.ReadFull(s.random, newClientSalt[len(newClientSalt)-32:]); err != nil {
return nil, nil, err
}
_, v := s.computeXV(password, newClientSalt, i.Salt2, big.NewInt(int64(i.G)), p)
// As usual in big endian form, padded to 2048 bits.
padded, _ := s.pad256FromBig(v)
return padded[:], newClientSalt, nil
}
+72
View File
@@ -0,0 +1,72 @@
package srp
import (
"testing"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
)
func TestSRP_NewHash(t *testing.T) {
password := []uint8{
110, 101, 119, 80, 97, 115, 115, 119, 111, 114, 100,
}
i := Input{
Salt1: []uint8{
230, 200, 149, 125, 223, 152, 141, 72,
},
Salt2: []uint8{
159, 99, 68, 130, 43, 9, 108, 255, 135, 239, 164, 38, 245, 120, 87, 182,
},
G: 3,
P: []uint8{
199, 28, 174, 185, 198, 177, 201, 4, 142, 108, 82, 47, 112, 241, 63, 115,
152, 13, 64, 35, 142, 62, 33, 193, 73, 52, 208, 55, 86, 61, 147, 15,
72, 25, 138, 10, 167, 193, 64, 88, 34, 148, 147, 210, 37, 48, 244, 219,
250, 51, 111, 110, 10, 201, 37, 19, 149, 67, 174, 212, 76, 206, 124, 55,
32, 253, 81, 246, 148, 88, 112, 90, 198, 140, 212, 254, 107, 107, 19, 171,
220, 151, 70, 81, 41, 105, 50, 132, 84, 241, 143, 175, 140, 89, 95, 100,
36, 119, 254, 150, 187, 42, 148, 29, 91, 205, 29, 74, 200, 204, 73, 136,
7, 8, 250, 155, 55, 142, 60, 79, 58, 144, 96, 190, 230, 124, 249, 164,
164, 166, 149, 129, 16, 81, 144, 126, 22, 39, 83, 181, 107, 15, 107, 65,
13, 186, 116, 216, 168, 75, 42, 20, 179, 20, 78, 14, 241, 40, 71, 84,
253, 23, 237, 149, 13, 89, 101, 180, 185, 221, 70, 88, 45, 177, 23, 141,
22, 156, 107, 196, 101, 176, 214, 255, 156, 163, 146, 143, 239, 91, 154, 228,
228, 24, 252, 21, 232, 62, 190, 160, 248, 127, 169, 255, 94, 237, 112, 5,
13, 237, 40, 73, 244, 123, 249, 89, 217, 86, 133, 12, 233, 41, 133, 31,
13, 129, 21, 246, 53, 177, 5, 238, 46, 78, 21, 208, 75, 36, 84, 191,
111, 79, 173, 240, 52, 177, 4, 3, 17, 156, 216, 227, 185, 47, 204, 91,
},
}
expectedHash := []uint8{
24, 106, 193, 141, 204, 87, 144, 191, 107, 186, 33, 189, 149, 141, 55, 94,
229, 72, 26, 240, 2, 158, 155, 215, 169, 198, 142, 201, 38, 189, 81, 150,
216, 31, 140, 216, 181, 142, 224, 108, 138, 16, 173, 234, 204, 127, 86, 232,
25, 255, 81, 72, 37, 222, 177, 91, 31, 173, 236, 106, 174, 23, 162, 68,
203, 35, 72, 141, 23, 52, 156, 212, 38, 26, 139, 164, 218, 123, 156, 44,
229, 196, 0, 20, 221, 158, 54, 39, 80, 172, 243, 172, 137, 184, 184, 245,
198, 24, 240, 182, 165, 114, 195, 143, 255, 58, 85, 77, 136, 24, 160, 184,
231, 182, 1, 94, 24, 54, 18, 138, 30, 78, 45, 92, 249, 151, 29, 29,
208, 72, 170, 24, 29, 134, 17, 82, 234, 231, 21, 83, 150, 38, 128, 99,
35, 135, 184, 154, 193, 134, 95, 222, 215, 200, 195, 218, 166, 78, 211, 141,
194, 80, 54, 102, 63, 160, 207, 119, 72, 197, 46, 161, 156, 24, 126, 112,
167, 82, 168, 5, 62, 64, 157, 72, 148, 33, 138, 66, 147, 147, 208, 51,
130, 228, 30, 80, 183, 65, 91, 59, 138, 208, 146, 253, 7, 144, 248, 141,
137, 78, 132, 220, 167, 143, 71, 244, 33, 137, 55, 215, 170, 153, 216, 140,
135, 192, 155, 203, 141, 168, 144, 229, 53, 2, 102, 35, 206, 166, 252, 139,
61, 37, 219, 112, 203, 66, 170, 164, 131, 35, 146, 125, 135, 168, 252, 241,
}
expectedNewSalt := []uint8{
230, 200, 149, 125, 223, 152, 141, 72, 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,
}
a := require.New(t)
s := NewSRP(testutil.ZeroRand{})
hash, newSalt, err := s.NewHash(password, i)
a.NoError(err)
a.Equal(expectedHash, hash)
a.Equal(expectedNewSalt, newSalt)
}
+23
View File
@@ -0,0 +1,23 @@
package srp
import (
"math/big"
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
)
func (s SRP) pad256FromBig(i *big.Int) (b [256]byte, r bool) {
r = crypto.FillBytes(i, b[:])
return b, r
}
func (s SRP) pad256(b []byte) [256]byte {
if len(b) >= 256 {
return *(*[256]byte)(b[len(b)-256:])
}
var tmp [256]byte
copy(tmp[256-len(b):], b)
return tmp
}
+63
View File
@@ -0,0 +1,63 @@
// Package srp contains implementation of Secure Remote Password protocol.
package srp
import (
"crypto/sha256"
"io"
"math/big"
"github.com/go-faster/xor"
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
)
// SRP is client implementation of Secure Remote Password protocol.
//
// See https://core.telegram.org/api/srp.
type SRP struct {
random io.Reader
}
// NewSRP creates new SRP instance.
func NewSRP(random io.Reader) SRP {
return SRP{random: random}
}
// Input is hashing algorithm parameters from server.
//
// Copy of tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow
type Input struct {
// One of two salts used by the derivation function (see SRP 2FA login)
Salt1 []byte
// One of two salts used by the derivation function (see SRP 2FA login)
Salt2 []byte
// Base (see SRP 2FA login)
G int
// 2048-bit modulus (see SRP 2FA login)
P []byte
}
// Answer is result of SRP algorithm.
type Answer struct {
// A parameter (see SRP)
A []byte
// M1 parameter (see SRP)
M1 []byte
}
func xor32(a, b [sha256.Size]byte) (res [sha256.Size]byte) {
xor.Bytes(res[:], a[:], b[:])
return res
}
func (s SRP) bigFromBytes(b []byte) *big.Int {
return new(big.Int).SetBytes(b)
}
func (s SRP) bigExp(x, y, m *big.Int) *big.Int {
return new(big.Int).Exp(x, y, m)
}
func checkInput(g int, p *big.Int) error {
return crypto.CheckDH(g, p)
}
+122
View File
@@ -0,0 +1,122 @@
package srp
import (
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
type srpArgs struct {
password []byte
srpB []byte
mp Input
}
func testSRPInput(t testing.TB) srpArgs {
return srpArgs{
password: []byte("123123"),
srpB: getHex(t, "9C52401A6A8084EC82F01C3725D3FB448BD2F0C909F9D97726EAC4B7A74172D9"+
"52F02466BE6734FA274D2B7429E27397F10372D66B400B80A5C5AE3F28B17BF3"+
"105D7A2D2A885998CDC2DEFC208AEC217AB58859A9ABC2374AD93DC285F4B3FB"+
"CAFF4143D7888F2425BD2FB711B25609CEB21757D935B1EF2F042173AD0CE2FE"+
"0E474DAC53914BD25A8A9AED4AEA8953D55CB88621DB37B871EA0D04393AC098"+
"7F68094CCC9DE8239251375D8FFFD263316CD528C097B7BC9FB919FBEDB76C52"+
"5DF3413C374EE076D97A1E6D352BB7CC80FD13651B04B32E2E48C5268150842C"+
"FD07CF855958B1B5EA9C36FDAD697FE3AEC8DCC6B1EFEC36874AF226204676CF"),
mp: Input{
Salt1: getHex(t, "4D11FB6BEC38F9D2546BB0F61E4F1C99A1BC0DB8F0D5F35B1291B37B213123D7ED48F3C6794D495B"),
Salt2: getHex(t, "A1B181AAFE88188680AE32860D60BB01"),
G: 3,
P: getHex(t, "C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F"+
"48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37"+
"20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64"+
"2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4"+
"A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754"+
"FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4"+
"E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F"+
"0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B"),
},
}
}
func TestSRP(t *testing.T) {
tests := []struct {
args srpArgs
want Answer
expectError assert.ErrorAssertionFunc
}{
{
args: testSRPInput(t),
want: Answer{
A: setByte(256, 3),
M1: getHex(t, "999DF906BDA2C6CBB52F503406EBA2D0D0503ACE0CC302C38F13EE5010AD4051"),
},
expectError: assert.NoError,
},
}
for i := range tests {
tcase := tests[i]
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
random := setByte(256, 1)
srp := NewSRP(rand.Reader)
got, err := srp.Hash(tcase.args.password, tcase.args.srpB, random, tcase.args.mp)
if !tcase.expectError(t, err) {
return
}
if !assert.Equal(t, tcase.want, got) {
return
}
})
}
}
func getHex(t testing.TB, in string) []byte {
res, err := hex.DecodeString(in)
if err != nil {
t.Fatal("failed to get hex", err)
}
return res
}
func setByte(size, value int) []byte {
res := make([]byte, size)
binary.BigEndian.PutUint32(res[size-4:], uint32(value))
return res
}
const pBase64 = "xxyuucaxyQSObFIvcPE_c5gNQCOOPiHBSTTQN1Y9kw9IGYoKp8FAWCKUk9IlMPTb-jNvbgrJJROVQ67UTM58NyD9UfaUWHBaxozU_" +
"mtrE6vcl0ZRKWkyhFTxj6-MWV9kJHf-lrsqlB1bzR1KyMxJiAcI-ps3jjxPOpBgvuZ8-aSkppWBEFGQfhYnU7VrD2tBDbp02KhLKhSzFE4O8ShHVP0" +
"X7ZUNWWW0ud1GWC2xF40WnGvEZbDW_5yjko_vW5rk5Bj8Feg-vqD4f6n_Xu1wBQ3tKEn0e_lZ2VaFDOkphR8NgRX2NbEF7i5OFdBLJFS_b0-" +
"t8DSxBAMRnNjjuS_MWw=="
func Test_checkInput(t *testing.T) {
p, err := base64.URLEncoding.DecodeString(pBase64)
if err != nil {
t.Fatal("no err expected", err)
}
err = checkInput(3, big.NewInt(0).SetBytes(p))
if err != nil {
t.Fatal("no err expected", err)
}
}
func BenchmarkSRP_Auth(b *testing.B) {
input := testSRPInput(b)
srp := NewSRP(rand.Reader)
random := setByte(256, 1)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = srp.Hash(input.password, input.srpB, random, input.mp)
}
}