Skip to content

Commit

Permalink
add zemu support
Browse files Browse the repository at this point in the history
  • Loading branch information
linfeng-crypto committed Mar 2, 2021
1 parent 04f022e commit 7c732ba
Show file tree
Hide file tree
Showing 8 changed files with 351 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/config/StaticConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const MainNetConfig: WalletConfig = {
};

// Supposed to be fully customizable by the user when it will be supported
const CustomDevNet: WalletConfig = {
export const CustomDevNet: WalletConfig = {
derivationPath: "m/44'/394'/0'/0/0",
enabled: true,
name: 'CUSTOM DEVNET',
Expand Down
6 changes: 3 additions & 3 deletions src/service/signers/LedgerTransactionSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class LedgerTransactionSigner implements ITransactionSigner {
this.signerProvider = signerProvider;
}

public getTransactionInfo(phrase: string, transaction: TransactionUnsigned) {
public getTransactionInfo(_phrase: string, transaction: TransactionUnsigned) {
this.setCustomFee(transaction);
const cro = sdk.CroSDK({ network: this.config.network });
const rawTx = new cro.RawTransaction();
Expand Down Expand Up @@ -120,7 +120,7 @@ export class LedgerTransactionSigner implements ITransactionSigner {
validatorAddress: transaction.validatorAddress,
});

const pubkeyoriginal = await (await this.signerProvider.getPubKey(0,false)).toUint8Array();
const pubkeyoriginal = await (await this.signerProvider.getPubKey(0, false)).toUint8Array();
const pubkey = Bytes.fromUint8Array(pubkeyoriginal.slice(1));

const signableTx = rawTx
Expand Down Expand Up @@ -154,7 +154,7 @@ export class LedgerTransactionSigner implements ITransactionSigner {
amount: new cro.Coin(transaction.amount, Units.BASE),
});

const pubkeyoriginal = await (await this.signerProvider.getPubKey(0,false)).toUint8Array();
const pubkeyoriginal = await (await this.signerProvider.getPubKey(0, false)).toUint8Array();
const pubkey = Bytes.fromUint8Array(pubkeyoriginal.slice(1));

const signableTx = rawTx
Expand Down
3 changes: 3 additions & 0 deletions tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
build
yarn-error.log
Binary file added tests/app/bin/app.elf
Binary file not shown.
Binary file added tests/app/bin/chain-maind
Binary file not shown.
47 changes: 47 additions & 0 deletions tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "integration-tests",
"author": "Crypto.com <[email protected]>",
"license": "Apache-2.0",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node ./build/tests/src/test.js",
"test": "jest --detectOpenHandles",
"build": "tsc -p tsconfig.json"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Zondax/ledger-cosmos"
},
"keywords": [
"zondax"
],
"dependencies": {
"@zondax/zemu": "leejw51crypto/zemu#master",
"bech32": "^2.0.0",
"ledger-cosmos-js": "^2.1.8",
"typescript": "^4.1.4"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@babel/node": "^7.8.7",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"@types/jest": "~26.0.19",
"@types/node": "~14.14.20",
"babel-eslint": "^10.1.0",
"babel-jest": "24",
"crypto-js": "4.0.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jest": "^23.8.2",
"eslint-plugin-prettier": "^3.1.3",
"jest": "24",
"jest-serial-runner": "^1.1.0 ",
"secp256k1": "^4.0.1"
}
}
264 changes: 264 additions & 0 deletions tests/src/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import * as Zemu from '@zondax/zemu';
import CosmosApp from 'ledger-cosmos-js';
import * as path from 'path';
import { LedgerSigner } from '../../src/service/signers/LedgerSigner';
import { ISignerProvider } from '../../src/service/signers/SignerProvider';
import { LedgerTransactionSigner } from '../../src/service/signers/LedgerTransactionSigner';
import {CustomDevNet} from '../../src/config/StaticConfig';
import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes';
import {NodeRpcService} from "../../src/service/rpc/NodeRpcService";
const { exec } = require("child_process");
import chai from "chai";

const APP_PATH = path.resolve(`./app/bin/app.elf`);
const seed = 'equip will roof matter pink blind book anxiety banner elbow sun young';
const SIM_OPTIONS = {
logging: true,
start_delay: 4000,
X11: true,
custom: `-s "${seed}" --display=headless --color LAGOON_BLUE`,
};

