mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 00:45:29 +08:00
148 lines
3.9 KiB
Go
148 lines
3.9 KiB
Go
package pow
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
)
|
|
|
|
// Challenge 对应 /api/v0/chat/create_pow_challenge 返回 dem data.biz_data.challenge。
|
|
type Challenge struct {
|
|
Algorithm string `json:"algorithm"`
|
|
Challenge string `json:"challenge"`
|
|
Salt string `json:"salt"`
|
|
ExpireAt int64 `json:"expire_at"`
|
|
Difficulty int64 `json:"difficulty"`
|
|
Signature string `json:"signature"`
|
|
TargetPath string `json:"target_path"`
|
|
}
|
|
|
|
// BuildPrefix: "<salt>_<expire_at>_" (对应 pow.go:89)
|
|
func BuildPrefix(salt string, expireAt int64) string {
|
|
return salt + "_" + strconv.FormatInt(expireAt, 10) + "_"
|
|
}
|
|
|
|
// SolvePow 搜索 nonce ∈ [0, difficulty) 使得 DeepSeekHashV1(prefix+str(nonce)) == challenge。
|
|
// prefix 预吸收进 state,循环内零分配。
|
|
func SolvePow(ctx context.Context, challengeHex, salt string, expireAt, difficulty int64) (int64, error) {
|
|
if len(challengeHex) != 64 {
|
|
return 0, errors.New("pow: challenge must be 64 hex chars")
|
|
}
|
|
target, err := hex.DecodeString(challengeHex)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var ta [32]byte
|
|
copy(ta[:], target)
|
|
t0 := binary.LittleEndian.Uint64(ta[0:])
|
|
t1 := binary.LittleEndian.Uint64(ta[8:])
|
|
t2 := binary.LittleEndian.Uint64(ta[16:])
|
|
t3 := binary.LittleEndian.Uint64(ta[24:])
|
|
|
|
prefix := []byte(BuildPrefix(salt, expireAt))
|
|
const rate = 136
|
|
var baseState [25]uint64
|
|
off := 0
|
|
for off+rate <= len(prefix) {
|
|
for i := 0; i < rate/8; i++ {
|
|
baseState[i] ^= binary.LittleEndian.Uint64(prefix[off+i*8:])
|
|
}
|
|
keccakF23(&baseState)
|
|
off += rate
|
|
}
|
|
tailLen := len(prefix) - off
|
|
var tail [rate]byte
|
|
copy(tail[:], prefix[off:])
|
|
|
|
var numBuf [20]byte
|
|
for n := int64(0); n < difficulty; n++ {
|
|
// Periodically check if context is canceled to avoid wasting CPU
|
|
if n&0x3FF == 0 {
|
|
if err := ctx.Err(); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
v := uint64(n)
|
|
pos := 20
|
|
if v == 0 {
|
|
pos--
|
|
numBuf[pos] = '0'
|
|
} else {
|
|
for v > 0 {
|
|
pos--
|
|
numBuf[pos] = byte('0' + v%10)
|
|
v /= 10
|
|
}
|
|
}
|
|
numLen := 20 - pos
|
|
s := baseState
|
|
totalTail := tailLen + numLen
|
|
if totalTail < rate {
|
|
var buf [rate]byte
|
|
copy(buf[:tailLen], tail[:tailLen])
|
|
copy(buf[tailLen:totalTail], numBuf[pos:])
|
|
buf[totalTail] = 0x06
|
|
buf[rate-1] |= 0x80
|
|
for i := 0; i < rate/8; i++ {
|
|
s[i] ^= binary.LittleEndian.Uint64(buf[i*8:])
|
|
}
|
|
keccakF23(&s)
|
|
} else {
|
|
var buf [rate]byte
|
|
copy(buf[:tailLen], tail[:tailLen])
|
|
copy(buf[tailLen:rate], numBuf[pos:pos+(rate-tailLen)])
|
|
for i := 0; i < rate/8; i++ {
|
|
s[i] ^= binary.LittleEndian.Uint64(buf[i*8:])
|
|
}
|
|
keccakF23(&s)
|
|
var buf2 [rate]byte
|
|
rem := totalTail - rate
|
|
copy(buf2[:rem], numBuf[pos+(rate-tailLen):pos+(rate-tailLen)+rem])
|
|
buf2[rem] = 0x06
|
|
buf2[rate-1] |= 0x80
|
|
for i := 0; i < rate/8; i++ {
|
|
s[i] ^= binary.LittleEndian.Uint64(buf2[i*8:])
|
|
}
|
|
keccakF23(&s)
|
|
}
|
|
if s[0] == t0 && s[1] == t1 && s[2] == t2 && s[3] == t3 {
|
|
return n, nil
|
|
}
|
|
}
|
|
return 0, errors.New("pow: no solution within difficulty")
|
|
}
|
|
|
|
// BuildPowHeader 序列化 {algorithm,challenge,salt,answer,signature,target_path} 为 base64(JSON)。
|
|
// 不含 difficulty/expire_at (对应 pow.go:218)。
|
|
func BuildPowHeader(c *Challenge, answer int64) (string, error) {
|
|
b, err := json.Marshal(map[string]any{
|
|
"algorithm": c.Algorithm, "challenge": c.Challenge, "salt": c.Salt,
|
|
"answer": answer, "signature": c.Signature, "target_path": c.TargetPath,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(b), nil
|
|
}
|
|
|
|
// SolveAndBuildHeader 端到端: Challenge → x-ds-pow-response header string。
|
|
func SolveAndBuildHeader(ctx context.Context, c *Challenge) (string, error) {
|
|
if c.Algorithm != "DeepSeekHashV1" {
|
|
return "", errors.New("pow: unsupported algorithm: " + c.Algorithm)
|
|
}
|
|
d := c.Difficulty
|
|
if d == 0 {
|
|
d = 144000
|
|
}
|
|
answer, err := SolvePow(ctx, c.Challenge, c.Salt, c.ExpireAt, d)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return BuildPowHeader(c, answer)
|
|
}
|