@alephium/ledger-app 0.2.1 → 0.3.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/src/index.d.ts +2 -2
- package/dist/src/index.js +29 -15
- package/dist/test/release.test.js +115 -9
- package/dist/test/speculos.test.js +325 -31
- package/docker/devnet.conf +55 -0
- package/docker/docker-compose.yaml +22 -0
- package/package.json +9 -9
- package/src/index.ts +30 -15
- package/test/release.test.ts +134 -10
- package/test/speculos.test.ts +343 -32
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
version: "3.3"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
alephium:
|
|
5
|
+
image: alephium/alephium:v3.3.0
|
|
6
|
+
restart: "no"
|
|
7
|
+
ports:
|
|
8
|
+
- 19973:19973/tcp
|
|
9
|
+
- 19973:19973/udp
|
|
10
|
+
- 127.0.0.1:20973:20973
|
|
11
|
+
- 127.0.0.1:21973:21973
|
|
12
|
+
- 127.0.0.1:22973:22973
|
|
13
|
+
security_opt:
|
|
14
|
+
- no-new-privileges:true
|
|
15
|
+
volumes:
|
|
16
|
+
- ./devnet.conf:/alephium-home/.alephium/user.conf
|
|
17
|
+
# - ~/.alephium:/alephium-home/.alephium
|
|
18
|
+
environment:
|
|
19
|
+
ALEPHIUM_FILE_LOG_LEVEL: "DEBUG"
|
|
20
|
+
healthcheck:
|
|
21
|
+
test: ["CMD", "curl", "http://127.0.0.1:22973/infos/self-clique"]
|
|
22
|
+
timeout: 45s
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alephium/ledger-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "GPL",
|
|
5
5
|
"types": "dist/src/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -25,19 +25,19 @@
|
|
|
25
25
|
"trailingComma": "none"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@alephium/
|
|
29
|
-
"@alephium/web3": "^
|
|
30
|
-
"@
|
|
31
|
-
"@alephium/web3-wallet": "^0.14.0",
|
|
32
|
-
"@ledgerhq/hw-transport": "6.27.10"
|
|
28
|
+
"@alephium/web3": "^1.2.0",
|
|
29
|
+
"@alephium/web3-wallet": "^1.2.0",
|
|
30
|
+
"@ledgerhq/hw-transport": "^6.31.0"
|
|
33
31
|
},
|
|
34
32
|
"devDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
33
|
+
"@alephium/web3-test": "^1.2.0",
|
|
34
|
+
"@alephium/cli": "^1.2.0",
|
|
35
|
+
"@ledgerhq/hw-transport-node-hid": "^6.29.1",
|
|
36
|
+
"@ledgerhq/hw-transport-node-speculos": "^6.29.0",
|
|
37
37
|
"@ledgerhq/logs": "^6.10.1",
|
|
38
38
|
"@types/elliptic": "^6.4.13",
|
|
39
39
|
"@types/jest": "^27.5.1",
|
|
40
|
-
"@types/node": "^
|
|
40
|
+
"@types/node": "^20.8.10",
|
|
41
41
|
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
|
42
42
|
"@typescript-eslint/parser": "^4.30.0",
|
|
43
43
|
"eslint": "^7.32.0",
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const CLA = 0x80
|
|
|
9
9
|
export enum INS {
|
|
10
10
|
GET_VERSION = 0x00,
|
|
11
11
|
GET_PUBLIC_KEY = 0x01,
|
|
12
|
-
|
|
12
|
+
SIGN_TX = 0x02
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const GROUP_NUM = 4
|
|
@@ -53,22 +53,37 @@ export default class AlephiumApp {
|
|
|
53
53
|
return [{ publicKey: publicKey, address: address, group: group, keyType: keyType ?? 'default' }, hdIndex] as const
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
async
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
async signUnsignedTx(path: string, unsignedTx: Buffer): Promise<string> {
|
|
57
|
+
console.log(`unsigned tx size: ${unsignedTx.length}`)
|
|
58
|
+
const encodedPath = serde.serializePath(path)
|
|
59
|
+
const firstFrameTxLength = 256 - 25;
|
|
60
|
+
const txData = unsignedTx.slice(0, unsignedTx.length > firstFrameTxLength ? firstFrameTxLength : unsignedTx.length)
|
|
61
|
+
const data = Buffer.concat([encodedPath, txData])
|
|
62
|
+
let response = await this.transport.send(CLA, INS.SIGN_TX, 0x00, 0x00, data, [StatusCodes.OK])
|
|
63
|
+
if (unsignedTx.length <= firstFrameTxLength) {
|
|
64
|
+
return decodeSignature(response)
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
const frameLength = 256 - 5
|
|
68
|
+
let fromIndex = firstFrameTxLength
|
|
69
|
+
while (fromIndex < unsignedTx.length) {
|
|
70
|
+
const remain = unsignedTx.length - fromIndex
|
|
71
|
+
const toIndex = remain > frameLength ? (fromIndex + frameLength) : unsignedTx.length
|
|
72
|
+
const data = unsignedTx.slice(fromIndex, toIndex)
|
|
73
|
+
response = await this.transport.send(CLA, INS.SIGN_TX, 0x01, 0x00, data, [StatusCodes.OK])
|
|
74
|
+
fromIndex = toIndex
|
|
75
|
+
}
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
const rLen = response.slice(3, 4)[0]
|
|
68
|
-
const r = response.slice(4, 4 + rLen)
|
|
69
|
-
const sLen = response.slice(5 + rLen, 6 + rLen)[0]
|
|
70
|
-
const s = response.slice(6 + rLen, 6 + rLen + sLen)
|
|
71
|
-
console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
|
|
72
|
-
return encodeHexSignature(r.toString('hex'), s.toString('hex'))
|
|
77
|
+
return decodeSignature(response)
|
|
73
78
|
}
|
|
74
79
|
}
|
|
80
|
+
|
|
81
|
+
function decodeSignature(response: Buffer): string {
|
|
82
|
+
// Decode signature: https://bitcoin.stackexchange.com/a/12556
|
|
83
|
+
const rLen = response.slice(3, 4)[0]
|
|
84
|
+
const r = response.slice(4, 4 + rLen)
|
|
85
|
+
const sLen = response.slice(5 + rLen, 6 + rLen)[0]
|
|
86
|
+
const s = response.slice(6 + rLen, 6 + rLen + sLen)
|
|
87
|
+
console.log(`${rLen} - ${r.toString('hex')}\n${sLen} - ${s.toString('hex')}`)
|
|
88
|
+
return encodeHexSignature(r.toString('hex'), s.toString('hex'))
|
|
89
|
+
}
|
package/test/release.test.ts
CHANGED
|
@@ -1,28 +1,152 @@
|
|
|
1
1
|
import NodeTransport from '@ledgerhq/hw-transport-node-hid'
|
|
2
2
|
import { listen } from '@ledgerhq/logs'
|
|
3
|
-
import blake from 'blakejs'
|
|
4
3
|
|
|
5
|
-
import { transactionVerifySignature } from '@alephium/web3'
|
|
4
|
+
import { ALPH_TOKEN_ID, Address, NodeProvider, ONE_ALPH, transactionVerifySignature, web3, waitForTxConfirmation } from '@alephium/web3'
|
|
6
5
|
|
|
7
6
|
import AlephiumApp from '../src'
|
|
7
|
+
import { getSigner, mintToken, transfer } from '@alephium/web3-test'
|
|
8
|
+
import { PrivateKeyWallet } from '@alephium/web3-wallet'
|
|
8
9
|
|
|
9
10
|
describe.skip('Integration', () => {
|
|
10
11
|
const path = `m/44'/1234'/0'/0/0`
|
|
12
|
+
const nodeProvider = new NodeProvider("http://127.0.0.1:22973")
|
|
13
|
+
web3.setCurrentNodeProvider(nodeProvider)
|
|
14
|
+
|
|
15
|
+
function randomP2PKHAddress(groupIndex: number): string {
|
|
16
|
+
return PrivateKeyWallet.Random(groupIndex, nodeProvider).address
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function getALPHBalance(address: Address) {
|
|
20
|
+
const balances = await nodeProvider.addresses.getAddressesAddressBalance(address)
|
|
21
|
+
return BigInt(balances.balance)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function transferToTestAccount(address: Address) {
|
|
25
|
+
const fromAccount = await getSigner()
|
|
26
|
+
const transferResult = await transfer(fromAccount, address, ALPH_TOKEN_ID, ONE_ALPH * 10n)
|
|
27
|
+
await waitForTxConfirmation(transferResult.txId, 1, 1000)
|
|
28
|
+
}
|
|
11
29
|
|
|
12
30
|
// enable this for integration test
|
|
13
|
-
it('should
|
|
31
|
+
it('should transfer ALPH', async () => {
|
|
32
|
+
const transport = await NodeTransport.open('')
|
|
33
|
+
listen((log) => console.log(log))
|
|
34
|
+
const app = new AlephiumApp(transport)
|
|
35
|
+
const [testAccount] = await app.getAccount(path)
|
|
36
|
+
await transferToTestAccount(testAccount.address)
|
|
37
|
+
|
|
38
|
+
const balance0 = await getALPHBalance(testAccount.address)
|
|
39
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
40
|
+
fromPublicKey: testAccount.publicKey,
|
|
41
|
+
destinations: [
|
|
42
|
+
{
|
|
43
|
+
address: randomP2PKHAddress(0),
|
|
44
|
+
attoAlphAmount: (ONE_ALPH * 2n).toString(),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
address: randomP2PKHAddress(0),
|
|
48
|
+
attoAlphAmount: (ONE_ALPH * 3n).toString(),
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
54
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
55
|
+
|
|
56
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
57
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
58
|
+
signature: signature
|
|
59
|
+
})
|
|
60
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
61
|
+
const balance1 = await getALPHBalance(testAccount.address)
|
|
62
|
+
const gasFee = BigInt(buildTxResult.gasAmount) * BigInt(buildTxResult.gasPrice)
|
|
63
|
+
expect(balance1).toEqual(balance0 - gasFee - ONE_ALPH * 5n)
|
|
64
|
+
|
|
65
|
+
await app.close()
|
|
66
|
+
}, 120000)
|
|
67
|
+
|
|
68
|
+
it('should transfer token', async () => {
|
|
69
|
+
const transport = await NodeTransport.open('')
|
|
70
|
+
listen((log) => console.log(log))
|
|
71
|
+
const app = new AlephiumApp(transport)
|
|
72
|
+
const [testAccount] = await app.getAccount(path)
|
|
73
|
+
await transferToTestAccount(testAccount.address)
|
|
74
|
+
|
|
75
|
+
const tokenAmount = 2222222222222222222222222n
|
|
76
|
+
const tokenInfo = await mintToken(testAccount.address, tokenAmount)
|
|
77
|
+
const balances0 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
|
|
78
|
+
|
|
79
|
+
const transferAmount = 2222222222222222222222222n / 2n
|
|
80
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
81
|
+
fromPublicKey: testAccount.publicKey,
|
|
82
|
+
destinations: [
|
|
83
|
+
{
|
|
84
|
+
address: '1BmVCLrjttchZMW7i6df7mTdCKzHpy38bgDbVL1GqV6P7',
|
|
85
|
+
attoAlphAmount: (ONE_ALPH * 2n).toString(),
|
|
86
|
+
tokens: [{ id: tokenInfo.contractId, amount: transferAmount.toString() }]
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
92
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
93
|
+
|
|
94
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
95
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
96
|
+
signature: signature
|
|
97
|
+
})
|
|
98
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
99
|
+
const balances1 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
|
|
100
|
+
const gasFee = BigInt(buildTxResult.gasAmount) * BigInt(buildTxResult.gasPrice)
|
|
101
|
+
const alphBalance = BigInt(balances1.balance)
|
|
102
|
+
expect(alphBalance).toEqual(BigInt(balances0.balance) - gasFee - ONE_ALPH * 2n)
|
|
103
|
+
|
|
104
|
+
const tokenBalance = balances1.tokenBalances!.find((t) => t.id === tokenInfo.contractId)!
|
|
105
|
+
expect(tokenBalance.amount).toEqual((tokenAmount - transferAmount).toString())
|
|
106
|
+
|
|
107
|
+
await app.close()
|
|
108
|
+
}, 120000)
|
|
109
|
+
|
|
110
|
+
it('should transfer to multisig address', async () => {
|
|
14
111
|
const transport = await NodeTransport.open('')
|
|
15
112
|
listen((log) => console.log(log))
|
|
16
113
|
const app = new AlephiumApp(transport)
|
|
114
|
+
const [testAccount] = await app.getAccount(path)
|
|
115
|
+
await transferToTestAccount(testAccount.address)
|
|
116
|
+
|
|
117
|
+
const tokenAmount = 2222222222222222222222222n
|
|
118
|
+
const tokenInfo = await mintToken(testAccount.address, tokenAmount)
|
|
119
|
+
const balances0 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
|
|
120
|
+
|
|
121
|
+
const transferAmount = 2222222222222222222222222n / 2n
|
|
122
|
+
const multiSigAddress = 'X3KYVteDjsKuUP1F68Nv9iEUecnnkMuwjbC985AnA6MvciDFJ5bAUEso2Sd7sGrwZ5rfNLj7Rp4n9XjcyzDiZsrPxfhNkPYcDm3ce8pQ9QasNFByEufMi3QJ3cS9Vk6cTpqNcq';
|
|
123
|
+
const buildTxResult = await nodeProvider.transactions.postTransactionsBuild({
|
|
124
|
+
fromPublicKey: testAccount.publicKey,
|
|
125
|
+
destinations: [
|
|
126
|
+
{
|
|
127
|
+
address: multiSigAddress,
|
|
128
|
+
attoAlphAmount: (ONE_ALPH * 2n).toString(),
|
|
129
|
+
tokens: [{ id: tokenInfo.contractId, amount: transferAmount.toString() }]
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const signature = await app.signUnsignedTx(path, Buffer.from(buildTxResult.unsignedTx, 'hex'))
|
|
135
|
+
expect(transactionVerifySignature(buildTxResult.txId, testAccount.publicKey, signature)).toBe(true)
|
|
17
136
|
|
|
18
|
-
const
|
|
19
|
-
|
|
137
|
+
const submitResult = await nodeProvider.transactions.postTransactionsSubmit({
|
|
138
|
+
unsignedTx: buildTxResult.unsignedTx,
|
|
139
|
+
signature: signature
|
|
140
|
+
})
|
|
141
|
+
await waitForTxConfirmation(submitResult.txId, 1, 1000)
|
|
142
|
+
const balances1 = await nodeProvider.addresses.getAddressesAddressBalance(testAccount.address)
|
|
143
|
+
const gasFee = BigInt(buildTxResult.gasAmount) * BigInt(buildTxResult.gasPrice)
|
|
144
|
+
const alphBalance = BigInt(balances1.balance)
|
|
145
|
+
expect(alphBalance).toEqual(BigInt(balances0.balance) - gasFee - ONE_ALPH * 2n)
|
|
20
146
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
console.log(signature)
|
|
24
|
-
expect(transactionVerifySignature(hash.toString('hex'), account.publicKey, signature)).toBe(true)
|
|
147
|
+
const tokenBalance = balances1.tokenBalances!.find((t) => t.id === tokenInfo.contractId)!
|
|
148
|
+
expect(tokenBalance.amount).toEqual((tokenAmount - transferAmount).toString())
|
|
25
149
|
|
|
26
150
|
await app.close()
|
|
27
|
-
},
|
|
151
|
+
}, 120000)
|
|
28
152
|
})
|