@alephium/ledger-app 0.6.2 → 0.6.3-rc.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.
@@ -1,5 +1,4 @@
1
- /// <reference types="node" />
2
- import { Account, KeyType } from '@alephium/web3';
1
+ import { GroupedAccount, KeyType } from '@alephium/web3';
3
2
  import Transport from '@ledgerhq/hw-transport';
4
3
  export declare const CLA = 128;
5
4
  export declare enum INS {
@@ -15,7 +14,7 @@ export declare class AlephiumApp {
15
14
  constructor(transport: Transport);
16
15
  close(): Promise<void>;
17
16
  getVersion(): Promise<string>;
18
- getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [Account, number]>;
17
+ getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [GroupedAccount, number]>;
19
18
  signHash(path: string, hash: Buffer): Promise<string>;
20
19
  signUnsignedTx(path: string, unsignedTx: Buffer): Promise<string>;
21
20
  }
@@ -15,23 +15,31 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.AlephiumApp = exports.HASH_LEN = exports.GROUP_NUM = exports.INS = exports.CLA = void 0;
27
37
  const web3_1 = require("@alephium/web3");
28
38
  const hw_transport_1 = require("@ledgerhq/hw-transport");
29
39
  const serde = __importStar(require("./serde"));
30
- const elliptic_1 = require("elliptic");
31
40
  const types_1 = require("./types");
32
41
  const tx_encoder_1 = require("./tx-encoder");
33
42
  const merkle_1 = require("./merkle");
34
- const ec = new elliptic_1.ec('secp256k1');
35
43
  exports.CLA = 0x80;
36
44
  var INS;
37
45
  (function (INS) {
@@ -39,7 +47,7 @@ var INS;
39
47
  INS[INS["GET_PUBLIC_KEY"] = 1] = "GET_PUBLIC_KEY";
40
48
  INS[INS["SIGN_HASH"] = 2] = "SIGN_HASH";
41
49
  INS[INS["SIGN_TX"] = 3] = "SIGN_TX";
42
- })(INS = exports.INS || (exports.INS = {}));
50
+ })(INS || (exports.INS = INS = {}));
43
51
  exports.GROUP_NUM = 4;
44
52
  exports.HASH_LEN = 32;
