-
Notifications
You must be signed in to change notification settings - Fork 0
/
tonConnect.ts
160 lines (126 loc) · 4.84 KB
/
tonConnect.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { TonProofItemReplySuccess, CHAIN } from '@tonconnect/protocol';
import { Wallet } from '@tonconnect/sdk';
import * as crypto from 'crypto';
import * as nacl from 'tweetnacl';
import { Address, Cell } from "ton-core";
interface Domain {
LengthBytes: number // uint32 `json:"lengthBytes"`
Value: string // string `json:"value"`
}
interface ParsedMessage {
Workchain: number // int32
Address: Buffer // []byte
Timstamp: number // int64
Domain: Domain // Domain
Signature: Buffer // []byte
Payload: string // string
StateInit: string // string
}
const tonProofPrefix = 'ton-proof-item-v2/'
const tonConnectPrefix = 'ton-connect'
const CreateMessage = async (message: ParsedMessage): Promise<Buffer> => {
const workChain = Buffer.alloc(4)
workChain.writeUInt32BE(message.Workchain,0)
const timeStamp = Buffer.alloc(8)
timeStamp.writeBigUInt64LE(BigInt(message.Timstamp))
const domainLength = Buffer.alloc(4)
domainLength.writeUInt32LE(message.Domain.LengthBytes,0)
const m = Buffer.concat([
Buffer.from(tonProofPrefix),
workChain,
message.Address,
domainLength,
Buffer.from(message.Domain.Value),
timeStamp,
Buffer.from(message.Payload),
])
const messageHash = crypto.createHash('sha256').update(m).digest()
const fullMes = Buffer.concat([
Buffer.from([0xff, 0xff]),
Buffer.from(tonConnectPrefix),
Buffer.from(messageHash),
])
const res = await crypto.createHash('sha256').update(fullMes).digest();
return Buffer.from(res)
};
const ConvertTonProofMessage = (
tp: TonProofItemReplySuccess,
walletInfo: Wallet,
): ParsedMessage => {
const address = Address.parse(walletInfo.account.address);
const res: ParsedMessage = {
Workchain: address.workChain,
Address: Buffer.from(address.hash),
Domain: {
LengthBytes: tp.proof.domain.lengthBytes,
Value: tp.proof.domain.value,
},
Signature: Buffer.from(tp.proof.signature, 'base64'),
Payload: tp.proof.payload,
StateInit: walletInfo.account.walletStateInit,
Timstamp: tp.proof.timestamp,
}
return res;
};
const verifyTonProof = async (tonProofReply: TonProofItemReplySuccess, wallet: Wallet) => {
const signatureBase64 = tonProofReply.proof.signature;
const signatureBuffer = Buffer.from(signatureBase64, 'base64');
// Later you can use "wallet.account.address" to associate wallet address with user in DB
if (wallet.account.chain !== CHAIN.MAINNET) {
// Don't allow testnet users
return false;
}
if (!wallet.account.publicKey) {
return false;
}
const pubKeyHex = wallet.account.publicKey;
const pubKeyBuffer = Buffer.from(pubKeyHex, 'hex');
const parsedMessage = ConvertTonProofMessage(tonProofReply, wallet);
const messageBuffer = await CreateMessage(parsedMessage);
const result = SignatureVerify(pubKeyBuffer, messageBuffer, signatureBuffer);
console.log('Verification result', result);
return result;
};
const SignatureVerify = (pubkey: Buffer, message: Buffer, signature: Buffer): boolean => {
return nacl.sign.detached.verify(message, signature, pubkey)
};
const getPublicKey = (receivedStateInit: string, receivedAddress: string) => {
const cell = Cell.fromBase64(receivedStateInit);
const hash = cell.hash();
const encodedHash = hash.toString('hex');
console.log({hash, receivedAddress, encodedHash});
if (!receivedAddress.endsWith(encodedHash)) {
throw new Error("Address does not match hash");
}
const publicKey = cell.refs[1].bits.substring(8, 40);
console.log({ref1: cell.refs[1], publicKe: publicKey.length , bits: cell.refs[1].bits, publicKeyString: publicKey.toString()})
return publicKey;
};
const data = {
}
const tonConnectAuthenticate = async (context, payload) => {
const {account, connectItems: {tonProof}} = payload;
const inputFields = {
address: account.address,
chain: account.chain,
walletStateInit: account.walletStateInit,
publicKey: account.publicKey,
proofName: tonProof.name,
timestamp: tonProof.proof.timestamp,
domainLengthBytes: tonProof.proof.domain.lengthBytes,
domainValue: tonProof.proof.domain.value,
signature: tonProof.proof.signature,
proofPayload: tonProof.proof.payload,
}
console.log({payload});
const receivedStateInit = payload.account.walletStateInit;
const receivedAddress = payload.account.address;
const pbKey = getPublicKey(receivedStateInit,receivedAddress);
console.log({pbKey});
const isSignatureValid = await verifyTonProof(payload.connectItems.tonProof, payload)
console.log({isSignatureValid})
if (!isSignatureValid) {
throw new Error('Invalir Signature');
}
}
export default tonConnectAuthenticate;