@ton-community/ton-ledger 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [4.1.0] - 2023-06-16
8
+
9
+ ### Added
10
+
11
+ - Added `signData` method along with `SignDataRequest` type
12
+
13
+ ## [4.0.1] - 2023-06-16
14
+
15
+ ### Fixed
16
+
17
+ - Fixed the address flags communication
18
+
7
19
  ## [4.0.0] - 2023-06-09
8
20
 
9
21
  ### Added
@@ -27,6 +27,16 @@ export type TonPayloadFormat = {
27
27
  forwardAmount: bigint;
28
28
  forwardPayload: Cell | null;
29
29
  };
30
+ export type SignDataRequest = {
31
+ type: 'plaintext';
32
+ text: string;
33
+ } | {
34
+ type: 'app-data';
35
+ address?: Address;
36
+ domain?: string;
37
+ data: Cell;
38
+ ext?: Cell;
39
+ };
30
40
  export declare class TonTransport {
31
41
  #private;
32
42
  readonly transport: Transport;
@@ -61,6 +71,13 @@ export declare class TonTransport {
61
71
  signature: Buffer;
62
72
  hash: Buffer;
63
73
  }>;
74
+ signData(path: number[], req: SignDataRequest, opts?: {
75
+ timestamp?: number;
76
+ }): Promise<{
77
+ signature: Buffer;
78
+ cell: Cell;
79
+ timestamp: number;
80
+ }>;
64
81
  signTransaction: (path: number[], transaction: {
65
82
  to: Address;
66
83
  sendMode: SendMode;
@@ -10,7 +10,9 @@ const LEDGER_SYSTEM = 0xB0;
10
10
  const LEDGER_CLA = 0xe0;
11
11
  const INS_VERSION = 0x03;
12
12
  const INS_ADDRESS = 0x05;
13
+ const INS_SIGN_TX = 0x06;
13
14
  const INS_PROOF = 0x08;
15
+ const INS_SIGN_DATA = 0x09;
14
16
  function chunks(buf, n) {
15
17
  const nc = Math.ceil(buf.length / n);
16
18
  const cs = [];
@@ -19,6 +21,19 @@ function chunks(buf, n) {
19
21
  }
20
22
  return cs;
21
23
  }
24
+ function processAddressFlags(opts) {
25
+ const bounceable = opts?.bounceable ?? true;
26
+ const testOnly = opts?.testOnly ?? false;
27
+ const chain = opts?.chain ?? 0;
28
+ let flags = 0x00;
29
+ if (testOnly) {
30
+ flags |= 0x01;
31
+ }
32
+ if (chain === -1) {
33
+ flags |= 0x02;
34
+ }
35
+ return { bounceable, testOnly, chain, flags };
36
+ }
22
37
  class TonTransport {
23
38
  transport;
24
39
  #lock = new teslabot_1.AsyncLock();
@@ -57,27 +72,7 @@ class TonTransport {
57
72
  // Check path
58
73
  validatePath(path);
59
74
  // Resolve flags
60
- let bounceable = true;
61
- let chain = 0;
62
- let test = false;
63
- let flags = 0x00;
64
- if (opts && opts.bounceable !== undefined && !opts.bounceable) {
65
- flags |= 0x01;
66
- bounceable = false;
67
- }
68
- if (opts && opts.testOnly) {
69
- flags |= 0x02;
70
- test = true;
71
- }
72
- if (opts && opts.chain !== undefined) {
73
- if (opts.chain !== 0 && opts.chain !== -1) {
74
- throw Error('Invalid chain');
75
- }
76
- chain = opts.chain;
77
- if (opts.chain === -1) {
78
- flags |= 0x04;
79
- }
80
- }
75
+ const { bounceable, testOnly, chain } = processAddressFlags(opts);
81
76
  // Get public key
82
77
  let response = await this.#doRequest(INS_ADDRESS, 0x00, 0x00, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
83
78
  if (response.length !== 32) {
@@ -86,33 +81,13 @@ class TonTransport {
86
81
  // Contract
87
82
  const contract = (0, getInit_1.getInit)(chain, response);
88
83
  const address = (0, ton_core_1.contractAddress)(chain, contract);
89
- return { address: address.toString({ bounceable: bounceable, testOnly: test }), publicKey: response };
84
+ return { address: address.toString({ bounceable, testOnly }), publicKey: response };
90
85
  }
91
86
  async validateAddress(path, opts) {
92
87
  // Check path
93
88
  validatePath(path);
94
89
  // Resolve flags
95
- let bounceable = true;
96
- let chain = 0;
97
- let test = false;
98
- let flags = 0x00;
99
- if (opts && opts.bounceable !== undefined && !opts.bounceable) {
100
- flags |= 0x01;
101
- bounceable = false;
102
- }
103
- if (opts && opts.testOnly) {
104
- flags |= 0x02;
105
- test = true;
106
- }
107
- if (opts && opts.chain !== undefined) {
108
- if (opts.chain !== 0 && opts.chain !== -1) {
109
- throw Error('Invalid chain');
110
- }
111
- chain = opts.chain;
112
- if (opts.chain === -1) {
113
- flags |= 0x04;
114
- }
115
- }
90
+ const { bounceable, testOnly, chain, flags } = processAddressFlags(opts);
116
91
  // Get public key
117
92
  let response = await this.#doRequest(INS_ADDRESS, 0x01, flags, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
118
93
  if (response.length !== 32) {
@@ -121,34 +96,14 @@ class TonTransport {
121
96
  // Contract
122
97
  const contract = (0, getInit_1.getInit)(chain, response);
123
98
  const address = (0, ton_core_1.contractAddress)(chain, contract);
124
- return { address: address.toString({ bounceable: bounceable, testOnly: test }), publicKey: response };
99
+ return { address: address.toString({ bounceable, testOnly }), publicKey: response };
125
100
  }
126
101
  async getAddressProof(path, params, opts) {
127
102
  // Check path
128
103
  validatePath(path);
129
104
  let publicKey = (await this.getAddress(path)).publicKey;
130
105
  // Resolve flags
131
- let bounceable = true;
132
- let chain = 0;
133
- let test = false;
134
- let flags = 0x00;
135
- if (opts && opts.bounceable !== undefined && !opts.bounceable) {
136
- flags |= 0x01;
137
- bounceable = false;
138
- }
139
- if (opts && opts.testOnly) {
140
- flags |= 0x02;
141
- test = true;
142
- }
143
- if (opts && opts.chain !== undefined) {
144
- if (opts.chain !== 0 && opts.chain !== -1) {
145
- throw Error('Invalid chain');
146
- }
147
- chain = opts.chain;
148
- if (opts.chain === -1) {
149
- flags |= 0x04;
150
- }
151
- }
106
+ const { flags } = processAddressFlags(opts);
152
107
  const domainBuf = Buffer.from(params.domain, 'utf-8');
153
108
  const reqBuf = Buffer.concat([
154
109
  pathElementsToBuffer(path.map((v) => v + 0x80000000)),
@@ -166,6 +121,98 @@ class TonTransport {
166
121
  }
167
122
  return { signature, hash };
168
123
  }
124
+ async signData(path, req, opts) {
125
+ validatePath(path);
126
+ const publicKey = (await this.getAddress(path)).publicKey;
127
+ const timestamp = opts?.timestamp ?? Math.floor(Date.now() / 1000);
128
+ let schema;
129
+ let data;
130
+ let cell;
131
+ switch (req.type) {
132
+ case 'plaintext': {
133
+ schema = 0x754bf91b;
134
+ data = Buffer.from(req.text, 'ascii');
135
+ cell = (0, ton_core_1.beginCell)().storeStringTail(req.text).endCell();
136
+ break;
137
+ }
138
+ case 'app-data': {
139
+ if (req.address === undefined && req.domain === undefined) {
140
+ throw new Error('At least one of `address` and `domain` must be set when using \'app-data\' request');
141
+ }
142
+ schema = 0x54b58535;
143
+ let b = (0, ton_core_1.beginCell)();
144
+ let dp = [];
145
+ if (req.address !== undefined) {
146
+ b.storeBit(1);
147
+ b.storeAddress(req.address);
148
+ dp.push((0, ledgerWriter_1.writeUint8)(1), (0, ledgerWriter_1.writeAddress)(req.address));
149
+ }
150
+ else {
151
+ b.storeBit(0);
152
+ dp.push((0, ledgerWriter_1.writeUint8)(0));
153
+ }
154
+ if (req.domain !== undefined) {
155
+ b.storeBit(1);
156
+ let inner = (0, ton_core_1.beginCell)();
157
+ req.domain.split('.').reverse().forEach(p => {
158
+ inner.storeBuffer(Buffer.from(p, 'ascii'));
159
+ inner.storeUint(0, 8);
160
+ });
161
+ b.storeRef(inner);
162
+ const db = Buffer.from(req.domain, 'ascii');
163
+ dp.push((0, ledgerWriter_1.writeUint8)(1), (0, ledgerWriter_1.writeUint8)(db.length), db);
164
+ }
165
+ else {
166
+ b.storeBit(0);
167
+ dp.push((0, ledgerWriter_1.writeUint8)(0));
168
+ }
169
+ b.storeRef(req.data);
170
+ dp.push((0, ledgerWriter_1.writeCellRef)(req.data));
171
+ if (req.ext !== undefined) {
172
+ b.storeBit(1);
173
+ b.storeRef(req.ext);
174
+ dp.push((0, ledgerWriter_1.writeUint8)(1), (0, ledgerWriter_1.writeCellRef)(req.ext));
175
+ }
176
+ else {
177
+ b.storeBit(0);
178
+ dp.push((0, ledgerWriter_1.writeUint8)(0));
179
+ }
180
+ data = Buffer.concat(dp);
181
+ cell = b.endCell();
182
+ break;
183
+ }
184
+ default: {
185
+ throw new Error(`Sign data request type '${req.type}' not supported`);
186
+ }
187
+ }
188
+ const commonPart = Buffer.concat([
189
+ (0, ledgerWriter_1.writeUint32)(schema),
190
+ (0, ledgerWriter_1.writeUint64)(BigInt(timestamp)),
191
+ ]);
192
+ const pkg = Buffer.concat([
193
+ commonPart,
194
+ data,
195
+ ]);
196
+ await this.#doRequest(INS_SIGN_DATA, 0x00, 0x03, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
197
+ const pkgCs = chunks(pkg, 255);
198
+ for (let i = 0; i < pkgCs.length - 1; i++) {
199
+ await this.#doRequest(INS_SIGN_DATA, 0x00, 0x02, pkgCs[i]);
200
+ }
201
+ const res = await this.#doRequest(INS_SIGN_DATA, 0x00, 0x00, pkgCs[pkgCs.length - 1]);
202
+ let signature = res.subarray(1, 1 + 64);
203
+ let hash = res.subarray(2 + 64, 2 + 64 + 32);
204
+ if (!hash.equals(cell.hash())) {
205
+ throw Error('Hash mismatch. Expected: ' + cell.hash().toString('hex') + ', got: ' + hash.toString('hex'));
206
+ }
207
+ if (!(0, ton_crypto_1.signVerify)(Buffer.concat([commonPart, hash]), signature, publicKey)) {
208
+ throw Error('Received signature is invalid');
209
+ }
210
+ return {
211
+ signature,
212
+ cell,
213
+ timestamp,
214
+ };
215
+ }
169
216
  signTransaction = async (path, transaction) => {
170
217
  // Check path
171
218
  validatePath(path);
@@ -304,12 +351,12 @@ class TonTransport {
304
351
  //
305
352
  // Send package
306
353
  //
307
- await this.#doRequest(0x06, 0x00, 0x03, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
354
+ await this.#doRequest(INS_SIGN_TX, 0x00, 0x03, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
308
355
  const pkgCs = chunks(pkg, 255);
309
356
  for (let i = 0; i < pkgCs.length - 1; i++) {
310
- await this.#doRequest(0x06, 0x00, 0x02, pkgCs[i]);
357
+ await this.#doRequest(INS_SIGN_TX, 0x00, 0x02, pkgCs[i]);
311
358
  }
312
- let res = await this.#doRequest(0x06, 0x00, 0x00, pkgCs[pkgCs.length - 1]);
359
+ let res = await this.#doRequest(INS_SIGN_TX, 0x00, 0x00, pkgCs[pkgCs.length - 1]);
313
360
  //
314
361
  // Parse response
315
362
  //
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { TonPayloadFormat, TonTransport } from './TonTransport';
1
+ export { TonPayloadFormat, TonTransport, SignDataRequest } from './TonTransport';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton-community/ton-ledger",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "repository": "https://github.com/ton-community/ton-ledger-ts",
5
5
  "author": "Steve Korshakov <steve@korshakov.com>",
6
6
  "license": "MIT",