|
2 | 2 |
|
3 | 3 | 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. |
4 | 4 |
|
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: |
6 | 6 |
|
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 | +``` |
12 | 10 |
|
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. |
14 | 12 |
|
15 | | -```http |
16 | | -POST /auth/api/v1/authentication-management/session |
17 | | -``` |
| 13 | +::: warning KEYS TO USE |
18 | 14 |
|
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). |
20 | 16 |
|
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. |
22 | 18 |
|
23 | | -::: code-group Iroha SDK references |
| 19 | +A signature made with the wrong key pair is rejected with a `422` response. |
24 | 20 |
|
25 | | -```kotlin [Iroha Java/Kotlin SDK] |
26 | | -// Your package value |
27 | | -package something |
| 21 | +::: |
28 | 22 |
|
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 |
33 | 24 |
|
34 | | -class SimpleSigner { |
| 25 | +The examples below sign the address `alice@wonderland.space` with the [test key pair](#test-vector). |
35 | 26 |
|
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 |
41 | 28 |
|
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 |
44 | 32 |
|
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 |
47 | 34 |
|
48 | | - // The user's email address |
49 | | - val toSign = args[2] |
50 | 35 |
|
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() |
53 | 41 |
|
54 | | - // Obtain a `signature` of the user's email address |
55 | | - val signature = keyPair.private.sign(toSign.toByteArray(Charsets.UTF_8)).toHex() |
56 | 42 |
|
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 |
61 | 48 | ``` |
62 | 49 |
|
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' |
65 | 54 |
|
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' |
68 | 57 |
|
69 | 58 | /** |
70 | | - * @param {string} publicKeyHex - ed25519 pub key hex |
71 | | - * @param {string} privateKeyHex - ed25519 private key hex |
72 | 59 | * @param {string} email |
| 60 | + * @param {string} authPrivateKeyHex - a 128-character key is the private key |
| 61 | + * followed by the public key |
73 | 62 | * @returns {string} - email signature hex |
74 | 63 | */ |
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' |
91 | 72 | }) |
| 73 | + |
| 74 | + const digest = Buffer.from( |
| 75 | + blake2b(new TextEncoder().encode(email), { dkLen: 32 }) |
| 76 | + ) |
| 77 | + |
| 78 | + return sign(null, digest, key).toString('hex') |
92 | 79 | } |
93 | 80 |
|
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 | + ) |
99 | 86 | ) |
100 | | - |
101 | | -console.log(signature) |
102 | | -// => 9729e8fbcd425bfe48809cc996c9e6d3cecddf0848a51d8758582b3c84bb2caca8e41a8290018aa7064f0b9ec61d2b1a155d5e4c772bc992d918528cf6cb6308 |
| 87 | +// => 57e7115dfb9faa9add2d2ceb321c20db8c1e7f468d2ffc122793fa61e8ed61581580faaeae83a07fe857894bb33defd61c4ba099b981020146fe8d2be00e630a |
103 | 88 | ``` |
104 | 89 |
|
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() |
117 | 106 | } |
118 | | -""") |
| 107 | +``` |
| 108 | + |
| 109 | +::: |
119 | 110 |
|
120 | | -# Hash the user's email address: |
121 | | -hashed_email = iroha.hash(b"email@address") |
| 111 | +## Test vector {#test-vector} |
122 | 112 |
|
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: |
125 | 114 |
|
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 |
128 | 130 | ``` |
129 | 131 |
|
130 | | -::: |
| 132 | +For the full request and the tokens it returns, see [Authorizing an account](./authorizing-an-account.md). |
0 commit comments