@kazeblockchain/krypton-js
Version:
Javascript libraries for kaze wallet using https://github.com/kazechainio/kazewallet/blob/master/js/wallet.js as the original source.
213 lines (197 loc) • 7.02 kB
JavaScript
import { num2hexstring, num2VarInt, reverseHex, Fixed8 } from '../utils'
import { getScriptHashFromAddress, getPublicKeysFromVerificationScript, getSignaturesFromInvocationScript, getSigningThresholdFromVerificationScript, verifySignature } from '../wallet'
import { ASSET_ID } from '../consts'
/**
* @typedef TransactionInput
* @property {string} prevHash - Transaction hash, Uint256
* @property {number} prevIndex - Index of the coin in the previous transaction, Uint16
*/
/**
* Serializes a TransactionInput.
* @param {TransactionInput} input
* @return {string}
*/
export const serializeTransactionInput = (input) => {
return reverseHex(input.prevHash) + reverseHex(num2hexstring(input.prevIndex, 2))
}
/**
* Deserializes a stream of hexstring into a TransactionInput.
* @param {StringStream} stream
* @return {TransactionInput}
*/
export const deserializeTransactionInput = (stream) => {
const prevHash = reverseHex(stream.read(32))
const prevIndex = parseInt(reverseHex(stream.read(2)), 16)
return { prevHash, prevIndex }
}
/**
* @typedef TransactionOutput
* @property {string} assetId - assetId, Uint256
* @property {number|Fixed8} value - value of output, Fixed8
* @property {string} scriptHash - Uint160
*/
export const TransactionOutput = (input) => {
return {
assetId: input.assetId,
value: new Fixed8(input.value),
scriptHash: input.scriptHash
}
}
/**
* Serializes a TransactionOutput.
* @param {TransactionOutput} output
* @return {string}
*/
export const serializeTransactionOutput = (output) => {
const value = new Fixed8(output.value).toReverseHex()
return reverseHex(output.assetId) + value + reverseHex(output.scriptHash)
}
/**
* Deserializes a stream into a TransactionOutput.
* @param {StringStream} stream
* @return {TransactionOutput}
*/
export const deserializeTransactionOutput = (stream) => {
const assetId = reverseHex(stream.read(32))
const value = Fixed8.fromReverseHex(stream.read(8))
const scriptHash = reverseHex(stream.read(20))
return { assetId, value, scriptHash }
}
/**
* A helper method to create a TransactionOutput using human-friendly inputs.
* @param {string} assetSym - The Symbol of the asset to send. Typically KAZE or STREAM.
* @param {number|Fixed8} val - The value to send.
* @param {string} address - The address to send the asset to.
* @return {TransactionOutput}
*/
export const createTransactionOutput = (assetSym, val, address) => {
const assetId = ASSET_ID[assetSym]
const scriptHash = getScriptHashFromAddress(address)
const value = new Fixed8(val)
return { assetId, value, scriptHash }
}
/**
* @typedef TransactionAttribute
* @property {number} usage - Identifying byte
* @property {string} data - Data
*/
const maxTransactionAttributeSize = 65535
/**
* Serializes a TransactionAttribute.
* @param {TransactionAttribute} attr
* @return {string}
*/
export const serializeTransactionAttribute = (attr) => {
if (attr.data.length > maxTransactionAttributeSize) throw new Error()
let out = num2hexstring(attr.usage)
if (attr.usage === 0x81) {
out += num2hexstring(attr.data.length / 2)
} else if (attr.usage === 0x90 || attr.usage >= 0xf0) {
out += num2VarInt(attr.data.length / 2)
}
if (attr.usage === 0x02 || attr.usage === 0x03) {
out += attr.data.substr(2, 64)
} else {
out += attr.data
}
return out
}
/**
* Deserializes a stream into a TransactionAttribute
* @param {StringStream} stream
* @return {TransactionAttribute}
*/
export const deserializeTransactionAttribute = (stream) => {
const attr = {
usage: parseInt(stream.read(1), 16)
}
if (attr.usage === 0x00 || attr.usage === 0x30 || (attr.usage >= 0xa1 && attr.usage <= 0xaf)) {
attr.data = stream.read(32)
} else if (attr.usage === 0x02 || attr.usage === 0x03) {
attr.data = num2hexstring(attr.usage) + stream.read(32)
} else if (attr.usage === 0x20) {
attr.data = stream.read(20)
} else if (attr.usage === 0x81) {
attr.data = stream.read(parseInt(stream.read(1), 16))
} else if (attr.usage === 0x90 || attr.usage >= 0xf0) {
attr.data = stream.readVarBytes()
} else {
throw new Error()
}
return attr
}
/**
* @typedef Witness
* @property {string} invocationScript - This data is stored as is (Little Endian)
* @property {string} verificationScript - This data is stored as is (Little Endian)
*/
/**
* Serializes a Witness.
* @param {Witness} witness
* @return {string}
*/
export const serializeWitness = (witness) => {
const invoLength = num2VarInt(witness.invocationScript.length / 2)
const veriLength = num2VarInt(witness.verificationScript.length / 2)
return invoLength + witness.invocationScript + veriLength + witness.verificationScript
}
/**
* Deserializes a stream into a Witness
* @param {StringStream} stream
* @return {Witness}
*/
export const deserializeWitness = (stream) => {
const invocationScript = stream.readVarBytes()
const verificationScript = stream.readVarBytes()
return { invocationScript, verificationScript }
}
export const Witness = {
buildMultiSig: function (tx, sigs, acctOrVerificationScript) {
const verificationScript =
typeof acctOrVerificationScript === 'string'
? acctOrVerificationScript
: acctOrVerificationScript.contract.script
const publicKeys = getPublicKeysFromVerificationScript(verificationScript)
const orderedSigs = Array(publicKeys.length).fill('')
sigs.forEach(element => {
if (typeof element === 'string') {
const position = publicKeys.findIndex(key => verifySignature(tx, element, key))
if (position === -1) {
throw new Error(`Invalid signature given: ${element}`)
}
orderedSigs[position] = element
} else if (typeof element === 'object') {
const keys = getPublicKeysFromVerificationScript(
element.verificationScript
)
if (keys.length !== 1) {
throw new Error('Given witness contains more than 1 public key!')
}
const position = publicKeys.indexOf(keys[0])
orderedSigs[position] = getSignaturesFromInvocationScript(
element.invocationScript
)[0]
} else {
throw new Error('Unable to process given signature')
}
})
const signingThreshold = getSigningThresholdFromVerificationScript(
verificationScript
)
const validSigs = orderedSigs.filter(s => s !== '')
if (validSigs.length < signingThreshold) {
throw new Error(
`Insufficient signatures: expected ${signingThreshold} but got ${
validSigs.length
} instead`
)
}
return {
invocationScript: validSigs
.slice(0, signingThreshold)
.map(s => '40' + s)
.join(''),
verificationScript
}
}
}