
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
qr-encoding
Advanced tools
The purpose of this document is to describe an efficient way of encoding Ethereum transactions in order to be transmitted over using QR codes. Since the amount of data to be transmitted is very limited we apply simple compression, and create numeric codes, since it is more effectively transmittable using QR codes.
In case of message signing, since there is no limitation of message bytes, we use no special encoding. The following table displays the simple encoding we use:
| 0-1 digits | 2-3 digits | n digits message |
|---|---|---|
| error check | Signature type + version | message bytes |
Error check = rightmost 2 digits of the sha3(signature + version + message bytes)
The Transaction encoding has the following characteristics:
We put together the transaction in four steps:
| 0-1 hex digits | 2-11. digits | 12-51. digits |
|---|---|---|
| Signature type + chain id + version | from address (only part) | to address |
| n digits delimited (only present if not encoded in 0-1 digits) | delimiter |
|---|---|
| chainId (only if was not encoded into the 0-1 digits) | (if chainId was encoded before) |
| n digits delimited | delimiter | n digits delimited | delimiter |
|---|---|---|---|
| nonce | gasPrice |
| n digits delimited | delimiter | n digits delimited | delimiter |
|---|---|---|---|
| gasLimit | value |
| n digits |
|---|
| data |
From address: Only the lefmost 4, 18-19 th (counting from 0 th) , and the rightmost 4 digits is kept from the from address.
0-1 digits encoded in hex, and then 9 escaped. This contains the Protocol version encoding, the signing type and the chainId.
0-1 bits (lowest bits): Protocol version
| Code (hex) | Meaning |
|---|---|
0 | Current version |
1-3 | Future versions |
2-3 bits: signing type
| Code (hex) | Meaning |
|---|---|
0 | sign_transaction |
1 | sign_message |
2 | sign_personal_message |
3 | sign_typed_data |
4-7 bits: ChainId
| Code (hex) | Meaning |
|---|---|
0 | 1: Frontier, Homestead, Metropolis, the Ethereum public main network |
1 | 1: Classic, the (un)forked public Ethereum Classic main network, chain ID 61 |
2 | 1: Expanse, an alternative Ethereum implementation, chain ID 2 |
3 | 2: Morden, the public Ethereum testnet, now Ethereum Classic testnet |
4 | 3: Ropsten, the public cross-client Ethereum testnet |
5 | 4: Rinkeby, the public Geth PoA testnet |
6 | 42: Kovan, the public Parity PoA testnet |
7 | 77: Sokol, the public POA Network testnet |
8 (!!!) | chainId encoded starting in the 54 th digit, and not here!!!! |
9 | 99: Core, the public POA Network main network |
a | 7762959: Musicoin, the music blockchain |
a-f | reserved |
If the chainId of the network differs from the above, then choinId '8' should be encoded here, and chainId should be encoded into digit no. 52 of the "flat string", and a delimiter should be placed after.
Eg.:
The process during which form an alphanumeric code containing chars of 0-9 a-f, we create a code that solely contains numeric 0-9 characters. We do this using escaping 9, a-f characters using '9'. This is useful since using numeric characters only, QR code can transmit 1.6 times more data, than in case of alphanumeric data.
| Hex digit to encode | Encoded digit | Meaning |
|---|---|---|
| a | 90 | |
| b | 91 | |
| c | 92 | |
| d | 93 | |
| e | 94 | |
| f | 95 | |
| | | 96 | delimiter between variable length data |
| zero compression | 97 | compress zeros (see later) |
| reserved, do not use | 98 | |
| 9 | 99 |
The rationale of this step is that QR codes can transmit 1.6 times more numeric, than alphanumeric data. We are still better off with escaping, than with using alphanumeric mode.
The reason of zero compression is to compress the many zeros in ethereum transactions, arising because of 0 padding.
| To encode | Encoded |
|---|---|
| 0000 | 970 |
| 00000 | 971 |
| 000000 | 972 |
| 000000000000 | 973 |
| 000000000000000000000000 | 974 |
| 0 x 48 (fourtyeight zeros) | 975 |
| reserved, do not use | 976 |
| reserved, do not use | 977 |
| reserved, do not use | 978 |
| do not use | 979 |
Eg.: to encode 0x0000000000000000000000000000002a hex, we first remove the '0x' from the beginning, then excape the 9-f digits. So we end up with: 000000000000000000000000000000290, then we do the zero compressing: we would start from the left and check what is the biggest 0 chunk we can compress. That will be 24 zeros since that is the biggest chunk we can compress at once. And the next would be 6 more zeros. so the encoded hex would be: 974972290 th This is much shorter than the original, and contains only numbers.
After the zero compressed numeric string has been created, we calculate an error checking
Verifiable string looks like the following:
| 0-1 digits | n digits |
|---|---|
| Error check | Zero compressed numeric string |
The error check is created using the rightmost two digits of keccak256(Zero compressed numeric string).
Let's start with the following transaction json data:
{"type":"sign_transaction":
"from":"0x1204d2a2f9a823c1a585fde6514a17ff695e0001",
"to":"0xb2565129883cfffe21a88eeafaa3ccd9ec5f6539",
"chainId":"1",
"nonce":"0x2d",
"gasPrice":"0x0165a0bc00",
"gasLimit":"0x5208",
"value":"0x68155a43676e00000",
"data":"0x000000000000000000000000000000af00000000000000000000000023ff99"}
1204d2a2f9a823c1a585fde6514a17ff695e0001b2565129883cfffe21a88eeafaa3ccd9ec5f65392d|0165a0bc00|5208|68155a43676e00000|000000000000000000000000000000af00000000000000000000000023ff99
Notice:
1204850001b2565129883cfffe21a88eeafaa3ccd9ec5f65392d|0165a0bc00|5208|68155a43676e00000|000000000000000000000000000000af00000000000000000000000023ff99
0 (version) + 4 * 0 (sign_transaction) + 16 * 0 (chain id code) = 00
We get:
001204850001b2565129883cfffe21a88eeafaa3ccd9ec5f65392d|0165a0bc00|5208|68155a43676e00000|000000000000000000000000000000af00000000000000000000000023ff99
00120485000191256512998839295959594219088949490959090392929399949259565399293|0165900919200|5208|6815590436769400000|00000000000000000000000000000090950000000000000000000000002395959999
Notice that after 9 escaping there should be no a-f in the code.
The code of the delimiters is 96 so we end up:
001204850001912565129988392959595942190889494909590903929293999492595653992939601659009192009652089668155904367694000009600000000000000000000000000000090950000000000000000000000002395959999
Since we have a 24 length of zeroes in the code (we substitute that with code of 974) the intermediate encoding is this:
001204850001912565129988392959595942190889494909590903929293999492595653992939601659009192009652089668155904367694000009697400000090959742395959999
Since there is a 6 length of zeroes (encoded 972) we can further compress it to have:
001204850001912565129988392959595942190889494909590903929293999492595653992939601659009192009652089668155904367694000009697497290959742395959999
Since there is still a 5 length of zeroes (encoded 971) we can substitute:
0012048500019125651299883929595959421908894949095909039292939994925956539929396016590091920096520896681559043676949719697497290959742395959999
There is no 4 zeros to substitute, so we stop zero compression.
sha3(0x0012048500019125651299883929595959421908894949095909039292939994925956539929396016590091920096520896681559043676949719697497290959742395959999) = 34787a5c78d1ce7077a2723a945f41ada69a102add88ef3ddcede8377234babf
The rightmost two digits are bf.
91950012048500019125651299883929595959421908894949095909039292939994925956539929396016590091920096520896681559043676949719697497290959742395959999
So from a 308 bytes long of byte data we got a 146 long of numeric data.
To decode the above we do the following:
We take the leftmost two digits, if it starts with 9 then we take the next digit, if that is nine too, we take the next one. The 0 th digit is 9 so we take the next one which is 1,and 91 decodes to 'b'. Then we take the next digit whic is 9 and then the next one which is 5 , so we decode 95 to 'f'. So the error check byte is bf.
We calculate the sha3 of the remaining code taken as hex to calculate:
sha3(0012048500019125651299883929595959421908894949095909039292939994925956539929396016590091920096520896681559043676949719697497290959742395959999) = 34787a5c78d1ce7077a2723a945f41ada69a102add88ef3ddcede8377234babf
The rightmost two digits bf match with the error check code of bf, so we are fine. (If not then the QR code we read was probably wrong.)
!!!!!This should be the first unescape. Otherwise the code will be wrong!!!!!
First we mark all 99 in the code to get:
00120485000191256512m88392959595942190889494909590903929293m9492595653m2939601659009192009652089668155904367694971969749729095974239595mm
We found 971 in the code and replaced that with 5 zeros to get:
00120485000191256512m88392959595942190889494909590903929293m9492595653m293960165900919200965208966815590436769400000969749729095974239595mm
We found 972 in the code and converted it to 000000 to get:
00120485000191256512m88392959595942190889494909590903929293m9492595653m293960165900919200965208966815590436769400000969740000009095974239595mm
We found 974 in the code and converted it to 24 x 0 to get:
00120485000191256512m88392959595942190889494909590903929293m9492595653m293960165900919200965208966815590436769400000960000000000000000000000000000009095000000000000000000000000239595mm
We found no code of 975 so we are done with decompression.
Unescape the delimiters (encoded 96):
00120485000191256512m88392959595942190889494909590903929293m9492595653m293|0165900919200|5208|6815590436769400000|0000000000000000000000000000009095000000000000000000000000239595mm
Then decode 90-95 to get a-f:
001204850001b256512m883cfffe21a88eeafaa3ccdmec5f653m2d|0165a0bc00|5208|68155a43676e00000|000000000000000000000000000000af00000000000000000000000023ffmm
Then substitute 9 instead of 'm':
001204850001b2565129883cfffe21a88eeafaa3ccd9ec5f65392d|0165a0bc00|5208|68155a43676e00000|000000000000000000000000000000af00000000000000000000000023ff99
Notes:
FAQs
Encodes json format of ethereum transaction into numeric string
We found that qr-encoding demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.