Skip to content
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

Allow up to 5 data pushes in OP_RETURN outputs #259

Merged
merged 5 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Dates are in `dd-mm-yyyy` format.

## [2.2.3] - 06-05-2024

### Added

- Support for signing transactions with `OP_RETURN` outputs extended to up to 5 push opcodes, instead of a single one.

## [2.2.2] - 08-04-2024

### Added
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ PATH_SLIP21_APP_LOAD_PARAMS = "LEDGER-Wallet policy"
# Application version
APPVERSION_M = 2
APPVERSION_N = 2
APPVERSION_P = 2
APPVERSION_P = 3
APPVERSION_SUFFIX = # if not empty, appended at the end. Do not add a dash.

ifeq ($(APPVERSION_SUFFIX),)
Expand Down
52 changes: 22 additions & 30 deletions src/common/script.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,66 +130,58 @@ int format_opscript_script(const uint8_t script[],
// However, signing such outputs is part of BIP-0322, and there's no danger in allowing them.

if (script_len == 1) {
--out_ctr; // remove extra space
} else {
// We parse the rest as a single push opcode.
// This supports a subset of the scripts that bitcoin-core considers standard.
out[out_ctr - 1] = '\0'; // remove extra space
return out_ctr;
}

size_t offset = 1; // start after OP_RETURN
int num_pushes = 0;
const char hex[] = "0123456789abcdef";

while (offset < script_len && num_pushes < 5) {
uint8_t opcode = script[offset++];
size_t hex_length = 0; // Data length to process

uint8_t opcode = script[1]; // the push opcode
if (opcode > OP_16 || opcode == OP_RESERVED || opcode == OP_PUSHDATA2 ||
opcode == OP_PUSHDATA4) {
return -1; // unsupported
}

int hex_offset = 1;
size_t hex_length = 0; // if non-zero, `hex_length` bytes starting from script[hex_offset]
// must be hex-encoded

if (opcode == OP_0) {
if (script_len != 1 + 1) return -1;
out[out_ctr++] = '0';
} else if (opcode >= 1 && opcode <= 75) {
hex_offset += 1;
hex_length = opcode;

if (script_len != 1 + 1 + hex_length) return -1;
if (offset + hex_length > script_len) return -1; // out of bounds
} else if (opcode == OP_PUSHDATA1) {
// OP_RETURN OP_PUSHDATA1 <len:1-byte> <data:len bytes>
hex_offset += 2;
hex_length = script[2];

if (script_len != 1 + 1 + 1 + hex_length || hex_length > 80) return -1;
if (offset >= script_len) return -1; // out of bounds for length byte
hex_length = script[offset++];
if (hex_length > 80 || offset + hex_length > script_len) return -1;
tdejoigny-ledger marked this conversation as resolved.
Show resolved Hide resolved
} else if (opcode == OP_1NEGATE) {
if (script_len != 1 + 1) return -1;

out[out_ctr++] = '-';
out[out_ctr++] = '1';
} else if (opcode >= OP_1 && opcode <= OP_16) {
if (script_len != 1 + 1) return -1;

// encode OP_1 to OP_16 as a decimal number
uint8_t num = opcode - 0x50;
if (num >= 10) {
out[out_ctr++] = '0' + (num / 10);
}
out[out_ctr++] = '0' + (num % 10);
} else {
return -1; // can never happen
}

if (hex_length > 0) {
const char hex[] = "0123456789abcdef";

out[out_ctr++] = '0';
out[out_ctr++] = 'x';
for (unsigned int i = 0; i < hex_length; i++) {
uint8_t data = script[hex_offset + i];
uint8_t data = script[offset + i];
out[out_ctr++] = hex[data / 16];
tdejoigny-ledger marked this conversation as resolved.
Show resolved Hide resolved
out[out_ctr++] = hex[data % 16];
}
offset += hex_length;
}

num_pushes++;
out[out_ctr++] = ' ';
}

out[out_ctr++] = '\0';
out[out_ctr - 1] = '\0';
return out_ctr;
}
}
8 changes: 6 additions & 2 deletions src/common/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,12 @@ int get_script_address(const uint8_t script[], size_t script_len, char *out, siz

#endif

// the longest OP_RETURN description "OP_RETURN 0x" followed by 160 hexadecimal characters
#define MAX_OPRETURN_OUTPUT_DESC_SIZE (12 + 80 * 2 + 1)
// the longest OP_RETURN description is upper bounded by:
// - 9 bytes for "OP_RETURN"
// - 5 times 3 for the " 0x"
// - up to 2 * 80 = 160 hexadecimal bytes
// - the termination null character
#define MAX_OPRETURN_OUTPUT_DESC_SIZE (9 + 5 * 3 + 2 * 80 + 1)

/**
* Formats a valid OP_RETURN script for user verification. The resulting string is "OP_RETURN
Expand Down
Binary file modified tests/snapshots/nanos/test_dashboard/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/snapshots/nanosp/test_dashboard/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/snapshots/nanox/test_dashboard/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/snapshots/stax/test_dashboard/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 13 additions & 6 deletions unit-tests/test_script.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,18 @@ static void test_format_opscript_script_valid(void **state) {
uint8_t input22[] = {OP_RETURN, OP_1NEGATE};
CHECK_VALID_TESTCASE(input22, "OP_RETURN -1");

uint8_t input_23[] = {OP_RETURN};
CHECK_VALID_TESTCASE(input_23, "OP_RETURN");
uint8_t input23[] = {OP_RETURN, OP_0, OP_1, OP_5, OP_7, OP_16};
CHECK_VALID_TESTCASE(input23, "OP_RETURN 0 1 5 7 16");

uint8_t input24[] = {
OP_RETURN, OP_8, OP_1NEGATE, OP_PUSHDATA1, 15, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, OP_0, OP_PUSHDATA1,
7, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
CHECK_VALID_TESTCASE(input24,
"OP_RETURN 8 -1 0x0102030405060708090a0b0c0d0e0f 0 0x11223344556677");

uint8_t input_25[] = {OP_RETURN};
CHECK_VALID_TESTCASE(input_25, "OP_RETURN");
}

static void test_format_opscript_script_invalid(void **state) {
Expand All @@ -263,11 +273,8 @@ static void test_format_opscript_script_invalid(void **state) {
{OP_RETURN, OP_PUSHDATA4, 0x06, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
CHECK_INVALID_TESTCASE(input_pushdata4);

uint8_t input_extra_push[] = {OP_RETURN, OP_0, OP_0};
uint8_t input_extra_push[] = {OP_RETURN, 4, 1, 2, 3, 4, 42};
CHECK_INVALID_TESTCASE(input_extra_push);

uint8_t input_extra_push2[] = {OP_RETURN, 4, 1, 2, 3, 4, 42};
CHECK_INVALID_TESTCASE(input_extra_push2);
}

int main() {
Expand Down