Home Reference Source

lib/ecc/src/signature.js

import {sign, recoverPubKey, verify, calcPubKeyRecoveryParam} from "./ecdsa";
import {sha256} from "./hash";
import {getCurveByName} from "ecurve";
var secp256k1 = getCurveByName("secp256k1");
import assert from "assert";
import BigInteger from "bigi";
import PublicKey from "./PublicKey";
const Buffer = require("safe-buffer").Buffer;

class Signature {
    constructor(r1, s1, i1) {
        this.r = r1;
        this.s = s1;
        this.i = i1;
        assert.equal(this.r != null, true, "Missing parameter");
        assert.equal(this.s != null, true, "Missing parameter");
        assert.equal(this.i != null, true, "Missing parameter");
    }

    static fromBuffer(buf) {
        var i, r, s;
        assert.equal(buf.length, 65, "Invalid signature length");
        i = buf.readUInt8(0);
        assert.equal(i - 27, (i - 27) & 7, "Invalid signature parameter");
        r = BigInteger.fromBuffer(buf.slice(1, 33));
        s = BigInteger.fromBuffer(buf.slice(33));
        return new Signature(r, s, i);
    }

    toBuffer() {
        var buf;
        buf = Buffer.alloc(65);
        buf.writeUInt8(this.i, 0);
        this.r.toBuffer(32).copy(buf, 1);
        this.s.toBuffer(32).copy(buf, 33);
        return buf;
    }

    recoverPublicKeyFromBuffer(buffer) {
        return this.recoverPublicKey(sha256(buffer));
    }

    /**
        @return {PublicKey}
    */
    recoverPublicKey(sha256_buffer) {
        let Q, e, i;
        e = BigInteger.fromBuffer(sha256_buffer);
        i = this.i;
        i -= 27;
        i = i & 3;
        Q = recoverPubKey(secp256k1, e, this, i);
        return PublicKey.fromPoint(Q);
    }

    /**
        @param {Buffer} buf
        @param {PrivateKey} private_key
        @return {Signature}
    */
    static signBuffer(buf, private_key) {
        var _hash = sha256(buf);
        return Signature.signBufferSha256(_hash, private_key);
    }

    /** Sign a buffer of exactally 32 bytes in size (sha256(text))
        @param {Buffer} buf - 32 bytes binary
        @param {PrivateKey} private_key
        @return {Signature}
    */
    static signBufferSha256(buf_sha256, private_key) {
        if (buf_sha256.length !== 32 || !Buffer.isBuffer(buf_sha256))
            throw new Error("buf_sha256: 32 byte buffer requred");
        var der, e, ecsignature, i, lenR, lenS, nonce;
        i = null;
        nonce = 0;
        e = BigInteger.fromBuffer(buf_sha256);
        while (true) {
            ecsignature = sign(secp256k1, buf_sha256, private_key.d, nonce++);
            der = ecsignature.toDER();
            lenR = der[3];
            lenS = der[5 + lenR];
            if (lenR === 32 && lenS === 32) {
                i = calcPubKeyRecoveryParam(
                    secp256k1,
                    e,
                    ecsignature,
                    private_key.toPublicKey().Q
                );
                i += 4; // compressed
                i += 27; // compact  //  24 or 27 :( forcing odd-y 2nd key candidate)
                break;
            }
            if (nonce % 10 === 0) {
                console.log(
                    "WARN: " + nonce + " attempts to find canonical signature"
                );
            }
        }
        return new Signature(ecsignature.r, ecsignature.s, i);
    }

    static sign(string, private_key) {
        return Signature.signBuffer(Buffer.from(string), private_key);
    }

    /**
        @param {Buffer} un-hashed
        @param {./PublicKey}
        @return {boolean}
    */
    verifyBuffer(buf, public_key) {
        var _hash = sha256(buf);
        return this.verifyHash(_hash, public_key);
    }

    verifyHash(hash, public_key) {
        assert.equal(
            hash.length,
            32,
            "A SHA 256 should be 32 bytes long, instead got " + hash.length
        );
        return verify(
            secp256k1,
            hash,
            {
                r: this.r,
                s: this.s
            },
            public_key.Q
        );
    }

    /* <HEX> */

    toByteBuffer() {
        var b;
        b = new ByteBuffer(
            ByteBuffer.DEFAULT_CAPACITY,
            ByteBuffer.LITTLE_ENDIAN
        );
        this.appendByteBuffer(b);
        return b.copy(0, b.offset);
    }

    static fromHex(hex) {
        return Signature.fromBuffer(Buffer.from(hex, "hex"));
    }

    toHex() {
        return this.toBuffer().toString("hex");
    }

    static signHex(hex, private_key) {
        var buf;
        buf = Buffer.from(hex, "hex");
        return Signature.signBuffer(buf, private_key);
    }

    verifyHex(hex, public_key) {
        var buf;
        buf = Buffer.from(hex, "hex");
        return this.verifyBuffer(buf, public_key);
    }
}

export default Signature;