-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ARC0047: Logic Signature Templates #226
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
fd1be4b
lsig templates
joe-p 918f5f4
Adding arc number + fix typo
SudoWeezy b022481
fix linting
SudoWeezy cf72745
use TMPL_
joe-p b8e6390
Unique Identification, required variables array
joe-p 261d942
fix typos/capitalization
joe-p a6ac0f0
add input validation
joe-p 3f3dde4
set status to last call
SudoWeezy f044d84
simplify string validation
joe-p 5437331
fix typos. Type/value clarity
joe-p 9660983
link to RFC 8785
joe-p f0a5af2
add wallet connect method
joe-p c765dcf
fix compliance with the validator
SudoWeezy c91c482
removing link to folder for html check
SudoWeezy 8b2d88a
add missing assert
joe-p 0837dca
add validate script
joe-p 61b7bf4
add asset and app type validation
joe-p 151a96a
Updating last call deadline
SudoWeezy 4a18ebd
fix typo
joe-p e3fca9d
Final
SudoWeezy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
--- | ||
arc: 47 | ||
title: Logic Signature Templates | ||
description: Defining templated logic signatures so wallets can safely sign them. | ||
author: Joe Polny (@joe-p) | ||
discussions-to: https://github.com/algorandfoundation/ARCs/issues/226 | ||
status: Final | ||
type: Standards Track | ||
category: ARC | ||
created: 2023-07-17 | ||
--- | ||
|
||
## Abstract | ||
This standard allows wallets to sign known logic signatures and clearly tell the user what they are signing. | ||
|
||
## Motivation | ||
Currently, most Algorand wallets do not enable the signing of logic signature programs for the purpose of delegation. The rationale is to prevent users from signing malicious programs, but this limitation also prevents non-malicious delegated logic signatures from being used in the Algorand ecosystem. As such, there needs to be a way to provide a safe way for wallets to sign logic signatures without putting users at risk. | ||
|
||
## Specification | ||
|
||
A logic signature **MUST** be described via the following JSON interface(s): | ||
|
||
### Interface | ||
|
||
```typescript | ||
interface LogicSignatureDescription { | ||
name: string, | ||
description: string, | ||
program: string, | ||
variables: { | ||
variable: string, | ||
name: string, | ||
type: string, | ||
description: string | ||
}[] | ||
} | ||
``` | ||
|
||
| Key | Description | | ||
| --- | ----------- | | ||
| `name` | The name of the logic signature. **SHOULD** be short and descriptive | | ||
| `description` | A description of what the logic signature does | | ||
| `program` | base64 encoding of the TEAL program source | ||
| `variables` | An array of variables in the program | | ||
| `variables.variable` | The name of the variable in the templated program. | | ||
| `variables.name` | Human-friendly name for the variable. **SHOULD** be short and descriptive | | ||
| `variables.type` | **MUST** be a type defined below in the `type` section | | ||
| `variables.description` | A description of how this variable is used in the program | | ||
|
||
### Variables | ||
|
||
A variable in the program **MUST** be start with `TMPL_` | ||
|
||
#### Types | ||
|
||
All non-reference ABI types **MUST** be supported by the client. ABI values **MUST** be encoded in base16 (with the leading `0x`) with the following exceptions: | ||
|
||
| Type | Description | | ||
| ---- | ----------- | | ||
| `address` | 58-character base32 Algorand public address. Typically to be used as an argument to the `addr` opcode. Front-ends **SHOULD** provide a link to the address on an explorer | ||
| `application` | Application ID. Alias for `uint64`. Front-ends **SHOULD** provide a link to the app on an explorer | | ||
| `asset` | Asset ID. Alias for `uint64`. Front-ends **SHOULD** provide a link to the asset on an explorer | | ||
| `string` | UTF-8 string. Typically used as an argument to `byte`, `method`, or a branching opcode. | ||
| `hex` | base16 encoding of binary data. Typically used as an argument to `byte`. **MUST** be prefixed with `0x` | | ||
|
||
For all other value, front-ends **MUST** decode the ABI value to display the human-readable value to the user. | ||
|
||
### Input Validation | ||
|
||
All ABI values **MUST** be encoded as base16 and prefixed with `0x`, with the exception of `uint64` which should be provided as an integer. | ||
|
||
String values **MUST NOT** include any unescaped `"` to ensure there is no TEAL injection. | ||
|
||
All values **MUST** be validated to ensure they are encoded properly. This includes the following checks: | ||
|
||
* An `address` value must be a valid Algorand address | ||
* A `uint64`, `application`, or `asset` value must be a valid unsigned 64-bit integer | ||
|
||
### Unique Identification | ||
|
||
To enable unique identification of a description, clients **MUST** calculate the SHA256 hash of the JSON description canonicalized in accordance with <a href="https://www.rfc-editor.org/rfc/rfc8785">RFC 8785</a>. | ||
|
||
### WalletConnect Method | ||
|
||
For wallets to support this ARC, they need to supprt the a `algo_templatedLsig` method. | ||
|
||
The method expects three parameters described by the interface below | ||
|
||
```ts | ||
interface TemplatedLsigParams { | ||
/** The canoncalized ARC47 templated lsig JSON as described in this ARC */ | ||
arc47: string | ||
/** The values of the templated variables, if there are any */ | ||
values?: {[variable: string]: string | number} | ||
/** The hash of the expected program. Wallets should compile the lsig with the given values to verify the program hash matches */ | ||
hash: string | ||
} | ||
``` | ||
|
||
## Rationale | ||
|
||
This provides a way for frontends to clearly display to the user what is being signed when signing a logic signature. | ||
|
||
Template variables must be immediate arguments. Otherwise a string variable could specify the opcode in the program, which could have unintended and unclear consequences. | ||
|
||
`TMPL_` prefix is used to align with existing template variable tooling. | ||
|
||
Hashing canonicalized JSON is useful for ensuring clients, such as wallets, can create a allowlist of templated logic signatures. | ||
|
||
## Backwards Compatibility | ||
N/A | ||
|
||
## Test Cases | ||
N/A | ||
|
||
## Reference Implementation | ||
|
||
A reference implementation can be found in the`../assets/arc-0047` folder. | ||
|
||
[lsig.teal](../assets/arc-0047/lsig.teal) contains the templated TEAL code for a logic signature that allows payments of a specific amount every 25,000 blocks. | ||
|
||
[dapp.ts](../assets/arc-0047/dapp.ts) contains a TypeScript script showcasing how a dapp would form a wallet connect request for a templated logic signature. | ||
|
||
[wallet.ts](../assets/arc-0047/wallet.ts) contains a TypeScript script showcasing how a wallet would handle a request for signing a templated logic signature. | ||
|
||
[validate.ts](../assets/arc-0047/validate.ts) contains a TypeScript script showcasing how one could validate templated TEAL and variable values. | ||
|
||
### String Variables | ||
|
||
|
||
#### Invalid: Partial Argument | ||
``` | ||
#pragma version 9 | ||
byte "Hello, TMPL_NAME" | ||
``` | ||
|
||
This is not valid because `TMPL_NAME` is not the full immediate argument. | ||
|
||
#### Invalid: Not An Argument | ||
``` | ||
#pragma version 9 | ||
TMPL_PUSH_HELLO_NAME | ||
``` | ||
|
||
This is not valid because `TMPL_PUSH_HELLO_NAME` is not an immediate argument to an opcode. | ||
|
||
#### Valid | ||
``` | ||
#pragma version 9 | ||
byte TMPL_HELLO_NAME | ||
``` | ||
|
||
This is valid as `TMPL_HELLO_NAME` is the entire immediate argument of the `byte` opcode. A possible value could be `Hello, AlgoDev` | ||
|
||
### Hex Variables | ||
|
||
#### Valid | ||
``` | ||
#pragma version 9 | ||
byte TMPL_DEAD_BEEF | ||
``` | ||
|
||
This is valid as `TMPL_DEAD_BEEF` is the full immediate argument to the `byte` opcode. A possible value could be `0xdeadbeef`. | ||
|
||
|
||
## Security Considerations | ||
It should be made clear that this standard alone does not define how frontends, particularly wallets, should deem a logic signature to be safe. This is a decision made solely by the front-ends as to which logic signatures they allow to be signed. It is **RECOMMENDED** to only support the signing of audited or otherwise trusted logic signatures. | ||
|
||
## Copyright | ||
Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore | ||
|
||
Comment on lines
+1
to
+2
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a |
||
# Logs | ||
|
||
logs | ||
_.log | ||
npm-debug.log_ | ||
yarn-debug.log* | ||
yarn-error.log* | ||
lerna-debug.log* | ||
.pnpm-debug.log* | ||
|
||
# Diagnostic reports (https://nodejs.org/api/report.html) | ||
|
||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json | ||
|
||
# Runtime data | ||
|
||
pids | ||
_.pid | ||
_.seed | ||
\*.pid.lock | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
|
||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
|
||
coverage | ||
\*.lcov | ||
|
||
# nyc test coverage | ||
|
||
.nyc_output | ||
|
||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | ||
|
||
.grunt | ||
|
||
# Bower dependency directory (https://bower.io/) | ||
|
||
bower_components | ||
|
||
# node-waf configuration | ||
|
||
.lock-wscript | ||
|
||
# Compiled binary addons (https://nodejs.org/api/addons.html) | ||
|
||
build/Release | ||
|
||
# Dependency directories | ||
|
||
node_modules/ | ||
jspm_packages/ | ||
|
||
# Snowpack dependency directory (https://snowpack.dev/) | ||
|
||
web_modules/ | ||
|
||
# TypeScript cache | ||
|
||
\*.tsbuildinfo | ||
|
||
# Optional npm cache directory | ||
|
||
.npm | ||
|
||
# Optional eslint cache | ||
|
||
.eslintcache | ||
|
||
# Optional stylelint cache | ||
|
||
.stylelintcache | ||
|
||
# Microbundle cache | ||
|
||
.rpt2_cache/ | ||
.rts2_cache_cjs/ | ||
.rts2_cache_es/ | ||
.rts2_cache_umd/ | ||
|
||
# Optional REPL history | ||
|
||
.node_repl_history | ||
|
||
# Output of 'npm pack' | ||
|
||
\*.tgz | ||
|
||
# Yarn Integrity file | ||
|
||
.yarn-integrity | ||
|
||
# dotenv environment variable files | ||
|
||
.env | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
.env.local | ||
|
||
# parcel-bundler cache (https://parceljs.org/) | ||
|
||
.cache | ||
.parcel-cache | ||
|
||
# Next.js build output | ||
|
||
.next | ||
out | ||
|
||
# Nuxt.js build / generate output | ||
|
||
.nuxt | ||
dist | ||
|
||
# Gatsby files | ||
|
||
.cache/ | ||
|
||
# Comment in the public line in if your project uses Gatsby and not Next.js | ||
|
||
# https://nextjs.org/blog/next-9-1#public-directory-support | ||
|
||
# public | ||
|
||
# vuepress build output | ||
|
||
.vuepress/dist | ||
|
||
# vuepress v2.x temp and cache directory | ||
|
||
.temp | ||
.cache | ||
|
||
# Docusaurus cache and generated files | ||
|
||
.docusaurus | ||
|
||
# Serverless directories | ||
|
||
.serverless/ | ||
|
||
# FuseBox cache | ||
|
||
.fusebox/ | ||
|
||
# DynamoDB Local files | ||
|
||
.dynamodb/ | ||
|
||
# TernJS port file | ||
|
||
.tern-port | ||
|
||
# Stores VSCode versions used for testing VSCode extensions | ||
|
||
.vscode-test | ||
|
||
# yarn v2 | ||
|
||
.yarn/cache | ||
.yarn/unplugged | ||
.yarn/build-state.yml | ||
.yarn/install-state.gz | ||
.pnp.\* |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import algosdk from 'algosdk' | ||
import { readFileSync } from 'fs' | ||
import {canonicalize} from 'json-canonicalize' | ||
import { formatJsonRpcRequest } from "@json-rpc-tools/utils"; | ||
import { sha256 } from 'js-sha256'; | ||
|
||
const algodClient = new algosdk.Algodv2('a'.repeat(64), 'http://localhost', 4001) | ||
|
||
const teal = readFileSync('./lsig.teal').toString() | ||
|
||
const arc47 = { | ||
name: "25000 block payment", | ||
description: "Allows a payment to be made every 25000 blocks of a specific amount to a specific address", | ||
program: btoa(teal), | ||
variables: [ | ||
{ | ||
name: "Payment Amount", | ||
variable: "TMPL_AMOUNT", | ||
type: "uint64", | ||
description: "Amount of the payment transaction in microAlgos" | ||
}, | ||
{ | ||
name: "Payment Receiver", | ||
variable: "TMPL_RECEIVER", | ||
type: "address", | ||
description: "Address to which the payment transaction is sent" | ||
} | ||
], | ||
} | ||
|
||
const values: Record<string, string | number> = { | ||
TMPL_AMOUNT: 1000000, | ||
TMPL_RECEIVER: 'Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA' | ||
} | ||
|
||
let finalTeal = teal | ||
|
||
for (const variable in values) { | ||
finalTeal = finalTeal.replaceAll(variable, values[variable].toString()) | ||
} | ||
|
||
const result = await algodClient.compile(finalTeal).do() | ||
|
||
const requestParams = [canonicalize(arc47), JSON.stringify(values), result.hash] | ||
|
||
const walletConnectRequest = formatJsonRpcRequest('algo_signTemplatedLsig', requestParams); | ||
|
||
console.log(`Request Params: ${console.log(requestParams)}`) | ||
console.log(`ARC47 SHA256: ${sha256(canonicalize(arc47))}`) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example drives home that it's a big ask for the wallet to parse TEAL. According to these rules, it needs to accept:
byte TMPL_NAME; int 7
,byteblock "hello\" " TEMPL_NAME
for example.I think we need to spec out the exact local rules that would allow a program to accept. I admit that's not easy.
Perhaps we should restrict things much more strongly. Only things that can be be properly escaped by the wallet, and result in a single
byte X
orint X
instruction, for exampleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah didn't think about constant blocks. I think enforcing
byte x
/int x
is reasonable.I also didn't think about
;
. How about enforcing that any usage of a variable must match this regex/^(byte|int) TMPL_/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or probably
/^\s*(byte|int) TMPL_/
to account for indented lines.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd allow
pushint
andpushbyte
as well. I think there are good reasons to use them in templates, so that the inserted bytes end up exactly where you expect them.(
int
andbyte
do some funny business around optimization, and moving the constants into constant blocks.)