PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/@vechain/sdk-core/src/certificate
Просмотр файла: Certificate.ts
import fastJsonStableStringify from 'fast-json-stable-stringify';
import { Address, Blake2b256, HexUInt, Txt } from '../vcdm';
import { Secp256k1 } from '../secp256k1';
import {
CertificateSignatureMismatch,
InvalidDataType
} from '@vechain/sdk-errors';
import { type CertificateData } from './CertificateData';
/**
* The Certificate class provides functionality to create, sign, and verify certificates.
* It implements the CertificateData interface.
*
* @remarks
* The properties of those class are immutable, except {@link signature},
* because properties are part of the {@link signature} computation.
* The signature is used of extract and match the {@link signer}.
* The fact the properties are immutable assure is not possible to create
* an object tampering properties and carry on the legitimate signature and
* signer address of the object before tampering to make tampered content
* to result in a validated certificate.
*
* @remarks
* Classes extending {@link Certificate} should expose immutable properties.
*
* @remarks
* This class implementation supports {@link signer}
* [mixed-case checksum address encoding](https://eips.ethereum.org/EIPS/eip-55).
*
* @implements CertificateData
*/
class Certificate implements CertificateData {
/**
* Return the intended use or context of the certificate.
*/
readonly purpose: string;
/**
* Returns the content of the certificate.
*/
readonly payload: {
/**
* Return the description of the type of content.
*/
readonly type: string;
/**
* Return the content serialized as a string.
*/
readonly content: string;
};
/**
* Return the description of the context of validity of this certificate.
*/
readonly domain: string;
/**
* The value expressed as of milliseconds elapsed since the
* [epoch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#the_epoch_timestamps_and_invalid_date),
* when the certificate was issued.
*
* @remarks
* The value is a natural number in the safe integer range of JS `number` type.
*/
readonly timestamp: number;
/**
* Return the address of the entity signed the certificate, as
* a lowercase hexadecimal expression prefixed by `0x`.
*
* @remarks
* Normalized lowercase prefixed expression is needed because
* the content of this property is part of the {@signature} computation:
* certificates made from checksum case address of the signer should
* result valid as the certificate made from the same signer address
* not checksum case.
*/
readonly signer: string;
/**
* Return the signature computed evaluating the properties of this object
* and the private key of the signer.
*
* @remarks
* The signature is a lowercase hexadecimal expression prefixed with `0x`.
*/
signature?: string;
/**
* Returns a new instance of this class assuring the formal validity of the
* arguments used to build the object.
*
* @param {string} purpose - The purpose of the certificate.
* @param {Object} payload - The payload containing type and content.
* @param {string} payload.type - The type of the payload.
* @param {string} payload.content - The content of the payload.
* @param {string} domain - The domain associated with the certificate.
* @param {number} timestamp - The time at which the certificate is created;
* must be a positive safe integer.
* @param {string} signer - The signer of the certificate;
* must be a valid address.
* @param {string|undefined} [signature] - The signature of the certificate;
* optional parameter.
*
* @throws {InvalidDataType} If timestamp is not a positive safe integer.
* @throws {InvalidDataType} If signer is not a valid address.
* @throws {InvalidDataType} If signature is invalid.
*
* @remarks
* The `signer` address is represented lowercase and `0x` prefixed.
*/
protected constructor(
purpose: string,
payload: { type: string; content: string },
domain: string,
timestamp: number,
signer: string,
signature?: string
) {
if (Number.isSafeInteger(timestamp) && timestamp >= 0) {
if (Address.isValid(signer)) {
this.purpose = purpose;
this.payload = payload;
this.domain = domain;
this.timestamp = timestamp;
this.signer = signer.toString().toLowerCase();
try {
this.signature =
typeof signature === 'string'
? HexUInt.of(signature).alignToBytes().toString()
: signature;
} catch (e) {
throw new InvalidDataType(
'Certificate.constructor',
'invalid signature',
{ signature },
e
);
}
} else
throw new InvalidDataType(
'Certificate.constructor',
'signer is not an address',
{ signer }
);
} else
throw new InvalidDataType(
'Certificate.constructor',
'not positive safe integer timestamp',
{ timestamp }
);
}
/**
* Encodes a given object into a Uint8Array representation
* applying the following operation to normalize the content:
* - the properties are sorted in ascending alphabetic order;
* - the key/value properties are delimited with `"` when serialized as JSON
* before to be encoded as bytes;
* - any not meaningful blank characters are ignored;
* - the JSON representation of this object is byte encoded using the UTF-8
* [normalization form for canonical composition](https://en.wikipedia.org/wiki/Unicode_equivalence#Normal_forms).
*
* @param {unknown} object - The input object to be encoded.
* @return {Uint8Array} The encoded Uint8Array representation of the input object.
*/
protected static encode(object: unknown): Uint8Array {
return Txt.of(fastJsonStableStringify(object)).bytes;
}
/**
* Encodes the current certificate instance into a Uint8Array representation.
*
* @remarks
* This method normalizes the content by:
* - Sorting the properties in ascending alphabetic order.
* - Delimiting key/value properties with `"` when serialized as JSON before encoding as bytes.
* - Ignoring any not meaningful blank characters.
* - Using the UTF-8 normalization form for canonical composition for byte encoding.
*
* @return {Uint8Array} The encoded Uint8Array representation of the current certificate instance.
*/
public encode(): Uint8Array {
return Certificate.encode({ ...this, signature: undefined });
}
/**
* Return `true` if the current instance has a signature.
*
* @return {boolean} `true` if the signature is a valid hexadecimal string,
* otherwise `false`.
*/
public isSigned(): boolean {
return (
typeof this.signature === 'string' &&
HexUInt.isValid(this.signature)
);
}
/**
* Creates a new Certificate instance from the provided CertificateData.
*
* @param {CertificateData} data - The data required to create the Certificate.
* @return {Certificate} A new Certificate instance.
* @throws {InvalidDataType} If the provided data is invalid:
* - if timestamp is not a positive safe integer;
* - if signer is not a valid address;
* - if signature is an invalid hexadecimal expression.
*
* @remarks
* This method supports {@link signer}
* [mixed-case checksum address encoding](https://eips.ethereum.org/EIPS/eip-55).
*
* @see constructor
*/
public static of(data: CertificateData): Certificate {
try {
return new Certificate(
data.purpose,
data.payload,
data.domain,
data.timestamp,
data.signer,
data.signature
);
} catch (e) {
throw new InvalidDataType(
'Certificate.of',
'invalid certificate data',
{ certifiable: data },
e
);
}
}
/**
* Signs the current object using a given private key.
*
* The {@link signature} is computed encoding this object according
* the following normalization rules:
* - the {@link signature} property is ignored, because its value
* is the result of this method.
* - the properties are sorted in ascending alphabetic order;
* - the key/value properties are delimited with `"` when serialized as JSON
* before to be encoded as bytes;
* - any not meaningful blank characters are ignored;
* - the JSON representation of this object is byte encoded using the UTF-8
* [normalization form for canonical composition](https://en.wikipedia.org/wiki/Unicode_equivalence#Normal_forms).
*
* @param {Uint8Array} privateKey - The private key used for signing.
* @return {this} The current instance after signing.
*
* @throws {InvalidOperation} - If a hash error occurs.
* @throws {InvalidSecp256k1PrivateKey} - If the private key is not a valid 32-byte private key.
*
* @remarks Security auditable method, depends on
* * {@link Blake2b256.of};
* * {@link Secp256k1.sign}.
*
* @see encode
* @see verify
*/
public sign(privateKey: Uint8Array): this {
this.signature = undefined;
this.signature = HexUInt.of(
Secp256k1.sign(
Blake2b256.of(Certificate.encode(this)).bytes,
privateKey
)
).toString();
return this;
}
/**
* Verifies the certificate by checking its signature.
*
* @throws {CertificateSignatureMismatch} if the certificate
* - is not signed, or
* - the signature does not match the signer's public key.
*
* @remarks
* This method supports {@link signer}
* [mixed-case checksum address encoding](https://eips.ethereum.org/EIPS/eip-55).
*
* @remarks Security auditable method, depends on
* * {@link Blake2b256.of};
* * {@link Secp256k1.recover}.
*/
public verify(): void {
if (!this.isSigned())
throw new CertificateSignatureMismatch(
'Certificate.verify',
'signature missing',
{ certificate: this }
);
const signer = Address.ofPublicKey(
Secp256k1.recover(
Blake2b256.of(
Certificate.encode({ ...this, signature: undefined })
).bytes,
HexUInt.of(this.signature as string).bytes
)
);
if (signer.toString().toLowerCase() !== this.signer)
throw new CertificateSignatureMismatch(
'Certificate.verify',
"signature doesn't match with signer's public key",
{ certificate: this }
);
}
}
export { Certificate };
Выполнить команду
Для локальной разработки. Не используйте в интернете!