Skip to content

Bg 5070 signature hash #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"bigi": "^1.4.0",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.3.0",
"blake2b": "^2.1.2",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
Expand Down
40 changes: 40 additions & 0 deletions src/bufferWriter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
var Buffer = require('safe-buffer').Buffer
var bufferutils = require('./bufferutils')
var varuint = require('varuint-bitcoin')

function BufferWriter (bufferSize) {
this.buffer = Buffer.allocUnsafe(bufferSize)
this.offset = 0
}

BufferWriter.prototype.getBuffer = function () {
return this.buffer
}

BufferWriter.prototype.writeSlice = function (slice) {
this.offset += slice.copy(this.buffer, this.offset)
}

BufferWriter.prototype.writeInt32 = function (input) {
this.offset = this.buffer.writeInt32LE(input, this.offset)
}

BufferWriter.prototype.writeUInt32 = function (input) {
this.offset = this.buffer.writeUInt32LE(input, this.offset)
}

BufferWriter.prototype.writeUInt64 = function (input) {
this.offset = bufferutils.writeUInt64LE(this.buffer, input, this.offset)
}

BufferWriter.prototype.writeVarInt = function (input) {
varuint.encode(input, this.buffer, this.offset)
this.offset += varuint.encode.bytes
}

BufferWriter.prototype.writeVarSlice = function (slice) {
this.writeVarInt(slice.length)
this.writeSlice(slice)
}

module.exports = BufferWriter
224 changes: 169 additions & 55 deletions src/transaction.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var Buffer = require('safe-buffer').Buffer
var BufferWriter = require('./bufferWriter')
var bcrypto = require('./crypto')
var bscript = require('./script')
var bufferutils = require('./bufferutils')
Expand All @@ -7,6 +8,7 @@ var opcodes = require('bitcoin-ops')
var typeforce = require('typeforce')
var types = require('./types')
var varuint = require('varuint-bitcoin')
var blake2b = require('blake2b')

function varSliceSize (someScript) {
var length = someScript.length
Expand All @@ -22,12 +24,13 @@ function vectorSize (someVector) {
}, 0)
}

