@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.
- package/dist/src/index.d.ts +2 -22
- package/dist/src/index.js +4 -101
- package/dist/src/ledger-app.d.ts +22 -0
- package/dist/src/ledger-app.js +115 -0
- package/dist/src/serde.d.ts +1 -9
- package/dist/src/serde.js +6 -8
- package/dist/src/serde.test.js +2 -1
- package/dist/src/types.d.ts +9 -0
- package/dist/src/types.js +6 -0
- package/dist/test/wallet.test.js +19 -42
- package/package.json +1 -1
- package/src/index.ts +2 -111
- package/src/ledger-app.ts +112 -0
- package/src/serde.ts +1 -10
- package/src/types.ts +10 -0
- package/test/wallet.test.ts +2 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -1,22 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
14
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
+
}
|
package/dist/src/serde.d.ts
CHANGED
|
@@ -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.
|
|
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(
|
|
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 >
|
|
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 >
|
|
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 !==
|
|
70
|
+
if (buffer.length !== types_1.TOKEN_METADATA_SIZE) {
|
|
73
71
|
throw new Error(`Invalid token metadata: ${metadata}`);
|
|
74
72
|
}
|
|
75
73
|
return buffer;
|
package/dist/src/serde.test.js
CHANGED
|
@@ -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(
|
|
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,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;
|
package/dist/test/wallet.test.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
84
|
-
for (let group = 0; 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
|
|
95
|
-
for (let group = 0; 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
package/src/index.ts
CHANGED
|
@@ -1,111 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
package/test/wallet.test.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import AlephiumApp,
|
|
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/
|
|
7
|
+
import { TokenMetadata } from '../src/types'
|
|
8
8
|
import { randomInt } from 'crypto'
|
|
9
9
|
|
|
10
10
|
describe('ledger wallet', () => {
|