Skip to content

Commit

Permalink
JAV-320 Facilitate message signing (#334)
Browse files Browse the repository at this point in the history
* Add message signing digest generator

* Add message signing example

* Actualize the changelog
  • Loading branch information
Radiokot authored May 13, 2024
1 parent 150775c commit 0ac11b7
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Unreleased
- Added `MessageSigningDigest` class to generate digests for message signing

## 7.1.0
- Removed unnecessary `amount` parameter from `InvokeInstanceRequest`.
- Added utility functions for converting between `CCDAmount` and `Energy`. Present in utility class `Converter`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.concordium.sdk.examples;

import com.concordium.sdk.crypto.ed25519.ED25519SecretKey;
import com.concordium.sdk.transactions.Index;
import com.concordium.sdk.transactions.MessageSigningDigest;
import com.concordium.sdk.transactions.SignerEntry;
import com.concordium.sdk.transactions.TransactionSigner;
import com.concordium.sdk.types.AccountAddress;
import lombok.val;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import picocli.CommandLine;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.concurrent.Callable;

/**
* Signs the given message in the same way the transactions are signed.
*/
@CommandLine.Command(name = "SignMessage", mixinStandardHelpOptions = true)
public class SignMessage implements Callable<Integer> {
@CommandLine.Option(
names = {"--endpoint"},
description = "GRPC interface of the node.",
defaultValue = "http://localhost:20001")
private String endpoint;

@CommandLine.Option(
names = "--hex",
description = "Whether the given file contains HEX-encoded data")
private boolean isHex;

@CommandLine.Parameters(
index = "0",
description = "A file containing the message"
)
private Path messageFilePath;

@Override
public Integer call() throws Exception {
val signer = TransactionSigner.from(
SignerEntry.from(
Index.from(0),
Index.from(0),
ED25519SecretKey.from("7100071c835a0a35e86dccba7ee9d10b89e36d1e596771cdc8ee36a17f7abbf2")
)
);
val address = AccountAddress.from("3WZE6etUvVp1eyhEtTxqZrQaanTAZnZCHEmZmDyCbCwxnmQuPE");
val fileContents = Files.readAllBytes(messageFilePath);
System.out.println(messageFilePath);
val message = (isHex) ? Hex.decodeHex(new String(fileContents)) : fileContents;
val digest = MessageSigningDigest.from(address, message);
val signature = signer.sign(digest);
System.out.println(Hex.encodeHexString(
Objects.requireNonNull(
Objects.requireNonNull(signature.getSignatures().firstEntry())
.getValue()
.getSignatures()
.firstEntry()
)
.getValue()
.getBytes()
));
return 0;
}

public static void main(String[] args) {
int exitCode = new CommandLine(new SignMessage()).execute(args);
System.exit(exitCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.concordium.sdk.transactions;

import com.concordium.sdk.crypto.SHA256;
import com.concordium.sdk.types.AccountAddress;
import com.concordium.sdk.types.UInt64;
import lombok.SneakyThrows;
import lombok.val;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class MessageSigningDigest {

/**
* Creates a digest for signing a regular message with {@link TransactionSigner}.
*
* @param address address of the account signing the message.
* @param message message contents.
* @return 32-byte digest which can be signed by {@link TransactionSigner}.
*/
public static byte[] from(AccountAddress address, byte[] message) {
// When signing a transaction, the 32 bytes of the account address
// gets followed by the account nonce, which is uint64 and by design >= 1.
// In order to sign a regular message and ensure that the user
// does not accidentally sign a transaction, 0 is used as certainly invalid nonce.
// This results in the following sequence: [32 bytes of the address, 8 zero bytes, message].
val finalMessage = ByteBuffer
.allocate(AccountAddress.BYTES + UInt64.BYTES + message.length)
.put(address.getBytes())
.put(new UInt64(0).getBytes())
.put(message)
.array();
return SHA256.hash(finalMessage);
}

/**
* Creates a digest for signing a regular plain-text message with {@link TransactionSigner}.
*
* @param address address of the account signing the message.
* @param message plain-text message.
* @return 32-byte digest which can be signed by {@link TransactionSigner}.
*/
@SneakyThrows
public static byte[] from(AccountAddress address, String message) {
return from(address, message.getBytes(StandardCharsets.UTF_8));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.concordium.sdk.transactions;

import com.concordium.sdk.types.AccountAddress;
import lombok.SneakyThrows;
import lombok.val;
import org.apache.commons.codec.binary.Hex;
import org.junit.Before;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertArrayEquals;

public class MessageSigningDigestTest {
private static final AccountAddress SIGNER_ADDRESS =
AccountAddress.from("3WZE6etUvVp1eyhEtTxqZrQaanTAZnZCHEmZmDyCbCwxnmQuPE"); // Dummy address

private final Map<String, String> hexTestVectors = new HashMap<>();
private final Map<String, String> plainTestVectors = new HashMap<>();

@Before
@SneakyThrows
public void setup() {
hexTestVectors.put(
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000",
"e8a1287efd3a0d41e4b7b0a7951c5b67421bd87e659408494c8174c5bef3df09"
);
hexTestVectors.put(
"d090d0b1d180d0b0d0bad0b0d0b4d0b0d0b1d180d0b0",
"f31c19fa2087a2f04f3cbb91cad10c4081969f29e8407930ce13f50682d9c470"
);
hexTestVectors.put(
"58e7030200000000e2753e1df53f25ed482ed42f66e69651961783f3e2978a8512a68b9408f30db600e2753e1df53f25ed482ed42f66e69651961783f3e2978a8512a68b9408f30db600e2753e1df53f25ed482ed42f66e69651961783f3e2978a8512a68b9408f30db6e2753e1df53f25ed482ed42f66e69651961783f3e2978a8512a68b9408f30db60300000000000000000000000000000037a2a8e52efad975dbf6580e7734e4f249eaa5ea8a763e934a8671cd7e446499632f567c9321405ce201a0a38615da41efe259ede154ff45ad96cdf860718e79bde07cff72c4d119c644552a8c7f0c413f5cf5390b0ea0458993d6d6374bd90437a2a8e52efad975dbf6580e7734e4f249eaa5ea8a763e934a8671cd7e44649920ccb643bd010000000300616263",
"38b12945d2f06d444b177ba4e4f1038f070646bfd313adbe5b8e1401ece2702c"
);
hexTestVectors.put(
"",
"c425a3ee7a5706ae8e09884693bd2ba80e843bc7983bac559a3f9edb545acf0c"
);

plainTestVectors.put(
"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks",
"6d054bda20eb34b96d7e2e43df2238f33d50982d183e2589a520762b1a766f90"
);
plainTestVectors.put(
"Абракадабра",
"f31c19fa2087a2f04f3cbb91cad10c4081969f29e8407930ce13f50682d9c470"
);
plainTestVectors.put(
"",
"c425a3ee7a5706ae8e09884693bd2ba80e843bc7983bac559a3f9edb545acf0c"
);
}

@SneakyThrows
@Test
public void testDigestHex() {
for (String messageHex : hexTestVectors.keySet()) {
val message = Hex.decodeHex(messageHex);
val expected = Hex.decodeHex(hexTestVectors.get(messageHex));
val actual = MessageSigningDigest.from(SIGNER_ADDRESS, message);
assertArrayEquals(expected,actual);
}
}

@SneakyThrows
@Test
public void testDigestPlain() {
for (String message : plainTestVectors.keySet()) {
val expected = Hex.decodeHex(plainTestVectors.get(message));
val actual = MessageSigningDigest.from(SIGNER_ADDRESS, message);
assertArrayEquals(expected,actual);
}
}
}

0 comments on commit 0ac11b7

Please sign in to comment.