@alephium/ledger-app 0.3.0 → 0.4.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/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +15 -4
- package/dist/test/utils.d.ts +16 -0
- package/dist/test/utils.js +215 -0
- package/dist/test/{speculos.test.js → wallet.test.js} +140 -174
- package/package.json +7 -7
- package/src/index.ts +18 -4
- package/test/utils.ts +222 -0
- package/test/{speculos.test.ts → wallet.test.ts} +156 -166
- package/dist/test/release.test.js +0 -132
- package/dist/test/speculos.test.d.ts +0 -1
- package/test/release.test.ts +0 -152
- /package/dist/test/{release.test.d.ts → wallet.test.d.ts} +0 -0
package/dist/src/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export declare const CLA = 128;
|
|
|
5
5
|
export declare enum INS {
|
|
6
6
|
GET_VERSION = 0,
|
|
7
7
|
GET_PUBLIC_KEY = 1,
|
|
8
|
-
|
|
8
|
+
SIGN_HASH = 2,
|
|
9
|
+
SIGN_TX = 3
|
|
9
10
|
}
|
|
10
11
|
export declare const GROUP_NUM = 4;
|
|
11
12
|
export declare const HASH_LEN = 32;
|
|
@@ -14,6 +15,7 @@ export default class AlephiumApp {
|
|
|
14
15
|
constructor(transport: Transport);
|
|
15
16
|
close(): Promise<void>;
|
|
16
17
|
getVersion(): Promise<string>;
|
|
17
|
-
getAccount(startPath: string, targetGroup?: number, keyType?: KeyType): Promise<readonly [Account, number]>;
|
|
18
|
+
getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [Account, number]>;
|
|
19
|
+
signHash(path: string, hash: Buffer): Promise<string>;
|
|
18
20
|
signUnsignedTx(path: string, unsignedTx: Buffer): Promise<string>;
|
|
19
21
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -34,7 +34,8 @@ var INS;
|
|
|
34
34
|
(function (INS) {
|
|
35
35
|
INS[INS["GET_VERSION"] = 0] = "GET_VERSION";
|
|
36
36
|
INS[INS["GET_PUBLIC_KEY"] = 1] = "GET_PUBLIC_KEY";
|
|
37
|
-
INS[INS["
|
|
37
|
+
INS[INS["SIGN_HASH"] = 2] = "SIGN_HASH";
|
|
38
|
+
INS[INS["SIGN_TX"] = 3] = "SIGN_TX";
|
|
38
39
|
})(INS = exports.INS || (exports.INS = {}));
|
|
39
40
|
exports.GROUP_NUM = 4;
|
|
40
41
|
exports.HASH_LEN = 32;
|
|
@@ -50,8 +51,7 @@ class AlephiumApp {
|
|
|
50
51
|
console.log(`response ${response.length} - ${response.toString('hex')}`);
|
|
51
52
|
return `${response[0]}.${response[1]}.${response[2]}`;
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
async getAccount(startPath, targetGroup, keyType) {
|
|
54
|
+
async getAccount(startPath, targetGroup, keyType, display = false) {
|
|
55
55
|
if ((targetGroup ?? 0) >= exports.GROUP_NUM) {
|
|
56
56
|
throw Error(`Invalid targetGroup: ${targetGroup}`);
|
|
57
57
|
}
|
|
@@ -60,13 +60,24 @@ class AlephiumApp {
|
|
|
60
60
|
}
|
|
61
61
|
const p1 = targetGroup === undefined ? 0x00 : exports.GROUP_NUM;
|
|
62
62
|
const p2 = targetGroup === undefined ? 0x00 : targetGroup;
|
|
63
|
-
const
|
|
63
|
+
const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
|
|
64
|
+
const response = await this.transport.send(exports.CLA, INS.GET_PUBLIC_KEY, p1, p2, payload);
|
|
64
65
|
const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex');
|
|
65
66
|
const address = (0, web3_1.addressFromPublicKey)(publicKey);
|
|
66
67
|
const group = (0, web3_1.groupOfAddress)(address);
|
|
67
68
|
const hdIndex = response.slice(65, 69).readUInt32BE(0);
|
|
68
69
|
return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex];
|
|
69
70
|
}
|
|
71
|
+
async signHash(path, hash) {
|
|
72
|
+
if (hash.length !== exports.HASH_LEN) {
|
|
73
|
+
throw new Error('Invalid hash length');
|
|
74
|
+
}
|
|
75
|
+
const data = Buffer.concat([serde.serializePath(path), hash]);
|
|
76
|
+
console.log(`data ${data.length}`);
|
|
77
|
+
const response = await this.transport.send(exports.CLA, INS.SIGN_HASH, 0x00, 0x00, data, [hw_transport_1.StatusCodes.OK]);
|
|
78
|
+
console.log(`response ${response.length} - ${response.toString('hex')}`);
|
|
79
|
+
return decodeSignature(response);
|
|
80
|
+
}
|
|
70
81
|
async signUnsignedTx(path, unsignedTx) {
|
|
71
82
|
console.log(`unsigned tx size: ${unsignedTx.length}`);
|
|
72
83
|
const encodedPath = serde.serializePath(path);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Transport from '@ledgerhq/hw-transport';
|
|
2
|
+
export declare enum OutputType {
|
|
3
|
+
Base = 0,
|
|
4
|
+
Multisig = 1,
|
|
5
|
+
Token = 2,
|
|
6
|
+
MultisigAndToken = 3
|
|
7
|
+
}
|
|
8
|
+
export declare function staxFlexApproveOnce(): Promise<void>;
|
|
9
|
+
export declare function approveTx(outputs: OutputType[], hasExternalInputs?: boolean): Promise<void>;
|
|
10
|
+
export declare function approveHash(): Promise<void>;
|
|
11
|
+
export declare function approveAddress(): Promise<void>;
|
|
12
|
+
export declare function skipBlindSigningWarning(): void;
|
|
13
|
+
export declare function enableBlindSigning(): Promise<void>;
|
|
14
|
+
export declare function getRandomInt(min: number, max: number): number;
|
|
15
|
+
export declare function needToAutoApprove(): boolean;
|
|
16
|
+
export declare function createTransport(): Promise<Transport>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTransport = exports.needToAutoApprove = exports.getRandomInt = exports.enableBlindSigning = exports.skipBlindSigningWarning = exports.approveAddress = exports.approveHash = exports.approveTx = exports.staxFlexApproveOnce = exports.OutputType = void 0;
|
|
7
|
+
const hw_transport_node_speculos_1 = __importDefault(require("@ledgerhq/hw-transport-node-speculos"));
|
|
8
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
|
+
const web3_1 = require("@alephium/web3");
|
|
10
|
+
const hw_transport_node_hid_1 = __importDefault(require("@ledgerhq/hw-transport-node-hid"));
|
|
11
|
+
async function pressButton(button) {
|
|
12
|
+
await (0, web3_1.sleep)(1000);
|
|
13
|
+
return (0, node_fetch_1.default)(`http://localhost:25000/button/${button}`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
body: JSON.stringify({ action: 'press-and-release' })
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function clickAndApprove(times) {
|
|
19
|
+
for (let i = 0; i < times; i++) {
|
|
20
|
+
await pressButton('right');
|
|
21
|
+
}
|
|
22
|
+
await pressButton('both');
|
|
23
|
+
}
|
|
24
|
+
var OutputType;
|
|
25
|
+
(function (OutputType) {
|
|
26
|
+
OutputType[OutputType["Base"] = 0] = "Base";
|
|
27
|
+
OutputType[OutputType["Multisig"] = 1] = "Multisig";
|
|
28
|
+
OutputType[OutputType["Token"] = 2] = "Token";
|
|
29
|
+
OutputType[OutputType["MultisigAndToken"] = 3] = "MultisigAndToken";
|
|
30
|
+
})(OutputType = exports.OutputType || (exports.OutputType = {}));
|
|
31
|
+
const NanosClickTable = new Map([
|
|
32
|
+
[OutputType.Base, 5],
|
|
33
|
+
[OutputType.Multisig, 10],
|
|
34
|
+
[OutputType.Token, 11],
|
|
35
|
+
[OutputType.MultisigAndToken, 16],
|
|
36
|
+
]);
|
|
37
|
+
const NanospClickTable = new Map([
|
|
38
|
+
[OutputType.Base, 3],
|
|
39
|
+
[OutputType.Multisig, 5],
|
|
40
|
+
[OutputType.Token, 6],
|
|
41
|
+
[OutputType.MultisigAndToken, 8],
|
|
42
|
+
]);
|
|
43
|
+
const StaxClickTable = new Map([
|
|
44
|
+
[OutputType.Base, 2],
|
|
45
|
+
[OutputType.Multisig, 3],
|
|
46
|
+
[OutputType.Token, 3],
|
|
47
|
+
[OutputType.MultisigAndToken, 4],
|
|
48
|
+
]);
|
|
49
|
+
function getOutputClickSize(outputType) {
|
|
50
|
+
const model = process.env.MODEL;
|
|
51
|
+
switch (model) {
|
|
52
|
+
case 'nanos': return NanosClickTable.get(outputType);
|
|
53
|
+
case 'nanosp':
|
|
54
|
+
case 'nanox': return NanospClickTable.get(outputType);
|
|
55
|
+
case 'stax':
|
|
56
|
+
case 'flex': return StaxClickTable.get(outputType);
|
|
57
|
+
default: throw new Error(`Unknown model ${model}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function click(outputs, hasExternalInputs) {
|
|
61
|
+
await (0, web3_1.sleep)(1000);
|
|
62
|
+
if (hasExternalInputs) {
|
|
63
|
+
await clickAndApprove(1);
|
|
64
|
+
}
|
|
65
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
66
|
+
await clickAndApprove(getOutputClickSize(outputs[index]));
|
|
67
|
+
}
|
|
68
|
+
await clickAndApprove(1); // fees
|
|
69
|
+
}
|
|
70
|
+
const STAX_CONTINUE_POSITION = { x: 342, y: 606 };
|
|
71
|
+
const STAX_APPROVE_POSITION = { x: 200, y: 515 };
|
|
72
|
+
const STAX_REJECT_POSITION = { x: 36, y: 606 };
|
|
73
|
+
const STAX_SETTINGS_POSITION = { x: 342, y: 55 };
|
|
74
|
+
const STAX_BLIND_SETTING_POSITION = { x: 342, y: 90 };
|
|
75
|
+
const FLEX_CONTINUE_POSITION = { x: 430, y: 550 };
|
|
76
|
+
const FLEX_APPROVE_POSITION = { x: 240, y: 435 };
|
|
77
|
+
const FLEX_REJECT_POSITION = { x: 55, y: 530 };
|
|
78
|
+
const FLEX_SETTINGS_POSITION = { x: 405, y: 75 };
|
|
79
|
+
const FLEX_BLIND_SETTING_POSITION = { x: 405, y: 96 };
|
|
80
|
+
async function touchPosition(pos) {
|
|
81
|
+
await (0, web3_1.sleep)(1000);
|
|
82
|
+
return (0, node_fetch_1.default)(`http://localhost:25000/finger`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
body: JSON.stringify({ action: 'press-and-release', x: pos.x, y: pos.y })
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function _touch(times) {
|
|
88
|
+
let continuePos = process.env.MODEL === 'stax' ? STAX_CONTINUE_POSITION : FLEX_CONTINUE_POSITION;
|
|
89
|
+
for (let i = 0; i < times; i += 1) {
|
|
90
|
+
await touchPosition(continuePos);
|
|
91
|
+
}
|
|
92
|
+
let approvePos = process.env.MODEL === 'stax' ? STAX_APPROVE_POSITION : FLEX_APPROVE_POSITION;
|
|
93
|
+
await touchPosition(approvePos);
|
|
94
|
+
}
|
|
95
|
+
async function staxFlexApproveOnce() {
|
|
96
|
+
if (process.env.MODEL === 'stax') {
|
|
97
|
+
await touchPosition(STAX_APPROVE_POSITION);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
await touchPosition(FLEX_APPROVE_POSITION);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.staxFlexApproveOnce = staxFlexApproveOnce;
|
|
104
|
+
async function touch(outputs, hasExternalInputs) {
|
|
105
|
+
await (0, web3_1.sleep)(1000);
|
|
106
|
+
if (hasExternalInputs) {
|
|
107
|
+
await staxFlexApproveOnce();
|
|
108
|
+
}
|
|
109
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
110
|
+
await _touch(getOutputClickSize(outputs[index]));
|
|
111
|
+
}
|
|
112
|
+
await _touch(2); // fees
|
|
113
|
+
}
|
|
114
|
+
async function approveTx(outputs, hasExternalInputs = false) {
|
|
115
|
+
if (!needToAutoApprove())
|
|
116
|
+
return;
|
|
117
|
+
const isSelfTransfer = outputs.length === 0 && !hasExternalInputs;
|
|
118
|
+
if (isSelfTransfer) {
|
|
119
|
+
if (isStaxOrFlex()) {
|
|
120
|
+
await _touch(2);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
await clickAndApprove(2);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (isStaxOrFlex()) {
|
|
128
|
+
await touch(outputs, hasExternalInputs);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
await click(outputs, hasExternalInputs);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.approveTx = approveTx;
|
|
135
|
+
async function approveHash() {
|
|
136
|
+
if (!needToAutoApprove())
|
|
137
|
+
return;
|
|
138
|
+
if (isStaxOrFlex()) {
|
|
139
|
+
return await _touch(3);
|
|
140
|
+
}
|
|
141
|
+
if (process.env.MODEL === 'nanos') {
|
|
142
|
+
await clickAndApprove(5);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
await clickAndApprove(3);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.approveHash = approveHash;
|
|
149
|
+
async function approveAddress() {
|
|
150
|
+
if (!needToAutoApprove())
|
|
151
|
+
return;
|
|
152
|
+
if (isStaxOrFlex()) {
|
|
153
|
+
return await _touch(2);
|
|
154
|
+
}
|
|
155
|
+
if (process.env.MODEL === 'nanos') {
|
|
156
|
+
await clickAndApprove(4);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
await clickAndApprove(2);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.approveAddress = approveAddress;
|
|
163
|
+
function isStaxOrFlex() {
|
|
164
|
+
return !process.env.MODEL.startsWith('nano');
|
|
165
|
+
}
|
|
166
|
+
function skipBlindSigningWarning() {
|
|
167
|
+
if (!needToAutoApprove())
|
|
168
|
+
return;
|
|
169
|
+
if (isStaxOrFlex()) {
|
|
170
|
+
const rejectPos = process.env.MODEL === 'stax' ? STAX_REJECT_POSITION : FLEX_REJECT_POSITION;
|
|
171
|
+
touchPosition(rejectPos);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
clickAndApprove(3);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.skipBlindSigningWarning = skipBlindSigningWarning;
|
|
178
|
+
async function enableBlindSigning() {
|
|
179
|
+
if (!needToAutoApprove())
|
|
180
|
+
return;
|
|
181
|
+
if (isStaxOrFlex()) {
|
|
182
|
+
const settingsPos = process.env.MODEL === 'stax' ? STAX_SETTINGS_POSITION : FLEX_SETTINGS_POSITION;
|
|
183
|
+
const blindSettingPos = process.env.MODEL === 'stax' ? STAX_BLIND_SETTING_POSITION : FLEX_BLIND_SETTING_POSITION;
|
|
184
|
+
await touchPosition(settingsPos);
|
|
185
|
+
await touchPosition(blindSettingPos);
|
|
186
|
+
await touchPosition(settingsPos);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
await clickAndApprove(2);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
exports.enableBlindSigning = enableBlindSigning;
|
|
193
|
+
function getRandomInt(min, max) {
|
|
194
|
+
min = Math.ceil(min);
|
|
195
|
+
max = Math.floor(max);
|
|
196
|
+
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
|
|
197
|
+
}
|
|
198
|
+
exports.getRandomInt = getRandomInt;
|
|
199
|
+
function needToAutoApprove() {
|
|
200
|
+
switch (process.env.BACKEND) {
|
|
201
|
+
case "speculos": return true;
|
|
202
|
+
case "device": return false;
|
|
203
|
+
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
exports.needToAutoApprove = needToAutoApprove;
|
|
207
|
+
const ApduPort = 9999;
|
|
208
|
+
async function createTransport() {
|
|
209
|
+
switch (process.env.BACKEND) {
|
|
210
|
+
case "speculos": return hw_transport_node_speculos_1.default.open({ apduPort: ApduPort });
|
|
211
|
+
case "device": return hw_transport_node_hid_1.default.open('');
|
|
212
|
+
default: throw new Error(`Invalid backend: ${process.env.BACKEND}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.createTransport = createTransport;
|