Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/logic/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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] {
Expand All @@ -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
Expand Down Expand Up @@ -79,6 +86,7 @@ function readIntConstBlock(
return [size, ints];
}

/** @deprecated for langspec.json is deprecated aross all SDKs */
function readByteConstBlock(
program: Uint8Array,
pc: number
Expand Down Expand Up @@ -116,6 +124,7 @@ function readByteConstBlock(
return [size, byteArrays];
}

/** @deprecated for langspec.json is deprecated aross all SDKs */
function readPushIntOp(
program: Uint8Array,
pc: number
Expand All @@ -129,6 +138,7 @@ function readPushIntOp(
return [size, numberFound];
}

/** @deprecated for langspec.json is deprecated aross all SDKs */
function readPushByteOp(
program: Uint8Array,
pc: number
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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;
40 changes: 35 additions & 5 deletions src/logicsig.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -93,7 +123,7 @@ export class LogicSig implements LogicSigStorageStructure {
}

try {
logic.checkProgram(this.logic, this.args);
sanityCheckProgram(this.logic);
} catch (e) {
return false;
}
Expand Down
5 changes: 0 additions & 5 deletions tests/7.AlgoSDK.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
10 changes: 0 additions & 10 deletions tests/8.LogicSig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
26 changes: 26 additions & 0 deletions tests/cucumber/steps/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions tests/cucumber/unit.tags
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@unit.indexer.ledger_refactoring
@unit.indexer.logs
@unit.offline
@unit.program_sanity_check
@unit.rekey
@unit.responses
@unit.responses.231
Expand Down