Skip to content
Open
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
6,483 changes: 3,224 additions & 3,259 deletions .pnp.cjs

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions packages/amino/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {
Secp256k1Pubkey,
} from "./pubkeys";

export function encodeSecp256k1Pubkey(pubkey: Uint8Array): Secp256k1Pubkey {
export function encodeSecp256k1Pubkey(pubkey: Uint8Array, urlType?: string): Secp256k1Pubkey {
if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) {
throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03");
}
urlType = urlType? urlType : pubkeyType.secp256k1
return {
type: pubkeyType.secp256k1,
type: urlType,
value: toBase64(pubkey),
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/amino/src/pubkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function isEd25519Pubkey(pubkey: Pubkey): pubkey is Ed25519Pubkey {
}

export interface Secp256k1Pubkey extends SinglePubkey {
readonly type: "tendermint/PubKeySecp256k1";
readonly type: string;
readonly value: string;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/amino/src/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ export interface StdSignature {
* @param pubkey a compressed secp256k1 public key
* @param signature a 64 byte fixed length representation of secp256k1 signature components r and s
*/
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array, urlType?: string): StdSignature {
if (signature.length !== 64) {
throw new Error(
"Signature must be 64 bytes long. Cosmos SDK uses a 2x32 byte fixed length encoding for the secp256k1 signature integers r and s.",
);
}

return {
pub_key: encodeSecp256k1Pubkey(pubkey),
pub_key: encodeSecp256k1Pubkey(pubkey, urlType),
signature: toBase64(signature),
};
}
Expand Down
136 changes: 115 additions & 21 deletions packages/proto-signing/src/directsecp256k1hdwallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
Slip10,
Slip10Curve,
stringToPath,
Keccak256
} from "@cosmjs/crypto";
import { fromBase64, fromUtf8, toBase64, toBech32, toUtf8 } from "@cosmjs/encoding";
import { fromBase64, fromUtf8, Bech32, toBase64, toBech32, toUtf8, fromHex, toHex, toAscii } from "@cosmjs/encoding";
import { assert, isNonNullObject } from "@cosmjs/utils";
import { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";

Expand All @@ -27,6 +28,7 @@ import {
supportedAlgorithms,
} from "./wallet";


interface AccountDataWithPrivkey extends AccountData {
readonly privkey: Uint8Array;
}
Expand Down Expand Up @@ -267,22 +269,41 @@ export class DirectSecp256k1HdWallet implements OfflineDirectSigner {
}));
}

