Skip to content

Commit

Permalink
sdk upgraded with multisignature feature (#7)
Browse files Browse the repository at this point in the history
* sdk updated with multisignature feature

* bump: [email protected]

* protobuf removed

* comments added to amino encoding
  • Loading branch information
itslesther authored Apr 3, 2024
1 parent c78b2be commit 88a9c3a
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 19 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@swisstronik/sdk",
"version": "1.6.0",
"version": "1.7.0",
"description": "TypeScript SDK for Swisstronik Network",
"license": "Apache-2.0",
"source": "src/index.ts",
Expand Down
39 changes: 28 additions & 11 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import {
Pubkey,
SinglePubkey,
encodeEd25519Pubkey,
MultisigThresholdPubkey,
} from "@cosmjs/amino";
import { Any } from "./types-proto/google/protobuf/any.js";
import Long from "long";
import { PubKey as CosmosCryptoEd25519Pubkey } from "cosmjs-types/cosmos/crypto/ed25519/keys.js";
import { PubKey as CosmosCryptoSecp256k1Pubkey } from "cosmjs-types/cosmos/crypto/secp256k1/keys.js";
import { PubKey as CommonPubKey } from "cosmjs-types/cosmos/crypto/secp256k1/keys.js";
import { Secp256k1 } from "./compatability/secp256k1.js";
import { LegacyAminoPubKey } from "cosmjs-types/cosmos/crypto/multisig/keys.js";

export class SwisstronikStargateClient extends StargateClient {
private readonly overridenAccountParser: AccountParser;
Expand Down Expand Up @@ -92,17 +94,32 @@ export class SwisstronikStargateClient extends StargateClient {
return Uint64.fromString(input.toString());
}

private decodePubkey(pubkey: Any): Pubkey {
switch (pubkey.typeUrl) {
case "/ethermint.crypto.v1.ethsecp256k1.PubKey":
case "/cosmos.crypto.secp256k1.PubKey":
case "/cosmos.crypto.ed25519.PubKey": {
return this.anyToSinglePubkey(pubkey);
}
default:
throw new Error(`Pubkey type_url ${pubkey.typeUrl} not recognized`);
}
}
private decodePubkey(pubkey: Any): Pubkey {
switch (pubkey.typeUrl) {
case "/ethermint.crypto.v1.ethsecp256k1.PubKey":
case "/cosmos.crypto.secp256k1.PubKey":
case "/cosmos.crypto.ed25519.PubKey": {
return this.anyToSinglePubkey(pubkey);
}
case "/cosmos.crypto.multisig.LegacyAminoPubKey": {
return this.anyToMultiPubkey(pubkey);
}
default:
throw new Error(`Pubkey type_url ${pubkey.typeUrl} not recognized`);
}
}

private anyToMultiPubkey(pubkey: Any): MultisigThresholdPubkey {
const { publicKeys, threshold } = LegacyAminoPubKey.decode(pubkey.value);
const keys = publicKeys.map((key) => this.anyToSinglePubkey(key));
return {
type: "tendermint/PubKeyMultisigThreshold",
value: {
pubkeys: keys,
threshold: String(threshold)
},
};
}

private anyToSinglePubkey(pubkey: Any): SinglePubkey {
switch (pubkey.typeUrl) {
Expand Down
55 changes: 55 additions & 0 deletions packages/sdk/src/compatability/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { fromBase64, fromHex, toBech32 } from "@cosmjs/encoding"
import { Pubkey, isEd25519Pubkey, isMultisigThresholdPubkey, isSecp256k1Pubkey } from "@cosmjs/amino"
import { Uint53 } from "@cosmjs/math"
import { rawSecp256k1PubkeyToRawAddress } from "./secp256k1";
import { sha256 } from '@cosmjs/crypto'

export function encodeAminoPubkey(pubkey: Pubkey) {
const pubkeyAminoPrefixMultisigThreshold = fromHex("22c1f7e2" /* variable length not included */);
const pubkeyAminoPrefixSecp256k1 = fromHex("21" /* fixed length */);
const pubkeyAminoPrefixEd25519 = fromHex("1624de64" + "20" /* fixed length */);


function encodeUvarint(value: number | string) {
const checked = Uint53.fromString(value.toString()).toNumber();
if (checked > 127) {
throw new Error("Encoding numbers > 127 is not supported here. Please tell those lazy CosmJS maintainers to port the binary.PutUvarint implementation from the Go standard library and write some tests.");
}
return [checked];
}

if (isMultisigThresholdPubkey(pubkey)) {
const out = Array.from(pubkeyAminoPrefixMultisigThreshold);
out.push(0x08); // https://github.com/cosmos/cosmjs/blob/v0.31.1/packages/amino/src/encoding.ts#L198
out.push(...encodeUvarint(pubkey.value.threshold));
for (const pubkeyData of pubkey.value.pubkeys.map((p) => encodeAminoPubkey(p))) {
out.push(0x12); // https://github.com/cosmos/cosmjs/blob/v0.31.1/packages/amino/src/encoding.ts#L201
out.push(...encodeUvarint(pubkeyData.length));
out.push(...pubkeyData);
}
return new Uint8Array(out);
}
else if (isEd25519Pubkey(pubkey)) {
return new Uint8Array([...pubkeyAminoPrefixEd25519, ...fromBase64(pubkey.value)]);
}
else if (isSecp256k1Pubkey(pubkey)) {
return new Uint8Array([...pubkeyAminoPrefixSecp256k1, ...fromBase64(pubkey.value)]);
}
else {
throw new Error("Unsupported pubkey type");
}
}

export function pubkeyToAddress(pubkey: Pubkey) {
if(isSecp256k1Pubkey(pubkey)) {
const buf = Buffer.from(pubkey.value, "base64");
const bytes = Uint8Array.from(buf);
return toBech32('swtr', rawSecp256k1PubkeyToRawAddress(bytes));
} else if(isMultisigThresholdPubkey(pubkey)) {
const pubkeyData = encodeAminoPubkey(pubkey);
return toBech32('swtr', sha256(pubkeyData).slice(0, 20));
}
else {
throw new Error("Unsupported public key type");
}
}
4 changes: 3 additions & 1 deletion packages/sdk/src/compatability/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './directsecp256k1hdwallet.js'
export * from './directsecp256k1wallet.js'
export * from './secp256k1.js'
export * from './wallet.js'
export * from './wallet.js'
export * from './address.js'
export * from './multisignature.js'
123 changes: 123 additions & 0 deletions packages/sdk/src/compatability/multisignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { pubkeyToAddress } from "./address";
import {
StdFee,
isSecp256k1Pubkey,
isMultisigThresholdPubkey,
MultisigThresholdPubkey,
isEd25519Pubkey,
Pubkey,
} from "@cosmjs/amino";
import { PubKey as Secp256k1PubKey } from "cosmjs-types/cosmos/crypto/secp256k1/keys.js";
import { PubKey as Ed25519PubKey } from "cosmjs-types/cosmos/crypto/ed25519/keys.js";
import { fromBase64 } from "@cosmjs/encoding";
import { LegacyAminoPubKey } from "cosmjs-types/cosmos/crypto/multisig/keys";
import { Uint53 } from "@cosmjs/math";
import { Any } from "../types-proto/google/protobuf/any.js";
import { makeCompactBitArray } from "@cosmjs/stargate/build/multisignature.js";
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing.js";
import Long from "long";
import { AuthInfo, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx.js";
import { MultiSignature } from "cosmjs-types/cosmos/crypto/multisig/v1beta1/multisig.js";

export function makeMultisignedTxBytes(
multisigPubkey: MultisigThresholdPubkey,
sequence: number,
fee: StdFee,
bodyBytes: Uint8Array,
signatures: Map<string, Uint8Array>
): Uint8Array {
const signedTx = makeMultisignedTx(
multisigPubkey,
sequence,
fee,
bodyBytes,
signatures
);
return TxRaw.encode(signedTx).finish();
}

export function makeMultisignedTx(
multisigPubkey: MultisigThresholdPubkey,
sequence: number,
fee: StdFee,
bodyBytes: Uint8Array,
signatures: Map<string, Uint8Array>
) {
const signers = Array(multisigPubkey.value.pubkeys.length).fill(false);
const signaturesList = new Array();
for (let i = 0; i < multisigPubkey.value.pubkeys.length; i++) {
const signerAddress = pubkeyToAddress(multisigPubkey.value.pubkeys[i]);
const signature = signatures.get(signerAddress);
if (signature) {
signers[i] = true;
signaturesList.push(signature);
}
}
const signerInfo = {
publicKey: encodePubkey(multisigPubkey),
modeInfo: {
multi: {
bitarray: makeCompactBitArray(signers),
modeInfos: signaturesList.map((_) => ({
single: { mode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON },
})),
},
},
sequence: Long.fromNumber(sequence),
} as any;

const authInfo = AuthInfo.fromPartial({
signerInfos: [signerInfo],
fee: {
amount: [...fee.amount],
gasLimit: Long.fromNumber(+fee.gas) as any,
},
});

const authInfoBytes = AuthInfo.encode(authInfo).finish();
const signedTx = TxRaw.fromPartial({
bodyBytes: bodyBytes,
authInfoBytes: authInfoBytes,
signatures: [
MultiSignature.encode(
MultiSignature.fromPartial({ signatures: signaturesList })
).finish(),
],
});
return signedTx;
}

function encodePubkey(pubkey: Pubkey) {
if (isSecp256k1Pubkey(pubkey)) {
const pubkeyProto = Secp256k1PubKey.fromPartial({
key: fromBase64(pubkey.value),
});

const anyPubkey = Any.fromPartial({
typeUrl: "/ethermint.crypto.v1.ethsecp256k1.PubKey",
value: Secp256k1PubKey.encode(pubkeyProto).finish(),
});

return anyPubkey;
} else if (isEd25519Pubkey(pubkey)) {
const pubkeyProto = Ed25519PubKey.fromPartial({
key: fromBase64(pubkey.value),
});
return Any.fromPartial({
typeUrl: "/cosmos.crypto.ed25519.PubKey",
value: Uint8Array.from(Ed25519PubKey.encode(pubkeyProto).finish()),
});
} else if (isMultisigThresholdPubkey(pubkey)) {
const pubkeyProto = LegacyAminoPubKey.fromPartial({
threshold: Uint53.fromString(pubkey.value.threshold).toNumber(),
publicKeys: pubkey.value.pubkeys.map(encodePubkey),
}) as any;

return Any.fromPartial({
typeUrl: "/cosmos.crypto.multisig.LegacyAminoPubKey",
value: Uint8Array.from(LegacyAminoPubKey.encode(pubkeyProto).finish()),
});
} else {
throw new Error(`Pubkey type ${pubkey.type} not recognized`);
}
}
Loading

0 comments on commit 88a9c3a

Please sign in to comment.