PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-xtz/src/lib

Просмотр файла: utils.ts

import { isValidXpub, SigningError } from '@bitgo/sdk-core';
import { InMemorySigner } from '@taquito/signer';
import * as base58check from 'bs58check';
import { secp256k1 } from '@noble/curves/secp256k1';
import sodium from 'libsodium-wrappers';
import { HashType, SignResponse } from './iface';
import { KeyPair } from './keyPair';
import { genericMultisigDataToSign } from './multisigUtils';

// By default, use the transactions prefix
export const DEFAULT_WATERMARK = new Uint8Array([3]);

/**
 * Encode the payload to base58 with a specific Tezos prefix.
 *
 * @param {Buffer} prefix to add to the encoded payload
 * @param {Buffer} payload to encode
 * @returns {any} base58 payload with a Tezos prefix
 */
export function base58encode(prefix: Buffer, payload: Buffer): string {
  const n = Buffer.alloc(prefix.length + payload.length);
  n.set(prefix);
  n.set(payload, prefix.length);

  return base58check.encode(n);
}

/**
 * Calculate the transaction id for a for a signed transaction.
 *
 * @param {string} encodedTransaction Signed transaction in hexadecimal
 * @returns {Promise<string>} The transaction id
 */
export async function calculateTransactionId(encodedTransaction: string): Promise<string> {
  await sodium.ready;
  const encodedTransactionBuffer = Uint8Array.from(Buffer.from(encodedTransaction, 'hex'));
  const operationHashPayload = sodium.crypto_generichash(32, encodedTransactionBuffer);
  return base58encode(hashTypes.o.prefix, Buffer.from(operationHashPayload));
}

/**
 * Calculate the address of a new originated account.
 *
 * @param {string} transactionId The transaction id
 * @param {number} index The index of the origination operation inside the transaction (starts at 0)
 * @returns {Promise<string>} An originated address with the KT prefix
 */
export async function calculateOriginatedAddress(transactionId: string, index: number): Promise<string> {
  // From https://github.com/TezTech/eztz/blob/cfdc4fcfc891f4f4f077c3056f414476dde3610b/src/main.js#L768
  const ob = base58check.decode(transactionId).slice(hashTypes.o.prefix.length);

  let tt: number[] = [];
  for (let i = 0; i < ob.length; i++) {
    tt.push(ob[i]);
  }

  tt = tt.concat([
    (index & 0xff000000) >> 24,
    (index & 0x00ff0000) >> 16,
    (index & 0x0000ff00) >> 8,
    index & 0x000000ff,
  ]);

  await sodium.ready;
  const payload = sodium.crypto_generichash(20, new Uint8Array(tt));
  return base58encode(hashTypes.KT.prefix, Buffer.from(payload));
}

/**
 * Generic data signing using Tezos library.
 *
 * @param {KeyPair} keyPair A Key Pair with a private key set
 * @param {string} data The data in hexadecimal to sign
 * @param {Uint8Array} watermark Magic byte: 1 for block, 2 for endorsement, 3 for generic
 * @returns {Promise<SignResponse>}
 */
export async function sign(
  keyPair: KeyPair,
  data: string,
  watermark: Uint8Array = DEFAULT_WATERMARK
): Promise<SignResponse> {
  if (!keyPair.getKeys().prv) {
    throw new SigningError('Missing private key');
  }
  const signer = new InMemorySigner(keyPair.getKeys().prv!);
  return signer.sign(data, watermark);
}

/**
 * Verifies the signature produced for a given message belongs to a secp256k1 public key.
 *
 * @param {string} message Message in hex format to verify
 * @param {string} publicKey secp256k1 public key with "sppk" prefix to verify the signature with
 * @param {string} signature Tezos signature with "sig" prefix
 * @param {Uint8Array} watermark Optional watermark used to generate the signature
 * @returns {Promise<boolean>}
 */
export async function verifySignature(
  message: string,
  publicKey: string,
  signature: string,
  watermark: Uint8Array = DEFAULT_WATERMARK
): Promise<boolean> {
  const rawPublicKey = decodeKey(publicKey, hashTypes.sppk).toString('hex');
  const key = secp256k1.ProjectivePoint.fromHex(rawPublicKey);

  const messageBuffer = Uint8Array.from(Buffer.from(message, 'hex'));
  // Tezos signatures always have a watermark
  const messageWithWatermark = new Uint8Array(watermark.length + messageBuffer.length);
  messageWithWatermark.set(watermark);
  messageWithWatermark.set(messageBuffer, watermark.length);

  await sodium.ready;
  const bytesHash = Buffer.from(sodium.crypto_generichash(32, messageWithWatermark));

  const rawSignature = decodeSignature(signature, hashTypes.sig);
  return secp256k1.verify(rawSignature, bytesHash, key.toHex());
}

/**
 * Useful wrapper to create the generic multisig contract data to sign when moving funds.
 *
 * @param {string} contractAddress The wallet contract address with the funds to withdraw
 * @param {string} destinationAddress The address to transfer the funds to
 * @param {number} amount Number mutez to transfer
 * @param {string} contractCounter Wallet counter to use in the transaction
 * @returns {any} A JSON representation of the Michelson script to sign and approve a transfer
 */