function runCmd(cmd) {
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.log(cmd, `error: ${error.message}`);
return;
}
if (stderr) {
console.log(cmd, `stderr: ${stderr}`);
return;
}
console.log(cmd, `stdout: ${stdout}`);
});
}

export class LedgerSignerZemu extends LedgerSigner {
public sim: any;
public click_times: number;

constructor(account: number = 0) {
super(account);
this.sim = new Zemu.default(APP_PATH);
this.click_times = 0;
}

public setClickTimes(times: number) {
this.click_times = times;
}

public async initChain() {
await Zemu.default.checkAndPullImage();
if (this.app === null || this.app === undefined) {
await this.sim.start(SIM_OPTIONS);
this.app = new CosmosApp(this.sim.getTransport());
console.log("start zemu grpc server");
this.sim.startgrpcServer("localhost", "3002");
await Zemu.default.sleep(5000);
}
// note: chain-maind need support zemu, so build it from `make build ledger=ZEMU` in chain-maind src
const cmd1 = "chain-maind init testnode --chain-id test -o";
const cmd2 = "chain-maind keys add validator1 --keyring-backend test";
const cmd3 = "chain-maind keys add hw --ledger --keyring-backend test";
const cmd4 = "chain-maind add-genesis-account $(chain-maind keys show validator1 -a --keyring-backend test) 200000000cro";
const cmd5 = "chain-maind add-genesis-account $(chain-maind keys show hw -a --keyring-backend test) 100000000cro";
const cmd6 = "chain-maind gentx validator1 100000000cro --keyring-backend test --chain-id test";
const cmd7 = "chain-maind collect-gentxs";
const commands = [cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7];
let i = 0;
for (let cmd of commands) {
await Zemu.default.sleep(3000);
i += 1;
runCmd(cmd);
if (i === 3) {
await Zemu.default.sleep(3000);
console.log("right click");
await this.sim.clickRight();
console.log("right click");
await this.sim.clickRight();
console.log("right click");
await this.sim.clickRight();
console.log("right click");
await this.sim.clickRight();
console.log("double click");
await this.sim.clickBoth();
}
}
console.log("stop grpc server");
this.sim.stopgrpcServer();
console.log("start chain-maind");
exec("sed -i.bak 's/cors_allowed_origins = \\[\\]/cors_allowed_origins = [\"*\"]/g' $HOME/.chain-maind/config/config.toml");
await Zemu.default.sleep(3000);
// spawn("chain-maind", ["start", "--home", "$HOME/.chain-maind"]);
exec("chain-maind start --home $HOME/.chain-maind&");
console.log("start chain-maind finished");
}

async createTransport() {
await Zemu.default.checkAndPullImage();
if (this.app === null || this.app === undefined) {
await this.sim.start(SIM_OPTIONS);
this.app = new CosmosApp(this.sim.getTransport());
}
}

async closeTransport() {
if (this.app === null || this.app === undefined) {
console.log("transport already closed");
} else {
console.log("close transport now");
await this.sim.close();
this.app = null;
}
}

async sign(message: Bytes): Promise<Bytes> {
await this.createTransport();

if (!this.app || !this.path) {
throw new Error('Not signed in');
}

console.log("message in sign function:", message);

const signatureRequest = this.app.sign(this.path, message.toUint8Array());
await Zemu.default.sleep(10000);

for (var i=0; i< this.click_times; i++) {
await Zemu.default.sleep(100);
console.log("right click");
await this.sim.clickRight();
}
console.log("click both");
await this.sim.clickBoth();
let response = await signatureRequest;

if (response.error_message !== 'No errors') {
throw new Error(`[${response.error_message}] ${response.error_message}`);
}
console.log("sign response:", response);

// Ledger has encoded the sig in ASN1 DER format, but we need a 64-byte buffer of <r,s>
// DER-encoded signature from Ledger:
// 0 0x30: a header byte indicating a compound structure
// 1 A 1-byte length descriptor for all what follows (ignore)
// 2 0x02: a header byte indicating an integer
// 3 A 1-byte length descriptor for the R value
// 4 The R coordinate, as a big-endian integer
// 0x02: a header byte indicating an integer
// A 1-byte length descriptor for the S value
// The S coordinate, as a big-endian integer
// = 7 bytes of overhead
let { signature } = response;
if (signature[0] !== 0x30) {
throw new Error('Ledger assertion failed: Expected a signature header of 0x30');
}

// decode DER string format
let rOffset = 4;
let rLen = signature[3];
const sLen = signature[4 + rLen + 1]; // skip over following 0x02 type prefix for s
let sOffset = signature.length - sLen;
// we can safely ignore the first byte in the 33 bytes cases
if (rLen === 33) {
rOffset++; // chop off 0x00 padding
rLen--;
}
if (sLen === 33) {
sOffset++;
} // as above
const sigR = signature.slice(rOffset, rOffset + rLen); // skip e.g. 3045022100 and pad
const sigS = signature.slice(sOffset);

signature = Buffer.concat([sigR, sigS]);
if (signature.length !== 64) {
throw new Error(`Ledger assertion failed: incorrect signature length ${signature.length}`);
}
const bytes = Bytes.fromUint8Array(new Uint8Array(signature));
await this.closeTransport();
return bytes;
}

}

