Sign a transaction
In this section, we will learn about signing transactions, which involves verifying the authenticity of the user who is 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..
Setup 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 key pair 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 key pair and stores it in a file called .chromia/config
within your project folder.
Define a struct for module arguments
We need to define a struct to hold our module arguments. This struct should be added to the module where you want to
access these arguments. For this example, add it to 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 example configuration:
book-review
: The name of your blockchain configuration. This should correspond to the blockchain configuration you are working with.module: main
: Specifies that themain
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, visit the
Chromia project settings documentation.
Require signed transactions
Now, update our create_book
operation to require that the administrator signs the transaction. Modify
src/main/operations.rell
with the following code:
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
.
- This fetches the public key from the module arguments defined in
- 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 checks if the transaction is signed with the administrator's public key. If not, it throws an error message:
This ensures that only transactions signed by the administrator can create books.
Setup and test signing
To test the transaction signing, we will update existing test functions to verify the correct behaviour.
Step 1: Update test_add_book
First, add a local key pair for testing in your test file src/test/book_review_test.rell
:
val book_keeper = rell.test.keypair(
priv = x"DEE3B1414196653BF7FA621B2EEFC3146093B1932BA2ABFAEED830906D81972A",
pub = x"0359A8F2CE1BEF95F583169B7DF053AA227A93B2652B0A9C22975FEED638032610"
);
This creates a test key pair for book_keeper
with the provided private and public keys.
Next, update your chromia.yml
to include this new key pair:
blockchains:
book-review:
module: main
moduleArgs:
main:
admin_pubkey: <pubkey from .chromia/config>
compile:
rellVersion: 0.13.5
database:
schema: schema_hello
test:
modules:
- test
moduleArgs:
main:
admin_pubkey: "0359A8F2CE1BEF95F583169B7DF053AA227A93B2652B0A9C22975FEED638032610"
In the test
section, we set the admin_pubkey
to the same public key used for book_keeper
.
Now, update the existing test_add_book
function to use the book_keeper
key pair:
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
Update the existing test_add_book_review
function to use the book_keeper
key pair:
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: Update test_get_books
Add the new test_get_books
function to verify the creation of books:
function test_get_books() {
rell.test.tx()
.op(create_book("123", "Book1", "Author1"))
.op(create_book("124", "Book2", "Author2"))
.sign(book_keeper)
.run();
val reviews = get_all_books();
assert_equals(reviews.size(), 2);
}
Step 4: Test non-administrator access
To further verify security, create another test where a key pair not configured as an administrator attempts to sign a transaction.
The available users/key pairs are:
bob, alice, trudy, charlie, dave, eve, frank, grace, heidi
Add a definition for the bob
key pair in your test file:
val bob = rell.test.keypairs.bob;
And use this key pair in a test to sign the create_book
transaction:
function test_add_book_as_non_admin() {
rell.test.tx()
.op(create_book("123", "Book1", "Author1"))
.sign(bob)
.run_must_fail();
}
This test is expected to fail, which means it is correctly passing 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 verify everything is working as expected:
chr test
This command will execute all the test functions and ensure that the transaction signing and authorization are working correctly.