From 76af3f322066cee2a55301ae32d48b91f9ea9691 Mon Sep 17 00:00:00 2001 From: mstorozhenko-ledger Date: Fri, 25 Oct 2024 12:08:06 +0100 Subject: [PATCH 1/2] chore: added code examples and fixed error response --- pages/docs/ledger-live/exchange/card/_meta.js | 3 +- .../exchange/card/code-examples.mdx | 9 ++ .../exchange/card/providers-backend.mdx | 2 + pages/docs/ledger-live/exchange/sell/_meta.js | 3 +- .../exchange/sell/code-examples.mdx | 9 ++ .../exchange/sell/providers-backend.mdx | 2 + public/exchange/import/code-examples.mdx | 100 ++++++++++++++++++ .../openapi/card-provider-openapi.yml | 3 + .../openapi/sell-provider-openapi.yml | 22 ++-- 9 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 pages/docs/ledger-live/exchange/card/code-examples.mdx create mode 100644 pages/docs/ledger-live/exchange/sell/code-examples.mdx create mode 100644 public/exchange/import/code-examples.mdx diff --git a/pages/docs/ledger-live/exchange/card/_meta.js b/pages/docs/ledger-live/exchange/card/_meta.js index 2c98c1a7..29dd5248 100644 --- a/pages/docs/ledger-live/exchange/card/_meta.js +++ b/pages/docs/ledger-live/exchange/card/_meta.js @@ -1,5 +1,6 @@ export default { 'providers-backend': "Backend", 'providers-liveapp': "LiveApp", - 'providers-test-and-submit': "Test & Submit" + 'providers-test-and-submit': "Test & Submit", + 'code-examples': "Code Examples" } \ No newline at end of file diff --git a/pages/docs/ledger-live/exchange/card/code-examples.mdx b/pages/docs/ledger-live/exchange/card/code-examples.mdx new file mode 100644 index 00000000..aacfb1a6 --- /dev/null +++ b/pages/docs/ledger-live/exchange/card/code-examples.mdx @@ -0,0 +1,9 @@ +import Zoom from 'react-medium-image-zoom' +import 'react-medium-image-zoom/dist/styles.css' +import { Tabs } from 'nextra/components' +import { Callout } from 'nextra/components' +import CodeExamples from '../../../../../public/exchange/import/code-examples.mdx' + +# Backend + + diff --git a/pages/docs/ledger-live/exchange/card/providers-backend.mdx b/pages/docs/ledger-live/exchange/card/providers-backend.mdx index 5e6db732..fbcef854 100644 --- a/pages/docs/ledger-live/exchange/card/providers-backend.mdx +++ b/pages/docs/ledger-live/exchange/card/providers-backend.mdx @@ -127,6 +127,8 @@ Here is a little diagram to explain how the `payload` and the `signature` are ge - `payload`: the trade parameters are assembled in a [protobuf](https://developers.google.com/protocol-buffers) message. Then using the protobuf tools we do a [binary encoding](https://developers.google.com/protocol-buffers/docs/encoding) of the protobuf (Byte Array). Finally, with [base64Url encoding](https://en.wikipedia.org/wiki/Base64) we get the `payload` field. - `signature`: From the [base64Url](https://en.wikipedia.org/wiki/Base64) encoded payload of the previous [protobuf](https://developers.google.com/protocol-buffers) (Byte Array), we sign it with [ES256](https://ldapwiki.com/wiki/Wiki.jsp?page=ES256) and the provider's private key to get a Signature Byte Array. Finally, with [base64Url encoding](https://en.wikipedia.org/wiki/Base64) we get the `signature` field ([more details](#jws-signature)). +If you need help with the signing process, please refer to our code [examples](code-examples.mdx). + ### Signature usage - Payload and Signature diff --git a/pages/docs/ledger-live/exchange/sell/_meta.js b/pages/docs/ledger-live/exchange/sell/_meta.js index 2c98c1a7..29dd5248 100644 --- a/pages/docs/ledger-live/exchange/sell/_meta.js +++ b/pages/docs/ledger-live/exchange/sell/_meta.js @@ -1,5 +1,6 @@ export default { 'providers-backend': "Backend", 'providers-liveapp': "LiveApp", - 'providers-test-and-submit': "Test & Submit" + 'providers-test-and-submit': "Test & Submit", + 'code-examples': "Code Examples" } \ No newline at end of file diff --git a/pages/docs/ledger-live/exchange/sell/code-examples.mdx b/pages/docs/ledger-live/exchange/sell/code-examples.mdx new file mode 100644 index 00000000..aacfb1a6 --- /dev/null +++ b/pages/docs/ledger-live/exchange/sell/code-examples.mdx @@ -0,0 +1,9 @@ +import Zoom from 'react-medium-image-zoom' +import 'react-medium-image-zoom/dist/styles.css' +import { Tabs } from 'nextra/components' +import { Callout } from 'nextra/components' +import CodeExamples from '../../../../../public/exchange/import/code-examples.mdx' + +# Backend + + diff --git a/pages/docs/ledger-live/exchange/sell/providers-backend.mdx b/pages/docs/ledger-live/exchange/sell/providers-backend.mdx index eca5cff9..a856e695 100644 --- a/pages/docs/ledger-live/exchange/sell/providers-backend.mdx +++ b/pages/docs/ledger-live/exchange/sell/providers-backend.mdx @@ -130,6 +130,8 @@ Here is a little diagram to explain how the `payload` and the `signature` are ge - `payload`: the trade parameters are assembled in a [protobuf](https://developers.google.com/protocol-buffers) message. Then using the protobuf tools we do a [binary encoding](https://developers.google.com/protocol-buffers/docs/encoding) of the protobuf (Byte Array). Finally, with [base64Url encoding](https://en.wikipedia.org/wiki/Base64) we get the `payload` field. - `signature`: From the [base64Url encoding](https://en.wikipedia.org/wiki/Base64) encoded payload of the previous [protobuf](https://developers.google.com/protocol-buffers) (Byte Array), we sign it with [ES256](https://ldapwiki.com/wiki/Wiki.jsp?page=ES256) and the provider's private key to get a Signature Byte Array. Finally, with [base64Url encoding](https://en.wikipedia.org/wiki/Base64) we get the `signature` field ([more details](#jws-signature)). +If you need help with the signing process, please refer to our code [examples](code-examples.mdx). + ### Signature usage - Payload and Signature diff --git a/public/exchange/import/code-examples.mdx b/public/exchange/import/code-examples.mdx new file mode 100644 index 00000000..b5b5918d --- /dev/null +++ b/public/exchange/import/code-examples.mdx @@ -0,0 +1,100 @@ +# Java +```java +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import java.security.*; +import java.util.Base64; + +public class App { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public static void main(String[] args) throws Exception { + byte[] payloadBytes = response.toByteArray(); + String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadBytes); + PrivateKey key = getPrivateKeyFromPEMFile(""); + byte[] sign = signPayload(("." + payload).getBytes(), key); + String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(sign); + } + + public static byte[] signPayload(byte[] payloadBytes, PrivateKey privateKey) throws Exception { + Signature signer = Signature.getInstance("SHA256withPlain-ECDSA", "BC"); + signer.initSign(privateKey); + signer.update(payloadBytes); + return signer.sign(); + } +} +``` + +# Python +```python +import base64 +import os +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import hashes +from google.protobuf import message +import sell_pb2 # Import the generated protobuf classes +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature + + +def base64_url_encode(data): + return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8') + + +def sign_payload(payload, private_key): + # Sign the payload using ECDSA with SHA-256 + der_signature = private_key.sign( + payload, + ec.ECDSA(hashes.SHA256()) + ) + # Decode the DER-encoded signature to get r and s + r, s = decode_dss_signature(der_signature) + # Convert r and s to bytes + r_bytes = r.to_bytes(32, byteorder='big') + s_bytes = s.to_bytes(32, byteorder='big') + return r_bytes + s_bytes + + +if __name__ == "__main__": + payload = response.SerializeToString() + encoded_payload = base64_url_encode(payload) + # Sign the payload + signature = sign_payload(("." + encoded_payload).encode('utf-8'), private_key) + # Base64 URL encode the signature + encoded_signature = base64_url_encode(signature) + provider_sig = { + "payload": encoded_payload, + "signature": encoded_signature + } +``` + +# typescript +```typescript +const base64EncodedPayload = base64url.encode( + Buffer.from(uInt8ArrayEncodedPayload), // this is encoded payload using protobuf +); + +function signProviderSignaturePayload(base64EncodedPayload: string): string { + const ec = new EC('secp256k1'); + const keyPair = ec.keyFromPrivate( + , + 'hex', + ); + const hashedMessage = crypto + .createHash('sha256') + .update(`.${base64EncodedPayload}`) + .digest(); + const signature = keyPair.sign(hashedMessage); + + const r = signature.r.toString('hex').padStart(64, '0'); + const s = signature.s.toString('hex').padStart(64, '0'); + + const signatureBuffer = Buffer.from(r + s, 'hex'); + const signatureBase64 = signatureBuffer.toString('base64url'); + + return signatureBase64; +} +``` \ No newline at end of file diff --git a/public/exchange/openapi/card-provider-openapi.yml b/public/exchange/openapi/card-provider-openapi.yml index 07e8f265..0cee8373 100644 --- a/public/exchange/openapi/card-provider-openapi.yml +++ b/public/exchange/openapi/card-provider-openapi.yml @@ -233,6 +233,9 @@ components: description: Payload signed in JWS format. ErrorPayload: type: object + required: + - messageKey + - message properties: messageKey: type: string diff --git a/public/exchange/openapi/sell-provider-openapi.yml b/public/exchange/openapi/sell-provider-openapi.yml index 3f319950..0e3a8b9d 100644 --- a/public/exchange/openapi/sell-provider-openapi.yml +++ b/public/exchange/openapi/sell-provider-openapi.yml @@ -72,7 +72,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ProviderServerError' + $ref: '#/components/schemas/ErrorPayload' example: code: 400 message: Server isn't able to handle the request because of whatever. @@ -161,13 +161,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ProviderServerError' + $ref: '#/components/schemas/ErrorPayload' default: description: Error sent by the provider content: application/json: schema: - $ref: '#/components/schemas/ProviderServerError' + $ref: '#/components/schemas/ErrorPayload' /crypto-currencies: get: operationId: getCrypto-currencies @@ -201,7 +201,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ProviderServerError' + $ref: '#/components/schemas/ErrorPayload' example: code: 42 message: Server isn't able to handle the request because of whatever. @@ -512,17 +512,6 @@ components: expiry: type: string description: Quote expiration timestamp - ProviderServerError: - required: - - code - - message - type: object - properties: - code: - type: integer - format: int32 - message: - type: string Sepa: type: object Token: @@ -614,6 +603,9 @@ components: description: Payload signed in JWS format. ErrorPayload: type: object + required: + - message + - messageKey properties: messageKey: type: string From b39e14039b8c0e5381153f1eb28c0317bd796567 Mon Sep 17 00:00:00 2001 From: mstorozhenko-ledger Date: Fri, 25 Oct 2024 15:50:04 +0100 Subject: [PATCH 2/2] chore: added type param to exchangeSdk --- pages/docs/ledger-live/exchange/card/providers-liveapp.mdx | 1 + pages/docs/ledger-live/exchange/sell/providers-liveapp.mdx | 1 + 2 files changed, 2 insertions(+) diff --git a/pages/docs/ledger-live/exchange/card/providers-liveapp.mdx b/pages/docs/ledger-live/exchange/card/providers-liveapp.mdx index 90bbdd11..bf9ff604 100644 --- a/pages/docs/ledger-live/exchange/card/providers-liveapp.mdx +++ b/pages/docs/ledger-live/exchange/card/providers-liveapp.mdx @@ -36,6 +36,7 @@ This method will need you to provide the following parameters: fromAmount: new BigNumber(1), toFiat: "EUR", // provider fiat id rate: 90000, // crypto/fiat rate [BTC/EUR] + type: "card" }); ``` diff --git a/pages/docs/ledger-live/exchange/sell/providers-liveapp.mdx b/pages/docs/ledger-live/exchange/sell/providers-liveapp.mdx index 88df96bb..1e14a912 100644 --- a/pages/docs/ledger-live/exchange/sell/providers-liveapp.mdx +++ b/pages/docs/ledger-live/exchange/sell/providers-liveapp.mdx @@ -37,6 +37,7 @@ This method will need you to provide the following parameters: fromAmount: new BigNumber(1), toFiat: "EUR", // provider fiat id rate: 90000, // crypto/fiat rate [BTC/EUR] + type: "sell" }); ```