Ctrl K
ComunidadGuías…

internal/app/wallet/proof.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package wallet

import (
	"bytes"
	"crypto/ed25519"
	"crypto/sha256"
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"unicode/utf8"

	"github.com/xssnick/tonutils-go/address"
	"github.com/xssnick/tonutils-go/tlb"
	"github.com/xssnick/tonutils-go/tvm/cell"
)

var ErrOwnershipProof = errors.New("invalid wallet ownership proof")

// Trampoline and revision-00 data layout match wallet-engine's public state
// derivation. A rotated signing key MUST NOT be used as an address anchor.
const trampolineBOC = "te6ccgEBAQEAGgAAMP8AIJgh10mDCLnyQN+Ahfgz0O0eIO1T2Q=="
const trampolineHash = "9149ae51c1e4689710cebf7830297b16acfbadb363a920a537893e7ffeeca768"

// InitialAddress derives the Wallet revision-00 account from its ANCHOR key.
// It does not infer a rotated account's address from its current signing key.
func InitialAddress(anchorKey []byte, testnet bool) (*address.Address, error) {
	if len(anchorKey) != ed25519.PublicKeySize {
		return nil, ErrOwnershipProof
	}
	boc, err := base64.StdEncoding.DecodeString(trampolineBOC)
	if err != nil {
		return nil, err
	}
	code, err := cell.FromBOC(boc)
	if err != nil {
		return nil, err
	}
	expected, _ := hex.DecodeString(trampolineHash)
	if !bytes.Equal(code.Hash(), expected) {
		return nil, errors.New("wallet trampoline hash mismatch")
	}
	walletID := uint64(0x7fff7f11)
	if testnet {
		walletID = 0x7fff7ffd
	}
	data := cell.BeginCell().MustStoreUInt(0, 8).MustStoreUInt(0, 32).MustStoreUInt(walletID, 32).MustStoreSlice(anchorKey, 256).EndCell()
	init, err := tlb.ToCell(tlb.StateInit{Code: code, Data: data})
	if err != nil {
		return nil, err
	}
	addr := address.NewAddress(0, 0, init.Hash())
	addr.SetTestnetOnly(testnet)
	return addr, nil
}

// ProofHash is the TON Connect v2 signing digest used by the current iOS wallet
// link flow. Domain and payload must be the server-issued values, never values
// taken solely from an untrusted request. Replay/expiry checks belong to the
// atomic challenge-consumption transaction in the RPC service.
func ProofHash(workchain int32, accountHash []byte, domain string, timestamp uint64, payload string) ([32]byte, error) {
	if len(accountHash) != 32 || len(domain) == 0 || len(domain) > 2048 || len(payload) > 65536 || !utf8.ValidString(domain) || !utf8.ValidString(payload) {
		return [32]byte{}, ErrOwnershipProof
	}
	message := make([]byte, 0, 80+len(domain)+len(payload))
	message = append(message, "ton-proof-item-v2/"...)
	message = binary.BigEndian.AppendUint32(message, uint32(workchain))
	message = append(message, accountHash...)
	message = binary.LittleEndian.AppendUint32(message, uint32(len(domain)))
	message = append(message, domain...)
	message = binary.LittleEndian.AppendUint64(message, timestamp)
	message = append(message, payload...)
	inner := sha256.Sum256(message)
	outer := append([]byte{0xff, 0xff}, "ton-connect"...)
	outer = append(outer, inner[:]...)
	return sha256.Sum256(outer), nil
}

// VerifyProof verifies the signature for an independently authenticated
// account/key mapping. It does NOT by itself establish that a key owns an
// address: obtain that mapping from validated StateInit or current chain state.
func VerifyProof(workchain int32, accountHash, signingPublicKey []byte, domain string, timestamp uint64, payload string, signature []byte) error {
	if len(signingPublicKey) != ed25519.PublicKeySize || len(signature) != ed25519.SignatureSize {
		return ErrOwnershipProof
	}
	hash, err := ProofHash(workchain, accountHash, domain, timestamp, payload)
	if err != nil || !ed25519.Verify(ed25519.PublicKey(signingPublicKey), hash[:], signature) {
		return ErrOwnershipProof
	}
	return nil
}