Skip to content

Commit

Permalink
wallet: derive all keys for generateNonce if importnonce api
Browse files Browse the repository at this point in the history
  • Loading branch information
rithvikvibhu committed Nov 16, 2022
1 parent 1064f61 commit efda192
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 27 deletions.
8 changes: 7 additions & 1 deletion lib/wallet/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,13 @@ class HTTP extends Server {
}

const nameHash = rules.hashName(name);
const nonce = await req.wallet.generateNonce(nameHash, address, bid);
const nonces = await req.wallet.generateNonce(
nameHash,
address,
bid,
true
);
const nonce = nonces[0];
const blind = rules.blind(bid, nonce);

return res.json(200, {
Expand Down
2 changes: 1 addition & 1 deletion lib/wallet/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2665,7 +2665,7 @@ class RPC extends RPCBase {
const nameHash = rules.hashName(name);
const address = parseAddress(addr, this.network);

const blind = await wallet.generateBlind(nameHash, address, value);
const blind = await wallet.generateBlind(nameHash, address, value, true);

return blind.toString('hex');
}
Expand Down
49 changes: 41 additions & 8 deletions lib/wallet/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -1197,10 +1197,11 @@ class Wallet extends EventEmitter {
* @param {Buffer} nameHash
* @param {Address} address
* @param {Amount} value
* @returns {Buffer}
* @param {boolean} forAllKeys
* @returns {Buffer|Buffer[]}
*/

async generateNonce(nameHash, address, value) {
async generateNonce(nameHash, address, value, forAllKeys) {
const path = await this.getPath(address.hash);

if (!path)
Expand All @@ -1219,8 +1220,20 @@ class Wallet extends EventEmitter {
for (const accountKey of [account.accountKey, ...account.keys]) {
publicKeys.push(accountKey.derive(index).publicKey);
}
publicKeys.sort(Buffer.compare);

// In case of multisig importnonce, we
// generate nonces for all N public keys
// with the first being own key
if (forAllKeys) {
const nonces = [];
for (const publicKey of publicKeys)
nonces.push(blake2b.multi(address.hash, publicKey, nameHash));

return nonces;
}

// Use smallest public key by default
publicKeys.sort(Buffer.compare);
return blake2b.multi(address.hash, publicKeys[0], nameHash);
}

Expand All @@ -1229,16 +1242,36 @@ class Wallet extends EventEmitter {
* @param {Buffer} nameHash
* @param {Address} address
* @param {Amount} value
* @param {boolean} forAllKeys
* @returns {Buffer}
*/

async generateBlind(nameHash, address, value) {
const nonce = await this.generateNonce(nameHash, address, value);
const blind = rules.blind(value, nonce);
async generateBlind(nameHash, address, value, forAllKeys) {
let nonces = await this.generateNonce(
nameHash,
address,
value,
forAllKeys
);

await this.txdb.saveBlind(blind, {value, nonce});
// Coerce into array if only once nonce was generated
if (!Array.isArray(nonces))
nonces = [nonces];

// Return the first blind to preserve backward compatibility.
// It is either based on
// - smallest key (forAllKeys=false), or
// - on own key (forAllKeys=true)
let firstBlind;
for (const nonce of nonces) {
const blind = rules.blind(value, nonce);
await this.txdb.saveBlind(blind, {value, nonce});

if (!firstBlind)
firstBlind = blind;
}

return blind;
return firstBlind;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions test/wallet-http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,8 @@ describe('Wallet HTTP', function() {
const nameHash = rules.hashName(name);

const primary = node.plugins.walletdb.wdb.primary;
const nonce = await primary.generateNonce(nameHash, address, bid);
const nonces = await primary.generateNonce(nameHash, address, bid, true);
const nonce = nonces[0];
const blind = rules.blind(bid, nonce);

assert.deepStrictEqual(response, {
Expand All @@ -494,7 +495,8 @@ describe('Wallet HTTP', function() {
const nameHash = rules.hashName(name);

const primary = node.plugins.walletdb.wdb.primary;
const nonce = await primary.generateNonce(nameHash, address, bid);
const nonces = await primary.generateNonce(nameHash, address, bid, true);
const nonce = nonces[0];
const blind = rules.blind(bid, nonce);

assert.deepStrictEqual(response, {
Expand Down
93 changes: 78 additions & 15 deletions test/wallet-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3274,7 +3274,7 @@ describe('Wallet', function() {
});
});

describe('Wallet nonce generation', function () {
describe('Nonce and blind generation', function () {
const name = 'random-name';
const nameHash = rules.hashString(name);
const value = 1e6;
Expand All @@ -3291,20 +3291,26 @@ describe('Wallet', function() {
const wallet = await wdb.create({
master: KEY1
});

const expectedNonce = Buffer.from(
'63d757a0e55b99db90b47fbfae81e3779801ec4d390c021ee30eafb6108e08ad',
'hex'
);

const addr = await wallet.receiveAddress();
assert.strictEqual(
addr.toString(network),
'hs1qyz8n88g6v5033r6fyj4jxkz29mqk5dgkdv7md7'
);

// Normal generate nonce
const nonce = await wallet.generateNonce(nameHash, addr, value);
assert.bufferEqual(
nonce,
Buffer.from(
'63d757a0e55b99db90b47fbfae81e3779801ec4d390c021ee30eafb6108e08ad',
'hex'
)
);
assert.bufferEqual(nonce, expectedNonce);

// Generate nonce from RPC/HTTP importnonce
const nonces = await wallet.generateNonce(nameHash, addr, value, true);
assert.strictEqual(nonces.length, 1);
assert.bufferEqual(nonces[0], expectedNonce);
});

it('should generate nonce (multisig accounts)', async () => {
Expand All @@ -3321,6 +3327,22 @@ describe('Wallet', function() {
n: 2
});

// Based on alice's and bob's public keys
const expectedNonces = {
alice: Buffer.from(
'3c6fcdeca8a5c4cd76cb84af2fbc47737f6a0dc1d135a9aefb20786944ea081a',
'hex'
),
bob: Buffer.from(
'097eca5a6d1a9ade3ef20ba3feab60b2dd1858f1b5888fb736917ec6ef0567e9',
'hex'
)
};
const expectedBlinds = {
alice: rules.blind(value, expectedNonces.alice),
bob: rules.blind(value, expectedNonces.bob)
};

// Initialize both multisig wallets
await alice.addSharedKey(0, await bob.accountKey(0));
await bob.addSharedKey(0, await alice.accountKey(0));
Expand All @@ -3332,19 +3354,60 @@ describe('Wallet', function() {
'hs1q8nnrenk92qdmm32zfv56shvhj6n6g8kq3ermpz50k5zxh4qjq34qk7v4d2'
);

// Generate Nonce
// --------------

// Generate nonce for same inputs from both wallets
const aliceNonce = await alice.generateNonce(nameHash, addr, value);
const bobNonce = await bob.generateNonce(nameHash, addr, value);

// Same nonce is generated by alice and bob
assert.bufferEqual(aliceNonce, bobNonce);
assert.bufferEqual(
aliceNonce,
Buffer.from(
'3c6fcdeca8a5c4cd76cb84af2fbc47737f6a0dc1d135a9aefb20786944ea081a',
'hex'
)
// and is based on the smallest public key (alice's)
assert.bufferEqual(aliceNonce, expectedNonces.alice);
assert.bufferEqual(bobNonce, expectedNonces.alice);

// Generate nonces for all multisig participants
const aliceNonces = await alice.generateNonce(nameHash, addr, value, true);
const bobNonces = await bob.generateNonce(nameHash, addr, value, true);

// Both alice and bob get N nonces
assert.strictEqual(aliceNonces.length, 2);
assert.strictEqual(bobNonces.length, 2);

// and are the same, but ordered differently.
// First nonce is always based on own key,
// Then the rest in order of public key.
assert.deepStrictEqual(
aliceNonces,
[expectedNonces.alice, expectedNonces.bob]
);
assert.deepStrictEqual(
bobNonces,
[expectedNonces.bob, expectedNonces.alice]
);

// Generate Blind
// --------------

// sanity check: no blinds as saved as of this point
assert(!await bob.txdb.hasBlind(expectedBlinds.alice));
assert(!await bob.txdb.hasBlind(expectedBlinds.bob));

// 1) Normal behavior:
const bobBlind = await bob.generateBlind(nameHash, addr, value);
// smallest public key (alice's) is used for blind
assert.bufferEqual(bobBlind, expectedBlinds.alice);
// bob's public key blind isn't stored
assert(await bob.txdb.hasBlind(expectedBlinds.alice));
assert(!await bob.txdb.hasBlind(expectedBlinds.bob));

// 2) ImportNonce (RPC/HTTP) behavior:
const bobBlindAll = await bob.generateBlind(nameHash, addr, value, true);
// own public key is used for blind
assert.bufferEqual(bobBlindAll, expectedBlinds.bob);
// but blinds for all key are saved in db
assert(await bob.txdb.hasBlind(expectedBlinds.alice));
assert(await bob.txdb.hasBlind(expectedBlinds.bob));
});
});
});

0 comments on commit efda192

Please sign in to comment.