2022-04-01 12:32:56 +00:00
|
|
|
import assert from 'assert';
|
2022-04-05 09:20:12 +00:00
|
|
|
import BIP32Factory from 'bip32';
|
|
|
|
import * as ecc from 'tiny-secp256k1';
|
|
|
|
import * as bip39 from 'bip39';
|
2022-04-08 05:29:03 +00:00
|
|
|
import canonicalStringify from 'canonical-json';
|
|
|
|
import secp256k1 from 'secp256k1';
|
2022-04-11 11:10:35 +00:00
|
|
|
import { utils } from 'ethers';
|
2022-04-14 04:34:52 +00:00
|
|
|
import { sha256 } from 'js-sha256';
|
2022-04-01 12:32:56 +00:00
|
|
|
import { MessageTypes, signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util';
|
2024-03-07 04:17:05 +00:00
|
|
|
import { Ripemd160 } from '@cosmjs/crypto';
|
2022-04-11 11:10:35 +00:00
|
|
|
import { fromHex, toHex } from '@cosmjs/encoding';
|
2024-03-07 04:17:05 +00:00
|
|
|
import { ethToEthermint } from '@tharsis/address-converter';
|
2022-04-12 10:54:26 +00:00
|
|
|
import { encodeSecp256k1Pubkey } from '@cosmjs/amino';
|
2024-03-07 04:17:05 +00:00
|
|
|
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
2022-04-05 09:20:12 +00:00
|
|
|
|
2022-04-08 05:29:03 +00:00
|
|
|
import { Payload, Signature } from './types';
|
|
|
|
|
|
|
|
const AMINO_PREFIX = 'EB5AE98721';
|
2022-04-05 09:20:12 +00:00
|
|
|
const HDPATH = "m/44'/60'/0'/0";
|
2024-03-07 04:17:05 +00:00
|
|
|
const ACCOUNT_PREFIX = 'laconic';
|
2022-04-05 09:20:12 +00:00
|
|
|
|
|
|
|
const bip32 = BIP32Factory(ecc);
|
2022-04-01 12:32:56 +00:00
|
|
|
|
|
|
|
interface TypedMessageDomain {
|
|
|
|
name?: string;
|
|
|
|
version?: string;
|
|
|
|
chainId?: number;
|
|
|
|
verifyingContract?: string;
|
|
|
|
salt?: ArrayBuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registry account.
|
|
|
|
*/
|
|
|
|
export class Account {
|
2024-03-07 04:17:05 +00:00
|
|
|
_privateKey: Buffer;
|
|
|
|
_publicKey!: Uint8Array;
|
|
|
|
_encodedPubkey!: string;
|
|
|
|
_formattedCosmosAddress!: string;
|
|
|
|
_registryPublicKey!: string;
|
|
|
|
_registryAddress!: string;
|
|
|
|
_ethAddress!: string;
|
|
|
|
_wallet!: DirectSecp256k1Wallet;
|
|
|
|
_address!: string;
|
2022-04-05 09:20:12 +00:00
|
|
|
|
2022-04-05 14:11:06 +00:00
|
|
|
/**
|
2022-04-05 09:20:12 +00:00
|
|
|
* Generate bip39 mnemonic.
|
|
|
|
*/
|
2024-03-07 04:17:05 +00:00
|
|
|
static generateMnemonic () {
|
2022-04-05 09:20:12 +00:00
|
|
|
return bip39.generateMnemonic();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate private key from mnemonic.
|
|
|
|
*/
|
2024-03-07 04:17:05 +00:00
|
|
|
static async generateFromMnemonic (mnemonic: string) {
|
2022-04-05 09:20:12 +00:00
|
|
|
assert(mnemonic);
|
|
|
|
|
|
|
|
const seed = await bip39.mnemonicToSeed(mnemonic);
|
|
|
|
const wallet = bip32.fromSeed(seed);
|
|
|
|
const account = wallet.derivePath(HDPATH);
|
|
|
|
const { privateKey } = account;
|
|
|
|
assert(privateKey);
|
|
|
|
|
|
|
|
return new Account(privateKey);
|
|
|
|
}
|
2022-04-01 12:32:56 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* New Account.
|
|
|
|
*/
|
2024-03-07 04:17:05 +00:00
|
|
|
constructor (privateKey: Buffer) {
|
2022-04-01 12:32:56 +00:00
|
|
|
assert(privateKey);
|
|
|
|
this._privateKey = privateKey;
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
get privateKey () {
|
2022-04-01 12:32:56 +00:00
|
|
|
return this._privateKey;
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
get encodedPubkey () {
|
2022-04-12 10:54:26 +00:00
|
|
|
return this._encodedPubkey;
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
get formattedCosmosAddress () {
|
2022-04-05 09:20:12 +00:00
|
|
|
return this._formattedCosmosAddress;
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
get registryPublicKey () {
|
2022-04-08 05:29:03 +00:00
|
|
|
return this._registryPublicKey;
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
get registryAddress () {
|
2022-04-08 05:29:03 +00:00
|
|
|
return this._registryAddress;
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
get address () {
|
2024-03-06 14:18:45 +00:00
|
|
|
return this._address;
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
get wallet () {
|
2024-03-06 14:18:45 +00:00
|
|
|
return this._wallet;
|
|
|
|
}
|
|
|
|
|
|
|
|
async init () {
|
|
|
|
this._wallet = await DirectSecp256k1Wallet.fromKey(
|
|
|
|
this._privateKey,
|
|
|
|
ACCOUNT_PREFIX
|
|
|
|
);
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
this._address = (await this._wallet.getAccounts())[0].address;
|
2024-03-06 14:18:45 +00:00
|
|
|
|
2022-04-01 12:32:56 +00:00
|
|
|
// Generate public key.
|
2024-03-07 04:17:05 +00:00
|
|
|
this._publicKey = secp256k1.publicKeyCreate(this._privateKey);
|
|
|
|
this._encodedPubkey = encodeSecp256k1Pubkey(this._publicKey).value;
|
2022-04-05 09:20:12 +00:00
|
|
|
|
2022-04-11 11:10:35 +00:00
|
|
|
// 2. Generate eth address.
|
2024-03-07 04:17:05 +00:00
|
|
|
this._ethAddress = utils.computeAddress(this._publicKey);
|
2022-04-05 09:20:12 +00:00
|
|
|
|
|
|
|
// 3. Generate cosmos-sdk formatted address.
|
2022-04-11 11:10:35 +00:00
|
|
|
this._formattedCosmosAddress = ethToEthermint(this._ethAddress);
|
2022-04-08 05:29:03 +00:00
|
|
|
|
|
|
|
// 4. Generate registry formatted public key.
|
|
|
|
const publicKeyInHex = AMINO_PREFIX + toHex(this._publicKey);
|
|
|
|
this._registryPublicKey = Buffer.from(publicKeyInHex, 'hex').toString('base64');
|
|
|
|
|
|
|
|
// 5. Generate registry formatted address.
|
|
|
|
let publicKeySha256 = sha256(Buffer.from(publicKeyInHex, 'hex'));
|
|
|
|
this._registryAddress = new Ripemd160().update(fromHex(publicKeySha256)).digest().toString();
|
2022-04-01 12:32:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get private key.
|
|
|
|
*/
|
2024-03-07 04:17:05 +00:00
|
|
|
getPrivateKey () {
|
2022-04-01 12:32:56 +00:00
|
|
|
return this._privateKey.toString('hex');
|
|
|
|
}
|
|
|
|
|
2022-04-25 09:42:46 +00:00
|
|
|
/**
|
|
|
|
* Get cosmos address.
|
|
|
|
*/
|
2024-03-07 04:17:05 +00:00
|
|
|
getCosmosAddress () {
|
2022-04-25 09:42:46 +00:00
|
|
|
return this._formattedCosmosAddress;
|
|
|
|
}
|
|
|
|
|
2022-04-08 05:29:03 +00:00
|
|
|
/**
|
|
|
|
* Get record signature.
|
|
|
|
*/
|
2024-03-07 04:17:05 +00:00
|
|
|
async signRecord (record: any) {
|
2022-04-08 05:29:03 +00:00
|
|
|
assert(record);
|
|
|
|
|
|
|
|
const recordAsJson = canonicalStringify(record);
|
|
|
|
// Double sha256.
|
|
|
|
const recordBytesToSign = Buffer.from(sha256(Buffer.from(sha256(Buffer.from(recordAsJson)), 'hex')), 'hex');
|
|
|
|
|
|
|
|
// Sign message
|
|
|
|
assert(recordBytesToSign);
|
|
|
|
|
|
|
|
const messageToSignSha256 = sha256(recordBytesToSign);
|
|
|
|
const messageToSignSha256InBytes = Buffer.from(messageToSignSha256, 'hex');
|
|
|
|
const sigObj = secp256k1.ecdsaSign(messageToSignSha256InBytes, this.privateKey);
|
|
|
|
|
|
|
|
return Buffer.from(sigObj.signature);
|
|
|
|
}
|
|
|
|
|
2024-03-07 04:17:05 +00:00
|
|
|
async signPayload (payload: Payload) {
|
2022-04-08 05:29:03 +00:00
|
|
|
assert(payload);
|
|
|
|
|
|
|
|
const { record } = payload;
|
|
|
|
const messageToSign = record.getMessageToSign();
|
|
|
|
|
|
|
|
const sig = await this.signRecord(messageToSign);
|
2024-03-07 04:17:05 +00:00
|
|
|
assert(this.registryPublicKey);
|
2022-04-08 05:29:03 +00:00
|
|
|
const signature = new Signature(this.registryPublicKey, sig.toString('base64'));
|
|
|
|
payload.addSignature(signature);
|
|
|
|
|
|
|
|
return signature;
|
|
|
|
}
|
|
|
|
|
2022-04-01 12:32:56 +00:00
|
|
|
/**
|
|
|
|
* Sign message.
|
|
|
|
*/
|
2024-03-07 04:17:05 +00:00
|
|
|
sign (message: any) {
|
2022-04-01 12:32:56 +00:00
|
|
|
assert(message);
|
|
|
|
const eipMessageDomain: any = message.eipToSign.domain;
|
|
|
|
|
|
|
|
const signature = signTypedData({
|
|
|
|
data: {
|
|
|
|
types: message.eipToSign.types as MessageTypes,
|
|
|
|
primaryType: message.eipToSign.primaryType,
|
|
|
|
domain: eipMessageDomain as TypedMessageDomain,
|
|
|
|
message: message.eipToSign.message as Record<string, unknown>
|
|
|
|
},
|
|
|
|
privateKey: this._privateKey,
|
|
|
|
version: SignTypedDataVersion.V4
|
2024-03-07 04:17:05 +00:00
|
|
|
});
|
2022-04-01 12:32:56 +00:00
|
|
|
|
|
|
|
return signature;
|
|
|
|
}
|
|
|
|
}
|