@scrypt-inc/bitcoinjs-lib
Version:
Client-side Bitcoin JavaScript library
181 lines (180 loc) • 6.3 kB
JavaScript
"use strict";
// Reference https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
// Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
// NOTE: SIGHASH byte ignored AND restricted, truncate before use
Object.defineProperty(exports, "__esModule", { value: true });
exports.check = check;
exports.decode = decode;
exports.parseDER = parseDER;
exports.encode = encode;
/**
* Checks if the given buffer is a valid BIP66-encoded signature.
*
* @param buffer - The buffer to check.
* @returns A boolean indicating whether the buffer is a valid BIP66-encoded signature.
*/
function check(buffer) {
if (buffer.length < 8)
return false;
if (buffer.length > 72)
return false;
if (buffer[0] !== 0x30)
return false;
if (buffer[1] !== buffer.length - 2)
return false;
if (buffer[2] !== 0x02)
return false;
const lenR = buffer[3];
if (lenR === 0)
return false;
if (5 + lenR >= buffer.length)
return false;
if (buffer[4 + lenR] !== 0x02)
return false;
const lenS = buffer[5 + lenR];
if (lenS === 0)
return false;
if (6 + lenR + lenS !== buffer.length)
return false;
if (buffer[4] & 0x80)
return false;
if (lenR > 1 && buffer[4] === 0x00 && !(buffer[5] & 0x80))
return false;
if (buffer[lenR + 6] & 0x80)
return false;
if (lenS > 1 && buffer[lenR + 6] === 0x00 && !(buffer[lenR + 7] & 0x80))
return false;
return true;
}
/**
* Decodes a DER-encoded signature buffer and returns the R and S values.
* @param buffer - The DER-encoded signature buffer.
* @returns An object containing the R and S values.
* @throws {Error} If the DER sequence length is too short, too long, or invalid.
* @throws {Error} If the R or S length is zero or invalid.
* @throws {Error} If the R or S value is negative or excessively padded.
*/
function decode(buffer) {
if (buffer.length < 8)
throw new Error('DER sequence length is too short');
if (buffer.length > 72)
throw new Error('DER sequence length is too long');
if (buffer[0] !== 0x30)
throw new Error('Expected DER sequence');
if (buffer[1] !== buffer.length - 2)
throw new Error('DER sequence length is invalid');
if (buffer[2] !== 0x02)
throw new Error('Expected DER integer');
const lenR = buffer[3];
if (lenR === 0)
throw new Error('R length is zero');
if (5 + lenR >= buffer.length)
throw new Error('R length is too long');
if (buffer[4 + lenR] !== 0x02)
throw new Error('Expected DER integer (2)');
const lenS = buffer[5 + lenR];
if (lenS === 0)
throw new Error('S length is zero');
if (6 + lenR + lenS !== buffer.length)
throw new Error('S length is invalid');
if (buffer[4] & 0x80)
throw new Error('R value is negative');
if (lenR > 1 && buffer[4] === 0x00 && !(buffer[5] & 0x80))
throw new Error('R value excessively padded');
if (buffer[lenR + 6] & 0x80)
throw new Error('S value is negative');
if (lenS > 1 && buffer[lenR + 6] === 0x00 && !(buffer[lenR + 7] & 0x80))
throw new Error('S value excessively padded');
// non-BIP66 - extract R, S values
return {
r: buffer.slice(4, 4 + lenR),
s: buffer.slice(6 + lenR),
};
}
/**
* In order to mimic the non-strict DER encoding of OpenSSL, set strict = false.
*/
function parseDER(buf, strict = false) {
const header = buf[0];
if (!(header === 0x30))
throw new Error('Header byte should be 0x30');
let length = buf[1];
const buflength = buf.slice(2).length;
if (!(!strict || length === buflength))
throw new Error('Length byte should length of what follows');
length = length < buflength ? length : buflength;
const rheader = buf[2 + 0];
if (!(rheader === 0x02))
throw new Error('Integer byte for r should be 0x02');
const rlength = buf[2 + 1];
const rbuf = buf.slice(2 + 2, 2 + 2 + rlength);
if (!(rlength === rbuf.length))
throw new Error('Length of r incorrect');
const sheader = buf[2 + 2 + rlength + 0];
if (!(sheader === 0x02))
throw new Error('Integer byte for s should be 0x02');
const slength = buf[2 + 2 + rlength + 1];
const sbuf = buf.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength);
if (!(slength === sbuf.length))
throw new Error('Length of s incorrect');
const sumlength = 2 + 2 + rlength + 2 + slength;
if (!(length === sumlength - 2))
throw new Error('Length of signature incorrect');
return {
r: rbuf,
s: sbuf,
};
}
/*
* Expects r and s to be positive DER integers.
*
* The DER format uses the most significant bit as a sign bit (& 0x80).
* If the significant bit is set AND the integer is positive, a 0x00 is prepended.
*
* Examples:
*
* 0 => 0x00
* 1 => 0x01
* -1 => 0xff
* 127 => 0x7f
* -127 => 0x81
* 128 => 0x0080
* -128 => 0x80
* 255 => 0x00ff
* -255 => 0xff01
* 16300 => 0x3fac
* -16300 => 0xc054
* 62300 => 0x00f35c
* -62300 => 0xff0ca4
*/
function encode(r, s) {
const lenR = r.length;
const lenS = s.length;
if (lenR === 0)
throw new Error('R length is zero');
if (lenS === 0)
throw new Error('S length is zero');
if (lenR > 33)
throw new Error('R length is too long');
if (lenS > 33)
throw new Error('S length is too long');
if (r[0] & 0x80)
throw new Error('R value is negative');
if (s[0] & 0x80)
throw new Error('S value is negative');
if (lenR > 1 && r[0] === 0x00 && !(r[1] & 0x80))
throw new Error('R value excessively padded');
if (lenS > 1 && s[0] === 0x00 && !(s[1] & 0x80))
throw new Error('S value excessively padded');
const signature = new Uint8Array(6 + lenR + lenS);
// 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
signature[0] = 0x30;
signature[1] = signature.length - 2;
signature[2] = 0x02;
signature[3] = r.length;
signature.set(r, 4);
signature[4 + lenR] = 0x02;
signature[5 + lenR] = s.length;
signature.set(s, 6 + lenR);
return signature;
}