Skip to content

Commit

Permalink
chore: added code examples and fixed error response
Browse files Browse the repository at this point in the history
  • Loading branch information
mstorozhenko-ledger committed Oct 25, 2024
1 parent a4df231 commit 76af3f3
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 17 deletions.
3 changes: 2 additions & 1 deletion pages/docs/ledger-live/exchange/card/_meta.js
Original file line number Diff line number Diff line change
@@ -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"
}
9 changes: 9 additions & 0 deletions pages/docs/ledger-live/exchange/card/code-examples.mdx
Original file line number Diff line number Diff line change
@@ -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

<CodeExamples />
2 changes: 2 additions & 0 deletions pages/docs/ledger-live/exchange/card/providers-backend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion pages/docs/ledger-live/exchange/sell/_meta.js
Original file line number Diff line number Diff line change
@@ -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"
}
9 changes: 9 additions & 0 deletions pages/docs/ledger-live/exchange/sell/code-examples.mdx
Original file line number Diff line number Diff line change
@@ -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

<CodeExamples />
2 changes: 2 additions & 0 deletions pages/docs/ledger-live/exchange/sell/providers-backend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 100 additions & 0 deletions public/exchange/import/code-examples.mdx
Original file line number Diff line number Diff line change
@@ -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("<path to your private key>");
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(
<PRIVATE_KEY_GOES_HERE>,
'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;
}
```
3 changes: 3 additions & 0 deletions public/exchange/openapi/card-provider-openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ components:
description: Payload signed in JWS format.
ErrorPayload:
type: object
required:
- messageKey
- message
properties:
messageKey:
type: string
Expand Down
22 changes: 7 additions & 15 deletions public/exchange/openapi/sell-provider-openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -614,6 +603,9 @@ components:
description: Payload signed in JWS format.
ErrorPayload:
type: object
required:
- message
- messageKey
properties:
messageKey:
type: string
Expand Down

0 comments on commit 76af3f3

Please sign in to comment.