public async signDirect(signerAddress: string, signDoc: SignDoc): Promise<DirectSignResponse> {
public async signDirect(signerAddress: string, signDoc: SignDoc, urlType?: string,): Promise<DirectSignResponse> {
const accounts = await this.getAccountsWithPrivkeys();
const account = accounts.find(({ address }) => address === signerAddress);
if (account === undefined) {
throw new Error(`Address ${signerAddress} not found in wallet`);
}
const { privkey, pubkey } = account;
const signBytes = makeSignBytes(signDoc);
const hashedMessage = sha256(signBytes);
const signature = await Secp256k1.createSignature(hashedMessage, privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
const stdSignature = encodeSecp256k1Signature(pubkey, signatureBytes);
return {
signed: signDoc,
signature: stdSignature,
};

switch (urlType) {
case '/ethermint.crypto.v1.ethsecp256k1.PubKey': {
// eth signing
const hashedMessage = new Keccak256(signBytes).digest()
const signature = await Secp256k1.createSignature(hashedMessage, privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
const stdSignature = encodeSecp256k1Signature(pubkey, signatureBytes, "/ethermint.crypto.v1.ethsecp256k1.PubKey");

return {
signed: signDoc,
signature: stdSignature
};
}
default: {
// cosmos sigining
const hashedMessage = sha256(signBytes);
const signature = await Secp256k1.createSignature(hashedMessage, privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
const stdSignature = encodeSecp256k1Signature(pubkey, signatureBytes);

return {
signed: signDoc,
signature: stdSignature,
};
}
}
}

/**
Expand Down Expand Up @@ -336,24 +357,97 @@ export class DirectSecp256k1HdWallet implements OfflineDirectSigner {
private async getKeyPair(hdPath: HdPath): Promise<Secp256k1Keypair> {
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, this.seed, hdPath);
const { pubkey } = await Secp256k1.makeKeypair(privkey);
return {
privkey: privkey,
pubkey: Secp256k1.compressPubkey(pubkey),
};

const coinType = pathToString(hdPath).split('/')[2]
switch (coinType) {
// ETH cointype=60
case "60'": // 65 byte len
return {
privkey: privkey,
pubkey: pubkey
}
default:
return {
privkey: privkey,
pubkey: Secp256k1.compressPubkey(pubkey) // 33 byte len,
}
}
}

private async getAccountsWithPrivkeys(): Promise<readonly AccountDataWithPrivkey[]> {
return Promise.all(
this.accounts.map(async ({ hdPath, prefix }) => {
const { privkey, pubkey } = await this.getKeyPair(hdPath);
const address = toBech32(prefix, rawSecp256k1PubkeyToRawAddress(pubkey));
return {
algo: "secp256k1" as const,
privkey: privkey,
pubkey: pubkey,
address: address,
};

const coinType = pathToString(hdPath).split('/')[2]
switch (coinType) {
case "60'":
const hash = new Keccak256(pubkey.slice(1)).digest()
const lastTwentyBytes = toHex(hash.slice(-20));
// EVM address
const address = DirectSecp256k1HdWallet.toChecksummedAddress('0x' + lastTwentyBytes)

return {
algo: "secp256k1" as const,
privkey: privkey,
pubkey: Secp256k1.compressPubkey(pubkey),
address: await DirectSecp256k1HdWallet.getBech32AddressFromEVMAddress(address, prefix)
};
default:
return {
algo: "secp256k1" as const,
privkey: privkey,
pubkey: pubkey,
address: toBech32(prefix, rawSecp256k1PubkeyToRawAddress(pubkey)),
};
}
}),
);
}
private static async getBech32AddressFromEVMAddress(evmAddress: string, bech32Prefix: string): Promise<string> {
if (!DirectSecp256k1HdWallet.isAddress(evmAddress.toLowerCase())) {
throw new TypeError('Please provide a valid EVM compatible address.');
}

var evmAddrWithoutHexPrefix = evmAddress.replace(/^(-)?0x/i, '$1');
var evmAddressBytes = fromHex(evmAddrWithoutHexPrefix);
var evmToBech32Address = Bech32.encode(bech32Prefix, evmAddressBytes);
return evmToBech32Address;
};
private static isValidAddress(address: string): boolean {
if (!address.match(/^0x[a-fA-F0-9]{40}$/)) {
return false;
}
return true
}
private static toChecksummedAddress(address: string): string {
// 40 low hex characters
let addressLower;
if (typeof address === "string") {
if (!DirectSecp256k1HdWallet.isValidAddress(address)) {
throw new Error("Input is not a valid Ethereum address");
}
addressLower = address.toLowerCase().replace("0x", "");
} else {
addressLower = toHex(address);
}

const addressHash = toHex(new Keccak256(toAscii(addressLower)).digest());
let checksumAddress = "0x";
for (let i = 0; i < 40; i++) {
checksumAddress += parseInt(addressHash[i], 16) > 7 ? addressLower[i].toUpperCase() : addressLower[i];
}
return checksumAddress;
}
private static isAddress (address: string): boolean {
// check if it has the basic requirements of an address
if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
return false;
// If it's ALL lowercase or ALL upppercase
} else if (/^(0x|0X)?[0-9a-f]{40}$/.test(address) || /^(0x|0X)?[0-9A-F]{40}$/.test(address)) {
return true;
// Otherwise check each case
}
return false
};
}
3 changes: 3 additions & 0 deletions packages/proto-signing/src/pubkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export function decodePubkey(pubkey?: Any | null): Pubkey | null {
case "/cosmos.crypto.secp256k1.PubKey": {
return decodeSinglePubkey(pubkey);
}
case "/ethermint.crypto.v1.ethsecp256k1.PubKey": {
return decodeSinglePubkey(pubkey);
}
case "/cosmos.crypto.multisig.LegacyAminoPubKey": {
const { threshold, publicKeys } = LegacyAminoPubKey.decode(pubkey.value);
const out: MultisigThresholdPubkey = {
Expand Down
2 changes: 1 addition & 1 deletion packages/proto-signing/src/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface DirectSignResponse {

export interface OfflineDirectSigner {
readonly getAccounts: () => Promise<readonly AccountData[]>;
readonly signDirect: (signerAddress: string, signDoc: SignDoc) => Promise<DirectSignResponse>;
readonly signDirect: (signerAddress: string, signDoc: SignDoc, coinType?: string) => Promise<DirectSignResponse>;
}

export type OfflineSigner = OfflineAminoSigner | OfflineDirectSigner;
Expand Down
4 changes: 4 additions & 0 deletions packages/stargate/src/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export function accountFromAny(input: Any): Account {
assert(baseAccount);
return accountFromBaseAccount(baseAccount);
}
case "/ethermint.types.v1.EthAccount":
const baseAccount = ModuleAccount.decode(value).baseAccount;
assert(baseAccount);
return accountFromBaseAccount(baseAccount);

// vesting

Expand Down
14 changes: 11 additions & 3 deletions packages/stargate/src/signingstargateclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import {
makeSignDoc,
OfflineSigner,
Registry,
TxBodyEncodeObject,
TxBodyEncodeObject
} from "@cosmjs/proto-signing";
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { assert, assertDefined } from "@cosmjs/utils";
import { pathToString } from "@cosmjs/crypto";
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx";
import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
Expand Down Expand Up @@ -160,6 +161,7 @@ export class SigningStargateClient extends StargateClient {
memo: string | undefined,
): Promise<number> {
const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m));

const accountFromSigner = (await this.signer.getAccounts()).find(
(account) => account.address === signerAddress,
);
Expand Down Expand Up @@ -382,22 +384,28 @@ export class SigningStargateClient extends StargateClient {
const accountFromSigner = (await this.signer.getAccounts()).find(
(account) => account.address === signerAddress,
);

const account = await this.getAccount(signerAddress)

if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}
const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey));
let pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey));


const txBodyEncodeObject: TxBodyEncodeObject = {
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: {
messages: messages,
memo: memo,
},
};

const txBodyBytes = this.registry.encode(txBodyEncodeObject);
const gasLimit = Int53.fromString(fee.gas).toNumber();
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], fee.amount, gasLimit);
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc);
const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc, pubkey.typeUrl);
return TxRaw.fromPartial({
bodyBytes: signed.bodyBytes,
authInfoBytes: signed.authInfoBytes,
Expand Down
Loading