Skip to content

Commit

Permalink
Switch to offline signer implementation instead
Browse files Browse the repository at this point in the history
  • Loading branch information
dssei committed Aug 20, 2024
1 parent 0285eb8 commit 21a9e2a
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 137 deletions.
35 changes: 12 additions & 23 deletions packages/ledger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,34 @@ yarn add @sei-js/ledger
## Usage
```typescript
import {
coin,
StargateClient,
coins,
SigningStargateClient,
StdFee
} from "@cosmjs/stargate";
import {
AminoMsg,
} from "@cosmjs/amino";

import {
createTransportAndApp,
getAddresses,
createSignDoc,
signAndBroadcast
LedgerOfflineAminoSigner
} from "@sei-js/ledger";

const testApp = async () => {
const validatorAddress = "seivaloper1sq7x0r2mf3gvwr2l9amtlye0yd3c6dqa4th95v";
const rpcUrl = "https://rpc-testnet.sei-apis.com/";
const client = await StargateClient.connect(rpcUrl);
const chainId = "atlantic-2";
const memo = "Delegation";
const path = "m/44'/60'/0'/0/0";

const {app} = await createTransportAndApp();
const {nativeAddress} = await getAddresses(app, path);
const account = await client.getAccount(nativeAddress.address);
if (!account) {
throw new Error("Account not found");
}
const ledgerSigner = new LedgerOfflineAminoSigner(app, path)
const signingStargateClient = await SigningStargateClient.connectWithSigner(rpcUrl, ledgerSigner)

const aminoMsg: AminoMsg = {
type: "cosmos-sdk/MsgDelegate",
const msgDelegate = {
typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
value: {
delegator_address: nativeAddress.address,
validator_address: validatorAddress,
amount: coin("1000", "usei"),
delegatorAddress: nativeAddress.address,
validatorAddress: validatorAddress,
amount: coins(500, "usei"),
},
};

Expand All @@ -52,12 +44,9 @@ const testApp = async () => {
gas: "200000",
};

const signDoc = createSignDoc(aminoMsg, fee, chainId, memo, account);
const broadcastResponse = await signAndBroadcast(app, path, signDoc, client, nativeAddress);

console.log("Broadcast result:", broadcastResponse);
const result = await signingStargateClient.signAndBroadcast(nativeAddress.address, [msgDelegate], fee, memo)
console.log("Broadcast result:", result);
};

testApp();

```
166 changes: 52 additions & 114 deletions packages/ledger/src/wallet/utils.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,59 @@
import Transport from '@ledgerhq/hw-transport-node-hid';
import {
Account,
AminoTypes,
createDefaultAminoConverters,
defaultRegistryTypes,
StargateClient,
StdFee
} from "@cosmjs/stargate";
import Transport from "@ledgerhq/hw-transport-node-hid";
import {
AminoMsg,
AminoSignResponse,
encodeSecp256k1Pubkey,
encodeSecp256k1Signature,
makeSignDoc,
OfflineAminoSigner,
serializeSignDoc,
StdSignDoc,
} from "@cosmjs/amino";
import {fromBase64, fromHex} from "@cosmjs/encoding";
import {TxRaw} from "cosmjs-types/cosmos/tx/v1beta1/tx";
import {encodePubkey, makeAuthInfoBytes, Registry, TxBodyEncodeObject} from "@cosmjs/proto-signing";
import {Secp256k1Signature} from "@cosmjs/crypto";
import {Int53} from "@cosmjs/math";
import {SignMode} from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
import {SeiApp} from "@zondax/ledger-sei";
StdSignDoc
} from '@cosmjs/amino';
import {fromHex } from '@cosmjs/encoding';
import { AccountData } from '@cosmjs/proto-signing';
import { Secp256k1Signature } from '@cosmjs/crypto';
import { SeiApp } from '@zondax/ledger-sei';

/**
* A signer implementation that uses a Ledger device to sign transactions
*/
export class LedgerOfflineAminoSigner implements OfflineAminoSigner {
private readonly path: string;
private readonly app: SeiApp;

/**
* Creates a new LedgerOfflineAminoSigner
* @param app Ledger Sei app instance
* @param path hd derivation path (e.g. "m/44'/60'/0'/0/0")
*/
constructor(app: SeiApp, path: string) {
this.path = path;
this.app = app;
}

public async getAccounts(): Promise<readonly AccountData[]> {
const nativeAddress = await this.app.getCosmosAddress(this.path);
return [
{
address: nativeAddress.address,
algo: "secp256k1",
pubkey: fromHex(nativeAddress.pubKey)
},
];
}

public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
const signature = await this.app.signCosmos(this.path, Buffer.from(serializeSignDoc(signDoc)));
const sig = new Secp256k1Signature(signature.r, signature.s).toFixedLength();
const nativeAddress = await this.app.getCosmosAddress(this.path);
return {
signed: signDoc,
signature: encodeSecp256k1Signature(fromHex(nativeAddress.pubKey), sig),
};
}
}

