@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.
@@ -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.2.1",
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/cli": "^0.14.0",
29
- "@alephium/web3": "^0.14.0",
30
- "@alephium/web3-test": "^0.14.0",
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
- "@ledgerhq/hw-transport-node-hid": "^6.27.9",
36
- "@ledgerhq/hw-transport-node-speculos": "^6.27.9",
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": "^16.7.8",
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
- SIGN_HASH = 0x02
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 signHash(path: string, hash: Buffer): Promise<string> {
57
- if (hash.length !== HASH_LEN) {
58
- throw new Error('Invalid hash length')
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 data = Buffer.concat([serde.serializePath(path), hash])
62
- console.log(`data ${data.length}`)
63
- const response = await this.transport.send(CLA, INS.SIGN_HASH, 0x00, 0x00, data, [StatusCodes.OK])
64
- console.log(`response ${response.length} - ${response.toString('hex')}`)
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
- // Decode signature: https://bitcoin.stackexchange.com/a/12556
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
+ }
@@ -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 test node', async () => {
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 [account] = await app.getAccount(path)
19
- console.log(`${JSON.stringify(account)}`)
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 hash = Buffer.from(blake.blake2b(Buffer.from([0, 1, 2, 3, 4]), undefined, 32))
22
- const signature = await app.signHash(path, hash)
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
- }, 100000)
151
+ }, 120000)
28
152
  })