Coder Social home page Coder Social logo

app-xrp's Introduction

XRP Wallet App for Ledger devices

Ensure compliance with Ledger guidelines

Build and run functional tests using ragger through reusable workflow

Introduction

This repository contains the source code for the XRP wallet app that makes it possible to securely store XRP and assets issued on the XRP Ledger using Ledger Nano devices.

To add Ledger Nano S and Ledger Nano X support in your application, please see the NPM package hw-app-xrp and the examples below.

Features

The XRP wallet app comes with the following features:

  • Support for all transaction types:
    • AccountSet
    • AccountDelete
    • CheckCancel
    • CheckCash
    • CheckCreate
    • DepositPreauth
    • EscrowCancel
    • EscrowCreate
    • EscrowFinish
    • OfferCancel
    • OfferCreate
    • Payment
    • PaymentChannelClaim
    • PaymentChannelCreate
    • PaymentChannelFund
    • SetRegularKey
    • SignerListSet
    • TrustSet
  • Support for all transaction common fields such as memos
  • Support for issued assets such as SOLO, stocks and ETFs
  • Support for signing on behalf of others
  • Support for multi-signing
  • Unified UI across Ledger Nano devices

User Interface

The user interface primarily consists of the idle menu and the transaction review menu.

Idle Menu

Upon starting the app on your device you are immediately greeted by the idle menu. This menu, as the name implies, is used when the device is in its idle state. At this point, an external application may initiate a transaction, which opens up the review menu.

Idle menu

Review Menu

When reviewing a transaction the entire UI is dedicated to displaying the transaction details. You can page through all the details by using the left and right buttons on your device, as indicated by arrows on the screen.

Review menu

Fields in arrays are suffixed with their array index in square brackets. See example below.

Array field in review menu

PathSet fields have their index information shown in square brackets on the form [Pi: Sj], where i is the path index and j is the step index within that path. See example below.

Path field in review menu

In order to take action on the transaction, you must first page through and review all transaction details. The last two items in the review menu are 'Sign transaction' and 'Reject'.

Approval menu

Page to either 'Sign transaction' or 'Reject' and press both buttons simultaneously to confirm your action.

Usage

In order to initiate transactions from NodeJS or a browser client, the library hw-app-xrp can be used.

An example of a basic payment transaction using this library is shown below:

import Transport from "@ledgerhq/hw-transport-node-hid";
// import Transport from "@ledgerhq/hw-transport-u2f"; // for browser
import Xrp from "@ledgerhq/hw-app-xrp";
import { encode } from 'ripple-binary-codec';

function establishConnection() {
    return Transport.create()
        .then(transport => new Xrp(transport));
}

function fetchAddress(xrp) {
    return xrp.getAddress("44'/144'/0'/0/0").then(deviceData => {
        return {
            xrp,
            address: deviceData.address,
            publicKey: deviceData.publicKey.toUpperCase()
        }
    });
}

function signTransaction(context, transaction) {
    const preparedTransaction = {
        Account: context.address,
        SigningPubKey: context.publicKey,
        ...transaction
    };

    const transactionBlob = encode(preparedTransaction);

    console.log('Sending transaction to device for approval...');
    return context.xrp.signTransaction("44'/144'/0'/0/0", transactionBlob);
}

const transactionJSON = {
    TransactionType: "Payment",
    Destination: "rTooLkitCksh5mQa67eaa2JaWHDBnHkpy",
    Amount: "1000000",
    Fee: "15",
    Flags: 2147483648,
    Sequence: 57,
};

establishConnection()
    .then(xrp => fetchAddress(xrp))
    .then(context => signTransaction(context, transactionJSON))
    .then(signature => console.log(`Signature: ${signature}`))
    .catch(e => console.log(`An error occurred (${e.message})`));

Advanced Usage

Multi-signing a Transaction

It is also possible to perform parallel multi-signing using the XRP wallet app. This is done by sourcing a list of signatures for the transaction and appending them to the Signers field of the transaction before submitting it for processing. An example of combining a couple of externally sourced signatures with a signature of the Ledger device is shown below (uses imports and functions declared in previous example).