45
53
  class AlephiumApp {
@@ -58,18 +66,20 @@ class AlephiumApp {
58
66
  if ((targetGroup ?? 0) >= exports.GROUP_NUM) {
59
67
  throw Error(`Invalid targetGroup: ${targetGroup}`);
60
68
  }
61
- if (keyType === 'bip340-schnorr') {
62
- throw Error('BIP340-Schnorr is not supported yet');
69
+ if (keyType !== undefined && keyType !== 'default') {
70
+ throw Error(`Unsupported key type: ${keyType}`);
63
71
  }
64
72
  const p1 = targetGroup === undefined ? 0x00 : exports.GROUP_NUM;
65
73
  const p2 = targetGroup === undefined ? 0x00 : targetGroup;
66
74
  const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
67
75
  const response = await this.transport.send(exports.CLA, INS.GET_PUBLIC_KEY, p1, p2, payload);
68
- const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex');
76
+ const prefix = (response[64] & 1) === 1 ? '03' : '02';
77
+ const publicKey = prefix + response.slice(1, 33).toString('hex');
69
78
  const address = (0, web3_1.addressFromPublicKey)(publicKey);
70
79
  const group = (0, web3_1.groupOfAddress)(address);
71
80
  const hdIndex = response.slice(65, 69).readUInt32BE(0);
72
- return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex];
81
+ const resolvedKeyType = 'default';
82
+ return [{ publicKey, address, group, keyType: resolvedKeyType }, hdIndex];
73
83
  }
74
84
  async signHash(path, hash) {
75
85
  if (hash.length !== exports.HASH_LEN) {
@@ -5,5 +5,5 @@ export declare function generateProofs(): {
5
5
  proofs: Record<string, string>;
6
6
  root: string;
7
7
  };
8
- export declare const tokenMerkleRoot: Uint8Array;
8
+ export declare const tokenMerkleRoot: Uint8Array<ArrayBufferLike>;
9
9
  export declare const tokenMerkleProofs: Record<string, string>;
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.tokenMerkleProofs = exports.tokenMerkleRoot = exports.generateProofs = exports.hashPair = exports.merkleTokens = void 0;
6
+ exports.tokenMerkleProofs = exports.tokenMerkleRoot = exports.merkleTokens = void 0;
7
+ exports.hashPair = hashPair;
8
+ exports.generateProofs = generateProofs;
7
9
  const web3_1 = require("@alephium/web3");
8
10
  const token_json_1 = __importDefault(require("../merkle-tree/token.json"));
9
11
  const proofs_json_1 = __importDefault(require("../merkle-tree/proofs.json"));
@@ -20,7 +22,6 @@ exports.merkleTokens = token_json_1.default.tokens.map((token) => {
20
22
  function hashPair(a, b) {
21
23
  return (0, blakejs_1.blake2b)(Buffer.concat([a, b].sort(Buffer.compare)), undefined, 32);
22
24
  }
23
- exports.hashPair = hashPair;
24
25
  function generateMerkleTree(tokens) {
25
26
  let level = tokens.map((token) => (0, blakejs_1.blake2b)((0, serde_1.serializeSingleTokenMetadata)(token), undefined, 32));
26
27
  const tree = [];
@@ -54,6 +55,5 @@ function generateProofs() {
54
55
  console.log('root', tree[tree.length - 1].map((hash) => (0, web3_1.binToHex)(hash)).join(''));
55
56
  return { proofs, root: (0, web3_1.binToHex)(tree[tree.length - 1][0]) };
56
57
  }
57
- exports.generateProofs = generateProofs;
58
58
  exports.tokenMerkleRoot = (0, web3_1.hexToBinUnsafe)('b3380866c595544781e9da0ccd79399de8878abfb0bf40545b57a287387d419d');
59
59
  exports.tokenMerkleProofs = proofs_json_1.default;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import { TokenMetadata } from './types';
3
2
  export declare const TRUE = 16;
4
3
  export declare const FALSE = 0;
package/dist/src/serde.js CHANGED
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serializeTokenMetadata = exports.serializeSingleTokenMetadata = exports.checkTokenMetadata = exports.serializePath = exports.splitPath = exports.FALSE = exports.TRUE = void 0;
3
+ exports.FALSE = exports.TRUE = void 0;
4
+ exports.splitPath = splitPath;
5
+ exports.serializePath = serializePath;
6
+ exports.checkTokenMetadata = checkTokenMetadata;
7
+ exports.serializeSingleTokenMetadata = serializeSingleTokenMetadata;
8
+ exports.serializeTokenMetadata = serializeTokenMetadata;
4
9
  const web3_1 = require("@alephium/web3");
5
10
  const types_1 = require("./types");
6
11
  exports.TRUE = 0x10;
@@ -21,7 +26,6 @@ function splitPath(path) {
21
26
  });
22
27
  return result;
23
28
  }
24
- exports.splitPath = splitPath;
25
29
  function serializePath(path) {
26
30
  const nodes = splitPath(path);
27
31
  if (nodes.length != 5) {
@@ -31,7 +35,6 @@ function serializePath(path) {
31
35
  nodes.forEach((element, index) => buffer.writeUInt32BE(element, 4 * index));
32
36
  return buffer;
33
37
  }
34
- exports.serializePath = serializePath;
35
38
  function symbolToBytes(symbol) {
36
39
  const buffer = Buffer.alloc(types_1.MAX_TOKEN_SYMBOL_LENGTH, 0);
37
40
  for (let i = 0; i < symbol.length; i++) {
@@ -56,7 +59,6 @@ function checkTokenMetadata(tokens) {
56
59
  throw new Error(`The token size exceeds maximum size`);
57
60
  }
58
61
  }
59
- exports.checkTokenMetadata = checkTokenMetadata;
60
62
  function serializeSingleTokenMetadata(metadata) {
61
63
  const symbolBytes = symbolToBytes(metadata.symbol);
62
64
  const buffer = Buffer.concat([
@@ -70,10 +72,8 @@ function serializeSingleTokenMetadata(metadata) {
70
72
  }
71
73
  return buffer;
72
74
  }
73
- exports.serializeSingleTokenMetadata = serializeSingleTokenMetadata;
74
75
  function serializeTokenMetadata(tokens) {
75
76
  checkTokenMetadata(tokens);
76
77
  const array = tokens.map((metadata) => serializeSingleTokenMetadata(metadata));
77
78
  return Buffer.concat([Buffer.from([array.length]), ...array]);
78
79
  }
79
- exports.serializeTokenMetadata = serializeTokenMetadata;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import { TokenMetadata } from "./types";
3
2
  export interface Frame {
4
3
  p1: number;
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assert = exports.encodeUnsignedTx = exports.encodeProofLength = exports.encodeTokenMetadata = void 0;
3
+ exports.encodeTokenMetadata = encodeTokenMetadata;
4
+ exports.encodeProofLength = encodeProofLength;
5
+ exports.encodeUnsignedTx = encodeUnsignedTx;
6
+ exports.assert = assert;
4
7
  const merkle_1 = require("./merkle");
5
8
  const serde_1 = require("./serde");
6
9
  const types_1 = require("./types");
@@ -25,7 +28,6 @@ function encodeTokenMetadata(tokenMetadata) {
25
28
  return frames;
26
29
  }
27
30
  }
28
- exports.encodeTokenMetadata = encodeTokenMetadata;
29
31
  function encodeTokenAndProof(tokenMetadata, firstFramePrefix) {
30
32
  const proof = merkle_1.tokenMerkleProofs[tokenMetadata.tokenId];
31
33
  if (proof === undefined)
@@ -56,7 +58,6 @@ function encodeProofLength(length) {
56
58
  buffer.writeUint16BE(length);
57
59
  return buffer;
58
60
  }
59
- exports.encodeProofLength = encodeProofLength;
60
61
  function encodeUnsignedTx(path, unsignedTx) {
61
62
  const encodedPath = (0, serde_1.serializePath)(path);
62
63
  const firstFrameTxLength = types_1.MAX_PAYLOAD_SIZE - 20;
@@ -74,9 +75,7 @@ function encodeUnsignedTx(path, unsignedTx) {
74
75
  }
75
76
  return frames;
76
77
  }
77
- exports.encodeUnsignedTx = encodeUnsignedTx;
78
78
  function assert(condition, msg) {
79
79
  if (!condition)
80
80
  throw Error(msg);
81
81
  }
82
- exports.assert = assert;
@@ -2,15 +2,18 @@ import Transport from '@ledgerhq/hw-transport';
2
2
  export declare enum OutputType {
3
3
  Base = 0,
4
4
  Multisig = 1,
5
- Token = 2,
6
- BaseAndToken = 3,
7
- MultisigAndToken = 4
5
+ Nanos10 = 2,
6
+ Nanos11 = 3,
7
+ Token = 4,
8
+ BaseAndToken = 5,
9
+ MultisigAndToken = 6
8
10
  }
9
11
  export declare function staxFlexApproveOnce(): Promise<void>;
10
12
  export declare function approveTx(outputs: OutputType[], hasExternalInputs?: boolean): Promise<void>;
11
13
  export declare function approveHash(): Promise<void>;
12
14
  export declare function approveAddress(): Promise<void>;
13
15
  export declare function isStaxOrFlex(): boolean;
16
+ export declare function isNanos(): boolean;
14
17
  export declare function skipBlindSigningWarning(): Promise<void>;
15
18
  export declare function staxFlexAcceptRisk(): Promise<void>;
16
19
  export declare function enableBlindSigning(): Promise<void>;
@@ -3,14 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createTransport = exports.needToAutoApprove = exports.getRandomInt = exports.enableBlindSigning = exports.staxFlexAcceptRisk = exports.skipBlindSigningWarning = exports.isStaxOrFlex = exports.approveAddress = exports.approveHash = exports.approveTx = exports.staxFlexApproveOnce = exports.OutputType = void 0;
6
+ exports.OutputType = void 0;
7
+ exports.staxFlexApproveOnce = staxFlexApproveOnce;
8
+ exports.approveTx = approveTx;
9
+ exports.approveHash = approveHash;
10
+ exports.approveAddress = approveAddress;
11
+ exports.isStaxOrFlex = isStaxOrFlex;
12
+ exports.isNanos = isNanos;
13
+ exports.skipBlindSigningWarning = skipBlindSigningWarning;
14
+ exports.staxFlexAcceptRisk = staxFlexAcceptRisk;
15
+ exports.enableBlindSigning = enableBlindSigning;
16
+ exports.getRandomInt = getRandomInt;
17
+ exports.needToAutoApprove = needToAutoApprove;
18
+ exports.createTransport = createTransport;
7
19
  const hw_transport_node_speculos_1 = __importDefault(require("@ledgerhq/hw-transport-node-speculos"));
8
- const node_fetch_1 = __importDefault(require("node-fetch"));
9
20
  const web3_1 = require("@alephium/web3");
10
21
  const hw_transport_node_hid_1 = __importDefault(require("@ledgerhq/hw-transport-node-hid"));
11
22
  async function pressButton(button) {
12
23
  await (0, web3_1.sleep)(1000);
13
- return (0, node_fetch_1.default)(`http://localhost:25000/button/${button}`, {
24
+ return fetch(`http://localhost:25000/button/${button}`, {
14
25
  method: 'POST',
15
26
  body: JSON.stringify({ action: 'press-and-release' })
16
27
  });
@@ -23,16 +34,27 @@ async function clickAndApprove(times) {
23
34
  }
24
35
  function getModel() {
25
36
  const model = process.env.MODEL;
26
- return model ? model : 'nanosp';
37
+ return model ? model : 'nanos';
27
38
  }
28
39
  var OutputType;
29
40
  (function (OutputType) {
30
41
  OutputType[OutputType["Base"] = 0] = "Base";
31
42
  OutputType[OutputType["Multisig"] = 1] = "Multisig";
32
- OutputType[OutputType["Token"] = 2] = "Token";
33
- OutputType[OutputType["BaseAndToken"] = 3] = "BaseAndToken";
34
- OutputType[OutputType["MultisigAndToken"] = 4] = "MultisigAndToken";
35
- })(OutputType = exports.OutputType || (exports.OutputType = {}));
43
+ OutputType[OutputType["Nanos10"] = 2] = "Nanos10";
44
+ OutputType[OutputType["Nanos11"] = 3] = "Nanos11";
45
+ OutputType[OutputType["Token"] = 4] = "Token";
46
+ OutputType[OutputType["BaseAndToken"] = 5] = "BaseAndToken";
47
+ OutputType[OutputType["MultisigAndToken"] = 6] = "MultisigAndToken";
48
+ })(OutputType || (exports.OutputType = OutputType = {}));
49
+ const NanosClickTable = new Map([
50
+ [OutputType.Base, 5],
51
+ [OutputType.Multisig, 10],
52
+ [OutputType.Nanos10, 10],
53
+ [OutputType.Nanos11, 11],
54
+ [OutputType.Token, 11],
55
+ [OutputType.BaseAndToken, 12],
56
+ [OutputType.MultisigAndToken, 16],
57
+ ]);
36
58
  const NanospClickTable = new Map([
37
59
  [OutputType.Base, 3],
38
60
  [OutputType.Multisig, 5],
@@ -57,6 +79,7 @@ const FlexClickTable = new Map([
57
79
  function getOutputClickSize(outputType) {
58
80
  const model = getModel();
59
81
  switch (model) {
82
+ case 'nanos': return NanosClickTable.get(outputType);
60
83
  case 'nanosp':
61
84
  case 'nanox': return NanospClickTable.get(outputType);
62
85
  case 'stax': return StaxClickTable.get(outputType);
@@ -76,28 +99,26 @@ async function click(outputs, hasExternalInputs) {
76
99
  }
77
100
  const STAX_CONTINUE_POSITION = { x: 342, y: 606 };
78
101
  const STAX_APPROVE_POSITION = { x: 200, y: 515 };
79
- const STAX_REJECT_POSITION = { x: 36, y: 606 };
80
102
  const STAX_SETTINGS_POSITION = { x: 342, y: 55 };
81
103
  const STAX_BLIND_SETTING_POSITION = { x: 342, y: 90 };
82
104
  const STAX_GO_TO_SETTINGS = { x: 36, y: 606 };
83
105
  const STAX_ACCEPT_RISK_POSITION = { x: 36, y: 606 };
84
106
  const FLEX_CONTINUE_POSITION = { x: 430, y: 550 };
85
107
  const FLEX_APPROVE_POSITION = { x: 240, y: 435 };
86
- const FLEX_REJECT_POSITION = { x: 55, y: 530 };
87
108
  const FLEX_SETTINGS_POSITION = { x: 405, y: 75 };
88
109
  const FLEX_BLIND_SETTING_POSITION = { x: 405, y: 96 };
89
110
  const FLEX_GO_TO_SETTINGS = { x: 55, y: 530 };
90
111
  const FLEX_ACCEPT_RISK_POSITION = { x: 55, y: 530 };
91
112
  async function touchPosition(pos) {
92
113
  await (0, web3_1.sleep)(1000);
93
- return (0, node_fetch_1.default)(`http://localhost:25000/finger`, {
114
+ return fetch(`http://localhost:25000/finger`, {
94
115
  method: 'POST',
95
116
  body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y })
96
117
  });
97
118
  }
98
119
  async function longPress(pos) {
99
120
  await (0, web3_1.sleep)(1000);
100
- return (0, node_fetch_1.default)(`http://localhost:25000/finger`, {
121
+ return fetch(`http://localhost:25000/finger`, {
101
122
  method: 'POST',
102
123
  body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y, delay: 3 })
103
124
  });
@@ -121,7 +142,6 @@ async function staxFlexApproveOnce() {
121
142
  await touchPosition(FLEX_APPROVE_POSITION);
122
143
  }
123
144
  }
124
- exports.staxFlexApproveOnce = staxFlexApproveOnce;
125
145
  async function touch(outputs, hasExternalInputs) {
126
146
  await (0, web3_1.sleep)(3000);
127
147
  if (hasExternalInputs) {
@@ -155,16 +175,19 @@ async function approveTx(outputs, hasExternalInputs = false) {
155
175
  await click(outputs, hasExternalInputs);
156
176
  }
157
177
  }
158
- exports.approveTx = approveTx;
159
178
  async function approveHash() {
160
179
  if (!needToAutoApprove())
161
180
  return;
162
181
  if (isStaxOrFlex()) {
163
182
  return await _touch(2, true);
164
183
  }
165
- await clickAndApprove(3);
184
+ if (getModel() === 'nanos') {
185
+ await clickAndApprove(5);
186
+ }
187
+ else {
188
+ await clickAndApprove(3);
189
+ }
166
190
  }
167
- exports.approveHash = approveHash;
168
191
  async function approveAddress() {
169
192
  if (!needToAutoApprove())
170
193
  return;
@@ -173,13 +196,19 @@ async function approveAddress() {
173
196
  await staxFlexApproveOnce();
174
197
  return;
175
198
  }
176
- await clickAndApprove(2);
199
+ if (getModel() === 'nanos') {
200
+ await clickAndApprove(4);
201
+ }
202
+ else {
203
+ await clickAndApprove(2);
204
+ }
177
205
  }
178
- exports.approveAddress = approveAddress;
179
206
  function isStaxOrFlex() {
180
207
  return !getModel().startsWith('nano');
181
208
  }
182
- exports.isStaxOrFlex = isStaxOrFlex;
209
+ function isNanos() {
210
+ return getModel() === 'nanos';
211
+ }
183
212
  async function skipBlindSigningWarning() {
184
213
  if (!needToAutoApprove())
185
214
  return;
@@ -192,7 +221,6 @@ async function skipBlindSigningWarning() {
192
221
  await clickAndApprove(3);
193
222
  }
194
223
  }
195
- exports.skipBlindSigningWarning = skipBlindSigningWarning;
196
224
  async function staxFlexAcceptRisk() {
197
225
  if (!needToAutoApprove())
198
226
  return;
@@ -204,7 +232,6 @@ async function staxFlexAcceptRisk() {
204
232
  await touchPosition(FLEX_ACCEPT_RISK_POSITION);
205
233
  }
206
234
  }
207
- exports.staxFlexAcceptRisk = staxFlexAcceptRisk;
208
235
  async function enableBlindSigning() {
209
236
  if (!needToAutoApprove())
210
237
  return;
@@ -220,13 +247,11 @@ async function enableBlindSigning() {
220
247
  await clickAndApprove(2);
221
248
  }
222
249
  }
223
- exports.enableBlindSigning = enableBlindSigning;
224
250
  function getRandomInt(min, max) {
225
251
  min = Math.ceil(min);
226
252
  max = Math.floor(max);
227
253
  return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
228
254
  }
229
- exports.getRandomInt = getRandomInt;
230
255
  function needToAutoApprove() {
231
256
  switch (process.env.BACKEND) {
232
257
  case "speculos": return true;
@@ -234,7 +259,6 @@ function needToAutoApprove() {
234
259
  default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
235
260
  }
236
261
  }
237
- exports.needToAutoApprove = needToAutoApprove;
238
262
  const ApduPort = 9999;
239
263
  async function createTransport() {
240
264
  switch (process.env.BACKEND) {
@@ -243,4 +267,3 @@ async function createTransport() {
243
267
  default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
244
268
  }
245
269
  }
246
- exports.createTransport = createTransport;
@@ -36,7 +36,7 @@ describe('ledger wallet', () => {
36
36
  const transport = await (0, utils_1.createTransport)();
37
37
  const app = new ledger_app_1.AlephiumApp(transport);
38
38
  const version = await app.getVersion();
39
- expect(version).toBe('0.4.4');
39
+ expect(version).toBe('0.4.0');
40
40
  await app.close();
41
41
  });
42
42
  it('should get public key', async () => {
@@ -209,32 +209,6 @@ describe('ledger wallet', () => {
209
209
  expect(token.amount).toEqual('1111111111111111111111111');
210
210
  await app.close();
211
211
  }, 120000);
212
- async function genTokensAndDestinations(fromAddress, toAddress, mintAmount, transferAmount) {
213
- const tokens = [];
214
- const tokenSymbol = 'TestTokenABC';
215
- const destinations = [];
216
- for (let i = 0; i < 5; i += 1) {
217
- const tokenInfo = await (0, web3_test_1.mintToken)(fromAddress, mintAmount);
218
- const tokenMetadata = {
219
- version: 0,
220
- tokenId: tokenInfo.contractId,
221
- symbol: tokenSymbol.slice(0, tokenSymbol.length - i),
222
- decimals: 18 - i
223
- };
224
- tokens.push(tokenMetadata);
225
- destinations.push({
226
- address: toAddress,
227
- attoAlphAmount: web3_1.DUST_AMOUNT.toString(),
228
- tokens: [
229
- {
230
- id: tokenMetadata.tokenId,
231
- amount: transferAmount.toString()
232
- }
233
- ]
234
- });
235
- }
236
- return { tokens, destinations };
237
- }
238
212
  it('should transfer tokens with proof', async () => {
239
213
  const transport = await (0, utils_1.createTransport)();
240
214
  const app = new ledger_app_1.AlephiumApp(transport);
@@ -242,10 +216,10 @@ describe('ledger wallet', () => {
242
216
  await transferToAddress(testAccount.address);
243
217
  const newAccount = await (0, web3_test_1.getSigner)();
244
218
  const selectedTokens = [
245
- merkle_1.merkleTokens[5],
246
- merkle_1.merkleTokens[6],
247
- merkle_1.merkleTokens[8],
248
- merkle_1.merkleTokens[11],
219
+ merkle_1.merkleTokens[5], // decimals is 0
220
+ merkle_1.merkleTokens[6], // decimals is 18
221
+ merkle_1.merkleTokens[8], // decimals is 9
222
+ merkle_1.merkleTokens[11], // decimals is 8
249
223
  merkle_1.merkleTokens[13], // decimals is 6
250
224
  ];
251
225
  const outputs = selectedTokens.map((token, index) => {
@@ -269,7 +243,12 @@ describe('ledger wallet', () => {
269
243
  fixedOutputs: outputs
270
244
  };
271
245
  const encodedUnsignedTx = web3_1.codec.unsignedTxCodec.encodeApiUnsignedTx(unsignedTx);
272
- (0, utils_1.approveTx)(Array(5).fill(utils_1.OutputType.BaseAndToken));
246
+ if ((0, utils_1.isNanos)()) {
247
+ (0, utils_1.approveTx)([utils_1.OutputType.Nanos11, utils_1.OutputType.Nanos10, utils_1.OutputType.Nanos10, utils_1.OutputType.Nanos10, utils_1.OutputType.Nanos11]);
248
+ }
249
+ else {
250
+ (0, utils_1.approveTx)(Array(5).fill(utils_1.OutputType.BaseAndToken));
251
+ }
273
252
  const signature = await app.signUnsignedTx(path, Buffer.from(encodedUnsignedTx));
274
253
  const txId = blakejs_1.default.blake2b(encodedUnsignedTx, undefined, 32);
275
254
  expect((0, web3_1.transactionVerifySignature)((0, web3_1.binToHex)(txId), testAccount.publicKey, signature)).toBe(true);
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts","../src/ledger-app.ts","../src/merkle.ts","../src/serde.test.ts","../src/serde.ts","../src/tx-encoder.ts","../src/types.ts","../test/merkle.test.ts","../test/tx-encoder.test.ts","../test/utils.ts","../test/wallet.test.ts"],"version":"5.8.3"}
@@ -29,11 +29,8 @@ alephium.consensus.mainnet.block-target-time = 10 millis
29
29
  alephium.consensus.mainnet.uncle-dependency-gap-time = 0 seconds
30
30
  alephium.consensus.rhone.block-target-time = 5 millis
31
31
  alephium.consensus.rhone.uncle-dependency-gap-time = 0 seconds
32
- alephium.consensus.danube.block-target-time = 5 millis
33
- alephium.consensus.danube.uncle-dependency-gap-time = 0 seconds
34
32
  alephium.network.leman-hard-fork-timestamp = 0
35
33
  alephium.network.rhone-hard-fork-timestamp = 0
36
- alephium.network.danube-hard-fork-timestamp = 0
37
34
 
38
35
  alephium.network.network-id = 4
39
36
  alephium.discovery.bootstrap = []
@@ -2,7 +2,7 @@ version: "3.3"
2
2
 
3
3
  services:
4
4
  alephium:
5
- image: alephium/alephium:v4.2.2
5
+ image: alephium/alephium:v3.3.0
6
6
  restart: "no"
7
7
  ports:
8
8
  - 19973:19973/tcp
@@ -0,0 +1,17 @@
1
+ const tseslint = require('typescript-eslint')
2
+ const eslintConfigPrettier = require('eslint-config-prettier')
3
+
4
+ module.exports = tseslint.config(
5
+ {
6
+ ignores: ['**/dist/', '**/templates/', '**/coverage/', 'eslint.config.js']
7
+ },
8
+ ...tseslint.configs.recommended,
9
+ eslintConfigPrettier,
10
+ {
11
+ languageOptions: {
12
+ parserOptions: {
13
+ project: 'tsconfig.json'
14
+ }
15
+ }
16
+ }
17
+ )
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
1
  {
2
2
  "name": "@alephium/ledger-app",
3
- "version": "0.6.2",
3
+ "version": "0.6.3-rc.0",
4
4
  "license": "GPL",
5
+ "main": "dist/src/index.js",
5
6
  "types": "dist/src/index.d.ts",
7
+ "repository": {
8
+ "url": "https://github.com/alephium/ledger-alephium"
9
+ },
6
10
  "exports": {
7
11
  ".": "./dist/src/index.js"
8
12
  },
9
13
  "scripts": {
10
- "build": "npm run clean:windows && npm run clean:unix && npx --yes tsc --build .",
11
- "clean:unix": "node -e \"if (process.platform !== 'win32') process.exit(1)\" || rm -rf dist",
12
- "clean:windows": "node -e \"if (process.platform === 'win32') process.exit(1)\" || , if exist dist rmdir /Q /S dist",
13
- "lint": "eslint . --ext ts",
14
- "lint:fix": "eslint . --fix --ext ts",
14
+ "build": "rm -rf dist && tsc --build .",
15
+ "lint": "eslint .",
16
+ "lint:fix": "eslint . --fix",
15
17
  "test": "BACKEND=speculos jest -i --config ./jest-config.json",
16
18
  "speculos-test": "BACKEND=speculos jest -i --config ./jest-config.json",
17
- "device-test": "BACKEND=device jest -i --config ./jest-config.json",
18
- "pub": "npm run build && npm publish --access public"
19
+ "device-test": "BACKEND=device jest -i --config ./jest-config.json"
19
20
  },
20
21
  "prettier": {
21
22
  "printWidth": 120,
@@ -27,32 +28,27 @@
27
28
  "trailingComma": "none"
28
29
  },
29
30
  "dependencies": {
30
- "@alephium/web3": "^1.5.0",
31
- "@ledgerhq/hw-transport": "^6.31.9",
31
+ "@alephium/web3": "3.0.0-test.9",
32
+ "@ledgerhq/hw-transport": "6.34.1",
32
33
  "blakejs": "^1.2.1"
33
34
  },
34
35
  "devDependencies": {
35
- "@alephium/cli": "^1.5.0",
36
- "@alephium/web3-test": "^1.5.0",
37
- "@alephium/web3-wallet": "^1.5.0",
38
- "@ledgerhq/hw-transport-node-hid": "^6.29.10",
39
- "@ledgerhq/hw-transport-node-speculos": "^6.29.9",
40
- "@types/elliptic": "^6.4.13",
41
- "@types/jest": "^27.5.1",
36
+ "@alephium/cli": "3.0.0-test.9",
37
+ "@alephium/web3-test": "3.0.0-test.9",
38
+ "@alephium/web3-wallet": "3.0.0-test.9",
39
+ "@ledgerhq/hw-transport-node-hid": "6.32.1",
40
+ "@ledgerhq/hw-transport-node-speculos": "6.33.1",
41
+ "@types/jest": "^29.5.0",
42
42
  "@types/node": "^20.8.10",
43
- "@typescript-eslint/eslint-plugin": "^4.30.0",
44
- "@typescript-eslint/parser": "^4.30.0",
45
- "eslint": "^7.32.0",
46
- "eslint-config-prettier": "^8.5.0",
47
- "eslint-plugin-prettier": "^4.0.0",
48
- "jest": "^28.1.0",
49
- "node-fetch": "^2.6.7",
50
- "ts-jest": "^28.0.2",
43
+ "eslint": "^9.0.0",
44
+ "eslint-config-prettier": "^10.0.0",
45
+ "typescript-eslint": "^8.0.0",
46
+ "jest": "^29.7.0",
47
+ "ts-jest": "^29.1.0",
51
48
  "ts-node": "^10.7.0",
52
- "typescript": "^4.4.2"
49
+ "typescript": "~5.8.0"
53
50
  },
54
51
  "engines": {
55
- "node": ">=14.0.0",
56
- "npm": ">=7.0.0"
52
+ "node": ">=18.0.0"
57
53
  }
58
54
  }
package/src/ledger-app.ts CHANGED
@@ -1,13 +1,10 @@
1
- import { Account, KeyType, addressFromPublicKey, binToHex, codec, encodeHexSignature, groupOfAddress } from '@alephium/web3'
1
+ import { GroupedAccount, GroupedKeyType, KeyType, addressFromPublicKey, binToHex, codec, encodeHexSignature, groupOfAddress } from '@alephium/web3'
2
2
  import Transport, { StatusCodes } from '@ledgerhq/hw-transport'
3
3
  import * as serde from './serde'
4
- import { ec as EC } from 'elliptic'
5
4
  import { MAX_TOKEN_SIZE, MAX_TOKEN_SYMBOL_LENGTH, TokenMetadata } from './types'
6
5
  import { encodeTokenMetadata, encodeUnsignedTx } from './tx-encoder'
7
6
  import { merkleTokens } from './merkle'
8
7
 
9
- const ec = new EC('secp256k1')
10
-
11
8
  export const CLA = 0x80
12
9
  export enum INS {
13
10
  GET_VERSION = 0x00,
@@ -36,25 +33,27 @@ export class AlephiumApp {
36
33
  return `${response[0]}.${response[1]}.${response[2]}`
37
34
  }
38
35
 
39
- async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display = false): Promise<readonly [Account, number]> {
36
+ async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display = false): Promise<readonly [GroupedAccount, number]> {
40
37
  if ((targetGroup ?? 0) >= GROUP_NUM) {
41
38
  throw Error(`Invalid targetGroup: ${targetGroup}`)
42
39
  }
43
40
 
44
- if (keyType === 'bip340-schnorr') {
45
- throw Error('BIP340-Schnorr is not supported yet')
41
+ if (keyType !== undefined && keyType !== 'default') {
42
+ throw Error(`Unsupported key type: ${keyType}`)
46
43
  }
47
44
 
48
45
  const p1 = targetGroup === undefined ? 0x00 : GROUP_NUM
49
46
  const p2 = targetGroup === undefined ? 0x00 : targetGroup
50
47
  const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
51
48
  const response = await this.transport.send(CLA, INS.GET_PUBLIC_KEY, p1, p2, payload)
52
- const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex')
49
+ const prefix = (response[64] & 1) === 1 ? '03' : '02'
50
+ const publicKey = prefix + response.slice(1, 33).toString('hex')
53
51
  const address = addressFromPublicKey(publicKey)
54
52
  const group = groupOfAddress(address)
55
53
  const hdIndex = response.slice(65, 69).readUInt32BE(0)
56
54
 
57
- return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex] as const
55
+ const resolvedKeyType: GroupedKeyType = 'default'
56
+ return [{ publicKey, address, group, keyType: resolvedKeyType }, hdIndex] as const
58
57
  }
59
58
 
60
59
  async signHash(path: string, hash: Buffer): Promise<string> {
package/src/tx-encoder.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { tokenMerkleProofs } from "./merkle"
2
- import { checkTokenMetadata, serializePath, serializeSingleTokenMetadata } from "./serde"
2
+ import { serializePath, serializeSingleTokenMetadata } from "./serde"
3
3
  import { MAX_PAYLOAD_SIZE, TokenMetadata } from "./types"
4
4
 
5
5
  export interface Frame {
package/test/utils.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import SpeculosTransport from '@ledgerhq/hw-transport-node-speculos'
2
- import fetch from 'node-fetch'
3
2
  import { sleep } from '@alephium/web3'
4
3
  import Transport from '@ledgerhq/hw-transport'
5
4
  import NodeTransport from '@ledgerhq/hw-transport-node-hid'
@@ -21,17 +20,29 @@ async function clickAndApprove(times: number) {
21
20
 
22
21
  function getModel(): string {
23
22
  const model = process.env.MODEL
24
- return model ? model as string : 'nanosp'
23
+ return model ? model as string : 'nanos'
25
24
  }
26
25
 
27
26
  export enum OutputType {
28
27
  Base,
29
28
  Multisig,
29
+ Nanos10,
30
+ Nanos11,
30
31
  Token,
31
32
  BaseAndToken,
32
33
  MultisigAndToken
33
34
  }
34
35
 
36
+ const NanosClickTable = new Map([
37
+ [OutputType.Base, 5],
38
+ [OutputType.Multisig, 10],
39
+ [OutputType.Nanos10, 10],
40
+ [OutputType.Nanos11, 11],
41
+ [OutputType.Token, 11],
42
+ [OutputType.BaseAndToken, 12],
43
+ [OutputType.MultisigAndToken, 16],
44
+ ])
45
+
35
46
  const NanospClickTable = new Map([
36
47
  [OutputType.Base, 3],
37
48
  [OutputType.Multisig, 5],
@@ -59,6 +70,7 @@ const FlexClickTable = new Map([
59
70
  function getOutputClickSize(outputType: OutputType) {
60
71
  const model = getModel()
61
72
  switch (model) {
73
+ case 'nanos': return NanosClickTable.get(outputType)!
62
74
  case 'nanosp':
63
75
  case 'nanox': return NanospClickTable.get(outputType)!
64
76
  case 'stax': return StaxClickTable.get(outputType)!
@@ -87,7 +99,6 @@ interface Position {
87
99
 
88
100
  const STAX_CONTINUE_POSITION = { x: 342, y: 606 }
89
101
  const STAX_APPROVE_POSITION = { x: 200, y: 515 }
90
- const STAX_REJECT_POSITION = { x: 36, y: 606 }
91
102
  const STAX_SETTINGS_POSITION = { x: 342, y: 55 }
92
103
  const STAX_BLIND_SETTING_POSITION = { x: 342, y: 90 }
93
104
  const STAX_GO_TO_SETTINGS = { x: 36, y: 606 }
@@ -95,7 +106,6 @@ const STAX_ACCEPT_RISK_POSITION = { x: 36, y: 606 }
95
106
 
96
107
  const FLEX_CONTINUE_POSITION = { x: 430, y: 550 }
97
108
  const FLEX_APPROVE_POSITION = { x: 240, y: 435 }
98
- const FLEX_REJECT_POSITION = { x: 55, y: 530 }
99
109
  const FLEX_SETTINGS_POSITION = { x: 405, y: 75 }
100
110
  const FLEX_BLIND_SETTING_POSITION = { x: 405, y: 96 }
101
111
  const FLEX_GO_TO_SETTINGS = { x: 55, y: 530 }
@@ -178,7 +188,11 @@ export async function approveHash() {
178
188
  if (isStaxOrFlex()) {
179
189
  return await _touch(2, true)
180
190
  }
181
- await clickAndApprove(3)
191
+ if (getModel() === 'nanos') {
192
+ await clickAndApprove(5)
193
+ } else {
194
+ await clickAndApprove(3)
195
+ }
182
196
  }
183
197
 
184
198
  export async function approveAddress() {
@@ -188,13 +202,21 @@ export async function approveAddress() {
188
202
  await staxFlexApproveOnce()
189
203
  return
190
204
  }
191
- await clickAndApprove(2)
205
+ if (getModel() === 'nanos') {
206
+ await clickAndApprove(4)
207
+ } else {
208
+ await clickAndApprove(2)
209
+ }
192
210
  }
193
211
 
194
212
  export function isStaxOrFlex(): boolean {
195
213
  return !getModel().startsWith('nano')
196
214
  }
197
215
 
216
+ export function isNanos(): boolean {
217
+ return getModel() === 'nanos'
218
+ }
219
+
198
220
  export async function skipBlindSigningWarning() {
199
221
  if (!needToAutoApprove()) return
200
222
  if (isStaxOrFlex()) {
@@ -3,8 +3,7 @@ import { ALPH_TOKEN_ID, Address, DUST_AMOUNT, NodeProvider, ONE_ALPH, binToHex,
3
3
  import { getSigner, mintToken, transfer } from '@alephium/web3-test'
4
4
  import { PrivateKeyWallet } from '@alephium/web3-wallet'
5
5
  import blake from 'blakejs'
6
- import { approveAddress, approveHash, approveTx, createTransport, enableBlindSigning, getRandomInt, isStaxOrFlex, needToAutoApprove, OutputType, skipBlindSigningWarning, staxFlexAcceptRisk, staxFlexApproveOnce } from './utils'
7
- import { TokenMetadata } from '../src/types'
6
+ import { approveAddress, approveHash, approveTx, createTransport, enableBlindSigning, getRandomInt, isNanos, isStaxOrFlex, needToAutoApprove, OutputType, skipBlindSigningWarning, staxFlexAcceptRisk } from './utils'
8
7
  import { randomBytes } from 'crypto'
9
8
  import { merkleTokens, tokenMerkleProofs } from '../src/merkle'
10
9
 
@@ -37,7 +36,7 @@ describe('ledger wallet', () => {
37
36
  const transport = await createTransport()
38
37
  const app = new AlephiumApp(transport)
39
38
  const version = await app.getVersion()
40
- expect(version).toBe('0.4.4')
39
+ expect(version).toBe('0.4.0')
41
40
  await app.close()
42
41
  })
43
42
 
@@ -241,38 +240,6 @@ describe('ledger wallet', () => {
241
240
  await app.close()
242
241
  }, 120000)
243
242
 
244
- async function genTokensAndDestinations(
245
- fromAddress: string,
246
- toAddress: string,
247
- mintAmount: bigint,
248
- transferAmount: bigint
249
- ) {
250
- const tokens: TokenMetadata[] = []
251
- const tokenSymbol = 'TestTokenABC'
252
- const destinations: node.Destination[] = []
253
- for (let i = 0; i < 5; i += 1) {
254
- const tokenInfo = await mintToken(fromAddress, mintAmount);
255
- const tokenMetadata: TokenMetadata = {
256
- version: 0,
257
- tokenId: tokenInfo.contractId,
258
- symbol: tokenSymbol.slice(0, tokenSymbol.length - i),
259
- decimals: 18 - i
260
- }
261
- tokens.push(tokenMetadata)
262
- destinations.push({
263
- address: toAddress,
264
- attoAlphAmount: DUST_AMOUNT.toString(),
265
- tokens: [
266
- {
267
- id: tokenMetadata.tokenId,
268
- amount: transferAmount.toString()
269
- }
270
- ]
271
- })
272
- }
273
- return { tokens, destinations }
274
- }
275
-
276
243
  it('should transfer tokens with proof', async () => {
277
244
  const transport = await createTransport()
278
245
  const app = new AlephiumApp(transport)
@@ -309,7 +276,11 @@ describe('ledger wallet', () => {
309
276
  }
310
277
  const encodedUnsignedTx = codec.unsignedTxCodec.encodeApiUnsignedTx(unsignedTx)
311
278
 
312
- approveTx(Array(5).fill(OutputType.BaseAndToken))
279
+ if (isNanos()) {
280
+ approveTx([OutputType.Nanos11, OutputType.Nanos10, OutputType.Nanos10, OutputType.Nanos10, OutputType.Nanos11])
281
+ } else {
282
+ approveTx(Array(5).fill(OutputType.BaseAndToken))
283
+ }
313
284
  const signature = await app.signUnsignedTx(path, Buffer.from(encodedUnsignedTx))
314
285
  const txId = blake.blake2b(encodedUnsignedTx, undefined, 32)
315
286
  expect(transactionVerifySignature(binToHex(txId), testAccount.publicKey, signature)).toBe(true)
package/tsconfig.json CHANGED
@@ -12,7 +12,6 @@
12
12
  "declaration": true,
13
13
  "moduleResolution": "node",
14
14
  "resolveJsonModule": true,
15
- "experimentalDecorators": true,
16
15
  "noImplicitOverride": true
17
16
  },
18
17
  "exclude": ["node_modules"],
package/.eslintignore DELETED
@@ -1,2 +0,0 @@
1
- **/dist/
2
- **/templates/
package/.eslintrc.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "extends": [
3
- "prettier",
4
- "plugin:prettier/recommended",
5
- "plugin:@typescript-eslint/recommended"
6
- ],
7
- "rules": {
8
- "header/header": ["off"]
9
- },
10
- "parserOptions": {
11
- "project": "tsconfig.json",
12
- "ecmaVersion": 2020,
13
- "sourceType": "module"
14
- },
15
- "parser": "@typescript-eslint/parser"
16
- }