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
JavaScript
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() + '>';
}
}