PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-algo/src/lib
Просмотр файла: utils.ts
import algosdk from 'algosdk';
import stellar from 'stellar-sdk';
import * as hex from '@stablelib/hex';
import * as nacl from 'tweetnacl';
import base32 from 'hi-base32';
import sha512 from 'js-sha512';
import _ from 'lodash';
import { Address, EncodedTx, Seed } from './ifaces';
import { KeyPair } from './keyPair';
import { SeedEncoding } from './seedEncoding';
import * as algoNacl from 'algosdk/dist/cjs/src/nacl/naclWrappers';
import * as encoding from 'algosdk/dist/cjs/src/encoding/encoding';
import {
BaseUtils,
NotImplementedError,
InvalidTransactionError,
InvalidKey,
isValidEd25519PublicKey,
isValidEd25519SecretKey,
} from '@bitgo/sdk-core';
const ALGORAND_CHECKSUM_BYTE_LENGTH = 4;
const ALGORAND_SEED_LENGTH = 58;
const ALGORAND_SEED_BYTE_LENGTH = 36;
const ALGORAND_TRANSACTION_LENGTH = 52;
const SEED_BYTES_LENGTH = 32;
/**
* Determines whether the string is only composed of hex chars.
*
* @param {string} maybe The string to be validated.
* @returns {boolean} true if the string consists of only hex characters, otherwise false.
*/
function allHexChars(maybe: string): boolean {
return /^([0-9a-f]{2})+$/i.test(maybe);
}
/**
* ConcatArrays takes two array and returns a joint array of both
*
* @param a {Uint8Array} first array to concat
* @param b {Uint8Array} second array
* @returns {Uint8Array} a new array containing all elements of 'a' followed by all elements of 'b'
*/
function concatArrays(a: Uint8Array, b: Uint8Array): Uint8Array {
const c = new Uint8Array(a.length + b.length);
c.set(a);
c.set(b, a.length);
return c;
}
export class Utils implements BaseUtils {
/** @inheritdoc */
isValidAddress(address: string): boolean {
return algosdk.isValidAddress(address);
}
/** @inheritdoc */
isValidTransactionId(txId: string): boolean {
if (txId.length !== 104) {
return false;
}
return allHexChars(txId);
}
/** @inheritdoc */
isValidPublicKey(key: string): boolean {
return isValidEd25519PublicKey(key);
}
/** @inheritdoc */
isValidPrivateKey(key: string): boolean {
return isValidEd25519SecretKey(key);
}
/**
* Returns an hex string of the given buffer
*
* @param {Uint8Array} buffer - the buffer to be converted to hex
* @returns {string} - the hex value
*/
toHex(buffer: Uint8Array): string {
return hex.encode(buffer, true);
}
/** @inheritdoc */
isValidSignature(signature: string): boolean {
throw new NotImplementedError('isValidSignature not implemented.');
}
/** @inheritdoc */
isValidBlockId(hash: string): boolean {
throw new NotImplementedError('hash not implemented.');
}
/**
* Compare two Keys
*
* @param {Uint8Array} key1 - key to be compare
* @param {Uint8Array} key2 - key to be compare
* @returns {boolean} - returns true if both keys are equal
*/
areKeysEqual(key1: Uint8Array, key2: Uint8Array): boolean {
return nacl.verify(key1, key2);
}
/**
* Returns a Uint8Array of the given hex string
*
* @param {string} str - the hex string to be converted
* @returns {string} - the Uint8Array value
*/
toUint8Array(str: string): Uint8Array {
return Buffer.from(str, 'hex');
}
/**
* Determines whether a seed is valid.
*
* @param {string} seed - the seed to be validated
* @returns {boolean} - true if the seed is valid
*/
isValidSeed(seed: string): boolean {
if (typeof seed !== 'string') return false;
if (seed.length !== ALGORAND_SEED_LENGTH) return false;
// Try to decode
let decoded;
try {
decoded = this.decodeSeed(seed);
} catch (e) {
return false;
}
// Compute checksum
const checksum = new Uint8Array(
sha512.sha512_256.array(decoded.seed).slice(SEED_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, SEED_BYTES_LENGTH)
);
// Check if the checksum matches the one from the decoded seed
return _.isEqual(checksum, decoded.checksum);
}
/**
* Encode an algo seed
*
* @param {Buffer} secretKey - the valid secretKey .
* @returns {string} - the seed to be validated.
*/
encodeSeed(secretKey: Buffer): string {
// get seed
const seed = secretKey.slice(0, SEED_BYTES_LENGTH);
// compute checksum
const checksum = Buffer.from(
sha512.sha512_256.array(seed).slice(SEED_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, SEED_BYTES_LENGTH)
);
const encodedSeed = base32.encode(concatArrays(seed, checksum));
return encodedSeed.toString().slice(0, ALGORAND_SEED_LENGTH); // removing the extra '===='
}
/**
* decodeSeed decodes an algo seed
*
* Decoding algo seed is same as decoding address.
* Latest version of algo sdk (1.9, at this writing) does not expose explicit method for decoding seed.
* Parameter is decoded and split into seed and checksum.
*
* @param {string} seed - hex or base64 encoded seed to be validated
* @returns {Seed} - validated object Seed
*/
decodeSeed(seed: string): Seed {
// try to decode
const decoded = base32.decode.asBytes(seed);
// Sanity check
if (decoded.length !== ALGORAND_SEED_BYTE_LENGTH) throw new Error('seed seems to be malformed');
return {
seed: new Uint8Array(decoded.slice(0, ALGORAND_SEED_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH)),
checksum: new Uint8Array(decoded.slice(SEED_BYTES_LENGTH, ALGORAND_SEED_BYTE_LENGTH)),
};
}
/**
* Verifies if signature for message is valid.
*
* @param pub {Uint8Array} public key
* @param message {Uint8Array} signed message
* @param signature {Buffer} signature to verify
* @returns {Boolean} true if signature is valid.
*/
verifySignature(message: Uint8Array, signature: Buffer, pub: Uint8Array): boolean {
return nacl.sign.detached.verify(message, signature, pub);
}
/**
* Transforms an Ed25519 public key into an algorand address.
*
* @param {Uint8Array} pk The Ed25519 public key.
* @see https://developer.algorand.org/docs/features/accounts/#transformation-public-key-to-algorand-address
*
* @returns {string} The algorand address.
*/
publicKeyToAlgoAddress(pk: Uint8Array): string {
return new KeyPair({ pub: Buffer.from(pk).toString('hex') }).getAddress();
}
/**
Transforms a decrypted Ed25519 private key into an algorand address.
@param {string} privateKey The Ed25519 private key.
@returns {string} The algorand address.
**/
privateKeyToAlgoAddress(privateKey: string): string {
// Derive the account from the private key
const keypair = new KeyPair({ prv: privateKey });
return keypair.getAddress();
}
/**
* Checks if a unsigned algo transaction can be decoded.
*
* @param {Uint8Array} txn The encoded unsigned transaction.
* @returns {boolean} true if the transaction can be decoded, otherwise false
*/
protected isDecodableUnsignedAlgoTxn(txn: Uint8Array): boolean {
try {
algosdk.decodeUnsignedTransaction(txn);
return true;
} catch {
return false;
}
}
/**
* Checks if a signed algo transaction can be decoded.
*
* @param {Uint8Array} txn The encoded signed transaction.
* @returns {boolean} true if the transaction can be decoded, otherwise false
*/
protected isDecodableSignedTransaction(txn: Uint8Array): boolean {
try {
algosdk.decodeSignedTransaction(txn);
return true;
} catch {
return false;
}
}
/**
* Decodes a signed or unsigned algo transaction.
*
* @param {Uint8Array | string} txnBytes The encoded unsigned or signed txn.
* @returns {EncodedTx} The decoded transaction.
*/
decodeAlgoTxn(txnBytes: Uint8Array | string): EncodedTx {
let buffer =
typeof txnBytes === 'string'
? Buffer.from(txnBytes, allHexChars(txnBytes) ? 'hex' : 'base64')
: Buffer.from(txnBytes);
// In order to maintain backward compatibility with old keyreg transactions encoded with
// forked algosdk 1.2.0 (https://github.com/BitGo/algosdk-bitgo),
// the relevant information is extracted and parsed following the latest algosdk
// release standard.
// This way we can decode transactions successfully by still maintaining backward compatibility.
const decodedTx = encoding.decode(buffer);
if (
decodedTx.txn &&
decodedTx.txn.type === 'keyreg' &&
decodedTx.txn.votefst &&
decodedTx.txn.votelst &&
decodedTx.txn.votekd
) {
decodedTx.txn.votekey = decodedTx.txn.votekey || decodedTx.msig.subsig[0].pk;
decodedTx.txn.selkey = decodedTx.txn.selkey || decodedTx.msig.subsig[0].pk;
buffer = decodedTx.msig || decodedTx.sig ? encoding.encode(decodedTx) : encoding.encode(decodedTx.txn);
}
try {
return this.tryToDecodeUnsignedTransaction(buffer);
} catch {
// Ignore error to try different format
}
try {
return this.tryToDecodeSignedTransaction(buffer);
} catch {
throw new InvalidTransactionError('Transaction cannot be decoded');
}
}
/**
* Try to decode a signed Algo transaction
* @param buffer the encoded transaction
* @returns { EncodedTx } the decoded signed transaction
* @throws error if it is not a valid encoded signed transaction
*/
tryToDecodeSignedTransaction(buffer: Buffer): EncodedTx {
// TODO: Replace with
// return algosdk.Transaction.from_obj_for_encoding(algosdk.decodeSignedTransaction(buffer).txn);
// see: https://github.com/algorand/js-algorand-sdk/issues/364
// "...some parts of the codebase treat the output of Transaction.from_obj_for_encoding as EncodedTransaction.
// They need to be fixed(or we at least need to make it so Transaction conforms to EncodedTransaction)."
const tx = algosdk.decodeSignedTransaction(buffer);
const signers: string[] = [];
const signedBy: string[] = [];
if (tx.msig && tx.msig.subsig) {
for (const sig of tx.msig.subsig) {
const addr = algosdk.encodeAddress(sig.pk);
signers.push(addr);
if (sig.s) {
signedBy.push(addr);
}
}
}
return {
rawTransaction: new Uint8Array(buffer),
txn: tx.txn,
signed: true,
signers: signers,
signedBy: signedBy,
};
}
/**
* Try to decode an unsigned Algo transaction
* @param buffer the encoded transaction
* @returns {EncodedTx} the decoded unsigned transaction
* @throws error if it is not a valid encoded unsigned transaction
*/
tryToDecodeUnsignedTransaction(buffer: Buffer): EncodedTx {
const txn = algosdk.decodeUnsignedTransaction(buffer);
return {
rawTransaction: new Uint8Array(buffer),
txn,
signed: false,
};
}
/*
* encodeObj takes a javascript object and returns its msgpack encoding
* Note that the encoding sorts the fields alphabetically
*
* @param {Record<string | number | symbol, any>} obj js obj
* @returns {Uint8Array} Uint8Array binary representation
*/
encodeObj(obj: Record<string | number | symbol, any>): Uint8Array {
return algosdk.encodeObj(obj);
}
/**
* decodeObj takes a Uint8Array and returns its javascript obj
* @param o - Uint8Array to decode
* @returns object
*/
decodeObj(o: ArrayLike<number>): unknown {
return algosdk.decodeObj(o);
}
/**
* secretKeyToMnemonic takes an Algorant secret key and returns the corresponding mnemonic
*
* @param sk - Algorant secret key
* @return Secret key is associated mnemonic
*/
secretKeyToMnemonic(sk: Buffer): string {
const skValid = Buffer.from(sk.toString('hex'));
if (!this.isValidPrivateKey(skValid.toString('hex'))) {
throw new InvalidKey(`The secret key: ${sk.toString('hex')} is invalid`);
}
const skUnit8Array = Buffer.from(sk);
return algosdk.secretKeyToMnemonic(skUnit8Array);
}
/**
* seedFromMnemonic converts a mnemonic generated using this library into the source key used to create it
* It returns an error if the passed mnemonic has an incorrect checksum, if the number of words is unexpected, or if one
* of the passed words is not found in the words list
*
* @param mnemonic - 25 words mnemonic
* @returns 32 bytes long seed
*/
seedFromMnemonic(mnemonic: string): Uint8Array {
return algosdk.mnemonicToMasterDerivationKey(mnemonic);
}
/**
* keyPairFromSeed generates an object with secretKey and publicKey using the algosdk
* @param seed 32 bytes long seed
* @returns KeyPair
*/
keyPairFromSeed(seed: Uint8Array): KeyPair {
const mn = this.mnemonicFromSeed(seed);
const base64PrivateKey = algosdk.mnemonicToSecretKey(mn).sk;
return this.createKeyPair(base64PrivateKey);
}
/**
* Generate a new `KeyPair` object from the given private key.
*
* @param base64PrivateKey 64 bytes long privateKey
* @returns KeyPair
*/
protected createKeyPair(base64PrivateKey: Uint8Array): KeyPair {
const sk = base64PrivateKey.slice(0, 32);
return new KeyPair({ prv: Buffer.from(sk).toString('hex') });
}
/**
* decodePrivateKey generates a seed with a mnemonic and using algosdk.
*
* @param seed 32 bytes long seed
* @returns mnemonic - 25 words mnemonic - 25 words mnemonic
*/
protected mnemonicFromSeed(seed: Uint8Array): string {
return algosdk.masterDerivationKeyToMnemonic(seed);
}
/**
* Validates the key with the stellar-sdk
*
* @param publicKey
* @returns boolean
*/
protected isValidEd25519PublicKeyStellar(publicKey: string): boolean {
return stellar.StrKey.isValidEd25519PublicKey(publicKey);
}
/**
* Decodes the key with the stellar-sdk
*
* @param publicKey
* @returns Buffer
*/
protected decodeEd25519PublicKeyStellar(publicKey: string): Buffer {
return stellar.StrKey.decodeEd25519PublicKey(publicKey);
}
/**
* Convert a stellar seed to algorand encoding
*
* @param seed
* @returns string the encoded seed
*/
convertFromStellarSeed(seed: string): string {
return SeedEncoding.encode(stellar.StrKey.decodeEd25519SecretSeed(seed));
}
/**
* Returns an address encoded with algosdk
*
* @param addr
* @returns string
*/
encodeAddress(addr: Uint8Array): string {
return algosdk.encodeAddress(addr);
}
/**
* Return an address decoded with algosdk
*
* @param addr
* @returns Address
*/
decodeAddress(addr: string): Address {
return algosdk.decodeAddress(addr);
}
/**
* Converts an address into an ALGO one
* If the given data is a Stellar address or public key, it is converted to ALGO address.
*
* @param addressOrPubKey an ALGO address, or an Stellar address or public key
* @returns address algo address string
*/
stellarAddressToAlgoAddress(addressOrPubKey: string): string {
// we have an Algorand address
if (this.isValidAddress(addressOrPubKey)) {
return addressOrPubKey;
}
// we have a stellar key
if (this.isValidEd25519PublicKeyStellar(addressOrPubKey)) {
const stellarPub = this.decodeEd25519PublicKeyStellar(addressOrPubKey);
const algoAddress = this.encodeAddress(stellarPub);
if (this.isValidAddress(algoAddress)) {
return algoAddress;
}
throw new Error('Cannot convert Stellar address to an Algorand address via pubkey.');
}
throw new Error('Neither an Algorand address nor a stellar pubkey.');
}
/**
* multisigAddress takes multisig metadata (preimage) and returns the corresponding human readable Algorand address.
*
* @param {number} version mutlisig version
* @param {number} threshold multisig threshold
* @param {string[]} addrs list of Algorand addresses
* @returns {string} human readable Algorand address.
*/
multisigAddress(version: number, threshold: number, addrs: string[]): string {
return algosdk.multisigAddress({
version,
threshold,
addrs,
});
}
/**
* generateAccount generates un account with a secretKey and an address
*
* Function has not params
* @returns Account
*/
generateAccount(): algosdk.Account {
return algosdk.generateAccount();
}
generateAccountFromSeed(seed: Uint8Array): algosdk.Account {
const keys = nacl.sign.keyPair.fromSeed(seed);
return {
addr: algosdk.encodeAddress(keys.publicKey),
sk: keys.secretKey,
};
}
/**
* Generates Tx ID from an encoded multisig transaction
*
* This is done because of a change made on version 1.10.1 on algosdk so method txID() only supports SignedTransaction type.
* (https://github.com/algorand/js-algorand-sdk/blob/develop/CHANGELOG.md#1101)
*
* @param {string} txBase64 - encoded base64 multisig transaction
* @returns {string} - transaction ID
*/
getMultisigTxID(txBase64: string): string {
const txBytes = Buffer.from(txBase64, 'base64');
const decodeSignTx = algosdk.decodeSignedTransaction(txBytes);
const wellFormedDecodedSignTx = decodeSignTx.txn.get_obj_for_encoding();
const txForEncoding = { msig: decodeSignTx.msig, txn: wellFormedDecodedSignTx };
const en_msg = encoding.encode(txForEncoding);
const tag = Buffer.from([84, 88]);
const gh = Buffer.from(concatArrays(tag, en_msg));
const hash = Buffer.from(algoNacl.genericHash(gh));
return base32.encode(hash).slice(0, ALGORAND_TRANSACTION_LENGTH);
}
/**
* Determines if a given transaction data is to enable or disable a token
* @param amount the amount in transaction
* @param from the originated address
* @param to the target address
* @param closeRemainderTo (optional) address to send remaining units in originated address
* @returns 'enableToken' or 'disableToken'
*/
getTokenTxType(amount: string, from: string, to: string, closeRemainderTo?: string): string {
let type = 'transferToken';
if (amount === '0' && from === to) {
type = !closeRemainderTo ? 'enableToken' : 'disableToken';
}
return type;
}
/**
* Validate if the key is a valid base64 string
* @param key the key to validate
*/
validateBase64(key: string): void {
if (!key || typeof key !== 'string') {
throw new Error('Invalid base64 string');
}
const base64RegExp =
/^(?:[a-zA-Z0-9+\/]{4})*(?:|(?:[a-zA-Z0-9+\/]{3}=)|(?:[a-zA-Z0-9+\/]{2}==)|(?:[a-zA-Z0-9+\/]{1}===))$/;
if (!base64RegExp.test(key)) {
throw new Error('Invalid base64 string');
}
}
}
const utils = new Utils();
export default utils;
Выполнить команду
Для локальной разработки. Не используйте в интернете!