Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend example #12

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ src/glyphs.h
gitignore
.vagrant
libsol/target
node_modules
3 changes: 3 additions & 0 deletions examples/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"printWidth": 100
}
31 changes: 31 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Examples

The project contains examples of communicating with a real device or speculos (ledger nano emulator) via javascript (typescript).

## Install deps

```shell
$ yarn
```

## Run

```bash
# to run a sign test on a real device
$ yarn run sign-real

# to run a sign test on speculos
$ yarn run sign-speculos
```

### Notes

No need to compile typescript before running. The project uses `ts-node` which can run typescript files directly.

```bash
$ npx ts-node ./src/sign-real-device
# or
$ ./node_modules/.bin/ts-node ./src/sign-real-device
# or
$ yarn run ts-node ./src/sign-real-device
```
16 changes: 16 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"scripts": {
"sign-real": "ts-node ./src/sign-real-device",
"sign-speculos": "ts-node ./src/sign-speculos"
},
"dependencies": {
"@ledgerhq/hw-transport-node-hid": "^6.7.0",
"@ledgerhq/hw-transport-node-speculos": "^6.7.0",
"@solana/web3.js": "^1.29.2",
"ts-node": "^10.2.1",
"typescript": "^4.4.3"
},
"devDependencies": {
"@types/bs58": "^4.0.1"
}
}
85 changes: 33 additions & 52 deletions examples/example-sign.js → examples/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,7 @@
/* package.json
{
"name": "test",
"version": "0.0.1",
"description": "test",
"main": "example-sign.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "solana",
"license": "ISC",
"dependencies": {
"@ledgerhq/hw-transport-node-hid": "5.17.0",
"bs58": "4.0.1",
"tweetnacl": "1.0.3",
"@solana/web3.js": "0.90.0",
"assert": "2.0.0"
}
}
*/

const Transport = require("@ledgerhq/hw-transport-node-hid").default;
const bs58 = require("bs58");
const nacl = require("tweetnacl");
const solana = require("@solana/web3.js");
const assert = require("assert");
import Transport from "@ledgerhq/hw-transport";
import { TransportError } from "@ledgerhq/errors";
import bs58 from "bs58";
import * as solana from "@solana/web3.js";

