112 lines
3.8 KiB
TypeScript
112 lines
3.8 KiB
TypeScript
|
// 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
|
||
|
|
||
|
export function check(buffer: Buffer): boolean {
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
export function decode(buffer: Buffer): { r: Buffer; s: 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),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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
|
||
|
*/
|
||
|
export function encode(r: Buffer, s: Buffer): Buffer {
|
||
|
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 = Buffer.allocUnsafe(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;
|
||
|
r.copy(signature, 4);
|
||
|
signature[4 + lenR] = 0x02;
|
||
|
signature[5 + lenR] = s.length;
|
||
|
s.copy(signature, 6 + lenR);
|
||
|
|
||
|
return signature;
|
||
|
}
|