Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ language: node_js
node_js:
- 12
- lts/*
- node
# - node
services:
- docker
env:
Expand Down
2 changes: 1 addition & 1 deletion cmds/enrollRewardProgram-testnet.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash
set -e
symbol-bootstrap enrollRewardProgram -t target/testnet-supernode --useKnownRestGateways --password 1111 $1 $2 $3
symbol-bootstrap enrollRewardProgram -t target/testnet-supernode --maxFee 1000000 --useKnownRestGateways --password 1111 $1 $2 $3
3 changes: 3 additions & 0 deletions docs/enrollRewardProgram.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ OPTIONS

-u, --url=url [default: http://localhost:3000] the network url

--ledger If --ledger is provided, the command will connect and sign the transactions using a
Ledger device.

--maxFee=maxFee the max fee used when announcing (absolute). The node min multiplier will be used if
it is not provided.

Expand Down
2 changes: 1 addition & 1 deletion docs/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ OPTIONS
--all see all commands in CLI
```

_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.1.0/src/commands/help.ts)_
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.2/src/commands/help.ts)_
3 changes: 3 additions & 0 deletions docs/link.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ OPTIONS

-u, --url=url [default: http://localhost:3000] the network url

--ledger If --ledger is provided, the command will connect and sign the transactions using a
Ledger device.

--maxFee=maxFee the max fee used when announcing (absolute). The node min multiplier will be used if
it is not provided.

Expand Down
2,241 changes: 1,164 additions & 1,077 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
},
"bugs": "https://github.com/nemtech/symbol-bootstrap/issues",
"dependencies": {
"@ledgerhq/hw-transport": "^5.51.1",
"@ledgerhq/hw-transport-node-hid": "^5.51.1",
"@oclif/command": "^1.7.0",
"@oclif/config": "^1.16.0",
"@oclif/plugin-autocomplete": "^0.3.0",
Expand All @@ -23,6 +25,7 @@
"semver": "^7.3.5",
"shx": "^0.3.2",
"sshpk": "^1.16.1",
"symbol-ledger-typescript": "0.0.1-alpha-202106081510",
"symbol-sdk": "^1.0.1",
"tslib": "^1.13.0",
"utf8": "^2.1.2",
Expand All @@ -36,6 +39,8 @@
"@types/handlebars": "^4.1.0",
"@types/inquirer": "^7.3.1",
"@types/js-yaml": "^3.12.5",
"@types/ledgerhq__hw-transport": "^4.21.3",
"@types/ledgerhq__hw-transport-node-hid": "^4.22.2",
"@types/lodash": "^4.14.165",
"@types/memorystream": "^0.3.0",
"@types/mocha": "^8.2.2",
Expand Down
665 changes: 385 additions & 280 deletions src/service/AnnounceService.ts

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions src/service/LinkService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type LinkParams = {
unlink: boolean;
useKnownRestGateways: boolean;
ready?: boolean;
ledger?: boolean;
customPreset?: string;
};

Expand Down Expand Up @@ -81,15 +82,12 @@ export class LinkService implements TransactionFactory {
const customPreset = this.configLoader.loadCustomPreset(this.params.customPreset, this.params.password);
logger.info(`${this.params.unlink ? 'Unlinking' : 'Linking'} nodes`);

await new AnnounceService().announce(
this.params.url,
this.params.maxFee,
this.params.useKnownRestGateways,
this.params.ready,
this.configLoader.mergePresets(presetData, customPreset),
await new AnnounceService().announce({
...this.params,
addresses,
this,
);
presetData: this.configLoader.mergePresets(presetData, customPreset),
transactionFactory: this,
});
}

async createTransactions({
Expand Down
16 changes: 7 additions & 9 deletions src/service/RewardProgramService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type RewardProgramParams = {
maxFee?: number;
useKnownRestGateways: boolean;
ready?: boolean;
ledger?: boolean;
customPreset?: string;
};

Expand Down Expand Up @@ -80,16 +81,13 @@ export class RewardProgramService implements TransactionFactory {
logger.warn('This network does not have a reward program controller public key. Nodes cannot be registered.');
return;
}
await new AnnounceService().announce(
this.params.url,
this.params.maxFee,
this.params.useKnownRestGateways,
this.params.ready,
this.configLoader.mergePresets(presetData, customPreset),
await new AnnounceService().announce({
...this.params,
addresses,
this,
'1M+',
);
presetData: this.configLoader.mergePresets(presetData, customPreset),
transactionFactory: this,
tokenAmount: '1M+',
});
}

async createTransactions({
Expand Down
127 changes: 127 additions & 0 deletions src/service/SigningUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2021 NEM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LedgerNetworkType, SymbolLedger } from 'symbol-ledger-typescript/lib';
import { Account, Address, AggregateTransaction, Convert, KeyPair, NetworkType, SignedTransaction, Transaction, UInt64 } from 'symbol-sdk';
import { LogType } from '../logger';
import Logger from '../logger/Logger';
import LoggerFactory from '../logger/LoggerFactory';

const logger: Logger = LoggerFactory.getLogger(LogType.System);

/**
* An account that knows how to asynchronously sign and co-sign transactions
*/
export interface SigningAccount {
readonly publicKey: string;
readonly address: Address;
cosignTransaction(transaction: Transaction, transactionHash: string): Promise<string>;
signTransaction(transaction: Transaction, generationHash: string): Promise<SignedTransaction>;
}

