diff --git a/src/logicsig.ts b/src/logicsig.ts index 3267c24d9..2e20996e2 100644 --- a/src/logicsig.ts +++ b/src/logicsig.ts @@ -60,6 +60,7 @@ export function sanityCheckProgram(program: Uint8Array) { } const programTag = new TextEncoder().encode('Program'); +const multisigProgramTag = new TextEncoder().encode('MsigProgram'); /** LogicSig implementation @@ -85,6 +86,10 @@ export class LogicSig implements encoding.Encodable { key: 'msig', valueSchema: new OptionalSchema(ENCODED_MULTISIG_SCHEMA), }, + { + key: 'lmsig', + valueSchema: new OptionalSchema(ENCODED_MULTISIG_SCHEMA), + }, ]) ); @@ -92,6 +97,7 @@ export class LogicSig implements encoding.Encodable { args: Uint8Array[]; sig?: Uint8Array; msig?: EncodedMultisig; + lmsig?: EncodedMultisig; constructor(program: Uint8Array, programArgs?: Array | null) { if ( @@ -112,6 +118,7 @@ export class LogicSig implements encoding.Encodable { this.args = args; this.sig = undefined; this.msig = undefined; + this.lmsig = undefined; } // eslint-disable-next-line class-methods-use-this @@ -128,6 +135,9 @@ export class LogicSig implements encoding.Encodable { if (this.msig) { data.set('msig', encodedMultiSigToEncodingData(this.msig)); } + if (this.lmsig) { + data.set('lmsig', encodedMultiSigToEncodingData(this.lmsig)); + } return data; } @@ -140,6 +150,9 @@ export class LogicSig implements encoding.Encodable { if (data.get('msig')) { lsig.msig = encodedMultiSigFromEncodingData(data.get('msig')); } + if (data.get('lmsig')) { + lsig.lmsig = encodedMultiSigFromEncodingData(data.get('lmsig')); + } return lsig; } @@ -148,7 +161,8 @@ export class LogicSig implements encoding.Encodable { * @param publicKey - Verification key (derived from sender address or escrow address) */ verify(publicKey: Uint8Array) { - if (this.sig && this.msig) { + const sigCount = [this.sig, this.msig, this.lmsig].filter(Boolean).length; + if (sigCount > 1) { return false; } @@ -160,7 +174,7 @@ export class LogicSig implements encoding.Encodable { const toBeSigned = utils.concatArrays(programTag, this.logic); - if (!this.sig && !this.msig) { + if (!this.sig && !this.msig && !this.lmsig) { const hash = nacl.genericHash(toBeSigned); return utils.arrayEqual(hash, publicKey); } @@ -169,7 +183,25 @@ export class LogicSig implements encoding.Encodable { return nacl.verify(toBeSigned, this.sig, publicKey); } - return verifyMultisig(toBeSigned, this.msig!, publicKey); + if (this.lmsig) { + const multisigAddr = addressFromMultisigPreImg({ + version: this.lmsig.v, + threshold: this.lmsig.thr, + pks: this.lmsig.subsig.map((subsig) => subsig.pk), + }); + const lmsigProgram = utils.concatArrays( + multisigProgramTag, + multisigAddr.publicKey, + this.logic + ); + return verifyMultisig(lmsigProgram, this.lmsig!, publicKey); + } + + if (this.msig) { + return verifyMultisig(toBeSigned, this.msig!, publicKey); + } + + return false; } /** @@ -193,14 +225,14 @@ export class LogicSig implements encoding.Encodable { } else { const subsigs = pksFromAddresses(msig.addrs).map((pk) => ({ pk })); - this.msig = { + this.lmsig = { v: msig.version, thr: msig.threshold, subsig: subsigs, }; - const [sig, index] = this.singleSignMultisig(secretKey, this.msig); - this.msig.subsig[index].s = sig; + const [sig, index] = this.singleSignMultisig(secretKey, this.lmsig); + this.lmsig.subsig[index].s = sig; } } @@ -209,11 +241,11 @@ export class LogicSig implements encoding.Encodable { * @param secretKey - Secret key to sign with */ appendToMultisig(secretKey: Uint8Array) { - if (this.msig === undefined) { + if (this.lmsig === undefined) { throw new Error('no multisig present'); } - const [sig, index] = this.singleSignMultisig(secretKey, this.msig); - this.msig.subsig[index].s = sig; + const [sig, index] = this.singleSignMultisig(secretKey, this.lmsig); + this.lmsig.subsig[index].s = sig; } signProgram(secretKey: Uint8Array) { @@ -222,6 +254,21 @@ export class LogicSig implements encoding.Encodable { return sig; } + signProgramMultisig(secretKey: Uint8Array, msig: EncodedMultisig) { + const multisigAddr = addressFromMultisigPreImg({ + version: msig.v, + threshold: msig.thr, + pks: msig.subsig.map((subsig) => subsig.pk), + }); + const toBeSigned = utils.concatArrays( + multisigProgramTag, + multisigAddr.publicKey, + this.logic + ); + const sig = nacl.sign(toBeSigned, secretKey); + return sig; + } + singleSignMultisig( secretKey: Uint8Array, msig: EncodedMultisig @@ -238,7 +285,7 @@ export class LogicSig implements encoding.Encodable { if (index === -1) { throw new Error('invalid secret key'); } - const sig = this.signProgram(secretKey); + const sig = this.signProgramMultisig(secretKey, msig); return [sig, index]; } @@ -332,7 +379,7 @@ export class LogicSigAccount implements encoding.Encodable { * To verify the delegation signature, use `verify`. */ isDelegated() { - return !!(this.lsig.sig || this.lsig.msig); + return !!(this.lsig.sig || this.lsig.msig || this.lsig.lmsig); } /** @@ -354,9 +401,12 @@ export class LogicSigAccount implements encoding.Encodable { * escrow address that is the hash of the LogicSig's program code. */ address(): Address { - if (this.lsig.sig && this.lsig.msig) { + const sigCount = [this.lsig.sig, this.lsig.msig, this.lsig.lmsig].filter( + Boolean + ).length; + if (sigCount > 1) { throw new Error( - 'LogicSig has too many signatures. At most one of sig or msig may be present' + 'LogicSig has too many signatures. At most one of sig, msig, or lmsig may be present' ); } @@ -367,11 +417,12 @@ export class LogicSigAccount implements encoding.Encodable { return new Address(this.sigkey); } - if (this.lsig.msig) { + const msig = this.lsig.lmsig || this.lsig.msig; + if (msig) { const msigMetadata = { - version: this.lsig.msig.v, - threshold: this.lsig.msig.thr, - pks: this.lsig.msig.subsig.map((subsig) => subsig.pk), + version: msig.v, + threshold: msig.thr, + pks: msig.subsig.map((subsig) => subsig.pk), }; return addressFromMultisigPreImg(msigMetadata); } diff --git a/src/signing.ts b/src/signing.ts index 29fd5bcc2..6a44c02c2 100644 --- a/src/signing.ts +++ b/src/signing.ts @@ -62,11 +62,11 @@ export function signLogicSigTransactionObject( // delegating account is the sender. If that's not the case, the signing // will fail. lsigAddress = new Address(txn.sender.publicKey); - } else if (lsig.msig) { + } else if (lsig.lmsig) { const msigMetadata = { - version: lsig.msig.v, - threshold: lsig.msig.thr, - pks: lsig.msig.subsig.map((subsig) => subsig.pk), + version: lsig.lmsig.v, + threshold: lsig.lmsig.thr, + pks: lsig.lmsig.subsig.map((subsig) => subsig.pk), }; lsigAddress = addressFromMultisigPreImg(msigMetadata); } else { diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts index c13e61c88..545cfccf0 100644 --- a/tests/8.LogicSig.ts +++ b/tests/8.LogicSig.ts @@ -1,6 +1,17 @@ /* eslint-env mocha */ import assert from 'assert'; import algosdk from '../src/index'; +import * as nacl from '../src/nacl/naclWrappers'; +import * as utils from '../src/utils/utils'; + +// unsafeSign returns the signature after using secretKey to sign a message. +// It should only be used in tests. +function unsafeSign(secretKey: Uint8Array, message: Uint8Array): Uint8Array { + return nacl.sign(message, secretKey); +} + +const sampleProgram = Uint8Array.from([1, 32, 1, 1, 34]); // int 1 +const sampleArgs = [Uint8Array.from([1]), Uint8Array.from([2, 3])]; 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' @@ -24,7 +35,7 @@ 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 program = sampleProgram; const programHash = algosdk.Address.fromString( '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY' ); @@ -55,7 +66,7 @@ describe('LogicSig', () => { assert.deepStrictEqual(decoded, lsig); }); it('should fail on tampered program', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); + const program = Uint8Array.from(sampleProgram); // Make a copy const programHash = '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; const pk = algosdk.decodeAddress(programHash).publicKey; @@ -69,7 +80,7 @@ describe('LogicSig', () => { describe('address', () => { it('should produce the correct address', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); + const program = sampleProgram; const programHash = '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; @@ -85,7 +96,7 @@ describe('LogicSig', () => { describe('LogicSigAccount', () => { describe('constructor', () => { it('should work on valid program without args', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); + const program = sampleProgram; const lsigAccount = new algosdk.LogicSigAccount(program); assert.deepStrictEqual(lsigAccount.lsig.logic, program); @@ -105,8 +116,8 @@ describe('LogicSigAccount', () => { 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 program = sampleProgram; + const args = sampleArgs; const lsigAccount = new algosdk.LogicSigAccount(program, args); assert.deepStrictEqual(lsigAccount.lsig.logic, program); @@ -129,8 +140,8 @@ describe('LogicSigAccount', () => { 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 program = sampleProgram; + const args = sampleArgs; const lsigAccount = new algosdk.LogicSigAccount(program, args); lsigAccount.sign(sampleAccount1.sk); @@ -164,17 +175,20 @@ describe('LogicSigAccount', () => { 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 program = sampleProgram; + const args = sampleArgs; const lsigAccount = new algosdk.LogicSigAccount(program, args); lsigAccount.signMultisig(sampleMultisigParams, sampleAccount1.sk); - const expectedSig = new Uint8Array( - algosdk.base64ToBytes( - 'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==' - ) + const msigProgramTag = new TextEncoder().encode('MsigProgram'); + const toBeSigned = utils.concatArrays( + msigProgramTag, + sampleMultisigAddr.publicKey, + program ); + const expectedSig = unsafeSign(sampleAccount1.sk, toBeSigned); + const expectedMsig: algosdk.EncodedMultisig = { v: sampleMultisigParams.version, thr: sampleMultisigParams.threshold, @@ -187,18 +201,12 @@ describe('LogicSigAccount', () => { 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.lsig.msig, undefined); // Legacy + assert.deepStrictEqual(lsigAccount.lsig.lmsig, expectedMsig); assert.strictEqual(lsigAccount.sigkey, undefined); // check serialization const encoded = lsigAccount.toByte(); - const expectedEncoded = new Uint8Array( - algosdk.base64ToBytes( - 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==' - ) - ); - assert.deepStrictEqual(encoded, expectedEncoded); - const decoded = algosdk.LogicSigAccount.fromByte(encoded); assert.deepStrictEqual(decoded, lsigAccount); }); @@ -206,42 +214,51 @@ describe('LogicSigAccount', () => { describe('appendToMultisig', () => { it('should properly append a signature', () => { - const msig1of3Encoded = algosdk.base64ToBytes( - 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoGicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==' + const lsigAccount = new algosdk.LogicSigAccount( + sampleProgram, + sampleArgs ); - const lsigAccount = algosdk.LogicSigAccount.fromByte(msig1of3Encoded); + const msigProgramTag = new TextEncoder().encode('MsigProgram'); + const toBeSigned = utils.concatArrays( + msigProgramTag, + sampleMultisigAddr.publicKey, + sampleProgram + ); + const knownSig1 = unsafeSign(sampleAccount1.sk, toBeSigned); - lsigAccount.appendToMultisig(sampleAccount2.sk); + // Manually create partial multisig (1 of 3 signed) + lsigAccount.lsig.lmsig = { + v: sampleMultisigParams.version, + thr: sampleMultisigParams.threshold, + subsig: [ + { pk: sampleAccount1.addr.publicKey, s: knownSig1 }, + { pk: sampleAccount2.addr.publicKey }, + { pk: sampleAccount3.addr.publicKey }, + ], + }; - const expectedSig1 = algosdk.base64ToBytes( - 'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==' - ); - const expectedSig2 = algosdk.base64ToBytes( - 'ZLxV2+2RokHUKrZg9+FKuZmaUrOxcVjO/D9P58siQRStqT1ehAUCChemaYMDIk6Go4tqNsVUviBQ/9PuqLMECQ==' - ); + // Append the second signature + lsigAccount.appendToMultisig(sampleAccount2.sk); + const expectedSig2 = unsafeSign(sampleAccount2.sk, toBeSigned); const expectedMsig: algosdk.EncodedMultisig = { v: sampleMultisigParams.version, thr: sampleMultisigParams.threshold, - subsig: sampleMultisigParams.addrs.map((addr) => ({ - pk: addr.publicKey, - })), + subsig: [ + { pk: sampleAccount1.addr.publicKey, s: knownSig1 }, + { pk: sampleAccount2.addr.publicKey, s: expectedSig2 }, + { pk: sampleAccount3.addr.publicKey }, + ], }; - expectedMsig.subsig[0].s = expectedSig1; - expectedMsig.subsig[1].s = expectedSig2; + assert.deepStrictEqual(lsigAccount.lsig.logic, sampleProgram); + assert.deepStrictEqual(lsigAccount.lsig.args, sampleArgs); assert.strictEqual(lsigAccount.lsig.sig, undefined); - assert.deepStrictEqual(lsigAccount.lsig.msig, expectedMsig); + assert.strictEqual(lsigAccount.lsig.msig, undefined); // Legacy + assert.deepStrictEqual(lsigAccount.lsig.lmsig, expectedMsig); assert.strictEqual(lsigAccount.sigkey, undefined); // check serialization const encoded = lsigAccount.toByte(); - const expectedEncoded = new Uint8Array( - algosdk.base64ToBytes( - 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB' - ) - ); - assert.deepStrictEqual(encoded, expectedEncoded); - const decoded = algosdk.LogicSigAccount.fromByte(encoded); assert.deepStrictEqual(decoded, lsigAccount); }); @@ -293,7 +310,19 @@ describe('LogicSigAccount', () => { assert.strictEqual(lsigAccount.verify(), true); }); - it('should fail multisig with wrong sig', () => { + it('should verify valid lmsig multisig', () => { + const msigEncoded = new Uint8Array( + algosdk.base64ToBytes( + // from: jq '{lsig: .lsig}' tests/resources/msig_delegated.txn | msgpacktool -e | base64 + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqVsbXNpZ4Omc3Vic2lnk4KicGvEIBt+wLBL6mG3lpCX5sv0B+EIpwU1HQvJir6xIgmoq4F4oXPEQIwzZcSx0RNw8j9w13dGn+HZR3m/TY1kgXZJNe94TMx2V2zA4O/pwUb6YHba+s5V7przG3aOvDK07BosjD3AZwaConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEBPVtR92cCxahX1iGTp50PVMQkf969ssoHfNA0VOiNupdkXc9n/l2WO9+pj8Ddozf4ovorGgnrzca3ZhKc46uUNgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded); + + assert.strictEqual(lsigAccount.verify(), true); + }); + + it('should fail multisig with wrong msig', () => { const msigEncoded = new Uint8Array( algosdk.base64ToBytes( 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYB' @@ -303,7 +332,20 @@ describe('LogicSigAccount', () => { // modify signature lsigAccount.lsig.msig!.subsig[0].s![0] = 0; + assert.strictEqual(lsigAccount.verify(), false); + }); + + it('should fail multisig with wrong lmsig', () => { + const msigEncoded = new Uint8Array( + algosdk.base64ToBytes( + // from: jq '{lsig: .lsig}' tests/resources/msig_delegated.txn | msgpacktool -e | base64 + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqVsbXNpZ4Omc3Vic2lnk4KicGvEIBt+wLBL6mG3lpCX5sv0B+EIpwU1HQvJir6xIgmoq4F4oXPEQIwzZcSx0RNw8j9w13dGn+HZR3m/TY1kgXZJNe94TMx2V2zA4O/pwUb6YHba+s5V7przG3aOvDK07BosjD3AZwaConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEBPVtR92cCxahX1iGTp50PVMQkf969ssoHfNA0VOiNupdkXc9n/l2WO9+pj8Ddozf4ovorGgnrzca3ZhKc46uUNgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AQ==' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte(msigEncoded); + // modify signature + lsigAccount.lsig.lmsig!.subsig[0].s![0] = 0; assert.strictEqual(lsigAccount.verify(), false); }); @@ -319,6 +361,20 @@ describe('LogicSigAccount', () => { assert.strictEqual(lsigAccount.verify(), false); }); + + it('should fail lmsig multisig that does not meet threshold', () => { + const msigBelowThresholdEncoded = new Uint8Array( + algosdk.base64ToBytes( + // from: jq '{lsig: .lsig} | .lsig.lmsig.subsig[1] |= del(."s:b64")' tests/resources/msig_delegated.txn | msgpacktool -e | base64 + 'gaRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqVsbXNpZ4Omc3Vic2lnk4KicGvEIBt+wLBL6mG3lpCX5sv0B+EIpwU1HQvJir6xIgmoq4F4oXPEQIwzZcSx0RNw8j9w13dGn+HZR3m/TY1kgXZJNe94TMx2V2zA4O/pwUb6YHba+s5V7przG3aOvDK07BosjD3AZwaBonBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcYGicGvEIOfw+E0GgR358xyNh4sRVfRnHVGhhcIAkIZn9ElYcGiho3RocgKhdgE=' + ) + ); + const lsigAccount = algosdk.LogicSigAccount.fromByte( + msigBelowThresholdEncoded + ); + + assert.strictEqual(lsigAccount.verify(), false); + }); }); describe('isDelegated', () => { @@ -403,8 +459,8 @@ describe('LogicSigAccount', () => { }); describe('signLogicSigTransaction', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - const args = [Uint8Array.from([1]), Uint8Array.from([2, 3])]; + const program = sampleProgram; + const args = sampleArgs; const otherAddr = 'WTDCE2FEYM2VB5MKNXKLRSRDTSPR2EFTIGVH4GRW4PHGD6747GFJTBGT2A'; @@ -527,7 +583,8 @@ describe('signLogicSigTransaction', () => { txID: 'UGGT5EZXG2OBPGWTEINC65UXIQ6UVAAOTNKRRCRAUCZH4FWJTVQQ', blob: new Uint8Array( algosdk.base64ToBytes( - 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5' + // from: msgpacktool -e < tests/resources/msig_delegated.txn | base64 + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqVsbXNpZ4Omc3Vic2lnk4KicGvEIBt+wLBL6mG3lpCX5sv0B+EIpwU1HQvJir6xIgmoq4F4oXPEQIwzZcSx0RNw8j9w13dGn+HZR3m/TY1kgXZJNe94TMx2V2zA4O/pwUb6YHba+s5V7przG3aOvDK07BosjD3AZwaConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEBPVtR92cCxahX1iGTp50PVMQkf969ssoHfNA0VOiNupdkXc9n/l2WO9+pj8Ddozf4ovorGgnrzca3ZhKc46uUNgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==' ) ), }; @@ -540,7 +597,8 @@ describe('signLogicSigTransaction', () => { txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ', blob: new Uint8Array( algosdk.base64ToBytes( - 'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKpHR5cGWjcGF5' + // from: msgpacktool -e < tests/resources/msig_delegated_other.txn | base64 + 'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqVsbXNpZ4Omc3Vic2lnk4KicGvEIBt+wLBL6mG3lpCX5sv0B+EIpwU1HQvJir6xIgmoq4F4oXPEQIwzZcSx0RNw8j9w13dGn+HZR3m/TY1kgXZJNe94TMx2V2zA4O/pwUb6YHba+s5V7przG3aOvDK07BosjD3AZwaConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEBPVtR92cCxahX1iGTp50PVMQkf969ssoHfNA0VOiNupdkXc9n/l2WO9+pj8Ddozf4ovorGgnrzca3ZhKc46uUNgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaRzZ25yxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqR0eXBlo3BheQ==' ) ), }; @@ -625,7 +683,8 @@ describe('signLogicSigTransaction', () => { txID: 'UGGT5EZXG2OBPGWTEINC65UXIQ6UVAAOTNKRRCRAUCZH4FWJTVQQ', blob: new Uint8Array( algosdk.base64ToBytes( - 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfppHR5cGWjcGF5' + // from: msgpacktool -e < tests/resources/msig_delegated.txn | base64 + 'gqRsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqVsbXNpZ4Omc3Vic2lnk4KicGvEIBt+wLBL6mG3lpCX5sv0B+EIpwU1HQvJir6xIgmoq4F4oXPEQIwzZcSx0RNw8j9w13dGn+HZR3m/TY1kgXZJNe94TMx2V2zA4O/pwUb6YHba+s5V7przG3aOvDK07BosjD3AZwaConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEBPVtR92cCxahX1iGTp50PVMQkf969ssoHfNA0VOiNupdkXc9n/l2WO9+pj8Ddozf4ovorGgnrzca3ZhKc46uUNgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==' ) ), }; @@ -638,7 +697,8 @@ describe('signLogicSigTransaction', () => { txID: 'DRBC5KBOYEUCL6L6H45GQSRKCCUTPNELUHUSQO4ZWCEODJEXQBBQ', blob: new Uint8Array( algosdk.base64ToBytes( - 'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqRtc2lng6ZzdWJzaWeTgqJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXihc8RASRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9AoKicGvEIAljMglTc4nwdWcRdzmRx9A+G3PIxPUr9q/wGqJc+cJxoXPEQGS8VdvtkaJB1Cq2YPfhSrmZmlKzsXFYzvw/T+fLIkEUrak9XoQFAgoXpmmDAyJOhqOLajbFVL4gUP/T7qizBAmBonBrxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaN0aHICoXYBpHNnbnLEII2StImQAXOgTfpDWaNmamr86ixCoF3Zwfc+66VHgDfpo3R4boqjYW10zROIo2ZlZc4AA0+oomZ2zgAO1tyjZ2VurXRlc3RuZXQtdjMxLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zgAO2sSkbm90ZcQItFF5Ofz60nGjcmN2xCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqNzbmTEILTGImikwzVQ9Ypt1LjKI5yfHRCzQap+GjbjzmH7/PmKpHR5cGWjcGF5' + // from: msgpacktool -e < tests/resources/msig_delegated_other.txn | base64 + 'g6Rsc2lng6NhcmeSxAEBxAICA6FsxAUBIAEBIqVsbXNpZ4Omc3Vic2lnk4KicGvEIBt+wLBL6mG3lpCX5sv0B+EIpwU1HQvJir6xIgmoq4F4oXPEQIwzZcSx0RNw8j9w13dGn+HZR3m/TY1kgXZJNe94TMx2V2zA4O/pwUb6YHba+s5V7przG3aOvDK07BosjD3AZwaConBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcaFzxEBPVtR92cCxahX1iGTp50PVMQkf969ssoHfNA0VOiNupdkXc9n/l2WO9+pj8Ddozf4ovorGgnrzca3ZhKc46uUNgaJwa8Qg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGjdGhyAqF2AaRzZ25yxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aN0eG6Ko2FtdM0TiKNmZWXOAANPqKJmds4ADtbco2dlbq10ZXN0bmV0LXYzMS4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds4ADtrEpG5vdGXECLRReTn8+tJxo3JjdsQgtMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yqjc25kxCC0xiJopMM1UPWKbdS4yiOcnx0Qs0Gqfho2485h+/z5iqR0eXBlo3BheQ==' ) ), }; diff --git a/tests/resources/msig_delegated.txn b/tests/resources/msig_delegated.txn new file mode 100644 index 000000000..8abc00d7b --- /dev/null +++ b/tests/resources/msig_delegated.txn @@ -0,0 +1,38 @@ +{ + "lsig": { + "arg:b64": [ + "AQ==", + "AgM=" + ], + "l:b64": "ASABASI=", + "lmsig": { + "subsig": [ + { + "pk:b64": "G37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXg=", + "s:b64": "jDNlxLHRE3DyP3DXd0af4dlHeb9NjWSBdkk173hMzHZXbMDg7+nBRvpgdtr6zlXumvMbdo68MrTsGiyMPcBnBg==" + }, + { + "pk:b64": "CWMyCVNzifB1ZxF3OZHH0D4bc8jE9Sv2r/Aaolz5wnE=", + "s:b64": "T1bUfdnAsWoV9Yhk6edD1TEJH/evbLKB3zQNFTojbqXZF3PZ/5dljvfqY/A3aM3+KL6KxoJ683Gt2YSnOOrlDQ==" + }, + { + "pk:b64": "5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=" + } + ], + "thr": 2, + "v": 1 + } + }, + "txn": { + "amt": 5000, + "fee": 217000, + "fv": 972508, + "gen": "testnet-v31.0", + "gh:b64": "JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=", + "lv": 973508, + "note:b64": "tFF5Ofz60nE=", + "rcv:b64": "tMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yo=", + "snd:b64": "jZK0iZABc6BN+kNZo2ZqavzqLEKgXdnB9z7rpUeAN+k=", + "type": "pay" + } +} diff --git a/tests/resources/msig_delegated_other.txn b/tests/resources/msig_delegated_other.txn new file mode 100644 index 000000000..3caa2d59d --- /dev/null +++ b/tests/resources/msig_delegated_other.txn @@ -0,0 +1,39 @@ +{ + "lsig": { + "arg:b64": [ + "AQ==", + "AgM=" + ], + "l:b64": "ASABASI=", + "lmsig": { + "subsig": [ + { + "pk:b64": "G37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXg=", + "s:b64": "jDNlxLHRE3DyP3DXd0af4dlHeb9NjWSBdkk173hMzHZXbMDg7+nBRvpgdtr6zlXumvMbdo68MrTsGiyMPcBnBg==" + }, + { + "pk:b64": "CWMyCVNzifB1ZxF3OZHH0D4bc8jE9Sv2r/Aaolz5wnE=", + "s:b64": "T1bUfdnAsWoV9Yhk6edD1TEJH/evbLKB3zQNFTojbqXZF3PZ/5dljvfqY/A3aM3+KL6KxoJ683Gt2YSnOOrlDQ==" + }, + { + "pk:b64": "5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=" + } + ], + "thr": 2, + "v": 1 + } + }, + "sgnr:b64": "jZK0iZABc6BN+kNZo2ZqavzqLEKgXdnB9z7rpUeAN+k=", + "txn": { + "amt": 5000, + "fee": 217000, + "fv": 972508, + "gen": "testnet-v31.0", + "gh:b64": "JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=", + "lv": 973508, + "note:b64": "tFF5Ofz60nE=", + "rcv:b64": "tMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yo=", + "snd:b64": "tMYiaKTDNVD1im3UuMojnJ8dELNBqn4aNuPOYfv8+Yo=", + "type": "pay" + } +}