@hashgraph/hedera-identify-snap
Version:
A snap for managing Decentralized Identifiers(DIDs)
250 lines (224 loc) • 7.52 kB
text/typescript
/*-
*
* Hedera Identify Snap
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import type { Key } from '@hashgraph/sdk';
import { PublicKey } from '@hashgraph/sdk';
import type { HashgraphProto } from '@hashgraph/sdk/lib/Key';
import { HDNodeWallet, Mnemonic, assertArgument, ethers } from 'ethers';
import { publicKeyConvert } from 'secp256k1';
import { DEFAULTCOINTYPE } from '../types/constants';
export class CryptoUtils {
/**
* Derives a wallet from the provided node using the provided path.
* NOTE: This method is a copy of the 'derivePath' method from the 'ethers' library as that method
* changed in the recent version and the new method does not work as expected.
* @param node - The node to derive the wallet from.
* @param path - The path to use for derivation.
* @returns The derived HDNodeWallet.
*/
// eslint-disable-next-line no-restricted-syntax
private static derivePathForWallet(
node: HDNodeWallet,
path: string,
): HDNodeWallet {
const components = path.split('/');
assertArgument(
components.length > 0 && (components[0] === 'm' || node.depth > 0),
'invalid path',
'path',
path,
);
if (components[0] === 'm') {
components.shift();
}
let result: HDNodeWallet = node;
const HardenedBit = 0x80000000;
for (let i = 0; i < components.length; i++) {
const component = components[i];
if (component.match(/^[0-9]+'$/u)) {
const index = parseInt(
component.substring(0, component.length - 1),
10,
);
assertArgument(
index < HardenedBit,
'invalid path index',
`path[${i}]`,
component,
);
result = result.deriveChild(HardenedBit + index);
} else if (component.match(/^[0-9]+$/u)) {
const index = parseInt(component, 10);
assertArgument(
index < HardenedBit,
'invalid path index',
`path[${i}]`,
component,
);
result = result.deriveChild(index);
} else {
assertArgument(
false,
'invalid path component',
`path[${i}]`,
component,
);
}
}
return result;
}
/**
* Generates a wallet using the provided EVM address to generate entropy.
* @param evmAddress - The EVM address used as salt for entropy.
* @returns A promise that resolves to an HDNodeWallet.
*/
public static async generateWallet(
evmAddress: string,
): Promise<HDNodeWallet> {
const entropy = await snap.request({
method: 'snap_getEntropy',
params: {
version: 1,
salt: evmAddress,
},
});
let nodeWallet = HDNodeWallet.fromMnemonic(Mnemonic.fromEntropy(entropy));
nodeWallet = CryptoUtils.derivePathForWallet(
nodeWallet,
`m/44'/${DEFAULTCOINTYPE}'/0'/0/0`,
);
return nodeWallet;
}
public static getCompressedPublicKey(publicKey: string): string {
const hexToUIntArray = new Uint8Array(
Buffer.from(publicKey.split('0x')[1], 'hex'),
);
const pKeyConvert = publicKeyConvert(hexToUIntArray, true);
return Buffer.from(pKeyConvert).toString('hex');
}
/**
* Checks whether the provided key is a valid Ethereum public key.
* @param key - The public key to check.
* @returns True if the key is valid, false otherwise.
*/
public static isValidEthereumPublicKey(key: string): boolean {
let publicKey: string = key;
// Check if the key has the '0x' prefix
if (!publicKey.startsWith('0x')) {
publicKey = `0x${publicKey}`;
}
// Check if the key is a compressed (66 characters) or uncompressed (130 characters) public key
return (
(publicKey.length === 68 || publicKey.length === 130) &&
ethers.isHexString(publicKey)
);
}
/**
* Checks whether the provided key is a valid Hedera public key.
* @param key - The public key to check.
* @returns True if the key is valid, false otherwise.
*/
public static isValidHederaPublicKey(key: string): boolean {
try {
PublicKey.fromString(key);
} catch (error: any) {
return false;
}
return true;
}
/**
* Converts a string to a Uint8Array.
* @param data - The string to convert.
* @returns The converted Uint8Array.
*/
public static stringToUint8Array(data: string): Uint8Array {
const encoder = new TextEncoder(); // The TextEncoder encodes into UTF-8 by default
const uint8Array = encoder.encode(data);
return uint8Array;
}
/**
* Converts a Uint8Array to a hexadecimal string.
* @param data - The Uint8Array to convert.
* @returns The hexadecimal string.
*/
public static uint8ArrayToHex(data: Uint8Array | null | undefined): string {
if (!data) {
return '';
}
return data.reduce(
(str, byte) => str + byte.toString(16).padStart(2, '0'),
'',
);
}
/**
* Converts a hexadecimal string to a Uint8Array.
* @param data - The hexadecimal string to convert.
* @returns The Uint8Array.
*/
public static hexToUInt8Array(data: string): Uint8Array {
let hexString = data;
// Remove the '0x' prefix if it exists
if (hexString.startsWith('0x')) {
hexString = hexString.slice(2);
}
// Ensure the hex string has an even length
if (hexString.length % 2 !== 0) {
throw new Error('Invalid hex string');
}
// Convert the hex string to a byte array
const byteArray = new Uint8Array(hexString.length / 2);
for (let i = 0; i < byteArray.length; i++) {
byteArray[i] = parseInt(hexString.substr(i * 2, 2), 16);
}
return byteArray;
}
/**
* Convert a key to a string representation.
* @param key - The key to convert.
* @returns The string representation of the key.
*/
public static keyToString(key: Key | null): string {
if (!key) {
return '';
}
// Convert the key to a protobuf key
const protobufKey: HashgraphProto.proto.IKey = key._toProtobufKey();
// Extract the key value from the protobuf key
if (protobufKey.ed25519) {
return CryptoUtils.uint8ArrayToHex(protobufKey.ed25519);
} else if (protobufKey.ECDSASecp256k1) {
return CryptoUtils.uint8ArrayToHex(protobufKey.ECDSASecp256k1);
} else if (protobufKey.RSA_3072) {
return CryptoUtils.uint8ArrayToHex(protobufKey.RSA_3072);
} else if (protobufKey.ECDSA_384) {
return CryptoUtils.uint8ArrayToHex(protobufKey.ECDSA_384);
} else if (protobufKey.contractID) {
return protobufKey.contractID.contractNum
? protobufKey.contractID.contractNum.toString()
: '';
}
// Handle other types of keys if necessary
return JSON.stringify(protobufKey, (_key, value) => {
if (value instanceof Uint8Array) {
return CryptoUtils.uint8ArrayToHex(value); // Convert Uint8Array to hex string
}
return value;
});
}
}