Skip to content

Commit 61ce510

Browse files
authored
Merge pull request #11 from dapperlabs/bug-multi-sig
Fix multi-sig bug
2 parents 1744575 + 1ec7098 commit 61ce510

File tree

4 files changed

+63
-32
lines changed

4 files changed

+63
-32
lines changed

index.js

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,33 @@ module.exports = class DappAuth {
1515
ethUtil.toBuffer(challenge),
1616
);
1717

18-
// Get the address of whoever signed this message
19-
const { v, r, s } = ethUtil.fromRpcSig(signature);
20-
const recoveredKey = ethUtil.ecrecover(challengeHash, v, r, s);
21-
const recoveredAddress = ethUtil.publicToAddress(recoveredKey);
18+
let isAuthorizedDirectKey;
2219

2320
// try direct-keyed wallet
24-
if (
25-
address.toLowerCase() ===
26-
ethUtil.bufferToHex(recoveredAddress).toLowerCase()
27-
) {
28-
return true;
21+
try {
22+
// Get the address of whoever signed this message
23+
const { v, r, s } = ethUtil.fromRpcSig(signature);
24+
const recoveredKey = ethUtil.ecrecover(challengeHash, v, r, s);
25+
const recoveredAddress = ethUtil.publicToAddress(recoveredKey);
26+
27+
if (
28+
address.toLowerCase() ===
29+
ethUtil.bufferToHex(recoveredAddress).toLowerCase()
30+
) {
31+
isAuthorizedDirectKey = true;
32+
}
33+
} catch (e) {
34+
// if either direct-keyed auth flow threw an error, or it did not conclude to be authorized, proceed to try smart-contract wallet in finally clause.
35+
} finally {
36+
if (isAuthorizedDirectKey === true) return isAuthorizedDirectKey;
37+
// try smart-contract wallet
38+
const erc1271CoreContract = new this.web3.eth.Contract(ERC1271, address);
39+
40+
const magicValue = await erc1271CoreContract.methods
41+
.isValidSignature(ethUtil.keccak(challenge), signature) // we send just a regular hash, which then the smart contract hashes ontop to an erc191 hash
42+
.call();
43+
44+
return magicValue === ERC1271_MAGIC_VALUE;
2945
}
30-
31-
// try smart-contract wallet
32-
const erc1271CoreContract = new this.web3.eth.Contract(ERC1271, address);
33-
34-
const magicValue = await erc1271CoreContract.methods
35-
.isValidSignature(ethUtil.keccak(challenge), signature) // we send just a regular hash, which then the smart contract hashes ontop to an erc191 hash
36-
.call();
37-
38-
return magicValue === ERC1271_MAGIC_VALUE;
3946
}
4047
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dapperlabs/dappauth",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"description": "A util to prove actionable control ('ownership') over a public Ethereum address using eth_sign",
55
"keywords": [
66
"ethereum",

test/contract-mock.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,14 @@ module.exports = class MockContract {
4646
if (this.errorIsValidSignature) {
4747
throw new Error('isValidSignature call returned an error');
4848
}
49+
// split to 65 bytes (130 hex) chunks
50+
const multi_sigs = signature.toString('hex').match(/.{1,130}/g);
51+
52+
// TODO: is it first or last?
53+
const expected_authrorised_sig = multi_sigs[0];
4954

5055
// Get the address of whoever signed this message
51-
const { v, r, s } = ethUtil.fromRpcSig(signature);
56+
const { v, r, s } = ethUtil.fromRpcSig(`0x${expected_authrorised_sig}`);
5257
const erc191MessageHash = utils.erc191MessageHash(hash, this.address);
5358
const recoveredKey = ethUtil.ecrecover(erc191MessageHash, v, r, s);
5459
const recoveredAddress = ethUtil.publicToAddress(recoveredKey);

test/test.js

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('dappauth', function() {
1616
isEOA: true,
1717
challenge: 'foo',
1818
challengeSign: 'foo',
19-
signingKey: keyA,
19+
signingKeys: [keyA],
2020
authAddr: utils.keyToAddress(keyA),
2121
mockContract: {
2222
authorizedKey: null,
@@ -33,7 +33,7 @@ describe('dappauth', function() {
3333
isEOA: true,
3434
challenge: 'foo',
3535
challengeSign: 'bar',
36-
signingKey: keyA,
36+
signingKeys: [keyA],
3737
authAddr: utils.keyToAddress(keyA),
3838
mockContract: {
3939
authorizedKey: ethUtil.privateToPublic(keyC),
@@ -49,7 +49,7 @@ describe('dappauth', function() {
4949
isEOA: true,
5050
challenge: 'foo',
5151
challengeSign: 'foo',
52-
signingKey: keyA,
52+
signingKeys: [keyA],
5353
authAddr: utils.keyToAddress(keyB),
5454
mockContract: {
5555
authorizedKey: ethUtil.privateToPublic(keyC),
@@ -65,7 +65,7 @@ describe('dappauth', function() {
6565
isEOA: false,
6666
challenge: 'foo',
6767
challengeSign: 'foo',
68-
signingKey: keyB,
68+
signingKeys: [keyB],
6969
authAddr: utils.keyToAddress(keyA),
7070
mockContract: {
7171
authorizedKey: ethUtil.privateToPublic(keyB),
@@ -75,13 +75,30 @@ describe('dappauth', function() {
7575
expectedAuthorizedSignerError: false,
7676
expectedAuthorizedSigner: true,
7777
},
78+
{
79+
title:
80+
'Smart-contract wallets with a 1-of-2 (multi-sig) correct internal key should be authorized signers over their address',
81+
isEOA: false,
82+
challenge: 'foo',
83+
challengeSign: 'foo',
84+
signingKeys: [keyB, keyC],
85+
authAddr: utils.keyToAddress(keyA),
86+
mockContract: {
87+
authorizedKey: ethUtil.privateToPublic(keyB),
88+
address: utils.keyToAddress(keyA),
89+
errorIsValidSignature: false,
90+
},
91+
expectedAuthorizedSignerError: false,
92+
expectedAuthorizedSigner: true,
93+
},
94+
7895
{
7996
title:
8097
'Smart-contract wallets with a 1-of-1 incorrect internal key should NOT be authorized signers over their address',
8198
isEOA: false,
8299
challenge: 'foo',
83100
challengeSign: 'foo',
84-
signingKey: keyB,
101+
signingKeys: [keyB],
85102
authAddr: utils.keyToAddress(keyA),
86103
mockContract: {
87104
authorizedKey: ethUtil.privateToPublic(keyC),
@@ -96,7 +113,7 @@ describe('dappauth', function() {
96113
isEOA: false,
97114
challenge: 'foo',
98115
challengeSign: 'foo',
99-
signingKey: keyB,
116+
signingKeys: [keyB],
100117
authAddr: utils.keyToAddress(keyA),
101118
mockContract: {
102119
authorizedKey: ethUtil.privateToPublic(keyB),
@@ -118,18 +135,20 @@ describe('dappauth', function() {
118135
? utils.signEOAPersonalMessage
119136
: utils.signERC1654PersonalMessage;
120137

121-
const signature = signatureFunc(
122-
test.challengeSign,
123-
test.signingKey,
124-
test.authAddr,
125-
);
138+
const signatures = `0x${test.signingKeys
139+
.map((signingKey) =>
140+
ethUtil.stripHexPrefix(
141+
signatureFunc(test.challengeSign, signingKey, test.authAddr),
142+
),
143+
)
144+
.join('')}`;
126145

127146
let isError = false;
128147
let isAuthorizedSigner = false;
129148
try {
130149
isAuthorizedSigner = await dappAuth.isAuthorizedSigner(
131150
test.challenge,
132-
signature,
151+
signatures,
133152
test.authAddr,
134153
);
135154
} catch (error) {

0 commit comments

Comments
 (0)