@alephium/ledger-app 0.5.0 → 0.5.1

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,22 +1,2 @@
1
- /// <reference types="node" />
2
- import { Account, KeyType } from '@alephium/web3';
3
- import Transport from '@ledgerhq/hw-transport';
4
- import * as serde from './serde';
5
- export declare const CLA = 128;
6
- export declare enum INS {
7
- GET_VERSION = 0,
8
- GET_PUBLIC_KEY = 1,
9
- SIGN_HASH = 2,
10
- SIGN_TX = 3
11
- }
12
- export declare const GROUP_NUM = 4;
13
- export declare const HASH_LEN = 32;
14
- export default class AlephiumApp {
15
- readonly transport: Transport;
16
- constructor(transport: Transport);
17
- close(): Promise<void>;
18
- getVersion(): Promise<string>;
19
- getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [Account, number]>;
20
- signHash(path: string, hash: Buffer): Promise<string>;
21
- signUnsignedTx(path: string, unsignedTx: Buffer, tokenMetadata?: serde.TokenMetadata[]): Promise<string>;
22
- }
1
+ export * from './types';
2
+ export * from './ledger-app';
package/dist/src/index.js CHANGED
@@ -10,106 +10,9 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
10
10
  if (k2 === undefined) k2 = k;
11
11
  o[k2] = m[k];
12
12
  }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
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;
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
24
15
  };
