libnexa-ts
Version:
A pure and powerful Nexa SDK library.
152 lines (131 loc) • 5.01 kB
text/typescript
import ValidationUtils from "../utils/validation.utils";
import BufferWriter from "../encoding/bufferwriter";
import Hash from "../crypto/hash";
import PrivateKey from "../keys/privatekey";
import ECDSA from "../crypto/ecdsa";
import Signature from "../crypto/signature";
import PublicKey from "../keys/publickey";
import Address from "./address/address";
import CommonUtils from "../utils/common.utils";
import type { IMessage } from "../common/interfaces";
import BufferUtils from "../utils/buffer.utils";
export default class Message implements IMessage {
public static readonly MAGIC_BYTES = BufferUtils.utf8ToBuffer('Bitcoin Signed Message:\n');
message: string;
error?: string;
constructor(message: string) {
ValidationUtils.validateArgument(typeof message === 'string', 'First argument should be a string');
this.message = message;
}
/**
* Will sign a message with a given private key.
*
* @param privateKey An instance of PrivateKey
* @returns A base64 encoded compact signature
*/
public sign(privateKey: PrivateKey): string {
let signature = this._sign(privateKey);
return BufferUtils.bufferToBase64(signature.toCompact());
}
/**
* Will return a boolean of the signature is valid for a given nexa address.
* If it isn't valid, the specific reason is accessible via the "error" member.
*
* @param nexaAddress A nexa address
* @param signatureString A base64 encoded compact signature
*/
public verify(nexaAddress: Address | string, signatureString: string): boolean {
ValidationUtils.validateArgument(nexaAddress != null, 'nexaAddress');
ValidationUtils.validateArgument(typeof signatureString === 'string' && signatureString.length > 0, 'signatureString');
if (typeof nexaAddress === 'string') {
nexaAddress = Address.fromString(nexaAddress);
}
let signature = Signature.fromCompact(BufferUtils.base64ToBuffer(signatureString));
// recover the public key
let ecdsa = new ECDSA();
ecdsa.hashbuf = this._magicHash();
ecdsa.sig = signature;
let publicKey = ecdsa.toPublicKey();
let signatureAddress = Address.fromPublicKey(publicKey, nexaAddress.network, nexaAddress.type);
// check that the recovered address and specified address match
if (nexaAddress.toString() !== signatureAddress.toString()) {
this.error = 'The signature did not match the message digest';
return false;
}
return this._verify(publicKey, signature);
}
private _sign(privateKey: PrivateKey): Signature {
ValidationUtils.validateArgument(privateKey instanceof PrivateKey, 'First argument should be an instance of PrivateKey');
let hash = this._magicHash();
let ecdsa = new ECDSA();
ecdsa.hashbuf = hash;
ecdsa.privkey = privateKey;
ecdsa.pubkey = privateKey.toPublicKey();
ecdsa.signRandomK();
ecdsa.calcI();
return ecdsa.sig as Signature;
}
private _magicHash(): Uint8Array {
let prefix1 = BufferWriter.varintBufNum(Message.MAGIC_BYTES.length);
let messageBuffer = BufferUtils.utf8ToBuffer(this.message);
let prefix2 = BufferWriter.varintBufNum(messageBuffer.length);
let buf = BufferUtils.concat([prefix1, Message.MAGIC_BYTES, prefix2, messageBuffer]);
let hash = Hash.sha256sha256(buf);
return hash;
}
private _verify(publicKey: PublicKey, signature: Signature): boolean {
ValidationUtils.validateArgument(publicKey instanceof PublicKey, 'First argument should be an instance of PublicKey');
ValidationUtils.validateArgument(signature instanceof Signature, 'Second argument should be an instance of Signature');
let hash = this._magicHash();
let verified = ECDSA.verify(hash, signature, publicKey);
if (!verified) {
this.error = 'The signature was invalid';
}
return verified;
}
/**
* Instantiate a message from a message string
*
* @param str A string of the message
* @returns A new instance of a Message
*/
public static fromString(str: string): Message {
return new Message(str);
}
/**
* Instantiate a message from JSON
*
* @param json An JSON string or Object with keys: message
* @returns A new instance of a Message
*/
public static fromJSON(json: string | IMessage): Message {
if (CommonUtils.isValidJSON(json as string)) {
json = JSON.parse(json as string);
}
return new Message((json as IMessage).message);
}
/**
* @returns A plain object with the message information
*/
public toObject(): IMessage {
return { message: this.message };
}
/**
* @returns A JSON representation as string of the message information
*/
public toJSON(): string {
return JSON.stringify(this.toObject());
}
/**
* Will return a the string representation of the message
*/
public toString(): string {
return this.message;
}
/**
* Will return a string formatted for the console
*/
public inspect(): string {
return `<Message: ${this}>`;
}
}