Skip to content

Commit

Permalink
wallet: use smallest pubkey everywhere, breaking RPC/HTTP
Browse files Browse the repository at this point in the history
  • Loading branch information
rithvikvibhu committed Dec 17, 2022
1 parent 3a50b88 commit bf56b6a
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 39 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ to these calls.
- New RPC methods:
- `createbatch` and `sendbatch` create batch transactions with any number
of outputs with any combination of covenants.
- Updates related to nonces and blinds
- RPC method `importnonce` now returns an array of blinds instead of a single blind.
- HTTP endpoint `/wallet/:id/nonce/:name`'s response replaces 2 string fields (`nonce`, `blind`) with arrays of the same type (`nonces`, `blinds`)

## v4.0.0

Expand Down
7 changes: 3 additions & 4 deletions lib/wallet/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -1030,13 +1030,12 @@ class HTTP extends Server {

const nameHash = rules.hashName(name);
const nonces = await req.wallet.generateNonces(nameHash, address, bid);
const nonce = nonces[0];
const blind = rules.blind(bid, nonce);
const blinds = nonces.map(nonce => rules.blind(bid, nonce));

return res.json(200, {
address: address.toString(this.network),
blind: blind.toString('hex'),
nonce: nonce.toString('hex'),
blinds: blinds.map(blind => blind.toString('hex')),
nonces: nonces.map(nonce => nonce.toString('hex')),
bid: bid,
name: name,
nameHash: nameHash.toString('hex')
Expand Down
2 changes: 1 addition & 1 deletion lib/wallet/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2669,7 +2669,7 @@ class RPC extends RPCBase {

// Return only first blind (based on own key)
// to stay backward-compatible
return blinds[0].toString('hex');
return blinds.map(blind => blind.toString('hex'));
}
}

Expand Down
8 changes: 3 additions & 5 deletions lib/wallet/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,9 @@ class Wallet extends EventEmitter {
for (const accountKey of [account.accountKey, ...account.keys])
publicKeys.push(accountKey.derive(index).publicKey);

// Use smallest public key
publicKeys.sort(Buffer.compare);

return publicKeys;
}

Expand All @@ -1232,10 +1235,6 @@ class Wallet extends EventEmitter {

async generateNonce(nameHash, address, value) {
const publicKeys = await this._getNoncePublicKeys(address, value);

// Use smallest public key
publicKeys.sort(Buffer.compare);

return blake2b.multi(address.hash, publicKeys[0], nameHash);
}

Expand All @@ -1252,7 +1251,6 @@ class Wallet extends EventEmitter {
const publicKeys = await this._getNoncePublicKeys(address, value);

// Generate nonces for all public keys
// Nonce based on own public key is always first
const nonces = [];
for (const publicKey of publicKeys)
nonces.push(blake2b.multi(address.hash, publicKey, nameHash));
Expand Down
4 changes: 2 additions & 2 deletions test/auction-rpc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,11 +336,11 @@ describe('Auction RPCs', function() {
// that same value. Note that "loser" MUST remember their original
// bid value. If this were a wallet recovery scenario, that value
// would have to be entered by the user without data from the blockchain.
const importedBlind = await util.wrpc(
const importedBlinds = await util.wrpc(
'importnonce',
[bidName, bidAddress, loserBid.bid]
);
assert.strictEqual(importedBlind, bidBlind);
assert.strictEqual(importedBlinds[0], bidBlind);
});

it('should create REVEAL with signing paths', async () => {
Expand Down
16 changes: 8 additions & 8 deletions test/wallet-http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,13 +469,13 @@ 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 blind = rules.blind(bid, nonce);
const nonces = await primary.generateNonces(nameHash, address, bid);
const blinds = nonces.map(nonce => rules.blind(bid, nonce));

assert.deepStrictEqual(response, {
address: address.toString(network.type),
blind: blind.toString('hex'),
nonce: nonce.toString('hex'),
blinds: blinds.map(blind => blind.toString('hex')),
nonces: nonces.map(nonce => nonce.toString('hex')),
bid: bid,
name: name,
nameHash: nameHash.toString('hex')
Expand All @@ -494,13 +494,13 @@ 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 blind = rules.blind(bid, nonce);
const nonces = await primary.generateNonces(nameHash, address, bid);
const blinds = nonces.map(nonce => rules.blind(bid, nonce));

assert.deepStrictEqual(response, {
address: address.toString(network.type),
blind: blind.toString('hex'),
nonce: nonce.toString('hex'),
blinds: blinds.map(blind => blind.toString('hex')),
nonces: nonces.map(nonce => nonce.toString('hex')),
bid: bid,
name: name,
nameHash: nameHash.toString('hex')
Expand Down
115 changes: 115 additions & 0 deletions test/wallet-rpc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -952,4 +952,119 @@ describe('Wallet RPC Methods', function() {
await nclient.execute('generatetoaddress', [1, addr]);
});
});

describe('Multisig Auction RPC', function() {
// wallet clients
let alice, bob;

// auction
let name;
const bidValue = 5, blindValue = 5;

before(async () => {
await wclient.createWallet('msAlice', {
type: 'multisig',
m: 2,
n: 2
});
await wclient.createWallet('msBob', {
type: 'multisig',
m: 2,
n: 2
});

alice = wclient.wallet('msAlice');
bob = wclient.wallet('msBob');

// Initialize both multisig wallets
const accountKeys = {
alice: (await alice.getAccount('default')).accountKey,
bob: (await bob.getAccount('default')).accountKey
};
await alice.addSharedKey('default', accountKeys.bob);
await bob.addSharedKey('default', accountKeys.alice);

// Fund Alice's wallet
await wclient.execute('selectwallet', ['msAlice']);
const addrAlice = await wclient.execute('getnewaddress', []);
await nclient.execute('generatetoaddress', [50, addrAlice]);

// Fund Bob's wallet
await wclient.execute('selectwallet', ['msBob']);
const addrBob = await wclient.execute('getnewaddress', []);
await nclient.execute('generatetoaddress', [50, addrBob]);
});

it('(alice) should open name for auction', async () => {
await wclient.execute('selectwallet', ['msAlice']);

// Create, sign, send OPEN
name = await nclient.execute('grindname', [5]);
const tx = await wclient.execute('createopen', [name]);
const txSigned = await signMultisigTx(tx, [alice, bob]);
await nclient.execute('sendrawtransaction', [txSigned.hex]);

// confirm and advance to bidding phase
const addrAlice = await wclient.execute('getnewaddress', []);
await nclient.execute('generatetoaddress', [treeInterval + 1, addrAlice]);
});

it('(alice) should bid on name with blind', async () => {
await wclient.execute('selectwallet', ['msAlice']);

// Create, sign, send BID
const tx = await wclient.execute('createbid', [name, bidValue, bidValue+blindValue]);
const txSigned = await signMultisigTx(tx, [alice, bob]);
await nclient.execute('sendrawtransaction', [txSigned.hex]);

// confirm and advance to reveal phase
const addrAlice = await wclient.execute('getnewaddress', []);
await nclient.execute('generatetoaddress', [biddingPeriod + 1, addrAlice]);
});

it('(bob) should not be able to reveal bid', async () => {
// Alice can create reveal
await wclient.execute('selectwallet', ['msAlice']);
assert.doesNotReject(wclient.execute('createreveal', [name]));

// Bob cannot.
await wclient.execute('selectwallet', ['msBob']);
assert.rejects(wclient.execute('createreveal', [name]));
});

it('(bob) should import nonce', async () => {
await wclient.execute('selectwallet', ['msBob']);
const bidsBob = await wclient.execute('getbids', [name, true, true]);
const address = bidsBob[0].address;
const blinds = await wclient.execute('importnonce', [name, address, 5]);
assert.strictEqual(blinds[0], bidsBob[0].blind);
});

it('(bob) should reveal bid', async () => {
await wclient.execute('selectwallet', ['msBob']);

// Create, sign, send REVEAL
const tx = await wclient.execute('createreveal', [name]);
const txSigned = await signMultisigTx(tx, [alice, bob]);
await nclient.execute('sendrawtransaction', [txSigned.hex]);

// confirm and advance to close auction
const addrAlice = await wclient.execute('getnewaddress', []);
await nclient.execute('generatetoaddress', [revealPeriod + 1, addrAlice]);

// Ensure name is owned
const ownedNames = await wclient.execute('getnames', [true]);
assert.strictEqual(ownedNames.length, 1);
});
});
});

async function signMultisigTx(tx, walletClients) {
assert(tx.hex, 'tx must be a json object with `hex`');
assert(walletClients.length);

for (const wclient of walletClients)
tx = await wclient.sign({tx: tx.hex});

return tx;
}
33 changes: 14 additions & 19 deletions test/wallet-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3370,44 +3370,39 @@ describe('Wallet', function() {
const aliceNonces = await alice.generateNonces(nameHash, addr, value);
const bobNonces = await bob.generateNonces(nameHash, addr, value);

// Both alice and bob get N nonces
// Both alice and bob get the same N nonces
assert.deepStrictEqual(aliceNonces, bobNonces);
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 saved as of this point
assert(!await bob.txdb.hasBlind(expectedBlinds.alice));
assert(!await bob.txdb.hasBlind(expectedBlinds.bob));
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.alice), false);
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.bob), false);

// 1) Generate single blind:
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));
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.alice), true);
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.bob), false);

// 2) Generate all blinds:
const bobBlinds = await bob.generateBlinds(nameHash, addr, value);
// own public key is used for blind (backwards-compatible)
assert.bufferEqual(bobBlinds[0], 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));
// smallest public key (alice's) is used for blind
assert.deepStrictEqual(
bobBlinds,
[expectedBlinds.alice, expectedBlinds.bob]
);
// but blinds for all keys are saved in db
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.alice), true);
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.bob), true);
});
});
});

0 comments on commit bf56b6a

Please sign in to comment.