Skip to content

Commit 5ecc986

Browse files
authored
Add wallet support for OfflineSigner (#30)
* Add new offline signer wallet capability * Bump version to 0.5.3
1 parent 4a8c3ae commit 5ecc986

File tree

11 files changed

+234
-15
lines changed

11 files changed

+234
-15
lines changed

docs/lib/classes/lumwalletfactory.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [fromKeyStore](LumWalletFactory.md#fromkeystore)
1212
- [fromLedgerTransport](LumWalletFactory.md#fromledgertransport)
1313
- [fromMnemonic](LumWalletFactory.md#frommnemonic)
14+
- [fromOfflineSigner](LumWalletFactory.md#fromofflinesigner)
1415
- [fromPrivateKey](LumWalletFactory.md#fromprivatekey)
1516

1617
## Constructors
@@ -81,6 +82,24 @@ Create a LumWallet instance based on a mnemonic and a derivation path
8182

8283
___
8384

85+
### fromOfflineSigner
86+
87+
`Static` **fromOfflineSigner**(`offlineSigner`): `Promise`<[`LumWallet`](LumWallet.md)\>
88+
89+
Create a LumWallet instance based on an OfflineDirectSigner instance compatible with Comsjs based implementations.
90+
91+
#### Parameters
92+
93+
| Name | Type | Description |
94+
| :------ | :------ | :------ |
95+
| `offlineSigner` | `OfflineDirectSigner` | OfflineDirectSigner instance compatible with Comsjs based implementations |
96+
97+
#### Returns
98+
99+
`Promise`<[`LumWallet`](LumWallet.md)\>
100+
101+
___
102+
84103
### fromPrivateKey
85104

86105
`Static` **fromPrivateKey**(`privateKey`, `addressPrefix?`): `Promise`<[`LumWallet`](LumWallet.md)\>

docs/lib/enums/lumconstants.lummessagesigner.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Signing wallets
99
### Enumeration members
1010

1111
- [LEDGER](LumConstants.LumMessageSigner.md#ledger)
12+
- [OFFLINE](LumConstants.LumMessageSigner.md#offline)
1213
- [PAPER](LumConstants.LumMessageSigner.md#paper)
1314

1415
## Enumeration members
@@ -19,6 +20,12 @@ Signing wallets
1920

2021
___
2122

23+
### OFFLINE
24+
25+
**OFFLINE** = `"lum-sdk/offline"`
26+
27+
___
28+
2229
### PAPER
2330

2431
**PAPER** = `"lum-sdk/paper"`

docs/lib/modules/lumconstants.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- [LumBech32PrefixValPub](LumConstants.md#lumbech32prefixvalpub)
1818
- [LumDenom](LumConstants.md#lumdenom)
1919
- [LumExponent](LumConstants.md#lumexponent)
20+
- [LumSignOnlyChainId](LumConstants.md#lumsignonlychainid)
2021
- [LumWalletSigningVersion](LumConstants.md#lumwalletsigningversion)
2122
- [MicroLumDenom](LumConstants.md#microlumdenom)
2223
- [PrivateKeyLength](LumConstants.md#privatekeylength)
@@ -104,6 +105,14 @@ Lum Exponent
104105

105106
___
106107

108+
### LumSignOnlyChainId
109+
110+
**LumSignOnlyChainId**: ``"lum-signature-only"``
111+
112+
Chain ID used for message signature by wallet implementations that require one
113+
114+
___
115+
107116
### LumWalletSigningVersion
108117

109118
**LumWalletSigningVersion**: ``"1"``

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lum-network/sdk-javascript",
3-
"version": "0.5.2",
3+
"version": "0.5.3",
44
"license": "Apache-2.0",
55
"description": "Javascript SDK library for NodeJS and Web browsers to interact with the Lum Network.",
66
"homepage": "https://github.com/lum-network/sdk-javascript#readme",

src/constants/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,10 @@ export const LumWalletSigningVersion = '1';
7777
export enum LumMessageSigner {
7878
PAPER = 'lum-sdk/paper',
7979
LEDGER = 'lum-sdk/ledger',
80+
OFFLINE = 'lum-sdk/offline',
8081
}
82+
83+
/**
84+
* Chain ID used for message signature by wallet implementations that require one
85+
*/
86+
export const LumSignOnlyChainId = 'lum-signature-only';

src/utils/transactions.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { makeSignBytes } from '@cosmjs/proto-signing';
66
import { TxRaw, AuthInfo } from '../codec/cosmos/tx/v1beta1/tx';
77
import { SignMode } from '../codec/cosmos/tx/signing/v1beta1/signing';
88

9-
import { LumMessageSigner } from '../constants';
9+
import { LumMessageSigner, LumSignOnlyChainId } from '../constants';
1010
import { Fee, Doc, SignDoc, SignMsg, DocSigner } from '../types';
1111
import { LumRegistry } from '../registry';
1212
import { sortJSON } from './commons';
@@ -125,13 +125,22 @@ export const verifySignMsg = async (msg: SignMsg): Promise<boolean> => {
125125
}
126126
if (msg.signer === LumMessageSigner.PAPER) {
127127
return verifySignature(msg.sig, toAscii(msg.msg), msg.publicKey);
128+
} else if (msg.signer === LumMessageSigner.OFFLINE) {
129+
const signDoc = {
130+
bodyBytes: toAscii(msg.msg),
131+
authInfoBytes: generateAuthInfoBytes([{ accountNumber: 0, sequence: 0, publicKey: msg.publicKey }], { amount: [], gas: '0' }, SignMode.SIGN_MODE_DIRECT),
132+
chainId: LumSignOnlyChainId,
133+
accountNumber: Long.fromNumber(0),
134+
};
135+
const signedBytes = generateSignDocBytes(signDoc);
136+
return verifySignature(msg.sig, signedBytes, msg.publicKey);
128137
} else if (msg.signer === LumMessageSigner.LEDGER) {
129138
// Re-generate ledger required amino payload to sign messages
130139
// This is basically an empty transaction payload
131140
// Same a used in the LumLedgerWallet > signMessage method
132141
const msgToSign = {
133142
'account_number': '0',
134-
'chain_id': 'lum-signature-only',
143+
'chain_id': LumSignOnlyChainId,
135144
'fee': {},
136145
'memo': msg.msg,
137146
'msgs': [],

src/wallet/LumLedgerWallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class LumLedgerWallet extends LumWallet {
9090
// that is only provided for basic message signature and verification
9191
const msgToSign = {
9292
'account_number': '0',
93-
'chain_id': 'lum-signature-only',
93+
'chain_id': LumConstants.LumSignOnlyChainId,
9494
'fee': {},
9595
'memo': msg,
9696
'msgs': [],
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { OfflineDirectSigner } from '@cosmjs/proto-signing';
2+
import { SignMode } from '../codec/cosmos/tx/signing/v1beta1/signing';
3+
import { LumUtils, LumTypes, LumConstants } from '..';
4+
import { LumWallet } from '.';
5+
import Long from 'long';
6+
7+
export class LumOfflineSignerWallet extends LumWallet {
8+
private readonly offlineSigner: OfflineDirectSigner;
9+
10+
/**
11+
* Create a LumOfflineSignerWallet instance based on an OfflineDirectSigner instance compatible with Comsjs based
12+
* implementations.
13+
* This constructor is not intended to be used directly as it does not initialize the underlying key pair
14+
* Better use the provided static LumPaperWallet builders
15+
*
16+
* @param mnemonicOrPrivateKey mnemonic (string) used to derive the private key or private key (Uint8Array)
17+
*/
18+
constructor(offlineSigner: OfflineDirectSigner) {
19+
super();
20+
this.offlineSigner = offlineSigner;
21+
}
22+
23+
signingMode = (): SignMode => {
24+
return SignMode.SIGN_MODE_DIRECT;
25+
};
26+
27+
canChangeAccount = (): boolean => {
28+
return false;
29+
};
30+
31+
useAccount = async (): Promise<boolean> => {
32+
const accounts = await this.offlineSigner.getAccounts();
33+
if (accounts.length === 0) {
34+
throw new Error('No account available.');
35+
}
36+
this.publicKey = accounts[0].pubkey;
37+
this.address = accounts[0].address;
38+
return true;
39+
};
40+
41+
sign = async (): Promise<Uint8Array> => {
42+
throw new Error('Feature not supported.');
43+
};
44+
45+
signTransaction = async (doc: LumTypes.Doc): Promise<Uint8Array> => {
46+
if (!this.address || !this.publicKey) {
47+
throw new Error('No account selected.');
48+
}
49+
const signerIndex = LumUtils.uint8IndexOf(
50+
doc.signers.map((signer) => signer.publicKey),
51+
this.publicKey as Uint8Array,
52+
);
53+
if (signerIndex === -1) {
54+
throw new Error('Signer not found in document');
55+
}
56+
const signDoc = LumUtils.generateSignDoc(doc, signerIndex, this.signingMode());
57+
const response = await this.offlineSigner.signDirect(this.address, signDoc);
58+
return LumUtils.fromBase64(response.signature.signature);
59+
};
60+
61+
signMessage = async (msg: string): Promise<LumTypes.SignMsg> => {
62+
if (!this.address || !this.publicKey) {
63+
throw new Error('No account selected.');
64+
}
65+
const signDoc = {
66+
bodyBytes: LumUtils.toAscii(msg),
67+
authInfoBytes: LumUtils.generateAuthInfoBytes([{ accountNumber: 0, sequence: 0, publicKey: this.getPublicKey() }], { amount: [], gas: '0' }, this.signingMode()),
68+
chainId: LumConstants.LumSignOnlyChainId,
69+
accountNumber: Long.fromNumber(0),
70+
};
71+
const response = await this.offlineSigner.signDirect(this.address, signDoc);
72+
return {
73+
address: this.getAddress(),
74+
publicKey: this.getPublicKey(),
75+
msg: msg,
76+
sig: LumUtils.fromBase64(response.signature.signature),
77+
version: LumConstants.LumWalletSigningVersion,
78+
signer: LumConstants.LumMessageSigner.OFFLINE,
79+
};
80+
};
81+
}

src/wallet/LumWalletFactory.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import Transport from '@ledgerhq/hw-transport';
2+
import { OfflineDirectSigner } from '@cosmjs/proto-signing';
23

34
import { LumWallet } from './LumWallet';
45
import { LumLedgerWallet } from './LumLedgerWallet';
56
import { LumPaperWallet } from './LumPaperWallet';
7+
import { LumOfflineSignerWallet } from './LumOfflineSignerWallet';
68
import { LumConstants, LumUtils } from '../';
79

810
export class LumWalletFactory {
@@ -45,6 +47,17 @@ export class LumWalletFactory {
4547
return wallet;
4648
};
4749

50+
/**
51+
* Create a LumWallet instance based on an OfflineDirectSigner instance compatible with Comsjs based implementations.
52+
*
53+
* @param offlineSigner OfflineDirectSigner instance compatible with Comsjs based implementations
54+
*/
55+
static fromOfflineSigner = async (offlineSigner: OfflineDirectSigner): Promise<LumWallet> => {
56+
const wallet = new LumOfflineSignerWallet(offlineSigner);
57+
await wallet.useAccount();
58+
return wallet;
59+
};
60+
4861
/**
4962
* Create a LumWallet instance based on a ledger transport
5063
*

src/wallet/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { LumWallet } from './LumWallet';
22
import { LumPaperWallet } from './LumPaperWallet';
33
import { LumLedgerWallet } from './LumLedgerWallet';
4+
import { LumOfflineSignerWallet } from './LumOfflineSignerWallet';
45
import { LumWalletFactory } from './LumWalletFactory';
56

6-
export { LumWallet, LumPaperWallet, LumLedgerWallet, LumWalletFactory };
7+
export { LumWallet, LumPaperWallet, LumLedgerWallet, LumOfflineSignerWallet, LumWalletFactory };

0 commit comments

Comments
 (0)