diff --git a/CHANGELOG.md b/CHANGELOG.md index bc95232d3..66f32728a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ from `algosdk` and sends it to the network. - Added verification for secret key signatures in `Runtime`. - Added replace2 and replace3 opcode to `runtime`. - Added sha3_256 opcode to `Runtime` +- Added ed25519verify_bare opcode to `Runtime` #### @algo-builder/web - Added `appendSignMultisigTransaction` function to `WebMode` for appending signature to multisig transaction in the algosigner. diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index eba96e233..7f220d71b 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -770,6 +770,41 @@ export class Ed25519verify extends Op { } } +// for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1} +// push to stack [...stack, bigint] +export class Ed25519verify_bare extends Ed25519verify { + /** + * Asserts 0 arguments are passed. + * @param args Expected arguments: [] // none + * @param line line number in TEAL file + */ + constructor(args: string[], line: number) { + super(args, line); + } + + computeCost(): number { + return OpGasCost[7]["ed25519verify_bare"]; + } + + execute(stack: TEALStack): number { + this.assertMinStackLen(stack, 3, this.line); + const pubkey = this.assertBytes(stack.pop(), this.line); + const signature = this.assertBytes(stack.pop(), this.line); + const data = this.assertBytes(stack.pop(), this.line); + + const addr = encodeAddress(pubkey); + const isValid = verifyBytes(data, signature, addr); + if (isValid) { + stack.push(1n); + } else { + stack.push(0n); + } + return this.computeCost(); + } +} + + + // If A < B pushes '1' else '0' // push to stack [...stack, bigint] export class LessThan extends Op { diff --git a/packages/runtime/src/lib/constants.ts b/packages/runtime/src/lib/constants.ts index 2ce9e4fb4..b3d3c2951 100644 --- a/packages/runtime/src/lib/constants.ts +++ b/packages/runtime/src/lib/constants.ts @@ -468,6 +468,7 @@ OpGasCost[6] = { OpGasCost[7] = { ...OpGasCost[6], sha3_256: 130, + ed25519verify_bare: 1900, }; export const enum MathOp { diff --git a/packages/runtime/src/parser/parser.ts b/packages/runtime/src/parser/parser.ts index a8236624b..d3cacf9ae 100644 --- a/packages/runtime/src/parser/parser.ts +++ b/packages/runtime/src/parser/parser.ts @@ -68,6 +68,7 @@ import { EcdsaPkRecover, EcdsaVerify, Ed25519verify, + Ed25519verify_bare, EqualTo, Err, Exp, @@ -402,6 +403,7 @@ opCodeMap[7] = { replace2: Replace2, replace3: Replace3, sha3_256: Sha3_256, + ed25519verify_bare: Ed25519verify_bare, }; // list of opcodes with exactly one parameter. @@ -515,6 +517,7 @@ const commonModeOps = new Set([ "sha3_256", "sha512_256", "ed25519verify", + "Ed25519verify_bare", "ecdsa_verify", "ecdsa_pk_decompress", "+", diff --git a/packages/runtime/test/src/interpreter/opcode-list.ts b/packages/runtime/test/src/interpreter/opcode-list.ts index 30efc9d5d..08fd54f2e 100644 --- a/packages/runtime/test/src/interpreter/opcode-list.ts +++ b/packages/runtime/test/src/interpreter/opcode-list.ts @@ -78,6 +78,7 @@ import { EcdsaPkRecover, EcdsaVerify, Ed25519verify, + Ed25519verify_bare, EqualTo, Err, Exp, @@ -136,8 +137,8 @@ import { Select, SetBit, SetByte, - Sha256, Sha3_256, + Sha256, Sha512_256, Shl, Shr, @@ -7203,4 +7204,83 @@ describe("Teal Opcodes", function () { }); }); + describe("Ed25519verify_bare", function () { + const stack = new Stack(); + + it("Should push 1 to stack if signature is valid", function () { + const account = generateAccount(); + const hexStr = "62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd"; + const data = Buffer.from( + hexStr, + "hex" + ); + const signature = signBytes(data, account.sk); + + stack.push(data); + stack.push(signature); + stack.push(decodeAddress(account.addr).publicKey); + + const op = new Ed25519verify_bare([], 1); + op.execute(stack); + const top = stack.pop(); + assert.equal(top, 1n); + }); + + it("Should push 0 to stack if signature is invalid", function () { + const account = generateAccount(); + let hexStr = "62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd"; + let data = Buffer.from( + hexStr, + "hex" + ); + const signature = signBytes(data, account.sk); + //flip a bit and it should not pass + hexStr = "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" + data = Buffer.from( + hexStr, + "hex" + ); + stack.push(data); + stack.push(signature); + stack.push(decodeAddress(account.addr).publicKey); + + const op = new Ed25519verify_bare([], 1); + op.execute(stack); + const top = stack.pop(); + assert.equal(top, 0n); + }); + + it( + "Should throw an invalid type error", + execExpectError( + stack, + ["1", "1", "1"].map(BigInt), + new Ed25519verify_bare([], 1), + RUNTIME_ERRORS.TEAL.INVALID_TYPE + ) + ); + + it( + "Should throw an error if stack is below min length", + execExpectError( + stack, + [], + new Ed25519verify_bare([], 1), + RUNTIME_ERRORS.TEAL.ASSERT_STACK_LENGTH + ) + ); + + it("Should return correct cost", () => { + const account = generateAccount(); + const data = new Uint8Array(Buffer.from([1, 9, 25, 49])); + const signature = signBytes(data, account.sk); + + stack.push(data); + stack.push(signature); + stack.push(decodeAddress(account.addr).publicKey); + + const op = new Ed25519verify_bare([], 1); + assert.equal(1900, op.execute(stack)); + }); + }); });