Skip to content

Commit 6ed9c3a

Browse files
author
Fabrice Bascoulergue
committed
Add working ledger signature and broadcast implementation
1 parent 88307c6 commit 6ed9c3a

File tree

9 files changed

+44
-19
lines changed

9 files changed

+44
-19
lines changed

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ module.exports = {
1313
tsconfig: 'tsconfig.spec.json',
1414
},
1515
},
16-
testTimeout: 30000,
16+
testTimeout: 60000,
1717
};

src/client/LumClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export class LumClient {
250250
if (!account) {
251251
throw new Error('Account not found');
252252
}
253-
const signDoc = LumUtils.generateSignDoc(doc, wallet.getPublicKey());
253+
const signDoc = LumUtils.generateSignDoc(doc, wallet.getPublicKey(), wallet.signingMode());
254254
const signature = await wallet.signTransaction(doc);
255255
return LumUtils.generateTxBytes(signDoc, signature);
256256
};

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import * as LumUtils from './utils';
22
import * as LumConstants from './constants';
33
import * as LumTypes from './types';
44
import * as LumMessages from './messages';
5-
import { LumRegistry } from './registry';
5+
import { LumRegistry, LumAminoRegistry } from './registry';
66
import { LumWallet, LumWalletFactory } from './wallet';
77
import { LumClient } from './client';
88

9-
export { LumWallet, LumWalletFactory, LumClient, LumTypes, LumUtils, LumConstants, LumMessages, LumRegistry };
9+
export { LumWallet, LumWalletFactory, LumClient, LumTypes, LumUtils, LumConstants, LumMessages, LumRegistry, LumAminoRegistry };

src/registry/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Registry, GeneratedType } from '@cosmjs/proto-signing';
2+
import { AminoTypes } from '@cosmjs/stargate';
23

34
import { Tx } from '../codec/cosmos/tx/v1beta1/tx';
45
import { PubKey } from '../codec/cosmos/crypto/secp256k1/keys';
@@ -26,5 +27,5 @@ class ExtendedRegistry extends Registry {
2627
return Tx.decode(tx);
2728
};
2829
}
29-
30+
export const LumAminoRegistry = new AminoTypes();
3031
export const LumRegistry = new ExtendedRegistry(registryTypes);

src/utils/transactions.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Secp256k1, Secp256k1Signature } from '@cosmjs/crypto';
44
import { makeAuthInfoBytes, makeSignBytes } from '@cosmjs/proto-signing';
55

66
import { TxRaw } from '../codec/cosmos/tx/v1beta1/tx';
7+
import { SignMode } from '../codec/cosmos/tx/signing/v1beta1/signing';
78

89
import { sha256 } from './encoding';
910
import { Fee, Doc, SignDoc } from '../types';
@@ -17,19 +18,20 @@ import { LumRegistry } from '../registry';
1718
* @param fee requested fee
1819
* @param sequence account sequence number
1920
*/
20-
export const generateAuthInfoBytes = (publicKey: Uint8Array, fee: Fee, sequence: number): Uint8Array => {
21+
export const generateAuthInfoBytes = (publicKey: Uint8Array, fee: Fee, sequence: number, signMode: SignMode): Uint8Array => {
2122
const pubkeyAny = publicKeyToProto(publicKey);
2223
const gasLimit = Int53.fromString(fee.gas).toNumber();
23-
return makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence);
24+
return makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence, signMode);
2425
};
2526

