@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.
- 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 -19
- 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 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
|
+
}
|
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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
84
|
-
for (let group = 0; 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
|
|
95
|
-
for (let group = 0; 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
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 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
package/test/wallet.test.ts
CHANGED
|
@@ -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/
|
|
7
|
+
import { TokenMetadata } from '../src/types'
|
|
8
8
|
import { randomInt } from 'crypto'
|
|
9
9
|
|
|
10
10
|
describe('ledger wallet', () => {
|