From 44ec2c768431341d6283718fe9a168dfc6621442 Mon Sep 17 00:00:00 2001 From: Xuejie Xiao Date: Thu, 7 Nov 2019 12:42:38 +0000 Subject: [PATCH 1/5] multi script group witness signing logic --- .../neuron-wallet/src/services/wallets.ts | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index fe7563a6cc..e2b70542e6 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -371,18 +371,39 @@ export default class WalletService { const paths = addressInfos.map(info => info.path) const pathAndPrivateKeys = this.getPrivateKeys(wallet, paths, password) - const witnesses: string[] = inputs!.map((input: Input) => { - const blake160: string = input.lock!.args! - const info = addressInfos.find(i => i.blake160 === blake160) - const { path } = info! - const pathAndPrivateKey = pathAndPrivateKeys.find(p => p.path === path) - if (!pathAndPrivateKey) { - throw new Error('no private key found') + const witnessesWithLockHashes = inputs!.map((input: Input) => { + return { + // TODO: fill in required DAO's type witness here + witness: { + lock: undefined, + inputType: undefined, + outputType: undefined + }, + lockHash: input.lockHash! + }; + }); + + const lockHashes = new Set(witnessesWithLockHashes.map(w => w.lockHash)); + + for (let lockHash of lockHashes) { + const firstIndex = witnessesWithLockHashes.findIndex(w => w.lockHash == lockHash); + const witnesses = witnessesWithLockHashes.filter(w => w.lockHash == lockHash).map(w => w.witness); + // A 65-byte empty signature used as placeholder + witnesses[0].lock = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + const signedWitness = core.signWitness(privateKey)({ + transactionHash: txHash, + witnesses: witnesses + })[0] as string; + + for (let w of witnessesWithLockHashes) { + if (w.lockHash == lockHash) { + w.witness = "0x" + } } - const { privateKey } = pathAndPrivateKey - const witness = this.signWitness('', privateKey, txHash) - return witness - }) + witnessesWithLockHashes[firstIndex].witness = signedWitness; + } + + const witnesses: string[] = witnessesWithLockHashes.map(w => w.witness); tx.witnesses = witnesses From 34c95dbacfc7274ab256f90af24efed10634071d Mon Sep 17 00:00:00 2001 From: classicalliu Date: Thu, 7 Nov 2019 22:10:03 +0800 Subject: [PATCH 2/5] chore: consummate sign input group --- .../neuron-wallet/src/services/wallets.ts | 64 ++++++++++++------- .../neuron-wallet/src/types/cell-types.ts | 6 ++ 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index e2b70542e6..9db8157cc2 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -4,7 +4,7 @@ import { AccountExtendedPublicKey, PathAndPrivateKey } from 'models/keys/key' import Keystore from 'models/keys/keystore' import Store from 'models/store' import LockUtils from 'models/lock-utils' -import { TransactionWithoutHash, Input } from 'types/cell-types' +import { TransactionWithoutHash, Input, WitnessArgs } from 'types/cell-types' import ConvertTo from 'types/convert-to' import { WalletNotFound, IsRequired, UsedName } from 'exceptions' import { Address as AddressInterface } from 'database/address/dao' @@ -372,38 +372,54 @@ export default class WalletService { const pathAndPrivateKeys = this.getPrivateKeys(wallet, paths, password) const witnessesWithLockHashes = inputs!.map((input: Input) => { + const blake160: string = input.lock!.args! + const witnessArgs: WitnessArgs = { + lock: undefined, + inputType: undefined, + outputType: undefined + } return { // TODO: fill in required DAO's type witness here - witness: { - lock: undefined, - inputType: undefined, - outputType: undefined - }, - lockHash: input.lockHash! - }; - }); - - const lockHashes = new Set(witnessesWithLockHashes.map(w => w.lockHash)); - - for (let lockHash of lockHashes) { - const firstIndex = witnessesWithLockHashes.findIndex(w => w.lockHash == lockHash); - const witnesses = witnessesWithLockHashes.filter(w => w.lockHash == lockHash).map(w => w.witness); + witnessArgs, + lockHash: input.lockHash!, + witness: '', + blake160, + } + }) + + const lockHashes = new Set(witnessesWithLockHashes.map(w => w.lockHash)) + + for (const lockHash of lockHashes) { + const firstIndex = witnessesWithLockHashes.findIndex(w => w.lockHash === lockHash) + const witnessesArgsWithBlake160 = witnessesWithLockHashes + .filter(w => w.lockHash === lockHash) + .map(w => ({args: w.witnessArgs, blake160: w.blake160})) // A 65-byte empty signature used as placeholder - witnesses[0].lock = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - const signedWitness = core.signWitness(privateKey)({ + witnessesArgsWithBlake160[0].args.lock = '0x' + '0'.repeat(130) + + const blake160: string = witnessesArgsWithBlake160[0].blake160 + const info = addressInfos.find(i => i.blake160 === blake160) + const { path } = info! + const pathAndPrivateKey = pathAndPrivateKeys.find(p => p.path === path) + if (!pathAndPrivateKey) { + throw new Error('no private key found') + } + const { privateKey } = pathAndPrivateKey + + const signedWitness = core.signWitnesses(privateKey)({ transactionHash: txHash, - witnesses: witnesses - })[0] as string; + witnesses: witnessesArgsWithBlake160.map(w => w.args) + })[0] as string - for (let w of witnessesWithLockHashes) { - if (w.lockHash == lockHash) { - w.witness = "0x" + for (const w of witnessesWithLockHashes) { + if (w.lockHash === lockHash) { + w.witness = '0x' } } - witnessesWithLockHashes[firstIndex].witness = signedWitness; + witnessesWithLockHashes[firstIndex].witness = signedWitness } - const witnesses: string[] = witnessesWithLockHashes.map(w => w.witness); + const witnesses: string[] = witnessesWithLockHashes.map(w => w.witness) tx.witnesses = witnesses diff --git a/packages/neuron-wallet/src/types/cell-types.ts b/packages/neuron-wallet/src/types/cell-types.ts index 3dce8003fc..43cf664568 100644 --- a/packages/neuron-wallet/src/types/cell-types.ts +++ b/packages/neuron-wallet/src/types/cell-types.ts @@ -57,6 +57,12 @@ export interface Transaction extends TransactionWithoutHash { hash: string } +export interface WitnessArgs { + lock: string | undefined + inputType: string | undefined + outputType: string | undefined +} + export interface TxStatus { blockHash: string | null status: 'pending' | 'proposed' | 'committed' From e2fd4106e8c7b6b03ceaf456b869e514f37aebb9 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 8 Nov 2019 09:39:38 +0900 Subject: [PATCH 3/5] refactor: Remove some local vars, extract getting private key out of the input group loop --- .../neuron-wallet/src/services/wallets.ts | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 9db8157cc2..0e00c5270e 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -344,13 +344,8 @@ export default class WalletService { return this.sendTx(walletID, tx, password, description) } - public sendTx = async ( - walletID: string = '', - tx: TransactionWithoutHash, - password: string = '', - description: string = '' - ) => { - const wallet = await this.get(walletID) + public sendTx = async (walletID: string = '', tx: TransactionWithoutHash, password: string = '', description: string = '') => { + const wallet = this.get(walletID) if (!wallet) { throw new WalletNotFound(walletID) } @@ -359,19 +354,24 @@ export default class WalletService { throw new IsRequired('Password') } - const addressInfos = await this.getAddressInfos(walletID) - let txHash: string = core.utils.rawTransactionToHash(ConvertTo.toSdkTxWithoutHash(tx)) if (!txHash.startsWith('0x')) { txHash = `0x${txHash}` } - const { inputs } = tx - + const addressInfos = await this.getAddressInfos(walletID) const paths = addressInfos.map(info => info.path) const pathAndPrivateKeys = this.getPrivateKeys(wallet, paths, password) + const findPrivateKey = (blake160: string) => { + const { path } = addressInfos.find(i => i.blake160 === blake160)! + const pathAndPrivateKey = pathAndPrivateKeys.find(p => p.path === path) + if (!pathAndPrivateKey) { + throw new Error('no private key found') + } + return pathAndPrivateKey.privateKey + } - const witnessesWithLockHashes = inputs!.map((input: Input) => { + const witnessesWithLockHashes = tx.inputs!.map((input: Input) => { const blake160: string = input.lock!.args! const witnessArgs: WitnessArgs = { lock: undefined, @@ -397,15 +397,7 @@ export default class WalletService { // A 65-byte empty signature used as placeholder witnessesArgsWithBlake160[0].args.lock = '0x' + '0'.repeat(130) - const blake160: string = witnessesArgsWithBlake160[0].blake160 - const info = addressInfos.find(i => i.blake160 === blake160) - const { path } = info! - const pathAndPrivateKey = pathAndPrivateKeys.find(p => p.path === path) - if (!pathAndPrivateKey) { - throw new Error('no private key found') - } - const { privateKey } = pathAndPrivateKey - + const privateKey = findPrivateKey(witnessesArgsWithBlake160[0].blake160) const signedWitness = core.signWitnesses(privateKey)({ transactionHash: txHash, witnesses: witnessesArgsWithBlake160.map(w => w.args) @@ -419,9 +411,7 @@ export default class WalletService { witnessesWithLockHashes[firstIndex].witness = signedWitness } - const witnesses: string[] = witnessesWithLockHashes.map(w => w.witness) - - tx.witnesses = witnesses + tx.witnesses = witnessesWithLockHashes.map(w => w.witness) const txToSend = ConvertTo.toSdkTxWithoutHash(tx) await core.rpc.sendTransaction(txToSend) @@ -440,9 +430,7 @@ export default class WalletService { return txHash } - public calculateFee = async ( - tx: TransactionWithoutHash - ) => { + public calculateFee = async (tx: TransactionWithoutHash) => { const inputCapacities = tx.inputs! .map(input => BigInt(input.capacity!)) .reduce((result, c) => result + c, BigInt(0)) @@ -511,12 +499,11 @@ export default class WalletService { // path is a BIP44 full path such as "m/44'/309'/0'/0/0" public getAddressInfos = async (walletID: string): Promise => { - const wallet = await this.get(walletID) + const wallet = this.get(walletID) if (!wallet) { throw new WalletNotFound(walletID) } - const addrs = await AddressService.allAddressesByWalletId(walletID) - return addrs + return AddressService.allAddressesByWalletId(walletID) } public getChangeAddress = async (): Promise => { @@ -537,7 +524,7 @@ export default class WalletService { })[0] as string } - // Derivate all child private keys for specified BIP44 paths. + // Derive all child private keys for specified BIP44 paths. public getPrivateKeys = (wallet: Wallet, paths: string[], password: string): PathAndPrivateKey[] => { const masterPrivateKey = wallet.loadKeystore().extendedPrivateKey(password) const masterKeychain = new Keychain( From 0258f57d927d775dd3fe47348139881e0dbeba3e Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 8 Nov 2019 09:56:11 +0900 Subject: [PATCH 4/5] refactor: No need to prefix 0x as rawTransactionToHash returns txhash with that already --- packages/neuron-wallet/src/services/wallets.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 0e00c5270e..3cbc6b6a1d 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -354,10 +354,7 @@ export default class WalletService { throw new IsRequired('Password') } - let txHash: string = core.utils.rawTransactionToHash(ConvertTo.toSdkTxWithoutHash(tx)) - if (!txHash.startsWith('0x')) { - txHash = `0x${txHash}` - } + const txHash = core.utils.rawTransactionToHash(ConvertTo.toSdkTxWithoutHash(tx)) const addressInfos = await this.getAddressInfos(walletID) const paths = addressInfos.map(info => info.path) From 4da38dea84c41720599921edaaa4e75844fab590 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 8 Nov 2019 10:11:59 +0900 Subject: [PATCH 5/5] refactor: Prefer shorter variable names --- .../neuron-wallet/src/services/wallets.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 3cbc6b6a1d..971f7a3fcd 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -368,7 +368,7 @@ export default class WalletService { return pathAndPrivateKey.privateKey } - const witnessesWithLockHashes = tx.inputs!.map((input: Input) => { + const witnessSigningEntries = tx.inputs!.map((input: Input) => { const blake160: string = input.lock!.args! const witnessArgs: WitnessArgs = { lock: undefined, @@ -384,31 +384,29 @@ export default class WalletService { } }) - const lockHashes = new Set(witnessesWithLockHashes.map(w => w.lockHash)) + const lockHashes = new Set(witnessSigningEntries.map(w => w.lockHash)) for (const lockHash of lockHashes) { - const firstIndex = witnessesWithLockHashes.findIndex(w => w.lockHash === lockHash) - const witnessesArgsWithBlake160 = witnessesWithLockHashes - .filter(w => w.lockHash === lockHash) - .map(w => ({args: w.witnessArgs, blake160: w.blake160})) + const firstIndex = witnessSigningEntries.findIndex(w => w.lockHash === lockHash) + const witnessesArgs = witnessSigningEntries.filter(w => w.lockHash === lockHash) // A 65-byte empty signature used as placeholder - witnessesArgsWithBlake160[0].args.lock = '0x' + '0'.repeat(130) + witnessesArgs[0].witnessArgs.lock = '0x' + '0'.repeat(130) - const privateKey = findPrivateKey(witnessesArgsWithBlake160[0].blake160) + const privateKey = findPrivateKey(witnessesArgs[0].blake160) const signedWitness = core.signWitnesses(privateKey)({ transactionHash: txHash, - witnesses: witnessesArgsWithBlake160.map(w => w.args) + witnesses: witnessesArgs.map(w => w.witnessArgs) })[0] as string - for (const w of witnessesWithLockHashes) { + for (const w of witnessSigningEntries) { if (w.lockHash === lockHash) { w.witness = '0x' } } - witnessesWithLockHashes[firstIndex].witness = signedWitness + witnessSigningEntries[firstIndex].witness = signedWitness } - tx.witnesses = witnessesWithLockHashes.map(w => w.witness) + tx.witnesses = witnessSigningEntries.map(w => w.witness) const txToSend = ConvertTo.toSdkTxWithoutHash(tx) await core.rpc.sendTransaction(txToSend)