UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

113 lines (112 loc) 4.63 kB
import { Hash } from './crypto/hash.js'; import { ECDSA } from './crypto/ecdsa.js'; import { Signature } from './crypto/signature.js'; import { PrivateKey } from './privatekey.js'; import { PublicKey } from './publickey.js'; import { Address } from './address.js'; import { JSUtil } from './util/js.js'; import { BufferWriter } from './encoding/bufferwriter.js'; import { Preconditions } from './util/preconditions.js'; export class Message { _message; error; static MAGIC_BYTES = Buffer.from('Bitcoin Signed Message:\n'); constructor(message) { Preconditions.checkArgument(typeof message === 'string', 'First argument should be a string'); this._message = message; } magicHash() { const prefix1 = BufferWriter.varintBufNum(Message.MAGIC_BYTES.length); const messageBuffer = Buffer.from(this._message); const prefix2 = BufferWriter.varintBufNum(messageBuffer.length); const buf = Buffer.concat([ prefix1, Message.MAGIC_BYTES, prefix2, messageBuffer, ]); const hash = Hash.sha256sha256(buf); return hash; } _sign(privateKey) { Preconditions.checkArgument(privateKey instanceof PrivateKey, 'First argument should be an instance of PrivateKey'); const hash = this.magicHash(); const ecdsa = new ECDSA(); ecdsa.hashbuf = hash; ecdsa.privkey = privateKey; ecdsa.pubkey = privateKey.toPublicKey(); ecdsa.signRandomK(); ecdsa.calci(); return ecdsa.sig; } sign(privateKey) { const signature = this._sign(privateKey); return signature.toCompact().toString('base64'); } _verify(publicKey, signature) { Preconditions.checkArgument(publicKey instanceof PublicKey, 'First argument should be an instance of PublicKey'); Preconditions.checkArgument(signature instanceof Signature, 'Second argument should be an instance of Signature'); const hash = this.magicHash(); const verified = ECDSA.verify(hash, signature, publicKey); if (!verified) { this.error = 'The signature was invalid'; } return verified; } verify(bitcoinAddress, signatureString) { Preconditions.checkArgument(!!bitcoinAddress, 'bitcoinAddress is required'); Preconditions.checkArgument(!!(signatureString && typeof signatureString === 'string'), 'signatureString is required'); const address = typeof bitcoinAddress === 'string' ? Address.fromString(bitcoinAddress) : bitcoinAddress; const signature = Signature.fromCompact(Buffer.from(signatureString, 'base64')); const ecdsa = new ECDSA(); ecdsa.hashbuf = this.magicHash(); ecdsa.sig = signature; const publicKey = ecdsa.toPublicKey(); const signatureAddress = Address.fromPublicKey(publicKey, address.network); if (address.toString() !== signatureAddress.toString()) { this.error = 'The signature did not match the message digest'; return false; } return this._verify(publicKey, signature); } recoverPublicKey(bitcoinAddress, signatureString) { Preconditions.checkArgument(!!bitcoinAddress, 'bitcoinAddress is required'); Preconditions.checkArgument(!!(signatureString && typeof signatureString === 'string'), 'signatureString is required'); const address = typeof bitcoinAddress === 'string' ? Address.fromString(bitcoinAddress) : bitcoinAddress; const signature = Signature.fromCompact(Buffer.from(signatureString, 'base64')); const ecdsa = new ECDSA(); ecdsa.hashbuf = this.magicHash(); ecdsa.sig = signature; const publicKey = ecdsa.toPublicKey(); const signatureAddress = Address.fromPublicKey(publicKey, address.network); if (address.toString() !== signatureAddress.toString()) { this.error = 'The signature did not match the message digest'; } return publicKey.toString(); } static fromString(str) { return new Message(str); } static fromJSON(json) { if (typeof json === 'string' && JSUtil.isValidJSON(json)) { json = JSON.parse(json); } return new Message(json.message); } toObject() { return { message: this._message }; } toJSON() { return JSON.stringify(this.toObject()); } toString() { return this._message; } inspect() { return '<Message: ' + this.toString() + '>'; } }