export function generateDataToSign(
  contractAddress: string,
  destinationAddress: string,
  amount: string,
  contractCounter: string
): any {
  if (!isValidOriginatedAddress(contractAddress)) {
    throw new Error('Invalid contract address ' + contractAddress + '. An originated account address was expected');
  }
  if (!isValidAddress(destinationAddress)) {
    throw new Error('Invalid destination address ' + destinationAddress);
  }
  return genericMultisigDataToSign(contractAddress, destinationAddress, amount, contractCounter);
}

/**
 * Returns whether or not the string is a valid Tezos hash of the given type
 *
 * @param {string} hash - the string to validate
 * @param {HashType} hashType - the type of the provided hash
 * @returns {boolean}
 */
export function isValidHash(hash: string, hashType: HashType): boolean {
  // Validate encoding
  let decodedHash;
  try {
    decodedHash = base58check.decode(hash);
  } catch (e) {
    return false;
  }
  const hashPrefix = decodedHash.slice(0, hashType.prefix.length);

  // Check prefix
  if (!hashPrefix.equals(Buffer.from(hashType.prefix))) {
    return false;
  }

  // Check length
  const hashLength = decodedHash.length - hashPrefix.length;
  return hashLength === hashType.byteLength;
}

/**
 * Returns whether or not the string is a valid Tezos address
 *
 * @param {string} hash - the address to validate
 * @returns {boolean}
 */
export function isValidAddress(hash: string): boolean {
  return isValidImplicitAddress(hash) || isValidHash(hash, hashTypes.KT);
}

/**
 * Returns whether or not the string is a valid Tezos implicit account address
 *
 * @param {string} hash - the address to validate
 * @returns {boolean}
 */
export function isValidImplicitAddress(hash: string): boolean {
  return isValidHash(hash, hashTypes.tz1) || isValidHash(hash, hashTypes.tz2) || isValidHash(hash, hashTypes.tz3);
}

/**
 * Returns whether or not the string is a valid Tezos originated account address
 *
 * @param {string} hash - the address to validate
 * @returns {boolean}
 */
export function isValidOriginatedAddress(hash: string): boolean {
  return isValidHash(hash, hashTypes.KT);
}

/**
 * Returns whether or not the string is a valid Tezos signature
 *
 * @param {string} hash - the signature to validate
 * @returns {boolean}
 */
export function isValidSignature(hash: string): boolean {
  return (
    isValidHash(hash, hashTypes.edsig) ||
    isValidHash(hash, hashTypes.spsig1) ||
    isValidHash(hash, hashTypes.p2sig) ||
    isValidHash(hash, hashTypes.sig)
  );
}

/**
 * Returns whether or not the string is a valid Tezos public key
 *
 * @param {string} publicKey The public key to validate
 * @returns {boolean}
 */
export function isValidPublicKey(publicKey: string): boolean {
  return (
    isValidHash(publicKey, hashTypes.sppk) ||
    isValidHash(publicKey, hashTypes.p2pk) ||
    isValidHash(publicKey, hashTypes.edpk) ||
    isValidXpub(publicKey) // xpubs are valid too.
  );
}

/**
 * Returns whether or not the string is a valid Tezos private key
 *
 * @param {string} privateKey The private key to validate
 * @returns {boolean}
 */
export function isValidPrivateKey(privateKey: string): boolean {
  return (
    isValidHash(privateKey, hashTypes.edesk) ||
    isValidHash(privateKey, hashTypes.edsk) ||
    isValidHash(privateKey, hashTypes.spsk) ||
    isValidHash(privateKey, hashTypes.p2sk)
  );
}

/**
 * Returns whether or not the string is a valid Tezos block hash
 *
 * @param {string} hash - the address to validate
 * @returns {boolean}
 */
export function isValidBlockHash(hash: string): boolean {
  return isValidHash(hash, hashTypes.b);
}

/**
 * Returns whether or not the string is a valid Tezos transaction hash
 *
 * @param {string} hash - the address to validate
 * @returns {boolean}
 */
export function isValidTransactionHash(hash: string): boolean {
  return isValidHash(hash, hashTypes.o);
}

/**
 * Returns whether or not the string is a valid Tezos key given a prefix
 *
 * @param {string} hash - the key to validate
 * @param {HashType} hashType - the type of the provided hash
 * @returns {boolean}
 */
export function isValidKey(hash: string, hashType: HashType): boolean {
  return isValidHash(hash, hashType);
}

/**
 * Get the original key form the text without the given prefix.
 *
 * @param {string} hash - base58 encoded key with a Tezos prefix
 * @param {HashType} hashType - the type of the provided hash
 * @returns {Buffer} the original decoded key
 */
export function decodeKey(hash: string, hashType: HashType): Buffer {
  if (!isValidKey(hash, hashType)) {
    throw new Error('Unsupported private key');
  }
  const decodedPrv = base58check.decode(hash);
  return Buffer.from(decodedPrv.slice(hashType.prefix.length, decodedPrv.length));
}

