mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-02 07:25:26 +08:00
190 lines
4.4 KiB
Go
190 lines
4.4 KiB
Go
package deepseek
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"math"
|
|
"os"
|
|
"sync"
|
|
|
|
"ds2api/internal/config"
|
|
|
|
"github.com/tetratelabs/wazero"
|
|
"github.com/tetratelabs/wazero/api"
|
|
)
|
|
|
|
type PowSolver struct {
|
|
wasmPath string
|
|
once sync.Once
|
|
err error
|
|
|
|
runtime wazero.Runtime
|
|
compiled wazero.CompiledModule
|
|
}
|
|
|
|
func NewPowSolver(wasmPath string) *PowSolver {
|
|
return &PowSolver{wasmPath: wasmPath}
|
|
}
|
|
|
|
func (p *PowSolver) init(ctx context.Context) error {
|
|
p.once.Do(func() {
|
|
wasmBytes, err := os.ReadFile(p.wasmPath)
|
|
if err != nil {
|
|
p.err = err
|
|
return
|
|
}
|
|
p.runtime = wazero.NewRuntime(ctx)
|
|
p.compiled, p.err = p.runtime.CompileModule(ctx, wasmBytes)
|
|
})
|
|
return p.err
|
|
}
|
|
|
|
func (p *PowSolver) Compute(ctx context.Context, challenge map[string]any) (int64, error) {
|
|
if err := p.init(ctx); err != nil {
|
|
return 0, err
|
|
}
|
|
algo, _ := challenge["algorithm"].(string)
|
|
if algo != "DeepSeekHashV1" {
|
|
return 0, errors.New("unsupported algorithm")
|
|
}
|
|
challengeStr, _ := challenge["challenge"].(string)
|
|
salt, _ := challenge["salt"].(string)
|
|
signature, _ := challenge["signature"].(string)
|
|
targetPath, _ := challenge["target_path"].(string)
|
|
_ = signature
|
|
_ = targetPath
|
|
|
|
difficulty := toFloat64(challenge["difficulty"], 144000)
|
|
expireAt := toInt64(challenge["expire_at"], 1680000000)
|
|
prefix := salt + "_" + itoa(expireAt) + "_"
|
|
|
|
mod, err := p.runtime.InstantiateModule(ctx, p.compiled, wazero.NewModuleConfig())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer mod.Close(ctx)
|
|
|
|
mem := mod.Memory()
|
|
if mem == nil {
|
|
return 0, errors.New("wasm memory missing")
|
|
}
|
|
stackFn := mod.ExportedFunction("__wbindgen_add_to_stack_pointer")
|
|
allocFn := mod.ExportedFunction("__wbindgen_export_0")
|
|
solveFn := mod.ExportedFunction("wasm_solve")
|
|
if stackFn == nil || allocFn == nil || solveFn == nil {
|
|
return 0, errors.New("required wasm exports missing")
|
|
}
|
|
|
|
retPtrs, err := stackFn.Call(ctx, uint64(uint32(^uint32(15)))) // -16 i32
|
|
if err != nil || len(retPtrs) == 0 {
|
|
return 0, errors.New("stack alloc failed")
|
|
}
|
|
retptr := uint32(retPtrs[0])
|
|
defer stackFn.Call(ctx, 16)
|
|
|
|
chPtr, chLen, err := writeUTF8(ctx, allocFn, mem, challengeStr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
prefixPtr, prefixLen, err := writeUTF8(ctx, allocFn, mem, prefix)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if _, err := solveFn.Call(ctx,
|
|
uint64(retptr),
|
|
uint64(chPtr), uint64(chLen),
|
|
uint64(prefixPtr), uint64(prefixLen),
|
|
math.Float64bits(difficulty),
|
|
); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
statusBytes, ok := mem.Read(retptr, 4)
|
|
if !ok {
|
|
return 0, errors.New("read status failed")
|
|
}
|
|
status := int32(binary.LittleEndian.Uint32(statusBytes))
|
|
valueBytes, ok := mem.Read(retptr+8, 8)
|
|
if !ok {
|
|
return 0, errors.New("read value failed")
|
|
}
|
|
value := math.Float64frombits(binary.LittleEndian.Uint64(valueBytes))
|
|
if status == 0 {
|
|
return 0, errors.New("pow solve failed")
|
|
}
|
|
return int64(value), nil
|
|
}
|
|
|
|
func writeUTF8(ctx context.Context, allocFn api.Function, mem api.Memory, text string) (uint32, uint32, error) {
|
|
data := []byte(text)
|
|
res, err := allocFn.Call(ctx, uint64(len(data)), 1)
|
|
if err != nil || len(res) == 0 {
|
|
return 0, 0, errors.New("alloc failed")
|
|
}
|
|
ptr := uint32(res[0])
|
|
if !mem.Write(ptr, data) {
|
|
return 0, 0, errors.New("mem write failed")
|
|
}
|
|
return ptr, uint32(len(data)), nil
|
|
}
|
|
|
|
func BuildPowHeader(challenge map[string]any, answer int64) (string, error) {
|
|
payload := map[string]any{
|
|
"algorithm": challenge["algorithm"],
|
|
"challenge": challenge["challenge"],
|
|
"salt": challenge["salt"],
|
|
"answer": answer,
|
|
"signature": challenge["signature"],
|
|
"target_path": challenge["target_path"],
|
|
}
|
|
b, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(b), nil
|
|
}
|
|
|
|
func toFloat64(v any, d float64) float64 {
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return n
|
|
case int:
|
|
return float64(n)
|
|
case int64:
|
|
return float64(n)
|
|
default:
|
|
return d
|
|
}
|
|
}
|
|
|
|
func toInt64(v any, d int64) int64 {
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return int64(n)
|
|
case int:
|
|
return int64(n)
|
|
case int64:
|
|
return n
|
|
default:
|
|
return d
|
|
}
|
|
}
|
|
|
|
func itoa(n int64) string {
|
|
b, _ := json.Marshal(n)
|
|
return string(b)
|
|
}
|
|
|
|
func PreloadWASM(wasmPath string) {
|
|
solver := NewPowSolver(wasmPath)
|
|
if err := solver.init(context.Background()); err != nil {
|
|
config.Logger.Warn("[WASM] preload failed", "error", err)
|
|
return
|
|
}
|
|
config.Logger.Info("[WASM] module preloaded", "path", wasmPath)
|
|
}
|