@alephium/ledger-app 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/merkle-tree/proofs.json +1 -0
- package/dist/merkle-tree/token.json +1107 -0
- package/dist/src/ledger-app.d.ts +1 -2
- package/dist/src/ledger-app.js +33 -20
- package/dist/src/merkle.d.ts +9 -0
- package/dist/src/merkle.js +59 -0
- package/dist/src/serde.d.ts +3 -1
- package/dist/src/serde.js +19 -17
- package/dist/src/tx-encoder.d.ts +11 -0
- package/dist/src/tx-encoder.js +82 -0
- package/dist/src/types.d.ts +1 -0
- package/dist/src/types.js +3 -1
- package/dist/test/merkle.test.d.ts +1 -0
- package/dist/test/merkle.test.js +18 -0
- package/dist/test/tx-encoder.test.d.ts +1 -0
- package/dist/test/tx-encoder.test.js +73 -0
- package/dist/test/utils.d.ts +6 -3
- package/dist/test/utils.js +13 -4
- package/dist/test/wallet.test.js +96 -32
- package/merkle-tree/proofs.json +1 -0
- package/merkle-tree/token.json +1107 -0
- package/package.json +8 -6
- package/src/index.ts +1 -1
- package/src/ledger-app.ts +38 -30
- package/src/merkle.ts +63 -0
- package/src/serde.ts +19 -18
- package/src/tx-encoder.ts +90 -0
- package/src/types.ts +3 -1
- package/test/merkle.test.ts +20 -0
- package/test/tx-encoder.test.ts +80 -0
- package/test/utils.ts +9 -0
- package/test/wallet.test.ts +101 -38
package/dist/src/ledger-app.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { Account, KeyType } from '@alephium/web3';
|
|
3
3
|
import Transport from '@ledgerhq/hw-transport';
|
|
4
|
-
import { TokenMetadata } from './types';
|
|
5
4
|
export declare const CLA = 128;
|
|
6
5
|
export declare enum INS {
|
|
7
6
|
GET_VERSION = 0,
|
|
@@ -18,5 +17,5 @@ export declare class AlephiumApp {
|
|
|
18
17
|
getVersion(): Promise<string>;
|
|
19
18
|
getAccount(startPath: string, targetGroup?: number, keyType?: KeyType, display?: boolean): Promise<readonly [Account, number]>;
|
|
20
19
|
signHash(path: string, hash: Buffer): Promise<string>;
|
|
21
|
-
signUnsignedTx(path: string, unsignedTx: Buffer
|
|
20
|
+
signUnsignedTx(path: string, unsignedTx: Buffer): Promise<string>;
|
|
22
21
|
}
|
package/dist/src/ledger-app.js
CHANGED
|
@@ -28,6 +28,9 @@ const web3_1 = require("@alephium/web3");
|
|
|
28
28
|
const hw_transport_1 = require("@ledgerhq/hw-transport");
|
|
29
29
|
const serde = __importStar(require("./serde"));
|
|
30
30
|
const elliptic_1 = require("elliptic");
|
|
31
|
+
const types_1 = require("./types");
|
|
32
|
+
const tx_encoder_1 = require("./tx-encoder");
|
|
33
|
+
const merkle_1 = require("./merkle");
|
|
31
34
|
const ec = new elliptic_1.ec('secp256k1');
|
|
32
35
|
exports.CLA = 0x80;
|
|
33
36
|
var INS;
|
|
@@ -39,8 +42,6 @@ var INS;
|
|
|
39
42
|
})(INS = exports.INS || (exports.INS = {}));
|
|
40
43
|
exports.GROUP_NUM = 4;
|
|
41
44
|
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
45
|
class AlephiumApp {
|
|
45
46
|
constructor(transport) {
|
|
46
47
|
this.transport = transport;
|
|
@@ -80,30 +81,42 @@ class AlephiumApp {
|
|
|
80
81
|
console.log(`response ${response.length} - ${response.toString('hex')}`);
|
|
81
82
|
return decodeSignature(response);
|
|
82
83
|
}
|
|
83
|
-
async signUnsignedTx(path, unsignedTx
|
|
84
|
+
async signUnsignedTx(path, unsignedTx) {
|
|
84
85
|
console.log(`unsigned tx size: ${unsignedTx.length}`);
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
let response =
|
|
91
|
-
|
|
92
|
-
|
|
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;
|
|
86
|
+
const tokenMetadata = getTokenMetadata(unsignedTx);
|
|
87
|
+
serde.checkTokenMetadata(tokenMetadata);
|
|
88
|
+
const tokenMetadataFrames = (0, tx_encoder_1.encodeTokenMetadata)(tokenMetadata);
|
|
89
|
+
const txFrames = (0, tx_encoder_1.encodeUnsignedTx)(path, unsignedTx);
|
|
90
|
+
const allFrames = [...tokenMetadataFrames, ...txFrames];
|
|
91
|
+
let response = undefined;
|
|
92
|
+
for (const frame of allFrames) {
|
|
93
|
+
response = await this.transport.send(exports.CLA, INS.SIGN_TX, frame.p1, frame.p2, frame.data, [hw_transport_1.StatusCodes.OK]);
|
|
102
94
|
}
|
|
103
95
|
return decodeSignature(response);
|
|
104
96
|
}
|
|
105
97
|
}
|
|
106
98
|
exports.AlephiumApp = AlephiumApp;
|
|
99
|
+
function getTokenMetadata(unsignedTx) {
|
|
100
|
+
const result = [];
|
|
101
|
+
const outputs = web3_1.codec.unsignedTxCodec.decode(unsignedTx).fixedOutputs;
|
|
102
|
+
outputs.forEach((output) => {
|
|
103
|
+
output.tokens.forEach((t) => {
|
|
104
|
+
const tokenIdHex = (0, web3_1.binToHex)(t.tokenId);
|
|
105
|
+
if (result.find((t) => isTokenIdEqual(t.tokenId, tokenIdHex)) !== undefined) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const metadata = merkle_1.merkleTokens.find((t) => isTokenIdEqual(t.tokenId, tokenIdHex));
|
|
109
|
+
if (metadata !== undefined && metadata.symbol.length <= types_1.MAX_TOKEN_SYMBOL_LENGTH) {
|
|
110
|
+
result.push(metadata);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
const size = Math.min(result.length, types_1.MAX_TOKEN_SIZE);
|
|
115
|
+
return result.slice(0, size);
|
|
116
|
+
}
|
|
117
|
+
function isTokenIdEqual(a, b) {
|
|
118
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
119
|
+
}
|
|
107
120
|
function decodeSignature(response) {
|
|
108
121
|
// Decode signature: https://bitcoin.stackexchange.com/a/12556
|
|
109
122
|
const rLen = response.slice(3, 4)[0];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TokenMetadata } from './types';
|
|
2
|
+
export declare const merkleTokens: TokenMetadata[];
|
|
3
|
+
export declare function hashPair(a: Uint8Array, b: Uint8Array): Uint8Array;
|
|
4
|
+
export declare function generateProofs(): {
|
|
5
|
+
proofs: Record<string, string>;
|
|
6
|
+
root: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const tokenMerkleRoot: Uint8Array;
|
|
9
|
+
export declare const tokenMerkleProofs: Record<string, string>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.tokenMerkleProofs = exports.tokenMerkleRoot = exports.generateProofs = exports.hashPair = exports.merkleTokens = void 0;
|
|
7
|
+
const web3_1 = require("@alephium/web3");
|
|
8
|
+
const token_json_1 = __importDefault(require("../merkle-tree/token.json"));
|
|
9
|
+
const proofs_json_1 = __importDefault(require("../merkle-tree/proofs.json"));
|
|
10
|
+
const serde_1 = require("./serde");
|
|
11
|
+
const blakejs_1 = require("blakejs");
|
|
12
|
+
exports.merkleTokens = token_json_1.default.tokens.map((token) => {
|
|
13
|
+
return {
|
|
14
|
+
version: 0,
|
|
15
|
+
tokenId: token.id,
|
|
16
|
+
symbol: token.symbol,
|
|
17
|
+
decimals: token.decimals
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
function hashPair(a, b) {
|
|
21
|
+
return (0, blakejs_1.blake2b)(Buffer.concat([a, b].sort(Buffer.compare)), undefined, 32);
|
|
22
|
+
}
|
|
23
|
+
exports.hashPair = hashPair;
|
|
24
|
+
function generateMerkleTree(tokens) {
|
|
25
|
+
let level = tokens.map((token) => (0, blakejs_1.blake2b)((0, serde_1.serializeSingleTokenMetadata)(token), undefined, 32));
|
|
26
|
+
const tree = [];
|
|
27
|
+
while (level.length > 1) {
|
|
28
|
+
tree.push(level);
|
|
29
|
+
level = level.reduce((acc, _, i, arr) => {
|
|
30
|
+
if (i % 2 === 0) {
|
|
31
|
+
acc.push(i + 1 < arr.length ? hashPair(arr[i], arr[i + 1]) : arr[i]);
|
|
32
|
+
}
|
|
33
|
+
return acc;
|
|
34
|
+
}, []);
|
|
35
|
+
}
|
|
36
|
+
tree.push(level); // Root
|
|
37
|
+
return tree;
|
|
38
|
+
}
|
|
39
|
+
function generateProofs() {
|
|
40
|
+
const tree = generateMerkleTree(exports.merkleTokens);
|
|
41
|
+
const proofs = exports.merkleTokens.reduce((acc, token, tokenIndex) => {
|
|
42
|
+
const proof = tree.slice(0, -1).reduce((proofAcc, level, levelIndex) => {
|
|
43
|
+
const index = Math.floor(tokenIndex / 2 ** levelIndex);
|
|
44
|
+
const pairIndex = index % 2 === 0 ? index + 1 : index - 1;
|
|
45
|
+
const siblingOrUncle = level[pairIndex];
|
|
46
|
+
if (siblingOrUncle) {
|
|
47
|
+
proofAcc.push(siblingOrUncle);
|
|
48
|
+
}
|
|
49
|
+
return proofAcc;
|
|
50
|
+
}, []);
|
|
51
|
+
acc[token.tokenId] = proof.map((hash) => (0, web3_1.binToHex)(hash)).join('');
|
|
52
|
+
return acc;
|
|
53
|
+
}, {});
|
|
54
|
+
console.log('root', tree[tree.length - 1].map((hash) => (0, web3_1.binToHex)(hash)).join(''));
|
|
55
|
+
return { proofs, root: (0, web3_1.binToHex)(tree[tree.length - 1][0]) };
|
|
56
|
+
}
|
|
57
|
+
exports.generateProofs = generateProofs;
|
|
58
|
+
exports.tokenMerkleRoot = (0, web3_1.hexToBinUnsafe)('b3380866c595544781e9da0ccd79399de8878abfb0bf40545b57a287387d419d');
|
|
59
|
+
exports.tokenMerkleProofs = proofs_json_1.default;
|
package/dist/src/serde.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { TokenMetadata } from
|
|
2
|
+
import { TokenMetadata } from './types';
|
|
3
3
|
export declare const TRUE = 16;
|
|
4
4
|
export declare const FALSE = 0;
|
|
5
5
|
export declare function splitPath(path: string): number[];
|
|
6
6
|
export declare function serializePath(path: string): Buffer;
|
|
7
|
+
export declare function checkTokenMetadata(tokens: TokenMetadata[]): void;
|
|
8
|
+
export declare function serializeSingleTokenMetadata(metadata: TokenMetadata): Buffer;
|
|
7
9
|
export declare function serializeTokenMetadata(tokens: TokenMetadata[]): Buffer;
|
package/dist/src/serde.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.serializeTokenMetadata = exports.serializePath = exports.splitPath = exports.FALSE = exports.TRUE = void 0;
|
|
3
|
+
exports.serializeTokenMetadata = exports.serializeSingleTokenMetadata = exports.checkTokenMetadata = exports.serializePath = exports.splitPath = exports.FALSE = exports.TRUE = void 0;
|
|
4
4
|
const web3_1 = require("@alephium/web3");
|
|
5
5
|
const types_1 = require("./types");
|
|
6
6
|
exports.TRUE = 0x10;
|
|
@@ -39,7 +39,7 @@ function symbolToBytes(symbol) {
|
|
|
39
39
|
}
|
|
40
40
|
return buffer;
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function checkTokenMetadata(tokens) {
|
|
43
43
|
const hasDuplicate = tokens.some((token, index) => index !== tokens.findIndex((t) => t.tokenId === token.tokenId));
|
|
44
44
|
if (hasDuplicate) {
|
|
45
45
|
throw new Error(`There are duplicate tokens`);
|
|
@@ -56,22 +56,24 @@ function check(tokens) {
|
|
|
56
56
|
throw new Error(`The token size exceeds maximum size`);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
exports.checkTokenMetadata = checkTokenMetadata;
|
|
60
|
+
function serializeSingleTokenMetadata(metadata) {
|
|
61
|
+
const symbolBytes = symbolToBytes(metadata.symbol);
|
|
62
|
+
const buffer = Buffer.concat([
|
|
63
|
+
Buffer.from([metadata.version]),
|
|
64
|
+
Buffer.from(metadata.tokenId, 'hex'),
|
|
65
|
+
symbolBytes,
|
|
66
|
+
Buffer.from([metadata.decimals]),
|
|
67
|
+
]);
|
|
68
|
+
if (buffer.length !== types_1.TOKEN_METADATA_SIZE) {
|
|
69
|
+
throw new Error(`Invalid token metadata: ${metadata}`);
|
|
70
|
+
}
|
|
71
|
+
return buffer;
|
|
72
|
+
}
|
|
73
|
+
exports.serializeSingleTokenMetadata = serializeSingleTokenMetadata;
|
|
59
74
|
function serializeTokenMetadata(tokens) {
|
|
60
|
-
|
|
61
|
-
const array = tokens
|
|
62
|
-
.map((metadata) => {
|
|
63
|
-
const symbolBytes = symbolToBytes(metadata.symbol);
|
|
64
|
-
const buffer = Buffer.concat([
|
|
65
|
-
Buffer.from([metadata.version]),
|
|
66
|
-
Buffer.from(metadata.tokenId, 'hex'),
|
|
67
|
-
symbolBytes,
|
|
68
|
-
Buffer.from([metadata.decimals])
|
|
69
|
-
]);
|
|
70
|
-
if (buffer.length !== types_1.TOKEN_METADATA_SIZE) {
|
|
71
|
-
throw new Error(`Invalid token metadata: ${metadata}`);
|
|
72
|
-
}
|
|
73
|
-
return buffer;
|
|
74
|
-
});
|
|
75
|
+
checkTokenMetadata(tokens);
|
|
76
|
+
const array = tokens.map((metadata) => serializeSingleTokenMetadata(metadata));
|
|
75
77
|
return Buffer.concat([Buffer.from([array.length]), ...array]);
|
|
76
78
|
}
|
|
77
79
|
exports.serializeTokenMetadata = serializeTokenMetadata;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { TokenMetadata } from "./types";
|
|
3
|
+
export interface Frame {
|
|
4
|
+
p1: number;
|
|
5
|
+
p2: number;
|
|
6
|
+
data: Buffer;
|
|
7
|
+
}
|
|
8
|
+
export declare function encodeTokenMetadata(tokenMetadata: TokenMetadata[]): Frame[];
|
|
9
|
+
export declare function encodeProofLength(length: number): Uint8Array;
|
|
10
|
+
export declare function encodeUnsignedTx(path: string, unsignedTx: Buffer): Frame[];
|
|
11
|
+
export declare function assert(condition: boolean, msg: string): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assert = exports.encodeUnsignedTx = exports.encodeProofLength = exports.encodeTokenMetadata = void 0;
|
|
4
|
+
const merkle_1 = require("./merkle");
|
|
5
|
+
const serde_1 = require("./serde");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
function encodeTokenMetadata(tokenMetadata) {
|
|
8
|
+
const frames = tokenMetadata.flatMap((metadata, index) => {
|
|
9
|
+
const isFirstToken = index === 0;
|
|
10
|
+
const firstFramePrefix = isFirstToken ? Buffer.from([tokenMetadata.length]) : Buffer.alloc(0);
|
|
11
|
+
const buffers = encodeTokenAndProof(metadata, firstFramePrefix);
|
|
12
|
+
if (buffers.length === 0)
|
|
13
|
+
return [];
|
|
14
|
+
assert(buffers.every((buffer) => buffer.length <= types_1.MAX_PAYLOAD_SIZE), 'Invalid token frame size');
|
|
15
|
+
const frames = [];
|
|
16
|
+
const firstFrameP2 = isFirstToken ? 0 : 1;
|
|
17
|
+
frames.push({ p1: 0, p2: firstFrameP2, data: buffers[0] });
|
|
18
|
+
buffers.slice(1).forEach((data) => frames.push({ p1: 0, p2: 2, data }));
|
|
19
|
+
return frames;
|
|
20
|
+
});
|
|
21
|
+
if (frames.length === 0) {
|
|
22
|
+
return [{ p1: 0, p2: 0, data: Buffer.from([0]) }];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
return frames;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.encodeTokenMetadata = encodeTokenMetadata;
|
|
29
|
+
function encodeTokenAndProof(tokenMetadata, firstFramePrefix) {
|
|
30
|
+
const proof = merkle_1.tokenMerkleProofs[tokenMetadata.tokenId];
|
|
31
|
+
if (proof === undefined)
|
|
32
|
+
return [];
|
|
33
|
+
const proofBytes = Buffer.from(proof, 'hex');
|
|
34
|
+
const encodedProofLength = encodeProofLength(proofBytes.length);
|
|
35
|
+
const encodedTokenMetadata = (0, serde_1.serializeSingleTokenMetadata)(tokenMetadata);
|
|
36
|
+
const firstFrameRemainSize = types_1.MAX_PAYLOAD_SIZE - encodedTokenMetadata.length - encodedProofLength.length - firstFramePrefix.length;
|
|
37
|
+
const firstFrameProofSize = Math.floor(firstFrameRemainSize / 32) * 32;
|
|
38
|
+
if (firstFrameProofSize >= proofBytes.length) {
|
|
39
|
+
return [Buffer.concat([firstFramePrefix, encodedTokenMetadata, encodedProofLength, proofBytes])];
|
|
40
|
+
}
|
|
41
|
+
const firstFrameProof = proofBytes.slice(0, firstFrameProofSize);
|
|
42
|
+
const result = [Buffer.concat([firstFramePrefix, encodedTokenMetadata, encodedProofLength, firstFrameProof])];
|
|
43
|
+
let from_index = firstFrameProofSize;
|
|
44
|
+
while (from_index < proofBytes.length) {
|
|
45
|
+
const remainProofLength = proofBytes.length - from_index;
|
|
46
|
+
const frameProofSize = Math.min(Math.floor(types_1.MAX_PAYLOAD_SIZE / 32) * 32, remainProofLength);
|
|
47
|
+
const frameProof = proofBytes.slice(from_index, from_index + frameProofSize);
|
|
48
|
+
from_index += frameProofSize;
|
|
49
|
+
result.push(frameProof);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function encodeProofLength(length) {
|
|
54
|
+
assert((length % 32 === 0) && (length > 0 && length < 0xffff), 'Invalid token proof size');
|
|
55
|
+
const buffer = Buffer.alloc(2);
|
|
56
|
+
buffer.writeUint16BE(length);
|
|
57
|
+
return buffer;
|
|
58
|
+
}
|
|
59
|
+
exports.encodeProofLength = encodeProofLength;
|
|
60
|
+
function encodeUnsignedTx(path, unsignedTx) {
|
|
61
|
+
const encodedPath = (0, serde_1.serializePath)(path);
|
|
62
|
+
const firstFrameTxLength = types_1.MAX_PAYLOAD_SIZE - 20;
|
|
63
|
+
if (firstFrameTxLength >= unsignedTx.length) {
|
|
64
|
+
return [{ p1: 1, p2: 0, data: Buffer.concat([encodedPath, unsignedTx]) }];
|
|
65
|
+
}
|
|
66
|
+
const firstFrameTxData = unsignedTx.slice(0, firstFrameTxLength);
|
|
67
|
+
const frames = [{ p1: 1, p2: 0, data: Buffer.concat([encodedPath, firstFrameTxData]) }];
|
|
68
|
+
let fromIndex = firstFrameTxLength;
|
|
69
|
+
while (fromIndex < unsignedTx.length) {
|
|
70
|
+
const remain = unsignedTx.length - fromIndex;
|
|
71
|
+
const frameTxLength = Math.min(types_1.MAX_PAYLOAD_SIZE, remain);
|
|
72
|
+
frames.push({ p1: 1, p2: 1, data: unsignedTx.slice(fromIndex, fromIndex + frameTxLength) });
|
|
73
|
+
fromIndex += frameTxLength;
|
|
74
|
+
}
|
|
75
|
+
return frames;
|
|
76
|
+
}
|
|
77
|
+
exports.encodeUnsignedTx = encodeUnsignedTx;
|
|
78
|
+
function assert(condition, msg) {
|
|
79
|
+
if (!condition)
|
|
80
|
+
throw Error(msg);
|
|
81
|
+
}
|
|
82
|
+
exports.assert = assert;
|
package/dist/src/types.d.ts
CHANGED
package/dist/src/types.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TOKEN_METADATA_SIZE = exports.MAX_TOKEN_SYMBOL_LENGTH = exports.MAX_TOKEN_SIZE = void 0;
|
|
3
|
+
exports.MAX_PAYLOAD_SIZE = exports.TOKEN_METADATA_SIZE = exports.MAX_TOKEN_SYMBOL_LENGTH = exports.MAX_TOKEN_SIZE = void 0;
|
|
4
4
|
exports.MAX_TOKEN_SIZE = 5;
|
|
5
5
|
exports.MAX_TOKEN_SYMBOL_LENGTH = 12;
|
|
6
6
|
exports.TOKEN_METADATA_SIZE = 46;
|
|
7
|
+
// The maximum payload size is 255: https://github.com/LedgerHQ/ledger-live/blob/develop/libs/ledgerjs/packages/hw-transport/src/Transport.ts#L261
|
|
8
|
+
exports.MAX_PAYLOAD_SIZE = 255;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const merkle_1 = require("../src/merkle");
|
|
4
|
+
const serde_1 = require("../src/serde");
|
|
5
|
+
const blakejs_1 = require("blakejs");
|
|
6
|
+
describe('Merkle', () => {
|
|
7
|
+
it('should verify proofs', () => {
|
|
8
|
+
for (const token of merkle_1.merkleTokens) {
|
|
9
|
+
const proof = merkle_1.tokenMerkleProofs[token.tokenId];
|
|
10
|
+
let currentHash = (0, blakejs_1.blake2b)((0, serde_1.serializeSingleTokenMetadata)(token), undefined, 32);
|
|
11
|
+
for (let i = 0; i < proof.length; i += 64) {
|
|
12
|
+
const sibling = proof.slice(i, i + 64);
|
|
13
|
+
currentHash = (0, merkle_1.hashPair)(currentHash, Buffer.from(sibling, 'hex'));
|
|
14
|
+
}
|
|
15
|
+
expect(JSON.stringify(currentHash)).toBe(JSON.stringify(merkle_1.tokenMerkleRoot));
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const merkle_1 = require("../src/merkle");
|
|
4
|
+
const tx_encoder_1 = require("../src/tx-encoder");
|
|
5
|
+
const src_1 = require("../src");
|
|
6
|
+
const serde_1 = require("../src/serde");
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
describe('TxEncoder', () => {
|
|
9
|
+
function shuffle(array) {
|
|
10
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
11
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
12
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
13
|
+
}
|
|
14
|
+
return array;
|
|
15
|
+
}
|
|
16
|
+
function getFrameSize(proofLength) {
|
|
17
|
+
if (proofLength <= 192)
|
|
18
|
+
return 1;
|
|
19
|
+
if (proofLength <= 416)
|
|
20
|
+
return 2;
|
|
21
|
+
if (proofLength <= 640)
|
|
22
|
+
return 3;
|
|
23
|
+
throw Error(`Invalid proof length: ${proofLength}`);
|
|
24
|
+
}
|
|
25
|
+
it('should encode token metadata and proof', () => {
|
|
26
|
+
const frames0 = (0, tx_encoder_1.encodeTokenMetadata)([]);
|
|
27
|
+
expect(frames0).toEqual([{ p1: 0, p2: 0, data: Buffer.from([0]) }]);
|
|
28
|
+
const tokenSize = Math.floor(Math.random() * src_1.MAX_TOKEN_SIZE) + 1;
|
|
29
|
+
(0, tx_encoder_1.assert)(tokenSize >= 1 && tokenSize <= 5, 'Invalid token size');
|
|
30
|
+
const tokens = shuffle(Object.entries(merkle_1.tokenMerkleProofs));
|
|
31
|
+
const selectedTokens = tokens.slice(0, tokenSize);
|
|
32
|
+
const tokenMetadatas = selectedTokens.map(([tokenId]) => merkle_1.merkleTokens.find((t) => t.tokenId === tokenId));
|
|
33
|
+
const frames = (0, tx_encoder_1.encodeTokenMetadata)(tokenMetadatas);
|
|
34
|
+
const tokenAndProofs = Buffer.concat(frames.map((frame, index) => index === 0 ? frame.data.slice(1) : frame.data));
|
|
35
|
+
const expected = Buffer.concat(tokenMetadatas.map((metadata, index) => {
|
|
36
|
+
const proof = Buffer.from(selectedTokens[index][1], 'hex');
|
|
37
|
+
const encodedProofLength = (0, tx_encoder_1.encodeProofLength)(proof.length);
|
|
38
|
+
const encodedTokenMetadata = (0, serde_1.serializeSingleTokenMetadata)(metadata);
|
|
39
|
+
return Buffer.concat([encodedTokenMetadata, encodedProofLength, proof]);
|
|
40
|
+
}));
|
|
41
|
+
expect(tokenAndProofs).toEqual(expected);
|
|
42
|
+
let frameIndex = 0;
|
|
43
|
+
tokenMetadatas.forEach((_, index) => {
|
|
44
|
+
const proof = Buffer.from(selectedTokens[index][1], 'hex');
|
|
45
|
+
const isFirstToken = index === 0;
|
|
46
|
+
const prefixLength = isFirstToken ? 1 + src_1.TOKEN_METADATA_SIZE + 2 : src_1.TOKEN_METADATA_SIZE + 2;
|
|
47
|
+
const tokenFrames = frames.slice(frameIndex, frameIndex + getFrameSize(proof.length));
|
|
48
|
+
const firstFrameP2 = isFirstToken ? 0 : 1;
|
|
49
|
+
expect(tokenFrames[0].p1).toEqual(0);
|
|
50
|
+
expect(tokenFrames[0].p2).toEqual(firstFrameP2);
|
|
51
|
+
tokenFrames.slice(1).forEach((frame) => {
|
|
52
|
+
expect(frame.p1).toEqual(0);
|
|
53
|
+
expect(frame.p2).toEqual(2);
|
|
54
|
+
});
|
|
55
|
+
const expectedProof = Buffer.concat([tokenFrames[0].data.slice(prefixLength), ...tokenFrames.slice(1).map((f) => f.data)]);
|
|
56
|
+
expect(proof).toEqual(expectedProof);
|
|
57
|
+
frameIndex += tokenFrames.length;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it('should encode tx', () => {
|
|
61
|
+
const path = `m/44'/1234'/0'/0/0`;
|
|
62
|
+
const encodedPath = (0, serde_1.serializePath)(path);
|
|
63
|
+
const unsignedTx0 = (0, crypto_1.randomBytes)(200);
|
|
64
|
+
const frames0 = (0, tx_encoder_1.encodeUnsignedTx)(path, unsignedTx0);
|
|
65
|
+
expect(frames0).toEqual([{ p1: 1, p2: 0, data: Buffer.concat([encodedPath, unsignedTx0]) }]);
|
|
66
|
+
const unsignedTx1 = (0, crypto_1.randomBytes)(250);
|
|
67
|
+
const frames1 = (0, tx_encoder_1.encodeUnsignedTx)(path, unsignedTx1);
|
|
68
|
+
expect(frames1).toEqual([
|
|
69
|
+
{ p1: 1, p2: 0, data: Buffer.concat([encodedPath, unsignedTx1.slice(0, src_1.MAX_PAYLOAD_SIZE - 20)]) },
|
|
70
|
+
{ p1: 1, p2: 1, data: unsignedTx1.slice(src_1.MAX_PAYLOAD_SIZE - 20) },
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
});
|
package/dist/test/utils.d.ts
CHANGED
|
@@ -2,14 +2,17 @@ import Transport from '@ledgerhq/hw-transport';
|
|
|
2
2
|
export declare enum OutputType {
|
|
3
3
|
Base = 0,
|
|
4
4
|
Multisig = 1,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Nanos10 = 2,
|
|
6
|
+
Nanos11 = 3,
|
|
7
|
+
Token = 4,
|
|
8
|
+
BaseAndToken = 5,
|
|
9
|
+
MultisigAndToken = 6
|
|
8
10
|
}
|
|
9
11
|
export declare function staxFlexApproveOnce(): Promise<void>;
|
|
10
12
|
export declare function approveTx(outputs: OutputType[], hasExternalInputs?: boolean): Promise<void>;
|
|
11
13
|
export declare function approveHash(): Promise<void>;
|
|
12
14
|
export declare function approveAddress(): Promise<void>;
|
|
15
|
+
export declare function isNanos(): boolean;
|
|
13
16
|
export declare function skipBlindSigningWarning(): void;
|
|
14
17
|
export declare function enableBlindSigning(): Promise<void>;
|
|
15
18
|
export declare function getRandomInt(min: number, max: number): number;
|
package/dist/test/utils.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createTransport = exports.needToAutoApprove = exports.getRandomInt = exports.enableBlindSigning = exports.skipBlindSigningWarning = exports.approveAddress = exports.approveHash = exports.approveTx = exports.staxFlexApproveOnce = exports.OutputType = void 0;
|
|
6
|
+
exports.createTransport = exports.needToAutoApprove = exports.getRandomInt = exports.enableBlindSigning = exports.skipBlindSigningWarning = exports.isNanos = exports.approveAddress = exports.approveHash = exports.approveTx = exports.staxFlexApproveOnce = exports.OutputType = void 0;
|
|
7
7
|
const hw_transport_node_speculos_1 = __importDefault(require("@ledgerhq/hw-transport-node-speculos"));
|
|
8
8
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
9
|
const web3_1 = require("@alephium/web3");
|
|
@@ -29,13 +29,17 @@ var OutputType;
|
|
|
29
29
|
(function (OutputType) {
|
|
30
30
|
OutputType[OutputType["Base"] = 0] = "Base";
|
|
31
31
|
OutputType[OutputType["Multisig"] = 1] = "Multisig";
|
|
32
|
-
OutputType[OutputType["
|
|
33
|
-
OutputType[OutputType["
|
|
34
|
-
OutputType[OutputType["
|
|
32
|
+
OutputType[OutputType["Nanos10"] = 2] = "Nanos10";
|
|
33
|
+
OutputType[OutputType["Nanos11"] = 3] = "Nanos11";
|
|
34
|
+
OutputType[OutputType["Token"] = 4] = "Token";
|
|
35
|
+
OutputType[OutputType["BaseAndToken"] = 5] = "BaseAndToken";
|
|
36
|
+
OutputType[OutputType["MultisigAndToken"] = 6] = "MultisigAndToken";
|
|
35
37
|
})(OutputType = exports.OutputType || (exports.OutputType = {}));
|
|
36
38
|
const NanosClickTable = new Map([
|
|
37
39
|
[OutputType.Base, 5],
|
|
38
40
|
[OutputType.Multisig, 10],
|
|
41
|
+
[OutputType.Nanos10, 10],
|
|
42
|
+
[OutputType.Nanos11, 11],
|
|
39
43
|
[OutputType.Token, 11],
|
|
40
44
|
[OutputType.BaseAndToken, 12],
|
|
41
45
|
[OutputType.MultisigAndToken, 16],
|
|
@@ -123,6 +127,7 @@ async function touch(outputs, hasExternalInputs) {
|
|
|
123
127
|
async function approveTx(outputs, hasExternalInputs = false) {
|
|
124
128
|
if (!needToAutoApprove())
|
|
125
129
|
return;
|
|
130
|
+
await (0, web3_1.sleep)(2000);
|
|
126
131
|
const isSelfTransfer = outputs.length === 0 && !hasExternalInputs;
|
|
127
132
|
if (isSelfTransfer) {
|
|
128
133
|
if (isStaxOrFlex()) {
|
|
@@ -172,6 +177,10 @@ exports.approveAddress = approveAddress;
|
|
|
172
177
|
function isStaxOrFlex() {
|
|
173
178
|
return !getModel().startsWith('nano');
|
|
174
179
|
}
|
|
180
|
+
function isNanos() {
|
|
181
|
+
return getModel() === 'nanos';
|
|
182
|
+
}
|
|
183
|
+
exports.isNanos = isNanos;
|
|
175
184
|
function skipBlindSigningWarning() {
|
|
176
185
|
if (!needToAutoApprove())
|
|
177
186
|
return;
|