Skip to content

Commit 2beec20

Browse files
committed
Merge remote-tracking branch 'origin/develop'
2 parents 86472a8 + a9fb264 commit 2beec20

15 files changed

+9594
-6517
lines changed

.github/workflows/lint-build-test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
uses: actions/checkout@v3
1515

1616
- name: Install
17-
run: yarn install --immutable --immutable-cache --check-cache
17+
run: yarn install --immutable
1818

1919
- name: Lint
2020
run: yarn run lint

.github/workflows/release-please.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ jobs:
1515
with:
1616
fetch-depth: 0
1717

18-
- name: Use Node.js 16.x
18+
- name: Use Node.js 22.13.1
1919
uses: actions/setup-node@v1
2020
with:
21-
node-version: 16
21+
node-version: 22.13.1
2222
registry-url: 'https://registry.npmjs.org'
2323
scope: '@lukso'
2424

2525
- name: 🧰 Install
26-
run: yarn install --immutable --immutable-cache --check-cache
26+
run: yarn install --immutable
2727

2828
- name: 💅 Lint
2929
run: yarn run lint

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ npm-debug.log*
55
yarn-debug.log*
66
yarn-error.log*
77
lerna-debug.log*
8+
.vscode
9+
.DS_Store
810

911
# Diagnostic reports (https://nodejs.org/api/report.html)
1012
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

.yarn/install-state.gz

771 KB
Binary file not shown.

.yarn/releases/yarn-4.6.0.cjs

+934
Large diffs are not rendered by default.

.yarnrc.yml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
nodeLinker: node-modules
2+
3+
yarnPath: .yarn/releases/yarn-4.6.0.cjs

README.md

+67
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,73 @@ import { EIP191Signer } from '@lukso/eip191-signer.js';
3232
const eip191Signer = new EIP191Signer();
3333
```
3434

35+
### Example: signing a LSP6KeyManager relay transaction
36+
37+
The [LSP6-KeyManager](https://docs.lukso.tech/standards/universal-profile/lsp6-key-manager#relay-execution) standard uses version 0 for signed messages. Therefore, it must use [`signDataWithIntendedValidator`](#signdatawithintendedvalidator).
38+
39+
```js
40+
// Web3.js Example
41+
42+
const chainId = await web3.eth.getChainId();
43+
44+
// Indicate that the signature is always valid
45+
const validityTimestamp = 0;
46+
47+
let encodedMessage = web3.utils.encodePacked(
48+
{ value: LSP25_VERSION, type: 'uint256' }, // //LSP25 Version = 25;
49+
{ value: chainId, type: 'uint256' },
50+
{ value: nonce, type: 'uint256' },
51+
{ value: validityTimestamp, type: 'uint256' },
52+
{ value: msgValue, type: 'uint256' },
53+
{ value: abiPayload, type: 'bytes' },
54+
);
55+
56+
let eip191Signer = new EIP191Signer();
57+
58+
let { signature } = await eip191Signer.signDataWithIntendedValidator(
59+
keyManagerAddress, // intended validator is the address of the Key Manager
60+
encodedMessage, //
61+
controllerPrivateKey,
62+
);
63+
```
64+
65+
```js
66+
// ethers.js Example
67+
68+
const network = await provider.getNetwork();
69+
70+
const chainId = network.chainId;
71+
72+
// The block.timestamp(s) which the signature is valid in between
73+
const startingTimestamp = ....;
74+
const endingTimestamp = ....;
75+
76+
const validityTimestamp = ethers.utils.hexConcat([
77+
ethers.utils.zeroPad(ethers.utils.hexlify(startingTimestamp), 16),
78+
ethers.utils.zeroPad(ethers.utils.hexlify(endingTimestamp), 16),
79+
]);
80+
81+
let encodedMessage = ethers.utils.solidityPack(
82+
["uint256", "uint256", "uint256", "uint256", "uint256", "bytes"],
83+
[
84+
LSP25_VERSION, // LSP25_VERSION = 25;
85+
chainId,
86+
nonce,
87+
validityTimestamp
88+
msgValue,
89+
abiPayload
90+
]
91+
);
92+
93+
let eip191Signer = new EIP191Signer();
94+
95+
let { signature } = await eip191Signer.signDataWithIntendedValidator(
96+
keyManagerAddress, // intended validator is the address of the Key Manager
97+
encodedMessage, //
98+
controllerPrivateKey,
99+
);
100+
```
101+
35102
## hashEthereumSignedMessage
36103

37104
```javascript