/**
* Basic signing account that adapts the sdk's account.
*/
export class PrivateKeyAccount implements SigningAccount {
public readonly address: Address;
public readonly publicKey: string;
constructor(private readonly account: Account) {
this.address = account.address;
this.publicKey = account.publicAccount.publicKey;
}

async cosignTransaction(transaction: Transaction, transactionHash: string): Promise<string> {
const keyPairEncoded = KeyPair.createKeyPairFromPrivateKeyString(this.account.privateKey);
return Convert.uint8ToHex(KeyPair.sign(keyPairEncoded, Convert.hexToUint8(transactionHash)));
}

async signTransaction(transaction: Transaction, generationHash: string): Promise<SignedTransaction> {
return this.account.sign(transaction, generationHash);
}
}

/**
* Ledger account that can sign by connecting to the device.
*/
export class LedgerAccount implements SigningAccount {
public readonly address: Address;

constructor(
private readonly ledger: SymbolLedger,
networkType: LedgerNetworkType,
private readonly path: string,
public readonly publicKey: string,
private readonly isOptinSymbolWallet: boolean,
) {
this.address = Address.createFromPublicKey(publicKey, networkType as number as NetworkType);
}

async cosignTransaction(transaction: Transaction, transactionHash: string): Promise<string> {
logger.info(`Co-signing transaction ${transactionHash} with Ledger account ${this.address.plain()}. Check your device!`);
return await this.ledger.signCosignatureTransaction(
this.path,
transaction,
transactionHash,
this.publicKey,
this.isOptinSymbolWallet,
);
}

async signTransaction(transaction: Transaction, generationHash: string): Promise<SignedTransaction> {
logger.info(`Signing transaction with Ledger account ${this.address.plain()}. Check your device!`);
const { payload } = await this.ledger.signTransaction(this.path, transaction, generationHash, this.publicKey, false);
const generationHashBytes = Array.from(Convert.hexToUint8(generationHash));
return new SignedTransaction(
payload,
Transaction.createTransactionHash(payload, generationHashBytes),
this.publicKey,
transaction.type,
transaction.networkType,
);
}
}

/**
* Utility object to sign transactions asynchronously
*/
export class SigningUtils {
public static async signTransactionWithCosignatories(
initiatorAccount: SigningAccount,
transaction: AggregateTransaction,
cosignatories: SigningAccount[],
generationHash: string,
): Promise<SignedTransaction> {
const signedTransaction = await initiatorAccount.signTransaction(transaction, generationHash);
let signedPayload = signedTransaction.payload;
for (const cosigner of cosignatories) {
const signature = await cosigner.cosignTransaction(transaction, signedTransaction.hash);
Convert.validateHexString(signature, 128, 'Cosignature is not valid hex!');
signedPayload += UInt64.fromUint(0).toHex() + cosigner.publicKey + signature;
}
// Calculate new size
const size = `00000000${(signedPayload.length / 2).toString(16)}`;
const formatedSize = size.substr(size.length - 8, size.length);
const littleEndianSize =
formatedSize.substr(6, 2) + formatedSize.substr(4, 2) + formatedSize.substr(2, 2) + formatedSize.substr(0, 2);
signedPayload = littleEndianSize + signedPayload.substr(8, signedPayload.length - 8);
return new SignedTransaction(
signedPayload,
signedTransaction.hash,
initiatorAccount.publicKey,
transaction.type,
transaction.networkType,
);
}
}
1 change: 1 addition & 0 deletions src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './PortService';
export * from './ReportService';
export * from './RewardProgramService';
export * from './RunService';
export * from './SigningUtils';
export * from './SshpkService';
export * from './VerifyService';
export * from './VotingService';
36 changes: 28 additions & 8 deletions test/service/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,28 @@ import {
Deadline,
LinkAction,
MultisigAccountModificationTransaction,
PublicAccount,
RawMessage,
RepositoryFactoryHttp,
TransactionGroup,
TransactionService,
TransferTransaction,
UInt64,
} from 'symbol-sdk';
import { BootstrapUtils } from '../../src/service';
import { AnnounceService, BootstrapUtils, PrivateKeyAccount, SigningUtils } from '../../src/service';

// const url = 'http://localhost:3000';
// const multisigPrivateKey = '96258E6EDBFA77D4138A56D03A7A38B89F1BC7721B532B91A43704AE87BBCC87';
// const cosignatory1PrivateKey = 'B1AA7A73D3FCB6054BC7A968C289EFDAF57BCBDC1756E0F3F58696C03F201BFD';
// const cosignatory2PrivateKey = 'AECAA68E6C12C88D28067612AA71B70AA9DA9549CEB3B54D897B711A63E93B71';
// const cosignatory3PrivateKey = '2515356696EB926299ACD3BA872A404BB6D8AD35F9F86015F280858D60C16600';

