Skip to content

Commit

Permalink
Merge pull request #151 from Concordium/signHelpers-support-binary-me…
Browse files Browse the repository at this point in the history
…ssage

signMessage and verifyMessageSignature now supports a binary message
  • Loading branch information
shjortConcordium authored Mar 23, 2023
2 parents 9174371 + 619eb74 commit eeefb91
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 36 deletions.
5 changes: 5 additions & 0 deletions packages/common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
### Changed

- Bumped @concordium/rust-bindings to 0.11.0. (Includes a fix to serialization of negative numbers for smart contract values)
- `signMessage` and `verifyMessageSignature` can now handle the message being a buffer/Uint8Array instead of only a utf8 string.

### Fixed

- `serializeTypeValue` now reports an error when called with invalid data, such as a receive function with missing schema, or a schema that cannot be parsed.

## 6.3.0 2023-02-27

Expand Down
2 changes: 2 additions & 0 deletions packages/common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ if (!verifyMessageSignature(message, signature, accountInfo)) {
}
```

The message can either be a utf8 encoded string or a Uint8Array directly containing the message bytes.

## Deserialize smart contract types with only the specific type's schema
The SDK exposes a general function to deserialize smart contract values from binary format to their JSON representation. In the previous sections the schema used was assumed to be the schema for an entire module, this function can be used with the schema containing only the specific type of the parameter, return value, event or error.

Expand Down
8 changes: 1 addition & 7 deletions packages/common/src/serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,13 +518,7 @@ export function serializeTypeValue(
JSON.stringify(value),
rawSchema.toString('hex')
);
try {
return Buffer.from(serializedValue, 'hex');
} catch (e) {
throw new Error(
'unable to deserialize value, due to: ' + serializedValue
); // In this case serializedValue is the error message from the rust module
}
return Buffer.from(serializedValue, 'hex');
}

/**
Expand Down
20 changes: 12 additions & 8 deletions packages/common/src/signHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,41 @@ export function signTransaction(

/**
* @param account the address of the account that will sign this message.
* @param message the message to sign, assumed to be utf8 encoded.
* @param message the message to sign, assumed to be utf8 encoded string or a Uint8Array/buffer.
*/
function getMessageDigest(account: AccountAddress, message: string): Buffer {
function getMessageDigest(
account: AccountAddress,
message: string | Uint8Array
): Buffer {
const prepend = Buffer.alloc(8, 0);
const rawMessage = Buffer.from(message, 'utf8');
const rawMessage =
typeof message === 'string' ? Buffer.from(message, 'utf8') : message;
return sha256([account.decodedAddress, prepend, rawMessage]);
}

/**
* Helper function to sign a message.
* Note that this function prepends the string "MyGoodPrepend" to ensure that the message is not a transaction.
* Note that this function prepends the account address (32 bytes) and 8 zero-bytes to ensure that the message is not a transaction.
* Note that the current prepend is temporary and will later be replaced.
* @param message the message to sign, assumed to be utf8 encoded.
* @param message the message to sign, assumed to be utf8 encoded string or a Uint8Array/buffer.
* @param signer An object that handles the keys of the account, and performs the actual signing.
*/
export function signMessage(
account: AccountAddress,
message: string,
message: string | Uint8Array,
signer: AccountSigner
): Promise<AccountTransactionSignature> {
return signer.sign(getMessageDigest(account, message));
}

/**
* Helper function to verify a signed message.
* @param message the message to sign, assumed to be utf8 encoded.
* @param message the message to sign, assumed to be utf8 encoded string or a Uint8Array/buffer.
* @param signature the signature of a message, from a specific account.
* @param accountInfo the address and credentials of the account
*/
export async function verifyMessageSignature(
message: string,
message: string | Uint8Array,
signature: AccountTransactionSignature,
accountInfo: Pick<
AccountInfo,
Expand Down
4 changes: 4 additions & 0 deletions packages/common/test/serialization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,7 @@ test('serialize type value and serializeUpdateContractParameters give same resul
serializedType.toString('hex')
);
});

test('serializeTypeValue throws an error if unable to serialize', () => {
expect(() => serializeTypeValue('test', Buffer.alloc(0))).toThrow();
});
41 changes: 22 additions & 19 deletions packages/common/test/signHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ const TEST_CREDENTIALS = {
},
};

test('test signMessage', async () => {
const testEachMessageType = test.each(['test', Buffer.from('test', 'utf8')]);

testEachMessageType('[%o] test signMessage', async (message) => {
const account = new AccountAddress(
'3eP94feEdmhYiPC1333F9VoV31KGMswonuHk5tqmZrzf761zK5'
);
const message = 'test';
const signature = await signMessage(
account,
message,
Expand All @@ -41,24 +42,26 @@ test('test signMessage', async () => {
);
});

test('verifyMessageSignature returns true on the correct address/signature', async () => {
const message = 'test';
const signature = await verifyMessageSignature(
message,
{
0: {
0: '445197d79ca90d8cc8440328dac9f307932ade0c03cc7aa575b59b746e26e5f1bca13ade5ff7a56e918ba5a32450fdf52b034cd2580929b21213263e81f7f809',
testEachMessageType(
'[%o] verifyMessageSignature returns true on the correct address/signature',
async (message) => {
const signature = await verifyMessageSignature(
message,
{
0: {
0: '445197d79ca90d8cc8440328dac9f307932ade0c03cc7aa575b59b746e26e5f1bca13ade5ff7a56e918ba5a32450fdf52b034cd2580929b21213263e81f7f809',
},
},
},
{
accountAddress:
'3eP94feEdmhYiPC1333F9VoV31KGMswonuHk5tqmZrzf761zK5',
accountThreshold: 1,
accountCredentials: TEST_CREDENTIALS,
} as unknown as AccountInfo
);
expect(signature).toBeTruthy();
});
{
accountAddress:
'3eP94feEdmhYiPC1333F9VoV31KGMswonuHk5tqmZrzf761zK5',
accountThreshold: 1,
accountCredentials: TEST_CREDENTIALS,
} as unknown as AccountInfo
);
expect(signature).toBeTruthy();
}
);

test('verifyMessageSignature returns false on the incorrect address', async () => {
const message = 'test';
Expand Down
5 changes: 3 additions & 2 deletions packages/rust-bindings/src/external_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ pub fn get_init_contract_parameter_schema_ext(
}

#[wasm_bindgen(js_name = serializeTypeValue)]
pub fn serialize_type_value_ext(value: JsonString, value_type: HexString) -> HexString {
error_to_string(serialize_type_value_aux(value, value_type))
pub fn serialize_type_value_ext(value: JsonString, schema: HexString) -> Result<HexString, String> {
serialize_type_value_aux(value, schema)
.map_err(|e| format!("Unable to serialize value due to: {}", e))
}

#[wasm_bindgen(js_name = createIdRequestV1)]
Expand Down

0 comments on commit eeefb91

Please sign in to comment.