function Transaction () {
// By default, assume is a bitcoin transaction
function Transaction (coin = coins.BTC) {
this.version = 1
this.locktime = 0
this.ins = []
this.outs = []
this.coin = coins.BTC // By default, assume is a bitcoin transaction
this.coin = coin
// ZCash version >= 2
this.joinsplits = []
this.joinsplitPubkey = []
Expand Down Expand Up @@ -324,7 +327,7 @@ Transaction.prototype.byteLength = function () {
}

Transaction.prototype.joinsplitByteLength = function () {
if (!this.fOverwintered && this.version < 2) {
if (!this.overwintered && this.version < 2) {
return 0
}

Expand Down Expand Up @@ -418,6 +421,16 @@ Transaction.prototype.clone = function () {
return newTx
}

/**
* Get Zcash header or version
* @returns {number}
*/
Transaction.prototype.getHeader = function () {
var mask = (this.overwintered ? 1 : 0)
var header = this.version | (mask << 31)
return header
}

/**
* Hash transaction for signing a specific input.
*
Expand Down Expand Up @@ -491,90 +504,191 @@ Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashT
return bcrypto.hash256(buffer)
}

Transaction.prototype.hashForWitnessV0 = function (inIndex, prevOutScript, value, hashType) {
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments)

var tbuffer, toffset
function writeSlice (slice) { toffset += slice.copy(tbuffer, toffset) }
function writeUInt32 (i) { toffset = tbuffer.writeUInt32LE(i, toffset) }
function writeUInt64 (i) { toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) }
function writeVarInt (i) {
varuint.encode(i, tbuffer, toffset)
toffset += varuint.encode.bytes
}
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) }

var hashOutputs = ZERO
var hashPrevouts = ZERO
var hashSequence = ZERO
/**
* Blake2b hashing algorithm for Zcash
* @param bufferToHash
* @param personalization
* @returns 256-bit BLAKE2b hash
*/
Transaction.prototype.getBlake2bHash = function(bufferToHash, personalization) {
var out = Buffer.allocUnsafe(32)
return blake2b(out.length, null, null, Buffer.from(personalization)).update(bufferToHash).digest(out)
}

/**
* Build a hash for all or none of the transaction inputs depending on the hashtype
* @param hashType
* @returns double SHA-256, 256-bit BLAKE2b hash or 256-bit zero if doesn't apply
*/
Transaction.prototype.getPrevoutHash = function (hashType) {
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
tbuffer = Buffer.allocUnsafe(36 * this.ins.length)
toffset = 0
var bufferWriter = new BufferWriter(36 * this.ins.length)

this.ins.forEach(function (txIn) {
writeSlice(txIn.hash)
writeUInt32(txIn.index)
bufferWriter.writeSlice(txIn.hash)
bufferWriter.writeUInt32(txIn.index)
})

hashPrevouts = bcrypto.hash256(tbuffer)
if (coins.isZcash(this.coin)) {
return this.getBlake2bHash(bufferWriter.getBuffer(), 'ZcashPrevoutHash')
}
return bcrypto.hash256(bufferWriter.getBuffer())
}
return ZERO
}

/**
* Build a hash for all or none of the transactions inputs sequence numbers depending on the hashtype
* @param hashType
* @returns double SHA-256, 256-bit BLAKE2b hash or 256-bit zero if doesn't apply
*/
Transaction.prototype.getSequenceHash = function (hashType) {
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
tbuffer = Buffer.allocUnsafe(4 * this.ins.length)
toffset = 0
var bufferWriter = new BufferWriter(4 * this.ins.length)

this.ins.forEach(function (txIn) {
writeUInt32(txIn.sequence)
bufferWriter.writeUInt32(txIn.sequence)
})

hashSequence = bcrypto.hash256(tbuffer)
if (coins.isZcash(this.coin)) {
return this.getBlake2bHash(bufferWriter.getBuffer(), 'ZcashSequencHash')
}
return bcrypto.hash256(bufferWriter.getBuffer())
}
return ZERO
}

if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
/**
* Build a hash for one, all or none of the transaction outputs depending on the hashtype
* @param hashType
* @param inIndex
* @returns double SHA-256, 256-bit BLAKE2b hash or 256-bit zero if doesn't apply
*/
Transaction.prototype.getOutputsHash = function (hashType, inIndex) {
var bufferWriter
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && (hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
// Find out the size of the outputs and write them
var txOutsSize = this.outs.reduce(function (sum, output) {
return sum + 8 + varSliceSize(output.script)
}, 0)

tbuffer = Buffer.allocUnsafe(txOutsSize)
toffset = 0
bufferWriter = new BufferWriter(txOutsSize)

this.outs.forEach(function (out) {
writeUInt64(out.value)
writeVarSlice(out.script)
bufferWriter.writeUInt64(out.value)
bufferWriter.writeVarSlice(out.script)
})

hashOutputs = bcrypto.hash256(tbuffer)
if (coins.isZcash(this.coin)) {
return this.getBlake2bHash(bufferWriter.getBuffer(), 'ZcashOutputsHash')
}
return bcrypto.hash256(bufferWriter.getBuffer())
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) {
// Write only the output specified in inIndex
var output = this.outs[inIndex]

tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script))
toffset = 0
writeUInt64(output.value)
writeVarSlice(output.script)
bufferWriter = new BufferWriter(8 + varSliceSize(output.script))
bufferWriter.writeUInt64(output.value)
bufferWriter.writeVarSlice(output.script)

if (coins.isZcash(this.coin)) {
return this.getBlake2bHash(bufferWriter.getBuffer(), 'ZcashOutputsHash')
}
return bcrypto.hash256(bufferWriter.getBuffer())
}
return ZERO
}

/**
* Hash transaction for signing a transparent transaction in Zcash. Protected transactions are not supported.
* @param inIndex
* @param prevOutScript
* @param value
* @param hashType
* @param consensusBranchId
* @returns double SHA-256 or 256-bit BLAKE2b hash
*/
Transaction.prototype.hashForZcashSignature = function (inIndex, prevOutScript, value, hashType, consensusBranchId) {
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments)
if (this.joinsplits.length > 0) {
throw new Error('Hash signature for Zcash protected transactions is not supported')
}

if (inIndex >= this.ins.length && inIndex !== VALUE_UINT64_MAX) {
throw new Error('Input index is out of range')
}

