diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..880031609 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: "\U0001F41C Bug report" +about: Report a reproducible bug. +title: '' +labels: new-bug +assignees: '' + +--- + +### Subject of the issue + + + +### Your environment + + + +### Steps to reproduce + +1. +2. + +### Expected behaviour + +### Actual behaviour diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..7a16b83cb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: "\U0001F514 Feature Request" +about: Suggestions for how we can improve the algorand platform. +title: '' +labels: new-feature-request +assignees: '' +--- + +## Problem + + + +## Solution + + + +## Dependencies + + + +## Urgency + + diff --git a/.prettierignore b/.prettierignore index ab32208e8..008c4506c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ dist tests/cucumber/features tests/cucumber/browser/build tests/browser/bundle.* +.github diff --git a/CHANGELOG.md b/CHANGELOG.md index 3246331c4..3239fb420 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.11.0 + +## Added + +- Signing support for rekeying to LogicSig/MultiSig account + # 1.10.1 ## Added diff --git a/package-lock.json b/package-lock.json index 332f76c5f..490bef85f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "algosdk", - "version": "1.10.1", + "version": "1.11.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "algosdk", - "version": "1.10.1", + "version": "1.11.0", "license": "MIT", "dependencies": { "algo-msgpack-with-bigint": "^2.1.1", diff --git a/package.json b/package.json index 7826468c5..0ab82b4bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algosdk", - "version": "1.10.1", + "version": "1.11.0", "description": "The official JavaScript SDK for Algorand", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/src/logic/logic.ts b/src/logic/logic.ts index 35bda56c5..9d8d60ef6 100644 --- a/src/logic/logic.ts +++ b/src/logic/logic.ts @@ -261,7 +261,7 @@ export function readProgram( * @throws * @returns true if success */ -export function checkProgram(program: Uint8Array, args: Uint8Array[]) { +export function checkProgram(program: Uint8Array, args?: Uint8Array[]) { const [, , success] = readProgram(program, args); return success; } diff --git a/src/logicsig.ts b/src/logicsig.ts index 9436be863..c91b56a63 100644 --- a/src/logicsig.ts +++ b/src/logicsig.ts @@ -7,8 +7,9 @@ import * as utils from './utils/utils'; import * as txnBuilder from './transaction'; import { EncodedLogicSig, + EncodedLogicSigAccount, EncodedMultisig, - EncodedTransaction, + EncodedSignedTransaction, } from './types/transactions/encoded'; import { MultisigMetadata } from './types/multisig'; @@ -32,30 +33,30 @@ export class LogicSig implements LogicSigStorageStructure { constructor( program: Uint8Array, - bufferOrUint8ArrArgs: Array | undefined + programArgs?: Array | null ) { + if ( + programArgs && + (!Array.isArray(programArgs) || + !programArgs.every( + (arg) => arg.constructor === Uint8Array || Buffer.isBuffer(arg) + )) + ) { + throw new TypeError('Invalid arguments'); + } + let args: Uint8Array[] | undefined; - if (typeof bufferOrUint8ArrArgs !== 'undefined') - args = bufferOrUint8ArrArgs.map((arg) => new Uint8Array(arg)); + if (programArgs != null) + args = programArgs.map((arg) => new Uint8Array(arg)); if (!logic.checkProgram(program, args)) { throw new Error('Invalid program'); } - function checkType(arg: any) { - return arg.constructor === Uint8Array || Buffer.isBuffer(arg); - } - - if (args && (!Array.isArray(args) || !args.every(checkType))) { - throw new TypeError('Invalid arguments'); - } - - Object.assign(this, { - logic: program, - args, - sig: undefined, - msig: undefined, - }); + this.logic = program; + this.args = args; + this.sig = undefined; + this.msig = undefined; } // eslint-disable-next-line camelcase @@ -126,8 +127,8 @@ export class LogicSig implements LogicSigStorageStructure { * @param secretKey - Secret key to sign with * @param msig - Multisig account as \{version, threshold, addrs\} */ - sign(secretKey: Uint8Array, msig: MultisigMetadata) { - if (msig === undefined) { + sign(secretKey: Uint8Array, msig?: MultisigMetadata) { + if (msig == null) { this.sig = this.signProgram(secretKey); } else { const subsigs = msig.addrs.map((addr) => ({ @@ -193,9 +194,162 @@ export class LogicSig implements LogicSigStorageStructure { } } +/** + * Represents an account that can sign with a LogicSig program. + */ +export class LogicSigAccount { + lsig: LogicSig; + sigkey?: Uint8Array; + + /** + * Create a new LogicSigAccount. By default this will create an escrow + * LogicSig account. Call `sign` or `signMultisig` on the newly created + * LogicSigAccount to make it a delegated account. + * + * @param program - The compiled TEAL program which contains the logic for + * this LogicSig. + * @param args - An optional array of arguments for the program. + */ + constructor(program: Uint8Array, args?: Array | null) { + this.lsig = new LogicSig(program, args); + this.sigkey = undefined; + } + + // eslint-disable-next-line camelcase + get_obj_for_encoding() { + const obj: EncodedLogicSigAccount = { + lsig: this.lsig.get_obj_for_encoding(), + }; + if (this.sigkey) { + obj.sigkey = this.sigkey; + } + return obj; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(encoded: EncodedLogicSigAccount) { + const lsigAccount = new LogicSigAccount(encoded.lsig.l, encoded.lsig.arg); + lsigAccount.lsig = LogicSig.from_obj_for_encoding(encoded.lsig); + lsigAccount.sigkey = encoded.sigkey; + return lsigAccount; + } + + /** + * Encode this object into msgpack. + */ + toByte() { + return encoding.encode(this.get_obj_for_encoding()); + } + + /** + * Decode a msgpack object into a LogicSigAccount. + * @param encoded - The encoded LogicSigAccount. + */ + static fromByte(encoded: ArrayLike) { + const decodedObj = encoding.decode(encoded) as EncodedLogicSigAccount; + return LogicSigAccount.from_obj_for_encoding(decodedObj); + } + + /** + * Check if this LogicSigAccount has been delegated to another account with a + * signature. + * + * Note this function only checks for the presence of a delegation signature. + * To verify the delegation signature, use `verify`. + */ + isDelegated() { + return !!(this.lsig.sig || this.lsig.msig); + } + + /** + * Verifies this LogicSig's program and signatures. + * @returns true if and only if the LogicSig program and signatures are valid. + */ + verify() { + const addr = this.address(); + return this.lsig.verify(address.decodeAddress(addr).publicKey); + } + + /** + * Get the address of this LogicSigAccount. + * + * If the LogicSig is delegated to another account, this will return the + * address of that account. + * + * If the LogicSig is not delegated to another account, this will return an + * escrow address that is the hash of the LogicSig's program code. + */ + address() { + if (this.lsig.sig && this.lsig.msig) { + throw new Error( + 'LogicSig has too many signatures. At most one of sig or msig may be present' + ); + } + + if (this.lsig.sig) { + if (!this.sigkey) { + throw new Error('Signing key for delegated account is missing'); + } + return address.encodeAddress(this.sigkey); + } + + if (this.lsig.msig) { + const msigMetadata = { + version: this.lsig.msig.v, + threshold: this.lsig.msig.thr, + pks: this.lsig.msig.subsig.map((subsig) => subsig.pk), + }; + return address.encodeAddress(address.fromMultisigPreImg(msigMetadata)); + } + + return this.lsig.address(); + } + + /** + * Turns this LogicSigAccount into a delegated LogicSig. This type of LogicSig + * has the authority to sign transactions on behalf of another account, called + * the delegating account. Use this function if the delegating account is a + * multisig account. + * + * @param msig - The multisig delegating account + * @param secretKey - The secret key of one of the members of the delegating + * multisig account. Use `appendToMultisig` to add additional signatures + * from other members. + */ + signMultisig(msig: MultisigMetadata, secretKey: Uint8Array) { + this.lsig.sign(secretKey, msig); + } + + /** + * Adds an additional signature from a member of the delegating multisig + * account. + * + * @param secretKey - The secret key of one of the members of the delegating + * multisig account. + */ + appendToMultisig(secretKey: Uint8Array) { + this.lsig.appendToMultisig(secretKey); + } + + /** + * Turns this LogicSigAccount into a delegated LogicSig. This type of LogicSig + * has the authority to sign transactions on behalf of another account, called + * the delegating account. If the delegating account is a multisig account, + * use `signMultisig` instead. + * + * @param secretKey - The secret key of the delegating account. + */ + sign(secretKey: Uint8Array) { + this.lsig.sign(secretKey); + this.sigkey = nacl.keyPairFromSecretKey(secretKey).publicKey; + } +} + /** * makeLogicSig creates LogicSig object from program and arguments * + * @deprecated Use new LogicSigAccount(...) instead + * * @param program - Program to make LogicSig from * @param args - Arguments as array of Uint8Array * @returns LogicSig object @@ -204,61 +358,91 @@ export function makeLogicSig(program: Uint8Array, args?: Uint8Array[]) { return new LogicSig(program, args); } -/** - * signLogicSigTransactionObject takes transaction.Transaction and a LogicSig object and returns a logicsig - * transaction which is a blob representing a transaction and logicsig object. - * @param txn - transaction.Transaction - * @param lsig - logicsig object - * @returns Object containing txID and blob representing signed transaction. - */ -export function signLogicSigTransactionObject( +function signLogicSigTransactionWithAddress( txn: txnBuilder.Transaction, - lsig: LogicSig + lsig: LogicSig, + lsigAddress: Uint8Array ) { - const lstx: { - lsig: EncodedLogicSig; - txn: EncodedTransaction; - sgnr?: Buffer; - } = { + if (!lsig.verify(lsigAddress)) { + throw new Error( + 'Logic signature verification failed. Ensure the program and signature are valid.' + ); + } + + const signedTxn: EncodedSignedTransaction = { lsig: lsig.get_obj_for_encoding(), txn: txn.get_obj_for_encoding(), }; - const isDelegated = lsig.sig || lsig.msig; - if (isDelegated) { - if (!lsig.verify(txn.from.publicKey)) { - throw new Error( - "Logic signature verification failed. Ensure the program is valid and the transaction sender is the program's delegated address." - ); - } - } else { - // add AuthAddr if signing with a different program than From indicates for non-delegated LogicSig - const programAddr = lsig.address(); - if (programAddr !== address.encodeAddress(txn.from.publicKey)) { - lstx.sgnr = Buffer.from(address.decodeAddress(programAddr).publicKey); - } + if (!nacl.bytesEqual(lsigAddress, txn.from.publicKey)) { + signedTxn.sgnr = Buffer.from(lsigAddress); } return { txID: txn.txID().toString(), - blob: encoding.encode(lstx), + blob: encoding.encode(signedTxn), }; } /** - * signLogicSigTransaction takes a raw transaction and a LogicSig object and returns a logicsig - * transaction which is a blob representing a transaction and logicsig object. - * @param txn - containing constructor arguments for a transaction - * @param lsig - logicsig object + * signLogicSigTransactionObject takes a transaction and a LogicSig object and + * returns a signed transaction. + * + * @param txn - The transaction to sign. + * @param lsigObject - The LogicSig object that will sign the transaction. + * + * @returns Object containing txID and blob representing signed transaction. + */ +export function signLogicSigTransactionObject( + txn: txnBuilder.Transaction, + lsigObject: LogicSig | LogicSigAccount +) { + let lsig: LogicSig; + let lsigAddress: Uint8Array; + + if (lsigObject instanceof LogicSigAccount) { + lsig = lsigObject.lsig; + lsigAddress = address.decodeAddress(lsigObject.address()).publicKey; + } else { + lsig = lsigObject; + + if (lsig.sig) { + // For a LogicSig with a non-multisig delegating account, we cannot derive + // the address of that account from only its signature, so assume the + // delegating account is the sender. If that's not the case, the signing + // will fail. + lsigAddress = txn.from.publicKey; + } else if (lsig.msig) { + const msigMetadata = { + version: lsig.msig.v, + threshold: lsig.msig.thr, + pks: lsig.msig.subsig.map((subsig) => subsig.pk), + }; + lsigAddress = address.fromMultisigPreImg(msigMetadata); + } else { + lsigAddress = address.decodeAddress(lsig.address()).publicKey; + } + } + + return signLogicSigTransactionWithAddress(txn, lsig, lsigAddress); +} + +/** + * signLogicSigTransaction takes a transaction and a LogicSig object and returns + * a signed transaction. + * + * @param txn - The transaction to sign. + * @param lsigObject - The LogicSig object that will sign the transaction. + * * @returns Object containing txID and blob representing signed transaction. * @throws error on failure */ export function signLogicSigTransaction( txn: txnBuilder.TransactionLike, - lsig: LogicSig + lsigObject: LogicSig | LogicSigAccount ) { const algoTxn = txnBuilder.instantiateTxnIfNeeded(txn); - return signLogicSigTransactionObject(algoTxn, lsig); + return signLogicSigTransactionObject(algoTxn, lsigObject); } /** @@ -303,9 +487,7 @@ export function tealSignFromProgram( data: Uint8Array | Buffer, program: Uint8Array ) { - const lsig = makeLogicSig(program); + const lsig = new LogicSig(program); const contractAddress = lsig.address(); return tealSign(sk, data, contractAddress); } - -export default LogicSig; diff --git a/src/main.ts b/src/main.ts index 544279910..c338c1d9f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,7 @@ import * as LogicTemplatesCommonJSExport from './logicTemplates'; import Bid, { BidOptions } from './bid'; import * as convert from './convert'; import * as utils from './utils/utils'; -import AnyTransaction from './types/transactions'; +import AnyTransaction, { EncodedSignedTransaction } from './types/transactions'; import { MultisigMetadata } from './types/multisig'; const SIGN_BYTES_PREFIX = Buffer.from([77, 88]); // "MX" @@ -44,8 +44,8 @@ export function signTransaction( const algoTxn = txnBuilder.instantiateTxnIfNeeded(txn); return { - txID: (algoTxn as txnBuilder.Transaction).txID().toString(), - blob: (algoTxn as txnBuilder.Transaction).signTxn(sk), + txID: algoTxn.txID().toString(), + blob: algoTxn.signTxn(sk), }; } @@ -117,16 +117,7 @@ export function signMultisigTransaction( threshold, addrs, }); - if (Object.prototype.hasOwnProperty.call(txn, 'from')) { - const actualSender = - typeof txn.from === 'string' - ? txn.from - : address.encodeAddress(txn.from.publicKey); - - if (actualSender !== expectedFromRaw) { - throw new Error(MULTISIG_BAD_SENDER_ERROR_MSG); - } - } else { + if (!Object.prototype.hasOwnProperty.call(txn, 'from')) { // eslint-disable-next-line no-param-reassign txn.from = expectedFromRaw; } @@ -180,7 +171,9 @@ export function appendSignMultisigTransaction( ) { const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey); // obtain underlying txn, sign it, and merge it - const multisigTxObj: any = encoding.decode(multisigTxnBlob); + const multisigTxObj = encoding.decode( + multisigTxnBlob + ) as EncodedSignedTransaction; const msigTxn = multisig.MultisigTransaction.from_obj_for_encoding( multisigTxObj.txn ); @@ -260,6 +253,7 @@ export { } from './convert'; export { computeGroupID, assignGroupID } from './group'; export { + LogicSigAccount, makeLogicSig, signLogicSigTransaction, signLogicSigTransactionObject, diff --git a/src/multisig.ts b/src/multisig.ts index fb0f8b54e..6a4dace26 100644 --- a/src/multisig.ts +++ b/src/multisig.ts @@ -7,7 +7,7 @@ import { EncodedTransaction } from './types/transactions'; import { MultisigMetadata } from './types/multisig'; import { EncodedMultisig, - EncodedMultisigBlob, + EncodedSignedTransaction, } from './types/transactions/encoded'; /** @@ -18,12 +18,12 @@ export const MULTISIG_MERGE_LESSTHANTWO_ERROR_MSG = 'Not enough multisig transactions to merge. Need at least two'; export const MULTISIG_MERGE_MISMATCH_ERROR_MSG = 'Cannot merge txs. txIDs differ'; +export const MULTISIG_MERGE_MISMATCH_AUTH_ADDR_MSG = + 'Cannot merge txs. Auth addrs differ'; export const MULTISIG_MERGE_WRONG_PREIMAGE_ERROR_MSG = 'Cannot merge txs. Multisig preimages differ'; export const MULTISIG_MERGE_SIG_MISMATCH_ERROR_MSG = 'Cannot merge txs. subsigs are mismatched.'; -const MULTISIG_BAD_FROM_FIELD_ERROR_MSG = - 'The transaction from field and multisig preimage do not match.'; const MULTISIG_KEY_NOT_EXIST_ERROR_MSG = 'Key does not exist'; export const MULTISIG_NO_MUTATE_ERROR_MSG = 'Cannot mutate a multisig field as it would invalidate all existing signatures.'; @@ -69,16 +69,32 @@ function createMultisigTransaction( if (keyExist === false) { throw new Error(MULTISIG_KEY_NOT_EXIST_ERROR_MSG); } + const msig: EncodedMultisig = { v: version, thr: threshold, subsig: subsigs, }; - const sTxn: EncodedMultisigBlob = { + const signedTxn: EncodedSignedTransaction = { msig, txn: txnForEncoding, }; - return new Uint8Array(encoding.encode(sTxn)); + + // if the address of this multisig is different from the transaction sender, + // we need to add the auth-addr field + const msigAddr = address.fromMultisigPreImg({ + version, + threshold, + pks, + }); + if ( + address.encodeAddress(txnForEncoding.snd) !== + address.encodeAddress(msigAddr) + ) { + signedTxn.sgnr = Buffer.from(msigAddr); + } + + return new Uint8Array(encoding.encode(signedTxn)); } /** @@ -122,18 +138,6 @@ export class MultisigTransaction extends txnBuilder.Transaction { { version, threshold, pks }: MultisigMetadataWithPks, sk: Uint8Array ) { - const expectedFromRaw = address.fromMultisigPreImg({ - version, - threshold, - pks, - }); - if ( - address.encodeAddress(this.from.publicKey) !== - address.encodeAddress(expectedFromRaw) - ) { - throw new Error(MULTISIG_BAD_FROM_FIELD_ERROR_MSG); - } - // get signature verifier const myPk = nacl.keyPairFromSecretKey(sk).publicKey; return createMultisigTransaction( @@ -160,18 +164,42 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { if (multisigTxnBlobs.length < 2) { throw new Error(MULTISIG_MERGE_LESSTHANTWO_ERROR_MSG); } - const refSigTx = encoding.decode(multisigTxnBlobs[0]) as EncodedMultisigBlob; - const refSigAlgoTx = MultisigTransaction.from_obj_for_encoding(refSigTx.txn); - const refTxIDStr = refSigAlgoTx.txID().toString(); - const from = address.encodeAddress(refSigTx.txn.snd); + const refSigTx = encoding.decode( + multisigTxnBlobs[0] + ) as EncodedSignedTransaction; + const refTxID = MultisigTransaction.from_obj_for_encoding( + refSigTx.txn + ).txID(); + const refAuthAddr = refSigTx.sgnr + ? address.encodeAddress(refSigTx.sgnr) + : undefined; + const refPreImage = { + version: refSigTx.msig.v, + threshold: refSigTx.msig.thr, + pks: refSigTx.msig.subsig.map((subsig) => subsig.pk), + }; + const refMsigAddr = address.encodeAddress( + address.fromMultisigPreImg(refPreImage) + ); let newSubsigs = refSigTx.msig.subsig; for (let i = 0; i < multisigTxnBlobs.length; i++) { - const unisig = encoding.decode(multisigTxnBlobs[i]) as EncodedMultisigBlob; + const unisig = encoding.decode( + multisigTxnBlobs[i] + ) as EncodedSignedTransaction; + const unisigAlgoTxn = MultisigTransaction.from_obj_for_encoding(unisig.txn); - if (unisigAlgoTxn.txID().toString() !== refTxIDStr) { + if (unisigAlgoTxn.txID() !== refTxID) { throw new Error(MULTISIG_MERGE_MISMATCH_ERROR_MSG); } + + const authAddr = unisig.sgnr + ? address.encodeAddress(unisig.sgnr) + : undefined; + if (refAuthAddr !== authAddr) { + throw new Error(MULTISIG_MERGE_MISMATCH_AUTH_ADDR_MSG); + } + // check multisig has same preimage as reference if (unisig.msig.subsig.length !== refSigTx.msig.subsig.length) { throw new Error(MULTISIG_MERGE_WRONG_PREIMAGE_ERROR_MSG); @@ -181,9 +209,11 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { threshold: unisig.msig.thr, pks: unisig.msig.subsig.map((subsig) => subsig.pk), }; - if (from !== address.encodeAddress(address.fromMultisigPreImg(preimg))) { + const msgigAddr = address.encodeAddress(address.fromMultisigPreImg(preimg)); + if (refMsigAddr !== msgigAddr) { throw new Error(MULTISIG_MERGE_WRONG_PREIMAGE_ERROR_MSG); } + // now, we can merge newSubsigs = unisig.msig.subsig.map((uniSubsig, index) => { const current = refSigTx.msig.subsig[index]; @@ -218,11 +248,14 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { thr: refSigTx.msig.thr, subsig: newSubsigs, }; - const sTxn: EncodedMultisigBlob = { + const signedTxn: EncodedSignedTransaction = { msig, txn: refSigTx.txn, }; - return new Uint8Array(encoding.encode(sTxn)); + if (typeof refAuthAddr !== 'undefined') { + signedTxn.sgnr = Buffer.from(address.decodeAddress(refAuthAddr).publicKey); + } + return new Uint8Array(encoding.encode(signedTxn)); } export function verifyMultisig( diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts index e762a64e7..80438cae1 100644 --- a/src/types/transactions/encoded.ts +++ b/src/types/transactions/encoded.ts @@ -294,7 +294,14 @@ export interface EncodedTransaction { } export interface EncodedSubsig { + /** + * The public key + */ pk: Uint8Array; + + /** + * The signature provided by the public key, if any + */ s?: Uint8Array; } @@ -321,6 +328,8 @@ export interface EncodedMultisig { /** * An encoded multisig blob that contains the encoded multisig as well as a copy of the encoded transaction + * + * @deprecated Use EncodedSignedTransaction instead. */ export interface EncodedMultisigBlob { /** @@ -341,6 +350,11 @@ export interface EncodedLogicSig { msig?: EncodedMultisig; } +export interface EncodedLogicSigAccount { + lsig: EncodedLogicSig; + sigkey?: Uint8Array; +} + /** * A structure for an encoded signed transaction object */ diff --git a/tests/6.Multisig.ts b/tests/6.Multisig.ts index b5624c722..d4b0e2475 100644 --- a/tests/6.Multisig.ts +++ b/tests/6.Multisig.ts @@ -47,8 +47,8 @@ describe('Sample Multisig Info', () => { }); describe('Multisig Functionality', () => { - describe('should generate correct partial signature', () => { - it('first partial sig should match golden main repo result', () => { + describe('signMultisigTransaction', () => { + it('should match golden main repo result', () => { const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ from: sampleMultisigAddr, to: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI', @@ -83,14 +83,9 @@ describe('Multisig Functionality', () => { assert.deepStrictEqual(Buffer.from(blob), expectedSignedTxn); }); - it('second partial sig should match golden main repo result', () => { - const oneSigTxn = Buffer.from( - 'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==', - 'base64' - ); - + it('should correctly handle a different sender', () => { const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ - from: sampleMultisigAddr, + from: 'EHGMQCXBIFBE364DEKWQVVNCTCTVCGQL3BR2Q5I7CFTRXWIVTF4SYA3GHU', to: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI', amount: 1000, note: new Uint8Array(Buffer.from('RSYiABhShvs=', 'base64')), @@ -109,6 +104,31 @@ describe('Multisig Functionality', () => { const { txID, blob } = algosdk.signMultisigTransaction( txn, sampleMultisigParams, + sampleAccount1.sk + ); + + const expectedTxID = + 'YQOXQNNO56WXQSU3IDUM2C4J7IZI6WMSCMKN5UCP5ZK6GWA6BXKQ'; + assert.strictEqual(txID, expectedTxID); + + const expectedSignedTxn = Buffer.from( + 'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaRzZ25yxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCAhzMgK4UFCTfuDIq0K1aKYp1EaC9hjqHUfEWcb2RWZeaR0eXBlo3BheQ==', + 'base64' + ); + assert.deepStrictEqual(Buffer.from(blob), expectedSignedTxn); + }); + }); + + describe('appendSignMultisigTransaction', () => { + it('should match golden main repo result', () => { + const oneSigTxn = Buffer.from( + 'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==', + 'base64' + ); + + const { txID, blob } = algosdk.appendSignMultisigTransaction( + oneSigTxn, + sampleMultisigParams, sampleAccount2.sk ); @@ -117,18 +137,33 @@ describe('Multisig Functionality', () => { assert.strictEqual(txID, expectedTxID); const expectedBlob = Buffer.from( - 'gqRtc2lng6ZzdWJzaWeTgaJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXiConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEAQIbskY7Dq5x6d7P8SPojCoi+0D7IfIiWdtmDiST9GhVMHnTK/musH3QV5NTnn8qwDdcKifa6ujvg61ldXx9AEgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==', + 'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQBAhuyRjsOrnHp3s/xI+iMKiL7QPsh8iJZ22YOJJP0aFUwedMr+a6wfdBXk1OefyrAN1wqJ9rq6O+DrWV1fH0ASBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5', 'base64' ); assert.deepStrictEqual(Buffer.from(blob), expectedBlob); + }); - const mergedBlob = algosdk.mergeMultisigTransactions([blob, oneSigTxn]); + it('should correctly handle a different sender', () => { + const oneSigTxn = Buffer.from( + 'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaRzZ25yxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQPoomZ2zfMVo2dlbqxkZXZuZXQtdjM4LjCiZ2jEIP6zbDkQFDkAw9pVQsoYNrAP0vgZWRJXzSP2BC+YyDadomx2zfb9pG5vdGXECEUmIgAYUob7o3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCAhzMgK4UFCTfuDIq0K1aKYp1EaC9hjqHUfEWcb2RWZeaR0eXBlo3BheQ==', + 'base64' + ); - const expectedMergedBlob = Buffer.from( - 'gqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAuLAFE0oma0skOoAmOzEwfPuLYpEWl4LINtsiLrUqWQkDxh4WHb29//YCpj4MFbiSgD2jKYt0XKRD86zKCF4RDYKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQBAhuyRjsOrnHp3s/xI+iMKiL7QPsh8iJZ22YOJJP0aFUwedMr+a6wfdBXk1OefyrAN1wqJ9rq6O+DrWV1fH0ASBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5', + const { txID, blob } = algosdk.appendSignMultisigTransaction( + oneSigTxn, + sampleMultisigParams, + sampleAccount2.sk + ); + + const expectedTxID = + 'YQOXQNNO56WXQSU3IDUM2C4J7IZI6WMSCMKN5UCP5ZK6GWA6BXKQ'; + assert.strictEqual(txID, expectedTxID); + + const expectedBlob = Buffer.from( + 'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQPuH2WlM2x1tflA6LGMhKXBiuO7dsRzjXYPgcfjvDth9ZsCazyOKHqQmtYjD+pXLM8fJbrRhoUGTkyLxINiT9wCBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEICHMyArhQUJN+4MirQrVopinURoL2GOodR8RZxvZFZl5pHR5cGWjcGF5', 'base64' ); - assert.deepStrictEqual(Buffer.from(mergedBlob), expectedMergedBlob); + assert.deepStrictEqual(Buffer.from(blob), expectedBlob); }); }); @@ -188,8 +223,10 @@ describe('Multisig Functionality', () => { assert.deepStrictEqual(Buffer.from(finMsigBlob), twoSigTxBlob); }); + }); - it('merging should be symmetric and match golden main repo result', () => { + describe('mergeMultisigTransactions', () => { + it('should be symmetric and match golden main repo result', () => { // prettier-ignore const oneAndThreeBlob = Buffer.from([130, 164, 109, 115, 105, 103, 131, 166, 115, 117, 98, 115, 105, 103, 147, 130, 162, 112, 107, 196, 32, 27, 126, 192, 176, 75, 234, 97, 183, 150, 144, 151, 230, 203, 244, 7, 225, 8, 167, 5, 53, 29, 11, 201, 138, 190, 177, 34, 9, 168, 171, 129, 120, 161, 115, 196, 64, 113, 61, 44, 215, 188, 9, 110, 249, 243, 107, 227, 105, 200, 124, 12, 209, 21, 155, 67, 225, 240, 42, 107, 19, 212, 242, 236, 251, 14, 157, 232, 202, 93, 76, 125, 237, 173, 175, 178, 41, 145, 52, 43, 74, 132, 202, 20, 132, 237, 142, 122, 248, 31, 104, 105, 253, 168, 172, 70, 227, 115, 249, 227, 13, 129, 162, 112, 107, 196, 32, 9, 99, 50, 9, 83, 115, 137, 240, 117, 103, 17, 119, 57, 145, 199, 208, 62, 27, 115, 200, 196, 245, 43, 246, 175, 240, 26, 162, 92, 249, 194, 113, 130, 162, 112, 107, 196, 32, 231, 240, 248, 77, 6, 129, 29, 249, 243, 28, 141, 135, 139, 17, 85, 244, 103, 29, 81, 161, 133, 194, 0, 144, 134, 103, 244, 73, 88, 112, 104, 161, 161, 115, 196, 64, 125, 39, 23, 127, 48, 75, 104, 78, 54, 53, 177, 125, 33, 29, 42, 49, 173, 205, 5, 9, 225, 99, 169, 16, 177, 95, 186, 134, 218, 129, 179, 15, 219, 90, 103, 54, 188, 25, 90, 235, 144, 137, 45, 35, 66, 53, 45, 183, 232, 27, 20, 50, 91, 219, 194, 187, 55, 146, 113, 126, 233, 186, 161, 15, 163, 116, 104, 114, 2, 161, 118, 1, 163, 116, 120, 110, 140, 163, 102, 101, 101, 206, 0, 3, 200, 192, 162, 102, 118, 206, 0, 14, 249, 218, 163, 103, 101, 110, 172, 100, 101, 118, 110, 101, 116, 45, 118, 51, 56, 46, 48, 162, 103, 104, 196, 32, 254, 179, 108, 57, 16, 20, 57, 0, 195, 218, 85, 66, 202, 24, 54, 176, 15, 210, 248, 25, 89, 18, 87, 205, 35, 246, 4, 47, 152, 200, 54, 157, 162, 108, 118, 206, 0, 14, 253, 194, 166, 115, 101, 108, 107, 101, 121, 196, 32, 50, 18, 43, 43, 214, 61, 220, 83, 49, 150, 23, 165, 170, 83, 196, 177, 194, 111, 227, 220, 202, 242, 141, 54, 34, 181, 105, 119, 161, 64, 92, 134, 163, 115, 110, 100, 196, 32, 141, 146, 180, 137, 144, 1, 115, 160, 77, 250, 67, 89, 163, 102, 106, 106, 252, 234, 44, 66, 160, 93, 217, 193, 247, 62, 235, 165, 71, 128, 55, 233, 164, 116, 121, 112, 101, 166, 107, 101, 121, 114, 101, 103, 167, 118, 111, 116, 101, 102, 115, 116, 206, 0, 13, 187, 160, 166, 118, 111, 116, 101, 107, 100, 205, 39, 16, 167, 118, 111, 116, 101, 107, 101, 121, 196, 32, 112, 27, 215, 251, 145, 43, 7, 179, 8, 17, 255, 40, 29, 159, 238, 149, 99, 229, 128, 46, 32, 38, 137, 35, 25, 37, 143, 119, 250, 147, 30, 136, 167, 118, 111, 116, 101, 108, 115, 116, 206, 0, 15, 66, 64]); // prettier-ignore @@ -208,6 +245,32 @@ describe('Multisig Functionality', () => { assert.deepStrictEqual(Buffer.from(finMsigBlob), allThreeBlob); assert.deepStrictEqual(Buffer.from(finMsigBlobTwo), allThreeBlob); }); + + it('should correctly handle a different sender', () => { + const oneAndThreeBlob = Buffer.from( + 'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgqJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGhc8RAQ3t8lAipybLbWaRa3EDKl0mLGwUpYzl0iBCHSflhJM6zboJiT10FnWtXqW0dUBzpj9yeqtSuSJyKb8Ml3YJkCqN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEICHMyArhQUJN+4MirQrVopinURoL2GOodR8RZxvZFZl5pHR5cGWjcGF5', + 'base64' + ); + const twoAndThreeBlob = Buffer.from( + 'g6Rtc2lng6ZzdWJzaWeTgaJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXiConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxED7h9lpTNsdbX5QOixjISlwYrju3bEc412D4HH47w7YfWbAms8jih6kJrWIw/qVyzPHyW60YaFBk5Mi8SDYk/cAgqJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGhc8RAQ3t8lAipybLbWaRa3EDKl0mLGwUpYzl0iBCHSflhJM6zboJiT10FnWtXqW0dUBzpj9yeqtSuSJyKb8Ml3YJkCqN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boujYW10zQPopWNsb3NlxCBA6TSSiCVky86cWaabZ1Qmiemhw6Kp6ltlpuikQh/8V6NmZWXNA+iiZnbN8xWjZ2VurGRldm5ldC12MzguMKJnaMQg/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp2ibHbN9v2kbm90ZcQIRSYiABhShvujcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEICHMyArhQUJN+4MirQrVopinURoL2GOodR8RZxvZFZl5pHR5cGWjcGF5', + 'base64' + ); + const allThreeBlob = Buffer.from( + 'g6Rtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RAWLcyn6nB68yo/PUHXB21wyTy+PhHgMQNOIXPTGB96faZP2xqpQ8IFlIR2LPotlX68ylK8MCl82SUfMR4FYyzAIKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQPuH2WlM2x1tflA6LGMhKXBiuO7dsRzjXYPgcfjvDth9ZsCazyOKHqQmtYjD+pXLM8fJbrRhoUGTkyLxINiT9wCConBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaFzxEBDe3yUCKnJsttZpFrcQMqXSYsbBSljOXSIEIdJ+WEkzrNugmJPXQWda1epbR1QHOmP3J6q1K5InIpvwyXdgmQKo3RocgKhdgGkc2ducsQgjZK0iZABc6BN+kNZo2ZqavzqLEKgXdnB9z7rpUeAN+mjdHhui6NhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0D6KJmds3zFaNnZW6sZGV2bmV0LXYzOC4womdoxCD+s2w5EBQ5AMPaVULKGDawD9L4GVkSV80j9gQvmMg2naJsds32/aRub3RlxAhFJiIAGFKG+6NyY3bEIHts4k/rW6zAsWTinCIsV/X2PcOH1DkEglhBHF/hD3wCo3NuZMQgIczICuFBQk37gyKtCtWimKdRGgvYY6h1HxFnG9kVmXmkdHlwZaNwYXk=', + 'base64' + ); + + const finMsigBlob = algosdk.mergeMultisigTransactions([ + new Uint8Array(twoAndThreeBlob), + new Uint8Array(oneAndThreeBlob), + ]); + const finMsigBlobTwo = algosdk.mergeMultisigTransactions([ + new Uint8Array(oneAndThreeBlob), + new Uint8Array(twoAndThreeBlob), + ]); + assert.deepStrictEqual(Buffer.from(finMsigBlob), allThreeBlob); + assert.deepStrictEqual(Buffer.from(finMsigBlobTwo), allThreeBlob); + }); }); describe('read-only transaction methods should work as expected on multisig transactions', () => { diff --git a/tests/8.LogicSig.js b/tests/8.LogicSig.js deleted file mode 100644 index e34ae233b..000000000 --- a/tests/8.LogicSig.js +++ /dev/null @@ -1,939 +0,0 @@ -const assert = require('assert'); -const algosdk = require('../index'); -const logic = require('../src/logic/logic'); -const utils = require('../src/utils/utils'); - -describe('LogicSig functionality', () => { - describe('Basic logic sig', () => { - it('should work on valid program', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - const programHash = - '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; - const pk = algosdk.decodeAddress(programHash).publicKey; - let lsig = algosdk.makeLogicSig(program); - assert.equal(lsig.logic, program); - assert.equal(lsig.args, undefined); - assert.equal(lsig.sig, undefined); - assert.equal(lsig.msig, undefined); - assert.equal(lsig.address(), programHash); - - let verified = lsig.verify(pk); - assert.equal(verified, true); - - const args = [Uint8Array.from([1, 2, 3]), Uint8Array.from([4, 5, 6])]; - lsig = algosdk.makeLogicSig(program, args); - assert.equal(lsig.logic, program); - assert.deepEqual(lsig.args, args); - assert.equal(lsig.sig, undefined); - assert.equal(lsig.msig, undefined); - - verified = lsig.verify(pk); - assert.equal(verified, true); - - // check serialization - const encoded = lsig.toByte(); - const decoded = algosdk.logicSigFromByte(encoded); - assert.deepStrictEqual(decoded, lsig); - }); - it('should fail on tampered program', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - const programHash = - '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; - const pk = algosdk.decodeAddress(programHash).publicKey; - - program[3] = 2; - const lsig = algosdk.makeLogicSig(program); - const verified = lsig.verify(pk); - assert.equal(verified, false); - }); - it('should fail on invalid program', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - program[0] = 128; - assert.throws(() => algosdk.makeLogicSig(program)); - }); - }); - - describe('Signatures', () => { - it('should sign a basic transaction', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - const lsig = algosdk.makeLogicSig(program); - - const from = lsig.address(); - const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; - const fee = 10; - const amount = 847; - const firstRound = 51; - const lastRound = 61; - const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; - const rekeyTo = - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - let closeRemainderTo; - const txn = { - from, - to, - fee, - amount, - closeRemainderTo, - firstRound, - lastRound, - note, - genesisHash, - genesisID, - reKeyTo: rekeyTo, - }; - - const actual = algosdk.signLogicSigTransaction(txn, lsig); - const expected = { - txID: 'D7H6THOHOCEWJYNWMKHVOR2W36KAJXSGG6DMNTHTBWONBCG4XATA', - blob: new Uint8Array( - Buffer.from( - 'gqRsc2lngaFsxAUBIAEBIqN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQg9nYtrHWxmX1sLJYYBoBQdJDXlREv/n+3YLJzivnH8a2kdHlwZaNwYXk=', - 'base64' - ) - ), - }; - - assert.deepStrictEqual(actual, expected); - }); - - it('should sign an already built transaction', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - const lsig = algosdk.makeLogicSig(program); - - const from = lsig.address(); - const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; - const fee = 10; - const amount = 847; - const firstRound = 51; - const lastRound = 61; - const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; - const rekeyTo = - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - let closeRemainderTo; - const suggestedParams = { - flatFee: false, - fee, - firstRound, - lastRound, - genesisHash, - genesisID, - }; - const txnObject = { - from, - to, - amount, - suggestedParams, - closeRemainderTo, - note, - rekeyTo, - }; - const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject( - txnObject - ); - - const actual = algosdk.signLogicSigTransaction(txn, lsig); - const expected = { - txID: 'D7H6THOHOCEWJYNWMKHVOR2W36KAJXSGG6DMNTHTBWONBCG4XATA', - blob: new Uint8Array( - Buffer.from( - 'gqRsc2lngaFsxAUBIAEBIqN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQg9nYtrHWxmX1sLJYYBoBQdJDXlREv/n+3YLJzivnH8a2kdHlwZaNwYXk=', - 'base64' - ) - ), - }; - - assert.deepStrictEqual(actual, expected); - }); - - it('should sign a transaction with a different AuthAddr', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - const lsig = algosdk.makeLogicSig(program); - - const from = 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; - const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; - const fee = 10; - const amount = 847; - const firstRound = 51; - const lastRound = 61; - const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; - const rekeyTo = - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - let closeRemainderTo; - const txn = { - from, - to, - fee, - amount, - closeRemainderTo, - firstRound, - lastRound, - note, - genesisHash, - genesisID, - reKeyTo: rekeyTo, - }; - - const actual = algosdk.signLogicSigTransaction(txn, lsig); - const expected = { - txID: 'A6G4CMEV7QHLTMWDGU6BRYYVG3IXSSFTVDISEPALUHKIP4DNHQ4A', - blob: new Uint8Array( - Buffer.from( - 'g6Rsc2lngaFsxAUBIAEBIqRzZ25yxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQguw62NBVKGAtqJ03XdSlcNtO6eq5rXbDMEMVGLbDzMN+kdHlwZaNwYXk=', - 'base64' - ) - ), - }; - - assert.deepStrictEqual(actual, expected); - }); - }); -}); - -describe('Logic validation', () => { - describe('Varint', () => { - it('should parse binary data correctly', () => { - let data = Uint8Array.from([1]); - let [value, length] = logic.parseUvarint(data); - assert.equal(length, 1); - assert.equal(value, 1); - - data = Uint8Array.from([123]); - [value, length] = logic.parseUvarint(data); - assert.equal(length, 1); - assert.equal(value, 123); - - data = Uint8Array.from([200, 3]); - [value, length] = logic.parseUvarint(data); - assert.equal(length, 2); - assert.equal(value, 456); - }); - }); - describe('Const blocks', () => { - it('should parse int const block correctly', () => { - const data = Uint8Array.from([32, 5, 0, 1, 200, 3, 123, 2]); - const size = logic.checkIntConstBlock(data, 0); - assert.equal(size, data.length); - }); - it('should parse bytes const block correctly', () => { - const data = Uint8Array.from([ - 38, - 2, - 13, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 48, - 49, - 50, - 51, - 2, - 1, - 2, - ]); - const size = logic.checkByteConstBlock(data, 0); - assert.equal(size, data.length); - }); - it('should parse int push op correctly', () => { - const data = Uint8Array.from([0x81, 0x80, 0x80, 0x04]); - const size = logic.checkPushIntOp(data, 0); - assert.strictEqual(size, data.length); - }); - it('should parse byte push op correctly', () => { - const data = Uint8Array.from([ - 0x80, - 0x0b, - 0x68, - 0x65, - 0x6c, - 0x6c, - 0x6f, - 0x20, - 0x77, - 0x6f, - 0x72, - 0x6c, - 0x64, - ]); - const size = logic.checkPushByteOp(data, 0); - assert.strictEqual(size, data.length); - }); - }); - describe('Program checker', () => { - it('should assess correct programs right', () => { - let program = Uint8Array.from([1, 32, 1, 1, 34]); - let result = logic.checkProgram(program); - assert.equal(result, true); - - result = logic.checkProgram(program, [Uint8Array.from('a' * 10)]); - assert.equal(result, true); - - program = utils.concatArrays(program, Uint8Array.from('\x22' * 10)); - result = logic.checkProgram(program, [Uint8Array.from('a' * 10)]); - assert.equal(result, true); - }); - it('should fail on long input', () => { - assert.throws(() => logic.checkProgram(), new Error('empty program')); - let program = Uint8Array.from([1, 32, 1, 1, 34]); - assert.throws( - () => logic.checkProgram(program, [new Uint8Array(1000).fill(55)]), - new Error('program too long') - ); - - program = utils.concatArrays(program, new Uint8Array(1000).fill(34)); - assert.throws( - () => logic.checkProgram(program), - new Error('program too long') - ); - }); - it('should fail on invalid program', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34, 255]); - assert.throws( - () => logic.checkProgram(program), - new Error('invalid instruction') - ); - }); - it('should fail on invalid args', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - assert.throws( - () => logic.checkProgram(program, '123'), - new Error('invalid arguments') - ); - }); - it('should fail on costly program', () => { - let program = Uint8Array.from([1, 38, 1, 1, 1, 40, 2]); // byte 0x01 + keccak256 - let result = logic.checkProgram(program); - assert.equal(result, true); - - // 10x keccak256 more is fine - program = utils.concatArrays(program, new Uint8Array(10).fill(2)); - result = logic.checkProgram(program); - assert.equal(result, true); - - // 800x keccak256 more is too costly - program = utils.concatArrays(program, new Uint8Array(800).fill(2)); - // old versions - const oldVersions = [0x1, 0x2, 0x3]; - let i; - for (i = 0; i < oldVersions.length; i++) { - program[0] = oldVersions[i]; - assert.throws( - () => logic.checkProgram(program), - new Error( - 'program too costly for Teal version < 4. consider using v4.' - ) - ); - } - // new versions - const newVersions = [0x4]; - for (i = 0; i < newVersions.length; i++) { - program[0] = newVersions[i]; - assert.ok(logic.checkProgram(program)); - } - }); - it('should support TEAL v2 opcodes', () => { - assert.ok(logic.langspecEvalMaxVersion >= 2); - assert.ok(logic.langspecLogicSigVersion >= 2); - - // balance - let program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x60]); // int 0; balance - let result = logic.checkProgram(program); - assert.equal(result, true); - - // app_opted_in - program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x61]); // int 0; int 0; app_opted_in - result = logic.checkProgram(program); - assert.equal(result, true); - - // 800x keccak256 more is to costly - // prettier-ignore - program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x70, 0x00 ]); // int 0; int 0; asset_holding_get Balance - result = logic.checkProgram(program); - assert.equal(result, true); - }); - it('should support TEAL v3 opcodes', () => { - assert.ok(logic.langspecEvalMaxVersion >= 3); - assert.ok(logic.langspecLogicSigVersion >= 3); - - // min_balance - let program = Uint8Array.from([0x03, 0x20, 0x01, 0x00, 0x22, 0x78]); // int 0; min_balance - assert.ok(logic.checkProgram(program)); - - // pushbytes - program = Uint8Array.from([ - 0x03, - 0x20, - 0x01, - 0x00, - 0x22, - 0x80, - 0x02, - 0x68, - 0x69, - 0x48, - ]); // int 0; pushbytes "hi"; pop - assert.ok(logic.checkProgram(program)); - - // pushint - program = Uint8Array.from([ - 0x03, - 0x20, - 0x01, - 0x00, - 0x22, - 0x81, - 0x01, - 0x48, - ]); // int 0; pushint 1; pop - assert.ok(logic.checkProgram(program)); - - // swap - program = Uint8Array.from([ - 0x03, - 0x20, - 0x02, - 0x00, - 0x01, - 0x22, - 0x23, - 0x4c, - 0x48, - ]); // int 0; int 1; swap; pop - assert.ok(logic.checkProgram(program)); - }); - it('should support TEAL v4 opcodes', () => { - assert.ok(logic.langspecEvalMaxVersion >= 4); - - // divmodw - let program = Uint8Array.from([ - 0x04, - 0x20, - 0x03, - 0x01, - 0x00, - 0x02, - 0x22, - 0x81, - 0xd0, - 0x0f, - 0x23, - 0x24, - 0x1f, - ]); // int 1; pushint 2000; int 0; int 2; divmodw - assert.ok(logic.checkProgram(program)); - - // gloads i - program = Uint8Array.from([0x04, 0x20, 0x01, 0x00, 0x22, 0x3b, 0x00]); // int 0; gloads 0 - assert.ok(logic.checkProgram(program)); - - // callsub - program = Uint8Array.from([ - 0x04, - 0x20, - 0x02, - 0x01, - 0x02, - 0x22, - 0x88, - 0x00, - 0x02, - 0x23, - 0x12, - 0x49, - ]); // int 1; callsub double; int 2; ==; double: dup; - assert.ok(logic.checkProgram(program)); - - // b>= - program = Uint8Array.from([ - 0x04, - 0x26, - 0x02, - 0x01, - 0x11, - 0x01, - 0x10, - 0x28, - 0x29, - 0xa7, - ]); // byte 0x11; byte 0x10; b>= - assert.ok(logic.checkProgram(program)); - - // b^ - program = Uint8Array.from([ - 0x04, - 0x26, - 0x03, - 0x01, - 0x11, - 0x01, - 0x10, - 0x01, - 0x01, - 0x28, - 0x29, - 0xad, - 0x2a, - 0x12, - ]); // byte 0x11; byte 0x10; b>= - assert.ok(logic.checkProgram(program)); - - // callsub, retsub - program = Uint8Array.from([ - 0x04, - 0x20, - 0x02, - 0x01, - 0x02, - 0x22, - 0x88, - 0x00, - 0x03, - 0x23, - 0x12, - 0x43, - 0x49, - 0x08, - 0x89, - ]); // int 1; callsub double; int 2; ==; return; double: dup; +; retsub; - assert.ok(logic.checkProgram(program)); - - // loop - program = Uint8Array.from([ - 0x04, - 0x20, - 0x04, - 0x01, - 0x02, - 0x0a, - 0x10, - 0x22, - 0x23, - 0x0b, - 0x49, - 0x24, - 0x0c, - 0x40, - 0xff, - 0xf8, - 0x25, - 0x12, - ]); // int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; == - assert.ok(logic.checkProgram(program)); - }); - }); -}); - -describe('Template logic validation', () => { - describe('Split', () => { - it('should match the goldens', () => { - // Inputs - const owner = - 'WO3QIJ6T4DZHBX5PWJH26JLHFSRT7W7M2DJOULPXDTUS6TUX7ZRIO4KDFY'; - const receivers = [ - 'W6UUUSEAOGLBHT7VFT4H2SDATKKSG6ZBUIJXTZMSLW36YS44FRP5NVAU7U', - 'XCIBIN7RT4ZXGBMVAMU3QS6L5EKB7XGROC5EPCNHHYXUIBAA5Q6C5Y7NEU', - ]; - const rat1 = 100; - const rat2 = 30; - const expiryRound = 123456; - const minPay = 10000; - const maxFee = 5000000; - const split = new algosdk.LogicTemplates.Split( - owner, - receivers[0], - receivers[1], - rat1, - rat2, - expiryRound, - minPay, - maxFee - ); - // Outputs - const goldenProgram = - 'ASAIAcCWsQICAMDEBx5kkE4mAyCztwQn0+DycN+vsk+vJWcsoz/b7NDS6i33HOkvTpf+YiC3qUpIgHGWE8/1LPh9SGCalSN7IaITeeWSXbfsS5wsXyC4kBQ38Z8zcwWVAym4S8vpFB/c0XC6R4mnPi9EBADsPDEQIhIxASMMEDIEJBJAABkxCSgSMQcyAxIQMQglEhAxAiEEDRAiQAAuMwAAMwEAEjEJMgMSEDMABykSEDMBByoSEDMACCEFCzMBCCEGCxIQMwAIIQcPEBA='; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = split.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - 'KPYGWKTV7CKMPMTLQRNGMEQRSYTYDHUOFNV4UDSBDLC44CLIJPQWRTCPBU'; - assert.deepStrictEqual(goldenAddress, split.getAddress()); - }); - }); - describe('HTLC', () => { - it('sha256 should match the goldens', () => { - // Inputs - const owner = - '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; - const receiver = - '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; - const hashFn = 'sha256'; - const hashImg = 'EHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8='; - const expiryRound = 600000; - const maxFee = 1000; - const htlc = new algosdk.LogicTemplates.HTLC( - owner, - receiver, - hashFn, - hashImg, - expiryRound, - maxFee - ); - // Outputs - const goldenProgram = - 'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IBB2YRNPIfx8AiI9UKues2ALw//DcSQjoeR7sfmp2/VfIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQEpEhAxCSoSMQIlDRAREA=='; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = htlc.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - 'FBZIR3RWVT2BTGVOG25H3VAOLVD54RTCRNRLQCCJJO6SVSCT5IVDYKNCSU'; - assert.deepStrictEqual(goldenAddress, htlc.getAddress()); - const goldenLtxn = - 'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgEHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8g/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItASkSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEIChyiO42rPQZmq42un3UDl1H3kZii2K4CElLvSrIU+oqpHR5cGWjcGF5'; - const o = { - from: goldenAddress, - to: receiver, - fee: 0, - amount: 0, - closeRemainderTo: receiver, - firstRound: 1, - lastRound: 100, - genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=', - type: 'pay', - }; - const preImageAsBase64 = 'cHJlaW1hZ2U='; - const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock( - htlc.getProgram(), - o, - preImageAsBase64 - ); - assert.deepEqual(Buffer.from(goldenLtxn, 'base64'), actualTxn.blob); - }); - it('keccak256 should match the goldens', () => { - // Inputs - const owner = - '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; - const receiver = - '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; - const hashFn = 'keccak256'; - const hashImg = 'D7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAc='; - const expiryRound = 600000; - const maxFee = 1000; - const htlc = new algosdk.LogicTemplates.HTLC( - owner, - receiver, - hashFn, - hashImg, - expiryRound, - maxFee - ); - // Outputs - const goldenProgram = - 'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IA+3eDK7wazksjUplLNJM7nLifv/Q2y5AOjjjqHMsKAHIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQIpEhAxCSoSMQIlDRAREA=='; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = htlc.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - '3MJ6JY3P6AU4R6I2RASYSAOPNI3QMWPZ7HYXJRNRGBIAXCHAY7QZRBH5PQ'; - assert.deepStrictEqual(goldenAddress, htlc.getAddress()); - const goldenLtxn = - 'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgD7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAcg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItAikSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEINsT5ONv8CnI+RqIJYkBz2o3Bln5+fF0xbEwUAuI4MfhpHR5cGWjcGF5'; - const o = { - from: goldenAddress, - to: receiver, - fee: 0, - amount: 0, - closeRemainderTo: receiver, - firstRound: 1, - lastRound: 100, - genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=', - type: 'pay', - }; - const preImageAsBase64 = 'cHJlaW1hZ2U='; - const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock( - htlc.getProgram(), - o, - preImageAsBase64 - ); - assert.deepEqual(Buffer.from(goldenLtxn, 'base64'), actualTxn.blob); - }); - it('other hash function should fail', () => { - // Inputs - const owner = - '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; - const receiver = - '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; - const hashFn = 'made-up-hash-fn'; - const hashImg = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='; - const expiryRound = 600000; - const maxFee = 1000; - assert.throws( - () => - new algosdk.LogicTemplates.HTLC( - owner, - receiver, - hashFn, - hashImg, - expiryRound, - maxFee - ) - ); - }); - }); - describe('Limit Order', () => { - it('should match the goldens', () => { - // Inputs - const owner = - '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; - const assetid = 12345; - const ratn = 30; - const ratd = 100; - const expiryRound = 123456; - const minTrade = 10000; - const maxFee = 5000000; - const limitOrder = new algosdk.LogicTemplates.LimitOrder( - owner, - assetid, - ratn, - ratd, - expiryRound, - minTrade, - maxFee - ); - // Outputs - const goldenProgram = - 'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ'; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = limitOrder.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - 'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I'; - assert.deepStrictEqual(goldenAddress, limitOrder.getAddress()); - const secretKey = Buffer.from( - 'DTKVj7KMON3GSWBwMX9McQHtaDDi8SDEBi0bt4rOxlHNRahLa0zVG+25BDIaHB1dSoIHIsUQ8FFcdnCdKoG+Bg==', - 'base64' - ); - const actualBlob = algosdk.LogicTemplates.getSwapAssetsTransaction( - actualBytes, - 3000, - 10000, - secretKey, - 10, - 1234, - 2234, - 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=' - ); - const expectedTxn1 = Buffer.from( - 'gqRsc2lngaFsxLcBIAoAAcCWsQICkE4EuWBkHsDEByYBIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMRYiEjEQIxIQMQEkDhAyBCMSQABVMgQlEjEIIQQNEDEJMgMSEDMBECEFEhAzAREhBhIQMwEUKBIQMwETMgMSEDMBEiEHHTUCNQExCCEIHTUENQM0ATQDDUAAJDQBNAMSNAI0BA8QQAAWADEJKBIxAiEJDRAxBzIDEhAxCCISEBCjdHhuiaNhbXTNJxCjZmVlzQisomZ2zQTSomdoxCB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaaNncnDEIKz368WOGpdE/Ww0L8wUu5Ly2u2bpG3ZSMKCJvcvGApTomx2zQi6o3JjdsQgzUWoS2tM1RvtuQQyGhwdXUqCByLFEPBRXHZwnSqBvgajc25kxCBd4Wnq60VaWXeVmuPt0x1XgMQSyP4nUnlh5G2Rhyo+taR0eXBlo3BheQ==', - 'base64' - ); - const expectedTxn2 = Buffer.from( - 'gqNzaWfEQKXv8Z6OUDNmiZ5phpoQJHmfKyBal4gBZLPYsByYnlXCAlXMBeVFG5CLP1k5L6BPyEG2/XIbjbyM0CGG55CxxAKjdHhuiqRhYW10zQu4pGFyY3bEIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITo2ZlZc0JJKJmds0E0qJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCs9+vFjhqXRP1sNC/MFLuS8trtm6Rt2UjCgib3LxgKU6Jsds0IuqNzbmTEIM1FqEtrTNUb7bkEMhocHV1KggcixRDwUVx2cJ0qgb4GpHR5cGWlYXhmZXKkeGFpZM0wOQ==', - 'base64' - ); - const expectedBlob = Buffer.concat([expectedTxn1, expectedTxn2]); - assert.deepEqual(expectedBlob, actualBlob); - }); - }); - describe('Periodic payment', () => { - it('should match the goldens', () => { - // Inputs - const receiver = - 'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I'; - const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg='; - const amount = 500000; - const withdrawalWindow = 95; - const period = 100; - const expiryRound = 2445756; - const maxFee = 1000; - const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment( - receiver, - amount, - withdrawalWindow, - period, - expiryRound, - maxFee, - leaseb64 - ); - // Outputs - const goldenProgram = - 'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ'; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = periodicPayment.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - 'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY'; - assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress()); - const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; - const goldenStx = - 'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk='; - const goldenStxBlob = Buffer.from(goldenStx, 'base64'); - const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction( - actualBytes, - 0, - 1200, - goldenGenesisHash - ); - const expectedDict = algosdk.decodeObj(goldenStxBlob); - const actualDict = algosdk.decodeObj(stx.blob); - assert.deepEqual(expectedDict, actualDict); - }); - }); - describe('Limit Order', () => { - it('should match the goldens', () => { - // Inputs - const owner = - '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; - const assetid = 12345; - const ratn = 30; - const ratd = 100; - const expiryRound = 123456; - const minTrade = 10000; - const maxFee = 5000000; - const limitOrder = new algosdk.LogicTemplates.LimitOrder( - owner, - assetid, - ratn, - ratd, - expiryRound, - minTrade, - maxFee - ); - // Outputs - const goldenProgram = - 'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ'; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = limitOrder.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - 'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I'; - assert.deepStrictEqual(goldenAddress, limitOrder.getAddress()); - }); - }); - describe('Periodic payment', () => { - it('should match the goldens', () => { - // Inputs - const receiver = - 'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I'; - const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg='; - const amount = 500000; - const withdrawalWindow = 95; - const period = 100; - const expiryRound = 2445756; - const maxFee = 1000; - const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment( - receiver, - amount, - withdrawalWindow, - period, - expiryRound, - maxFee, - leaseb64 - ); - // Outputs - const goldenProgram = - 'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ'; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = periodicPayment.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - 'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY'; - assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress()); - const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; - const goldenStx = - 'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk='; - const goldenStxBlob = Buffer.from(goldenStx, 'base64'); - const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction( - actualBytes, - 0, - 1200, - goldenGenesisHash - ); - const expectedDict = algosdk.decodeObj(goldenStxBlob); - const actualDict = algosdk.decodeObj(stx.blob); - assert.deepEqual(expectedDict, actualDict); - }); - }); - describe('Dynamic Fee', () => { - it('should match the goldens', () => { - // Inputs - const receiver = - '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; - const amount = 5000; - const firstValid = 12345; - const lastValid = 12346; - const closeRemainder = - '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; - const artificialLease = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; - const leaseBytes = new Uint8Array(Buffer.from(artificialLease, 'base64')); - const dynamicFee = new algosdk.LogicTemplates.DynamicFee( - receiver, - amount, - firstValid, - lastValid, - closeRemainder, - leaseBytes - ); - // Outputs - const goldenProgram = - 'ASAFAgGIJ7lgumAmAyD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEyDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+SB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaTIEIhIzABAjEhAzAAcxABIQMwAIMQESEDEWIxIQMRAjEhAxBygSEDEJKRIQMQgkEhAxAiUSEDEEIQQSEDEGKhIQ'; - const goldenBytes = Buffer.from(goldenProgram, 'base64'); - const actualBytes = dynamicFee.getProgram(); - assert.deepStrictEqual(goldenBytes, actualBytes); - const goldenAddress = - 'GCI4WWDIWUFATVPOQ372OZYG52EULPUZKI7Y34MXK3ZJKIBZXHD2H5C5TI'; - assert.deepStrictEqual(goldenAddress, dynamicFee.getAddress()); - const privateKeyOneB64 = - 'cv8E0Ln24FSkwDgGeuXKStOTGcze5u8yldpXxgrBxumFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT7w=='; - const privateKeyOne = Buffer.from(privateKeyOneB64, 'base64'); - const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; - const txnAndLsig = algosdk.LogicTemplates.signDynamicFee( - actualBytes, - privateKeyOne, - goldenGenesisHash - ); - const txnDict = txnAndLsig.txn; - const txnObj = new algosdk.Transaction(txnDict); - const txnBytes = txnObj.toByte(); - const goldenTxn = - 'iqNhbXTNE4ilY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmibHbNMDqibHjEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpo3JjdsQg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohOjc25kxCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76R0eXBlo3BheQ=='; - assert.deepStrictEqual( - new Uint8Array(Buffer.from(goldenTxn, 'base64')), - txnBytes - ); - const { lsig } = txnAndLsig; - const lsigBytes = lsig.toByte(); - const goldenLsig = - 'gqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYH'; - assert.deepStrictEqual( - new Uint8Array(Buffer.from(goldenLsig, 'base64')), - lsigBytes - ); - const privateKeyTwoB64 = - '2qjz96Vj9M6YOqtNlfJUOKac13EHCXyDty94ozCjuwwriI+jzFgStFx9E6kEk1l4+lFsW4Te2PY1KV8kNcccRg=='; - const privateKeyTwo = Buffer.from(privateKeyTwoB64, 'base64'); - const stxns = algosdk.LogicTemplates.getDynamicFeeTransactions( - txnDict, - lsig, - privateKeyTwo, - 1234, - firstValid, - lastValid - ); - const goldenStxns = - 'gqNzaWfEQJBNVry9qdpnco+uQzwFicUWHteYUIxwDkdHqY5Qw2Q8Fc2StrQUgN+2k8q4rC0LKrTMJQnE+mLWhZgMMJvq3QCjdHhuiqNhbXTOAAWq6qNmZWXOAATzvqJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76NzbmTEICuIj6PMWBK0XH0TqQSTWXj6UWxbhN7Y9jUpXyQ1xxxGpHR5cGWjcGF5gqRsc2lngqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYHo3R4boujYW10zROIpWNsb3NlxCDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+aNmZWXOAAWq6qJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiE6NzbmTEIIU9h0wnKapwajF0N7K4zy3orGLF+rQ8kLIk/vW6FhPvpHR5cGWjcGF5'; - const goldenStxnBytes = Buffer.from(goldenStxns, 'base64'); - assert.deepStrictEqual(new Uint8Array(goldenStxnBytes), stxns); - }); - }); -}); diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts new file mode 100644 index 000000000..b774aea3a --- /dev/null +++ b/tests/8.LogicSig.ts @@ -0,0 +1,1487 @@ +import assert from 'assert'; +import algosdk from '../index'; +import * as logic from '../src/logic/logic'; +import * as utils from '../src/utils/utils'; + +const sampleAccount1 = algosdk.mnemonicToSecretKey( + 'auction inquiry lava second expand liberty glass involve ginger illness length room item discover ahead table doctor term tackle cement bonus profit right above catch' +); +const sampleAccount2 = algosdk.mnemonicToSecretKey( + 'since during average anxiety protect cherry club long lawsuit loan expand embark forum theory winter park twenty ball kangaroo cram burst board host ability left' +); +const sampleAccount3 = algosdk.mnemonicToSecretKey( + 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor' +); + +// Multisig Golden Params +const sampleMultisigParams: algosdk.MultisigMetadata = { + version: 1, + threshold: 2, + addrs: [sampleAccount1.addr, sampleAccount2.addr, sampleAccount3.addr], +}; + +const sampleMultisigAddr = algosdk.multisigAddress(sampleMultisigParams); + +describe('LogicSig', () => { + describe('makeLogicSig', () => { + it('should work on valid program', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + const programHash = + '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; + const pk = algosdk.decodeAddress(programHash).publicKey; + let lsig = algosdk.makeLogicSig(program); + assert.strictEqual(lsig.logic, program); + assert.strictEqual(lsig.args, undefined); + assert.strictEqual(lsig.sig, undefined); + assert.strictEqual(lsig.msig, undefined); + assert.strictEqual(lsig.address(), programHash); + + let verified = lsig.verify(pk); + assert.strictEqual(verified, true); + + const args = [Uint8Array.from([1, 2, 3]), Uint8Array.from([4, 5, 6])]; + lsig = algosdk.makeLogicSig(program, args); + assert.strictEqual(lsig.logic, program); + assert.deepStrictEqual(lsig.args, args); + assert.strictEqual(lsig.sig, undefined); + assert.strictEqual(lsig.msig, undefined); + + verified = lsig.verify(pk); + assert.strictEqual(verified, true); + + // check serialization + const encoded = lsig.toByte(); + const decoded = algosdk.logicSigFromByte(encoded); + assert.deepStrictEqual(decoded, lsig); + }); + it('should fail on tampered program', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + const programHash = + '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; + const pk = algosdk.decodeAddress(programHash).publicKey; + + program[3] = 2; + const lsig = algosdk.makeLogicSig(program); + const verified = lsig.verify(pk); + assert.strictEqual(verified, false); + }); + it('should fail on invalid program', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + program[0] = 128; + assert.throws(() => algosdk.makeLogicSig(program)); + }); + }); + + describe('address', () => { + it('should produce the correct address', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + const programHash = + '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; + + const lsig = algosdk.makeLogicSig(program); + const address = lsig.address(); + + assert.deepStrictEqual(address, programHash); + }); + }); +}); + +describe('LogicSigAccount', () => { + describe('constructor', () => { + it('should work on valid program without args', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + + const lsigAccount = new algosdk.LogicSigAccount(program); + assert.deepStrictEqual(lsigAccount.lsig.logic, program); + assert.strictEqual(lsigAccount.lsig.args, undefined); + assert.strictEqual(lsigAccount.lsig.sig, undefined); + assert.strictEqual(lsigAccount.lsig.msig, undefined); + assert.strictEqual(lsigAccount.sigkey, undefined); + + // check serialization + const encoded = lsigAccount.toByte(); + const expectedEncoded = new Uint8Array( + Buffer.from('gaRsc2lngaFsxAUBIAEBIg==', 'base64') + ); + assert.deepStrictEqual(encoded, expectedEncoded); + + const decoded = algosdk.LogicSigAccount.fromByte(encoded); + assert.deepStrictEqual(decoded, lsigAccount); + }); + it('should work on valid program with args', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])]; + + const lsigAccount = new algosdk.LogicSigAccount(program, args); + assert.deepStrictEqual(lsigAccount.lsig.logic, program); + assert.deepStrictEqual(lsigAccount.lsig.args, args); + assert.strictEqual(lsigAccount.lsig.sig, undefined); + assert.strictEqual(lsigAccount.lsig.msig, undefined); + assert.strictEqual(lsigAccount.sigkey, undefined); + + // check serialization + const encoded = lsigAccount.toByte(); + const expectedEncoded = new Uint8Array( + Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64') + ); + assert.deepStrictEqual(encoded, expectedEncoded); + + const decoded = algosdk.LogicSigAccount.fromByte(encoded); + assert.deepStrictEqual(decoded, lsigAccount); + }); + it('should fail on invalid program', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + program[0] = 128; + assert.throws(() => new algosdk.LogicSigAccount(program)); + }); + }); + + describe('sign', () => { + it('should properly sign the program', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])]; + + const lsigAccount = new algosdk.LogicSigAccount(program, args); + lsigAccount.sign(sampleAccount1.sk); + + const expectedSig = new Uint8Array( + Buffer.from( + 'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==', + 'base64' + ) + ); + const expectedSigKey = algosdk.decodeAddress(sampleAccount1.addr) + .publicKey; + + assert.deepStrictEqual(lsigAccount.lsig.logic, program); + assert.deepStrictEqual(lsigAccount.lsig.args, args); + assert.deepStrictEqual(lsigAccount.lsig.sig, expectedSig); + assert.strictEqual(lsigAccount.lsig.msig, undefined); + assert.deepStrictEqual(lsigAccount.sigkey, expectedSigKey); + + // check serialization + const encoded = lsigAccount.toByte(); + const expectedEncoded = new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==', + 'base64' + ) + ); + assert.deepStrictEqual(encoded, expectedEncoded); + + const decoded = algosdk.LogicSigAccount.fromByte(encoded); + assert.deepStrictEqual(decoded, lsigAccount); + }); + }); + + describe('signMultisig', () => { + it('should properly sign the program', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])]; + + const lsigAccount = new algosdk.LogicSigAccount(program, args); + lsigAccount.signMultisig(sampleMultisigParams, sampleAccount1.sk); + + const expectedSig = new Uint8Array( + Buffer.from( + 'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==', + 'base64' + ) + ); + const expectedMsig: algosdk.EncodedMultisig = { + v: sampleMultisigParams.version, + thr: sampleMultisigParams.threshold, + subsig: sampleMultisigParams.addrs.map((addr) => ({ + pk: algosdk.decodeAddress(addr).publicKey, + })), + }; + expectedMsig.subsig[0].s = expectedSig; + + assert.deepStrictEqual(lsigAccount.lsig.logic, program); + assert.deepStrictEqual(lsigAccount.lsig.args, args); + assert.strictEqual(lsigAccount.lsig.sig, undefined); + assert.deepStrictEqual(lsigAccount.lsig.msig, expectedMsig); + assert.strictEqual(lsigAccount.sigkey, undefined); + + // check serialization + const encoded = lsigAccount.toByte(); + const expectedEncoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==', + 'base64' + ) + ); + assert.deepStrictEqual(encoded, expectedEncoded); + + const decoded = algosdk.LogicSigAccount.fromByte(encoded); + assert.deepStrictEqual(decoded, lsigAccount); + }); + }); + + describe('appendToMultisig', () => { + it('should properly append a signature', () => { + const msig1of3Encoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(msig1of3Encoded); + + lsigAccount.appendToMultisig(sampleAccount2.sk); + + const expectedSig1 = new Uint8Array( + Buffer.from( + 'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==', + 'base64' + ) + ); + const expectedSig2 = new Uint8Array( + Buffer.from( + 'ZLxV2+2RokHUKrZg9+FKuZmaUrOxcVjO/D9P58siQRStqT1ehAUCChemaYMDIk6Go4tqNsVUviBQ/9PuqLMECQ==', + 'base64' + ) + ); + const expectedMsig: algosdk.EncodedMultisig = { + v: sampleMultisigParams.version, + thr: sampleMultisigParams.threshold, + subsig: sampleMultisigParams.addrs.map((addr) => ({ + pk: algosdk.decodeAddress(addr).publicKey, + })), + }; + expectedMsig.subsig[0].s = expectedSig1; + expectedMsig.subsig[1].s = expectedSig2; + + assert.strictEqual(lsigAccount.lsig.sig, undefined); + assert.deepStrictEqual(lsigAccount.lsig.msig, expectedMsig); + assert.strictEqual(lsigAccount.sigkey, undefined); + + // check serialization + const encoded = lsigAccount.toByte(); + const expectedEncoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB', + 'base64' + ) + ); + assert.deepStrictEqual(encoded, expectedEncoded); + + const decoded = algosdk.LogicSigAccount.fromByte(encoded); + assert.deepStrictEqual(decoded, lsigAccount); + }); + }); + + describe('verify', () => { + it('should verify valid escrow', () => { + const escrowEncoded = new Uint8Array( + Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64') + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(escrowEncoded); + + assert.strictEqual(lsigAccount.verify(), true); + }); + + it('should verify valid single sig', () => { + const sigEncoded = new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded); + + assert.strictEqual(lsigAccount.verify(), true); + }); + + it('should fail single sig with wrong sig', () => { + const sigEncoded = new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded); + + // modify signature + lsigAccount.lsig.sig![0] = 0; + + assert.strictEqual(lsigAccount.verify(), false); + }); + + it('should verify valid multisig', () => { + const msigEncoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded); + + assert.strictEqual(lsigAccount.verify(), true); + }); + + it('should fail multisig with wrong sig', () => { + const msigEncoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded); + + // modify signature + lsigAccount.lsig.msig!.subsig[0].s[0] = 0; + + assert.strictEqual(lsigAccount.verify(), false); + }); + + it('should fail multisig that does not meet threshold', () => { + const msigBelowThresholdEncoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte( + msigBelowThresholdEncoded + ); + + assert.strictEqual(lsigAccount.verify(), false); + }); + }); + + describe('isDelegated', () => { + it('should be correct for escrow', () => { + const escrowEncoded = new Uint8Array( + Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64') + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(escrowEncoded); + + assert.strictEqual(lsigAccount.isDelegated(), false); + }); + + it('should be correct for single sig', () => { + const sigEncoded = new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded); + + assert.strictEqual(lsigAccount.isDelegated(), true); + }); + + it('should be correct for multisig', () => { + const msigEncoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded); + + assert.strictEqual(lsigAccount.isDelegated(), true); + }); + }); + + describe('address', () => { + it('should be correct for escrow', () => { + const escrowEncoded = new Uint8Array( + Buffer.from('gaRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIg==', 'base64') + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(escrowEncoded); + + const addr = lsigAccount.address(); + + const expectedAddr = + '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; + + assert.strictEqual(addr, expectedAddr); + }); + + it('should be correct for single sig', () => { + const sigEncoded = new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQEkTuAXRnn8sEID2M34YVKfO6u4Q3b0TZYS/k7dfMGMVkcojDO3vI9F0G1KdsP/vN1TWRvS1YfyLvC17TmNcvQKmc2lna2V5xCAbfsCwS+pht5aQl+bL9AfhCKcFNR0LyYq+sSIJqKuBeA==', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(sigEncoded); + + const addr = lsigAccount.address(); + + const expectedAddr = + 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA'; + + assert.strictEqual(addr, expectedAddr); + }); + + it('should be correct for multisig', () => { + const msigEncoded = new Uint8Array( + Buffer.from( + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB', + 'base64' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded); + + const addr = lsigAccount.address(); + + const expectedAddr = + 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM'; + + assert.strictEqual(addr, expectedAddr); + }); + }); +}); + +describe('signLogicSigTransaction', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])]; + + const otherAddr = + 'WTDCE2FEYM2VB5MKNXKLRSRDTSPR2EFTIGVH4GRW4PHGD6747GFJTBGT2A'; + + function testSign( + lsigObject: + | ReturnType + | algosdk.LogicSigAccount, + sender: string, + expected: { txID: string; blob: Uint8Array } + ) { + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + from: sender, + to: otherAddr, + amount: 5000, + suggestedParams: { + flatFee: true, + fee: 217000, + firstRound: 972508, + lastRound: 973508, + genesisID: 'testnet-v31.0', + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, + note: new Uint8Array([180, 81, 121, 57, 252, 250, 210, 113]), + }); + + const actual = algosdk.signLogicSigTransaction(txn, lsigObject); + + assert.deepStrictEqual(actual, expected); + } + + describe('with LogicSig', () => { + describe('escrow', () => { + const lsig = algosdk.makeLogicSig(program, args); + + it('should match expected when sender is LogicSig address', () => { + const sender = lsig.address(); + const expected = { + txID: 'SV3GD4AKRRX43F3V4V7GYYB6YCQEPULGUI6GKZO6GPJDKOO75NFA', + blob: new Uint8Array( + Buffer.from( + 'gqRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraR0eXBlo3BheQ==', + 'base64' + ) + ), + }; + testSign(lsig, sender, expected); + }); + + it('should match expected when sender is not LogicSig address', () => { + const sender = otherAddr; + const expected = { + txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ', + blob: new Uint8Array( + Buffer.from( + 'g6Rsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqRzZ25yxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqR0eXBlo3BheQ==', + 'base64' + ) + ), + }; + testSign(lsig, sender, expected); + }); + }); + + describe('single sig', () => { + const account = algosdk.mnemonicToSecretKey( + 'olympic cricket tower model share zone grid twist sponsor avoid eight apology patient party success claim famous rapid donor pledge bomb mystery security ability often' + ); + const lsig = algosdk.makeLogicSig(program, args); + lsig.sign(account.sk); + + it('should match expected when sender is LogicSig address', () => { + const sender = account.addr; + const expected = { + txID: 'EZB2N2TEFR5OOL76Z46ZMRUL3ZQQOYKRFIX6WSHQ5FWESHU4LZPA', + blob: new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQD4FPTlN+xK8ZXmf6jGKe46iUYtVLIq+bNenZS3YsBh+IQUtuSRiiRblYXTNDxmsuWxFpCmRmREd5Hzk/BLszgKjdHhuiqNhbXTNE4ijZmVlzgADT6iiZnbOAA7W3KNnZW6tdGVzdG5ldC12MzEuMKJnaMQgJgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dKibHbOAA7axKRub3RlxAi0UXk5/PrScaNyY3bEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKo3NuZMQgXmdPHAru7DdxiY9hx2/10koZeT4skfoIUWJj44Vz6kKkdHlwZaNwYXk=', + 'base64' + ) + ), + }; + testSign(lsig, sender, expected); + }); + + it('should throw an error when sender is not LogicSig address', () => { + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + from: otherAddr, + to: otherAddr, + amount: 5000, + suggestedParams: { + flatFee: true, + fee: 217000, + firstRound: 972508, + lastRound: 973508, + genesisID: 'testnet-v31.0', + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, + note: new Uint8Array([180, 81, 121, 57, 252, 250, 210, 113]), + }); + + assert.throws( + () => algosdk.signLogicSigTransaction(txn, lsig), + (err) => + err.message === + 'Logic signature verification failed. Ensure the program and signature are valid.' + ); + }); + }); + + describe('multisig', () => { + const lsig = algosdk.makeLogicSig(program, args); + lsig.sign(sampleAccount1.sk, sampleMultisigParams); + lsig.appendToMultisig(sampleAccount2.sk); + + it('should match expected when sender is LogicSig address', () => { + const sender = sampleMultisigAddr; + const expected = { + txID: 'UGGT5EZXG2OBPGWTEINC65UXIQ6UVAAOTNKRRCRAUCZH4FWJTVQQ', + blob: new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5', + 'base64' + ) + ), + }; + testSign(lsig, sender, expected); + }); + + it('should match expected when sender is not LogicSig address', () => { + const sender = otherAddr; + const expected = { + txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ', + blob: new Uint8Array( + Buffer.from( + 'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKpHR5cGWjcGF5', + 'base64' + ) + ), + }; + testSign(lsig, sender, expected); + }); + }); + }); + + describe('with LogicSigAccount', () => { + describe('escrow', () => { + const lsigAccount = new algosdk.LogicSigAccount(program, args); + + it('should match expected when sender is LogicSig address', () => { + const sender = lsigAccount.address(); + const expected = { + txID: 'SV3GD4AKRRX43F3V4V7GYYB6YCQEPULGUI6GKZO6GPJDKOO75NFA', + blob: new Uint8Array( + Buffer.from( + 'gqRsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraR0eXBlo3BheQ==', + 'base64' + ) + ), + }; + testSign(lsigAccount, sender, expected); + }); + + it('should match expected when sender is not LogicSig address', () => { + const sender = otherAddr; + const expected = { + txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ', + blob: new Uint8Array( + Buffer.from( + 'g6Rsc2lngqNhcmeSxAEBxAICA6FsxAUBIAEBIqRzZ25yxCD2di2sdbGZfWwslhgGgFB0kNeVES/+f7dgsnOK+cfxraN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqR0eXBlo3BheQ==', + 'base64' + ) + ), + }; + testSign(lsigAccount, sender, expected); + }); + }); + + describe('single sig', () => { + const account = algosdk.mnemonicToSecretKey( + 'olympic cricket tower model share zone grid twist sponsor avoid eight apology patient party success claim famous rapid donor pledge bomb mystery security ability often' + ); + const lsigAccount = new algosdk.LogicSigAccount(program, args); + lsigAccount.sign(account.sk); + + it('should match expected when sender is LogicSig address', () => { + const sender = account.addr; + const expected = { + txID: 'EZB2N2TEFR5OOL76Z46ZMRUL3ZQQOYKRFIX6WSHQ5FWESHU4LZPA', + blob: new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQD4FPTlN+xK8ZXmf6jGKe46iUYtVLIq+bNenZS3YsBh+IQUtuSRiiRblYXTNDxmsuWxFpCmRmREd5Hzk/BLszgKjdHhuiqNhbXTNE4ijZmVlzgADT6iiZnbOAA7W3KNnZW6tdGVzdG5ldC12MzEuMKJnaMQgJgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dKibHbOAA7axKRub3RlxAi0UXk5/PrScaNyY3bEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKo3NuZMQgXmdPHAru7DdxiY9hx2/10koZeT4skfoIUWJj44Vz6kKkdHlwZaNwYXk=', + 'base64' + ) + ), + }; + testSign(lsigAccount, sender, expected); + }); + + it('should match expected when sender is not LogicSig address', () => { + const sender = otherAddr; + const expected = { + txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ', + blob: new Uint8Array( + Buffer.from( + 'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqNzaWfEQD4FPTlN+xK8ZXmf6jGKe46iUYtVLIq+bNenZS3YsBh+IQUtuSRiiRblYXTNDxmsuWxFpCmRmREd5Hzk/BLszgKkc2ducsQgXmdPHAru7DdxiY9hx2/10koZeT4skfoIUWJj44Vz6kKjdHhuiqNhbXTNE4ijZmVlzgADT6iiZnbOAA7W3KNnZW6tdGVzdG5ldC12MzEuMKJnaMQgJgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dKibHbOAA7axKRub3RlxAi0UXk5/PrScaNyY3bEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKo3NuZMQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+YqkdHlwZaNwYXk=', + 'base64' + ) + ), + }; + testSign(lsigAccount, sender, expected); + }); + }); + + describe('multisig', () => { + const lsigAccount = new algosdk.LogicSigAccount(program, args); + lsigAccount.signMultisig(sampleMultisigParams, sampleAccount1.sk); + lsigAccount.appendToMultisig(sampleAccount2.sk); + + it('should match expected when sender is LogicSig address', () => { + const sender = sampleMultisigAddr; + const expected = { + txID: 'UGGT5EZXG2OBPGWTEINC65UXIQ6UVAAOTNKRRCRAUCZH4FWJTVQQ', + blob: new Uint8Array( + Buffer.from( + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5', + 'base64' + ) + ), + }; + testSign(lsigAccount, sender, expected); + }); + + it('should match expected when sender is not LogicSig address', () => { + const sender = otherAddr; + const expected = { + txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ', + blob: new Uint8Array( + Buffer.from( + 'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKpHR5cGWjcGF5', + 'base64' + ) + ), + }; + testSign(lsigAccount, sender, expected); + }); + }); + }); + + it('should sign a raw transaction object', () => { + const lsig = algosdk.makeLogicSig(program); + + const from = lsig.address(); + const to = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; + const fee = 10; + const amount = 847; + const firstRound = 51; + const lastRound = 61; + const note = new Uint8Array([123, 12, 200]); + const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; + const genesisID = ''; + const rekeyTo = + 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; + let closeRemainderTo; + const txn = { + from, + to, + fee, + amount, + closeRemainderTo, + firstRound, + lastRound, + note, + genesisHash, + genesisID, + reKeyTo: rekeyTo, + }; + + const actual = algosdk.signLogicSigTransaction(txn, lsig); + const expected = { + txID: 'D7H6THOHOCEWJYNWMKHVOR2W36KAJXSGG6DMNTHTBWONBCG4XATA', + blob: new Uint8Array( + Buffer.from( + 'gqRsc2lngaFsxAUBIAEBIqN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQg9nYtrHWxmX1sLJYYBoBQdJDXlREv/n+3YLJzivnH8a2kdHlwZaNwYXk=', + 'base64' + ) + ), + }; + + assert.deepStrictEqual(actual, expected); + }); +}); + +describe('Program validation', () => { + describe('Varint', () => { + it('should parse binary data correctly', () => { + let data = Uint8Array.from([1]); + let [value, length] = logic.parseUvarint(data); + assert.strictEqual(length, 1); + assert.strictEqual(value, 1); + + data = Uint8Array.from([123]); + [value, length] = logic.parseUvarint(data); + assert.strictEqual(length, 1); + assert.strictEqual(value, 123); + + data = Uint8Array.from([200, 3]); + [value, length] = logic.parseUvarint(data); + assert.strictEqual(length, 2); + assert.strictEqual(value, 456); + }); + }); + describe('Const blocks', () => { + it('should parse int const block correctly', () => { + const data = Uint8Array.from([32, 5, 0, 1, 200, 3, 123, 2]); + const size = logic.checkIntConstBlock(data, 0); + assert.strictEqual(size, data.length); + }); + it('should parse bytes const block correctly', () => { + const data = Uint8Array.from([ + 38, + 2, + 13, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 48, + 49, + 50, + 51, + 2, + 1, + 2, + ]); + const size = logic.checkByteConstBlock(data, 0); + assert.strictEqual(size, data.length); + }); + it('should parse int push op correctly', () => { + const data = Uint8Array.from([0x81, 0x80, 0x80, 0x04]); + const size = logic.checkPushIntOp(data, 0); + assert.strictEqual(size, data.length); + }); + it('should parse byte push op correctly', () => { + const data = Uint8Array.from([ + 0x80, + 0x0b, + 0x68, + 0x65, + 0x6c, + 0x6c, + 0x6f, + 0x20, + 0x77, + 0x6f, + 0x72, + 0x6c, + 0x64, + ]); + const size = logic.checkPushByteOp(data, 0); + assert.strictEqual(size, data.length); + }); + }); + describe('Program checker', () => { + it('should assess correct programs right', () => { + let program = Uint8Array.from([1, 32, 1, 1, 34]); + let result = logic.checkProgram(program); + assert.strictEqual(result, true); + + const args = [Uint8Array.from([1, 2, 3])]; + + result = logic.checkProgram(program, args); + assert.strictEqual(result, true); + + program = utils.concatArrays(program, new Array(10).fill(0x22)); + result = logic.checkProgram(program, args); + assert.strictEqual(result, true); + }); + it('should fail on long input', () => { + assert.throws( + () => (logic.checkProgram as any)(), + new Error('empty program') + ); + let program = Uint8Array.from([1, 32, 1, 1, 34]); + assert.throws( + () => logic.checkProgram(program, [new Uint8Array(1000).fill(55)]), + new Error('program too long') + ); + + program = utils.concatArrays(program, new Uint8Array(1000).fill(34)); + assert.throws( + () => logic.checkProgram(program), + new Error('program too long') + ); + }); + it('should fail on invalid program', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34, 255]); + assert.throws( + () => logic.checkProgram(program), + new Error('invalid instruction') + ); + }); + it('should fail on invalid args', () => { + const program = Uint8Array.from([1, 32, 1, 1, 34]); + assert.throws( + () => logic.checkProgram(program, '123' as any), + new Error('invalid arguments') + ); + }); + it('should fail on costly program', () => { + let program = Uint8Array.from([1, 38, 1, 1, 1, 40, 2]); // byte 0x01 + keccak256 + let result = logic.checkProgram(program); + assert.strictEqual(result, true); + + // 10x keccak256 more is fine + program = utils.concatArrays(program, new Uint8Array(10).fill(2)); + result = logic.checkProgram(program); + assert.strictEqual(result, true); + + // 800x keccak256 more is too costly + program = utils.concatArrays(program, new Uint8Array(800).fill(2)); + // old versions + const oldVersions = [0x1, 0x2, 0x3]; + let i; + for (i = 0; i < oldVersions.length; i++) { + program[0] = oldVersions[i]; + assert.throws( + () => logic.checkProgram(program), + new Error( + 'program too costly for Teal version < 4. consider using v4.' + ) + ); + } + // new versions + const newVersions = [0x4]; + for (i = 0; i < newVersions.length; i++) { + program[0] = newVersions[i]; + assert.ok(logic.checkProgram(program)); + } + }); + it('should support TEAL v2 opcodes', () => { + assert.ok(logic.langspecEvalMaxVersion >= 2); + assert.ok(logic.langspecLogicSigVersion >= 2); + + // balance + let program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x60]); // int 0; balance + let result = logic.checkProgram(program); + assert.strictEqual(result, true); + + // app_opted_in + program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x61]); // int 0; int 0; app_opted_in + result = logic.checkProgram(program); + assert.strictEqual(result, true); + + // 800x keccak256 more is to costly + // prettier-ignore + program = Uint8Array.from([0x02, 0x20, 0x01, 0x00, 0x22, 0x22, 0x70, 0x00 ]); // int 0; int 0; asset_holding_get Balance + result = logic.checkProgram(program); + assert.strictEqual(result, true); + }); + it('should support TEAL v3 opcodes', () => { + assert.ok(logic.langspecEvalMaxVersion >= 3); + assert.ok(logic.langspecLogicSigVersion >= 3); + + // min_balance + let program = Uint8Array.from([0x03, 0x20, 0x01, 0x00, 0x22, 0x78]); // int 0; min_balance + assert.ok(logic.checkProgram(program)); + + // pushbytes + program = Uint8Array.from([ + 0x03, + 0x20, + 0x01, + 0x00, + 0x22, + 0x80, + 0x02, + 0x68, + 0x69, + 0x48, + ]); // int 0; pushbytes "hi"; pop + assert.ok(logic.checkProgram(program)); + + // pushint + program = Uint8Array.from([ + 0x03, + 0x20, + 0x01, + 0x00, + 0x22, + 0x81, + 0x01, + 0x48, + ]); // int 0; pushint 1; pop + assert.ok(logic.checkProgram(program)); + + // swap + program = Uint8Array.from([ + 0x03, + 0x20, + 0x02, + 0x00, + 0x01, + 0x22, + 0x23, + 0x4c, + 0x48, + ]); // int 0; int 1; swap; pop + assert.ok(logic.checkProgram(program)); + }); + it('should support TEAL v4 opcodes', () => { + assert.ok(logic.langspecEvalMaxVersion >= 4); + + // divmodw + let program = Uint8Array.from([ + 0x04, + 0x20, + 0x03, + 0x01, + 0x00, + 0x02, + 0x22, + 0x81, + 0xd0, + 0x0f, + 0x23, + 0x24, + 0x1f, + ]); // int 1; pushint 2000; int 0; int 2; divmodw + assert.ok(logic.checkProgram(program)); + + // gloads i + program = Uint8Array.from([0x04, 0x20, 0x01, 0x00, 0x22, 0x3b, 0x00]); // int 0; gloads 0 + assert.ok(logic.checkProgram(program)); + + // callsub + program = Uint8Array.from([ + 0x04, + 0x20, + 0x02, + 0x01, + 0x02, + 0x22, + 0x88, + 0x00, + 0x02, + 0x23, + 0x12, + 0x49, + ]); // int 1; callsub double; int 2; ==; double: dup; + assert.ok(logic.checkProgram(program)); + + // b>= + program = Uint8Array.from([ + 0x04, + 0x26, + 0x02, + 0x01, + 0x11, + 0x01, + 0x10, + 0x28, + 0x29, + 0xa7, + ]); // byte 0x11; byte 0x10; b>= + assert.ok(logic.checkProgram(program)); + + // b^ + program = Uint8Array.from([ + 0x04, + 0x26, + 0x03, + 0x01, + 0x11, + 0x01, + 0x10, + 0x01, + 0x01, + 0x28, + 0x29, + 0xad, + 0x2a, + 0x12, + ]); // byte 0x11; byte 0x10; b>= + assert.ok(logic.checkProgram(program)); + + // callsub, retsub + program = Uint8Array.from([ + 0x04, + 0x20, + 0x02, + 0x01, + 0x02, + 0x22, + 0x88, + 0x00, + 0x03, + 0x23, + 0x12, + 0x43, + 0x49, + 0x08, + 0x89, + ]); // int 1; callsub double; int 2; ==; return; double: dup; +; retsub; + assert.ok(logic.checkProgram(program)); + + // loop + program = Uint8Array.from([ + 0x04, + 0x20, + 0x04, + 0x01, + 0x02, + 0x0a, + 0x10, + 0x22, + 0x23, + 0x0b, + 0x49, + 0x24, + 0x0c, + 0x40, + 0xff, + 0xf8, + 0x25, + 0x12, + ]); // int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; == + assert.ok(logic.checkProgram(program)); + }); + }); +}); + +describe('Template logic validation', () => { + describe('Split', () => { + it('should match the goldens', () => { + // Inputs + const owner = + 'WO3QIJ6T4DZHBX5PWJH26JLHFSRT7W7M2DJOULPXDTUS6TUX7ZRIO4KDFY'; + const receivers = [ + 'W6UUUSEAOGLBHT7VFT4H2SDATKKSG6ZBUIJXTZMSLW36YS44FRP5NVAU7U', + 'XCIBIN7RT4ZXGBMVAMU3QS6L5EKB7XGROC5EPCNHHYXUIBAA5Q6C5Y7NEU', + ]; + const rat1 = 100; + const rat2 = 30; + const expiryRound = 123456; + const minPay = 10000; + const maxFee = 5000000; + const split = new algosdk.LogicTemplates.Split( + owner, + receivers[0], + receivers[1], + rat1, + rat2, + expiryRound, + minPay, + maxFee + ); + // Outputs + const goldenProgram = + 'ASAIAcCWsQICAMDEBx5kkE4mAyCztwQn0+DycN+vsk+vJWcsoz/b7NDS6i33HOkvTpf+YiC3qUpIgHGWE8/1LPh9SGCalSN7IaITeeWSXbfsS5wsXyC4kBQ38Z8zcwWVAym4S8vpFB/c0XC6R4mnPi9EBADsPDEQIhIxASMMEDIEJBJAABkxCSgSMQcyAxIQMQglEhAxAiEEDRAiQAAuMwAAMwEAEjEJMgMSEDMABykSEDMBByoSEDMACCEFCzMBCCEGCxIQMwAIIQcPEBA='; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = split.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + 'KPYGWKTV7CKMPMTLQRNGMEQRSYTYDHUOFNV4UDSBDLC44CLIJPQWRTCPBU'; + assert.deepStrictEqual(goldenAddress, split.getAddress()); + }); + }); + describe('HTLC', () => { + it('sha256 should match the goldens', () => { + // Inputs + const owner = + '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; + const receiver = + '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; + const hashFn = 'sha256'; + const hashImg = 'EHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8='; + const expiryRound = 600000; + const maxFee = 1000; + const htlc = new algosdk.LogicTemplates.HTLC( + owner, + receiver, + hashFn, + hashImg, + expiryRound, + maxFee + ); + // Outputs + const goldenProgram = + 'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IBB2YRNPIfx8AiI9UKues2ALw//DcSQjoeR7sfmp2/VfIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQEpEhAxCSoSMQIlDRAREA=='; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = htlc.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + 'FBZIR3RWVT2BTGVOG25H3VAOLVD54RTCRNRLQCCJJO6SVSCT5IVDYKNCSU'; + assert.deepStrictEqual(goldenAddress, htlc.getAddress()); + const goldenLtxn = + 'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgEHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8g/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItASkSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEIChyiO42rPQZmq42un3UDl1H3kZii2K4CElLvSrIU+oqpHR5cGWjcGF5'; + const o = { + from: goldenAddress, + to: receiver, + fee: 0, + amount: 0, + closeRemainderTo: receiver, + firstRound: 1, + lastRound: 100, + genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=', + type: 'pay', + }; + const preImageAsBase64 = 'cHJlaW1hZ2U='; + const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock( + htlc.getProgram(), + o, + preImageAsBase64 + ); + assert.deepStrictEqual( + Buffer.from(goldenLtxn, 'base64'), + Buffer.from(actualTxn.blob) + ); + }); + it('keccak256 should match the goldens', () => { + // Inputs + const owner = + '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; + const receiver = + '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; + const hashFn = 'keccak256'; + const hashImg = 'D7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAc='; + const expiryRound = 600000; + const maxFee = 1000; + const htlc = new algosdk.LogicTemplates.HTLC( + owner, + receiver, + hashFn, + hashImg, + expiryRound, + maxFee + ); + // Outputs + const goldenProgram = + 'ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IA+3eDK7wazksjUplLNJM7nLifv/Q2y5AOjjjqHMsKAHIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQIpEhAxCSoSMQIlDRAREA=='; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = htlc.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + '3MJ6JY3P6AU4R6I2RASYSAOPNI3QMWPZ7HYXJRNRGBIAXCHAY7QZRBH5PQ'; + assert.deepStrictEqual(goldenAddress, htlc.getAddress()); + const goldenLtxn = + 'gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZgeSZVK+mCNe0zNjyoiRi7nJOKkVtvkgD7d4MrvBrOSyNSmUs0kzucuJ+/9DbLkA6OOOocywoAcg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhAxBzIDEhAxCCQSEDEJKBItAikSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEINsT5ONv8CnI+RqIJYkBz2o3Bln5+fF0xbEwUAuI4MfhpHR5cGWjcGF5'; + const o = { + from: goldenAddress, + to: receiver, + fee: 0, + amount: 0, + closeRemainderTo: receiver, + firstRound: 1, + lastRound: 100, + genesisHash: 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=', + type: 'pay', + }; + const preImageAsBase64 = 'cHJlaW1hZ2U='; + const actualTxn = algosdk.LogicTemplates.signTransactionWithHTLCUnlock( + htlc.getProgram(), + o, + preImageAsBase64 + ); + assert.deepStrictEqual( + Buffer.from(goldenLtxn, 'base64'), + Buffer.from(actualTxn.blob) + ); + }); + it('other hash function should fail', () => { + // Inputs + const owner = + '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; + const receiver = + '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; + const hashFn = 'made-up-hash-fn'; + const hashImg = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='; + const expiryRound = 600000; + const maxFee = 1000; + assert.throws( + () => + new algosdk.LogicTemplates.HTLC( + owner, + receiver, + hashFn, + hashImg, + expiryRound, + maxFee + ) + ); + }); + }); + describe('Limit Order', () => { + it('should match the goldens', () => { + // Inputs + const owner = + '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; + const assetid = 12345; + const ratn = 30; + const ratd = 100; + const expiryRound = 123456; + const minTrade = 10000; + const maxFee = 5000000; + const limitOrder = new algosdk.LogicTemplates.LimitOrder( + owner, + assetid, + ratn, + ratd, + expiryRound, + minTrade, + maxFee + ); + // Outputs + const goldenProgram = + 'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ'; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = limitOrder.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + 'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I'; + assert.deepStrictEqual(goldenAddress, limitOrder.getAddress()); + const secretKey = Buffer.from( + 'DTKVj7KMON3GSWBwMX9McQHtaDDi8SDEBi0bt4rOxlHNRahLa0zVG+25BDIaHB1dSoIHIsUQ8FFcdnCdKoG+Bg==', + 'base64' + ); + const actualBlob = algosdk.LogicTemplates.getSwapAssetsTransaction( + actualBytes, + 3000, + 10000, + secretKey, + 10, + 1234, + 2234, + 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=' + ); + const expectedTxn1 = Buffer.from( + 'gqRsc2lngaFsxLcBIAoAAcCWsQICkE4EuWBkHsDEByYBIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMRYiEjEQIxIQMQEkDhAyBCMSQABVMgQlEjEIIQQNEDEJMgMSEDMBECEFEhAzAREhBhIQMwEUKBIQMwETMgMSEDMBEiEHHTUCNQExCCEIHTUENQM0ATQDDUAAJDQBNAMSNAI0BA8QQAAWADEJKBIxAiEJDRAxBzIDEhAxCCISEBCjdHhuiaNhbXTNJxCjZmVlzQisomZ2zQTSomdoxCB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaaNncnDEIKz368WOGpdE/Ww0L8wUu5Ly2u2bpG3ZSMKCJvcvGApTomx2zQi6o3JjdsQgzUWoS2tM1RvtuQQyGhwdXUqCByLFEPBRXHZwnSqBvgajc25kxCBd4Wnq60VaWXeVmuPt0x1XgMQSyP4nUnlh5G2Rhyo+taR0eXBlo3BheQ==', + 'base64' + ); + const expectedTxn2 = Buffer.from( + 'gqNzaWfEQKXv8Z6OUDNmiZ5phpoQJHmfKyBal4gBZLPYsByYnlXCAlXMBeVFG5CLP1k5L6BPyEG2/XIbjbyM0CGG55CxxAKjdHhuiqRhYW10zQu4pGFyY3bEIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITo2ZlZc0JJKJmds0E0qJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCs9+vFjhqXRP1sNC/MFLuS8trtm6Rt2UjCgib3LxgKU6Jsds0IuqNzbmTEIM1FqEtrTNUb7bkEMhocHV1KggcixRDwUVx2cJ0qgb4GpHR5cGWlYXhmZXKkeGFpZM0wOQ==', + 'base64' + ); + const expectedBlob = Buffer.concat([expectedTxn1, expectedTxn2]); + assert.deepStrictEqual(expectedBlob, Buffer.from(actualBlob)); + }); + }); + describe('Periodic payment', () => { + it('should match the goldens', () => { + // Inputs + const receiver = + 'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I'; + const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg='; + const amount = 500000; + const withdrawalWindow = 95; + const period = 100; + const expiryRound = 2445756; + const maxFee = 1000; + const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment( + receiver, + amount, + withdrawalWindow, + period, + expiryRound, + maxFee, + leaseb64 + ); + // Outputs + const goldenProgram = + 'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ'; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = periodicPayment.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + 'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY'; + assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress()); + const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; + const goldenStx = + 'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk='; + const goldenStxBlob = Buffer.from(goldenStx, 'base64'); + const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction( + actualBytes, + 0, + 1200, + goldenGenesisHash + ); + const expectedDict = algosdk.decodeObj(new Uint8Array(goldenStxBlob)); + const actualDict = algosdk.decodeObj(stx.blob); + assert.deepStrictEqual(expectedDict, actualDict); + }); + }); + describe('Limit Order', () => { + it('should match the goldens', () => { + // Inputs + const owner = + '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; + const assetid = 12345; + const ratn = 30; + const ratd = 100; + const expiryRound = 123456; + const minTrade = 10000; + const maxFee = 5000000; + const limitOrder = new algosdk.LogicTemplates.LimitOrder( + owner, + assetid, + ratn, + ratd, + expiryRound, + minTrade, + maxFee + ); + // Outputs + const goldenProgram = + 'ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ'; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = limitOrder.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + 'LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I'; + assert.deepStrictEqual(goldenAddress, limitOrder.getAddress()); + }); + }); + describe('Periodic payment', () => { + it('should match the goldens', () => { + // Inputs + const receiver = + 'SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I'; + const leaseb64 = 'AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg='; + const amount = 500000; + const withdrawalWindow = 95; + const period = 100; + const expiryRound = 2445756; + const maxFee = 1000; + const periodicPayment = new algosdk.LogicTemplates.PeriodicPayment( + receiver, + amount, + withdrawalWindow, + period, + expiryRound, + maxFee, + leaseb64 + ); + // Outputs + const goldenProgram = + 'ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGCUSEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRAxCCUSEBEQ'; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = periodicPayment.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + 'JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY'; + assert.deepStrictEqual(goldenAddress, periodicPayment.getAddress()); + const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; + const goldenStx = + 'gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxECISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjEHMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEBQYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0znzkLw7akdHlwZaNwYXk='; + const goldenStxBlob = Buffer.from(goldenStx, 'base64'); + const stx = algosdk.LogicTemplates.getPeriodicPaymentWithdrawalTransaction( + actualBytes, + 0, + 1200, + goldenGenesisHash + ); + const expectedDict = algosdk.decodeObj(new Uint8Array(goldenStxBlob)); + const actualDict = algosdk.decodeObj(stx.blob); + assert.deepStrictEqual(expectedDict, actualDict); + }); + }); + describe('Dynamic Fee', () => { + it('should match the goldens', () => { + // Inputs + const receiver = + '726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM'; + const amount = 5000; + const firstValid = 12345; + const lastValid = 12346; + const closeRemainder = + '42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE'; + const artificialLease = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; + const leaseBytes = new Uint8Array(Buffer.from(artificialLease, 'base64')); + const dynamicFee = new algosdk.LogicTemplates.DynamicFee( + receiver, + amount, + firstValid, + lastValid, + closeRemainder, + leaseBytes + ); + // Outputs + const goldenProgram = + 'ASAFAgGIJ7lgumAmAyD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEyDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+SB/g7Flf/H8U7ktwYFIodZd/C1LH6PWdyhK3dIAEm2QaTIEIhIzABAjEhAzAAcxABIQMwAIMQESEDEWIxIQMRAjEhAxBygSEDEJKRIQMQgkEhAxAiUSEDEEIQQSEDEGKhIQ'; + const goldenBytes = Buffer.from(goldenProgram, 'base64'); + const actualBytes = dynamicFee.getProgram(); + assert.deepStrictEqual(goldenBytes, actualBytes); + const goldenAddress = + 'GCI4WWDIWUFATVPOQ372OZYG52EULPUZKI7Y34MXK3ZJKIBZXHD2H5C5TI'; + assert.deepStrictEqual(goldenAddress, dynamicFee.getAddress()); + const privateKeyOneB64 = + 'cv8E0Ln24FSkwDgGeuXKStOTGcze5u8yldpXxgrBxumFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT7w=='; + const privateKeyOne = Buffer.from(privateKeyOneB64, 'base64'); + const goldenGenesisHash = 'f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk='; + const txnAndLsig = algosdk.LogicTemplates.signDynamicFee( + actualBytes, + privateKeyOne, + goldenGenesisHash + ); + const txnDict = txnAndLsig.txn; + const txnObj = new algosdk.Transaction(txnDict); + const txnBytes = txnObj.toByte(); + const goldenTxn = + 'iqNhbXTNE4ilY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmibHbNMDqibHjEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpo3JjdsQg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohOjc25kxCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76R0eXBlo3BheQ=='; + assert.deepStrictEqual( + new Uint8Array(Buffer.from(goldenTxn, 'base64')), + txnBytes + ); + const { lsig } = txnAndLsig; + const lsigBytes = lsig.toByte(); + const goldenLsig = + 'gqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYH'; + assert.deepStrictEqual( + new Uint8Array(Buffer.from(goldenLsig, 'base64')), + lsigBytes + ); + const privateKeyTwoB64 = + '2qjz96Vj9M6YOqtNlfJUOKac13EHCXyDty94ozCjuwwriI+jzFgStFx9E6kEk1l4+lFsW4Te2PY1KV8kNcccRg=='; + const privateKeyTwo = Buffer.from(privateKeyTwoB64, 'base64'); + const stxns = algosdk.LogicTemplates.getDynamicFeeTransactions( + txnDict, + lsig, + privateKeyTwo, + 1234, + firstValid, + lastValid + ); + const goldenStxns = + 'gqNzaWfEQJBNVry9qdpnco+uQzwFicUWHteYUIxwDkdHqY5Qw2Q8Fc2StrQUgN+2k8q4rC0LKrTMJQnE+mLWhZgMMJvq3QCjdHhuiqNhbXTOAAWq6qNmZWXOAATzvqJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCCFPYdMJymqcGoxdDeyuM8t6Kxixfq0PJCyJP71uhYT76NzbmTEICuIj6PMWBK0XH0TqQSTWXj6UWxbhN7Y9jUpXyQ1xxxGpHR5cGWjcGF5gqRsc2lngqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYHo3R4boujYW10zROIpWNsb3NlxCDmmpYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+aNmZWXOAAWq6qJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiE6NzbmTEIIU9h0wnKapwajF0N7K4zy3orGLF+rQ8kLIk/vW6FhPvpHR5cGWjcGF5'; + const goldenStxnBytes = Buffer.from(goldenStxns, 'base64'); + assert.deepStrictEqual(new Uint8Array(goldenStxnBytes), stxns); + }); + }); +});