Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General offline signer #41

Merged
merged 4 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions libs/interchainjs/src/signing-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { toEncoder } from '@interchainjs/cosmos/utils';
import { TxBody, TxRaw } from '@interchainjs/cosmos-types/cosmos/tx/v1beta1/tx';
import { TxRpc } from '@interchainjs/cosmos-types/types';
import { BroadcastOptions, HttpEndpoint, StdFee } from '@interchainjs/types';
import { BroadcastOptions, HttpEndpoint, SIGN_MODE, StdFee } from '@interchainjs/types';
import { fromBase64 } from '@interchainjs/utils';

import {
Expand Down Expand Up @@ -165,7 +165,7 @@ export class SigningClient {

getSinger(signerAddress: string) {
const signer = this.options.preferredSignType ?
this.options.preferredSignType === 'amino' ? this.aminoSigners[signerAddress] : this.directSigners[signerAddress]
this.options.preferredSignType === SIGN_MODE.SIGN_MODE_LEGACY_AMINO_JSON ? this.aminoSigners[signerAddress] : this.directSigners[signerAddress]
: this.aminoSigners[signerAddress] || this.directSigners[signerAddress];

if (!signer) {
Expand Down
2 changes: 1 addition & 1 deletion libs/interchainjs/src/types/signing-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface SignerOptions {
gasPrice?: Price | string;
prefix?: string;
broadcast?: BroadcastOptions;
preferredSignType?: 'amino' | 'direct';
preferredSignType?: string;
}

export interface SignerData {
Expand Down
5 changes: 3 additions & 2 deletions libs/interchainjs/starship/__tests__/staking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import BigNumber from 'bignumber.js';
import { RpcQuery } from 'interchainjs/query/rpc';
import { CosmosSigningClient } from 'interchainjs/cosmos';
import { useChain } from 'starshipjs';
import { SIGN_MODE } from '@interchainjs/types';

const cosmosHdPath = "m/44'/118'/0'/0/0";

Expand Down Expand Up @@ -135,7 +136,7 @@ describe('Staking tokens testing', () => {
await getRpcEndpoint(),
wallet,
{
preferredSignType: 'direct',
preferredSignType: SIGN_MODE.SIGN_MODE_DIRECT,
broadcast: {
checkTx: true,
deliverTx: true,
Expand Down Expand Up @@ -183,7 +184,7 @@ describe('Staking tokens testing', () => {
await getRpcEndpoint(),
wallet,
{
preferredSignType: 'amino',
preferredSignType: SIGN_MODE.SIGN_MODE_LEGACY_AMINO_JSON,
broadcast: {
checkTx: true,
deliverTx: true,
Expand Down
23 changes: 18 additions & 5 deletions networks/cosmos/src/signers/amino.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
CosmosAminoDoc,
CosmosAminoSigner,
Encoder,
ICosmosGeneralOfflineSigner,
SignerOptions,
} from '../types';
import { AminoDocAuth } from '../types/docAuth';
import { OfflineAminoSigner } from '../types/wallet';
import { isOfflineAminoSigner, OfflineAminoSigner } from '../types/wallet';

/**
* AminoDocSigner is a signer for Amino document.
Expand Down Expand Up @@ -125,13 +126,19 @@ export class AminoSigner
* if there're multiple accounts in the wallet, it will return the first one by default.
*/
static async fromWallet(
signer: OfflineAminoSigner,
signer: OfflineAminoSigner | ICosmosGeneralOfflineSigner,
encoders: Encoder[],
converters: AminoConverter[],
endpoint?: string | HttpEndpoint,
options?: SignerOptions
) {
const [auth] = await AminoDocAuth.fromOfflineSigner(signer);
let auth: AminoDocAuth;

if(isOfflineAminoSigner(signer)){
[auth] = await AminoDocAuth.fromOfflineSigner(signer);
} else {
[auth] = await AminoDocAuth.fromGeneralOfflineSigner(signer);
}

return new AminoSigner(auth, encoders, converters, endpoint, options);
}
Expand All @@ -141,13 +148,19 @@ export class AminoSigner
* if there're multiple accounts in the wallet, it will return all of the signers.
*/
static async fromWalletToSigners(
signer: OfflineAminoSigner,
signer: OfflineAminoSigner | ICosmosGeneralOfflineSigner,
encoders: Encoder[],
converters: AminoConverter[],
endpoint?: string | HttpEndpoint,
options?: SignerOptions
) {
const auths = await AminoDocAuth.fromOfflineSigner(signer);
let auths: AminoDocAuth[];

if(isOfflineAminoSigner(signer)) {
auths = await AminoDocAuth.fromOfflineSigner(signer);
} else {
auths = await AminoDocAuth.fromGeneralOfflineSigner(signer);
}

return auths.map((auth) => {
return new AminoSigner(auth, encoders, converters, endpoint, options);
Expand Down
24 changes: 19 additions & 5 deletions networks/cosmos/src/signers/direct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
CosmosDirectDoc,
CosmosDirectSigner,
Encoder,
ICosmosGeneralOfflineSigner,
SignerOptions,
} from '../types';
import { DirectDocAuth } from '../types/docAuth';
import { OfflineDirectSigner } from '../types/wallet';
import { isOfflineDirectSigner, OfflineDirectSigner } from '../types/wallet';

/**
* DirectDocSigner is a signer for Direct document.
Expand Down Expand Up @@ -72,12 +73,19 @@ export class DirectSigner
* If there're multiple accounts in the wallet, it will return the first one by default.
*/
static async fromWallet(
signer: OfflineDirectSigner,
signer: OfflineDirectSigner | ICosmosGeneralOfflineSigner,
encoders: Encoder[],
endpoint?: string | HttpEndpoint,
options?: SignerOptions
) {
const [auth] = await DirectDocAuth.fromOfflineSigner(signer);
let auth: DirectDocAuth;

if(isOfflineDirectSigner(signer)){
[auth] = await DirectDocAuth.fromOfflineSigner(signer);
} else {
[auth] = await DirectDocAuth.fromGeneralOfflineSigner(signer);
}

return new DirectSigner(auth, encoders, endpoint, options);
}

Expand All @@ -86,12 +94,18 @@ export class DirectSigner
* If there're multiple accounts in the wallet, it will return all of the signers.
*/
static async fromWalletToSigners(
signer: OfflineDirectSigner,
signer: OfflineDirectSigner | ICosmosGeneralOfflineSigner,
encoders: Encoder[],
endpoint?: string | HttpEndpoint,
options?: SignerOptions
) {
const auths = await DirectDocAuth.fromOfflineSigner(signer);
let auths: DirectDocAuth[];

if(isOfflineDirectSigner(signer)) {
auths = await DirectDocAuth.fromOfflineSigner(signer);
} else {
auths = await DirectDocAuth.fromGeneralOfflineSigner(signer);
}

return auths.map((auth) => {
return new DirectSigner(auth, encoders, endpoint, options);
Expand Down
48 changes: 47 additions & 1 deletion networks/cosmos/src/types/docAuth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {
DocAuth,
IKey,
SIGN_MODE,
SignDoc,
SignDocResponse,
StdSignDoc,
} from '@interchainjs/types';
import { Key } from '@interchainjs/utils';

import { OfflineAminoSigner, OfflineDirectSigner } from './wallet';
import { AminoSignResponse, DirectSignResponse, OfflineAminoSigner, OfflineDirectSigner } from './wallet';
import { CosmosAminoDoc, CosmosDirectDoc, ICosmosGeneralOfflineSigner } from './signer';

/**
* Base class for Doc Auth.
Expand Down Expand Up @@ -55,6 +57,28 @@ export class AminoDocAuth extends BaseDocAuth<OfflineAminoSigner, StdSignDoc> {
);
});
}

static async fromGeneralOfflineSigner(offlineSigner: ICosmosGeneralOfflineSigner) {
if(offlineSigner.signMode !== SIGN_MODE.SIGN_MODE_LEGACY_AMINO_JSON) {
throw new Error('not an amino general offline signer');
}

const accounts = await offlineSigner.getAccounts();

return accounts.map((account) => {
return new AminoDocAuth(
account.algo,
account.address,
account.pubkey,
{
getAccounts: offlineSigner.getAccounts,
signAmino(signerAddress: string, signDoc: CosmosAminoDoc) {
return offlineSigner.sign({ signerAddress, signDoc }) as Promise<AminoSignResponse>;
}
}
);
});
}
}

/**
Expand Down Expand Up @@ -82,4 +106,26 @@ export class DirectDocAuth extends BaseDocAuth<OfflineDirectSigner, SignDoc> {
);
});
}

static async fromGeneralOfflineSigner(offlineSigner: ICosmosGeneralOfflineSigner) {
if(offlineSigner.signMode !== SIGN_MODE.SIGN_MODE_DIRECT) {
throw new Error('not a direct general offline signer');
}

const accounts = await offlineSigner.getAccounts();

return accounts.map((account) => {
return new DirectDocAuth(
account.algo,
account.address,
account.pubkey,
{
getAccounts: offlineSigner.getAccounts,
signDirect(signerAddress: string, signDoc: CosmosDirectDoc) {
return offlineSigner.sign({ signerAddress, signDoc }) as Promise<DirectSignResponse>;
}
}
);
});
}
}
12 changes: 10 additions & 2 deletions networks/cosmos/src/types/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CreateDocResponse,
HttpEndpoint,
IAccount,
IGeneralOfflineSigner,
IKey,
Price,
SignerConfig,
Expand All @@ -26,6 +27,7 @@ import { AccountBase } from '@interchainjs/types/account';
import { Key } from '@interchainjs/utils';
import { ripemd160 } from '@noble/hashes/ripemd160';
import { sha256 } from '@noble/hashes/sha256';
import { AminoSignResponse, DirectSignResponse } from './wallet';

/**
* Signer options for cosmos chains
Expand Down Expand Up @@ -292,7 +294,7 @@ export function isICosmosAccount(
* Cosmos account implementation
*/
export class CosmosAccount extends AccountBase {
getAddress() {
getAddressByPubKey() {
return Key.from(ripemd160(sha256(this.publicKey.value))).toBech32(
this.prefix
);
Expand All @@ -303,5 +305,11 @@ export class CosmosAccount extends AccountBase {
* cosmos wallet interface
*/
export interface ICosmosWallet {
getAccounts: () => Promise<AccountData[]>;
getAccounts: () => Promise<readonly AccountData[]>;
}

/**
* general offline signer for cosmos chains
*/
export interface ICosmosGeneralOfflineSigner extends IGeneralOfflineSigner<string, CosmosDirectDoc | CosmosAminoDoc, DirectSignResponse | AminoSignResponse> {
}
46 changes: 41 additions & 5 deletions networks/cosmos/src/types/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AccountData, SignerConfig } from '@interchainjs/types';
import { AccountData, IGeneralOfflineSignArgs, IGeneralOfflineSigner, SIGN_MODE, SignerConfig } from '@interchainjs/types';

import { CosmosAminoDoc, CosmosDirectDoc } from './signer';
import { CosmosAminoDoc, CosmosDirectDoc, ICosmosGeneralOfflineSigner } from './signer';

/**
* Wallet options
Expand Down Expand Up @@ -35,7 +35,7 @@ export interface AminoSignResponse {
* Offline amino signer
*/
export interface OfflineAminoSigner {
getAccounts: () => Promise<AccountData[]>;
getAccounts: () => Promise<readonly AccountData[]>;
signAmino: (
signerAddress: string,
signDoc: CosmosAminoDoc
Expand All @@ -54,7 +54,7 @@ export interface DirectSignResponse {
* Offline direct signer
*/
export interface OfflineDirectSigner {
getAccounts: () => Promise<AccountData[]>;
getAccounts: () => Promise<readonly AccountData[]>;
signDirect: (
signerAddress: string,
signDoc: CosmosDirectDoc
Expand Down Expand Up @@ -82,4 +82,40 @@ export function isOfflineDirectSigner(
/**
* Offline signer can be either amino or direct signer or both
*/
export type OfflineSigner = OfflineAminoSigner | OfflineDirectSigner;
export type OfflineSigner = OfflineAminoSigner | OfflineDirectSigner | ICosmosGeneralOfflineSigner;

/**
* Amino general offline signer.
* This is a wrapper for offline amino signer.
*/
export class AminoGeneralOfflineSigner implements IGeneralOfflineSigner<string, CosmosAminoDoc, AminoSignResponse> {
constructor(public offlineSigner: OfflineAminoSigner) {

}

readonly signMode: string = SIGN_MODE.SIGN_MODE_LEGACY_AMINO_JSON;
getAccounts(): Promise<readonly AccountData[]> {
return this.offlineSigner.getAccounts();
}
sign({ signerAddress, signDoc }: IGeneralOfflineSignArgs<string, CosmosAminoDoc>) {
return this.offlineSigner.signAmino(signerAddress, signDoc);
}
}

/**
* Direct general offline signer.
* This is a wrapper for offline direct signer.
*/
export class DirectGeneralOfflineSigner implements IGeneralOfflineSigner<string, CosmosDirectDoc, DirectSignResponse> {
constructor(public offlineSigner: OfflineDirectSigner) {

}

readonly signMode: string = SIGN_MODE.SIGN_MODE_DIRECT;
getAccounts(): Promise<readonly AccountData[]> {
return this.offlineSigner.getAccounts();
}
sign({ signerAddress, signDoc }: IGeneralOfflineSignArgs<string, CosmosDirectDoc>) {
return this.offlineSigner.signDirect(signerAddress, signDoc);
}
}
19 changes: 17 additions & 2 deletions networks/cosmos/src/wallets/secp256k1hd.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Secp256k1Auth } from '@interchainjs/auth/secp256k1';
import { AccountData, AddrDerivation, Auth, SignerConfig } from '@interchainjs/types';
import { AccountData, AddrDerivation, Auth, SignerConfig, SIGN_MODE, IGeneralOfflineSignArgs } from '@interchainjs/types';

import { AminoDocSigner } from '../signers/amino';
import { defaultSignerConfig } from '../defaults';
Expand All @@ -9,6 +9,7 @@ import {
CosmosAminoDoc,
CosmosDirectDoc,
ICosmosAccount,
ICosmosGeneralOfflineSigner,
ICosmosWallet,
} from '../types';
import {
Expand Down Expand Up @@ -62,7 +63,7 @@ implements ICosmosWallet, OfflineAminoSigner, OfflineDirectSigner
* Get account data
* @returns account data
*/
async getAccounts(): Promise<AccountData[]> {
async getAccounts(): Promise<readonly AccountData[]> {
return this.accounts.map((acct) => {
return acct.toAccountData();
});
Expand Down Expand Up @@ -156,4 +157,18 @@ implements ICosmosWallet, OfflineAminoSigner, OfflineDirectSigner
this.signAmino(signerAddress, signDoc),
};
}

/**
* Convert this to general offline signer for hiding the private key.
* @param signMode sign mode. (direct or amino)
* @returns general offline signer for direct or amino
*/
toGeneralOfflineSigner(signMode: string): ICosmosGeneralOfflineSigner {
return {
signMode: signMode,
getAccounts: async () => this.getAccounts(),
sign: async ({ signerAddress, signDoc }: IGeneralOfflineSignArgs<string, CosmosDirectDoc | CosmosAminoDoc>) =>
signMode === SIGN_MODE.SIGN_MODE_DIRECT ? this.signDirect(signerAddress, signDoc as CosmosDirectDoc) : this.signAmino(signerAddress, signDoc as CosmosAminoDoc),
}
}
}
Loading
Loading