Skip to content

Commit cf6f043

Browse files
sorabotCre-eD
authored andcommitted
docs: simplify the email signature guide
The signature required for API authorization is a standard Ed25519 signature over the BLAKE2b-256 hash of the email address, so state the formula plainly and lead with examples that need no blockchain SDK. Add a test vector so an implementation can be checked before it is pointed at the API. The previous JavaScript example printed the signature of the unhashed email address, which is not what the endpoint verifies. Also document two things that were missing: the Profile screen shows the private key concatenated with the public key, and the Authorization key pair is the one to sign with. Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
1 parent ea0cfaf commit cf6f043

4 files changed

Lines changed: 107 additions & 91 deletions

File tree

src/getting-started/calling-api-endpoints.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
Once your registration application has been approved, the connection credentials are sent to the email address you provided during registration.
44

5+
::: tip NOTE
6+
7+
The API is intended for automated integrations. To upload, flag or download fraud data manually, use the FIB Web App: it signs every request for you, and no code is needed. See [Tutorials: Web App](../tutorials-web.md), for example [Downloading fraud data](../tutorials-web/downloading-fraud-data.md).
8+
9+
:::
10+
511
Using these credentials, you can implement calls to the FIB API endpoints into the code of your own application or system.
612

713
Following the basic API architecture, your application or system—the _API client_ in this relationship—connects to the FIB _API server_.
814

915
To integrate your API client with the FIB API server, you can use different programming languages and frameworks, including Python, Java, Node.js, RUST, depending on the requirements of your own application or system.
1016

11-
Some tutorials in this documentation provide code snippets in Kotlin/Java that are required in order to perform Iroha-related operations.
17+
Some tutorials in this documentation provide code snippets for the operations that require a signature, such as [Signing user email addresses](../tutorials-api/signing-user-email-addresses.md).
1218

1319
The provided FIB [API endpoints](../api-specification.md) allow users to access the network's blockchain data, enabling users to perform operations such as [submitting](../tutorials-api/submitting-a-contribution.md), [retrieving](../tutorials-api/retrieving-contributions.md) and [flagging contributions](../tutorials-api/flagging-a-contribution.md), etc.
1420

src/overview/web-interface.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ Also see the following related tutorials:
9393
- [Authorizing an account](../tutorials-api/authorizing-an-account.md)
9494
- [Signing user email addresses](../tutorials-api/signing-user-email-addresses.md)
9595

96+
::: tip NOTE
97+
98+
If the private key is displayed as a 128-character string, it is the private key followed by the public key. Whenever an API tutorial asks for a private key, use its first 64 characters.
99+
100+
:::
101+
96102
### 'Blockchain' key pair {#bkp}
97103

98104
Displays public and private keys of the **Blockchain** key pair. These keys are unique for every user on the FIB network and are generated automatically at the time of account registration.

