From 0f3e96194b89d62a8317c1565ed9ac8ba397fc6a Mon Sep 17 00:00:00 2001
From: Zetazzz
Date: Wed, 14 Aug 2024 12:27:28 +0800
Subject: [PATCH 1/3] fixed README and add charts to signer.md
---
docs/signer.md | 144 ++++++++++++++++++++++--
docs/wallet.md | 2 +-
libs/interchainjs/README.md | 37 ++++--
networks/cosmos/src/base/base-signer.ts | 1 +
packages/auth/README.md | 21 ++--
5 files changed, 175 insertions(+), 30 deletions(-)
diff --git a/docs/signer.md b/docs/signer.md
index 5f6f620e..3e0d57c6 100644
--- a/docs/signer.md
+++ b/docs/signer.md
@@ -2,15 +2,135 @@
The main purpose of the `@interchainjs/cosmos`, `@interchainjs/ethereum`, `@interchainjs/ethermint` is to offer developers a way to have different `Signer` implementations on different types of Blockchains. All of these `Signer`s are implementing [`UniSigner` interface](#unisigner-interface) and extending the same `BaseSigner` class which with `Auth` object being utilized in construction.
+Class diagram:
+
+```mermaid
+classDiagram
+ class UniSigner {
+ <>
+ IKey publicKey
+ AddressResponse getAddress()
+ IKey | Promise~IKey~ signArbitrary(Uint8Array data)
+ SignDocResponse~Doc~ | Promise~SignDocResponse~Doc~~ signDoc(Doc doc)
+ Promise~BroadcastResponse~ broadcastArbitrary(Uint8Array data, BroadcastOptions options)
+ Promise~SignResponse~Tx, Doc, BroadcastResponse~~ sign(SignArgs args)
+ Promise~BroadcastResponse~ signAndBroadcast(SignArgs args, BroadcastOptions options)
+ Promise~BroadcastResponse~ broadcast(Tx tx, BroadcastOptions options)
+ }
+
+ class BaseSigner {
+ <>
+ +Auth auth
+ +SignerConfig config
+ }
+
+ class CosmosDocSigner {
+ +ISigBuilder txBuilder
+ +CosmosDocSigner(Auth auth, SignerConfig config)
+ +abstract ISigBuilder getTxBuilder()
+ +Promise~SignDocResponse~ signDoc(SignDoc doc)
+ }
+
+ class CosmosBaseSigner {
+ +Encoder[] encoders
+ +string prefix
+ +IAccount account
+ +BaseCosmosTxBuilder txBuilder
+ +CosmosBaseSigner(Auth auth, Encoder[] encoders, string|HttpEndpoint endpoint, SignerOptions options)
+ +abstract Promise~IAccount~ getAccount()
+ +abstract BaseCosmosTxBuilder getTxBuilder()
+ +Promise~string~ getPrefix()
+ +Promise~string~ getAddress()
+ +void setEndpoint(string|HttpEndpoint endpoint)
+ +QueryClient get queryClient()
+ +Promise~SignResponse~ sign(CosmosSignArgs args)
+ +Promise~BroadcastResponse~ broadcast(TxRaw txRaw, BroadcastOptions options)
+ +Promise~BroadcastResponse~ broadcastArbitrary(Uint8Array message, BroadcastOptions options)
+ +Promise~BroadcastResponse~ signAndBroadcast(CosmosSignArgs args, BroadcastOptions options)
+ +Promise~SimulateResponse~ simulate(CosmosSignArgs args)
+ }
+
+ class DirectSigner {
+ +auth: Auth
+ +encoders: Encoder[]
+ +endpoint: string | HttpEndpoint
+ +options: SignerOptions
+ +static fromWallet(signer: OfflineDirectSigner, encoders: Encoder[], endpoint?: string | HttpEndpoint, options?: SignerOptions): Promise~DirectSigner~
+ +static fromWalletToSigners(signer: OfflineDirectSigner, encoders: Encoder[], endpoint?: string | HttpEndpoint, options?: SignerOptions): Promise~DirectSigner[]~
+ }
+
+ class AminoSigner {
+ +auth: Auth
+ +encoders: Encoder[]
+ +endpoint: string | HttpEndpoint
+ +options: SignerOptions
+ +static fromWallet(signer: OfflineDirectSigner, encoders: Encoder[], endpoint?: string | HttpEndpoint, options?: SignerOptions): Promise~DirectSigner~
+ +static fromWalletToSigners(signer: OfflineDirectSigner, encoders: Encoder[], endpoint?: string | HttpEndpoint, options?: SignerOptions): Promise~DirectSigner[]~
+ }
+
+ class ISigBuilder {
+ <>
+ +buildSignature(doc: Doc): Sig | Promise
+ }
+
+ class ITxBuilder {
+ <>
+ +buildSignedTxDoc(args: SignArgs): Promise
+ }
+
+ class BaseCosmosTxBuilder {
+ +SignMode signMode
+ +BaseCosmosTxBuilderContext ctx
+ +buildDoc(args: CosmosSignArgs, txRaw: Partial~TxRaw~): Promise~SignDoc~
+ +buildDocBytes(doc: SignDoc): Promise~Uint8Array~
+ +buildTxRaw(args: CosmosSignArgs): Promise~Partial~TxRaw~
+ +buildTxBody(args: CosmosSignArgs): Promise~TxBody~
+ +buildSignerInfo(publicKey: EncodedMessage, sequence: bigint, signMode: SignMode): Promise~SignerInfo~
+ +buildAuthInfo(signerInfos: SignerInfo[], fee: Fee): Promise~AuthInfo~
+ +getFee(fee: StdFee, txBody: TxBody, signerInfos: SignerInfo[], options: DocOptions): Promise~StdFee~
+ +buildSignedTxDoc(args: CosmosSignArgs): Promise~CosmosCreateDocResponse~SignDoc~~
+ }
+
+ BaseSigner <|-- CosmosDocSigner
+ CosmosDocSigner <|-- CosmosBaseSigner
+ CosmosBaseSigner <|-- DirectSigner
+ CosmosBaseSigner <|-- AminoSigner
+ UniSigner <|.. CosmosBaseSigner
+
+ BaseCosmosTxBuilder --|> ITxBuilder
+
+ CosmosDocSigner *-- ISigBuilder
+ CosmosBaseSigner *-- ITxBuilder
+
+ style UniSigner fill:#f9f,stroke:#333,stroke-width:2px
+ style ISigBuilder fill:#f9f,stroke:#333,stroke-width:2px
+ style ITxBuilder fill:#f9f,stroke:#333,stroke-width:2px
+```
+
+Workflow:
+
+```mermaid
+graph TD
+ A[Signer.sign] --> B[Create partial TxRaw by buildTxRaw]
+ B --> C[Call buildDoc]
+ C --> E[Sign the document by signDoc]
+ E -- isDocAuth --> F[auth.signDoc]
+ E -- isByteAuth --> G[txBuilder.buildSignature]
+ F --> H[Create signed TxRaw]
+ G --> H[Create signed TxRaw]
+ H --> I[Return CosmosCreateDocResponse]
+ I --> J[End]
+```
+
```ts
-import { UniSigner } from "@interchainjser/types";
-import { BaseSigner } from "@interchainjser/utils";
+import { UniSigner } from "@interchainjs/types";
+import { BaseSigner } from "@interchainjs/types";
```
Need to note that there are 2 type parameters that indicates 2 types of document involved in signing and broadcasting process for interface `UniSigner`:
- `SignDoc` is the document type as the signing target to get signature
-- `Tx` is the transaction type to broadcast
+- `Tx` is the signed transaction type to broadcast
The `Signer` class is a way to sign and broadcast transactions on blockchains with ease. With it, you can just pass a Message that you want to be packed in a transaction and the transaction will be prepared, signed and broadcasted.
@@ -26,13 +146,15 @@ import { toEncoder } from "@interchainjs/cosmos/utils";
import { Secp256k1Auth } from "@interchainjs/auth/secp256k1";
import { MsgSend } from "@interchainjs/cosmos-types/cosmos/bank/v1beta1/tx";
-const auth = Secp256k1Auth.fromMnemonic("", "cosmos");
+const [auth] = Secp256k1Auth.fromMnemonic("", [
+ HDPath.cosmos().toString(),
+ ]);
const signer = new DirectSigner(auth, [toEncoder(MsgSend)], );
```
## Signer + Wallet
-As we know, `Wallet` object can be used to sign documents (See [details](/docs/auth.md#auth-vs-wallet)). However, some sign document is still not human-readable (i.e. for `DirectSigner`, the `SignDoc` type is an object with binary data type)
+`Wallet` object can also be used to sign documents (See [details](/docs/auth.md#auth-vs-wallet)). However, some sign document is still not human-readable (i.e. for `DirectSigner`, the `SignDoc` type is an object with binary data types)
However, combining with the `Signer` class allows you to sign human-readable messages or transactions using one function call.
@@ -44,10 +166,12 @@ import { DirectWallet, SignDoc } from "@interchainjs/cosmos/types";
import { toEncoder } from "@interchainjs/cosmos/utils";
import { MsgSend } from "@interchainjs/cosmos-types/cosmos/bank/v1beta1/tx";
-const wallet: DirectWallet = {
- async getAccount(){},
- async sign(doc: SignDoc){}
-}
+const directWallet = Secp256k1HDWallet.fromMnemonic("", [
+ {
+ prefix: commonPrefix,
+ hdPath: cosmosHdPath,
+ },
+]);
const signer = await DirectSigner.fromWallet(wallet, [toEncoder(MsgSend)], );
```
@@ -55,7 +179,7 @@ const signer = await DirectSigner.fromWallet(wallet, [toEncoder(MsgSend)],
-Wrapper of `@interchainjs/auth` and `@interchainjs/cosmos` to fit corresponding interfaces in `@cosmjs`
+Wrapper of `@interchainjs/auth` and `@interchainjs/cosmos` to fit corresponding interfaces in `@cosmjs`
## Usage
@@ -26,10 +26,23 @@ To sign messages (taking `stargate` signing client as example)
// import * from "interchainjs"; // Error: use sub-imports, to ensure small app size
import { StargateSigningClient } from "interchainjs/stargate";
-const client = StargateSigningClient.connectWithSigner(, );
-const result = await client.signAndBroadcast(, [], "auto");
+const directWallet = Secp256k1HDWallet.fromMnemonic(generateMnemonic(), [
+ {
+ prefix: commonPrefix,
+ hdPath: cosmosHdPath,
+ },
+]);
+
+const directSigner = directWallet.toOfflineDirectSigner();
+
+const signingClient = await StargateSigningClient.connectWithSigner(
+ await getRpcEndpoint(),
+ directSigner
+);
+
+const result = await signingClient.signAndBroadcast(, [], "auto");
// or you can use helper functions to do `signAndBroadcast`. taking send tokens as example
-const result = await client.helpers.send(, , "auto", "");
+const result = await signingClient.helpers.send(, , "auto", "");
console.log(result.transactionHash); // the hash of TxRaw
```
@@ -37,10 +50,14 @@ console.log(result.transactionHash); // the hash of TxRaw
To construct an offline signer (taking `direct` signer as example)
```ts
-import { Secp256k1Wallet } from "interchainjs/wallets/secp256k1";
-
-const wallet = Secp256k1Wallet.fromMnemonic("", { prefix: "" });
-const directOfflineSigner = wallet.toOfflineDirectSigner();
+const directWallet = Secp256k1HDWallet.fromMnemonic(generateMnemonic(), [
+ {
+ prefix: commonPrefix,
+ hdPath: cosmosHdPath,
+ },
+]);
+
+const directSigner = directWallet.toOfflineDirectSigner();
```
## Implementations
@@ -50,10 +67,10 @@ const directOfflineSigner = wallet.toOfflineDirectSigner();
- **stargate signing client** from `interchainjs/stargate`
- **cosmwasm signing client** from `interchainjs/cosmwasm-stargate`
- **wallet**
- - **secp256k1 wallet** from `interchainjs/wallets/secp256k1`
+ - **secp256k1 wallet** from `@interchainjs/cosmos/wallets/secp256k1hd`
## License
MIT License (MIT) & Apache License
-Copyright (c) 2024 Cosmology (https://cosmology.zone/)
\ No newline at end of file
+Copyright (c) 2024 Cosmology (https://cosmology.zone/)
diff --git a/networks/cosmos/src/base/base-signer.ts b/networks/cosmos/src/base/base-signer.ts
index b685765b..efe607d1 100644
--- a/networks/cosmos/src/base/base-signer.ts
+++ b/networks/cosmos/src/base/base-signer.ts
@@ -217,6 +217,7 @@ export abstract class CosmosBaseSigner
return this._queryClient;
}
+
/**
* convert relative timeoutHeight to absolute timeoutHeight
*/
diff --git a/packages/auth/README.md b/packages/auth/README.md
index a0d082dd..28bec445 100644
--- a/packages/auth/README.md
+++ b/packages/auth/README.md
@@ -26,33 +26,36 @@ Taking `secp256k1` as example.
// import * from "@interchainjs/auth"; // Error: use sub-imports, to ensure small app size
import { Secp256k1Auth } from "@interchainjs/auth/secp256k1";
-const auth = Secp256k1Auth.fromMnemonic("", "");
+const [directAuth] = Secp256k1Auth.fromMnemonic(generateMnemonic(), [
+ "m/44'/118'/0'/0/0",
+]);
const signature = auth.sign(Uint8Array.from([1, 2, 3]));
console.log(signature.toHex());
```
-It's easy to derive _cosmos/injective/ethereum_ network HD path (taking `cosmos` as example)
+It's easy to derive _cosmos/ethermint/ethereum_ network HD path (taking `cosmos` as example)
```ts
// derive with Cosmos default HD path "m/44'/118'/0'/0/0"
-const auth = Secp256k1Auth.fromMnemonic("", "cosmos");
+const [auth] = Secp256k1Auth.fromMnemonic("", [
+ HDPath.cosmos().toString(),
+]);
// is identical to
-const auth = Secp256k1Auth.fromMnemonic(
- "",
- "m/44'/118'/0'/0/0"
-);
+const [auth] = Secp256k1Auth.fromMnemonic("", [
+ "m/44'/118'/0'/0/0",
+]);
```
`Auth` objected can be utilized by different signers. See
- [@interchainjs/cosmos](/networks/cosmos/README.md)
- [@interchainjs/ethereum](/networks/ethereum/README.md)
-- [@interchainjs/ethermint](/networks/injective/README.md)
+- [@interchainjs/ethermint](/networks/ethermint/README.md)
## Implementations
- **secp256k1 auth** from `@interchainjs/auth/secp256k1`
-- **ed25519 auth** from `@interchainjs/auth/ed25519` (`Not fully implemented yet`)
+- **ethSecp256k1 auth** from `@interchainjs/auth/ethSecp256k1` (`Not fully implemented yet`)
## License
From e8ce5a318ff7d0fbef19edd87dc4a27443ea17f0 Mon Sep 17 00:00:00 2001
From: Zetazzz
Date: Wed, 14 Aug 2024 12:50:00 +0800
Subject: [PATCH 2/3] add class diagram to auth.md
---
docs/auth.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 71 insertions(+), 8 deletions(-)
diff --git a/docs/auth.md b/docs/auth.md
index 0d61a81b..6fdf08b6 100644
--- a/docs/auth.md
+++ b/docs/auth.md
@@ -2,22 +2,78 @@
The main purpose of the `@interchainjs/auth` is to offer developers a way to have different wallet algorithm implementations on Blockchain, including `secp256k1`, `ethSecp256k1`, etc. All of these algorithms implementations are exposing the same `Auth` interface which means that `Signer`s can just use these methods without the need to know the underlying implementation for specific algorithms as they are abstracted away.
+```mermaid
+classDiagram
+ class Auth {
+ <>
+ +string algo
+ +string hdPath
+ +IKey getPublicKey(isCompressed: boolean)
+ }
+
+ class ByteAuth {
+ <>
+ +ISignatureWraper~Sig~ sign(data: Uint8Array)
+ }
+
+ class DocAuth {
+ <>
+ +string address
+ +SignDocResponse~Doc~ signDoc(doc: Doc)
+ }
+
+ ByteAuth --|> Auth
+ DocAuth --|> Auth
+ BaseDocAuth ..|> DocAuth
+
+ class BaseDocAuth {
+ <>
+ +abstract Promise~SignDocResponse~ signDoc(doc: Doc)
+ }
+
+ class AminoDocAuth {
+ +Promise~SignDocResponse~ signDoc(doc: StdSignDoc)
+ +static Promise~AminoDocAuth[]~ fromOfflineSigner(offlineSigner: OfflineAminoSigner)
+ }
+
+ class DirectDocAuth {
+ +Promise~SignDocResponse~ signDoc(doc: SignDoc)
+ +static Promise~DirectDocAuth[]~ fromOfflineSigner(offlineSigner: OfflineDirectSigner)
+ }
+
+ BaseDocAuth <|-- AminoDocAuth
+ BaseDocAuth <|-- DirectDocAuth
+
+ class Secp256k1Auth {
+ +Key privateKey
+ +string algo
+ +string hdPath
+ +Secp256k1Auth(privateKey: Uint8Array | HDKey | Key, hdPath?: string)
+ +static Secp256k1Auth[] fromMnemonic(mnemonic: string, hdPaths: string[], options?: AuthOptions)
+ +Key getPublicKey(isCompressed?: boolean)
+ +ISignatureWraper~RecoveredSignatureType~ sign(data: Uint8Array)
+ }
+
+ Secp256k1Auth ..|> ByteAuth
+
+ style Auth fill:#f9f,stroke:#333,stroke-width:2px
+ style ByteAuth fill:#f9f,stroke:#333,stroke-width:2px
+ style DocAuth fill:#f9f,stroke:#333,stroke-width:2px
+```
+
To start, you have to make an instance of the `*Auth` (i.e. `Secp256k1Auth`) class which gives you the ability to use different algorithms out of the box.
-Usually it can be instantiated from three static methods
+Usually it can be instantiated from constructor or static methods.
- `fromMnemonic` makes an instance from a mnemonic words string. This instance can both `sign` and `verify`.
-- `fromPrivateKey` makes an instance from a private key. This instance can both `sign` and `verify`.
-- `fromPublicKey` makes an instance from a public key. This instance can only `verify` but no `sign` since the private key necessary for signing can not be derived from public key.
Let's have a look at the properties and methods that `Auth` interface exposes and what they mean:
- `algo` implies the algorithm name, i.e. `secp256k1`, `ed25519`.
- `getPublicKey` gets the public key. This method returns the compressed or uncompressed public key according to the value of argument `isCompressed`.
- `sign` signs binary data that can be any piece of information or message that needs to be digitally signed, and returns a `Signature` typed object. Note: this method itself usually does not inherently involve any hash method.
-- `verify` verifies the authenticity of given signature, that is checking if the signature is valid for the provided binary data. Same with `sign`, this method itself usually doesn't apply any hash function to the signed data.
-It's important to note that for a specific cryptographic algorithms, the corresponding `*Auth` class implements `Auth` interface in a way that can be universally applied on different networks. That's why both `sign` and `verify` methods usually don't apply any hash function to the targeted message data. Those various hashing processes will be configured in different `Signer`s. That is:
+It's important to note that for a specific cryptographic algorithms, the corresponding `*Auth` class implements `Auth` interface in a way that can be universally applied on different networks. That's why `sign` method usually don't apply any hash function to the targeted message data. Those various hashing processes will be configured in different `Signer`s. That is:
- `*Auth` classes differs across algorithms but independent of networks
- `*Signer` classes differs across networks but independent of algorithms
@@ -26,7 +82,7 @@ See [usage example](/docs/signer.md#signer--auth).
## Auth vs. Wallet
-Both `Auth` and `Wallet` are interfaces that contains `sign` method. But they differs in the arguments.
+Both `Auth` and `Wallet` are interfaces that contains `sign` method.
```ts
/** you can import { Auth, Wallet } from "@interchainjs/types" */
@@ -38,12 +94,19 @@ export interface Auth {
export interface Wallet {
...,
- sign: (doc: SignDoc) => Promise>;
+ async signDirect(
+ signerAddress: string,
+ signDoc: CosmosDirectDoc
+ ): Promise;
+ async signAmino(
+ signerAddress: string,
+ signDoc: CosmosAminoDoc
+ ): Promise;
}
```
As we can see above, the signing target of `Wallet` is can be any type (usually we set it as the sign document type) while in `Auth` it's limited to binary data.
-For each `Signer` it always has a specific type of sign document type as the signing target to get signature (i.e. for `AminoSigner` it's `StdSignDoc` and for `DirectSigner` it's `SignDoc`). And for some Web3 wallet, they only expose signing methods of the sign document rather than the generalized binary data. Under this circumstanc, users are still abled to construct a `Signer` object via the `fromWallet` static method. This is why `Wallet` interface is created.
+For each `Signer` it always has a specific type of sign document type as the signing target to get signature (i.e. for `AminoSigner` it's `StdSignDoc` and for `DirectSigner` it's `SignDoc`). And for some Web3 wallet, they only expose signing methods of the sign document rather than the generalized binary data. Under this circumstance, users are still abled to construct a `Signer` object via the `fromWallet` static method. This is why `Wallet` interface is created.
See [usage example](/docs/signer.md#signer--wallet).
From 8fecc1e424d7549e06b69505584d7c19f92d7559 Mon Sep 17 00:00:00 2001
From: Zetazzz
Date: Wed, 14 Aug 2024 13:23:40 +0800
Subject: [PATCH 3/3] fix auth doc
---
docs/auth.md | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/docs/auth.md b/docs/auth.md
index 6fdf08b6..dca9c28c 100644
--- a/docs/auth.md
+++ b/docs/auth.md
@@ -65,7 +65,7 @@ To start, you have to make an instance of the `*Auth` (i.e. `Secp256k1Auth`) cla
Usually it can be instantiated from constructor or static methods.
-- `fromMnemonic` makes an instance from a mnemonic words string. This instance can both `sign` and `verify`.
+- `fromMnemonic` makes an instance from a mnemonic words string. This instance can both `sign`.
Let's have a look at the properties and methods that `Auth` interface exposes and what they mean:
@@ -80,6 +80,16 @@ It's important to note that for a specific cryptographic algorithms, the corresp
See [usage example](/docs/signer.md#signer--auth).
+## ByteAuth vs. DocAuth
+
+### ByteAuth
+
+`ByteAuth` is an interface that extends the `Auth` interface and represents an authentication method that can sign arbitrary bytes. It is typically used for signing arbitrary data using specific algorithms like `secp256k1` or `eth_secp256k1`. The `sign` method in `ByteAuth` takes a `Uint8Array` of data and returns a signature wrapped in an `ISignatureWraper`.
+
+### DocAuth
+
+`DocAuth` is an interface that extends the `Auth` interface and represents an authentication method that can sign documents using offline signers. It is a wrapper for offline signers and is usually used by signers built from offline signers. The `signDoc` method in `DocAuth` takes a document of a specific type and returns a `SignDocResponse`. The `DocAuth` interface also includes an `address` property that represents the address associated with the authentication method.
+
## Auth vs. Wallet
Both `Auth` and `Wallet` are interfaces that contains `sign` method.