const transactionJSON = {
    Account: "r4PCuDkjuV2e23xVP8ChkVxo1aG2Ufpkjb",
    TransactionType: "Payment",
    Destination: "rTooLkitCksh5mQa67eaa2JaWHDBnHkpy",
    Amount: "1000000",
    Fee: "15",
    Flags: 2147483648,
    Sequence: 47,
    SigningPubKey: "" // Must be blank
};

// Sourced externally from other signing parties, replace "..." with actual values.
const otherSigners = [
    {
        Signer: {
            Account: "...",
            SigningPubKey: "...",
            TxnSignature: "..."
        }
    },
    {
        Signer: {
            Account: "...",
            SigningPubKey: "...",
            TxnSignature: "..."
        }
    }
];

function retrieveSignerData(transaction) {
    return establishConnection()
        .then(xrp => fetchAddress(xrp))
        .then(context => {
            return signTransaction(context, transaction)
                .then(signature => {
                    return {
                        Signer: {
                            Account: context.account,
                            SigningPubKey: context.publicKey,
                            TxnSignature: signature.toUpperCase()
                        }
                    }
                });
        })
        .catch(e => console.log(`An error occurred (${e.message})`));
}

retrieveSignerData(transactionJSON)
    .then(signer => {
        return {
            ...transactionJSON,
            Signers: [
                ...otherSigners,
                signer
            ]
        }
    })
    .then(transaction => console.log(transaction))
    .catch(e => console.log(`An error occurred (${e.message})`));

Additional Notes

From version 2.0.0 of the XRP wallet app it is possible to sign larger transactions than in previous versions. In order to enable support for larger transactions, there have been slight modifications to the transport protocol, which is used to communicate between the client and the device.

The protocol changes are fully backwards-compatible with previous versions of hw-app-xrp, but in order to sign larger transactions you must use version 5.12.0 or above of hw-app-xrp.

Limitations

Because of resource constraints the following limits apply for the respective hardware wallet:

Ledger Nano S

  • Maximum fields per transaction: 24 fields
  • Maximum displayed field value length: 128 characters
  • Maximum transaction size: 800 bytes
  • Maximum number of elements per array field: 8 elements
  • Multi-sign support: Parallel only

Ledger Nano X

  • Maximum fields per transaction: 60 fields
  • Maximum displayed field value length: 1024 characters
  • Maximum transaction size: 10 000 bytes
  • Maximum number of elements per array field: 8 elements
  • Multi-sign support: Parallel only

Building

Make sure that you have configured a development environment as outlined in the development documentation for Ledger devices. Then run make from the repository root to build the app:

make

Installing

To upload the app to your device, run the following command:

make load

Testing

Manual testing can be conducted with the help of the testing utility TowoLabs/ledger-tests-xrp. Make sure that your device is running the latest firmware and then follow the instructions in the test repository.

app-xrp's People

Contributors

agrojean-ledger avatar apaillier-ledger avatar btchip avatar cedelavergne-ledger avatar fbeutin-ledger avatar gbillou avatar greenknot avatar philippebonnaz avatar raredata avatar saltari avatar tamtamhero avatar tjoly-ledger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

app-xrp's Issues

Ripple Destination Tags Creation on LedgerNano or Ledger Blue

Hello,
I'm looking to find a way to create destination tags for accepting ripple in my account for Ledger Nano and Ledger Blue.

I tried to google and read the documents on ledger website but could not find the way to do it. Can you please help me over here to help me with the procedure to enable this option and create destination tags.

Thank you in advance for your support.

Ability To Sign Arbitrary Strings

When attempting to create an identity JWT for the wallet, which could be used for a backend, there is no way to sign the jwt payload. Using signTransaction returns an empty string.

Given a common JWT payload:

const payload = {
            "pk": ledgerAccount.publicKey,
            "sub": ledgerAccount.address,
            "exp": exp,
            "iat": d.getTime()
        }