src/tutorials-api/authorizing-an-account.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ For instructions on how to generate the `signature` string, see [Signing user em
3232

3333
The `authPublicKeyHex` string is taken from the user's **Authorization** key pair on the FIB Web App **Profile** screen. For details, see [Web App UI: 'Authorization' key pair](../overview/web-interface.md#akp).
3434

35+
The `signature` string must be produced with the private key of that same **Authorization** key pair. A request signed with the **Blockchain** key pair is rejected with a `422` response.
36+
3537
:::
3638

3739
### Expected result

src/tutorials-api/signing-user-email-addresses.md

Lines changed: 92 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,129 +2,131 @@
22

33
Every API request to [authorize a user in the system](../api-specification/auth-controller/authorizing-a-user-in-the-system.md) requires a signature of the user's email address.
44

5-
The exact implementation to your system may vary depending on the specific SDK or programming language being used. However, the overall sequence of events to sign a user email address should be the following:
5+
The signature is a standard Ed25519 signature over the BLAKE2b-256 hash of the email address, encoded as a Hex string:
66

7-
1. Create a `keyPair` object from the public and private keys of the user's [**Authorization** key pair](../overview/web-interface.md#authorization-key-pair).
8-
2. Obtain a `signature` of the user's email address.
9-
3. Encode the `signature` as a Hex string.
10-
11-
The resulting encoded `signature` Hex string can be used as a part of the body for requests to the following endpoint:
7+
```
8+
signature = Hex( Ed25519-Sign( authPrivateKey, BLAKE2b-256( UTF-8(email) ) ) )
9+
```
1210

13-
> [Authorizing a user in the system](../api-specification/auth-controller/authorizing-a-user-in-the-system.md)
11+
Any cryptographic library that provides Ed25519 and BLAKE2b-256 can produce it, in any programming language. No blockchain SDK is required.
1412

15-
```http
16-
POST /auth/api/v1/authentication-management/session
17-
```
13+
::: warning KEYS TO USE
1814

19-
## Iroha SDK references
15+
Sign with the **Authorization** key pair, not with the **Blockchain** one. Both key pairs are shown on the FIB Web App **Profile** screen. For details, see [Web App UI: 'Authorization' key pair](../overview/web-interface.md#akp).
2016

21-
You can use [any Iroha SDK available](../index.md#what-is-iroha-2) to sign a user's email address. Below are references on how to sign a transaction using the following Iroha SDKs:
17+
If the private key on that screen is 128 characters long, it is the private key followed by the public key. Use its first 64 characters as the private key.
2218

23-
::: code-group Iroha SDK references
19+
A signature made with the wrong key pair is rejected with a `422` response.
2420

25-
```kotlin [Iroha Java/Kotlin SDK]
26-
// Your package value
27-
package something
21+
:::
2822

29-
// Import dependencies
30-
import jp.co.soramitsu.iroha2.keyPairFromHex
31-
import jp.co.soramitsu.iroha2.sign
32-
import jp.co.soramitsu.iroha2.toHex
23+
## Examples
3324

34-
class SimpleSigner {
25+
The examples below sign the address `alice@wonderland.space` with the [test key pair](#test-vector).
3526

36-
fun main(args: Array<String>) {
37-
if (args.size != 3) {
38-
println("Specify public_key, private_key and text to sign")
39-
return
40-
}
27+
::: code-group
4128

42-
// The public key of the user's 'Authorization' key pair
43-
val publicKey = args[0]
29+
```python [Python]
30+
# pip install cryptography
31+
import hashlib
4432

45-
// The private key of the user's 'Authorization' key pair
46-
val privateKey = args[1]
33+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
4734

48-
// The user's email address
49-
val toSign = args[2]
5035

51-
// Create a `keyPair` object from the Hex string of the public and private keys of the user's 'Authorization' key pair
52-
val keyPair = keyPairFromHex(publicKey, privateKey)
36+
def sign_email(email: str, auth_private_key_hex: str) -> str:
37+
# A 128-character key is the private key followed by the public key.
38+
key = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(auth_private_key_hex[:64]))
39+
digest = hashlib.blake2b(email.encode("utf-8"), digest_size=32).digest()
40+
return key.sign(digest).hex()
5341

54-
// Obtain a `signature` of the user's email address
55-
val signature = keyPair.private.sign(toSign.toByteArray(Charsets.UTF_8)).toHex()
5642

57-
// Encode the `signature` as a Hex string
58-
println("Signed message (Hex): $signature")
59-
}
60-
}
43+
print(sign_email(
44+
"alice@wonderland.space",
45+
"413b285d1819a6166b0daa762bb6bef2d082cffb9a13ce041cb0fda5e2f06dc3",
46+
))
47+
# => 57e7115dfb9faa9add2d2ceb321c20db8c1e7f468d2ffc122793fa61e8ed61581580faaeae83a07fe857894bb33defd61c4ba099b981020146fe8d2be00e630a
6148
```
6249

63-
```js [Iroha JavaScript SDK]
64-
// @ts-check
50+
```js [Node.js]
51+
// npm install @noble/hashes
52+
import { createPrivateKey, sign } from 'node:crypto'
53+
import { blake2b } from '@noble/hashes/blake2b'
6554

66-
import { crypto } from '@iroha2/crypto-target-node' // version: 1.1.1
67-
import { freeScope } from '@iroha2/crypto-core' // version: 1.1.1
55+
// DER header that turns 32 raw key bytes into a PKCS#8 Ed25519 key
56+
const PKCS8_ED25519_PREFIX = '302e020100300506032b657004220420'
6857

6958
/**
70-
* @param {string} publicKeyHex - ed25519 pub key hex
71-
* @param {string} privateKeyHex - ed25519 private key hex
7259
* @param {string} email
60+
* @param {string} authPrivateKeyHex - a 128-character key is the private key
61+
* followed by the public key
7362
* @returns {string} - email signature hex
7463
*/
75-
function createEmailSignature(publicKeyHex, privateKeyHex, email) {
76-
return freeScope(() => {
77-
const keyPair = crypto.KeyPair.fromJSON({
78-
public_key: 'ed0120' + publicKeyHex,
79-
private_key: {
80-
digest_function: 'ed25519',
81-
payload: privateKeyHex
82-
}
83-
})
84-
85-
const hashedEmail = crypto.Hash.hash(
86-
'array',
87-
new TextEncoder().encode(email)
88-
).bytes()
89-
90-
return keyPair.sign('array', hashedEmail).payload('hex')
64+
function signEmail(email, authPrivateKeyHex) {
65+
const key = createPrivateKey({
66+
key: Buffer.from(
67+
PKCS8_ED25519_PREFIX + authPrivateKeyHex.slice(0, 64),
68+
'hex'
69+
),
70+
format: 'der',
71+
type: 'pkcs8'
9172
})
73+
74+
const digest = Buffer.from(
75+
blake2b(new TextEncoder().encode(email), { dkLen: 32 })
76+
)
77+
78+
return sign(null, digest, key).toString('hex')
9279
}
9380

94-
// example signature
95-
const signature = createEmailSignature(
96-
'7fbedb314a9b0c00caef967ac5cabb982ec45da828a0c58a9aafc854f32422ac',
97-
'413b285d1819a6166b0daa762bb6bef2d082cffb9a13ce041cb0fda5e2f06dc37fbedb314a9b0c00caef967ac5cabb982ec45da828a0c58a9aafc854f32422ac',
98-
'alice@wonderland.space'
81+
console.log(
82+
signEmail(
83+
'alice@wonderland.space',
84+
'413b285d1819a6166b0daa762bb6bef2d082cffb9a13ce041cb0fda5e2f06dc3'
85+
)
9986
)
100-
101-
console.log(signature)
102-
// => 9729e8fbcd425bfe48809cc996c9e6d3cecddf0848a51d8758582b3c84bb2caca8e41a8290018aa7064f0b9ec61d2b1a155d5e4c772bc992d918528cf6cb6308
87+
// => 57e7115dfb9faa9add2d2ceb321c20db8c1e7f468d2ffc122793fa61e8ed61581580faaeae83a07fe857894bb33defd61c4ba099b981020146fe8d2be00e630a
10388
```
10489

