Skip to main content

Sign a transaction

In this section, we will learn how to sign transactions, which involves verifying the authenticity of the user sending the transaction to the blockchain.

We will create an administration function for a bookkeeper responsible for managing and adding books available for review. Only a specific administrator will be authorized to sign the transactions required for this task.

Setting up module arguments

We will use module arguments to ensure that only the administrator can add books. These arguments configure a module on the Chromia blockchain and enable access to variables from a context called chain_context. Here’s how to set it up:

Create a keypair

First, generate a new keypair for the administrator, which will be used to sign transactions. Run the following command in your project folder:

chr keygen --file .chromia/config

This command generates a new keypair and saves it in a file called .chromia/config within your project folder.

Define a struct for module arguments

Next, we need to define a struct to hold our module arguments. This struct should be added to the module where these arguments will be accessed. For this example, add it to src/main/entities.rell:

src/main/entities.rell
struct module_args {
admin_pubkey: byte_array;
}

Configure module arguments in chromia.yml

Next, define the module arguments in the chromia.yml configuration file. Update your chromia.yml file as follows:

blockchains:
book-review:
module: main
moduleArgs:
main:
admin_pubkey: <pubkey from .chromia/config>

Explanation of the example configuration:

  • book-review: This is the name of your blockchain configuration. It should correspond to the blockchain configuration you are working with.
  • module: main: Specifies that the main module is used for this blockchain configuration.
  • moduleArgs: Under this section, you define arguments specific to the module.
    • main: This should match the module name defined in your source code.
    • admin_pubkey: This is the argument name that the module will use to verify the administrator's public key.
    • <pubkey from .chromia/config>: Replace this placeholder with the actual public key extracted from the .chromia/config file. This key is used to authorize administrative actions.

For more details on the chromia.yml configuration file, refer to the Chromia project settings documentation.

Require signed transactions

Now, we will update our create_book operation to require that the administrator signs the transaction. Modify src/main/operations.rell with the following code:

src/main/operations.rell
operation create_book(isbn: text, title: text, author: text) {
val adminPubkey = chain_context.args.admin_pubkey;
require(op_context.is_signer(adminPubkey), "Only admin can create books");

create book ( .isbn = isbn, .title = title, .author = author );
}

Here’s what happens in this operation:

  • Retrieve the administrator's public key: val adminPubkey = chain_context.args.admin_pubkey;
    • This fetches the public key from the module arguments defined in chain_context.
  • Check if the transaction is signed by the administrator: require(op_context.is_signer(adminPubkey), "Only admin can create books");
    • This checks if the transaction is signed with the administrator's public key. If not, it throws an error message: "Only admin can create books".

This ensures that only transactions signed by the administrator can create books.

Setup and test signing

To verify transaction signing, we will update the existing test functions to ensure correct behavior.

Step 1: Update test_add_book

First, add a local keypair for testing in your test file src/test/book_review_test.rell:

src/test/book_review_test.rell
val book_keeper = rell.test.keypair(
priv = x"DEE3B1414196653BF7FA621B2EEFC3146093B1932BA2ABFAEED830906D81972A",
pub = x"0359A8F2CE1BEF95F583169B7DF053AA227A93B2652B0A9C22975FEED638032610"
);

This snippet creates a test keypair for book_keeper with the provided private and public keys.

Next, update your chromia.yml to include this new keypair:

chromia.yml
blockchains:
book-review:
module: main
moduleArgs:
main:
admin_pubkey: <pubkey from .chromia/config>
compile:
rellVersion: 0.14.9
database:
schema: schema_hello
test:
modules:
- test
moduleArgs:
main:
admin_pubkey: "0359A8F2CE1BEF95F583169B7DF053AA227A93B2652B0A9C22975FEED638032610"

In the test section, set the admin_pubkey to match the public key used for book_keeper.

Now, update the existing test_add_book function to utilize the book_keeper keypair:

src/test/book_review_test.rell
function test_add_book() {
rell.test.tx()
.op(create_book("123", "Book1", "Author1"))
.op(create_book("124", "Book2", "Author2"))
.sign(book_keeper)
.run();

val all_books = book @* { };

assert_equals(all_books.size(), 2);
assert_equals(all_books[0].title, "Book1");
assert_equals(all_books[0].author, "Author1");
}

Step 2: Update test_add_book_review

Next, modify the existing test_add_book_review function to use the book_keeper keypair:

src/test/book_review_test.rell
function test_add_book_review() {
rell.test.tx()
.op(create_book("123", "Book1", "Author1"))
.op(create_book_review("123", "Reviewer1", "ReviewText1", 5))
.op(create_book_review("123", "Reviewer2", "ReviewText2", 3))
.sign(book_keeper)
.run();

val reviews = book_review @* { };
val book = book @ { .isbn == "123" };

assert_equals(reviews.size(), 2);
assert_equals(book, reviews[0].book);
assert_equals(reviews[0].reviewer_name, "Reviewer1");
assert_equals(reviews[0].review, "ReviewText1");
assert_equals(reviews[0].rating, 5);
}

Step 3: Add test_get_books

Add a new function called test_get_books to verify the creation of books:

src/test/book_review_test.rell
function test_get_books() {
rell.test.tx()
.op(create_book("123", "Book1", "Author1"))
.op(create_book("124", "Book2", "Author2"))
.sign(book_keeper)
.run();

val books = get_all_books();
assert_equals(books.size(), 2);
}

Step 4: Test non-administrator access

To further check security, create a test where a keypair not configured as an administrator attempts to sign a transaction.

The available users/keypairs are:

bob, alice, trudy, charlie, dave, eve, frank, grace, heidi

Add a definition for the bob keypair in your test file:

src/test/book_review_test.rell
val bob = rell.test.keypairs.bob;

Use this keypair in a test to sign the create_book transaction:

src/test/book_review_test.rell
function test_add_book_as_non_admin() {
rell.test.tx()
.op(create_book("123", "Book1", "Author1"))
.sign(bob)
.run_must_fail();
}
note

This test is expected to fail, indicating that it is correctly enforcing the test condition. Bob's public key is not listed as an administrator in the module arguments, so the transaction should fail.

Run tests

After updating your tests, run them to ensure everything is working as expected:

chr test

This command will execute all the test functions and confirm that the transaction signing and authorization are functioning correctly.