Author SHA1 Message Date
Felicio Mununga fd6c9dddf6 hotfix: copy 2025-02-28 13:14:42 +01:00
Felicio Mununga deef802fd0 hotfix: copy 2025-02-28 11:04:59 +01:00
Jakub Kotula 200f6b63bd Remove tooltips (#44) 2025-02-27 11:07:53 +01:00
Jakub Kotula 79773c0b1e Fix accordion chevron hover (#42) 2025-02-26 12:00:31 +01:00
Jakub Kotula d9ce1f3b56 v2 (#40) 2025-02-25 16:26:03 +01:00
marcelines 0b666ba5bf Check Webgl support (#38) 2025-01-16 14:04:46 +00:00
Jakub Kotula adec63d51c Copyright as a client component (#37) 2025-01-08 13:22:22 +01:00
Pavel be68e34701 update Status features 2024-12-20 14:43:47 +01:00
Pavel 45b608a45c fix 404 copy 2024-12-19 15:56:21 +01:00
Pavel 0fd7e2310a fix features slider 2024-12-18 17:58:06 +01:00
Pavel 59298982ef fix gradient 2024-12-18 17:42:59 +01:00
pavel 22f3362609 fix opengraph image (#36) 2024-12-18 15:34:35 +01:00
f58fb1b0ab Release (#5)
Co-authored-by: marcelines <marcio@status.im>
Co-authored-by: Jakub Kotula <520927+jkbktl@users.noreply.github.com>
Co-authored-by: Pavel <14926950+prichodko@users.noreply.github.com>
Co-authored-by: guylouis <guylouis@status.im>
2024-12-18 13:15:36 +01:00
294 changed files with 17153 additions and 4217 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
],
"rules": {
"no-constant-binary-expression": "error",
"no-restricted-globals": ["error", "process"],
"no-restricted-globals": ["off", "process"],
"tailwindcss/classnames-order": "off", // handled by official prettier-plugin-tailwindcss
"tailwindcss/migration-from-tailwind-2": "off"
},
+1 -1
View File
@@ -1 +1 @@
pnpx lint-staged
pnpm dlx lint-staged
+1
View File
@@ -0,0 +1 @@
20.18.0
+7
View File
@@ -0,0 +1,7 @@
; https://pnpm.io/npmrc#node-options
; why: https://github.com/vercel/next.js/discussions/70423
node-options=--network-family-autoselection-attempt-timeout=1000
; https://pnpm.io/npmrc#node-version
node-version=20.18.0
; https://pnpm.io/npmrc#engine-strict
engine-strict=true
+9
View File
@@ -0,0 +1,9 @@
node_modules/
.next/
public/
*.mdx
*.md
pnpm-lock.yaml
+2 -1
View File
@@ -2,6 +2,7 @@
"recommendations": [
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"graphql.vscode-graphql"
]
}
+43
View File
@@ -0,0 +1,43 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3030",
"skipFiles": [
".next/**",
"${workspaceFolder}/node_modules/**",
"<node_internals>/**",
"**/webpack-internal://**"
]
// "runtimeArgs": ["--auto-open-devtools-for-tabs"]
// "cascadeTerminateToConfigurations": ["Next.js: debug full stack"]
},
// todo: consider compound config https://code.visualstudio.com/docs/editor/debugging#_compound-launch-configurations
// todo: consider client+prelaunch
{
"name": "Next.js: debug full stack",
// note: config https://github.com/microsoft/vscode-js-debug/blob/e620efe1841742402a332b532df2aa2ec95361f6/OPTIONS.md
"type": "node-terminal",
"request": "launch",
"command": "NODE_OPTIONS='' pnpm dev --port 3030",
"serverReadyAction": {
"pattern": "\\s- Local:\\s+(https?://\\S+)",
"action": "startDebugging",
"name": "Next.js: debug client-side",
"killOnServerStop": true
},
// "cascadeTerminateToConfigurations": ["Next.js: debug client-side"],
"skipFiles": [
".next/**",
"${workspaceFolder}/node_modules/**",
"<node_internals>/**"
]
}
]
}
+35
View File
@@ -0,0 +1,35 @@
---
id: apdu_changelog
title: CHANGELOG
---
# Changelog
## Version 3.1
* Added IDENT command to Keycard an Cash applet
* Added Ident applet
* Added ability to export the extended public key
* Added duress PIN
* The INIT command now optionally accepts the max number of PIN, PUK retries and the duress PIN
## Version 3.0
* **BREAKING** Removed the DUPLICATE KEY command
* **BREAKING** Removed SET NDEF command (replaced by STORE DATA)
* Added Cash applet for payments/burner wallet scenarios
* STORE DATA/GET DATA can now be used also for NDEF and Cash data
## Version 2.2
* Added additional options to the SIGN command to facilitate usage in POS transactions.
* Make SET NDEF more tolerant on the input format by automatically appending missing length.
## Version 2.1
* Added concept of capabilities, making some APDU conditional and extending the SELECT response.
## Version 2.0
* **BREAKING** Changed application AID
* **BREAKING** Completely redefined the EXPORT KEY command
* **BREAKING** Removed assisted key derivation
* **BREAKING** Removed plain data signing, now only 32-byte long hashes can be signed
* Added internal key generation (GENERATE KEY)
* Added the ability to customize the NDEF response (SET NDEF)
* Added DUPLICATE KEY command
+22
View File
@@ -0,0 +1,22 @@
---
id: apdu_changepin
title: CHANGEPIN
---
# CHANGE PIN
* CLA = 0x80
* INS = 0x21
* P1 = PIN identifier
* P2 = 0x00
* Data = the new PIN
* Response SW = 0x9000 on success, 0x6A80 if the PIN format is invalid, 0x6A86 if P1 is invalid
* Preconditions: Secure Channel must be opened, user PIN must be verified
* Capability: Credentials management
Used to change a PIN or secret. In case of invalid format, the code 0x6A80 is returned. If the conditions match, the PIN or secret is updated. The no-error SW 0x9000 is returned.
P1:
* 0x00: User PIN. Must be 6-digits. The updated PIN is authenticated for the rest of the session.
* 0x01: Applet PUK. Must be 12-digits.
* 0x02: Pairing secret. Must be 32-bytes long. Existing pairings are not broken, but new pairings will need to use the new secret. Only implemented if the Secure Channel capability is implemented.
+26
View File
@@ -0,0 +1,26 @@
---
id: apdu_derivekey
title: DERIVE KEY
---
# DERIVE KEY
* CLA = 0x80
* INS = 0xD1
* P1 = derivation options
* P2 = 0x00
* Data = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used.
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid.
* Preconditions: Secure Channel must be opened, user PIN must be verified, an extended keyset must be loaded
This command sets the derivation path used for subsequent SIGN and EXPORT KEY commands without an explicit path. The maximum depth of derivation from the master key is 10. Any attempt to get deeper results in 0x6A80 being returned.
Using this command is now discouraged. It is recommended to explicitly give the derivation path in each SIGN and EXPORT KEY command.
P1:
* bit 0-5 = reserved
* bit 7-6:
- 00 derive from master keys
- 01 derive from parent keys
- 10 derive from current keys
- 11 reserved
+38
View File
@@ -0,0 +1,38 @@
---
id: apdu_exportkey
title: EXPORT KEY
---
# EXPORT KEY
* CLA = 0x80
* INS = 0xC2
* P1 = derivation options
* P2 = export options
* Response SW = 0x9000 on success, 0x6A86 if P1 or P2 are wrong
* Data = a sequence of 32-bit integers (empty if P1=0x00)
* Response Data = key pair template
* Response SW = 0x9000 on success, 0x6985 if the private key cannot be exported, 0x6A80 if the path is malformed
* Preconditions: Secure Channel must be opened, user PIN must be verified
P1:
0x00 = Current key
0x01 = Derive
0x02 = Derive and make current
P2:
0x00 = private and public key
0x01 = public key only
0x02 = extended public key
Response Data format:
- Tag 0xA1 = keypair template
- Tag 0x80 = ECC public key component (omitted if P2=0x00)
- Tag 0x81 = ECC private key component (if P2=0x00)
- Tag 0x82 = Chain code (if P2=0x02)
This command exports the requested public and private key. The public key can be always exported (P2=0x01), but the private key (P2=0x00) can be exported if and only if the requested key path is in the [EIP-1581](https://eips.ethereum.org/EIPS/eip-1581) subtree. The extended public key (P2=0x02) can be exported for any path except those in the EIP-1581 subtree.
The P1 parameter indicates how to the derive the desired key. P1 = 0x00 indicates that the current key must be exported, and no derivation will be performed. P1 = 0x01 derives the path given in the data field without changing the current path of the card. P1 = 0x02 derives the path but also changes the current path of the card. The source for derivation can be set by OR'ing P1 with the constants defined in the DERIVE KEY command. This allows deriving from master, parent or current.
If the private key is being exported, the card could omit exporting the public key for performance reason. The public key can then be calculate off-card if needed.
+17
View File
@@ -0,0 +1,17 @@
---
id: apdu_generatekey
title: GENERATE KEY
---
# GENERATE KEY
* CLA = 0x80
* INS = 0xD4
* P1 = 0x00
* P2 = 0x00
* Response SW = 0x9000 on success.
* Response Data = the key UID, defined as the SHA-256 of the public key
* Preconditions: Secure Channel must be opened, user PIN must be verified
* Capability: Key management
Generates and stores keys completely on card. The state of the card after execution is the same as if a LOAD KEY command had been performed.
+17
View File
@@ -0,0 +1,17 @@
---
id: apdu_generatemnemonic
title: GENERATE MNEMONIC
---
# GENERATE MNEMONIC
* CLA = 0x80
* INS = 0xD2
* P1 = checksum size (between 4 and 8)
* P2 = 0x00
* Response SW = 0x9000 on success. 0x6A86 if P1 is invalid.
* Response Data = a sequence of 16-bit integers (most significant byte first).
* Preconditions: Secure Channel must be opened
* Capability: Key management
Used to generate a mnemonic according to the algorithm specified in [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). The returned data is a list of 16-byte integers which should be used as indexes in a wordlist to generate the human-readable mnemonic. Each integer can have a value from 0 to 2047.
+16
View File
@@ -0,0 +1,16 @@
---
id: apdu_getdata
title: GET DATA
---
# GET DATA
* CLA = 0x80
* INS = 0xCA
* P1 = 0x00 for public data, 0x01 for NDEF data, 0x02 for Cash data
* P2 = 0x00
* Response SW = 0x9000 on success, 0x6A86 on undefined P1
* Response Data = the data as previously stored by STORE DATA command
* Capabilities: NDEF for P1 = 0x01
Notably, this command does not require an open secure channel
+26
View File
@@ -0,0 +1,26 @@
---
id: apdu_getstatus
title: GET STATUS
---
# GET STATUS
* CLA = 0x80
* INS = 0xF2
* P1 = 0x00 for application status, 0x01 for key path status
* P2 = 0x00
* Response SW = 0x9000 on success, 0x6A86 on undefined P1
* Response Data = Application Status Template or Key Path
* Preconditions: Secure Channel must be opened
Response Data format:
if P1 = 0x00:
- Tag 0xA3 = Application Status Template
- Tag 0x02 = PIN retry count (1 byte)
- Tag 0x02 = PUK retry count (1 byte)
- Tag 0x01 = 0xff if key is initialized, 0 otherwise
This must be implemented even if Credential and/or Key management is not implemented. If the PIN/PUK retry count is not available or is not applicable, its value should be set to 0xff.
if P1 = 0x01:
- a sequence of 32-bit numbers indicating the current key path. Empty if master key is selected.
+27
View File
@@ -0,0 +1,27 @@
---
id: apdu_ident
title: IDENT
---
# IDENT
* CLA = 0x80
* INS = 0x14
* P1 = 0x00
* P2 = 0x00
* Data = a 256-bit random challenge
* Response Data = the challenge's signature, followed by the card's certificate
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid.
Response Data format:
- Tag 0xA0 = signature template
- Tag 0x8A = Certificate
- Tag 0x30 = ECDSA Signature
- Tag 0x02 = R value
- Tag 0x02 = S value
Signs the given challenge using the card's identification private key and returns the card certificate followed by the generated signature. The certificate is composed by the concatenation of the card's identification public key in compressed form (33 bytes) and its signature r,s (32 bytes each) and recovery id (1 byte) generated by the certificate authority.
This command does not require PIN authentication or a Secure Channel to be established. It is strongly recommended that the client performs this identification step before initializing the card and/or before pairing with it. After a card has been paired it is not necessary to repeat the identification step.
To perform verification, first verify the challenge's signature using public key in the certificate. If the signature is correct, recover the CA's public key using the r,s, and recovery ID part of the certificate. If the public key matches that of a certificate authority you trust proceed to verify the signature, which has been calculated on the SHA-256 hash of the card's identification public key in the form returned in the certificate.
+32
View File
@@ -0,0 +1,32 @@
---
id: apdu_index
title: APDU API
---
# Summary
This document describes all APDUs part of the Keycard APDU protocol.
* [Overview](apdu/overview)
* [CHANGELOG](apdu/changelog)
* [SELECT](apdu/select)
* [INIT](apdu/init)
* [IDENT](apdu/ident)
* [OPEN SECURE CHANNEL](apdu/opensecurechannel)
* [MUTUALLY AUTHENTICATE](apdu/mutuallyauthenticate)
* [PAIR](apdu/pair)
* [UNPAIR](apdu/unpair)
* [GET STATUS](apdu/getstatus)
* [VERIFY PIN](apdu/verifypin)
* [CHANGE PIN](apdu/changepin)
* [UNBLOCK PIN](apdu/unblockpin)
* [LOAD KEY](apdu/loadkey)
* [DERIVE KEY](apdu/derivekey)
* [GENERATE MNEMONIC](apdu/generatemnemonic)
* [REMOVE KEY](apdu/removekey)
* [GENERATE KEY](apdu/generatekey)
* [SIGN](apdu/sign)
* [SET PINLESS PATH](apdu/setpinlesspath)
* [EXPORT KEY](apdu/exportkey)
* [STORE DATA](apdu/storedata)
* [KEY DATA](apdu/keydata)
+26
View File
@@ -0,0 +1,26 @@
---
id: apdu_init
title: INIT
---
# INIT
* CLA = 0x80
* INS = 0xFE
* P1 = 0x00
* P2 = 0x00
* Data = EC public key (LV encoded) | IV | encrypted payload
* Response SW = 0x9000 on success, 0x6D00 if the applet is already initialized, 0x6A80 if the data is invalid
* Capability: Credentials management
This command is only available when the applet is in pre-initialized state and successful execution brings the applet in the initialized state. This command is needed to allow securely storing secrets on the applet at a different moment and place than installation is taking place. Currently these are the PIN, PUK and pairing password.
The client must take the public key received after the SELECT command, generate a random keypair and perform EC-DH to generate an AES key. It must then generate a random IV and encrypt the payload using AES-CBC with ISO/IEC 9797-1 Method 2 padding.
They payload is the concatenation of the PIN (6 digits/bytes), PUK (12 digits/bytes) and, if the Secure Channel capability is implemented, pairing secret (32 bytes).
Additionally, since version 3.1 of the applet, the max number of PIN attempts (1 byte), the max number of PUK attempts (1 bytes) and a duress PIN (6 digits/bytes) can be specified.
If these are not specified, the default values will be respectively, 3, 5 and the first half of the PUK.
This scheme guarantees protection against passive MITM attacks. Since the applet has no "owner" before the execution of this command, protection against active MITM cannot be provided at this stage. However since the communication happens locally (either through NFC or contacted interface) the realization of such an attack at this point is unrealistic.
After successful execution, this command cannot be executed anymore. The regular SecureChannel (with pairing) is active and PIN and PUK are initialized.
+33
View File
@@ -0,0 +1,33 @@
---
id: apdu_loadkey
title: LOAD KEY
---
# LOAD KEY
* CLA = 0x80
* INS = 0xD0
* P1 = key type
* P2 = 0x00
* Data = the key data
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A86 if P1 is invalid
* Response Data = the key UID, defined as the SHA-256 of the public key
* Preconditions: Secure Channel must be opened, user PIN must be verified
* Capability: Key management
P1:
* 0x01 = ECC SECP256k1 keypair
* 0x02 = ECC SECP256k1 extended keypair
* 0x03 = Binary seed as defined in [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)
Data:
If P1 is 0x01 or 0x02
- Tag 0xA1 = keypair template
- Tag 0x80 = ECC public key component (can be omitted)
- Tag 0x81 = ECC private key component
- Tag 0x82 = chain code (if P1=0x02)
If P1 is 0x03 a 64 byte sequence generated according to the BIP39 specifications is expected. The master key will be generated according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specifications. This is only supported if the hardware supports public key derivation.
This command is used to load or replace the keypair used for signing on the card. The PIN-less path will be reset. Unless a DERIVE KEY is sent, a subsequent SIGN command will use this keypair for signature.
@@ -0,0 +1,17 @@
---
id: apdu_mutuallyauthenticate
title: MUTUALLY AUTHENTICATE
---
# MUTUALLY AUTHENTICATE
* CLA = 0x80
* INS = 0x11
* P1 = 0x00
* P2 = 0x00
* Data = 256-bit random number
* Response Data = 256-bit random number
* Response SW = 0x9000 on success, 0x6985 if the previous successfully executed APDU was not OPEN SECURE CHANNEL, 0x6982 if authentication failed or the data is not 256-bit long
* Capability: Secure Channel
This APDU allows both parties to verify that the keys generated in the OPEN SECURE CHANNEL step are matching and thus guarantee authentication of the counterpart. The data sent by both parties is a 256-bit random number The APDU data is sent encrypted with the keys generated in the OPEN SECURE CHANNEL step. Each party must verify the MAC of the received APDU. If the MAC can be verified, it means that both parties are using the same keys. Only after this step has been executed the secure channel can be considered to be open and other commands can be sent. If the authentication fails the card must respond with 0x6982. In this case the OPEN SECURE CHANNEL command must be repeated to generate new keys.
+57
View File
@@ -0,0 +1,57 @@
---
id: apdu_opensecurechannel
title: OPEN SECURE CHANNEL
---
# OPEN SECURE CHANNEL
* CLA = 0x80
* INS = 0x10
* P1 = the pairing index
* P2 = 0x00
* Data = An EC-256 public key on the SECP256k1 curve encoded as an uncompressed point.
* Response Data = A 256-bit salt and a 128-bit seed IV
* Response SW = 0x9000 on success, 0x6A86 if P1 is invalid, 0x6A80 if the data is not a public key
* Capability: Secure Channel
This APDU is the first step to establish a Secure Channel session. A session is aborted when the application is deselected, either directly or because of a card reset/tear.
The card generates a random 256-bit salt which is sent to the client. Both the client and the card do the following for key derivation
1. Use their private key and the counterpart public key to generate a secret using the EC-DH algorithm.
2. The generated secret, the pairing key and the salt are concatenated and the SHA-512 of the concatenated value is calculated.
3. The output of the SHA-512 algorithm is split in two parts of 256-bit. The first part is used as the encryption key and the second part is used as the MAC key for further communication.
The seed IV is used by the client as the IV for the next encrypted APDU.
## Encrypted APDUs
After a successful OPEN SECURE CHANNEL command all communication between card and client is encrypted. Note that only the data fields of C-APDU are encrypted, which means that CLA, INS, P1, P2 for C-APDU are plaintext. This means no sensitive data should be sent in these parameters. Additionally a MAC is calculated for the entire APDU, including the unencrypted fields.
Because R-APDU can only contain data if their SW is a success or warning status word (0x9000, 0x62XX, 0x63XX), when the secure channel is open all responses will have SW 0x9000. The actual SW is always appended at the end of the response data before encryption, which means the client must interpret the last two bytes of the plaintext response as the SW. An exception to this is SW 0x6982, which indicates that the SecureChannel has been aborted and as such is returned without any MAC.
To encrypt the data both the card and the client do the following:
1. The data is padded using the ISO/IEC 9797-1 Method 2 algorithm.
2. The data is encrypted using AES in CBC mode using the session key.
3. An AES CBC-MAC is calculated over the entire APDU data
4. The data field of the APDU is set to the MAC followed by the encrypted data.
To decrypt the data both the card and the client do the following:
1. The first 16 bytes of the APDU data are the MAC to be verified
2. The remaining data is decrypted using AES in CBC mode using the session key.
3. The padding is removed.
The IV used for the encryption is the last seen MAC from the counterpart. This optimizes the number of transmitted bytes and guarantees protection from replay attacks. For the MAC generation, a zero IV is always used.
MAC generation for C-APDUs is calculated on the concatenation of CLA INS P1 P2 LC 00 00 00 00 00 00 00 00 00 00 00 and the encrypted data field. The 11-byte long padding does not become part of the data field and does not affect LC
MAC generation fo R-APDUs is calculated on the concatenation of Lr 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 and the encrypted data field. The 15-byte long padding does not become part of the response field. Lr is the length of the encrypted response data field and is not transmitted.
Because AES in CBC mode requires the data field length in bytes to be a multiple of 16, the maximum effective APDU size becomes 240 bytes. Of these 16 bytes are used for the MAC and minimum of 1 byte for padding, making the maximum payload size in a single APDU 223 bytes, meaning about a 13,5% overhead.
## Error conditions
1. If a sensitive command is received without an active Secure Channel, the card shall respond with SW 0x6985 (SW_CONDITIONS_NOT_SATISFIED)
2. If a MAC cannot be verified the card shall respond 0x6982 and the Secure Channel must be closed
+48
View File
@@ -0,0 +1,48 @@
---
id: apdu_overview
title: APDU API Overview
---
# Overview
This document describes all APDUs part of the Keycard APDU protocol. Any implementation must fully implement this specification except for items explicitly marked as optional or conditional. The Keycard applet is a full implementation of this specification. The Java SDK provides both low-level methods to send each of the APDUs described here as well as convenience methods and classes to simplify integration.
## Version
This documentation reflects version 3.1 of the applet.
## Conventions
* Bits are counted from 0. If this documentation talks mentions bit 7 it refers to the MSB of a byte.
* When a command has a precondition clause and these are not met the Status Word 0x6985 is returned.
* When a command is unsupported the Status Word 0x6A81 is returned.
* All tagged data structures are encoded in the [BER-TLV format](http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-d.aspx).
* When parsing the card's response, the client should ignore unrecognized fields at the end of the response. This allows extending responses in future minor revisions without breaking compatibility with older clients.
## Capabilities
The Keycard protocol can be implemented by different devices which might have very different capabilities, different user interaction and connection types. For this reason some features not strictly related to signing transactions are optional. Each device reports its capabilities as response to the SELECT command. The optional capabilities are described below
### Secure Channel
The Secure Channel protocol defined in this document provides both passive and active MITM protection and PFS. Some devices however might have inherently secure connections to the client (for example Bluetooth based devices can use pairing and encrypted communication). To avoid overhead, devices using inherently secure connections can avoid implementing the secure channel defined here.
Devices not implementing the Secure Channel capability do not implement the **PAIR**, **UNPAIR**, **OPEN SECURE CHANNEL** and **MUTUALLY AUTHENTICATE** commands. The **CHANGE PIN** and **INIT** commands do not allow setting the pairing password.
### Key management
Key management is the ability to generate, remove, backup and restore keys. Some devices might have internal key management and not allow the client to take part in this process.
Devices not implementing the Key management capability do not implement the **LOAD KEY**, **GENERATE KEY**, **GENERATE MNEMONIC**, **REMOVE KEY** commands.
### Credentials management
Credentials management is the ability to authenticate the user and manipulate PIN, PUK and pairing password. Some devices, especially those with screen and buttons, might want to handle this completely internally without client intervention.
Devices not implementing the Credentials management capability do not implement the **INIT**, **VERIFY PIN**, **UNBLOCK PIN** and **CHANGE PIN** commands.
### NDEF
The NDEF capability is the ability to set a custom NDEF record on the NFC interface. The NDEF record is used, for example, by mobile phones to decide the action to perform when a card is tapped to the device and no application using NFC is open. Customizing the NDEF record allow setting a default client or link to launch. Obviously, not all devices have a NFC interface and even those who do might not provide a way to customize the NDEF response.
Devices not implementing the NDEF capability do not support P1 = 0x01 (SET NDEF DATA) in the **STORE DATA** command.
+35
View File
@@ -0,0 +1,35 @@
---
id: apdu_pair
title: PAIR
---
# PAIR
* CLA = 0x80
* INS = 0x12
* P1 = pairing phase
* P2 = 0x00
* Data = see below
* Response Data = see below
* Response SW = 0x9000 on success, 0x6A80 if the data is in the wrong format, 0x6982 if client cryptogram verification fails, 0x6A84 if all available pairing slot are taken, 0x6A86 if P1 is invalid or is 0x01 but the first phase was not completed, 0x6985 if a secure channel is open
* Capability: Secure Channel
P1:
* 0x00: First step
* 0x01: Final step
Data:
* On first step: a 256-bit random client challenge
* On second step: the client cryptogram as SHA-256(shared secret, card challenge)
Response Data:
* On first step: the card cryptogram as SHA-256(shared secret, client challenge) followed by a 256-bit card challenge
* On second step: the pairing index followed by a 256-bit salt
This APDU is sent to pair a client. Pairing is performed with two commands which must be sent immediately one after the other.
In the first phase the client sends a random challenge to the card. The card replies with the SHA-256 hash of the shared secret and the challenge followed by its random challenge. The client is thus able to authenticate the card by verifying the card cryptogram (since the client can generate the same and verify that it matches).
In the second phase the client sends the client cryptogram which is the SHA-256 hash of the shared secret and the card challenge. The card verifies the cryptogram and thus authenticates the client. On success the card generates a random 256-bit salt which is appended to the shared secret. The SHA-256 hash of the concatenated value is stored in the first available pairing slot and will be further used to derive session keys. The card responds with the pairing index (which the client must send in all OPEN SECURE CHANNEL commands) and the salt used to generate the key, so that the client can generate and store the same key.
The shared secret is a 256-bit value which must be be known to both parts being paired.
+16
View File
@@ -0,0 +1,16 @@
---
id: apdu_removekey
title: REMOVE KEY
---
# REMOVE KEY
* CLA = 0x80
* INS = 0xD3
* P1 = 0x00
* P2 = 0x00
* Response SW = 0x9000 on success.
* Preconditions: Secure Channel must be opened, user PIN must be verified
* Capability: Key management
Removes the key from the card, bringing it back to an uninitialized state. No signing operation is possible after this command until a new LOAD KEY command is performed.
+39
View File
@@ -0,0 +1,39 @@
---
id: apdu_select
title: SELECT
---
# SELECT
* CLA = 0x00
* INS = 0xA4
* P1 = 0x04
* P2 = 0x00
* Data = the instance AID
* Response = Application Info Template or ECC public key.
Response Data format:
- Tag 0xA4 = Application Info Template
- Tag 0x8F = Instance UID (16 bytes)
- Tag 0x80 = ECC public Key (0 or 65 bytes)
- Tag 0x02 = Application Version (2 bytes)
- Tag 0x02 = Number of remaining pairing slots (1 byte)
- Tag 0x8E = Key UID (0 or 32 bytes)
- Tag 0x8D = Capabilities (1 byte)
The SELECT command is documented in the ISO 7816-4 specifications and is used to select the application on the card, making it the active one. The data field is the AID of the application.
The response is the Application Info template which contains:
* the instance UID, which can be used by the client to keep track of multiple cards.
* an ECC public key for the Secure Channel. If the device does not implement the Secure Channel capability then this field has 0 length.
* the application version number, formatted on two bytes. The first byte is the major version and the second is the minor version (e.g: version 2.1 is formatted as 0x0201). This number must match the version of the protocol implemented.
* the number of remaining pairing slots for the Secure Channel. If the device does not implement the Secure Channel capability then this field has a fixed value of 0xff.
* the Key UID, which can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key. Allows identifying the master key being used without revealing it.
* the capabilities descriptor. Its value is the bitwise OR of all capabilities supported by the device. The capabilities are defined as:
* Secure Channel: 0x01
* Key management: 0x02
* Credentials management: 0x04
* NDEF: 0x08
When the applet is in pre-initializated state (which implies support of Credentials management capability), it only returns the ECC public key, BER-TLV encoded with tag 0x80 (with length 0 if Secure Channel is not implemented).
+16
View File
@@ -0,0 +1,16 @@
---
id: apdu_setpinlesspath
title: SET PINLESS PATH
---
# SET PINLESS PATH
* CLA = 0x80
* INS = 0xC1
* P1 = 0x00
* P2 = 0x00
* Data = a sequence of 32-bit integers
* Response SW = 0x9000 on success, 0x6A80 if data is invalid
* Preconditions: Secure Channel must be opened, user PIN must be verified
Sets the given sequence of 32-bit integers as a PIN-less path. When the current derived key matches this path, SIGN will work even if no PIN authentication or pairing has been performed. An empty sequence means that no PIN-less path is defined.
+32
View File
@@ -0,0 +1,32 @@
---
id: apdu_sign
title: SIGN
---
# SIGN
* CLA = 0x80
* INS = 0xC0
* P1 = derivation options
* P2 = 0x00
* Data = the hash to sign (32 bytes) | sequence of 32-bit integers (if P1=01 or P1=02)
* Response = public key and the signature
* Response SW = 0x9000 on success, 0x6A80 if the data is less than 32-byte long (36 bytes if P1=01 or P1=02), 0x6A88 if P1=0x03 but no PIN-less path is defined
* Preconditions: Secure Channel must be opened, user PIN must be verified (or a PIN-less key must be active), a valid keypair must be loaded
P1:
0x00 = Current key
0x01 = Derive
0x02 = Derive and make current
0x03 = PIN-less path
Response Data format:
- Tag 0xA0 = signature template
- Tag 0x80 = ECC public key component
- Tag 0x30 = ECDSA Signature
- Tag 0x02 = R value
- Tag 0x02 = S value
Returns the ECDSA signature of the hash. The hash can be calculated using any algorithm, but must be 32-bytes long. The signature is returned in a signature template, containing the public key associated to the signature and the signature itself. For usage on the blockchain, you will need to calculate the recovery ID in addition to extracting R and S. To calculate the recovery ID you need to apply the same algorithm used for public key recovery from a transaction starting with a recovery ID of 0. If the public key matches the one returned in the template, then you have found the recovery ID, otherwise you try again by incrementing the recovery ID.
P1 = 0x01 derives the path given in the data field without changing the current path of the card. P1 = 0x02 derives the path but also changes the current path of the card. The source for derivation can be set by OR'ing P1 with the constants defined in the DERIVE KEY command. This allows deriving from master, parent or current.
P1 = 0x03 is specifically thought for POS transactions. It can be executed without Secure Channel (since no sensitive info is transmitted) and does not require PIN authentication. The current derivation path on the card remains unchanged, but the signing process is performed using the PIN-less derivation path previously defined using the SET PINLESS PATH command.
+15
View File
@@ -0,0 +1,15 @@
---
id: apdu_storedata
title: STORE DATA
---
# STORE DATA
* CLA = 0x80
* INS = 0xE2
* P1 = 0x00 for public data, 0x01 for NDEF data, 0x02 for Cash data
* P2 = 0x00
* Data = the data to store. Implementations should accept at least 127 bytes of data.
* Response SW = 0x9000 on success, 0x6A86 on undefined P1, 0x6A80 if the data is too long
* Preconditions: Secure Channel must be opened, user PIN must be verified
* Capabilities: NDEF for P1 = 0x01
+17
View File
@@ -0,0 +1,17 @@
---
id: apdu_unblockpin
title: UNBLOCK PIN
---
# UNBLOCK PIN
* CLA = 0x80
* INS = 0x22
* P1 = 0x00
* P2 = 0x00
* Data = the PUK followed by the new PIN
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid
* Preconditions: Secure Channel must be opened, user PIN must be blocked
* Capability: Credentials management
Used to unblock the user PIN. The data field must contain exactly 18 numeric digits, otherwise SW 0x6A80 is returned. The first 12 digits are the PUK and the last 6 are the new PIN. If the PUK is correct the PIN is changed to the supplied one, it is unblocked and authenticated for the rest of the session. The status code 0x9000 is returned. When the PUK is wrong, the number of remaining retries is decreased and the SW 0x63CX, where X is the number of available retries is returned. When the number of remaining retries reaches 0 the PUK is blocked. When the PUK is blocked this command always returns 0x63C0, even if the PUK is inserted correctly. In this case the wallet is effectively lost.
+17
View File
@@ -0,0 +1,17 @@
---
id: apdu_unpair
title: UNPAIR
---
# UNPAIR
* CLA = 0x80
* INS = 0x13
* P1 = the index to unpair
* P2 = 0x00
* Response SW = 0x9000 on success, 0x6985 if security conditions are not met, 0x6A86 if the index is higher than the
highest possible pairing index.
* Preconditions: Secure Channel must be opened, user PIN must be verified
* Capability: Secure Channel
This APDU is sent to unpair a client. An existing secure channel session must be open. The application implementing this protocol may apply additional restrictions, such as the verification of a user PIN. On success the pairing slot at the given index will be freed and will be made available to pair other clients. If the index is already free nothing will happen.
+17
View File
@@ -0,0 +1,17 @@
---
id: apdu_verifypin
title: VERIFY PIN
---
# VERIFY PIN
* CLA = 0x80
* INS = 0x20
* P1 = 0x00
* P2 = 0x00
* Data = the PIN to be verified
* Response SW = 0x9000 on success, 0x63CX on failure, where X is the number of attempt remaining
* Preconditions: Secure Channel must be opened
* Capability: Credentials management
Used to verify the user PIN. On correct PIN entry the card returns 0x9000, the retry counter is reset and the PIN is marked as authenticated for the entire session (until the application is deselected or the card reset/teared). On error, the number of remaining retries is decreased and the SW 0x63CX, where X is the number of available retries is returned. When the number of remaining retries reaches 0 the PIN is blocked. When the PIN is blocked this command always returns 0x63C0, even if the PIN is inserted correctly.
+32
View File
@@ -0,0 +1,32 @@
---
id: applet_installation
title: Card Setup
---
# Preparing the card
Keycard ships with all applets fully installed but not initialized. The most convenient way to initialize and manage the card is to install the [Keycard Connect](https://github.com/keycard-tech/keycard-connect) Android app. It allows to fully initialize a card with pairing key, PIN, PUK and BIP39 seed. It also provides basic functionality like changing credentials, changing or removing keys and clear pairing slots. The last functionality is especially useful if you are integrating with Keycard and accidentally fill all pairing slots. Using the "Unpair others" button will remove all pairing except the Keycard Connect's one.
In some cases you might want to reset the applet status completely for testing purposes. This is done by reinstalling the applet, which can be accomplished using the [Keycard CLI](https://github.com/keycard-tech/keycard-cli). To use the Keycard CLI, a USB reader is needed to be able to communicate with the card. The repository contains example scripts for applet reinstallation.
## Card requirements
The Keycard applet can be installed not only on our cards, but on any card which meets the following requirements:
* JavaCard 3.0.4 or later.
* Cipher.ALG_AES_BLOCK_128_CBC_NOPAD
* Cipher.ALG_AES_CBC_ISO9797_M2
* KeyAgreement.ALG_EC_SVDP_DH_PLAIN
* KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY (defined in JavaCard 3.0.5 but available on some 3.0.4 cards nonetheless)
* KeyPair.ALG_EC_FP (generation of 256-bit keys)
* MessageDigest.ALG_SHA_256
* MessageDigest.ALG_SHA_512
* RandomData.ALG_SECURE_RANDOM
* Signature.ALG_AES_MAC_128_NOPAD
* Signature.ALG_ECDSA_SHA_256
Best performance is achieved if the card supports:
* Signature.ALG_HMAC_SHA_512
Keycard requires about 10kb of NVM. All allocations, instantiations and checks are performed at installation time, so if installation succeeds, the applet will work fine.
+34
View File
@@ -0,0 +1,34 @@
---
id: index
title: Overview
---
# Overview
Keycard provides to developer an hardware implementation of a [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) HD wallet. This means it supports key generation, derivation and signing. It also allows exporting keys defined in the context of [EIP-1581](https://eips.ethereum.org/EIPS/eip-1581).
Communication with the Keycard happens through a simple APDU interface, together with a Secure Channel guaranteeing confidentiality, authentication and integrity of all commands. It supports both NFC and ISO7816 physical interfaces, meaning that it is compatible with any Android phone equipped with NFC and all USB Smartcard readers.
A feature added in version 3.1 of the applet is a duress PIN, which is completely transparent in the API and undetectable. It changes the keys generated during derivation in a deterministic way. Wallets should make sure they account for this by hiding balance and wallet address until proper authentication, and eventually be able to transparently switch to using the addresses generated after inserting the duress PIN.
The most obvious case for integration of Keycard is crypto wallets (ETH, BTC, etc), however it can be used in other systems where a BIP-32 key tree is used and/or you perform authentication/identification.
To further simplify integration, we have developed a Java-based API which can be used on both desktop and Android systems. On the desktop it uses the javax.smartcardio to interface with the card, which is compatible with most USB readers. On Android it uses the on-board NFC reader. If you develop in Java or any other language available, this is the easiest way to use the Keycard. You can also find a Go and Swift SDK and more resources [here](resources)
[Read the SDK documentation](sdk/installation)
If you use a different language, please first refer to the [Java SDK](sdk/installation) documentation for a high level overview of how to perform different tasks with the Keycard. Then, please check the [protocol documentation](apdu) out for the low-level details.
Since Keycard 3.0, besides the hardware wallet functionality, you find a second applet with a different purpouse and security model. This applet, internally called Cash applet, does not share any data with the wallet and provides a single keypair which can be used to sign any piece of information. Unlike the wallet applet, the cash applet does not require a PIN or SecureChannel and can be tapped on 3rd party terminal. This enable convenient in-person interactions at the expense of security. For this reason the keypair in the Cash applet should not hold any funds at all, but should instead be used in concert with smartcontracts in scenarios with lower security requirements. It is especially suited for signing EIP-712 formatted metatx. Some examples can include ticketing, small payments, etc. It is worth repeating that the Wallet and Cash applet, while living on the same card, do not share any data or key and are completely independent from each other. In order to easily integrate Keycard with any dApp, we defined a small extension to the [Web3 API](web3).
## Versioning
The current version of the applet is 3.1. This documentation applies to said version. [Semantic versioning](https://semver.org) is used throughout the project, with the omission of the patch number for the applet, but not the SDK and related tools. The version of all components of the Keycard project are tied to the protocol version.
## Get in touch
You are at the right place to find informations about Keycard and get you going.
If you need more and want to discuss:
- to check or raise issues/bugs/feature requests please do so in our [GitHub repos](resources)
- get in touch with the team by [email](mailto:support@keycard.tech)
+23
View File
@@ -0,0 +1,23 @@
---
id: resources
title: Resources
---
# Resources
Here you find the links to all repositories and tools related to Keycard development.
## GitHub repositories
* [Javacard applet](https://github.com/keycard-tech/status-keycard)
* [Java SDK](https://github.com/keycard-tech/status-keycard-java)
* [Go SDK](https://github.com/keycard-tech/keycard-go/)
* [Swift SDK](https://github.com/keycard-tech/Keycard.swift)
* [Keycard on Ledger](https://github.com/keycard-tech/keycard-ledger)
* [React Native library](https://github.com/keycard-tech/react-native-status-keycard)
* [Go Status library](https://github.com/keycard-tech/status-keycard-go)
## Tools
* [Keycard CLI](https://github.com/keycard-tech/keycard-cli)
* [Keycard Desktop](https://github.com/choppu/keycard-desktop)
+40
View File
@@ -0,0 +1,40 @@
---
id: sdk_authentication
title: Authentication
---
# User authentication
Most operations with the card (all involving operations with the wallet or credentials) require authenticating the user. After authentication, the user remains authenticated until the card is powered off or the application is re-selected.
Authentication is performed by verifying the user PIN. Note that this piece of information is sensitive and must be handled accordingly in the application. PIN verification is done with a single step
```java
// pin is the user PIN as a string of 6 digits
try {
cmdSet.verifyPIN(pin).checkAuthOK();
} catch(WrongPINException e) {
System.out.println("Number of remaining attempts: " + e.getRetryAttempts());
}
```
if the PIN is wrong, you will receive an error SW in the format 0x63CX where X is the number of attempts remaining. When the number of remaining attempts is 0, the card is blocked. The user must then enter the PUK and a new PIN to restore access to the card. The maximum number of retries for the PUK is 5. To simplify things, the `APDUResponse.checkAuthOK()` method can be used to verify if the authentication was correct, and if not throw a `WrongPINException` which contains the number of remaining attempts.
```java
cmdSet.unblockPIN(puk, newPIN).checkAuthOK();
```
## Changing credentials
All credentials of the Keycard can be changed (PIN, PUK, pairing password). Changing the pairing password does not invalidate existing pairings, but applies to the ones which can be created in the future. Changing credentials, requires user authentication.
```java
// Changes the user PIN
cmdSet.changePIN("123456").checkOK();
// Changes the PUK
cmdSet.changePUK("123456123456").checkOK();
// Changes the pairing password
cmdSet.changePairingPassword("my pairing password").checkOK();
```
+42
View File
@@ -0,0 +1,42 @@
---
id: sdk_cash
title: Cash
---
# Cash applet
Since version 3.0 the Keycard package also includes a Cash applet. When installed, the applet immediately generates a keypair which cannot be changed, exported or be used for derivation. The applet only responds to the SELECT and SIGN commands and has no concept of PIN or SecureChannel. All in all, it is a very basic version of the main Keycard applet. This applet is completely separate from the Wallet applet and they do not share any key.
The use cases for this applet are burner wallets, payment cards (including prepaid ones). This applet can be installed alongside the main Keycard applet and does not interfere with it. As the name implies, the applet has roughly the same security level of cash in the pocket and should be used with this in mind. Security can be enhanced by making the applet a signer to a smartcontract (instead of holding funds directly) and applying anti-fraud policies on the network.
The SDK provides a separate command set to interact with this applet.
## Instatiating the command set
```java
// cardChannel is our CardChannel instance
CashCommandSet cashCmdSet = new CashCommandSet(cardChannel);
```
## Selecting the CashApplet
```java
// note that selecting the cash applet deselects the Keycard applet, so if you use both command sets make sure you invoke select every time you want to switch application
CashApplicationInfo info = new CashApplicationInfo(cashCmdSet.select().checkOK().getData());
// Returns the public key of the wallet's keypair. This can be used to calculate the Ethereum address.
info.getPubKey();
// Returns the version of the applet.
info.getAppVersion();
// Returns arbitrary data which has been set either at applet installation or through the STORE DATA command in the Keycard applet
info.getPubData();
```
## Signing
```java
// hash is the hash to sign, for example the Keccak-256 hash of an Ethereum transaction
// the signature object contains r, s, recId and the public key associated to this signature
RecoverableSignature signature = new RecoverableSignature(hash, cashCmdSet.sign(hash).checkOK().getData());
```
+97
View File
@@ -0,0 +1,97 @@
---
id: sdk_connection
title: Card connection
---
# Connecting to the card
Before you can send commands to the Keycard, you must first connect to the card. Connection handling is the only task which is handled differently between Android an the desktop.
## Android
On Android, the NFC connection handling must happen on a thread separate from the UI thread. The SDK provides the class `NFCCardManager` to handle this. This an example activity starting the NFC reader and handling the connection to the card. Refer to the comments in the example for more information.
```java
public class MainActivity extends AppCompatActivity {
private NfcAdapter nfcAdapter;
private NFCCardManager cardManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the Android NFC default adapter
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
// Create the NFCCardManager, this class is provided by the Keycard SDK and handles connections to the card
cardManager = new NFCCardManager();
// The Card Listener receives the connected/disconnected events. These can happen at any time since the user can
// introduce or remove the card to/from the field at any time. This is where your code goes.
cardManager.setCardListener(new CardListener() {
@Override
public void onConnected(CardChannel cardChannel) {
// Card is connected. Here you can start working with the Keycard. The CardChannel is what you will use to
// communicate with the card.
}
@Override
public void onDisconnected() {
// Card is disconnected (was removed from the field). You can perform cleanup here.
}
});
cardManager.start();
}
@Override
public void onResume() {
super.onResume();
// We need to enable the reader on resume.
if (nfcAdapter != null) {
nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
}
}
@Override
public void onPause() {
super.onPause();
// We disable the reader on pause to allow other apps to use it.
if (nfcAdapter != null) {
nfcAdapter.disableReaderMode(this);
}
}
}
```
## Desktop
On the desktop we use the javax.smartcardio library. There are several ways to handle connections, the important part is getting a CardChannel open. Below is an example of how this can be achieved (assumes that a single smartcard reader is connected).
```java
// We create a TerminalFactory object
TerminalFactory tf = TerminalFactory.getDefault();
CardTerminal cardTerminal;
// We search a terminal with a card inside
for (CardTerminal t : tf.terminals().list()) {
if (t.isCardPresent()) {
cardTerminal = t;
break;
}
}
}
// If not found, we throw an exception. Of course you should decide how to handle this situation
if (cardTerminal == null) {
throw new RuntimeException("No terminal found");
}
// If a terminal is found, we connect to it
Card apduCard = cardTerminal.connect("*");
// We create a PCSCCardChannel, which is an implementation of CardChannel and can be used with the rest of the SDK.
PCSCCardChannel apduChannel = new PCSCCardChannel(apduCard.getBasicChannel());
```
+62
View File
@@ -0,0 +1,62 @@
---
id: sdk_create_wallet
title: Wallet creation
---
# Wallet creation
To actually use the Keycard, it needs to have a wallet. This can be achieved in several different ways, which one you choose depends on your usage scenario. Creating a wallet requires user authentication and is possible even if a wallet already exists on the card (the new wallet replaces the old one). Use the `ApplicationInfo.hasMasterKey()` method to determine if the card already has a wallet or not. Note that the response of the `KeycardCommandSet.loadKey` method contains the key UID of the created wallet. This UID can be stored to keep track of this specific wallet in the client. The UID is tied to the key itself (is derived from the public key) so it will change if the wallet on card is replaced. The key UID is also part of the response of the applet selection command, so the wallet can be identified immediately upon selection.
## Creating a BIP39 mnemonic phrase
This method is great for interoperability with other wallets. The card can assist in creating the mnemonic phrase, since it features a TRNG. Generating the mnemonic itself does not require user authentication (since it does not modify the card state), but loading the key derived from it does. Example of the entire procedure is below
```java
// Generates a Mnemonic object from the card. You can choose between generating 12, 15, 18, 21 or 24 words
Mnemonic mnemonic = new Mnemonic(cmdSet.generateMnemonic(KeycardCommandSet.GENERATE_MNEMONIC_12_WORDS).checkOK().getData());
// We need to set a wordlist if we plan using this object to derive the binary seed. We can set our own list or we can
// fatch the official BIP39 english word list as shown below.
mnemonic.fetchBIP39EnglishWordlist();
// If we did not verify the PIN before, we can do it now
cmdSet.verifyPIN(pin).checkOK();
// Loads the key generated from the mnemonic phrase.
cmdSet.loadKey(mnemonic.toBIP32KeyPair()).checkOK();
```
## Importing a wallet from BIP39 mnemonic phrase
Importing an existing passphrase requires only the loading step.
```java
// The passphrase is a string with space separated words. The password can be any non-null string, usually is empty.
cmdSet.loadKey(Mnemonic.toBinarySeed(passphrase, password)).checkOK();
```
## Generating keys on-card
This is the simplest and safest method, because the generated wallet never leaves the card and there is no "paper backup" to keep secure. Using a multi-sig wallet can solve the problem of backups, by having multiple cards signers for the wallet. Using the SDK, you simply do
```java
cmdSet.generateKey().checkOK();
```
## Importing an EC keypair
You can import on the keycard any EC keypair on the SECP256k1 curve, with or without the BIP32 extension. If your import a key without the BIP32 extension, then key derivation will not work, but you will still be able to use the Keycard for signing transactions using the imported key. This scenario can be useful if you are migrating from a wallet not using BIP39 passphrases or for wallets following some custom generation rules. It is however generally preferable to use one of the methods presented above.
An example of key import is
```java
// privKey is the S component of the key, as a 32-byte long byte array
// chainCode is the extension to the keypair defined by BIP32, this is another 32-byte long byte array. Can be null, in
// which case the created wallet won't be BIP32 compatible.
// pubKey is the DER encoded, uncompressed public key. Can be null, in which case it is automatically calculated from
// the private key.
BIP32KeyPair keypair = new BIP32KeyPair(privKey, chainCode, pubKey);
// Loads the keypair
cmdSet.loadKey(keypair).checkOK();
```
+56
View File
@@ -0,0 +1,56 @@
---
id: sdk_derivation_signing
title: Derivation & signing
---
# Derivation & signing
Signing is the main goal of the Keycard and indeed any hardware wallet. The Keycard performs signing with the currently active key. Key derivation is used to select the active key to a specific key BIP32 key path.
## Key derivation
As mentioned before, the Keycard is a BIP32 compatible wallet. This means that it can perform key derivation as defined by the BIP32 specification in order to create a hierarchical deterministic wallet. When deriving a key, this key becomes active.
When creating or importing a wallet to the Keycard, the active key is the master key. Note that the maximum depth of the key path is 10, excluding the master key.
Key derivation requires user authentication
Since a line of code is worth a thousand words, below is an example of deriving a standard key path
```java
cmdSet.deriveKey("m/44'/0'/0'/0/0").checkOK();
```
You can also read the current active path with with
```java
// you can then get is as a string with currentPath.toString()
KeyPath currentPath = new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData());
```
## Signing
Your Keycard has been initialized, has a wallet and you have derived the keypath you need. You can now perform transactions by signing them with the card. Since the Keycard has no user input/output capabilities, it would be useless to transfer the entire transaction to the card for signing. You should instead calculate the transaction hash, according to the rules of the cryptocurrency you are handling and send that for signature instead. This also means, that you can handle anything which requires ECDSA signatures over SECP256k1 curve, regardless of the used hashing algorithm (at the condition that it output a 256-bit hash of course). This opens the door to signing transactions for the most common cryptocurrencies, but also makes it usable outside the realm of crypto transactions.
Signing is done as
```java
// hash is the hash to sign, for example the Keccak-256 hash of an Ethereum transaction
// the signature object contains r, s, recId and the public key associated to this signature
RecoverableSignature signature = new RecoverableSignature(hash, cmdSet.sign(hash).checkOK().getData());
```
Signing requires user authentication.
## Combined derivation and sign
Since version 2.2 of the Keycard API, it is possible to combine derivation and sign in a single step. Additionally, it is possible to choose whether the given path becomes the current path or not. Example
```java
// hash is the hash to sign, the second argument is the path to use, in the same format as for the DERIVE KEY command
// the third argument is a flag indicating whether the derived key should become the current key or not
APDUResponse resp = cmdSet.signWithPath(hash, "m/44'/0'/0'/0/0", false);
RecoverableSignature signature = new RecoverableSignature(hash, resp.checkOK().getData());
```
this method of signing is now actually the recommended method since it does not rely on existing card state.
+53
View File
@@ -0,0 +1,53 @@
---
id: sdk_export
title: Java SDK
---
# Exporting (public or EIP-1581 compliant) keys
Sorry for the long title, but let's make it immediately clear: the keys used to sign transactions never leave the card and cannot be exported. You can however export any public key as well as the private key of keypaths defined in the [EIP-1581 specifications](https://eips.ethereum.org/EIPS/eip-1581). Those keys, by design, are not to be used for transactions but are instead usable for operations with lower security concerns where caching or storing the key outside the card might be beneficial from an UX point of view. Of course, exporting a key always requires user authentication.
## Exporting the current key
```java
// Exports the current public key. This is allowed for any key path
BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(KeycardCommandSet.EXPORT_KEY_P2_PUBLIC_ONLY).checkOK().getData());
// Exports the entire key pair. This is only allowed for key path following the EIP-1581 definition
BIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(KeycardCommandSet.EXPORT_KEY_P2_PRIVATE_AND_PUBLIC).checkOK().getData());
```
## Derive & export
The export command is very powerful, since it allows you to derive & export a key in one step. You have the option to make the derived and exported key active or leave the active key untouched. You can also decide whether to export only the public key or the entire keypair (following the rules defined above).
A very convenient use case is deriving an account key and retrieving the public key in one step. This is faster than doing it with two commands (derive key and export public), because every command processed has some overhead. Example
```java
// The first parameter is the keypath, the second tells whether that you want to make the derived & exported key current
// and the third tells that you only want the public key to be exported.
BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportKey("m/44'/0'/0'/0/0", true, KeycardCommandSet.EXPORT_KEY_P2_PUBLIC_ONLY).checkOK().getData());
// The line above is equivalent to
// cmdSet.deriveKey("m/44'/0'/0'/0/0").checkOK();
// BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(KeycardCommandSet.EXPORT_KEY_P2_PUBLIC_ONLY).checkOK().getData());
```
Another use case, is to export keys defined by EIP-1581 without changing the current active key, since you won't be signing with the exported key using the card
```java
// Let's assume the current active path is "m/44'/0'/0'/0/0"
// The first parameter is the key path, the second tells that you do not want to make it current and the third that you
// want the entire keypair, not only the public key
BIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportKey("m/43'/60'/1581'/0'/0", false, false).checkOK().getData());
// At this point, the current active path would still be "m/44'/0'/0'/0/0"
```
For non EIP-1581 paths you can also export the extended public key, including the chain code. This allows, for non-hardened keys, to derive child public keys.
```java
BIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportKey("m/44'/0'/0'/0", false, KeycardCommandSet.EXPORT_KEY_P2_EXTENDED_PUBLIC).checkOK().getData());
```
+20
View File
@@ -0,0 +1,20 @@
---
id: skd_overview
title: Java SDK
---
# Overview
This is an SKD essentially a Java library for development of applications for Status Keycard that can be installed via tools klike Gradle or Maven in your project.
See the sub-sections below for more details:
* [Installation](sdk/installation)
* [Card Connection](sdk/connection)
* [Preparation](sdk/preparation)
* [Secure Channel](sdk/securechannel)
* [Authentication](sdk/authentication)
* [Wallet Creation](sdk/create_wallet)
* [Derivation & Signing](sdk/derivation_sign)
* [Pinless Signing](sdk/pinless)
* [Exporting Keys](sdk/export)
* [Cash Applet](sdk/cash)
+35
View File
@@ -0,0 +1,35 @@
---
id: sdk_installation
title: Installation
---
# Installation
You can import the SDK in your Gradle or Maven project using [Jitpack.io](https://jitpack.io). If using Gradle, to use
JitPack all you have to do is insert these lines in you `build.gradle` file
```groovy
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
```
Then, you must import the correct dependency. In case you are building an Android-based project, you need to add this line
```groovy
dependencies {
implementation 'com.github.status-im.status-keycard-java:android:3.0.2'
}
```
If you are working on the desktop, then you need this line instead
```groovy
dependencies {
implementation 'com.github.status-im.status-keycard-java:desktop:3.0.2'
}
```
In both case, you will have the same SDK, except for the way connection with the card is established.
+28
View File
@@ -0,0 +1,28 @@
---
id: sdk_pinless
title: Pinless signing
---
# Pinless signing
The card offers the option to define a BIP32 path which can be used to sign transactions (or meta-transactions) without requiring a PIN. The **SIGN** command also has the option to sign with this pinless path (if defined) without having to know its value in advance. Since in such a scenario no sensitive data is being transferred, signing using the pinless path can be done without opening a Secure Channel.
There are several scenarios made possible by this feature. One example, is the ability to use the card for payments at POS terminals. Pairing with every terminal is not practical and inserting the PIN on an untrusted device (the terminal is owned by a 3rd party) is not safe, since it could be logged. Pinless signing with no Secure Channel solves both issues.
Since the wallet assigned to pinless signing is basically unprotected, it must not hold great value. One option is to consider the funds in that account like pocket cash. You put a little of your favourite cryptocurrency and should someone get hold of the card, you lost maybe the equivalent of $5. Another option, valid when using Ethereum, is to not hold any value in that wallet at all. Instead it can be used to sign meta-transactions and actual fund allocation can be handled by a smart contract through a dApp. Of course in this case the POS must be compatible with the specific payment scheme for this to work properly.
## Defining a pinless path
Defining which path is used for pinless signing, of course, requires PIN authentication and thus a Secure Channel. This means that only the owner can define which wallet is allocated for this operation. Then SDK call to set the pinless path is
```java
cmdSet.setPinlessPath("m/44'/0'/0'/0/200").checkOK();
```
## Signing with pinless path
When a pinless path is defined, a wallet (or POS terminal, or whatever) can invoke the `signPinless` method without having to know the path. If no pinless path was defined, the Status Word 0x6A88 will be returned. As mentioned before, opening a Secure Channel and authenticating with PIN before sending this command is allowed but not necessary.
```java
cmdSet.signPinless(hashToSign).checkOK();
```
+76
View File
@@ -0,0 +1,76 @@
---
id: sdk_preparation
title: Preparation
---
# Working with the applet
Regardless whether you are on Android or desktop, you should at this point have an implementation of the CardChannel interface (be it NFCCardChannel or PCSCCardChannel). You can now start working with the card. The first thing to do is creating a `KeycardCommandSet` instance. This class gives access to all of the applet functionality, wrapping the low-level APDUs in easy to use methods. All other classes in the SDK are helper to format parameters and parse responses from the card. To create a command set, just do
```java
// cardChannel is our CardChannel instance
KeycardCommandSet cmdSet = new KeycardCommandSet(cardChannel);
```
## Applet selection
Modern SmartCards can have several applications installed, so after connection with the card you need to select the Keycard applet. This is easily done with
```java
// The checkOK method can be called on any APDUResponse object to confirm that the
cmdSet.select().checkOK();
```
While this correctly selects the applet, it discards the card response, which contains information that can be useful to identify this specific card and its state. For this reason we could rewrite this as
```java
ApplicationInfo info = new ApplicationInfo(cmdSet.select().checkOK().getData());
// This method tells if the card is initialized (has a PIN, PUK and pairing password). If it is not, it must be
// initialized and no other operation is possible. Note that initialization touches only credentials to authenticate
// the user or the client, but does not touch the creation of a wallet on the card
info.isInitializedCard();
// Returns the instance UID of the applet. This can be used to identify this specific applet instance, very
// useful when storing instance-specific data on the client (pairing info, cached data, etc).
info.getInstanceUID();
// Returns the version of the applet.
info.getAppVersion();
// Returns the number of free pairing slots. If you are not yet paired with the card, it helps you know if you can still
// pair or not
info.getFreePairingSlots();
// Tells if the card has a wallet or not. If no wallet is available, you must create once before you can perform most
// operations on the card
info.hasMasterKey();
// Returns the UID of the master key of the wallet. The UID is value generated starting from the public key and is
// useful to identify if the card has the expected wallet.
info.getKeyUID();
```
After the applet is selected, you can start working with it. Note that the application remains selected until another applet is explicitly selected, or the card is powered off (for example is removed from the field)
## Applet initialization
This step is necessary to bring the initial credentials on the Keycard instance. When the card is not initialized, it cannot perform any operation. Initialization sets the initial PIN, PUK and pairing password and requires no authentication, but still uses a SecureChannel resistant to passive MITM attacks. Once the card is initialized, it cannot be initialized again (but credentials can be different with a different mechanism with previous authentication).
Initialization is done with
```java
// Usually, you want to check if the card is initialized before trying to initialize it, otherwise you will receive an
// error.
if (!info.isInitializedCard()) {
// The PIN must be 6 digits, the PUK 12 digits and the pairing password can be any password.
// All parameters are strings
cmdSet.init(pin, puk, pairingPassword).checkOK();
}
```
Since applet version 3.1 you can also specify the number of retries for PIN/PUK and a duress PIN
```java
cmdSet.init(pin, puk, pairingPassword, 3, 5, duressPin).checkOK();
```
+58
View File
@@ -0,0 +1,58 @@
---
id: sdk_securechannel
title: Secure Channel
---
# Secure Channel
Communication with the card happens over a Secure Channel to protect sensitive information being transmitted. The Secure Channel relies on a pairing mechanism for mutual authentication.
## Verifying card genuinity
Although this step is optional, it is highly recommended to verify that the applet on card is genuine. It is necessary to do this only once before pairing with the card. You can do this with
```java
// challenge is a random 256-bit (32-bytes) challenge
byte[] authData = cmdSet.identifyCard(challenge).checkOK().getData();
// returns null if invalid or the public key of the signing authority/
byte[] pubKey = Certificate.verifyIdentity(challenge, authData);
```
You must verify that the public key returned by ```Certificate.verifyIdentity``` is that of a certification authority you trust.
## Pairing
Clients wishing to communicate with the card, need to pair with it first. This allows creating secure channels resistant not only to passive but also to active MITM attacks. Although pairing allows the card and the client to authenticate each other, the card does not grant access to any operation with the wallet until the user is authenticated (by verifying its PIN). To establish the pairing, the client needs to know the pairing password. After it is established, the pairing info (not the password) must be stored as securely as possible on the client for subsequent sessions. You should store the pairing information together with the instance UID to simplify handling of multiple cards.
Only 5 clients can be paired at once, but it is possible to unpair previously paired clients.
Using the SDK, pairing is a simple operation
```java
// pairingPassword is usually provided by the user. This method throws an exception if pairing fails.
cmdSet.autoPair(pairingPassword);
// Retrieves the pairing object from the command set. This is what must be persisted (together with the instance UID)
Pairing pairing = cmdSet.getPairing();
// The pairing object can be serialized by calling
pairing.toByteArray();
// or the convenience method
pairing.toBase64();
```
If you have already paired, you should instead load the persisted pairing information in the command set
```java
// serializedPairing can be either the byte array or base64 string representation
Pairing pairing = new Pairing(serializedPairing);
// Sets the pairing info in the command set. This must be done before further operation is possible
cmdSet.setPairing(pairing);
```
## Opening the Secure Channel
After a pairing has been established, a secure channel can be opened. Before opening a secure channel, the card won't allow sending any command. This guarantees secrecy, integrity and authenticity of the commands. Opening a secure channel must be performed every time the applet is selected (this means also after a power loss). After opening it, the SDK handles the secure channel transparently, encrypting and signing all command APDUs and decrypting and verifying the signature of all responses. To open a secure channel all you need to do is
```java
cmdSet.autoOpenSecureChannel();
```
+42
View File
@@ -0,0 +1,42 @@
---
id: web3
title: Web3 API Extensions
---
# Web3 API Extensions
The Keycard wallet behaves like a regular hardware wallet and thus dApps can use it transparently as any other account. The Cash applet however, not being associated to a wallet and not being suitable to sign transactions (since it does not hold funds), must be specifically supported by dApps supporting it, usually to sign [EIP-712](https://eips.ethereum.org/EIPS/eip-712) formatted metatx. For this reason we defined some extensions to the Web3 API. At the moment, these are supported by the [Status](https://status.app) app. The specifications in this page are both for dApps developers wanting to support Keycard Cash in their dApp as well as for wallet app developers wanting to implement these extensions.
## EIP-712 signing
Support for this is provided by a single function: `keycard_signTypedData(data)`.
This function takes the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) data in the same format as `eth_signTypedData` and returns a signature generated by the tapped Keycard Cash. Unlike `eth_signTypedData` there is no `address` argument because the address will be that of the Cash applet of whatever Keycard is tapped by the user. The returned signature can then be sent, together with the message data, to an EIP-712 compatible smartcontract by the dApp. Assuming the Web 3.0 browser runs on a smartphone, calling this function will cause a pop-up to appear on the phone prompting to tap the Keycard. Since the function has all of the data being signed available, the screen can display this data in a user friendly way. In Status, messages for some specific smartcontracts are recognized and a custom UI is shown. These UX concern are not part of the specification itself, but implementers may want to consider this approach.
In order to generate the signature using Keycard Cash, browsers implementing this function should hash the message data using keccak256, listen on the NFC interface and send the hash to the Keycard Cash applet which will return a signature. More details can be found in the [Cash applet](sdk/cash) section of this site.
This function only returns a signature. If your dApp requires knowing the address of the signer you can use `recoverTypedSignature(data, sig)` from the [eth-sig-util](https://github.com/MetaMask/eth-sig-util) package.
### Implementation notes
This section explains some UX choices adopted by Status when implementing the `keycard_signTypedData(data)` in the browser. While these are not part of the specifications, it helps to visualize what happens when a dApp invokes this function and can be used by both web3 browser devs and dApp developers as a reference.
The idea behind's Status implementation, is to have a generic way to display these transactions, but if it finds specific fields in the data to sign then try to show a more user friendly interface. The generic screen looks like this
![Generic EIP-712 screen](/assets/docs/web3-generic.png)
Clicking on the "Show transaction data" brings up something like this
![Generic EIP-712 deta screen](/assets/docs/web3-generic-data.png)
When however the data to be signed contains `uint256 amount` and `address currency` among its fields this is interpreted as a payment transaction, and the following screen is shown instead.
![EIP-712 ERC20 screen](/assets/docs/web3-erc20.png)
If the currency field address is considered to be the address of an ERC20 token. If this address is zero, the amount is interpreted as plain ETH. This transaction format is used by the [Keycard Payment Network](https://github.com/keycard-tech/payment-network-apps) project.
Another example of a custom screen, is the one displayed for the [Keycard Redeem](https://github.com/keycard-tech/keycard-redeem) project. In this case we recognize the presence of the fields `address recipient` and `bytes32 code` field as a redeem request and we display the recipient field address clearly in the signature screen. If the recipient field in account managed by Status, instead of the plain address, the account name is shown. See screenshot below
![EIP-712 ERC20 screen](/assets/docs/web3-redeem.png)
One thing to keep in mind however, is that these custom screens are only there to provide better UX for some specific use case we are aware of. Any unrecognized message format can still be displayed generically and signed. If you are implementing a web3 browser you can adopt any strategy you prefer on this. Conversely, if you are developing SmartContract and dApps, you might want consider naming the fields of the message in a way that will trigger a specific interface on a browser. For example if your use case scenario involve transferring a certain `amount` of `currency` in a transaction, naming the message fields like that will show an easy to understand screen in Status.
+64
View File
@@ -0,0 +1,64 @@
---
title: Keycard - Privacy Policy
---
This Privacy Policy is intended to inform users of our approach to privacy in respect of this website (“**Website**”). In this regard, if you are visiting our Website, this Privacy Policy applies to you.
## 1) Who we are
For the purposes of this Privacy Policy and the collection and processing of personal data as a controller, the relevant entity is the Logos Collective Association, which has its registered office in Zug and its legal domicile address at
<ins>Logos Collective Association</ins>
c/o PST Consulting GmbH
Baarerstrasse 10
6300 Zug
Switzerland
Whenever we refer to “Logos”, “we” or other similar references, we are referring to the Logos Collective Association.
## 2) We limit the collection and processing of personal data from your use of the Website
We aim to limit the collection and collection and processing of personal data from users of the Website. We only collect and process certain personal data for specific purposes and where we have the legal basis to do so under applicable data protection law. We will not collect or process any personal data that we dont need and where we do store any personal data, we will only store it for the least amount of time needed for the indicated purpose.
In this regard, we collect and process the following personal data from your use of the Website:
* **IP address**: As part of such use of the Website we briefly process your IP address but we have no way of identifying you. We however have a legitimate interest in processing such IP addresses to ensure the technical functionality and enhance the security measures of the Website. This IP address is not stored by us over time.
* **Collection of email addresses**: We only collect your email address on our website for the purpose of sending you information in relation to the development of and the potential sale of Keycard Shell. Your email address is securely stored on our self-hosted servers, and we have implemented measures to protect such personal data by securing it with appropriate technical and organisational measures. We will not share such personal data to third parties without your consent, except as required by law. We will only collect and process your email address on the basis of you providing your consent for us to do so. We will retain your email address for as long as necessary to provide you with updates about Keycard Shell, unless you withdraw your consent or request deletion earlier
## 3) Third party processing of personal data
In addition to our limited and collection of personal data, third parties may collect or process personal data as a result of the Website making use of certain features or to provide certain content. To the extent you interact with such third party content or features, their respective privacy policies will apply.
## 4) Security measures we take in respect of the Website
As a general approach, we take data security seriously and we have implemented a variety of security measures on the Website to maintain the safety of your personal data when you submit such information to us.
## 5) Exporting data outside the European Union and Switzerland
We are obliged to protect the privacy of personal data that you may have submitted in the unlikely event that we export your personal data to places outside the European Union or Switzerland. This means that personal data will only be processed in countries or by parties that provide an adequate level of protection as deemed by Switzerland or the European Commission. Otherwise, we will use other forms of protections, such as specific forms of contractual clauses to ensure such personal data is provided the same protection as required in Switzerland or Europe. In any event, the transmission of personal data outside the European Union and Switzerland will always occur in conformity with applicable data protection law.
## 6) Your choices and rights
As explained in this Privacy Policy, we limit our collection and processing of your personal data wherever possible. Nonetheless, you still have certain choices and rights in respect of the personal data which we do collect and process. As laid out in relevant applicable data protection law, you have the right to:
* Ask us to correct or update your personal data (where reasonably possible);
* Ask us to remove your personal data from our systems;
* Ask us for a copy of your personal data, which may also be transferred to another data controller at your request;
* Withdraw your consent to process your personal data (only if consent was asked for a processing activity), which only affects processing activities that are based on your consent and doesnt affect the validity of such processing activities before you have withdrawn your consent;
* Object to the processing of your personal data; and
* File a complaint with the Federal Data Protection and Information Commissioner (FDPIC), if you believe that your personal data has been processed unlawfully.
## 7) Third party links
On this Website, you may come across links to third party websites. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these third party websites.
## 8) This Privacy Policy might change
We may modify or replace any part of this Privacy Policy at any time and without notice. Please check the Website periodically for any changes. The new Privacy Policy will be effective immediately upon its posting on our Website.
## 9) Contact information
To the extent that you have any questions about the Privacy Policy, please contact us at [legal@free.technology](mailto:legal@free.technology).
This document is licensed under CC-BY-SA.
+89
View File
@@ -0,0 +1,89 @@
---
title: Keycard - Terms of Use
---
These website terms of use (“**Website Terms of Use**”) are entered into by you and us, and they govern your access and use of this Website, including any content and functionality contained in the Website.
It is your responsibility to read the Website Terms of Use carefully before your use of the Website and your use of the Website means you have agreed to be bound and comply with these Website Terms of Use.
If you do not agree with these Website Terms of Use, you must not access or use the Website.
## 1) Who we are
For the purposes of these Website Terms of Use, the relevant entity is the Logos Collective Association, which has its registered office in Zug and its legal domicile address at:
<ins>Logos Collective Association</ins>
c/o PST Consulting GmbH
Baarerstrasse 10
6300 Zug
Switzerland
Whenever we refer to “Logos”, “we”, “us” or any other similar references, we are referring to the Logos Collective Association.
## 2) Disclaimers
The Website is provided by us on an as is basis and you use the Website at your own sole discretion and risk.
We disclaim all warranties of any kind, express or implied, including without limitation the warranties of merchantability, fitness for a particular purpose, and non-infringement of intellectual property or other violation of rights. We do not warrant or make any representations concerning the completeness, accuracy, legality, utility, reliability, suitability or availability of the use of the Website, the content on this Website or otherwise relating to the Website, such content or on any sites linked to this site. These disclaimers will apply to the maximum extent permitted by applicable law.
We make no claims that the Website or any of its content is accessible, legally compliant or appropriate in your jurisdiction. Your access or use of the Website is at your own sole discretion and you are solely responsible for complying with any applicable local laws.
The content herein or as accessible through the Website is intended to be made available for informational purposes only and should not be considered as creating any expectations or forming the basis of any contract, commitment or binding obligation with us. No information herein shall be considered to contain or be relied upon as a promise, representation, warranty or guarantee, whether express or implied and whether as to the past, present or the future in relation to the projects and matters described herein.
The information contained herein does not constitute financial, legal, tax, or other advice and should not be treated as such.
## 3) Forward looking statements
The Website may also contain forward-looking statements that are based on current expectations, estimates, forecasts, assumptions and projections about the technology, industry and markets in general.
The forward looking statements, which may include statements about the roadmap, project descriptions, technical details, functionalities, features, the development and use of tokens by projects, and any other statements related to such matters or as accessible through this website are subject to a high degree of risk and uncertainty. The forward looking statements are subject to change based on, among other things, market conditions, technical developments, and regulatory environment. The actual development and results, including the order and the timeline, might vary from whats presented. The information contained herein is a summary and does not purport to be accurate, reliable or complete and we bear no responsibility for the accuracy, reliability or completeness of information contained herein. Because of the high degree of risk and uncertainty described above, you should not place undue reliance on any matters described in this website or as accessible through this website.
While we aim to update the Website regularly, all information, including the timeline and the specifics of each stage, is subject to change and may be amended or supplemented at any time, without notice and at our sole discretion.
## 4) Intellectual property rights
The Website and its contents are made available under Creative Commons Attribution 4.0 International license (CC-BY 4.0). In essence this licence allows users to copy, modify and distribute the content in any format for any purpose, including commercial use, subject to certain requirements such as attributing us. For the full terms of this licence, please refer to the following website: [https://creativecommons.org/licenses/by/4.0/](https://creativecommons.org/licenses/by/4.0/).
## 5) Third party website links
To the extent the Website provides any links to a third party website, then their terms and conditions, including privacy policies, govern your use of those third party websites. By linking such third party websites, we do not represent or imply that we endorse or support such third party websites or content therein, or that we believe such third party websites and content therein to be accurate, useful or non-harmful. We have no control over such third party websites and will not be liable for your use of or activities on any third party websites accessed through the Website. If you access such third party websites through the Website, it is at your own risk and you are solely responsible for your activities on such third party websites.
## 6) Limitation of liability
We will not be held liable to you under any contract, negligence, strict liability, or other legal or equitable theory for any lost profits, cost of procurement for substitute services, or any special, incidental, or consequential damages related to, arising from, or in any way connected with these Website Terms of Use, the Website, the content on the Website, or your use of the Website, even if we have been advised of the possibility of such damages. In any event, our aggregate liability for such claims is limited to EUR 100 (one hundred Euros). This limitation of liability will apply to the maximum extent permitted by applicable law.
## 7) Indemnity
You shall indemnify us and hold us harmless from and against any and all claims, damages and expenses, including attorneys fees, arising from or related to your use of the Website, the content on the Website, including without limitation your violation of these Website Terms of Use.
## 8) Modifications
We may modify or replace any part of this Website Terms of Use at any time and without notice. You are responsible for checking the Website periodically for any changes. The new Website Terms of Use will be effective immediately upon its posting on the Website.
## 9) Governing law
Swiss law governs these Website Terms of Use and any disputes between you and us, whether in court or arbitration, without regard to conflict of laws provisions.
## 10) Disputes
In these terms, “dispute” has the broadest meaning enforceable by law and includes any claim you make against or controversy you may have in relation to these Website Terms of Use, the Website, the content on the Website, or your use of the Website.
We prefer arbitration over litigation as we believe it meets our principle of resolving disputes in the most effective and cost effective manner. You are bound by the following arbitration clause, which waives your right to litigation and to be heard by a judge. Please note that court review of an arbitration award is limited. You also waive all your rights to a jury trial (if any) in any and all jurisdictions.
If a (potential) dispute arises, you must first use your reasonable efforts to resolve it amicably with us. If these efforts do not result in a resolution of such dispute, you shall then send us a written notice of dispute setting out (i) the nature of the dispute, and the claim you are making; and (ii) the remedy you are seeking.
If we and you are unable to further resolve this dispute within sixty (60) calendar days of us receiving this notice of dispute, then any such dispute will be referred to and finally resolved by you and us through an arbitration administered by the Swiss Chambers Arbitration Institution in accordance with the Swiss Rules of International Arbitration for the time being in force, which rules are deemed to be incorporated herein by reference. The arbitral decision may be enforced in any court. The arbitration will be held in Zug, Switzerland, and may be conducted via video conference virtual/online methods if possible. The tribunal will consist of one arbitrator, and all proceedings as well as communications between the parties will be kept confidential. The language of the arbitration will be in English. Payment of all relevant fees in respect of the arbitration, including filing, administration and arbitrator fees will be in accordance with the Swiss Rules of International Arbitration.
Regardless of any applicable statute of limitations, you must bring any claims within one year after the claim arose or the time when you should have reasonably known about the claim. You also waive the right to participate in a class action lawsuit or a classwide arbitration against us.
## 11) About these Website Terms of Use
These Website Terms of Use cover the entire agreement between you and us regarding the Website and supersede all prior and contemporaneous understandings, agreements, representations and warranties, both written and oral, with respect to the Website.
The captions and headings identifying sections and subsections of these Website Terms of Use are for reference only and do not define, modify, expand, limit, or affect the interpretation of any provisions of these Website Terms of Use.
If any part of these Website Terms of Use is held invalid or unenforceable, that part will be severable from these Website Terms of Use, and the remaining portions will remain in full force and effect. If we fail to enforce any of these Website Terms of Use, that does not mean that we have waived our right to enforce them.
If you have any specific questions about these Website Terms of Use, please contact us at [legal@free.technology](mailto:legal@free.technology).
This document is licensed under CC-BY-SA.
Vendored
+11
View File
@@ -0,0 +1,11 @@
declare global {
namespace NodeJS {
interface ProcessEnv extends Env {
SHOPIFY_STORE_DOMAIN: string
SHOPIFY_STOREFRONT_API_PUBLIC_ACCESS_TOKEN: string
SHOPIFY_ADMIN_API_PRIVATE_ACCESS_TOKEN: string
}
}
}
export {}
Vendored
+17
View File
@@ -0,0 +1,17 @@
import type * as React from 'react'
// why: https://github.com/mdx-js/mdx/issues/2487#issuecomment-2407661869
// why: https://react.dev/blog/2024/04/25/react-19-upgrade-guide#the-jsx-namespace-in-typescript
declare global {
namespace JSX {
// type ElementClass = React.JSX.ElementClass
// type Element = React.JSX.Element
type IntrinsicElements = React.JSX.IntrinsicElements
}
}
declare module 'framer-motion' {
export interface MotionProps {
className?: string
}
}
+20 -1
View File
@@ -1,4 +1,23 @@
/** @type {import('next').NextConfig} */
const config = {}
const config = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'demo.ghost.io',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'news.keycard.tech',
pathname: '/**',
},
],
},
transpilePackages: [
// why: https://github.com/hashicorp/next-mdx-remote/issues/467#issuecomment-2432166413
'next-mdx-remote',
],
}
export default config
+69 -15
View File
@@ -1,8 +1,10 @@
{
"name": "keycard-website",
"scripts": {
"preinstall": "npx only-allow pnpm",
"prepare": "husky",
"dev": "pnpm install && next dev",
"predev": "pnpm install",
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
@@ -10,37 +12,77 @@
"check": "tsc --noEmit"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.3",
"@react-three/drei": "^9.115.0",
"@react-three/fiber": "^8.17.10",
"@types/three": "^0.169.0",
"@react-three/drei": "^9.117.2",
"@react-three/fiber": "9.0.0-rc.1",
"@react-three/postprocessing": "^2.16.3",
"@status-im/icons": "^1.0.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^5.59.17",
"@tryghost/content-api": "^1.11.21",
"@types/three": "^0.170.0",
"@types/unist": "^3.0.3",
"@vercel/analytics": "^1.4.1",
"cva": "1.0.0-beta.1",
"d3-time-format": "^4.1.0",
"embla-carousel-react": "^8.3.1",
"embla-carousel-wheel-gestures": "^8.0.1",
"framer-motion": "^11.11.9",
"framer-motion-3d": "^11.11.10",
"next": "14.2.15",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"three": "^0.169.0",
"three-stdlib": "^2.33.0",
"ts-pattern": "^5.5.0"
"github-slugger": "^2.0.0",
"google-auth-library": "^9.14.2",
"google-spreadsheet": "^4.1.4",
"next": "15.0.3",
"next-mdx-remote": "^5.0.0",
"react": "19.0.0-rc.1",
"react-children-utilities": "^2.10.0",
"react-dom": "19.0.0-rc.1",
"react-error-boundary": "^5.0.0",
"react-hook-form": "^7.53.1",
"server-only": "^0.0.1",
"three": "^0.170.0",
"three-stdlib": "^2.34.0",
"ts-pattern": "^5.5.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@types/d3-time-format": "^4.0.3",
"@types/mdx": "^2.0.13",
"@types/node": "^20",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"@types/tryghost__content-api": "^1.3.17",
"@types/unist": "^3.0.3",
"embla-carousel": "^8.3.1",
"eslint": "^8.57.1",
"eslint-config-next": "14.2.15",
"eslint-config-next": "15.0.3",
"eslint-plugin-tailwindcss": "^3.17.5",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"mdast-util-to-string": "^4.0.0",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"rehype-parse": "^8.0.4",
"rehype-pretty-code": "^0.14.0",
"rehype-react": "^7.2.0",
"rehype-slug": "^6.0.0",
"remark-mdx": "^3.1.0",
"tailwind-scrollbar-utilities": "^0.2.0",
"tailwindcss": "^3.4.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.10.0"
"typescript-eslint": "^8.10.0",
"unified": "^10.1.2",
"unist-util-visit": "^5.0.0"
},
"engines": {
"node": "20.x"
},
"lint-staged": {
"*.{js,mjs,ts,tsx}": [
@@ -49,6 +91,18 @@
],
"*.css": [
"prettier --write"
],
"*.{md,mdx}": [
"prettier --write"
]
},
"pnpm": {
"overrides": {
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
},
"patchedDependencies": {
"@react-three/postprocessing": "patches/@react-three__postprocessing.patch"
}
}
}
@@ -0,0 +1,16 @@
diff --git a/dist/EffectComposer.js b/dist/EffectComposer.js
index 335539d470b862efff4d34818a28d1ed78255005..c4e47fad5ea95dd28ef1570919bbcba0b1883ad8 100644
--- a/dist/EffectComposer.js
+++ b/dist/EffectComposer.js
@@ -77,9 +77,9 @@ const EffectComposer = React.memo(
useLayoutEffect(() => {
const passes = [];
if (group.current && instance.current && composer) {
- const children2 = instance.current.objects;
+ const children2 = instance.current.children;
for (let i = 0; i < children2.length; i++) {
- const child = children2[i];
+ const child = children2[i].object;
if (child instanceof Effect) {
const effects = [child];
if (!isConvolution(child)) {
+5939 -2907
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Some files were not shown because too many files have changed in this diff Show More