@alephium/ledger-app 0.5.0 → 0.5.2

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 declare 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.AlephiumApp = 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.AlephiumApp = 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;
@@ -1,32 +1,9 @@
1
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
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
4
  };
28
5
  Object.defineProperty(exports, "__esModule", { value: true });
29
- const src_1 = __importStar(require("../src"));
6
+ const ledger_app_1 = require("../src/ledger-app");
30
7
  const web3_1 = require("@alephium/web3");
31
8
  const web3_test_1 = require("@alephium/web3-test");
32
9
  const web3_wallet_1 = require("@alephium/web3-wallet");
@@ -56,14 +33,14 @@ describe('ledger wallet', () => {
56
33
  }
57
34
  it('should get version', async () => {
58
35
  const transport = await (0, utils_1.createTransport)();
59
- const app = new src_1.default(transport);
36
+ const app = new ledger_app_1.AlephiumApp(transport);
60
37
  const version = await app.getVersion();
61
38
  expect(version).toBe('0.4.0');
62
39
  await app.close();
63
40
  });
64
41
  it('should get public key', async () => {
65
42
  const transport = await (0, utils_1.createTransport)();
66
- const app = new src_1.default(transport);
43
+ const app = new ledger_app_1.AlephiumApp(transport);
67
44
  const [account, hdIndex] = await app.getAccount(path);
68
45
  expect(hdIndex).toBe(pathIndex);
69
46
  console.log(account);
@@ -71,7 +48,7 @@ describe('ledger wallet', () => {
71
48
  });
72
49
  it('should get public key and confirm address', async () => {
73
50
  const transport = await (0, utils_1.createTransport)();
74
- const app = new src_1.default(transport);
51
+ const app = new ledger_app_1.AlephiumApp(transport);
75
52
  (0, utils_1.approveAddress)();
76
53
  const [account, hdIndex] = await app.getAccount(path, undefined, undefined, true);
77
54
  expect(hdIndex).toBe(pathIndex);
@@ -80,8 +57,8 @@ describe('ledger wallet', () => {
80
57
  }, 30000);
81
58
  it('should get public key for group', async () => {
82
59
  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++) {
60
+ const app = new ledger_app_1.AlephiumApp(transport);
61
+ for (let group = 0; group < ledger_app_1.GROUP_NUM; group++) {
85
62
  const [account, hdIndex] = await app.getAccount(path, group);
86
63
  expect(hdIndex >= pathIndex).toBe(true);
87
64
  expect((0, web3_1.groupOfAddress)(account.address)).toBe(group);
@@ -91,15 +68,15 @@ describe('ledger wallet', () => {
91
68
  });
92
69
  it('should get public key for group for Schnorr signature', async () => {
93
70
  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++) {
71
+ const app = new ledger_app_1.AlephiumApp(transport);
72
+ for (let group = 0; group < ledger_app_1.GROUP_NUM; group++) {
96
73
  await expect(app.getAccount(path, group, 'bip340-schnorr')).rejects.toThrow('BIP340-Schnorr is not supported yet');
97
74
  }
98
75
  await app.close();
99
76
  });
100
77
  it('should sign hash', async () => {
101
78
  const transport = await (0, utils_1.createTransport)();
102
- const app = new src_1.default(transport);
79
+ const app = new ledger_app_1.AlephiumApp(transport);
103
80
  const [account] = await app.getAccount(path);
104
81
  console.log(account);
105
82
  const hash = Buffer.from(blakejs_1.default.blake2b(Buffer.from([0, 1, 2, 3, 4]), undefined, 32));
@@ -111,7 +88,7 @@ describe('ledger wallet', () => {
111
88
  }, 10000);
112
89
  it('should transfer alph to one address', async () => {
113
90
  const transport = await (0, utils_1.createTransport)();
114
- const app = new src_1.default(transport);
91
+ const app = new ledger_app_1.AlephiumApp(transport);
115
92
  const [testAccount] = await app.getAccount(path);
116
93
  await transferToAddress(testAccount.address);
117
94
  const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
@@ -137,7 +114,7 @@ describe('ledger wallet', () => {
137
114
  }, 120000);
138
115
  it('should transfer alph to multiple addresses', async () => {
139
116
  const transport = await (0, utils_1.createTransport)();
140
- const app = new src_1.default(transport);
117
+ const app = new ledger_app_1.AlephiumApp(transport);
141
118
  const [testAccount] = await app.getAccount(path);
142
119
  await transferToAddress(testAccount.address);
143
120
  const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
@@ -167,7 +144,7 @@ describe('ledger wallet', () => {
167
144
  }, 120000);
168
145
  it('should transfer alph to multisig address', async () => {
169
146
  const transport = await (0, utils_1.createTransport)();
170
- const app = new src_1.default(transport);
147
+ const app = new ledger_app_1.AlephiumApp(transport);
171
148
  const [testAccount] = await app.getAccount(path);
172
149
  await transferToAddress(testAccount.address);
173
150
  const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
@@ -194,7 +171,7 @@ describe('ledger wallet', () => {
194
171
  }, 120000);
195
172
  it('should transfer token to multisig address', async () => {
196
173
  const transport = await (0, utils_1.createTransport)();
197
- const app = new src_1.default(transport);
174
+ const app = new ledger_app_1.AlephiumApp(transport);
198
175
  const [testAccount] = await app.getAccount(path);
199
176
  await transferToAddress(testAccount.address);
200
177
  const tokenInfo = await (0, web3_test_1.mintToken)(testAccount.address, 2222222222222222222222222n);
@@ -259,7 +236,7 @@ describe('ledger wallet', () => {
259
236
  }
260
237
  it('should transfer token with metadata', async () => {
261
238
  const transport = await (0, utils_1.createTransport)();
262
- const app = new src_1.default(transport);
239
+ const app = new ledger_app_1.AlephiumApp(transport);
263
240
  const [testAccount] = await app.getAccount(path);
264
241
  await transferToAddress(testAccount.address);
265
242
  const toAddress = '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7';
@@ -288,7 +265,7 @@ describe('ledger wallet', () => {
288
265
  }, 120000);
289
266
  it('should reject tx if the metadata version is invalid', async () => {
290
267
  const transport = await (0, utils_1.createTransport)();
291
- const app = new src_1.default(transport);
268
+ const app = new ledger_app_1.AlephiumApp(transport);
292
269
  const [testAccount] = await app.getAccount(path);
293
270
  await transferToAddress(testAccount.address);
294
271
  const toAddress = '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7';
@@ -306,7 +283,7 @@ describe('ledger wallet', () => {
306
283
  }, 120000);
307
284
  it('should transfer from multiple inputs', async () => {
308
285
  const transport = await (0, utils_1.createTransport)();
309
- const app = new src_1.default(transport);
286
+ const app = new ledger_app_1.AlephiumApp(transport);
310
287
  const [testAccount] = await app.getAccount(path);
311
288
  for (let i = 0; i < 20; i += 1) {
312
289
  await transferToAddress(testAccount.address, web3_1.ONE_ALPH);
@@ -344,7 +321,7 @@ describe('ledger wallet', () => {
344
321
  }
345
322
  it('should test external inputs', async () => {
346
323
  const transport = await (0, utils_1.createTransport)();
347
- const app = new src_1.default(transport);
324
+ const app = new ledger_app_1.AlephiumApp(transport);
348
325
  const [testAccount] = await app.getAccount(path);
349
326
  const { account: newAccount, unlockScript: unlockScript0 } = getAccount(testAccount.group);
350
327
  for (let i = 0; i < 2; i += 1) {
@@ -401,7 +378,7 @@ describe('ledger wallet', () => {
401
378
  }, 120000);
402
379
  it('should test self transfer tx', async () => {
403
380
  const transport = await (0, utils_1.createTransport)();
404
- const app = new src_1.default(transport);
381
+ const app = new ledger_app_1.AlephiumApp(transport);
405
382
  const [testAccount] = await app.getAccount(path);
406
383
  await transferToAddress(testAccount.address);
407
384
  const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
@@ -427,7 +404,7 @@ describe('ledger wallet', () => {
427
404
  }, 12000);
428
405
  it('should test script execution tx', async () => {
429
406
  const transport = await (0, utils_1.createTransport)();
430
- const app = new src_1.default(transport);
407
+ const app = new ledger_app_1.AlephiumApp(transport);
431
408
  const [testAccount] = await app.getAccount(path);
432
409
  await transferToAddress(testAccount.address);
433
410
  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.2",
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 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', () => {