25
16
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.HASH_LEN = exports.GROUP_NUM = exports.INS = exports.CLA = void 0;
27
- const web3_1 = require("@alephium/web3");
28
- const hw_transport_1 = require("@ledgerhq/hw-transport");
29
- const serde = __importStar(require("./serde"));
30
- const elliptic_1 = require("elliptic");
31
- const ec = new elliptic_1.ec('secp256k1');
32
- exports.CLA = 0x80;
33
- var INS;
34
- (function (INS) {
35
- INS[INS["GET_VERSION"] = 0] = "GET_VERSION";
36
- INS[INS["GET_PUBLIC_KEY"] = 1] = "GET_PUBLIC_KEY";
37
- INS[INS["SIGN_HASH"] = 2] = "SIGN_HASH";
38
- INS[INS["SIGN_TX"] = 3] = "SIGN_TX";
39
- })(INS = exports.INS || (exports.INS = {}));
40
- exports.GROUP_NUM = 4;
41
- exports.HASH_LEN = 32;
42
- // The maximum payload size is 255: https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-transport/src/Transport.ts#L261
43
- const MAX_PAYLOAD_SIZE = 255;
44
- class AlephiumApp {
45
- constructor(transport) {
46
- this.transport = transport;
47
- }
48
- async close() {
49
- await this.transport.close();
50
- }
51
- async getVersion() {
52
- const response = await this.transport.send(exports.CLA, INS.GET_VERSION, 0x00, 0x00);
53
- console.log(`response ${response.length} - ${response.toString('hex')}`);
54
- return `${response[0]}.${response[1]}.${response[2]}`;
55
- }
56
- async getAccount(startPath, targetGroup, keyType, display = false) {
57
- if ((targetGroup ?? 0) >= exports.GROUP_NUM) {
58
- throw Error(`Invalid targetGroup: ${targetGroup}`);
59
- }
60
- if (keyType === 'bip340-schnorr') {
61
- throw Error('BIP340-Schnorr is not supported yet');
62
- }
63
- const p1 = targetGroup === undefined ? 0x00 : exports.GROUP_NUM;
64
- const p2 = targetGroup === undefined ? 0x00 : targetGroup;
65
- const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
66
- const response = await this.transport.send(exports.CLA, INS.GET_PUBLIC_KEY, p1, p2, payload);
67
- const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex');
68
- const address = (0, web3_1.addressFromPublicKey)(publicKey);
69
- const group = (0, web3_1.groupOfAddress)(address);
70
- const hdIndex = response.slice(65, 69).readUInt32BE(0);
71
- return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex];
72
- }
73
- async signHash(path, hash) {
74
- if (hash.length !== exports.HASH_LEN) {
75
- throw new Error('Invalid hash length');
76
- }
77
- const data = Buffer.concat([serde.serializePath(path), hash]);
78
- console.log(`data ${data.length}`);
79
- const response = await this.transport.send(exports.CLA, INS.SIGN_HASH, 0x00, 0x00, data, [hw_transport_1.StatusCodes.OK]);
80
- console.log(`response ${response.length} - ${response.toString('hex')}`);
81
- return decodeSignature(response);
82
- }
83
- async signUnsignedTx(path, unsignedTx, tokenMetadata = []) {
84
- console.log(`unsigned tx size: ${unsignedTx.length}`);
85
- const encodedPath = serde.serializePath(path);
86
- const encodedTokenMetadata = serde.serializeTokenMetadata(tokenMetadata);
87
- const firstFrameTxLength = MAX_PAYLOAD_SIZE - 20 - encodedTokenMetadata.length;
88
- const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length);
89
- const data = Buffer.concat([encodedPath, encodedTokenMetadata, txData]);
90
- let response = await this.transport.send(exports.CLA, INS.SIGN_TX, 0x00, 0x00, data, [hw_transport_1.StatusCodes.OK]);
91
- if (unsignedTx.length <= firstFrameTxLength) {
92
- return decodeSignature(response);
93
- }
94
- const frameLength = MAX_PAYLOAD_SIZE;
95
- let fromIndex = firstFrameTxLength;
96
- while (fromIndex < unsignedTx.length) {
97
- const remain = unsignedTx.length - fromIndex;
98
- const toIndex = remain > frameLength ? (fromIndex + frameLength) : unsignedTx.length;
99
- const data = unsignedTx.slice(fromIndex, toIndex);
100
- response = await this.transport.send(exports.CLA, INS.SIGN_TX, 0x01, 0x00, data, [hw_transport_1.StatusCodes.OK]);
101
- fromIndex = toIndex;
102
- }
103
- return decodeSignature(response);
104
- }
105
- }
106
- exports.default = AlephiumApp;
107
- function decodeSignature(response) {
108
- // Decode signature: https://bitcoin.stackexchange.com/a/12556
109
- const rLen = response.slice(3, 4)[0];
110
- const r = response.slice(4, 4 + rLen);
111
- const sLen = response.slice(5 + rLen, 6 + rLen)[0];
112
- const s = response.slice(6 + rLen, 6 + rLen + sLen);
113
- console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`);
114
- return (0, web3_1.encodeHexSignature)(r.toString('hex'), s.toString('hex'));
115
- }
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./ledger-app"), exports);
@@ -0,0 +1,22 @@
1
+ /// <reference types="node" />
2
+ import { Account, KeyType } from '@alephium/web3';
3
+ import Transport from '@ledgerhq/hw-transport';
4
+ import { TokenMetadata } from './types';
5
+ export declare const CLA = 128;
6
+ export declare enum INS {
7
+ GET_VERSION = 0,
8
+ GET_PUBLIC_KEY = 1,
9
+ SIGN_HASH = 2,
10
+ SIGN_TX = 3
11
+ }
12
+ export declare const GROUP_NUM = 4;
13
+ export declare const HASH_LEN = 32;
14
+ export default class AlephiumApp {
15
+ readonly transport: Transport;
16
+ constructor(transport: Transport);
17
+ close(): Promise<void>;
18
+ getVersion(): Promise<string>;
19
+ getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [Account, number]>;
20
+ signHash(path: string, hash: Buffer): Promise<string>;
21
+ signUnsignedTx(path: string, unsignedTx: Buffer, tokenMetadata?: TokenMetadata[]): Promise<string>;
22
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
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
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.HASH_LEN = exports.GROUP_NUM = exports.INS = exports.CLA = void 0;
27
+ const web3_1 = require("@alephium/web3");
28
+ const hw_transport_1 = require("@ledgerhq/hw-transport");
29
+ const serde = __importStar(require("./serde"));
30
+ const elliptic_1 = require("elliptic");
31
+ const ec = new elliptic_1.ec('secp256k1');
32
+ exports.CLA = 0x80;
33
+ var INS;
34
+ (function (INS) {
35
+ INS[INS["GET_VERSION"] = 0] = "GET_VERSION";
36
+ INS[INS["GET_PUBLIC_KEY"] = 1] = "GET_PUBLIC_KEY";
37
+ INS[INS["SIGN_HASH"] = 2] = "SIGN_HASH";
38
+ INS[INS["SIGN_TX"] = 3] = "SIGN_TX";
39
+ })(INS = exports.INS || (exports.INS = {}));
40
+ exports.GROUP_NUM = 4;
41
+ exports.HASH_LEN = 32;
42
+ // The maximum payload size is 255: https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-transport/src/Transport.ts#L261
43
+ const MAX_PAYLOAD_SIZE = 255;
44
+ class AlephiumApp {
45
+ constructor(transport) {
46
+ this.transport = transport;
47
+ }
48
+ async close() {
49
+ await this.transport.close();
50
+ }
51
+ async getVersion() {
52
+ const response = await this.transport.send(exports.CLA, INS.GET_VERSION, 0x00, 0x00);
53
+ console.log(`response ${response.length} - ${response.toString('hex')}`);
54
+ return `${response[0]}.${response[1]}.${response[2]}`;
55
+ }
56
+ async getAccount(startPath, targetGroup, keyType, display = false) {
57
+ if ((targetGroup ?? 0) >= exports.GROUP_NUM) {
58
+ throw Error(`Invalid targetGroup: ${targetGroup}`);
59
+ }
60
+ if (keyType === 'bip340-schnorr') {
61
+ throw Error('BIP340-Schnorr is not supported yet');
62
+ }
63
+ const p1 = targetGroup === undefined ? 0x00 : exports.GROUP_NUM;
64
+ const p2 = targetGroup === undefined ? 0x00 : targetGroup;
65
+ const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
66
+ const response = await this.transport.send(exports.CLA, INS.GET_PUBLIC_KEY, p1, p2, payload);
67
+ const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex');
68
+ const address = (0, web3_1.addressFromPublicKey)(publicKey);
69
+ const group = (0, web3_1.groupOfAddress)(address);
70
+ const hdIndex = response.slice(65, 69).readUInt32BE(0);
71
+ return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex];
72
+ }
73
+ async signHash(path, hash) {
74
+ if (hash.length !== exports.HASH_LEN) {
75
+ throw new Error('Invalid hash length');
76
+ }
77
+ const data = Buffer.concat([serde.serializePath(path), hash]);
78
+ console.log(`data ${data.length}`);
79
+ const response = await this.transport.send(exports.CLA, INS.SIGN_HASH, 0x00, 0x00, data, [hw_transport_1.StatusCodes.OK]);
80
+ console.log(`response ${response.length} - ${response.toString('hex')}`);
81
+ return decodeSignature(response);
82
+ }
83
+ async signUnsignedTx(path, unsignedTx, tokenMetadata = []) {
84
+ console.log(`unsigned tx size: ${unsignedTx.length}`);
85
+ const encodedPath = serde.serializePath(path);
86
+ const encodedTokenMetadata = serde.serializeTokenMetadata(tokenMetadata);
87
+ const firstFrameTxLength = MAX_PAYLOAD_SIZE - 20 - encodedTokenMetadata.length;
88
+ const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length);
89
+ const data = Buffer.concat([encodedPath, encodedTokenMetadata, txData]);
90
+ let response = await this.transport.send(exports.CLA, INS.SIGN_TX, 0x00, 0x00, data, [hw_transport_1.StatusCodes.OK]);
91
+ if (unsignedTx.length <= firstFrameTxLength) {
92
+ return decodeSignature(response);
93
+ }
94
+ const frameLength = MAX_PAYLOAD_SIZE;
95
+ let fromIndex = firstFrameTxLength;
96
+ while (fromIndex < unsignedTx.length) {
97
+ const remain = unsignedTx.length - fromIndex;
98
+ const toIndex = remain > frameLength ? (fromIndex + frameLength) : unsignedTx.length;
99
+ const data = unsignedTx.slice(fromIndex, toIndex);
100
+ response = await this.transport.send(exports.CLA, INS.SIGN_TX, 0x01, 0x00, data, [hw_transport_1.StatusCodes.OK]);
101
+ fromIndex = toIndex;
102
+ }
103
+ return decodeSignature(response);
104
+ }
105
+ }
106
+ exports.default = AlephiumApp;
107
+ function decodeSignature(response) {
108
+ // Decode signature: https://bitcoin.stackexchange.com/a/12556
109
+ const rLen = response.slice(3, 4)[0];
110
+ const r = response.slice(4, 4 + rLen);
111
+ const sLen = response.slice(5 + rLen, 6 + rLen)[0];
112
+ const s = response.slice(6 + rLen, 6 + rLen + sLen);
113
+ console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`);
114
+ return (0, web3_1.encodeHexSignature)(r.toString('hex'), s.toString('hex'));
115
+ }
@@ -1,15 +1,7 @@
1
1
  /// <reference types="node" />
