Skip to content

Commit

Permalink
Merge pull request #164 from tonkeeper/tonconnect-proof
Browse files Browse the repository at this point in the history
Add tonconnect.CreateSignedProof to generate proof for tonconnect challenge
  • Loading branch information
mr-tron authored Aug 2, 2023
2 parents ff714c2 + 1e37058 commit 17902ba
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 5 deletions.
50 changes: 50 additions & 0 deletions tonconnect/proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package tonconnect

import (
"crypto/ed25519"
"encoding/base64"
"time"

"github.com/tonkeeper/tongo"
"github.com/tonkeeper/tongo/boc"
"github.com/tonkeeper/tongo/tlb"
)

// ProofOptions configures particular aspects of a proof.
type ProofOptions struct {
Timestamp time.Time
Domain string
}

// CreateSignedProof returns a proof that the caller posses a private key of a particular account.
// This can be used on the client side,
// when the server side runs tonconnect.Server or any other server implementation of ton-connect.
func CreateSignedProof(payload string, accountID tongo.AccountID, privateKey ed25519.PrivateKey, stateInit tlb.StateInit, options ProofOptions) (*Proof, error) {
stateInitCell := boc.NewCell()
if err := tlb.Marshal(stateInitCell, stateInit); err != nil {
return nil, err
}
stateInitBase64, err := stateInitCell.ToBocBase64()
if err != nil {
return nil, err
}
proof := Proof{
Address: accountID.String(),
Proof: ProofData{
Timestamp: options.Timestamp.Unix(),
Domain: options.Domain,
Payload: payload,
StateInit: stateInitBase64,
},
}
parsedMsg, err := convertTonProofMessage(&proof)
if err != nil {
return nil, err
}
msg, err := createMessage(parsedMsg)
if err != nil {
return nil, err
}
proof.Proof.Signature = base64.StdEncoding.EncodeToString(signMessage(privateKey, msg))
return &proof, nil
}
71 changes: 71 additions & 0 deletions tonconnect/proof_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package tonconnect

import (
"context"
"crypto/ed25519"
"testing"
"time"

"github.com/tonkeeper/tongo/liteapi"
"github.com/tonkeeper/tongo/wallet"
)

func TestCreateSignedProof(t *testing.T) {
cli, err := liteapi.NewClient(liteapi.Testnet())
if err != nil {
t.Fatalf("liteapi.NewClient() failed: %v", err)
}
tests := []struct {
name string
version wallet.Version
secret string
}{
{
name: "v4r2",
version: wallet.V4R2,
secret: "some-random-secret",
},
{
name: "v4r1",
version: wallet.V4R1,
secret: "another-random-secret",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv, err := NewTonConnect(cli, tt.secret)
if err != nil {
t.Fatalf("NewTonConnect() failed: %v", err)
}
payload, err := srv.GeneratePayload()
if err != nil {
t.Fatalf("GeneratePayload() failed: %v", err)
}
seed := wallet.RandomSeed()
privateKey, err := wallet.SeedToPrivateKey(seed)
if err != nil {
t.Fatalf("SeedToPrivateKey() failed: %v", err)
}
publicKey := privateKey.Public().(ed25519.PublicKey)
stateInit, err := wallet.GenerateStateInit(publicKey, tt.version, 0, nil)
if err != nil {
t.Fatalf("GenerateStateInit() failed: %v", err)
}
accountID, err := wallet.GenerateWalletAddress(publicKey, tt.version, 0, nil)
if err != nil {
t.Fatalf("GenerateWalletAddress() failed: %v", err)
}
signedProof, err := CreateSignedProof(payload, accountID, privateKey, stateInit, ProofOptions{Timestamp: time.Now(), Domain: "web"})
if err != nil {
t.Fatalf("CreateSignedProof() failed: %v", err)
}
verified, _, err := srv.CheckProof(context.Background(), signedProof)
if err != nil {
t.Fatalf("CheckProof() failed: %v", err)
}
if verified != true {
t.Fatalf("proof is invalid")
}
})
}
}
8 changes: 6 additions & 2 deletions tonconnect/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (s *Server) CheckProof(ctx context.Context, tp *Proof) (bool, ed25519.Publi
return false, nil, fmt.Errorf("failed verify payload")
}

parsed, err := s.convertTonProofMessage(tp)
parsed, err := convertTonProofMessage(tp)
if err != nil {
return false, nil, err
}
Expand Down Expand Up @@ -212,7 +212,7 @@ func (s *Server) checkPayload(payload string) (bool, error) {
return true, nil
}

func (s *Server) convertTonProofMessage(tp *Proof) (*parsedMessage, error) {
func convertTonProofMessage(tp *Proof) (*parsedMessage, error) {
addr := strings.Split(tp.Address, ":")
if len(addr) != 2 {
return nil, fmt.Errorf("invalid address param: %v", tp.Address)
Expand Down Expand Up @@ -292,6 +292,10 @@ func signatureVerify(pubKey ed25519.PublicKey, message, signature []byte) bool {
return ed25519.Verify(pubKey, message, signature)
}

func signMessage(privateKey ed25519.PrivateKey, message []byte) []byte {
return ed25519.Sign(privateKey, message)
}

func ParseStateInit(stateInit string) ([]byte, error) {
cells, err := boc.DeserializeBocBase64(stateInit)
if err != nil || len(cells) != 1 {
Expand Down
6 changes: 3 additions & 3 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func GenerateWalletAddress(
workchain int,
subWalletId *int,
) (tongo.AccountID, error) {
state, err := generateStateInit(key, ver, workchain, subWalletId)
state, err := GenerateStateInit(key, ver, workchain, subWalletId)
if err != nil {
return tongo.AccountID{}, fmt.Errorf("can not generate wallet state: %v", err)
}
Expand All @@ -76,7 +76,7 @@ func GenerateWalletAddress(
return tongo.AccountID{Workchain: int32(workchain), Address: hash}, nil
}

func generateStateInit(
func GenerateStateInit(
key ed25519.PublicKey,
ver Version,
workchain int,
Expand Down Expand Up @@ -237,7 +237,7 @@ func (w *Wallet) RawSend(
func (w *Wallet) getInit() (tlb.StateInit, error) {
publicKey := w.key.Public().(ed25519.PublicKey)
id := int(w.subWalletId)
return generateStateInit(publicKey, w.ver, int(w.address.Workchain), &id)
return GenerateStateInit(publicKey, w.ver, int(w.address.Workchain), &id)
}

func checkMessagesLimit(msgQty int, ver Version) error { // TODO: maybe return bool
Expand Down

0 comments on commit 17902ba

Please sign in to comment.