jest.config.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
module.exports = async () => {
22
return {
33
roots: ['<rootDir>'],
4+
preset: 'ts-jest',
45
transform: {
5-
'^.+\\.tsx?$': [
6-
'esbuild-jest-transform',
7-
{
8-
sourcemap: true,
9-
},
10-
],
6+
'^.+\\.tsx?$': 'ts-jest',
117
},
128
collectCoverageFrom: ['src/**/*.ts'],
139
coveragePathIgnorePatterns: [

package.json

+11-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lukso/eip191-signer.js",
3-
"version": "0.2.2",
3+
"version": "0.3.0",
44
"description": "Helper Library to allows to sign any EIP191 data",
55
"main": "build/main/src/index.js",
66
"typings": "build/main/src/index.d.ts",
@@ -12,7 +12,7 @@
1212
"test": "jest",
1313
"lint": "eslint src --ext .ts",
1414
"lint-fix": "eslint src --ext .ts --fix",
15-
"build": "run-p build:*",
15+
"build": "run-p 'build:*'",
1616
"build:main": "tsc -p tsconfig.json",
1717
"build:module": "tsc -p tsconfig.module.json",
1818
"release": "release-please",
@@ -49,27 +49,23 @@
4949
"@typescript-eslint/eslint-plugin": "^5.37.0",
5050
"@typescript-eslint/parser": "^5.37.0",
5151
"all-contributors-cli": "^6.21.0",
52-
"esbuild": "^0.15.7",
53-
"esbuild-jest-transform": "^1.1.0",
5452
"eslint": "^8.23.1",
55-
"eslint-config-prettier": "^8.3.0",
53+
"eslint-config-prettier": "^8.5.0",
5654
"eslint-plugin-eslint-comments": "^3.2.0",
5755
"eslint-plugin-import": "^2.26.0",
5856
"eslint-plugin-jest": "^27.0.4",
59-
"eslint-plugin-prettier": "^4.0.0",
60-
"jest": "^29.0.2",
57+
"eslint-plugin-prettier": "^4.2.1",
58+
"jest": "^29.0.3",
6159
"npm-run-all": "^4.1.5",
62-
"prettier": "^2.3.2",
60+
"prettier": "^2.7.1",
6361
"release-please": "^14.7.0",
64-
"ts-jest": "^29.0.1",
62+
"ts-jest": "^29.2.5",
6563
"ts-node": "^10.9.1",
66-
"typescript": "^4.8.3",
64+
"typescript": "^5.7.3",
6765
"typescript-compiler": "^1.4.1-2"
6866
},
6967
"dependencies": {
70-
"eth-lib": "^0.1.29",
71-
"ethereumjs-account": "^3.0.0",
72-
"ethereumjs-util": "^7.1.5",
73-
"web3-utils": "^1.7.5"
74-
}
68+
"viem": "^2.23.2"
69+
},
70+
"packageManager": "[email protected]"
7571
}

src/index.ts

+76-71
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,110 @@
11
/*
22
This file contains functions to sign message for executeRelayCall.
33
*/
4-
import Account from 'eth-lib/lib/account';
5-
import { bufferToHex, keccak256 } from 'ethereumjs-util';
6-
import utils from 'web3-utils';
7-
8-
import { Message } from './interfaces';
4+
import {
5+
type Address,
6+
type ByteArray,
7+
hexToBytes,
8+
isAddress,
9+
isBytes,
10+
isHex,
11+
keccak256,
12+
recoverAddress,
13+
serializeSignature,
14+
stringToBytes,
15+
} from 'viem';
16+
import { sign } from 'viem/accounts';
917