2
+ import { TokenMetadata } from "./types";
2
3
  export declare const TRUE = 16;
3
4
  export declare const FALSE = 0;
4
5
  export declare function splitPath(path: string): number[];
5
6
  export declare function serializePath(path: string): Buffer;
6
- export declare const MAX_TOKEN_SIZE = 5;
7
- export declare const MAX_TOKEN_SYMBOL_LENGTH = 12;
8
- export declare const TOKEN_METADATA_SIZE = 46;
9
- export interface TokenMetadata {
10
- version: number;
11
- tokenId: string;
12
- symbol: string;
13
- decimals: number;
14
- }
15
7
  export declare function serializeTokenMetadata(tokens: TokenMetadata[]): Buffer;
package/dist/src/serde.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serializeTokenMetadata = exports.TOKEN_METADATA_SIZE = exports.MAX_TOKEN_SYMBOL_LENGTH = exports.MAX_TOKEN_SIZE = exports.serializePath = exports.splitPath = exports.FALSE = exports.TRUE = void 0;
3
+ exports.serializeTokenMetadata = exports.serializePath = exports.splitPath = exports.FALSE = exports.TRUE = void 0;
4
4
  const web3_1 = require("@alephium/web3");
5
+ const types_1 = require("./types");
5
6
  exports.TRUE = 0x10;
6
7
  exports.FALSE = 0x00;