2627
/**
2728
* Generate transaction doc to be signed
2829
*
2930
* @param doc document to create the sign version
3031
* @param publicKey public key used for signature
32+
* @param signMode signing mode for the transaction
3133
*/
32-
export const generateSignDoc = (doc: Doc, publicKey: Uint8Array): SignDoc => {
34+
export const generateSignDoc = (doc: Doc, publicKey: Uint8Array, signMode: SignMode): SignDoc => {
3335
const txBody = {
3436
messages: doc.messages,
3537
memo: doc.memo,
@@ -41,7 +43,7 @@ export const generateSignDoc = (doc: Doc, publicKey: Uint8Array): SignDoc => {
4143

4244
return {
4345
bodyBytes,
44-
authInfoBytes: generateAuthInfoBytes(publicKey, doc.fee, doc.sequence),
46+
authInfoBytes: generateAuthInfoBytes(publicKey, doc.fee, doc.sequence, signMode),
4547
chainId: doc.chainId,
4648
accountNumber: Long.fromNumber(doc.accountNumber),
4749
};

src/wallet/LumLedgerWallet.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import Transport from '@ledgerhq/hw-transport';
22
import Cosmos from '@ledgerhq/hw-app-cosmos';
3+
import { ExtendedSecp256k1Signature } from '@cosmjs/crypto';
34

4-
import { LumUtils, LumTypes } from '..';
5+
import { SignMode } from '../codec/cosmos/tx/signing/v1beta1/signing';
6+
import { LumUtils, LumTypes, LumAminoRegistry } from '..';
57
import { LumWallet } from '.';
68

79
export class LumLedgerWallet extends LumWallet {
@@ -13,6 +15,10 @@ export class LumLedgerWallet extends LumWallet {
1315
this.cosmosApp = new Cosmos(transport, 'CSM'); // TODO: CSM identifier should either be LUM or dynamic depending on our ledger implementation
1416
}
1517

18+
signingMode = (): SignMode => {
19+
return SignMode.SIGN_MODE_LEGACY_AMINO_JSON;
20+
};
21+
1622
canChangeAccount = () => {
1723
return true;
1824
};
@@ -37,17 +43,18 @@ export class LumLedgerWallet extends LumWallet {
3743
// sign call: https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-cosmos/src/Cosmos.js
3844
// Expected tx format: https://github.com/cosmos/ledger-cosmos/blob/master/docs/TXSPEC.md
3945
const msg = {
40-
'account_number': doc.accountNumber,
46+
'account_number': doc.accountNumber.toString(),
4147
'chain_id': doc.chainId,
4248
'fee': doc.fee,
4349
'memo': doc.memo,
44-
'msgs': doc.messages,
45-
'sequence': doc.sequence,
50+
'msgs': doc.messages.map((msg) => LumAminoRegistry.toAmino(msg)),
51+
'sequence': doc.sequence.toString(),
4652
};
4753
const { signature, return_code } = await this.cosmosApp.sign(this.hdPath, JSON.stringify(LumUtils.sortJSON(msg)));
4854
if (!signature || return_code === 0) {
4955
throw new Error(`Failed to sign message: error code ${return_code}`);
5056
}
51-
return new Uint8Array(signature);
57+
const sig = ExtendedSecp256k1Signature.fromDer(signature);
58+
return new Uint8Array([...sig.r(32), ...sig.s(32)]);
5259
};
5360
}

src/wallet/LumPaperWallet.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SignMode } from '../codec/cosmos/tx/signing/v1beta1/signing';
12
import { LumUtils, LumConstants, LumTypes } from '..';
23
import { LumWallet } from '.';
34

@@ -21,6 +22,10 @@ export class LumPaperWallet extends LumWallet {
2122
}
2223
}
2324

25+
signingMode = (): SignMode => {
26+
return SignMode.SIGN_MODE_DIRECT;
27+
};
28+
2429
canChangeAccount = (): boolean => {
2530
return !!this.mnemonic;
2631
};
@@ -43,7 +48,7 @@ export class LumPaperWallet extends LumWallet {
4348
if (!this.privateKey || !this.publicKey) {
4449
throw new Error('No account selected.');
4550
}
46-
const signDoc = LumUtils.generateSignDoc(doc, this.getPublicKey());
51+
const signDoc = LumUtils.generateSignDoc(doc, this.getPublicKey(), this.signingMode());
4752
const signBytes = LumUtils.generateSignDocBytes(signDoc);
4853
const hashedMessage = LumUtils.sha256(signBytes);
4954
const signature = await LumUtils.generateSignature(hashedMessage, this.privateKey);

src/wallet/LumWallet.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SignMode } from '../codec/cosmos/tx/signing/v1beta1/signing';
12
import { LumTypes } from '..';
23

34
export abstract class LumWallet {
@@ -30,6 +31,11 @@ export abstract class LumWallet {
3031
return this.publicKey;
3132
};
3233

34+
/**
35+
* Gets the wallet signin mode
36+
*/
37+
abstract signingMode(): SignMode;
38+
3339
/**
3440
* Whether or not the wallet support changing account
3541
* Basically only wallet initialized using a private key should not be able to do so

tests/ledger.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,24 @@ describe('Ledger', () => {
1313
await expect(clt.disconnect()).resolves.toBeTruthy();
1414
});
1515

16-
it('Should be able to connect to ledger hardware wallet', async () => {
17-
// WIP - manual testing using ledger device
16+
it('Manual signature must work', async () => {
17+
// Manual testing using ledger device
18+
// Ledger device must be unlocked and Cosmos app opened prior to running those tests
1819
// const transport = await TransportNodeHid.create();
1920
// const w1 = await LumWalletFactory.fromLedgerTransport(transport, `m/44'/118'/0'/0/0`, 'lum');
21+
// expect(w1).toBeTruthy();
2022
// const acc = await clt.getAccount(w1.getAddress());
2123
// expect(acc).toBeTruthy();
2224
// const balance = await clt.getBalance(acc.address, 'lum');
2325
// expect(parseInt(balance.amount)).toBeGreaterThan(0);
24-
// TODO: requires to fixe the ledger signing implementation
26+
2527
// const chainId = await clt.getChainId();
26-
// const sendMsg = LumMessages.BuildMsgSend(w1.getAddress(), 'lum1ty3meqzqxq7vkdyp7l7znzvn0t50w92uf6h4px', [{ denom: 'lum', amount: '3' }]);
28+
// const sendMsg = LumMessages.BuildMsgSend(w1.getAddress(), 'lum1lsagfzrm4gz28he4wunt63sts5xzmczwjttsr9', [{ denom: 'lum', amount: '3' }]);
2729
// const fee = {
2830
// amount: [{ denom: LumConstants.LumDenom, amount: '1' }],
2931
// gas: '100000',
3032
// };
33+
3134
// const doc = {
3235
// accountNumber: acc.accountNumber,
3336
// chainId,
@@ -36,6 +39,7 @@ describe('Ledger', () => {
3639
// messages: [sendMsg],
3740
// sequence: acc.sequence,
3841
// };
42+
3943
// const res = await clt.signAndBroadcastTx(w1, doc);
4044
// expect(LumUtils.broadcastTxCommitSuccess(res)).toBeTruthy();
4145
});

0 commit comments

Comments
 (0)