/**
* Creates a transport and app instance
*
* @returns {Promise<{transport: Transport, app: SeiApp}>} transport and app instances
*/
export const createTransportAndApp = async () => {
const transport = await Transport.create();
Expand All @@ -37,103 +65,13 @@ export const createTransportAndApp = async () => {
* Get the EVM and Cosmos addresses from the Ledger device
* @param app Ledger Sei app instance
* @param path hd derivation path (e.g. "m/44'/60'/0'/0/0")
*
* @returns {Promise<{evmAddress: string, nativeAddress: string}>} EVM and Cosmos address objects containing
* address and public key
*/
export const getAddresses = async (app: SeiApp, path: string) => {
const evmAddress = await app.getEVMAddress(path);
const nativeAddress = await app.getCosmosAddress(path);
return {evmAddress, nativeAddress};
};

/**
* Create a sign doc for a Cosmos transaction.
*
* Example usage:
*
* const aminoMsg: AminoMsg = {
* type: "cosmos-sdk/MsgDelegate",
* value: {
* delegator_address: nativeAddress.address,
* validator_address: validatorAddress,
* amount: coin("1000", "usei"),
* },
* };
*
* const fee: StdFee = {
* amount: [{denom: "usei", amount: "20000"}],
* gas: "200000",
* };
*
* const chainId = "atlantic-2";
* const memo = "Delegation";
* const account = await client.getAccount(nativeAddress.address);
*
* const signDoc = createSignDoc(aminoMsg, fee, chainId, memo, account);
*
* @param aminoMsg amino message
* @param fee transaction fee
* @param chainId chain id
* @param memo transaction memo
* @param account account
*/
export const createSignDoc = (aminoMsg: AminoMsg, fee: StdFee, chainId: string, memo: string, account: Account) => {
return makeSignDoc(
[aminoMsg],
fee,
chainId,
memo,
account.accountNumber.toString(),
account.sequence.toString(),
);
};

/**
* Sign and broadcast a Cosmos transaction
*
* @param app Ledger Sei app instance
* @param path hd derivation path (e.g. "m/44'/60'/0'/0/0")
* @param signDoc sign doc
* @param client Stargate client
* @param nativeAddress native Sei address
*/
export const signAndBroadcast = async (app: SeiApp, path: string, signDoc: StdSignDoc, client: StargateClient, nativeAddress: {
address: string,
pubKey: string
}) => {
const signature = await app.signCosmos(path, Buffer.from(serializeSignDoc(signDoc)));
const sig = new Secp256k1Signature(signature.r, signature.s).toFixedLength();
const aminoSignResponse: AminoSignResponse = {
signed: signDoc,
signature: encodeSecp256k1Signature(fromHex(nativeAddress.pubKey), sig),
};

const registry = new Registry(defaultRegistryTypes);
const aminoTypes = new AminoTypes(createDefaultAminoConverters());
const signedTxBody = {
messages: aminoSignResponse.signed.msgs.map((msg) => aminoTypes.fromAmino(msg)),
memo: aminoSignResponse.signed.memo,
};
const signedTxBodyEncodeObject: TxBodyEncodeObject = {
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: signedTxBody,
};
const signedTxBodyBytes = registry.encode(signedTxBodyEncodeObject);
const signedGasLimit = Int53.fromString(aminoSignResponse.signed.fee.gas).toNumber();
const signedSequence = Int53.fromString(aminoSignResponse.signed.sequence).toNumber();
const pubkey = encodePubkey(encodeSecp256k1Pubkey(fromHex(nativeAddress.pubKey)));
const signedAuthInfoBytes = makeAuthInfoBytes(
[{pubkey, sequence: signedSequence}],
aminoSignResponse.signed.fee.amount,
signedGasLimit,
aminoSignResponse.signed.fee.granter,
aminoSignResponse.signed.fee.payer,
SignMode.SIGN_MODE_LEGACY_AMINO_JSON,
);
const txRaw = TxRaw.fromPartial({
bodyBytes: signedTxBodyBytes,
authInfoBytes: signedAuthInfoBytes,
signatures: [fromBase64(aminoSignResponse.signature.signature)],
});

return await client.broadcastTx(TxRaw.encode(txRaw).finish());
};

0 comments on commit 21a9e2a

Please sign in to comment.