7
8
  function splitPath(path) {
@@ -31,11 +32,8 @@ function serializePath(path) {
31
32
  return buffer;
32
33
  }
33
34
  exports.serializePath = serializePath;
34
- exports.MAX_TOKEN_SIZE = 5;
35
- exports.MAX_TOKEN_SYMBOL_LENGTH = 12;
36
- exports.TOKEN_METADATA_SIZE = 46;
37
35
  function symbolToBytes(symbol) {
38
- const buffer = Buffer.alloc(exports.MAX_TOKEN_SYMBOL_LENGTH, 0);
36
+ const buffer = Buffer.alloc(types_1.MAX_TOKEN_SYMBOL_LENGTH, 0);
39
37
  for (let i = 0; i < symbol.length; i++) {
40
38
  buffer[i] = symbol.charCodeAt(i) & 0xFF;
41
39
  }
@@ -50,11 +48,11 @@ function check(tokens) {
50
48
  if (!((0, web3_1.isHexString)(token.tokenId) && token.tokenId.length === 64)) {
51
49
  throw new Error(`Invalid token id: ${token.tokenId}`);
52
50
  }
53
- if (token.symbol.length > exports.MAX_TOKEN_SYMBOL_LENGTH) {
51
+ if (token.symbol.length > types_1.MAX_TOKEN_SYMBOL_LENGTH) {
54
52
  throw new Error(`The token symbol is too long: ${token.symbol}`);
55
53
  }
56
54
  });
57
- if (tokens.length > exports.MAX_TOKEN_SIZE) {
55
+ if (tokens.length > types_1.MAX_TOKEN_SIZE) {
58
56
  throw new Error(`The token size exceeds maximum size`);
59
57
  }
60
58
  }
@@ -69,7 +67,7 @@ function serializeTokenMetadata(tokens) {
69
67
  symbolBytes,
70
68
  Buffer.from([metadata.decimals])
71
69
  ]);
72
- if (buffer.length !== exports.TOKEN_METADATA_SIZE) {
70
+ if (buffer.length !== types_1.TOKEN_METADATA_SIZE) {
73
71
  throw new Error(`Invalid token metadata: ${metadata}`);
74
72
  }
75
73
  return buffer;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const web3_1 = require("@alephium/web3");
4
4
  const serde_1 = require("./serde");
5
5
  const crypto_1 = require("crypto");
6
+ const types_1 = require("./types");
6
7
  describe('serde', () => {
7
8
  it('should split path', () => {
8
9
  expect((0, serde_1.splitPath)(`m/44'/1234'/0'/0/0`)).toStrictEqual([44 + 0x80000000, 1234 + 0x80000000, 0 + 0x80000000, 0, 0]);
@@ -44,7 +45,7 @@ describe('serde', () => {
44
45
  decimals: 12
45
46
  };
46
47
  const encodeSymbol = (symbol) => {
47
- return (0, web3_1.binToHex)(Buffer.from(symbol, 'ascii')).padEnd(serde_1.MAX_TOKEN_SYMBOL_LENGTH * 2, '0');
48
+ return (0, web3_1.binToHex)(Buffer.from(symbol, 'ascii')).padEnd(types_1.MAX_TOKEN_SYMBOL_LENGTH * 2, '0');
48
49
  };
49
50
  expect((0, web3_1.binToHex)((0, serde_1.serializeTokenMetadata)([]))).toEqual('00');
50
51
  expect((0, web3_1.binToHex)((0, serde_1.serializeTokenMetadata)([token0]))).toEqual('01' + '00' + token0.tokenId + encodeSymbol(token0.symbol) + '08');
@@ -0,0 +1,9 @@
1
+ export declare const MAX_TOKEN_SIZE = 5;
2
+ export declare const MAX_TOKEN_SYMBOL_LENGTH = 12;
3
+ export declare const TOKEN_METADATA_SIZE = 46;
4
+ export interface TokenMetadata {
5
+ version: number;
6
+ tokenId: string;
7
+ symbol: string;
8
+ decimals: number;
9
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOKEN_METADATA_SIZE = exports.MAX_TOKEN_SYMBOL_LENGTH = exports.MAX_TOKEN_SIZE = void 0;
4
+ exports.MAX_TOKEN_SIZE = 5;
5
+ exports.MAX_TOKEN_SYMBOL_LENGTH = 12;
6
+ exports.TOKEN_METADATA_SIZE = 46;
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- const src_1 = __importStar(require("../src"));
29
+ const ledger_app_1 = __importStar(require("../src/ledger-app"));
30
30
  const web3_1 = require("@alephium/web3");
31
31
  const web3_test_1 = require("@alephium/web3-test");
32
32
  const web3_wallet_1 = require("@alephium/web3-wallet");
@@ -56,14 +56,14 @@ describe('ledger wallet', () => {
56
56
  }
57
57
  it('should get version', async () => {
58
58
  const transport = await (0, utils_1.createTransport)();
59
- const app = new src_1.default(transport);
59
+ const app = new ledger_app_1.default(transport);
60
60
  const version = await app.getVersion();
61
61
  expect(version).toBe('0.4.0');
62
62
  await app.close();
63
63
  });
64
64
  it('should get public key', async () => {
65
65
  const transport = await (0, utils_1.createTransport)();
66
- const app = new src_1.default(transport);
66
+ const app = new ledger_app_1.default(transport);
67
67
  const [account, hdIndex] = await app.getAccount(path);
68
68
  expect(hdIndex).toBe(pathIndex);
69
69
  console.log(account);
@@ -71,7 +71,7 @@ describe('ledger wallet', () => {
71
71
  });
72
72
  it('should get public key and confirm address', async () => {
73
73
  const transport = await (0, utils_1.createTransport)();
74
- const app = new src_1.default(transport);
74
+ const app = new ledger_app_1.default(transport);
75
75
  (0, utils_1.approveAddress)();
76
76
  const [account, hdIndex] = await app.getAccount(path, undefined, undefined, true);
77
77
  expect(hdIndex).toBe(pathIndex);
@@ -80,8 +80,8 @@ describe('ledger wallet', () => {
80
80
  }, 30000);
81
81
  it('should get public key for group', async () => {
82
82
  const transport = await (0, utils_1.createTransport)();
83
- const app = new src_1.default(transport);
84
- for (let group = 0; group < src_1.GROUP_NUM; group++) {
83
+ const app = new ledger_app_1.default(transport);
84
+ for (let group = 0; group < ledger_app_1.GROUP_NUM; group++) {
85
85
  const [account, hdIndex] = await app.getAccount(path, group);
86
86
  expect(hdIndex >= pathIndex).toBe(true);
87
87
  expect((0, web3_1.groupOfAddress)(account.address)).toBe(group);
@@ -91,15 +91,15 @@ describe('ledger wallet', () => {
91
91
  });
92
92
  it('should get public key for group for Schnorr signature', async () => {
93
93
  const transport = await (0, utils_1.createTransport)();
94
- const app = new src_1.default(transport);
95
- for (let group = 0; group < src_1.GROUP_NUM; group++) {
94
+ const app = new ledger_app_1.default(transport);
95
+ for (let group = 0; group < ledger_app_1.GROUP_NUM; group++) {
96
96
  await expect(app.getAccount(path, group, 'bip340-schnorr')).rejects.toThrow('BIP340-Schnorr is not supported yet');
97
97
  }
98
98
  await app.close();
99
99
  });
100
100
  it('should sign hash', async () => {
101
101
  const transport = await (0, utils_1.createTransport)();
102
- const app = new src_1.default(transport);
102
+ const app = new ledger_app_1.default(transport);
103
103
  const [account] = await app.getAccount(path);
104
104
  console.log(account);
105
105
  const hash = Buffer.from(blakejs_1.default.blake2b(Buffer.from([0, 1, 2, 3, 4]), undefined, 32));
@@ -111,7 +111,7 @@ describe('ledger wallet', () => {
111
111
  }, 10000);
112
112
  it('should transfer alph to one address', async () => {
113
113
  const transport = await (0, utils_1.createTransport)();
114
- const app = new src_1.default(transport);
114
+ const app = new ledger_app_1.default(transport);
115
115
  const [testAccount] = await app.getAccount(path);
116
116
  await transferToAddress(testAccount.address);
117
117
  const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
@@ -137,7 +137,7 @@ describe('ledger wallet', () => {
137
137
  }, 120000);
138
138
  it('should transfer alph to multiple addresses', async () => {
139
139
  const transport = await (0, utils_1.createTransport)();
140
- const app = new src_1.default(transport);
140
+ const app = new ledger_app_1.default(transport);
141
141
  const [testAccount] = await app.getAccount(path);
142
142
  await transferToAddress(testAccount.address);
143
143
  const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
@@ -167,7 +167,7 @@ describe('ledger wallet', () => {
167
167
  }, 120000);
168
168
  it('should transfer alph to multisig address', async () => {
169
169
  const transport = await (0, utils_1.createTransport)();
170
- const app = new src_1.default(transport);
170
+ const app = new ledger_app_1.default(transport);
171
171
  const [testAccount] = await app.getAccount(path);
172
172
  await transferToAddress(testAccount.address);
173
173
  const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
@@ -194,7 +194,7 @@ describe('ledger wallet', () => {
194
194
  }, 120000);
195
195
  it('should transfer token to multisig address', async () => {
196
196
  const transport = await (0, utils_1.createTransport)();
197
- const app = new src_1.default(transport);
197
+ const app = new ledger_app_1.default(transport);
198
198
  const [testAccount] = await app.getAccount(path);
199
199
  await transferToAddress(testAccount.address);
200
200
  const tokenInfo = await (0, web3_test_1.mintToken)(testAccount.address, 2222222222222222222222222n);
@@ -259,7 +259,7 @@ describe('ledger wallet', () => {
259
259
  }
260
260
  it('should transfer token with metadata', async () => {
261
261
  const transport = await (0, utils_1.createTransport)();
262
- const app = new src_1.default(transport);
262
+ const app = new ledger_app_1.default(transport);
263
263
  const [testAccount] = await app.getAccount(path);
264
264
  await transferToAddress(testAccount.address);
265
265
  const toAddress = '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7';
@@ -288,7 +288,7 @@ describe('ledger wallet', () => {
288
288
  }, 120000);
289
289
  it('should reject tx if the metadata version is invalid', async () => {
290
290
  const transport = await (0, utils_1.createTransport)();
291
- const app = new src_1.default(transport);
291
+ const app = new ledger_app_1.default(transport);
292
292
  const [testAccount] = await app.getAccount(path);
293
293
  await transferToAddress(testAccount.address);
294
294
  const toAddress = '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7';
@@ -306,7 +306,7 @@ describe('ledger wallet', () => {
306
306
  }, 120000);
307
307
  it('should transfer from multiple inputs', async () => {
308
308
  const transport = await (0, utils_1.createTransport)();
309
- const app = new src_1.default(transport);
309
+ const app = new ledger_app_1.default(transport);
310
310
  const [testAccount] = await app.getAccount(path);
311
311
  for (let i = 0; i < 20; i += 1) {
312
312
  await transferToAddress(testAccount.address, web3_1.ONE_ALPH);
@@ -344,7 +344,7 @@ describe('ledger wallet', () => {
344
344
  }
345
345
  it('should test external inputs', async () => {
346
346
  const transport = await (0, utils_1.createTransport)();
347
- const app = new src_1.default(transport);
347
+ const app = new ledger_app_1.default(transport);
348
348
  const [testAccount] = await app.getAccount(path);
349
349
  const { account: newAccount, unlockScript: unlockScript0 } = getAccount(testAccount.group);
350
350
  for (let i = 0; i < 2; i += 1) {
@@ -401,7 +401,7 @@ describe('ledger wallet', () => {
401
401
  }, 120000);
402
402
  it('should test self transfer tx', async () => {
403
403
  const transport = await (0, utils_1.createTransport)();
404
- const app = new src_1.default(transport);
404
+ const app = new ledger_app_1.default(transport);
405
405
  const [testAccount] = await app.getAccount(path);
406
406
  await transferToAddress(testAccount.address);
407
407
  const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
@@ -427,7 +427,7 @@ describe('ledger wallet', () => {
427
427
  }, 12000);
428
428
  it('should test script execution tx', async () => {
429
429
  const transport = await (0, utils_1.createTransport)();
430
- const app = new src_1.default(transport);
430
+ const app = new ledger_app_1.default(transport);
431
431
  const [testAccount] = await app.getAccount(path);
432
432
  await transferToAddress(testAccount.address);
433
433
  const buildTxResult = await nodeProvider.contracts.postContractsUnsignedTxDeployContract({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alephium/ledger-app",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "license": "GPL",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "exports": {
package/src/index.ts CHANGED
@@ -1,111 +1,2 @@
1
- import { Account, KeyType, addressFromPublicKey, encodeHexSignature, groupOfAddress } from '@alephium/web3'
2
- import Transport, { StatusCodes } from '@ledgerhq/hw-transport'
3
- import * as serde from './serde'
4
- import { ec as EC } from 'elliptic'
5
-
6
- const ec = new EC('secp256k1')
7
-
8
- export const CLA = 0x80
9
- export enum INS {
10
- GET_VERSION = 0x00,
11
- GET_PUBLIC_KEY = 0x01,
12
- SIGN_HASH = 0x02,
13
- SIGN_TX = 0x03
14
- }
15
-
16
- export const GROUP_NUM = 4
17
- export const HASH_LEN = 32
18
-
19
- // The maximum payload size is 255: https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-transport/src/Transport.ts#L261
20
- const MAX_PAYLOAD_SIZE = 255
21
-
22
- export default class AlephiumApp {
23
- readonly transport: Transport
24
-
25
- constructor(transport: Transport) {
26
- this.transport = transport
27
- }
28
-
29
- async close(): Promise<void> {
30
- await this.transport.close()
31
- }
32
-
33
- async getVersion(): Promise<string> {
34
- const response = await this.transport.send(CLA, INS.GET_VERSION, 0x00, 0x00)
35
- console.log(`response ${response.length} - ${response.toString('hex')}`)
36
- return `${response[0]}.${response[1]}.${response[2]}`
37
- }
38
-
39
- async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display = false): Promise<readonly [Account, number]> {
40
- if ((targetGroup ?? 0) >= GROUP_NUM) {
41
- throw Error(`Invalid targetGroup: ${targetGroup}`)
42
- }
43
-
44
- if (keyType === 'bip340-schnorr') {
45
- throw Error('BIP340-Schnorr is not supported yet')
46
- }
47
-
48
- const p1 = targetGroup === undefined ? 0x00 : GROUP_NUM
49
- const p2 = targetGroup === undefined ? 0x00 : targetGroup
50
- const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
51
- 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')
53
- const address = addressFromPublicKey(publicKey)
54
- const group = groupOfAddress(address)
55
- const hdIndex = response.slice(65, 69).readUInt32BE(0)
56
-
57
- return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex] as const
58
- }
59
-
60
- async signHash(path: string, hash: Buffer): Promise<string> {
61
- if (hash.length !== HASH_LEN) {
62
- throw new Error('Invalid hash length')
63
- }
64
-
65
- const data = Buffer.concat([serde.serializePath(path), hash])
66
- console.log(`data ${data.length}`)
67
- const response = await this.transport.send(CLA, INS.SIGN_HASH, 0x00, 0x00, data, [StatusCodes.OK])
68
- console.log(`response ${response.length} - ${response.toString('hex')}`)
69
-
70
- return decodeSignature(response)
71
- }
72
-
73
- async signUnsignedTx(
74
- path: string,
75
- unsignedTx: Buffer,
76
- tokenMetadata: serde.TokenMetadata[] = []
77
- ): Promise<string> {
78
- console.log(`unsigned tx size: ${unsignedTx.length}`)
79
- const encodedPath = serde.serializePath(path)
80
- const encodedTokenMetadata = serde.serializeTokenMetadata(tokenMetadata)
81
- const firstFrameTxLength = MAX_PAYLOAD_SIZE - 20 - encodedTokenMetadata.length;
82
- const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length)
83
- const data = Buffer.concat([encodedPath, encodedTokenMetadata, txData])
84
- let response = await this.transport.send(CLA, INS.SIGN_TX, 0x00, 0x00, data, [StatusCodes.OK])
85
- if (unsignedTx.length <= firstFrameTxLength) {
86
- return decodeSignature(response)
87
- }
88
-
89
- const frameLength = MAX_PAYLOAD_SIZE
90
- let fromIndex = firstFrameTxLength
91
- while (fromIndex < unsignedTx.length) {
92
- const remain = unsignedTx.length - fromIndex
93
- const toIndex = remain > frameLength ? (fromIndex + frameLength) : unsignedTx.length
94
- const data = unsignedTx.slice(fromIndex, toIndex)
95
- response = await this.transport.send(CLA, INS.SIGN_TX, 0x01, 0x00, data, [StatusCodes.OK])
96
- fromIndex = toIndex
97
- }
98
-
99
- return decodeSignature(response)
100
- }
101
- }
102
-
103
- function decodeSignature(response: Buffer): string {
104
- // Decode signature: https://bitcoin.stackexchange.com/a/12556
105
- const rLen = response.slice(3, 4)[0]
106
- const r = response.slice(4, 4 + rLen)
107
- const sLen = response.slice(5 + rLen, 6 + rLen)[0]
108
- const s = response.slice(6 + rLen, 6 + rLen + sLen)
109
- console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
110
- return encodeHexSignature(r.toString('hex'), s.toString('hex'))
111
- }
1
+ export * from './types'
2
+ export * from './ledger-app'
@@ -0,0 +1,112 @@
1
+ import { Account, KeyType, addressFromPublicKey, encodeHexSignature, groupOfAddress } from '@alephium/web3'
2
+ import Transport, { StatusCodes } from '@ledgerhq/hw-transport'
3
+ import * as serde from './serde'
4
+ import { ec as EC } from 'elliptic'
5
+ import { TokenMetadata } from './types'
6
+
7
+ const ec = new EC('secp256k1')
8
+
9
+ export const CLA = 0x80
10
+ export enum INS {
11
+ GET_VERSION = 0x00,
12
+ GET_PUBLIC_KEY = 0x01,
13
+ SIGN_HASH = 0x02,
14
+ SIGN_TX = 0x03
15
+ }
16
+
17
+ export const GROUP_NUM = 4
18
+ export const HASH_LEN = 32
19
+
20
+ // The maximum payload size is 255: https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-transport/src/Transport.ts#L261
21
+ const MAX_PAYLOAD_SIZE = 255
22
+
23
+ export default class AlephiumApp {
24
+ readonly transport: Transport
25
+
26
+ constructor(transport: Transport) {
27
+ this.transport = transport
28
+ }
29
+
30
+ async close(): Promise<void> {
31
+ await this.transport.close()
32
+ }
33
+
34
+ async getVersion(): Promise<string> {
35
+ const response = await this.transport.send(CLA, INS.GET_VERSION, 0x00, 0x00)
36
+ console.log(`response ${response.length} - ${response.toString('hex')}`)
37
+ return `${response[0]}.${response[1]}.${response[2]}`
38
+ }
39
+
40
+ async getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display = false): Promise<readonly [Account, number]> {
41
+ if ((targetGroup ?? 0) >= GROUP_NUM) {
42
+ throw Error(`Invalid targetGroup: ${targetGroup}`)
43
+ }
44
+
45
+ if (keyType === 'bip340-schnorr') {
46
+ throw Error('BIP340-Schnorr is not supported yet')
47
+ }
48
+
49
+ const p1 = targetGroup === undefined ? 0x00 : GROUP_NUM
50
+ const p2 = targetGroup === undefined ? 0x00 : targetGroup
51
+ const payload = Buffer.concat([serde.serializePath(startPath), Buffer.from([display ? 1 : 0])]);
52
+ const response = await this.transport.send(CLA, INS.GET_PUBLIC_KEY, p1, p2, payload)
53
+ const publicKey = ec.keyFromPublic(response.slice(0, 65)).getPublic(true, 'hex')
54
+ const address = addressFromPublicKey(publicKey)
55
+ const group = groupOfAddress(address)
56
+ const hdIndex = response.slice(65, 69).readUInt32BE(0)
57
+
58
+ return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex] as const
59
+ }
60
+
61
+ async signHash(path: string, hash: Buffer): Promise<string> {
62
+ if (hash.length !== HASH_LEN) {
63
+ throw new Error('Invalid hash length')
64
+ }
65
+
66
+ const data = Buffer.concat([serde.serializePath(path), hash])
67
+ console.log(`data ${data.length}`)
68
+ const response = await this.transport.send(CLA, INS.SIGN_HASH, 0x00, 0x00, data, [StatusCodes.OK])
69
+ console.log(`response ${response.length} - ${response.toString('hex')}`)
70
+
71
+ return decodeSignature(response)
72
+ }
73
+
74
+ async signUnsignedTx(
75
+ path: string,
76
+ unsignedTx: Buffer,
77
+ tokenMetadata: TokenMetadata[] = []
78
+ ): Promise<string> {
79
+ console.log(`unsigned tx size: ${unsignedTx.length}`)
80
+ const encodedPath = serde.serializePath(path)
81
+ const encodedTokenMetadata = serde.serializeTokenMetadata(tokenMetadata)
82
+ const firstFrameTxLength = MAX_PAYLOAD_SIZE - 20 - encodedTokenMetadata.length;
83
+ const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length)
84
+ const data = Buffer.concat([encodedPath, encodedTokenMetadata, txData])
85
+ let response = await this.transport.send(CLA, INS.SIGN_TX, 0x00, 0x00, data, [StatusCodes.OK])
86
+ if (unsignedTx.length <= firstFrameTxLength) {
87
+ return decodeSignature(response)
88
+ }
89
+
90
+ const frameLength = MAX_PAYLOAD_SIZE
91
+ let fromIndex = firstFrameTxLength
92
+ while (fromIndex < unsignedTx.length) {
93
+ const remain = unsignedTx.length - fromIndex
94
+ const toIndex = remain > frameLength ? (fromIndex + frameLength) : unsignedTx.length
95
+ const data = unsignedTx.slice(fromIndex, toIndex)
96
+ response = await this.transport.send(CLA, INS.SIGN_TX, 0x01, 0x00, data, [StatusCodes.OK])
97
+ fromIndex = toIndex
98
+ }
99
+
100
+ return decodeSignature(response)
101
+ }
102
+ }
103
+
104
+ function decodeSignature(response: Buffer): string {
105
+ // Decode signature: https://bitcoin.stackexchange.com/a/12556
106
+ const rLen = response.slice(3, 4)[0]
107
+ const r = response.slice(4, 4 + rLen)
108
+ const sLen = response.slice(5 + rLen, 6 + rLen)[0]
109
+ const s = response.slice(6 + rLen, 6 + rLen + sLen)
110
+ console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
111
+ return encodeHexSignature(r.toString('hex'), s.toString('hex'))
112
+ }
package/src/serde.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isHexString } from "@alephium/web3"
2
+ import { MAX_TOKEN_SIZE, MAX_TOKEN_SYMBOL_LENGTH, TOKEN_METADATA_SIZE, TokenMetadata } from "./types"
2
3
 
3
4
  export const TRUE = 0x10
4
5
  export const FALSE = 0x00
@@ -31,16 +32,6 @@ export function serializePath(path: string): Buffer {
31
32
  return buffer
32
33
  }
33
34
 
34
- export const MAX_TOKEN_SIZE = 5
35
- export const MAX_TOKEN_SYMBOL_LENGTH = 12
36
- export const TOKEN_METADATA_SIZE = 46
37
- export interface TokenMetadata {
38
- version: number,
39
- tokenId: string,
40
- symbol: string,
41
- decimals: number
42
- }
43
-
44
35
  function symbolToBytes(symbol: string): Buffer {
45
36
  const buffer = Buffer.alloc(MAX_TOKEN_SYMBOL_LENGTH, 0)
46
37
  for (let i = 0; i < symbol.length; i++) {
package/src/types.ts ADDED
@@ -0,0 +1,10 @@
1
+ export const MAX_TOKEN_SIZE = 5
2
+ export const MAX_TOKEN_SYMBOL_LENGTH = 12
3
+ export const TOKEN_METADATA_SIZE = 46
4
+
5
+ export interface TokenMetadata {
6
+ version: number,
7
+ tokenId: string,
8
+ symbol: string,
9
+ decimals: number
10
+ }
@@ -1,10 +1,10 @@
1
- import AlephiumApp, { GROUP_NUM } from '../src'
1
+ import AlephiumApp, { GROUP_NUM } from '../src/ledger-app'
2
2
  import { ALPH_TOKEN_ID, Address, DUST_AMOUNT, NodeProvider, ONE_ALPH, binToHex, codec, groupOfAddress, node, sleep, transactionVerifySignature, waitForTxConfirmation, web3 } from '@alephium/web3'
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
6
  import { approveAddress, approveHash, approveTx, createTransport, enableBlindSigning, getRandomInt, needToAutoApprove, OutputType, skipBlindSigningWarning, staxFlexApproveOnce } from './utils'
7
- import { TokenMetadata } from '../src/serde'
7
+ import { TokenMetadata } from '../src/types'
8
8
  import { randomInt } from 'crypto'
9
9
 
10
10
  describe('ledger wallet', () => {