|
| 1 | +import { Address, concatArrays, hash, isValidAddress } from '@algorandfoundation/algokit-common' |
| 2 | +import { MultisigAccount } from './multisig' |
| 3 | +import { MultisigSignature } from './transactions/signed-transaction' |
| 4 | + |
| 5 | +// base64regex is the regex to test for base64 strings |
| 6 | +const base64regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/ |
| 7 | + |
| 8 | +/** sanityCheckProgram performs heuristic program validation: |
| 9 | + * check if passed in bytes are Algorand address or is B64 encoded, rather than Teal bytes |
| 10 | + * |
| 11 | + * @param program - Program bytes to check |
| 12 | + */ |
| 13 | +export function sanityCheckProgram(program: Uint8Array) { |
| 14 | + if (!program || program.length === 0) throw new Error('empty program') |
| 15 | + |
| 16 | + const lineBreakOrd = '\n'.charCodeAt(0) |
| 17 | + const blankSpaceOrd = ' '.charCodeAt(0) |
| 18 | + const tildeOrd = '~'.charCodeAt(0) |
| 19 | + |
| 20 | + const isPrintable = (x: number) => blankSpaceOrd <= x && x <= tildeOrd |
| 21 | + const isAsciiPrintable = program.every((x: number) => x === lineBreakOrd || isPrintable(x)) |
| 22 | + |
| 23 | + if (isAsciiPrintable) { |
| 24 | + const programStr = new TextDecoder().decode(program) |
| 25 | + |
| 26 | + if (isValidAddress(programStr)) throw new Error('requesting program bytes, get Algorand address') |
| 27 | + |
| 28 | + if (base64regex.test(programStr)) throw new Error('program should not be b64 encoded') |
| 29 | + |
| 30 | + throw new Error('program bytes are all ASCII printable characters, not looking like Teal byte code') |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +const PROGRAM_TAG = new TextEncoder().encode('Program') |
| 35 | +const MSIG_PROGRAM_TAG = new TextEncoder().encode('MsigProgram') |
| 36 | +const SIGN_PROGRAM_DATA_PREFIX = new TextEncoder().encode('ProgData') |
| 37 | + |
| 38 | +/** Function for signing logic signatures for delegation */ |
| 39 | +export type DelegatedLsigSigner = (lsig: LogicSig, msig?: MultisigAccount) => Promise<Uint8Array> |
| 40 | + |
| 41 | +/** Function for signing program data for a logic signature */ |
| 42 | +export type ProgramDataSigner = (data: Uint8Array, lsig: LogicSig) => Promise<Uint8Array> |
| 43 | + |
| 44 | +export class LogicSig { |
| 45 | + logic: Uint8Array |
| 46 | + args: Uint8Array[] |
| 47 | + sig?: Uint8Array |
| 48 | + msig?: MultisigSignature |
| 49 | + lmsig?: MultisigSignature |
| 50 | + |
| 51 | + constructor(program: Uint8Array, programArgs?: Array<Uint8Array> | null) { |
| 52 | + if (programArgs && (!Array.isArray(programArgs) || !programArgs.every((arg) => arg.constructor === Uint8Array))) { |
| 53 | + throw new TypeError('Invalid arguments') |
| 54 | + } |
| 55 | + |
| 56 | + let args: Uint8Array[] = [] |
| 57 | + if (programArgs != null) args = programArgs.map((arg) => new Uint8Array(arg)) |
| 58 | + |
| 59 | + sanityCheckProgram(program) |
| 60 | + |
| 61 | + this.logic = program |
| 62 | + this.args = args |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * Compute hash of the logic sig program (that is the same as escrow account address) as string address |
| 67 | + * @returns String representation of the address |
| 68 | + */ |
| 69 | + address(): Address { |
| 70 | + const toBeSigned = concatArrays(PROGRAM_TAG, this.logic) |
| 71 | + const h = hash(toBeSigned) |
| 72 | + return new Address(h) |
| 73 | + } |
| 74 | + |
| 75 | + async delegate(signer: DelegatedLsigSigner) { |
| 76 | + this.sig = await signer(this) |
| 77 | + } |
| 78 | + |
| 79 | + async deletegateMultisig(msig: MultisigAccount) { |
| 80 | + if (this.lmsig == undefined) { |
| 81 | + this.lmsig = { |
| 82 | + subsignatures: [], |
| 83 | + version: msig.params.version, |
| 84 | + threshold: msig.params.threshold, |
| 85 | + } |
| 86 | + } |
| 87 | + for (const addrWithSigner of msig.subSigners) { |
| 88 | + const { lsigSigner, addr } = addrWithSigner |
| 89 | + const signature = await lsigSigner(this, msig) |
| 90 | + |
| 91 | + this.lmsig.subsignatures.push({ address: addr, signature }) |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + bytesToSignForDelegation(msig?: MultisigAccount): Uint8Array { |
| 96 | + if (msig) { |
| 97 | + return concatArrays(MSIG_PROGRAM_TAG, msig.addr.publicKey, this.logic) |
| 98 | + } else { |
| 99 | + return concatArrays(PROGRAM_TAG, this.logic) |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + signProgramData(data: Uint8Array, signer: ProgramDataSigner): Promise<Uint8Array> { |
| 104 | + return signer(data, this) |
| 105 | + } |
| 106 | + |
| 107 | + programDataToSign(data: Uint8Array): Uint8Array { |
| 108 | + return concatArrays(SIGN_PROGRAM_DATA_PREFIX, this.address().publicKey, data) |
| 109 | + } |
| 110 | +} |
0 commit comments