and a ledger sign implementation with HID transport...

  // secp256k1 is the default curve used by XRP
  const signPayload = async (
    payload,
    accountIndex = 0, // change that to get details of accounts at different indexes
    keyIndex = 0, 
    ledgerTransport) => 
  {
    let transport = ledgerTransport
    if (!transport) {
      transport = await getLedgerTransport()
      if (!transport) {
        return undefined
      }
    }

    const xrp = new AppXrp(transport)
    console.log("sign payload", payload);

    try {
      const bip32Path = `44'/144'/${accountIndex}'/0/${keyIndex}`;
      const txe = encode(payload);
      const signedPayload = await xrp.signTransaction(bip32Path, txe);
      console.log("signTransaction signedPayload", signedPayload);
      return { signedPayload, bip32Path }
    } 
    catch (error) {
      if (error instanceof Error) {
        console.error(error)
        return
        // handleError(error)
      }
      return undefined
    }  
  }

an empty result is returned for the signed body.

{
    "signedPayload": "",
    "bip32Path": "44'/144'/0'/0/0"
}

Ledger device: Missing critical parameter (0x6800)

If I try to sign the following tx json it fails with Ledger device: Missing critical parameter (0x6800)

{
    Account: "OMMITED"
    Domain: "OMMITED"
    Fee: "12"
    Flags: 65536
    LastLedgerSequence: 67379294
    Sequence: 67377767
    SetFlag: 8
    SigningPubKey: "OMMITED"
    TickSize: 6
    TransactionType: "AccountSet"
    TransferRate: 1030000000
}

If I remove the Flags parameter it succeeds in the signature but fails to decode correctly.

Any Ideas?

Support 'TrustSet' transaction type

The Ledger XRP app currently supports nothing but payment transactions. Which promotes insecure practices such as entering ones mnemonic words in third-party tools, in order to sign trust line transactions.

In other words, Ledger Nano S/Nano/Blue users currently have no secure way to participate in XRP ledger airdrops and trade on the XRP ledger's decentralized exchange.

References:
https://developers.ripple.com/transaction-types.html
https://developers.ripple.com/trustset.html

Support 'OfferCreate' transaction type for packages/hw-app-xrp

Right now SignTransaction only supports 'Payment' transaction types for XRP. Hoping to add support for at least 'OfferCreate' transaction types. Seems like the second most important one after payments. I started working on this but I suppose I should see if anyone is already working on this. Or letting me know how hard it would be?

This is definitely out of my comfort zone so if anyone has any docs they could point me to so I could understand where to even begin

https://developers.ripple.com/transaction-types.html

Attempting to add support for InvoiceID, failing to parse transaction though unclear why

I am ignorant of C in general, and embedded C in particular. But I've attempted to add support for signing Ripple transactions containing InvoiceID anyway. As far as I can tell this should work, but instead it fails to parse for some reason that I have not yet been able to discover.

I'm sure my error is obvious and clear to everyone else, but right now I lack the eyes to see it.

Here's a git applyable patch for xrpParse.c that accepts a Ripple transaction with an InvoiceID as valid input for handleSign:
invoice-id-patch.txt

diff --git a/src/xrpParse.c b/src/xrpParse.c
index 534ab2e..c29143c 100644
--- a/src/xrpParse.c
+++ b/src/xrpParse.c
@@ -22,6 +22,7 @@
 #define STI_AMOUNT 0x06
 #define STI_VL 0x07
 #define STI_ACCOUNT 0x08
+#define STI_HASH256 0x05

 #define XRP_UINT16_TRANSACTION_TYPE 0x02
 #define XRP_UINT32_FLAGS 0x02
@@ -34,6 +35,7 @@
 #define XRP_VL_SIGNING_PUB_KEY 0x03
 #define XRP_ACCOUNT_ACCOUNT 0x01
 #define XRP_ACCOUNT_DESTINATION 0x03
+#define XRP_HASH256_INVOICE_ID 0x11

 void parse_xrp_amount(uint64_t *value, uint8_t *data) {
     *value = ((uint64_t)data[7]) | ((uint64_t)data[6] << 8) |
@@ -214,6 +216,44 @@ error:
     return result;
 }

