@tendermint/sig
Version:
A signing library for Cosmos
317 lines (278 loc) • 9.26 kB
text/typescript
import {
base64ToBytes,
bufferToBytes,
bytesToBase64,
toCanonicalJSONBytes
} from '@tendermint/belt';
import {
Bech32String,
Bytes
} from '@tendermint/types';
import {
encode as bech32Encode,
toWords as bech32ToWords
} from 'bech32';
import {
BIP32Interface,
fromSeed as bip32FromSeed
} from 'bip32';
import { mnemonicToSeedSync as bip39MnemonicToSeed } from 'bip39';
import {
publicKeyCreate as secp256k1PublicKeyCreate,
ecdsaSign as secp256k1EcdsaSign,
ecdsaVerify as secp256k1EcdsaVerify
} from 'secp256k1';
import {
COSMOS_PREFIX,
COSMOS_PATH,
BROADCAST_MODE_SYNC
} from './constants';
import {
ripemd160,
sha256
} from './hash';
import {
BroadcastMode,
BroadcastTx,
KeyPair,
StdSignature,
StdSignMsg,
StdTx,
Tx,
SignMeta,
Wallet
} from './types';
/**
* Create a {@link Wallet|`Wallet`} from a known mnemonic.
*
* @param mnemonic - BIP39 mnemonic seed
* @param prefix - Bech32 human readable part, defaulting to {@link COSMOS_PREFIX|`COSMOS_PREFIX`}
* @param path - BIP32 derivation path, defaulting to {@link COSMOS_PATH|`COSMOS_PATH`}
*
* @returns a keypair and address derived from the provided mnemonic
* @throws will throw if the provided mnemonic is invalid
*/
export function createWalletFromMnemonic (mnemonic: string, prefix: string = COSMOS_PREFIX, path: string = COSMOS_PATH): Wallet {
const masterKey = createMasterKeyFromMnemonic(mnemonic);
return createWalletFromMasterKey(masterKey, prefix, path);
}
/**
* Derive a BIP32 master key from a mnemonic.
*
* @param mnemonic - BIP39 mnemonic seed
*
* @returns BIP32 master key
* @throws will throw if the provided mnemonic is invalid
*/
export function createMasterKeyFromMnemonic (mnemonic: string): BIP32Interface {
const seed = bip39MnemonicToSeed(mnemonic);
return bip32FromSeed(seed);
}
/**
* Create a {@link Wallet|`Wallet`} from a BIP32 master key.
*
* @param masterKey - BIP32 master key
* @param prefix - Bech32 human readable part, defaulting to {@link COSMOS_PREFIX|`COSMOS_PREFIX`}
* @param path - BIP32 derivation path, defaulting to {@link COSMOS_PATH|`COSMOS_PATH`}
*
* @returns a keypair and address derived from the provided master key
*/
export function createWalletFromMasterKey (masterKey: BIP32Interface, prefix: string = COSMOS_PREFIX, path: string = COSMOS_PATH): Wallet {
const { privateKey, publicKey } = createKeyPairFromMasterKey(masterKey, path);
const address = createAddress(publicKey, prefix);
return {
privateKey,
publicKey,
address
};
}
/**
* Derive a keypair from a BIP32 master key.
*
* @param masterKey - BIP32 master key
* @param path - BIP32 derivation path, defaulting to {@link COSMOS_PATH|`COSMOS_PATH`}
*
* @returns derived public and private key pair
* @throws will throw if a private key cannot be derived
*/
export function createKeyPairFromMasterKey (masterKey: BIP32Interface, path: string = COSMOS_PATH): KeyPair {
const buffer = masterKey.derivePath(path).privateKey;
if (!buffer) {
throw new Error('could not derive private key');
}
const privateKey = bufferToBytes(buffer);
const publicKey = secp256k1PublicKeyCreate(privateKey, true);
return {
privateKey,
publicKey
};
}
/**
* Derive a Bech32 address from a public key.
*
* @param publicKey - public key bytes
* @param prefix - Bech32 human readable part, defaulting to {@link COSMOS_PREFIX|`COSMOS_PREFIX`}
*
* @returns Bech32-encoded address
*/
export function createAddress (publicKey: Bytes, prefix: string = COSMOS_PREFIX): Bech32String {
const hash1 = sha256(publicKey);
const hash2 = ripemd160(hash1);
const words = bech32ToWords(hash2);
return bech32Encode(prefix, words);
}
/**
* Sign a transaction.
*
* This combines the {@link Tx|`Tx`} and {@link SignMeta|`SignMeta`} into a {@link StdSignMsg|`StdSignMsg`}, signs it,
* and attaches the signature to the transaction. If the transaction is already signed, the signature will be
* added to the existing signatures.
*
* @param tx - transaction (signed or unsigned)
* @param meta - metadata for signing
* @param keyPair - public and private key pair (or {@link Wallet|`Wallet`})
*
* @returns a signed transaction
*/
export function signTx (tx: Tx | StdTx, meta: SignMeta, keyPair: KeyPair): StdTx {
const signMsg = createSignMsg(tx, meta);
const signature = createSignature(signMsg, keyPair);
const signatures = (('signatures' in tx) && (tx.signatures != null)) ? [...tx.signatures, signature] : [signature];
return {
...tx,
signatures
};
}
/**
* Create a transaction with metadata for signing.
*
* @param tx - unsigned transaction
* @param meta - metadata for signing
*
* @returns a transaction with metadata for signing
*/
export function createSignMsg (tx: Tx, meta: SignMeta): StdSignMsg {
return {
account_number: meta.account_number,
chain_id: meta.chain_id,
fee: tx.fee,
memo: tx.memo,
msgs: tx.msg,
sequence: meta.sequence
};
}
/**
* Create a signature from a {@link StdSignMsg|`StdSignMsg`}.
*
* @param signMsg - transaction with metadata for signing
* @param keyPair - public and private key pair (or {@link Wallet|`Wallet`})
*
* @returns a signature and corresponding public key
*/
export function createSignature (signMsg: StdSignMsg, { privateKey, publicKey }: KeyPair): StdSignature {
const signatureBytes = createSignatureBytes(signMsg, privateKey);
return {
signature: bytesToBase64(signatureBytes),
pub_key: {
type: 'tendermint/PubKeySecp256k1',
value: bytesToBase64(publicKey)
}
};
}
/**
* Create signature bytes from a {@link StdSignMsg|`StdSignMsg`}.
*
* @param signMsg - transaction with metadata for signing
* @param privateKey - private key bytes
*
* @returns signature bytes
*/
export function createSignatureBytes (signMsg: StdSignMsg, privateKey: Bytes): Bytes {
const bytes = toCanonicalJSONBytes(signMsg);
return sign(bytes, privateKey);
}
/**
* Sign the sha256 hash of `bytes` with a secp256k1 private key.
*
* @param bytes - bytes to hash and sign
* @param privateKey - private key bytes
*
* @returns signed hash of the bytes
* @throws will throw if the provided private key is invalid
*/
export function sign (bytes: Bytes, privateKey: Bytes): Bytes {
const hash = sha256(bytes);
const { signature } = secp256k1EcdsaSign(hash, privateKey);
return signature;
}
/**
* Verify a signed transaction's signatures.
*
* @param tx - signed transaction
* @param meta - metadata for signing
*
* @returns `true` if all signatures are valid and match, `false` otherwise or if no signatures were provided
*/
export function verifyTx (tx: StdTx, meta: SignMeta): boolean {
const signMsg = createSignMsg(tx, meta);
return verifySignatures(signMsg, tx.signatures);
}
/**
* Verify a {@link StdSignMsg|`StdSignMsg`} against multiple {@link StdSignature|`StdSignature`}s.
*
* @param signMsg - transaction with metadata for signing
* @param signatures - signatures
*
* @returns `true` if all signatures are valid and match, `false` otherwise or if no signatures were provided
*/
export function verifySignatures (signMsg: StdSignMsg, signatures: StdSignature[]): boolean {
if (signatures.length > 0) {
return signatures.every(function (signature: StdSignature): boolean {
return verifySignature(signMsg, signature);
});
}
else {
return false;
}
}
/**
* Verify a {@link StdSignMsg|`StdSignMsg`} against a {@link StdSignature|`StdSignature`}.
*
* @param signMsg - transaction with metadata for signing
* @param signature - signature
*
* @returns `true` if the signature is valid and matches, `false` otherwise
*/
export function verifySignature (signMsg: StdSignMsg, signature: StdSignature): boolean {
const signatureBytes = base64ToBytes(signature.signature);
const publicKey = base64ToBytes(signature.pub_key.value);
return verifySignatureBytes(signMsg, signatureBytes, publicKey);
}
/**
* Verify a signature against a {@link StdSignMsg|`StdSignMsg`}.
*
* @param signMsg - transaction with metadata for signing
* @param signature - signature bytes
* @param publicKey - public key bytes
*
* @returns `true` if the signature is valid and matches, `false` otherwise
*/
export function verifySignatureBytes (signMsg: StdSignMsg, signature: Bytes, publicKey: Bytes): boolean {
const bytes = toCanonicalJSONBytes(signMsg);
const hash = sha256(bytes);
return secp256k1EcdsaVerify(signature, hash, publicKey);
}
/**
* Prepare a signed transaction for broadcast.
*
* @param tx - signed transaction
* @param mode - broadcast mode
*
* @returns a transaction broadcast
*/
export function createBroadcastTx (tx: StdTx, mode: BroadcastMode = BROADCAST_MODE_SYNC): BroadcastTx {
return {
tx,
mode
};
}