18+
import type { Message } from './interfaces';
1019
export class EIP191Signer {
11-
hashEthereumSignedMessage(message: string) {
12-
const messageHex = utils.isHexStrict(message)
20+
hashEthereumSignedMessage(message: string | ByteArray): `0x${string}` {
21+
const messageBytes = isBytes(message)
1322
? message
14-
: utils.utf8ToHex(message);
15-
const messageBytes = utils.hexToBytes(messageHex);
16-
const messageBuffer = Buffer.from(messageBytes);
17-
const preamble =
18-
'\x19' + '\x45' + 'thereum Signed Message:\n' + messageBytes.length;
19-
const preambleBuffer = Buffer.from(preamble);
20-
const ethMessage = Buffer.concat([preambleBuffer, messageBuffer]);
21-
return bufferToHex(keccak256(ethMessage));
23+
: isHex(message, { strict: true })
24+
? hexToBytes(message)
25+
: stringToBytes(message);
26+
const encoder = new TextEncoder();
27+
const preambleBytes = encoder.encode(
28+
`\x19\x45thereum Signed Message:\n${messageBytes.length}`,
29+
);
30+
const ethMessage = new Uint8Array(
31+
preambleBytes.length + messageBytes.length,
32+
);
33+
ethMessage.set(preambleBytes, 0); // Add the byte array starting at index 0
34+
ethMessage.set(messageBytes, preambleBytes.length); // Add the string bytes after
35+
return keccak256(ethMessage, 'hex');
2236
}
23-
24-
hashDataWithIntendedValidator(validatorAddress: string, data: string) {
37+
hashDataWithIntendedValidator(
38+
validatorAddress: Address,
39+
data: string | ByteArray,
40+
) {
2541
// validator address
26-
if (!utils.isAddress(validatorAddress))
42+
if (!isAddress(validatorAddress)) {
2743
throw new Error('Validator needs to be a valid address');
28-
29-
const validatorBuffer = Buffer.from(utils.hexToBytes(validatorAddress));
44+
}
45+
const validatorBytes = hexToBytes(validatorAddress);
3046
// data to sign
31-
const dataHex = utils.isHexStrict(data) ? data : utils.utf8ToHex(data);
32-
const dataBuffer = Buffer.from(utils.hexToBytes(dataHex));
33-
47+
const dataBytes = isBytes(data)
48+
? data
49+
: isHex(data, { strict: true })
50+
? hexToBytes(data)
51+
: stringToBytes(data);
3452
// concatenate it
35-
const preambleBuffer = Buffer.from('\x19');
36-
const versionBuffer = Buffer.from('\x00');
37-
const ethMessage = Buffer.concat([
38-
preambleBuffer,
39-
versionBuffer,
40-
validatorBuffer,
41-
dataBuffer,
42-
]);
43-
return bufferToHex(keccak256(ethMessage));
53+
const encoder = new TextEncoder();
54+
const preambleBytes = encoder.encode('\x19\x00');
55+
const ethMessage = new Uint8Array(
56+
preambleBytes.length + validatorBytes.length + dataBytes.length,
57+
);
58+
ethMessage.set(preambleBytes, 0); // Add the byte array starting at index 0
59+
ethMessage.set(validatorBytes, preambleBytes.length); // Add the string bytes after
60+
ethMessage.set(dataBytes, preambleBytes.length + validatorBytes.length); // Add the string bytes after
61+
return keccak256(ethMessage, 'hex');
4462
}
4563

46-
signEthereumSignedMessage(message: string, privateKey: string): Message {
47-
if (!privateKey.startsWith('0x')) {
48-
privateKey = '0x' + privateKey;
49-
}
50-
51-
// 64 hex characters + hex-prefix
52-
if (privateKey.length !== 66) {
53-
throw new Error('Private key must be 32 bytes long');
54-
}
55-
64+
async signEthereumSignedMessage(
65+
message: string,
66+
privateKey: `0x${string}`,
67+
): Promise<Message> {
5668
const hash = this.hashEthereumSignedMessage(message);
57-
const signature = Account.sign(hash, privateKey);
58-
const vrs = Account.decodeSignature(signature);
69+
const signature = await sign({ hash, privateKey, to: 'object' });
5970
return {
71+
v: BigInt(0),
72+
...signature,
6073
message: message,
6174
messageHash: hash,
62-
v: vrs[0],
63-
r: vrs[1],
64-
s: vrs[2],
65-
signature: signature,
75+
signature: serializeSignature(signature),
6676
};
6777
}
6878

69-
signDataWithIntendedValidator(
70-
validatorAddress: string,
71-
data: string,
72-
privateKey: string,
73-
): Message {
74-
if (!privateKey.startsWith('0x')) {
75-
privateKey = '0x' + privateKey;
76-
}
77-
78-
// 64 hex characters + hex-prefix
79-
if (privateKey.length !== 66) {
80-
throw new Error('Private key must be 32 bytes long');
81-
}
79+
async signDataWithIntendedValidator(
80+
validatorAddress: Address,
81+
data: string | ByteArray,
82+
privateKey: `0x${string}`,
83+
): Promise<Message> {
8284
const hash = this.hashDataWithIntendedValidator(validatorAddress, data);
83-
const signature = Account.sign(hash, privateKey);
84-
const vrs = Account.decodeSignature(signature);
85-
85+
const signature = await sign({ hash, privateKey, to: 'object' });
8686
return {
87+
v: BigInt(0),
88+
...signature,
8789
message: data,
8890
messageHash: hash,
89-
v: vrs[0],
90-
r: vrs[1],
91-
s: vrs[2],
92-
signature: signature,
91+
signature: serializeSignature(signature),
9392
};
9493
}
9594

96-
recover(messageHash: string | Message, signature: string): string {
95+
async recover(
96+
messageHash: `0x${string}` | Message,
97+
signature: `0x${string}`,
98+
): Promise<Address> {
9799
if (!!messageHash && typeof messageHash === 'object') {
98100
return this.recover(
99101
messageHash.messageHash,
100-
Account.encodeSignature([messageHash.v, messageHash.r, messageHash.s]),
102+
serializeSignature(messageHash),
101103
);
102104
}
103-
return Account.recover(messageHash, signature);
105+
return await recoverAddress({
106+
hash: messageHash as `0x${string}`,
107+
signature,
108+
});
104109
}
105110
}

src/interfaces.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import type { ByteArray } from 'viem';
2+
13
export interface Message {
2-
message: string;
3-
messageHash: string;
4-
v: string;
5-
r: string;
6-
s: string;
7-
signature: string;
4+
message: string | ByteArray;
5+
messageHash: `0x${string}`;
6+
v: bigint;
7+
r: `0x${string}`;
8+
s: `0x${string}`;
9+
signature: `0x${string}`;
810
}

0 commit comments

Comments
 (0)