export class LedgerWalletSignerProviderZemu implements ISignerProvider {
public provider: LedgerSignerZemu;

constructor() {
this.provider = new LedgerSignerZemu();
}

public async getPubKey(index: number): Promise<Bytes> {
const result = await this.provider.enable(index, 'cro', true); // dummy value
await this.provider.closeTransport();
return result[1];
}

public async getAddress(index: number, addressPrefix: string): Promise<string> {
const result = await this.provider.enable(index, addressPrefix, true);
await this.provider.closeTransport();
return result[0];
}

public async sign(message: Bytes): Promise<Bytes> {
const result = await this.provider.sign(message);
await this.provider.closeTransport();
return result;
}
}

async function main() {
const signerProvider = new LedgerWalletSignerProviderZemu();
await signerProvider.provider.initChain();
await Zemu.default.sleep(10000);
const walletConfig = CustomDevNet;
walletConfig.nodeUrl = "http://127.0.0.1";
const signer = new LedgerTransactionSigner(
walletConfig,
signerProvider,
);
console.log(signer);

const phrase = "";
signerProvider.provider.setClickTimes(7);
const ledgerAddress = 'cro1tzhdkuc328cgh2hycyfddtdpqfwwu42yq3qgkr';
const nodeRpc = await NodeRpcService.init(walletConfig.nodeUrl);
await Zemu.default.sleep(20000);
const accountNumber = await nodeRpc.fetchAccountNumber(ledgerAddress);
console.log("get account number ", accountNumber);
const accountSequence = await nodeRpc.loadSequenceNumber(ledgerAddress);
console.log("get accountSequence ", accountSequence);
const signedTxHex = await signer.signTransfer(
{
accountNumber: accountNumber,
accountSequence: accountSequence,
amount: '100000000',
fromAddress: ledgerAddress,
memo: '',
toAddress: 'cro1sza72v70tm9l38h6uxhwgra5eg33xd4jr3ujl7',
},
phrase,
);
console.log("broadcast transaction");
console.log(signedTxHex);
const broadCastResult = await nodeRpc.broadcastTransaction(signedTxHex);
console.log("broadCast result:", broadCastResult);
await Zemu.default.sleep(10000);
console.log("get sender's balance")
const senderBalance = await nodeRpc.loadAccountBalance(ledgerAddress, "basecro");
console.log("sender's balance:", senderBalance);
chai.assert.notEqual(senderBalance,"10000000000000000");
}

function afterFinish() {
const cmd = "pgrep 'chain-maind' | xargs kill -9";
exec(cmd);
}

(async () => {
try {
await main();
} finally {
afterFinish();
}
})();
33 changes: 33 additions & 0 deletions tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"allowJs": true,
"importHelpers": true,
"jsx": "react",
"alwaysStrict": true,
"strict": true,
"rootDir":"../",
"sourceMap": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": false,
"noImplicitThis": false,
"strictNullChecks": false,
"skipLibCheck": true ,
"noEmitOnError": true,
"esModuleInterop": true ,
"outDir": "./build",
"typeRoots": ["node_modules/@types"],
"experimentalDecorators": true
},
"include": [
"src/**/*",
]
}

0 comments on commit 7c732ba

Please sign in to comment.