Home Reference Source

lib/ecc/src/ecsignature.js

import assert from "assert"; // from https://github.com/bitcoinjs/bitcoinjs-lib
import enforceType from "./enforce_types";
import BigInteger from "bigi";
const Buffer = require("safe-buffer").Buffer;

function ECSignature(r, s) {
    enforceType(BigInteger, r);
    enforceType(BigInteger, s);

    this.r = r;
    this.s = s;
}

// Import operations
ECSignature.parseCompact = function(buffer) {
    assert.equal(buffer.length, 65, "Invalid signature length");
    var i = buffer.readUInt8(0) - 27;

    // At most 3 bits
    assert.equal(i, i & 7, "Invalid signature parameter");
    var compressed = !!(i & 4);

    // Recovery param only
    i = i & 3;

    var r = BigInteger.fromBuffer(buffer.slice(1, 33));
    var s = BigInteger.fromBuffer(buffer.slice(33));

    return {
        compressed: compressed,
        i: i,
        signature: new ECSignature(r, s)
    };
};

ECSignature.fromDER = function(buffer) {
    assert.equal(buffer.readUInt8(0), 0x30, "Not a DER sequence");
    assert.equal(
        buffer.readUInt8(1),
        buffer.length - 2,
        "Invalid sequence length"
    );
    assert.equal(buffer.readUInt8(2), 0x02, "Expected a DER integer");

    var rLen = buffer.readUInt8(3);
    assert(rLen > 0, "R length is zero");

    var offset = 4 + rLen;
    assert.equal(buffer.readUInt8(offset), 0x02, "Expected a DER integer (2)");

    var sLen = buffer.readUInt8(offset + 1);
    assert(sLen > 0, "S length is zero");

    var rB = buffer.slice(4, offset);
    var sB = buffer.slice(offset + 2);
    offset += 2 + sLen;

    if (rLen > 1 && rB.readUInt8(0) === 0x00) {
        assert(rB.readUInt8(1) & 0x80, "R value excessively padded");
    }

    if (sLen > 1 && sB.readUInt8(0) === 0x00) {
        assert(sB.readUInt8(1) & 0x80, "S value excessively padded");
    }

    assert.equal(offset, buffer.length, "Invalid DER encoding");
    var r = BigInteger.fromDERInteger(rB);
    var s = BigInteger.fromDERInteger(sB);

    assert(r.signum() >= 0, "R value is negative");
    assert(s.signum() >= 0, "S value is negative");

    return new ECSignature(r, s);
};

// FIXME: 0x00, 0x04, 0x80 are SIGHASH_* boundary constants, importing Transaction causes a circular dependency
ECSignature.parseScriptSignature = function(buffer) {
    var hashType = buffer.readUInt8(buffer.length - 1);
    var hashTypeMod = hashType & ~0x80;

    assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, "Invalid hashType");

    return {
        signature: ECSignature.fromDER(buffer.slice(0, -1)),
        hashType: hashType
    };
};

// Export operations
ECSignature.prototype.toCompact = function(i, compressed) {
    if (compressed) i += 4;
    i += 27;

    var buffer = Buffer.alloc(65);
    buffer.writeUInt8(i, 0);

    this.r.toBuffer(32).copy(buffer, 1);
    this.s.toBuffer(32).copy(buffer, 33);

    return buffer;
};

ECSignature.prototype.toDER = function() {
    var rBa = this.r.toDERInteger();
    var sBa = this.s.toDERInteger();

    var sequence = [];

    // INTEGER
    sequence.push(0x02, rBa.length);
    sequence = sequence.concat(rBa);

    // INTEGER
    sequence.push(0x02, sBa.length);
    sequence = sequence.concat(sBa);

    // SEQUENCE
    sequence.unshift(0x30, sequence.length);

    return Buffer.from(sequence);
};

ECSignature.prototype.toScriptSignature = function(hashType) {
    var hashTypeBuffer = Buffer.alloc(1);
    hashTypeBuffer.writeUInt8(hashType, 0);

    return Buffer.concat([this.toDER(), hashTypeBuffer]);
};

export default ECSignature;