import { codec , Schema } from '@liskhq/lisk-codec' ; import { getAddressAndPublicKeyFromPassphrase , signData , signDataWithPrivateKey , hash , } from '@liskhq/lisk-cryptography' ; import { validateTransaction } from './validate' ; import { baseTransactionSchema } from './schema' ; import { TAG_TRANSACTION } from './constants' ; interface MultiSignatureKeys { readonly mandatoryKeys : Array < Buffer > ; readonly optionalKeys : Array < Buffer > ; } export const getSigningBytes = ( assetSchema , transactionObject ) : Buffer => { const validationErrors = validateTransaction ( assetSchema , transactionObject ) ; if ( validationErrors ) { throw validationErrors ; } if ( typeof transactionObject . asset !== 'object' || transactionObject . asset === null ) { throw new Error ( 'Asset must be of type object and not null' ) ; } const assetBytes = codec . encode ( ( assetSchema as unknown ) as Schema , transactionObject . asset ) ; const transactionBytes = codec . encode ( baseTransactionSchema , { ... transactionObject , asset : assetBytes , signatures : [ ] , } ) ; return transactionBytes ; } ; export const getBytes = ( assetSchema , transactionObject ) : Buffer => { if ( typeof transactionObject . asset !== 'object' || transactionObject . asset === null ) { throw new Error ( 'Asset must be of type object and not null' ) ; } const assetBytes = codec . encode ( ( assetSchema as unknown ) as Schema , transactionObject . asset ) ; const transactionBytes = codec . encode ( baseTransactionSchema , { ... transactionObject , asset : assetBytes , } ) ; return transactionBytes ; } ; export const signTransaction = ( assetSchema , transactionObject , networkIdentifier , passphrase ) : Record < string , unknown > => { if ( ! networkIdentifier . length ) { throw new Error ( 'Network identifier is required to sign a transaction' ) ; } if ( ! passphrase ) { throw new Error ( 'Passphrase is required to sign a transaction' ) ; } const validationErrors = validateTransaction ( assetSchema , transactionObject ) ; if ( validationErrors ) { throw validationErrors ; } const { publicKey } = getAddressAndPublicKeyFromPassphrase ( passphrase ) ; if ( ! Buffer . isBuffer ( transactionObject . senderPublicKey ) || ! transactionObject . senderPublicKey . equals ( publicKey ) ) { throw new Error ( 'Transaction senderPublicKey does not match public key from passphrase' ) ; } const signature = signData ( TAG_TRANSACTION , networkIdentifier , getSigningBytes ( assetSchema , transactionObject ) , passphrase ) ; transactionObject . signatures = [ signature ] ; return { ... transactionObject , id : hash ( getBytes ( assetSchema , transactionObject ) ) } ; } ; const sanitizeSignaturesArray = ( transactionObject , keys , includeSenderSignature ) : void => { const numberOfSignatures = ( includeSenderSignature ? 1 : 0 ) + keys . mandatoryKeys . length + keys . optionalKeys . length ; for ( let i = 0 ; i < numberOfSignatures ; i += 1 ) { if ( Array . isArray ( transactionObject . signatures ) && transactionObject . signatures [ i ] === undefined ) { transactionObject . signatures [ i ] = Buffer . alloc ( 0 ) ; } } } ; export const signMultiSignatureTransaction = ( assetSchema , transactionObject , networkIdentifier , passphrase , keys , includeSenderSignature = false ) : Record < string , unknown > => { if ( ! networkIdentifier . length ) { throw new Error ( 'Network identifier is required to sign a transaction' ) ; } if ( ! passphrase ) { throw new Error ( 'Passphrase is required to sign a transaction' ) ; } if ( ! Array . isArray ( transactionObject . signatures ) ) { throw new Error ( 'Signatures must be of type array' ) ; } const validationErrors = validateTransaction ( assetSchema , transactionObject ) ; if ( validationErrors ) { throw validationErrors ; } keys . mandatoryKeys . sort ( ( publicKeyA , publicKeyB ) => publicKeyA . compare ( publicKeyB ) ) ; keys . optionalKeys . sort ( ( publicKeyA , publicKeyB ) => publicKeyA . compare ( publicKeyB ) ) ; const { publicKey } = getAddressAndPublicKeyFromPassphrase ( passphrase ) ; const signature = signData ( TAG_TRANSACTION , networkIdentifier , getSigningBytes ( assetSchema , transactionObject ) , passphrase ) ; if ( includeSenderSignature && Buffer . isBuffer ( transactionObject . senderPublicKey ) && publicKey . equals ( transactionObject . senderPublicKey ) ) { transactionObject . signatures [ 0 ] = signature ; } const mandatoryKeyIndex = keys . mandatoryKeys . findIndex ( aPublicKey => aPublicKey . equals ( publicKey ) ) ; const optionalKeyIndex = keys . optionalKeys . findIndex ( aPublicKey => aPublicKey . equals ( publicKey ) ) ; if ( mandatoryKeyIndex !== - 1 ) { const signatureOffset = includeSenderSignature ? 1 : 0 ; transactionObject . signatures [ mandatoryKeyIndex + signatureOffset ] = signature ; } if ( optionalKeyIndex !== - 1 ) { const signatureOffset = includeSenderSignature ? 1 : 0 ; transactionObject . signatures [ keys . mandatoryKeys . length + optionalKeyIndex + signatureOffset ] = signature ; } sanitizeSignaturesArray ( transactionObject , keys , includeSenderSignature ) ; return { ... transactionObject , id : hash ( getBytes ( assetSchema , transactionObject ) ) } ; } ; export const signTransactionWithPrivateKey = ( assetSchema , transactionObject , networkIdentifier , privateKey ) : Record < string , unknown > => { if ( ! networkIdentifier . length ) { throw new Error ( 'Network identifier is required to sign a transaction' ) ; } if ( ! privateKey . length || privateKey . length !== 64 ) { throw new Error ( 'Private key must be 64 bytes' ) ; } const validationErrors = validateTransaction ( assetSchema , transactionObject ) ; if ( validationErrors ) { throw validationErrors ; } const transactionWithNetworkIdentifierBytes = Buffer . concat ( [ networkIdentifier , getSigningBytes ( assetSchema , transactionObject ) , ] ) ; const signature = signDataWithPrivateKey ( TAG_TRANSACTION , networkIdentifier , transactionWithNetworkIdentifierBytes , privateKey ) ; transactionObject . signatures = [ signature ] ; return { ... transactionObject , id : hash ( getBytes ( assetSchema , transactionObject ) ) } ; } ; export const signMultiSignatureTransactionWithPrivateKey = ( assetSchema , transactionObject , networkIdentifier , privateKey , keys , includeSenderSignature = false ) : Record < string , unknown > => { if ( ! networkIdentifier . length ) { throw new Error ( 'Network identifier is required to sign a transaction' ) ; } if ( ! privateKey . length || privateKey . length !== 64 ) { throw new Error ( 'Private key must be 64 bytes' ) ; } if ( ! Array . isArray ( transactionObject . signatures ) ) { throw new Error ( 'Signatures must be of type array' ) ; } const validationErrors = validateTransaction ( assetSchema , transactionObject ) ; if ( validationErrors ) { throw validationErrors ; } const { senderPublicKey : publicKey } = transactionObject ; keys . mandatoryKeys . sort ( ( publicKeyA , publicKeyB ) => publicKeyA . compare ( publicKeyB ) ) ; keys . optionalKeys . sort ( ( publicKeyA , publicKeyB ) => publicKeyA . compare ( publicKeyB ) ) ; const transactionWithNetworkIdentifierBytes = Buffer . concat ( [ networkIdentifier , getSigningBytes ( assetSchema , transactionObject ) , ] ) ; const signature = signDataWithPrivateKey ( TAG_TRANSACTION , networkIdentifier , transactionWithNetworkIdentifierBytes , privateKey ) ; if ( includeSenderSignature && Buffer . isBuffer ( transactionObject . senderPublicKey ) ) { transactionObject . signatures [ 0 ] = signature ; } const mandatoryKeyIndex = keys . mandatoryKeys . findIndex ( aPublicKey => aPublicKey . equals ( publicKey as Buffer ) ) ; const optionalKeyIndex = keys . optionalKeys . findIndex ( aPublicKey => aPublicKey . equals ( publicKey as Buffer ) ) ; if ( mandatoryKeyIndex !== - 1 ) { const signatureOffset = includeSenderSignature ? 1 : 0 ; transactionObject . signatures [ mandatoryKeyIndex + signatureOffset ] = signature ; } if ( optionalKeyIndex !== - 1 ) { const signatureOffset = includeSenderSignature ? 1 : 0 ; transactionObject . signatures [ keys . mandatoryKeys . length + optionalKeyIndex + signatureOffset ] = signature ; } sanitizeSignaturesArray ( transactionObject , keys , includeSenderSignature ) ; return { ... transactionObject , id : hash ( getBytes ( assetSchema , transactionObject ) ) } ; } ;