/**
 * Get the raw signature from a Tezos encoded one.
 *
 * @param {string} signature Tezos signatures prefixed with sig, edsig, p2sig or spsig
 * @param {HashType} hashType The prefix of remove
 * @returns {Buffer} The decoded signature without prefix
 */
export function decodeSignature(signature: string, hashType: HashType): Buffer {
  if (!isValidSignature(signature)) {
    throw new Error('Unsupported signature');
  }
  const decodedPrv = base58check.decode(signature);
  return Buffer.from(decodedPrv.slice(hashType.prefix.length, decodedPrv.length));
}

// Base58Check is used for encoding
// hashedTypes is used to validate hashes by type, by checking their prefix and
// the length of the Buffer obtained by decoding the hash (excluding the prefix)
export const hashTypes = {
  /* 20 bytes long */
  // ed25519 public key hash
  tz1: {
    prefix: Buffer.from([6, 161, 159]),
    byteLength: 20,
  },
  // secp256k1 public key hash
  tz2: {
    prefix: Buffer.from([6, 161, 161]),
    byteLength: 20,
  },
  // p256 public key hash
  tz3: {
    prefix: Buffer.from([6, 161, 164]),
    byteLength: 20,
  },
  KT: {
    prefix: Buffer.from([2, 90, 121]),
    byteLength: 20,
  },
  /* 32 bytes long */
  // ed25519 public key
  edpk: {
    prefix: Buffer.from([13, 15, 37, 217]),
    byteLength: 32,
  },
  // ed25519 secret key
  edsk2: {
    prefix: Buffer.from([13, 15, 58, 7]),
    byteLength: 32,
  },
  // secp256k1 secret key
  spsk: {
    prefix: Buffer.from([17, 162, 224, 201]),
    byteLength: 32,
  },
  // p256 secret key
  p2sk: {
    prefix: Buffer.from([16, 81, 238, 189]),
    byteLength: 32,
  },
  // block hash
  b: {
    prefix: Buffer.from([1, 52]),
    byteLength: 32,
  },
  // operation hash
  o: {
    prefix: Buffer.from([5, 116]),
    byteLength: 32,
  },
  // operation list hash
  Lo: {
    prefix: Buffer.from([133, 233]),
    byteLength: 32,
  },
  // operation list list hash
  LLo: {
    prefix: Buffer.from([29, 159, 109]),
    byteLength: 32,
  },
  // protocol hash
  P: {
    prefix: Buffer.from([2, 170]),
    byteLength: 32,
  },
  // context hash
  Co: {
    prefix: Buffer.from([79, 179]),
    byteLength: 32,
  },
  /* 33 bytes long */
  // secp256k1 public key
  sppk: {
    prefix: Buffer.from([3, 254, 226, 86]),
    byteLength: 33,
  },
  // p256 public key
  p2pk: {
    prefix: Buffer.from([3, 178, 139, 127]),
    byteLength: 33,
  },
  /* 56 bytes long */
  // ed25519 encrypted seed
  edesk: {
    prefix: Buffer.from([7, 90, 60, 179, 41]),
    byteLength: 56,
  },
  /* 63 bytes long */
  // ed25519 secret key
  edsk: {
    prefix: Buffer.from([43, 246, 78, 7]),
    byteLength: 64,
  },
  // ed25519 signature
  edsig: {
    prefix: Buffer.from([9, 245, 205, 134, 18]),
    byteLength: 64,
  },
  // secp256k1 signature
  spsig1: {
    prefix: Buffer.from([13, 115, 101, 19, 63]),
    byteLength: 64,
  },
  // p256_signature
  p2sig: {
    prefix: Buffer.from([54, 240, 44, 52]),
    byteLength: 64,
  },
  // generic signature
  sig: {
    prefix: Buffer.from([4, 130, 43]),
    byteLength: 64,
  },
  /* 15 bytes long */
  // network hash
  Net: {
    prefix: Buffer.from([87, 82, 0]),
    byteLength: 15,
  },
  // nonce hash
  nce: {
    prefix: Buffer.from([69, 220, 169]),
    byteLength: 15,
  },
  /* 4 bytes long */
  // chain id
  id: {
    prefix: Buffer.from([153, 103]),
    byteLength: 4,
  },
};

// From https://github.com/ecadlabs/taquito/blob/master/packages/taquito/src/constants.ts

export enum DEFAULT_GAS_LIMIT {
  DELEGATION = 10600,
  ORIGINATION = 10600,
  TRANSFER = 10600,
  REVEAL = 10600,
}

export enum DEFAULT_FEE {
  DELEGATION = 1257,
  ORIGINATION = 10000,
  TRANSFER = 10000,
  REVEAL = 1420,
}

export enum DEFAULT_STORAGE_LIMIT {
  DELEGATION = 0,
  ORIGINATION = 257,
  TRANSFER = 257,
  REVEAL = 0,
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!