+parserStatus_e processHash256(uint8_t *data, uint32_t length,
+                              txContent_t *context, uint32_t *offsetParam) {
+    parserStatus_e result = USTREAM_FAULT;
+    uint32_t offset = *offsetParam;
+    uint8_t fieldId = data[offset] & 0x0f;
+    uint8_t dataLength = 32;
+    if ((offset + 1 + dataLength) > length) {
+        result = USTREAM_PROCESSING;
+        goto error;
+    }
+    offset++;
+    switch (fieldId) {
+    case 0: {
+        uint8_t fieldId2 = data[offset];
+        if ((offset + 1 + dataLength) > length) {
+            result = USTREAM_PROCESSING;
+            goto error;
+        }
+        offset++;
+        switch (fieldId2) {
+        case XRP_HASH256_INVOICE_ID:
+            // TODO : display invoiceId on device for confirmation
+            break;
+        default:
+            goto error;
+        }
+        break;
+    }
+
+    default:
+        goto error;
+    }
+    *offsetParam = offset + dataLength;
+    result = USTREAM_FINISHED;
+error:
+    return result;
+}
+
 parserStatus_e parseTxInternal(uint8_t *data, uint32_t length,
                                txContent_t *context) {
     uint32_t offset = 0;
@@ -239,6 +279,9 @@ parserStatus_e parseTxInternal(uint8_t *data, uint32_t length,
         case STI_ACCOUNT:
             result = processAccount(data, length, context, &offset);
             break;
+        case STI_HASH256:
+            result = processHash256(data, length, context, &offset);
+            break;
         default:
             goto error;
         }

As you can see it merely detects the field is present, and moves the offset.

But it still throws a 6a80 because the parse result is not USTREAM_FINISHED by the time parseTx is done, and I can't see why. I've verified that processHash256 merely catches the field and moves the offset, by throwing the next byte in the status message in a kind of primitive console.log-style debugging session. I don't see why there would be additional side effects.

If I let handleSign ignore the invalid parse status, the device will happily ask the user for confirmation, and the fields all appear to be correct on the device's display including fields that come after the InvoiceID in the tx blob, but that produces a different kind of failure by sending more IO that I also don't understand.

I've been testing with @ledgerhq/hw-app-xrp and a nano s.

Here's the debug output of signing a transaction with no InvoiceID.

txJSON:
{ TransactionType: 'Payment',
  Account: 'r...',
  Destination: 'r...',
  Amount: '1',
  Flags: 2147483648,
  LastLedgerSequence: 6242469,
  Fee: '12',
  Sequence: 2,
  SigningPubKey: '03...' }
  
txBLOB:
12000022800000002400000002201B005F40A561400000000000000168400000000000000C732103FBA31B07B997C7C7E72AE378FCA376265B64E5190EFFEA0D2872FB270324205381148C26A748819CBD05D5E3205E51801D9B409C00BC8314000000000000000000000000000000015F953B5B

=>0101050000008ee004004089058000002c8000009080000000000000000000000012000022800000002400000002201b005f40a5614000000000000001684000
=>010105000100000000000c732103fba31b07b997c7c7e72ae378fca376265b64e5190effea0d2872fb270324205381148c26a748819cbd05d5e3205e51801d9b
=>0101050002409c00bc8314000000000000000000000000000000015f953b5b000000000000000000000000000000000000000000000000000000000000000000

<=010105000000483044022053efbfca23608d501036728ae21ce1d959612d32c13684eaddd2696b20899fda02203b375a5506ca09d2570d022b51b67316916662
<=0101050001e9282b22e78216fd8c0a4df52990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Here's the debug output of attempting to sign the same transaction with an InvoiceID, which fails to parse, despite my additional code appearing to be valid.

txJSON:
{ TransactionType: 'Payment',
  Account: 'r...',
  Destination: 'r...',
  Amount: '1',
  Flags: 2147483648,
  InvoiceID: '72B3BBCD268B2B838E19B9073239D06811CF2B1CBA14CFF071FAB5434BA5C979',
  LastLedgerSequence: 6242469,
  Fee: '12',
  Sequence: 2,
  SigningPubKey: '03...' }
  
txBLOB:
12000022800000002400000002201B005F40A5501172B3BBCD268B2B838E19B9073239D06811CF2B1CBA14CFF071FAB5434BA5C97961400000000000000168400000000000000C732103FBA31B07B997C7C7E72AE378FCA376265B64E5190EFFEA0D2872FB270324205381148C26A748819CBD05D5E3205E51801D9B409C00BC8314000000000000000000000000000000015F953B5B

=>0101050000009be004004096058000002c8000009080000000000000000000000012000022800000002400000002201b005f40a5501172b3bbcd268b2b838e19
=>0101050001b9073239d06811cf2b1cba14cff071fab5434ba5c97961400000000000000168400000000000000c732103fba31b07b997c7c7e72ae378fca37626
=>01010500025b64e5190effea0d2872fb270324205381148c26a748819cbd05d5e3205e51801d9b409c00bc830000000000000000000000000000000000000000

<=010105000000026a8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Here's the debug output of attempting to sign a similar transaction (merely differs by LastLedgerSequence) with an InvoiceID, ignoring the parse error, seemingly returning a valid signature, but then continuing with some additional IO that causes the entire apdu exchange to fail.

txJSON:
{ TransactionType: 'Payment',
  Account: 'r...',
  Destination: 'r...',
  Amount: '1',
  Flags: 2147483648,
  InvoiceID: '72B3BBCD268B2B838E19B9073239D06811CF2B1CBA14CFF071FAB5434BA5C979',
  LastLedgerSequence: 6242393,
  Fee: '12',
  Sequence: 2,
  SigningPubKey: '03...' }

txBLOB:  
12000022800000002400000002201B005F4059501172B3BBCD268B2B838E19B9073239D06811CF2B1CBA14CFF071FAB5434BA5C97961400000000000000168400000000000000C732103FBA31B07B997C7C7E72AE378FCA376265B64E5190EFFEA0D2872FB270324205381148C26A748819CBD05D5E3205E51801D9B409C00BC8314000000000000000000000000000000015F953B5B


=>0101050000009be004004096058000002c8000009080000000000000000000000012000022800000002400000002201b005f4059501172b3bbcd268b2b838e19
=>0101050001b9073239d06811cf2b1cba14cff071fab5434ba5c97961400000000000000168400000000000000c732103fba31b07b997c7c7e72ae378fca37626
=>01010500025b64e5190effea0d2872fb270324205381148c26a748819cbd05d5e3205e51801d9b409c00bc830000000000000000000000000000000000000000

Confirmation display is accurate on the device.

<=01010500000048304402205c3fcda39f693525160413f763be485c1cc7a0fc7deecbf3dd11a183acc57f2c0220767f7b863c8352a4f6dfcda161999baa5b06d2
<=01010500017e9534560ea5ea5dcdb63a42ff90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Seems like a successful signTransaction response.

=>0101050000001ae00480401514000000000000000000000000000000015f953b5b00000000000000000000000000000000000000000000000000000000000000

Where is this additional send coming from?

<=010105000000026b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Here's a 6b00, invalid input response, presumably specifically referring to the send immediately above.

I appreciate any assistance.

When approving a payment transaction, display currency-code in both HEX and ASCII

In the XRPL, there are two different ways to assemble a currency-code. The first way (ISO standard codes) support only three-character ASCII strings (e.g., USD); the other way (non-standard, but still legitimate) is a 160-bit HEX-string value (for codes longer than 3 ASCII characters).

I propose that when using the Nano to sign a payment transaction containing a non-standard currency code, the device will display both the HEX and the ASCII equivalent of the currency code.

Displaying the HEX string is correct IMO (i.e., not every HEX string maps to valid ASCII) and likely just good security practice (i.e., we want users to see the real bytes for what they're about to sign). However, displaying 160-bit HEX strings is a sub-optimal user-experience (if that's all that is displayed), especially given that many issued currency codes are actually just ASCII strings that have more than 3 characters (and are thus non-standard).

For example, I envision something like this scrolling across the device's display, as part of payment transaction signature approval:

5553445400000000000000000000000000000000 - USDT

Curious to hear thoughts around this proposal.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.