if (this.version >= Transaction.ZCASH_OVERWINTER_VERSION) {
var hashPrevouts = this.getPrevoutHash(hashType)
var hashSequence = this.getSequenceHash(hashType)
var hashOutputs = this.getOutputsHash(hashType, inIndex)
var hashJoinSplits = ZERO

var bufferWriter
var baseBufferSize = 4 * 5 + 32 * 4
if (inIndex !== VALUE_UINT64_MAX) {
// If this hash is for a transparent input signature (i.e. not for txTo.joinSplitSig), we need extra space
bufferWriter = new BufferWriter(baseBufferSize + 4 * 4 + 32 + varSliceSize(prevOutScript))
} else {
bufferWriter = new BufferWriter(baseBufferSize)
}

bufferWriter.writeInt32(this.getHeader())
bufferWriter.writeUInt32(this.versionGroupId)
bufferWriter.writeSlice(hashPrevouts)
bufferWriter.writeSlice(hashSequence)
bufferWriter.writeSlice(hashOutputs)
bufferWriter.writeSlice(hashJoinSplits)
bufferWriter.writeUInt32(this.locktime)
bufferWriter.writeUInt32(this.expiryHeight)
bufferWriter.writeUInt32(hashType)

// If this hash is for a transparent input signature (i.e. not for txTo.joinSplitSig):
if (inIndex !== VALUE_UINT64_MAX) {
// The input being signed (replacing the scriptSig with scriptCode + amount)
// The prevout may already be contained in hashPrevout, and the nSequence
// may already be contained in hashSequence.
var input = this.ins[inIndex]
bufferWriter.writeSlice(input.hash)
bufferWriter.writeUInt32(input.index)
bufferWriter.writeVarSlice(prevOutScript)
bufferWriter.writeUInt64(value)
bufferWriter.writeUInt32(input.sequence)
}

var personalization = Buffer.alloc(16)
var prefix = 'ZcashSigHash'
personalization.write(prefix)
personalization.writeUInt32LE(consensusBranchId, prefix.length)

hashOutputs = bcrypto.hash256(tbuffer)
return this.getBlake2bHash(bufferWriter.getBuffer(), personalization)
}
// TODO: support non overwinter transactions
}

Transaction.prototype.hashForWitnessV0 = function (inIndex, prevOutScript, value, hashType) {
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments)

tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript))
toffset = 0
var hashPrevouts = this.getPrevoutHash(hashType)
var hashSequence = this.getSequenceHash(hashType)
var hashOutputs = this.getOutputsHash(hashType, inIndex)

var bufferWriter = new BufferWriter(156 + varSliceSize(prevOutScript))
var input = this.ins[inIndex]
writeUInt32(this.version)
writeSlice(hashPrevouts)
writeSlice(hashSequence)
writeSlice(input.hash)
writeUInt32(input.index)
writeVarSlice(prevOutScript)
writeUInt64(value)
writeUInt32(input.sequence)
writeSlice(hashOutputs)
writeUInt32(this.locktime)
writeUInt32(hashType)
return bcrypto.hash256(tbuffer)
bufferWriter.writeUInt32(this.version)
bufferWriter.writeSlice(hashPrevouts)
bufferWriter.writeSlice(hashSequence)
bufferWriter.writeSlice(input.hash)
bufferWriter.writeUInt32(input.index)
bufferWriter.writeVarSlice(prevOutScript)
bufferWriter.writeUInt64(value)
bufferWriter.writeUInt32(input.sequence)
bufferWriter.writeSlice(hashOutputs)
bufferWriter.writeUInt32(this.locktime)
bufferWriter.writeUInt32(hashType)
return bcrypto.hash256(bufferWriter.getBuffer())
}

/**
Expand Down Expand Up @@ -669,7 +783,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne
}

if (coins.isZcash(this.coin) && this.version >= Transaction.ZCASH_OVERWINTER_VERSION) {
const mask = (this.overwintered ? 1 : 0)
var mask = (this.overwintered ? 1 : 0)
writeInt32(this.version | (mask << 31)) // Set overwinter bit
writeUInt32(this.versionGroupId)
} else {
Expand Down
Loading