Skip to content

Commit 3041e21

Browse files
authored
Fix optinal argument (#184)
* make scheme optional * make signature backward compatible * update readme * upgrade deps * update docs * add util to parse rsv signature * remove duplicated readme * change readme zondax icon on workflow * update package * update images
1 parent c54a850 commit 3041e21

File tree

10 files changed

+493
-464
lines changed

10 files changed

+493
-464
lines changed

.github/workflows/publish.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,18 @@ jobs:
4040

4141
- run: bun install
4242

43-
- run: mv README-npm.md README.md
4443
- run: bun run build
4544

45+
- name: Prepare README for npm
46+
run: |
47+
# Create a new README with the modified content
48+
{
49+
head -n 2 README.md
50+
echo "![zondax](docs/zondax_light.png)"
51+
tail -n +4 README.md
52+
} > README.md.tmp
53+
mv README.md.tmp README.md
54+
4655
- name: Get latest release version number
4756
id: get_version
4857
uses: battila7/get-version-action@v2

README-npm.md

Lines changed: 0 additions & 88 deletions
This file was deleted.

README.md

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,61 @@
66
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
77
[![npm version](https://badge.fury.io/js/%40zondax%2Fledger-substrate.svg)](https://badge.fury.io/js/%40zondax%2Fledger-substrate)
88

9-
This package provides a basic client library to communicate with Substrate Apps running in a Ledger Nano S/S+/X devices
9+
This package provides a basic client library to communicate with Substrate Apps running in a Ledger Nano S/S+/X, Flex and Stax devices
1010
Additionally, it provides a hd_key_derivation function to retrieve the keys that Ledger apps generate with
1111
BIP32-ED25519. Warning: the hd_key_derivation function is not audited and depends on external packages. We recommend
1212
using the official Substrate Ledger apps in recovery mode.
1313

14-
# Available commands
14+
# Generic app Available commands
1515

16-
| Operation | Response | Command |
17-
| ---------- | ---------------- | ----------------------- |
18-
| getVersion | app version | --------------- |
19-
| getAddress | pubkey + address | path + ( showInDevice ) |
20-
| sign | signed message | path + message |
16+
## Address Operations
17+
18+
| Operation | Response | Command | Notes |
19+
| ----------------- | ----------------------------------- | ------------------------------------------------- | -------------------------------------- |
20+
| getAddress | { pubKey: string, address: string } | path + ss58prefix + (showAddrInDevice) + (scheme) | Deprecated, use specific methods below |
21+
| getAddressEd25519 | { pubKey: string, address: string } | path + ss58prefix + (showAddrInDevice) | Uses ED25519 scheme |
22+
| getAddressEcdsa | { pubKey: string, address: string } | path + (showAddrInDevice) | Uses ECDSA scheme |
23+
24+
## Signing Operations
25+
26+
| Operation | Response | Command | Notes |
27+
| ----------- | --------------------- | -------------------------------- | --------------------------------------------------------------------------------------- |
28+
| sign | { signature: Buffer } | path + txBlob + Optional(scheme) | Deprecated, use specific methods below |
29+
| signEd25519 | { signature: Buffer } | path + txBlob | Uses ED25519 scheme |
30+
| signEcdsa | { signature: Buffer } | path + txBlob | Uses ECDSA scheme. Signature is in RSV format: R (32 bytes) + S (32 bytes) + V (1 byte) |
31+
32+
## Raw Signing Operations
33+
34+
| Operation | Response | Command | Notes |
35+
| -------------- | --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
36+
| signRaw | { signature: Buffer } | path + txBlob + Optional(scheme) | Deprecated, use specific methods below |
37+
| signRawEd25519 | { signature: Buffer } | path + txBlob | Raw signing with ED25519 |
38+
| signRawEcdsa | { signature: Buffer } | path + txBlob | Raw signing with ECDSA. Signature is in RSV format: R (32 bytes) + S (32 bytes) + V (1 byte) |
39+
40+
## Metadata Signing Operations
41+
42+
| Operation | Response | Command | Notes |
43+
| ----------------------- | --------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
44+
| signWithMetadata | { signature: Buffer } | path + txBlob + txMetadata + Optional(scheme) | Deprecated, use specific methods below |
45+
| signWithMetadataEd25519 | { signature: Buffer } | path + txBlob + txMetadata | Metadata signing with ED25519 |
46+
| signWithMetadataEcdsa | { signature: Buffer } | path + txBlob + txMetadata | Metadata signing with ECDSA. Signature is in RSV format: R (32 bytes) + S (32 bytes) + V (1 byte) |
47+
| signMigration | { signature: Buffer } | path + txBlob + (txMetadataChainId) + (txMetadataSrvUrl) | Migration-specific signing |
48+
49+
## Utility Functions
50+
51+
| Operation | Response | Input | Notes |
52+
| ------------------- | ----------------------------------- | ----------------- | --------------------------------------------------------------------------------------- |
53+
| parseEcdsaSignature | { r: string, s: string, v: string } | signature: Buffer | Utility to parse ECDSA signatures into R, S, V components. Input must be 65 bytes long. |
54+
55+
# Substrate apps Available commands
56+
57+
| Operation | Response | Command |
58+
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
59+
| getVersion | { device_locked: boolean, major: number, minor: number, patch: number, test_mode: boolean, error_message: string, return_code: number } |
60+
| appInfo | { error_message: string, return_code: number, ...appInfo } |
61+
| getAddress | { address: string, pubKey: string, error_message: string, return_code: number } | account + change + addressIndex + (requireConfirmation) + (scheme) |
62+
| sign | { signature: Buffer, error_message: string, return_code: number } | account + change + addressIndex + message + (scheme) |
63+
| signRaw | { signature: Buffer, error_message: string, return_code: number } | account + change + addressIndex + message + (scheme) |
2164

2265
getAddress command requires that you set the derivation path (account, change, index) and has an option parameter to
2366
display the address on the device. By default, it will retrieve the information without confirmation from the user.
@@ -33,7 +76,7 @@ display the address on the device. By default, it will retrieve the information
3376
- Provide the following new arguments:
3477
- **txMetadataChainId**: This is the id of the chain where you intend to sign transactions. This should match the chain id configured at the generic app API service.
3578
- **txMetadataSrvUrl**: This is the URL for the generic app API service, which generates the transaction metadata needed for signing transactions on the device. Zondax provides a live demo of this service. The url is:
36-
- https://api.zondax.ch/polkadot/transaction/metadata
79+
- <https://api.zondax.ch/polkadot/transaction/metadata>
3780

3881
3. **Configure Address Retrieval**:
3982

@@ -46,6 +89,8 @@ display the address on the device. By default, it will retrieve the information
4689

4790
# Add new chain
4891

92+
If you are using Generic App, there is no need to add your chain in the supported apps.
93+
4994
If you want to add support for your chain, you just need to create a PR in this repository adding the parameters that
5095
belong to the chain. Go to [supported APPs](./src/supported_apps.ts) and add a new entry at the end of the file.
5196

@@ -80,7 +125,7 @@ yarn install
80125
yarn test
81126
```
82127

83-
## Example:
128+
## Example
84129

85130
Visit and download the [latest release](https://github.com/Zondax/ledger-kusama/releases/latest) from repository (in
86131
this case Kusama).

docs/zondax_dark.png

-60.5 KB
Loading

docs/zondax_light.png

-55.6 KB
Loading

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"files": [
2626
"dist/**",
2727
"LICENSE",
28-
"package.json"
28+
"package.json",
29+
"README.md"
2930
],
3031
"scripts": {
3132
"build": "tsc",
@@ -67,7 +68,7 @@
6768
"jest-serial-runner": "^1.2.1",
6869
"prettier": "^3.5.3",
6970
"sort-package-json": "^3.0.0",
70-
"ts-jest": "29.3.2",
71+
"ts-jest": "29.3.4",
7172
"ts-node": "^10.9.2",
7273
"typescript": "5.8.3"
7374
},

src/common.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,6 @@ export interface GenericResponseSign {
181181
signature: Buffer
182182
}
183183

184-
export interface GenericResponseSignEcdsa {
185-
r: Buffer
186-
s: Buffer
187-
v: Buffer
188-
}
189-
190184
/**
191185
* @deprecated Moved to @zondax/ledger-js
192186
*/

src/generic_app.ts

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
ECDSA_PUBKEY_LEN,
2323
ED25519_PUBKEY_LEN,
2424
GenericResponseSign,
25-
GenericResponseSignEcdsa,
2625
GenericeResponseAddress,
2726
P1_VALUES,
2827
SCHEME,
@@ -265,14 +264,18 @@ export class PolkadotGenericApp extends BaseApp {
265264
* @param blob - The transaction blob.
266265
* @param metadata - The optional metadata.
267266
* @throws {ResponseError} If the response from the device indicates an error.
268-
* @returns The response containing the signature and status.
267+
* @returns The response containing the signature and status. For ECDSA, the signature is in RSV format:
268+
* - R: First 32 bytes (signature.slice(0, 32))
269+
* - S: Next 32 bytes (signature.slice(32, 64))
270+
* - V: Last byte (signature.slice(64, 65))
271+
* @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components
269272
*/
270273
private async signImplEcdsa(
271274
path: BIP32Path,
272275
ins: number,
273276
blob: TransactionBlob,
274277
metadata?: TransactionMetadataBlob
275-
): Promise<GenericResponseSignEcdsa> {
278+
): Promise<GenericResponseSign> {
276279
const chunks = this.getSignReqChunks(path, blob, metadata)
277280

278281
try {
@@ -283,9 +286,7 @@ export class PolkadotGenericApp extends BaseApp {
283286
}
284287

285288
return {
286-
r: result.readBytes(32),
287-
s: result.readBytes(32),
288-
v: result.readBytes(1),
289+
signature: result.readBytes(result.length()),
289290
}
290291
} catch (e) {
291292
throw processErrorResponse(e)
@@ -342,7 +343,11 @@ export class PolkadotGenericApp extends BaseApp {
342343
* @param path - The BIP44 path.
343344
* @param txBlob - The transaction blob.
344345
* @throws {ResponseError} If the response from the device indicates an error.
345-
* @returns The response containing the signature and status.
346+
* @returns The response containing the signature and status. For ECDSA, the signature is in RSV format:
347+
* - R: First 32 bytes (signature.slice(0, 32))
348+
* - S: Next 32 bytes (signature.slice(32, 64))
349+
* - V: Last byte (signature.slice(64, 65))
350+
* @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components
346351
*/
347352
async signEcdsa(path: BIP32Path, txBlob: TransactionBlob) {
348353
if (!this.txMetadataSrvUrl) {
@@ -425,7 +430,11 @@ export class PolkadotGenericApp extends BaseApp {
425430
* @param path - The BIP44 path.
426431
* @param txBlob - The transaction blob.
427432
* @throws {ResponseError} If the response from the device indicates an error.
428-
* @returns The response containing the signature and status.
433+
* @returns The response containing the signature and status. For ECDSA, the signature is in RSV format:
434+
* - R: First 32 bytes (signature.slice(0, 32))
435+
* - S: Next 32 bytes (signature.slice(32, 64))
436+
* - V: Last byte (signature.slice(64, 65))
437+
* @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components
429438
*/
430439
async signRawEcdsa(path: BIP32Path, txBlob: TransactionBlob) {
431440
return await this.signImplEcdsa(path, this.INS.SIGN_RAW, txBlob)
@@ -440,7 +449,7 @@ export class PolkadotGenericApp extends BaseApp {
440449
* @throws {ResponseError} If the response from the device indicates an error.
441450
* @returns The response containing the signature and status.
442451
*/
443-
async signWithMetadata(path: BIP32Path, txBlob: TransactionBlob, txMetadata: TransactionMetadataBlob, scheme: SCHEME) {
452+
async signWithMetadata(path: BIP32Path, txBlob: TransactionBlob, txMetadata: TransactionMetadataBlob, scheme = SCHEME.ED25519) {
444453
if (scheme != SCHEME.ECDSA && scheme != SCHEME.ED25519) {
445454
throw new ResponseError(LedgerError.ConditionsOfUseNotSatisfied, `Unexpected scheme ${scheme}. Needs to be ECDSA (2) or ED25519 (0)`)
446455
}
@@ -456,7 +465,11 @@ export class PolkadotGenericApp extends BaseApp {
456465
* @param txBlob - The transaction blob.
457466
* @param txMetadata - The transaction metadata.
458467
* @throws {ResponseError} If the response from the device indicates an error.
459-
* @returns The response containing the signature and status.
468+
* @returns The response containing the signature and status. For ECDSA, the signature is in RSV format:
469+
* - R: First 32 bytes (signature.slice(0, 32))
470+
* - S: Next 32 bytes (signature.slice(32, 64))
471+
* - V: Last byte (signature.slice(64, 65))
472+
* @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components
460473
*/
461474
async signWithMetadataEcdsa(path: BIP32Path, txBlob: TransactionBlob, txMetadata: TransactionMetadataBlob) {
462475
return await this.signImplEcdsa(path, this.INS.SIGN, txBlob, txMetadata)
@@ -473,4 +486,22 @@ export class PolkadotGenericApp extends BaseApp {
473486
async signWithMetadataEd25519(path: BIP32Path, txBlob: TransactionBlob, txMetadata: TransactionMetadataBlob) {
474487
return await this.signImplEd25519(path, this.INS.SIGN, txBlob, txMetadata)
475488
}
489+
490+
/**
491+
* Utility function to convert ECDSA signature response into RSV structure
492+
* @param signature - The ECDSA signature buffer from the device response
493+
* @returns Object containing R, S, and V components of the ECDSA signature
494+
* @throws {Error} If signature length is not 65 bytes (expected for ECDSA RSV format)
495+
*/
496+
static parseEcdsaSignature(signature: Buffer): { r: string; s: string; v: string } {
497+
if (signature.length !== 65) {
498+
throw new Error('Invalid ECDSA signature length. Expected 65 bytes for RSV format')
499+
}
500+
501+
return {
502+
r: signature.slice(0, 32).toString('hex'),
503+
s: signature.slice(32, 64).toString('hex'),
504+
v: signature.slice(64, 65).toString('hex'),
505+
}
506+
}
476507
}

0 commit comments

Comments
 (0)