105-
```python [Iroha Python SDK]
106-
# Import dependency
107-
import iroha
108-
109-
# Example ed25519 key pair
110-
key_pair = iroha.KeyPair.from_json("""
111-
{
112-
"public_key": "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0",
113-
"private_key": {
114-
"digest_function": "ed25519",
115-
"payload": "9ac47abf59b356e0bd7dcbbbb4dec080e302156a48ca907e47cb6aea1d32719e7233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0"
116-
}
90+
```kotlin [Kotlin/Java]
91+
// Import dependencies
92+
import jp.co.soramitsu.iroha2.keyPairFromHex
93+
import jp.co.soramitsu.iroha2.sign
94+
import jp.co.soramitsu.iroha2.toHex
95+
96+
// The SDK applies the BLAKE2b-256 hash inside `sign`,
97+
// so the email address is passed to it as raw bytes.
98+
fun signEmail(
99+
email: String,
100+
authPublicKeyHex: String,
101+
authPrivateKeyHex: String,
102+
): String {
103+
val keyPair = keyPairFromHex(authPublicKeyHex, authPrivateKeyHex)
104+
105+
return keyPair.private.sign(email.toByteArray(Charsets.UTF_8)).toHex()
117106
}
118-
""")
107+
```
108+
109+
:::
119110

120-
# Hash the user's email address:
121-
hashed_email = iroha.hash(b"email@address")
111+
## Test vector {#test-vector}
122112

123-
# Sign the user's email address:
124-
signature = key_pair.sign(bytes(hashed_email))
113+
Before you call the API, check your implementation against these values. All of them are public example data, not credentials of a real account:
125114

126-
# Retrieve the encoded Hex string of the user's `signature`
127-
print(f"Encoded signature:\n{bytes(signature).hex()}")
115+
| Field | Value |
116+
| --- | --- |
117+
| Email address | `alice@wonderland.space` |
118+
| Authorization private key | `413b285d1819a6166b0daa762bb6bef2d082cffb9a13ce041cb0fda5e2f06dc3` |
119+
| Authorization public key | `7fbedb314a9b0c00caef967ac5cabb982ec45da828a0c58a9aafc854f32422ac` |
120+
| Signature | `57e7115dfb9faa9add2d2ceb321c20db8c1e7f468d2ffc122793fa61e8ed61581580faaeae83a07fe857894bb33defd61c4ba099b981020146fe8d2be00e630a` |
121+
122+
Ed25519 signatures are deterministic, so a correct implementation returns exactly this signature.
123+
124+
## Using the signature
125+
126+
Send the resulting Hex string as the `signature` field of the authorization request:
127+
128+
```http
129+
POST /auth/api/v1/authentication-management/session
128130
```
129131

130-
:::
132+
For the full request and the tokens it returns, see [Authorizing an account](./authorizing-an-account.md).

0 commit comments

Comments
 (0)