const INS_GET_PUBKEY = 0x05;
const INS_SIGN_MESSAGE = 0x06;
Expand All @@ -43,18 +21,19 @@ const STATUS_OK = 0x9000;
/*
* Helper for chunked send of large payloads
*/
async function solana_send(transport, instruction, p1, payload) {
async function solana_send(transport: Transport, instruction: number, p1: number, payload: Buffer) {
var p2 = 0;
var payload_offset = 0;

if (payload.length > MAX_PAYLOAD) {
while ((payload.length - payload_offset) > MAX_PAYLOAD) {
while (payload.length - payload_offset > MAX_PAYLOAD) {
const buf = payload.slice(payload_offset, payload_offset + MAX_PAYLOAD);
payload_offset += MAX_PAYLOAD;
console.log("send", (p2 | P2_MORE).toString(16), buf.length.toString(16), buf);
const reply = await transport.send(LEDGER_CLA, instruction, p1, (p2 | P2_MORE), buf);
const reply = await transport.send(LEDGER_CLA, instruction, p1, p2 | P2_MORE, buf);
if (reply.length != 2) {
throw new TransportError(
//TODO: fix
throw TransportError(
"solana_send: Received unexpected reply payload",
"UnexpectedReplyPayload"
);
Expand All @@ -70,15 +49,15 @@ async function solana_send(transport, instruction, p1, payload) {
return reply.slice(0, reply.length - 2);
}

const BIP32_HARDENED_BIT = ((1 << 31) >>> 0);
function _harden(n) {
const BIP32_HARDENED_BIT = (1 << 31) >>> 0;
function _harden(n: number) {
return (n | BIP32_HARDENED_BIT) >>> 0;
}

function solana_derivation_path(account, change) {
function solana_derivation_path(account?: number, change?: number) {
var length;
if (typeof(account) === 'number') {
if (typeof(change) === 'number') {
if (typeof account === "number") {
if (typeof change === "number") {
length = 4;
} else {
length = 3;
Expand All @@ -87,27 +66,31 @@ function solana_derivation_path(account, change) {
length = 2;
}

var derivation_path = Buffer.alloc(1 + (length * 4));
var derivation_path = Buffer.alloc(1 + length * 4);
var offset = 0;
offset = derivation_path.writeUInt8(length, offset);
offset = derivation_path.writeUInt32BE(_harden(44), offset); // Using BIP44
offset = derivation_path.writeUInt32BE(_harden(44), offset); // Using BIP44
offset = derivation_path.writeUInt32BE(_harden(501), offset); // Solana's BIP44 path

if (length > 2) {
offset = derivation_path.writeUInt32BE(_harden(account), offset);
offset = derivation_path.writeUInt32BE(_harden(account as number), offset);
if (length == 4) {
offset = derivation_path.writeUInt32BE(_harden(change), offset);
offset = derivation_path.writeUInt32BE(_harden(change as number), offset);
}
}

return derivation_path;
}

async function solana_ledger_get_pubkey(transport, derivation_path) {
async function solana_ledger_get_pubkey(transport: Transport, derivation_path: Buffer) {
return solana_send(transport, INS_GET_PUBKEY, P1_NON_CONFIRM, derivation_path);
}

async function solana_ledger_sign_transaction(transport, derivation_path, transaction) {
async function solana_ledger_sign_transaction(
transport: Transport,
derivation_path: Buffer,
transaction: solana.Transaction
) {
const msg_bytes = transaction.compileMessage().serialize();

// XXX: Ledger app only supports a single derivation_path per call ATM
Expand All @@ -119,9 +102,7 @@ async function solana_ledger_sign_transaction(transport, derivation_path, transa
return solana_send(transport, INS_SIGN_MESSAGE, P1_CONFIRM, payload);
}

( async () => {
var transport = await Transport.create();

export async function runSignTest(transport: Transport) {
const from_derivation_path = solana_derivation_path();
const from_pubkey_bytes = await solana_ledger_get_pubkey(transport, from_derivation_path);
const from_pubkey_string = bs58.encode(from_pubkey_bytes);
Expand All @@ -143,16 +124,17 @@ async function solana_ledger_sign_transaction(transport, derivation_path, transa
// XXX: Fake blockhash so this example doesn't need a
// network connection. It should be queried from the
// cluster in normal use.
const recentBlockhash = bs58.encode(Buffer.from([
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
]));
const recentBlockhash = bs58.encode(
Buffer.from([
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3,
])
);

var tx = new solana.Transaction({
recentBlockhash,
feePayer: from_pubkey,
})
.add(ix);
}).add(ix);

const sig_bytes = await solana_ledger_sign_transaction(transport, from_derivation_path, tx);

Expand All @@ -161,5 +143,4 @@ async function solana_ledger_sign_transaction(transport, derivation_path, transa

tx.addSignature(from_pubkey, sig_bytes);
console.log("--- verifies:", tx.verifySignatures());
})().catch(e => console.log(e) );

}
14 changes: 14 additions & 0 deletions examples/src/sign-real-device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";
import { runSignTest } from "./core";

const run = async () => {
try {
await runSignTest(await TransportNodeHid.create());
process.exit(0);
} catch (e) {
console.log(e);
process.exit(1);
}
};

run();
16 changes: 16 additions & 0 deletions examples/src/sign-speculos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SpeculosTransport from "@ledgerhq/hw-transport-node-speculos";
import { runSignTest } from "./core";

const APDU_PORT = 9999;

const run = async () => {
try {
await runSignTest(await SpeculosTransport.open({ apduPort: APDU_PORT }));
process.exit(0);
} catch (e) {
console.log(e);
process.exit(1);
}
};

run();
10 changes: 10 additions & 0 deletions examples/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
Loading