const url = 'http://api-01.ap-northeast-1.testnet.symboldev.network:3000';
const url = 'http://ngl-dual-501.testnet.symboldev.network:3000';
const multisigPrivateKey = 'CA82E7ADAF7AB729A5462A1BD5AA78632390634904A64EB1BB22295E2E1A1BDD';
const cosignatory1PrivateKey = 'AAAAB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const cosignatory2PrivateKey = 'BBBEE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE';
const cosignatory3PrivateKey = 'CCCEE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08EE';
const ledgerPublicKey = '638bb9ccad2f9c4e4f14215c91b3fe8e4623291926860f19196d3f89709a5772';

const example = async () => {
const repositoryFactory = new RepositoryFactoryHttp(url);
Expand Down Expand Up @@ -70,12 +72,17 @@ const example = async () => {
const cosignatory3 = Account.createFromPrivateKey(cosignatory3PrivateKey, networkType);
console.log(`cosignatory3 ${cosignatory3.address.plain()}`);

const ledger4 = PublicAccount.createFromPublicKey(ledgerPublicKey, networkType);
console.log(`ledger4 ${ledger4.address.plain()}`);

const ledgerData = await AnnounceService.resolveLedgerData(networkType);

const sendModification = async () => {
const multisigAccountModificationTransaction = MultisigAccountModificationTransaction.create(
deadline,
2,
2,
[cosignatory1.address, cosignatory2.address, cosignatory3.address],
[cosignatory1.address, cosignatory2.address, cosignatory3.address, ledger4.address],
[],
networkType,
);
Expand All @@ -88,12 +95,19 @@ const example = async () => {
maxFee,
);

const signedTransaction = multisig.signTransactionWithCosignatories(
const ledgerAccount = ledgerData.accounts.find((l) => l.address.equals(ledger4.address));
if (!ledgerAccount) {
throw new Error('Ledger account not found!!');
}
const signedTransaction = await SigningUtils.signTransactionWithCosignatories(
new PrivateKeyAccount(multisig),
aggregateTransaction,
[cosignatory1, cosignatory2, cosignatory3],
[new PrivateKeyAccount(cosignatory1), new PrivateKeyAccount(cosignatory2), new PrivateKeyAccount(cosignatory3), ledgerAccount],
networkGenerationHash,
);
console.log('Announcing multisig ' + multisig.address.plain());
await service.announce(signedTransaction, listener).toPromise();
console.log(`Multisig ${multisig.address.plain()} confirmed`);
return signedTransaction;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -212,10 +226,16 @@ const example = async () => {
} catch (e) {
console.log(e);
} finally {
ledgerData.ledger.close();
listener.close();
}
};

example().then(() => {
console.log('finish!');
});
example().then(
() => {
console.log('finish!');
},
(err) => {
console.error(err);
},
);
18 changes: 8 additions & 10 deletions test/supernode.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
#privateKeySecurityMode: PROMPT_MAIN
nodeUseRemoteAccount: false
nodeUseRemoteAccount: true
logLevel: 'Debug'
nodes:
- voting: false
- voting: true
rewardProgram: 'supernode'
host: fboucquez-agent-symbollocal.ngrok.io
agentUrl: https://fboucquez-agent-symbollocal.ngrok.io
restGatewayUrl: http://fboucquez-rest-gateway-symbollocal.ngrok.io
mainPrivateKey: E095162875BB1D98CA5E0941670E01C1B0DBDF86DF7B3BEDA4A93635F8E51A03
transportPrivateKey: 415F253ABF0FB2DFD39D7F409EFA2E88769873CAEB45617313B98657A1476A15
remotePrivateKey: 24B485712045FEDA5782ECFF1752F63D41C134371BB46AC68EE6DC9183CF5061
vrfPrivateKey: D34A231C860EB1B6E3A4F8366C8F0F6C7C2462A40263DCB67124FB76CDD4E368
agentPrivateKey: AAAA231C860EB1B6E3A4F8366C8F0F6C7C2462A40263DCB67124FB76CDD4E368
host: my-supernode
mainPrivateKey: CA82E7ADAF7AB729A5462A1BD5AA78632390634904A64EB1BB22295E2E1A1BDD
transportPrivateKey: A15F253ABF0FB2DFD39D7F409EFA2E88769873CAEB45617313B98657A1476A15
remotePrivateKey: B4B485712045FEDA5782ECFF1752F63D41C134371BB46AC68EE6DC9183CF5061
vrfPrivateKey: C34A231C860EB1B6E3A4F8366C8F0F6C7C2462A40263DCB67124FB76CDD4E368
agentPrivateKey: DAAA231C860EB1B6E3A4F8366C8F0F6C7C2462A40263DCB67124FB76CDD4E368