@airgap/coinlib-core
Version:
The @airgap/coinlib-core is a protocol agnostic library to prepare, sign and broadcast cryptocurrency transactions.
158 lines (144 loc) • 4.25 kB
JavaScript
const bs58check = require('../bs58check-2.1.2/index')
const bech32 = require('../bech32-1.1.3/index')
const bufferEquals = require('../buffer-equals-1.0.4/index')
const createHash = require('../create-hash-1.2.0/browser')
const secp256k1 = require('../secp256k1-3.7.1/elliptic')
const varuint = require('../varuint-bitcoin-1.1.2/index')
const SEGWIT_TYPES = {
P2WPKH: 'p2wpkh',
P2SH_P2WPKH: 'p2sh(p2wpkh)'
}
function sha256 (b) {
return createHash('sha256')
.update(b)
.digest()
}
function hash256 (buffer) {
return sha256(sha256(buffer))
}
function hash160 (buffer) {
return createHash('ripemd160')
.update(sha256(buffer))
.digest()
}
function encodeSignature (signature, recovery, compressed, segwitType) {
if (segwitType !== undefined) {
recovery += 8
if (segwitType === SEGWIT_TYPES.P2WPKH) recovery += 4
} else {
if (compressed) recovery += 4
}
return Buffer.concat([Buffer.alloc(1, recovery + 27), signature])
}
function decodeSignature (buffer) {
if (buffer.length !== 65) throw new Error('Invalid signature length')
const flagByte = buffer.readUInt8(0) - 27
if (flagByte > 15 || flagByte < 0) {
throw new Error('Invalid signature parameter')
}
return {
compressed: !!(flagByte & 12),
segwitType: !(flagByte & 8)
? null
: !(flagByte & 4)
? SEGWIT_TYPES.P2SH_P2WPKH
: SEGWIT_TYPES.P2WPKH,
recovery: flagByte & 3,
signature: buffer.slice(1)
}
}
function magicHash (message, messagePrefix) {
messagePrefix = messagePrefix || '\u0018Bitcoin Signed Message:\n'
if (!Buffer.isBuffer(messagePrefix)) {
messagePrefix = Buffer.from(messagePrefix, 'utf8')
}
if (!Buffer.isBuffer(message)) {
message = Buffer.from(message, 'utf8')
}
const messageVISize = varuint.encodingLength(message.length)
const buffer = Buffer.allocUnsafe(
messagePrefix.length + messageVISize + message.length
)
messagePrefix.copy(buffer, 0)
varuint.encode(message.length, buffer, messagePrefix.length)
message.copy(buffer, messagePrefix.length + messageVISize)
return hash256(buffer)
}
function sign (
message,
privateKey,
compressed,
messagePrefix,
sigOptions
) {
if (typeof messagePrefix === 'object' && sigOptions === undefined) {
sigOptions = messagePrefix
messagePrefix = undefined
}
let { segwitType, extraEntropy } = sigOptions || {}
if (
segwitType &&
(typeof segwitType === 'string' || segwitType instanceof String)
) {
segwitType = segwitType.toLowerCase()
}
if (
segwitType &&
segwitType !== SEGWIT_TYPES.P2SH_P2WPKH &&
segwitType !== SEGWIT_TYPES.P2WPKH
) {
throw new Error(
'Unrecognized segwitType: use "' +
SEGWIT_TYPES.P2SH_P2WPKH +
'" or "' +
SEGWIT_TYPES.P2WPKH +
'"'
)
}
const hash = magicHash(message, messagePrefix)
const sigObj = secp256k1.sign(hash, privateKey, { data: extraEntropy })
return encodeSignature(
sigObj.signature,
sigObj.recovery,
compressed,
segwitType
)
}
function verify (message, address, signature, messagePrefix) {
if (!Buffer.isBuffer(signature)) signature = Buffer.from(signature, 'base64')
const parsed = decodeSignature(signature)
const hash = magicHash(message, messagePrefix)
const publicKey = secp256k1.recover(
hash,
parsed.signature,
parsed.recovery,
parsed.compressed
)
const publicKeyHash = hash160(publicKey)
let actual, expected
if (parsed.segwitType) {
if (parsed.segwitType === SEGWIT_TYPES.P2SH_P2WPKH) {
const redeemScript = Buffer.concat([
Buffer.from('0014', 'hex'),
publicKeyHash
])
const redeemScriptHash = hash160(redeemScript)
actual = redeemScriptHash
expected = bs58check.decode(address).slice(1)
} else if (parsed.segwitType === SEGWIT_TYPES.P2WPKH) {
const result = bech32.decode(address)
const data = bech32.fromWords(result.words.slice(1))
actual = publicKeyHash
expected = Buffer.from(data)
}
} else {
actual = publicKeyHash
expected = bs58check.decode(address).slice(1)
}
return bufferEquals(actual, expected)
}
module.exports = {
magicHash: magicHash,
sign: sign,
verify: verify
}