diff --git a/src/logic/logic.ts b/src/logic/logic.ts index 9f66b23f6..f42532940 100644 --- a/src/logic/logic.ts +++ b/src/logic/logic.ts @@ -3,10 +3,12 @@ * Utilities for working with program bytes. */ +/** @deprecated langspec.json is deprecated aross all SDKs */ import langspec from './langspec.json'; /** * Langspec Op Structure + * @deprecated for langspec.json is deprecated aross all SDKs */ interface OpStructure { Opcode: number; @@ -23,13 +25,17 @@ interface OpStructure { Groups: string[]; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ let opcodes: { [key: number]: OpStructure; }; +/** @deprecated for langspec.json is deprecated aross all SDKs */ const maxCost = 20000; +/** @deprecated for langspec.json is deprecated aross all SDKs */ const maxLength = 1000; +/** @deprecated for langspec.json is deprecated aross all SDKs */ export function parseUvarint( array: Uint8Array ): [numberFound: number, size: number] { @@ -49,6 +55,7 @@ export function parseUvarint( return [0, 0]; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ function readIntConstBlock( program: Uint8Array, pc: number @@ -79,6 +86,7 @@ function readIntConstBlock( return [size, ints]; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ function readByteConstBlock( program: Uint8Array, pc: number @@ -116,6 +124,7 @@ function readByteConstBlock( return [size, byteArrays]; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ function readPushIntOp( program: Uint8Array, pc: number @@ -129,6 +138,7 @@ function readPushIntOp( return [size, numberFound]; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ function readPushByteOp( program: Uint8Array, pc: number @@ -151,6 +161,12 @@ function readPushByteOp( /** readProgram validates program for length and running cost, * and additionally provides the found int variables and byte blocks + * + * @deprecated Validation relies on metadata (`langspec.json`) that + * does not accurately represent opcode behavior across program versions. + * The behavior of `readProgram` relies on `langspec.json`. + * Thus, this method is being deprecated. + * * @param program - Program to check * @param args - Program arguments as array of Uint8Array arrays * @throws @@ -254,6 +270,12 @@ export function readProgram( /** * checkProgram validates program for length and running cost + * + * @deprecated Validation relies on metadata (`langspec.json`) that + * does not accurately represent opcode behavior across program versions. + * The behavior of `checkProgram` relies on `langspec.json`. + * Thus, this method is being deprecated. + * * @param program - Program to check * @param args - Program arguments as array of Uint8Array arrays * @throws @@ -264,25 +286,31 @@ export function checkProgram(program: Uint8Array, args?: Uint8Array[]) { return success; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ export function checkIntConstBlock(program: Uint8Array, pc: number) { const [size] = readIntConstBlock(program, pc); return size; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ export function checkByteConstBlock(program: Uint8Array, pc: number) { const [size] = readByteConstBlock(program, pc); return size; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ export function checkPushIntOp(program: Uint8Array, pc: number) { const [size] = readPushIntOp(program, pc); return size; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ export function checkPushByteOp(program: Uint8Array, pc: number) { const [size] = readPushByteOp(program, pc); return size; } +/** @deprecated for langspec.json is deprecated aross all SDKs */ export const langspecEvalMaxVersion = langspec.EvalMaxVersion; +/** @deprecated for langspec.json is deprecated aross all SDKs */ export const langspecLogicSigVersion = langspec.LogicSigVersion; diff --git a/src/logicsig.ts b/src/logicsig.ts index 4904d3c52..74c0e0bef 100644 --- a/src/logicsig.ts +++ b/src/logicsig.ts @@ -1,10 +1,10 @@ import * as nacl from './nacl/naclWrappers'; import * as address from './encoding/address'; import * as encoding from './encoding/encoding'; -import * as logic from './logic/logic'; import { verifyMultisig } from './multisig'; import * as utils from './utils/utils'; import * as txnBuilder from './transaction'; +import { isValidAddress } from './encoding/address'; import { EncodedLogicSig, EncodedLogicSigAccount, @@ -20,6 +20,38 @@ interface LogicSigStorageStructure { msig?: EncodedMultisig; } +/** sanityCheckProgram performs heuristic program validation: + * check if passed in bytes are Algorand address or is B64 encoded, rather than Teal bytes + * + * @param program - Program bytes to check + */ +export function sanityCheckProgram(program: Uint8Array) { + if (!program || program.length === 0) throw new Error('empty program'); + + const lineBreakOrd = '\n'.charCodeAt(0); + const blankSpaceOrd = ' '.charCodeAt(0); + const tildeOrd = '~'.charCodeAt(0); + + const isPrintable = (x: number) => blankSpaceOrd <= x && x <= tildeOrd; + const isAsciiPrintable = program.every( + (x: number) => x === lineBreakOrd || isPrintable(x) + ); + + if (isAsciiPrintable) { + const programStr = Buffer.from(program).toString(); + + if (isValidAddress(programStr)) + throw new Error('requesting program bytes, get Algorand address'); + + if (Buffer.from(programStr, 'base64').toString('base64') === programStr) + throw new Error('program should not be b64 encoded'); + + throw new Error( + 'program bytes are all ASCII printable characters, not looking like Teal byte code' + ); + } +} + /** LogicSig implementation */ @@ -49,9 +81,7 @@ export class LogicSig implements LogicSigStorageStructure { if (programArgs != null) args = programArgs.map((arg) => new Uint8Array(arg)); - if (!logic.checkProgram(program, args)) { - throw new Error('Invalid program'); - } + sanityCheckProgram(program); this.logic = program; this.args = args; @@ -93,7 +123,7 @@ export class LogicSig implements LogicSigStorageStructure { } try { - logic.checkProgram(this.logic, this.args); + sanityCheckProgram(this.logic); } catch (e) { return false; } diff --git a/tests/7.AlgoSDK.js b/tests/7.AlgoSDK.js index e831786b9..6cc0c67ee 100644 --- a/tests/7.AlgoSDK.js +++ b/tests/7.AlgoSDK.js @@ -830,11 +830,6 @@ describe('Algosdk (AKA end to end)', () => { assert.equal(lsig.logic, program); assert.deepEqual(lsig.args, args); }); - it('should throw on invalid program', () => { - const program = Uint8Array.from([1, 32, 1, 1, 34]); - program[0] = 128; - assert.throws(() => algosdk.makeLogicSig(program)); - }); }); describe('Single logic sig', () => { it('should work on valid program', () => { diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts index e7e7a5fba..08be74abf 100644 --- a/tests/8.LogicSig.ts +++ b/tests/8.LogicSig.ts @@ -65,11 +65,6 @@ describe('LogicSig', () => { 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', () => { @@ -129,11 +124,6 @@ describe('LogicSigAccount', () => { 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', () => { diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 9f8cbb6df..dabaa82f6 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -4454,6 +4454,32 @@ module.exports = function getSteps(options) { } ); + Given( + 'a base64 encoded program bytes for heuristic sanity check {string}', + async function (programByteStr) { + this.seeminglyProgram = new Uint8Array( + Buffer.from(programByteStr, 'base64') + ); + } + ); + + When('I start heuristic sanity check over the bytes', async function () { + this.actualErrMsg = undefined; + try { + new algosdk.LogicSigAccount(this.seeminglyProgram); // eslint-disable-line + } catch (e) { + this.actualErrMsg = e.message; + } + }); + + Then( + 'if the heuristic sanity check throws an error, the error contains {string}', + async function (errMsg) { + if (errMsg !== '') assert.ok(this.actualErrMsg.includes(errMsg)); + else assert.strictEqual(this.actualErrMsg, undefined); + } + ); + if (!options.ignoreReturn) { return steps; } diff --git a/tests/cucumber/unit.tags b/tests/cucumber/unit.tags index ca26f899b..4062eadb6 100644 --- a/tests/cucumber/unit.tags +++ b/tests/cucumber/unit.tags @@ -11,6 +11,7 @@ @unit.indexer.ledger_refactoring @unit.indexer.logs @unit.offline +@unit.program_sanity_check @unit.rekey @unit.responses @unit.responses.231