gitiumiota
Version:
IOTA Client Reference Implementation
456 lines (405 loc) • 16.5 kB
text/typescript
/**
* @module transaction
*/
import { tritsToValue } from '@iota/converter'
import Curl from '@iota/curl'
import Kerl from '@iota/kerl'
import { FRAGMENT_LENGTH } from '@iota/signing'
import * as warning from 'warning'
import * as errors from '../../errors'
import { isTrits } from '../../guards'
import '../../typed-array'
export const SIGNATURE_OR_MESSAGE_OFFSET = 0
export const SIGNATURE_OR_MESSAGE_LENGTH = FRAGMENT_LENGTH
// export const EXTRA_DATA_DIGEST_OFFSET = SIGNATURE_OR_MESSAGE_OFFSET + SIGNATURE_OR_MESSAGE_LENGTH
// export const EXTRA_DATA_DIGEST_LENGTH = 243
export const ADDRESS_OFFSET = SIGNATURE_OR_MESSAGE_OFFSET + SIGNATURE_OR_MESSAGE_LENGTH // EXTRA_DATA_DIGEST_OFFSET + EXTRA_DATA_DIGEST_LENGTH
export const ADDRESS_LENGTH = Kerl.HASH_LENGTH
export const VALUE_OFFSET = ADDRESS_OFFSET + ADDRESS_LENGTH
export const VALUE_LENGTH = 81
export const OBSOLETE_TAG_OFFSET = VALUE_OFFSET + VALUE_LENGTH
export const OBSOLETE_TAG_LENGTH = 81
export const ISSUANCE_TIMESTAMP_OFFSET = OBSOLETE_TAG_OFFSET + OBSOLETE_TAG_LENGTH
export const ISSUANCE_TIMESTAMP_LENGTH = 27
// export const TIMELOCK_LOWER_BOUND_OFFSET = ISSUANCE_TIMESTAMP_OFFSET + ISSUANCE_TIMESTAMP_LENGTH
// export const TIMELOCK_LOWER_BOUND_LENGTH = 27
// export const TIMELOCK_UPPER_BOUND_OFFSET = TIMELOCK_LOWER_BOUND_OFFSET + TIMELOCK_LOWER_BOUND_LENGTH
// export const TIMELOCK_UPPER_BOUND_LENGTH = 27
export const CURRENT_INDEX_OFFSET = ISSUANCE_TIMESTAMP_OFFSET + ISSUANCE_TIMESTAMP_LENGTH
export const CURRENT_INDEX_LENGTH = 27
export const LAST_INDEX_OFFSET = CURRENT_INDEX_OFFSET + CURRENT_INDEX_LENGTH
export const LAST_INDEX_LENGTH = 27
// export const BUNDLE_NONCE_OFFSET = TIMELOCK_UPPER_BOUND_OFFSET + TIMELOCK_LOWER_BOUND_LENGTH
// export const BUNDLE_NONCE_LENGTH = 243
export const BUNDLE_OFFSET = LAST_INDEX_OFFSET + LAST_INDEX_LENGTH
export const BUNDLE_LENGTH = Kerl.HASH_LENGTH
export const TRUNK_TRANSACTION_OFFSET = BUNDLE_OFFSET + BUNDLE_LENGTH // BUNDLE_NONCE_OFFSET + BUNDLE_NONCE_LENGTH
export const TRUNK_TRANSACTION_LENGTH = Curl.HASH_LENGTH
export const BRANCH_TRANSACTION_OFFSET = TRUNK_TRANSACTION_OFFSET + TRUNK_TRANSACTION_LENGTH
export const BRANCH_TRANSACTION_LENGTH = Curl.HASH_LENGTH
export const TAG_OFFSET = BRANCH_TRANSACTION_OFFSET + BRANCH_TRANSACTION_LENGTH
export const TAG_LENGTH = 81
export const ATTACHMENT_TIMESTAMP_OFFSET = TAG_OFFSET + TAG_LENGTH
export const ATTACHMENT_TIMESTAMP_LENGTH = 27
export const ATTACHMENT_TIMESTAMP_LOWER_BOUND_OFFSET = ATTACHMENT_TIMESTAMP_OFFSET + ATTACHMENT_TIMESTAMP_LENGTH
export const ATTACHMENT_TIMESTAMP_LOWER_BOUND_LENGTH = 27
export const ATTACHMENT_TIMESTAMP_UPPER_BOUND_OFFSET =
ATTACHMENT_TIMESTAMP_LOWER_BOUND_OFFSET + ATTACHMENT_TIMESTAMP_LOWER_BOUND_LENGTH
export const ATTACHMENT_TIMESTAMP_UPPER_BOUND_LENGTH = 27
export const TRANSACTION_NONCE_OFFSET =
ATTACHMENT_TIMESTAMP_UPPER_BOUND_OFFSET + ATTACHMENT_TIMESTAMP_UPPER_BOUND_LENGTH
export const TRANSACTION_NONCE_LENGTH = 81
export const TRANSACTION_ESSENCE_OFFSET = ADDRESS_OFFSET // EXTRA_DATA_DIGEST_OFFSET
export const TRANSACTION_ESSENCE_LENGTH = BUNDLE_OFFSET - ADDRESS_OFFSET // BUNDLE_NONCE_OFFSET - EXTRA_DATA_DIGEST_OFFSET
export const TRANSACTION_LENGTH = TRANSACTION_NONCE_OFFSET + TRANSACTION_NONCE_LENGTH
export const TRANSACTION_HASH_LENGTH = Curl.HASH_LENGTH
/**
* Checks if given value is a valid transaction buffer length or offset.
*
* @method isMultipleOfTransactionLength
*
* @param {Int8Array} lengthOrOffset
*
* @return {bolean}
*/
export const isMultipleOfTransactionLength = (lengthOrOffset: number) => {
if (!Number.isInteger(lengthOrOffset)) {
throw new TypeError(errors.ILLEGAL_LENGTH_OR_OFFSET)
}
return lengthOrOffset >= 0 && lengthOrOffset % TRANSACTION_LENGTH === 0
}
/**
* Creates a function that copies a fixed size part of the buffer.
*
* @method transactionBufferSlice
*
* @param {number} transactionFieldOffset
* @param {number} transactionFieldLength
*
* @return {Function}
*
* @ignore
*/
export const transactionBufferSlice = (transactionFieldOffset: number, transactionFieldLength: number) => {
if (!Number.isInteger(transactionFieldOffset)) {
throw new TypeError(errors.ILLEGAL_TRANSACTION_FIELD_OFFSET)
}
if (transactionFieldOffset < 0) {
throw new RangeError(errors.ILLEGAL_TRANSACTION_FIELD_OFFSET)
}
if (!Number.isInteger(transactionFieldLength)) {
throw new TypeError(errors.ILLEGAL_TRANSACTION_FIELD_LENGTH)
}
if (transactionFieldLength < 0) {
throw new RangeError(errors.ILLEGAL_TRANSACTION_FIELD_LENGTH)
}
return (transactionBuffer: Int8Array, transactionOffset = 0): Int8Array => {
if (!(transactionBuffer instanceof Int8Array)) {
throw new Error(errors.ILLEGAL_TRANSACTION_BUFFER)
}
if (!isMultipleOfTransactionLength(transactionBuffer.length)) {
throw new RangeError(errors.ILLEGAL_TRANSACTION_BUFFER_LENGTH)
}
if (!isMultipleOfTransactionLength(transactionOffset)) {
throw new RangeError(errors.ILLEGAL_TRANSACTION_OFFSET)
}
return transactionBuffer.slice(
transactionOffset + transactionFieldOffset,
transactionOffset + transactionFieldOffset + transactionFieldLength
)
}
}
/**
* Returns a copy of `signatureOrMessage` field.
*
* @method signatureOrMessage
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const signatureOrMessage = transactionBufferSlice(SIGNATURE_OR_MESSAGE_OFFSET, SIGNATURE_OR_MESSAGE_LENGTH)
/**
* Returns a copy of `address` field.
*
* @method address
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const address = transactionBufferSlice(ADDRESS_OFFSET, ADDRESS_LENGTH)
/**
* Returns a copy of `value` field.
*
* @method value
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const value = transactionBufferSlice(VALUE_OFFSET, VALUE_LENGTH)
export const createObsoleteTag = (warn = true) =>
/**
* Returns a copy of `obsoleteTag` field.
*
* @method obsoleteTag
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
(buffer: Int8Array, offset = 0): Int8Array => {
warning(warn, 'Deprecation warning: `obsoleteTag` field will be removed in final design.')
return transactionBufferSlice(OBSOLETE_TAG_OFFSET, OBSOLETE_TAG_LENGTH)(buffer, offset)
}
export const obsoleteTag = createObsoleteTag()
/**
* Returns a copy of `issuanceTimestamp` field.
*
* @method issuanceTimestamp
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const issuanceTimestamp = transactionBufferSlice(ISSUANCE_TIMESTAMP_OFFSET, ISSUANCE_TIMESTAMP_LENGTH)
export const createCurrentIndex = (warn = true) =>
/**
* Returns a copy of `currentIndex` field.
*
* @method currentIndex
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
(buffer: Int8Array, offset = 0): Int8Array => {
warning(warn, 'Deprecation warning: `currentIndex` field will be removed in final design.')
return transactionBufferSlice(CURRENT_INDEX_OFFSET, CURRENT_INDEX_LENGTH)(buffer, offset)
}
export const currentIndex = createCurrentIndex()
export const createLastIndex = (warn = true) =>
/**
* Returns a copy of `lastIndex` field.
*
* @method lastIndex
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
(buffer: Int8Array, offset = 0): Int8Array => {
warning(warn, 'Deprecation warning: `lastIndex` field will be removed in final design.')
return transactionBufferSlice(LAST_INDEX_OFFSET, LAST_INDEX_LENGTH)(buffer, offset)
}
export const lastIndex = createLastIndex()
export const createBundle = (warn = true) =>
/**
* Returns a copy of `bundle` field.
*
* @method bundle
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
(buffer: Int8Array, offset = 0): Int8Array => {
warning(warn, 'Deprecation warning: `bundle` field will be removed in final design.')
return transactionBufferSlice(BUNDLE_OFFSET, BUNDLE_LENGTH)(buffer, offset)
}
export const bundle = createBundle()
/**
* Returns a copy of `trunkTransaction` field.
*
* @method trunkTransaction
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const trunkTransaction = transactionBufferSlice(TRUNK_TRANSACTION_OFFSET, TRUNK_TRANSACTION_LENGTH)
/**
* Returns a copy of `branchTransaction` field.
*
* @method branchTransaction
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const branchTransaction = transactionBufferSlice(BRANCH_TRANSACTION_OFFSET, BRANCH_TRANSACTION_LENGTH)
/**
* Returns a copy of `tag` field.
*
* @method tag
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const tag = transactionBufferSlice(TAG_OFFSET, TAG_LENGTH)
/**
* Returns a copy of `attachmentTimestamp` field.
*
* @method attachmentTimestamp
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const attachmentTimestamp = transactionBufferSlice(ATTACHMENT_TIMESTAMP_OFFSET, ATTACHMENT_TIMESTAMP_LENGTH)
/**
* Returns a copy of `attachmentTimestampLowerBound` field.
*
* @method attachmentTimestampLowerBound
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const attachmentTimestampLowerBound = transactionBufferSlice(
ATTACHMENT_TIMESTAMP_LOWER_BOUND_OFFSET,
ATTACHMENT_TIMESTAMP_LOWER_BOUND_LENGTH
)
/**
* Returns a copy of `attachmentTimestampUpperBound` field.
*
* @method attachmentTimestampUpperBound
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const attachmentTimestampUpperBound = transactionBufferSlice(
ATTACHMENT_TIMESTAMP_UPPER_BOUND_OFFSET,
ATTACHMENT_TIMESTAMP_UPPER_BOUND_LENGTH
)
/**
* Returns a copy of `tansactionNonce` field.
*
* @method transactionNonce
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const transactionNonce = transactionBufferSlice(TRANSACTION_NONCE_OFFSET, TRANSACTION_NONCE_LENGTH)
/**
* Returns a copy of transaction essence fields.
*
* @method bundle
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be a multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array}
*/
export const transactionEssence = transactionBufferSlice(TRANSACTION_ESSENCE_OFFSET, TRANSACTION_ESSENCE_LENGTH)
/**
* Calculates the transaction hash.
*
* @method transactionHash
*
* @param {Int8Array} buffer - Transaction buffer. Buffer length must be multiple of transaction length.
* @param {Number} [offset=0] - Transaction trit offset. It must be a multiple of transaction length.
*
* @return {Int8Array} Transaction hash
*/
export const transactionHash = (buffer: Int8Array, offset = 0): Int8Array => {
if (!isMultipleOfTransactionLength(buffer.length)) {
throw new Error(errors.ILLEGAL_TRANSACTION_BUFFER_LENGTH)
}
if (!isMultipleOfTransactionLength(offset)) {
throw new Error(errors.ILLEGAL_TRANSACTION_OFFSET)
}
const output = new Int8Array(Curl.HASH_LENGTH)
const sponge = new Curl()
sponge.absorb(buffer, offset, TRANSACTION_LENGTH)
sponge.squeeze(output, 0, Curl.HASH_LENGTH)
return output
}
/* Guards */
/**
* Checks if input trits represent a syntactically valid transaction.
*
* @method isTransaction
*
* @param {Int8Array} transaction - Transaction trits.
* @param {number} [minWeightMagnitude=0] - Min weight magnitude.
*
* @return {boolean}
*/
export const isTransaction = (transaction: any, minWeightMagnitude = 0): boolean => {
if (!Number.isInteger(minWeightMagnitude)) {
throw new TypeError(errors.ILLEGAL_MIN_WEIGHT_MAGNITUDE)
}
if (minWeightMagnitude < 0 || minWeightMagnitude > TRANSACTION_HASH_LENGTH) {
throw new RangeError(errors.ILLEGAL_MIN_WEIGHT_MAGNITUDE)
}
return (
transaction.length === TRANSACTION_LENGTH &&
isTrits(transaction) &&
// Last address trit of value transaction must be 0.
// Revisions in signature scheme may affect this in the future.
(tritsToValue(value(transaction)) === 0 || transaction[ADDRESS_OFFSET + ADDRESS_LENGTH - 1] === 0) &&
(!minWeightMagnitude ||
transactionHash(transaction)
.subarray(TRANSACTION_HASH_LENGTH - minWeightMagnitude, TRANSACTION_HASH_LENGTH)
.every(trit => trit === 0))
)
}
/**
* Checks if given transaction is tail.
* A tail transaction is the one with `currentIndex=0`.
*
* @method isTailTransaction
*
* @param {Int8Array} transaction
*
* @return {boolean}
*/
export const isTail = (transaction: any): transaction is Int8Array =>
isTransaction(transaction) && tritsToValue(createCurrentIndex(false)(transaction)) === 0
/**
* Checks if given transaction is head.
* The head transaction is the one with `currentIndex=lastIndex`.
*
* @method isHeadTransaction
*
* @param {Int8Array} transaction
*
* @return {boolean}
*/
export const isHead = (transaction: any): transaction is Int8Array =>
isTransaction(transaction) &&
tritsToValue(createCurrentIndex(false)(transaction)) === tritsToValue(createLastIndex(false)(transaction))
/**
* Checks if given transaction has been attached.
*
* @method isAttachedTransaction
*
* @param {Int8Array} transaction
*
* @return {boolean}
*/
export const isAttached = (transaction: Int8Array): boolean =>
isTransaction(transaction) &&
transaction
.subarray(ATTACHMENT_TIMESTAMP_OFFSET, ATTACHMENT_TIMESTAMP_OFFSET + ATTACHMENT_TIMESTAMP_LENGTH)
.some(trit => trit !== 0)