From 89287eb546a280f7abd54729f9b474d5d1cbd9e1 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Fri, 18 Jan 2019 19:48:37 +0300 Subject: [PATCH] configuration --- _config.dev.yml | 8 ++------ _config.prod.yml | 5 +---- db.json | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/_config.dev.yml b/_config.dev.yml index 5fe0a3aa..3beff047 100644 --- a/_config.dev.yml +++ b/_config.dev.yml @@ -5,7 +5,7 @@ author: Status language: en timezone: UTC -url: https://dev-keycard.status.im/ +url: https://status-im.github.io/keycard.status.im/public/ root: / permalink: news/:year/:month/:day/:title/ archive_dir: news @@ -19,7 +19,7 @@ deploy: type: git branch: gh-pages name: status-im-auto - repo: git@github.com:bitgamma/keycard.status.im.git + repo: git@github.com:status-im/keycard.status.im.git theme_config: favicon: img/logo-32.png @@ -27,7 +27,3 @@ theme_config: highlight: enable: true line_number: false - -disqus_shortname: hexojs -twitter: cryptowanderer -github: bitgamma diff --git a/_config.prod.yml b/_config.prod.yml index e5afcb83..08794baf 100644 --- a/_config.prod.yml +++ b/_config.prod.yml @@ -19,7 +19,7 @@ deploy: type: git branch: gh-pages name: status-im-auto - repo: git@github.com:bitgamma/keycard.status.im.git + repo: git@github.com:status-im/keycard.status.im.git theme_config: favicon: img/logo-32.png @@ -28,6 +28,3 @@ highlight: enable: true line_number: false -disqus_shortname: hexojs -twitter: cryptowanderer -github: bitgamma diff --git a/db.json b/db.json index 59154118..d4b1d640 100644 --- a/db.json +++ b/db.json @@ -1 +1 @@ -{"meta":{"version":1,"warehouse":"2.2.0"},"models":{"Asset":[{"_id":"source/CNAME","path":"CNAME","modified":0,"renderable":0},{"_id":"source/browserconfig.xml","path":"browserconfig.xml","modified":0,"renderable":0},{"_id":"source/robots.txt","path":"robots.txt","modified":0,"renderable":0},{"_id":"themes/navy/source/fonts/GTWalsheim-Bold.woff","path":"fonts/GTWalsheim-Bold.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/GTWalsheim-Bold.woff2","path":"fonts/GTWalsheim-Bold.woff2","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.woff","path":"fonts/PostGrotesk-Book.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/img/apple-touch-icon-1024.png","path":"img/apple-touch-icon-1024.png","modified":0,"renderable":1},{"_id":"themes/navy/source/img/apple-touch-icon-180.png","path":"img/apple-touch-icon-180.png","modified":0,"renderable":1},{"_id":"themes/navy/source/img/apple-touch-icon-120.png","path":"img/apple-touch-icon-120.png","modified":0,"renderable":1},{"_id":"themes/navy/source/img/apple-touch-icon-152.png","path":"img/apple-touch-icon-152.png","modified":0,"renderable":1},{"_id":"themes/navy/source/img/apple-touch-icon-76.png","path":"img/apple-touch-icon-76.png","modified":0,"renderable":1},{"_id":"themes/navy/source/img/arrow.svg","path":"img/arrow.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/circle.svg","path":"img/circle.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/close.svg","path":"img/close.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/clippy.svg","path":"img/clippy.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/footer-logo.svg","path":"img/footer-logo.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/funding.svg","path":"img/funding.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-arrow-down.svg","path":"img/icon-arrow-down.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-close.svg","path":"img/icon-close.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-contribute-blue.svg","path":"img/icon-contribute-blue.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-github-mobile.svg","path":"img/icon-github-mobile.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-discussion-blue.svg","path":"img/icon-discussion-blue.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-github.svg","path":"img/icon-github.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-gitter-mobile.svg","path":"img/icon-gitter-mobile.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-gitter.svg","path":"img/icon-gitter.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-join-blue.svg","path":"img/icon-join-blue.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-menu.svg","path":"img/icon-menu.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-twitter-mobile.svg","path":"img/icon-twitter-mobile.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon-twitter.svg","path":"img/icon-twitter.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon_fb.svg","path":"img/icon_fb.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon_gh.svg","path":"img/icon_gh.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon_rd.svg","path":"img/icon_rd.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon_ri.svg","path":"img/icon_ri.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon_yt.svg","path":"img/icon_yt.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/icon_tw.svg","path":"img/icon_tw.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/incubate_beaker.svg","path":"img/incubate_beaker.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/legal_reg.svg","path":"img/legal_reg.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/link-arrow.svg","path":"img/link-arrow.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-16.png","path":"img/logo-16.png","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-32.png","path":"img/logo-32.png","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-dribbble.svg","path":"img/logo-dribbble.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-black.svg","path":"img/logo-black.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-footer.svg","path":"img/logo-footer.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-flat.svg","path":"img/logo-flat.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-status.svg","path":"img/logo-status.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-github.svg","path":"img/logo-github.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo-youtube.svg","path":"img/logo-youtube.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/logo.svg","path":"img/logo.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/marketing_comm.svg","path":"img/marketing_comm.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/mentorship.svg","path":"img/mentorship.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/product_design.svg","path":"img/product_design.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/status_logo.svg","path":"img/status_logo.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/img/technical_support.svg","path":"img/technical_support.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/js/main.js","path":"js/main.js","modified":0,"renderable":1},{"_id":"themes/navy/source/js/vendor.js","path":"js/vendor.js","modified":0,"renderable":1},{"_id":"themes/navy/source/js/vendor.js.map","path":"js/vendor.js.map","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/archive.scss","path":"scss/archive.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/base.scss","path":"scss/base.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/fonts.scss","path":"scss/fonts.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/colors.scss","path":"scss/colors.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/footer.scss","path":"scss/footer.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/formReset.scss","path":"scss/formReset.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/header.scss","path":"scss/header.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/highlight.scss","path":"scss/highlight.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/index.scss","path":"scss/index.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/main.scss","path":"scss/main.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/page.scss","path":"scss/page.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/popup.scss","path":"scss/popup.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/scss/sidebar.scss","path":"scss/sidebar.scss","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.ttf","path":"fonts/PostGrotesk-Book.ttf","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.ttf","path":"fonts/PostGrotesk-Medium.ttf","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.woff","path":"fonts/PostGrotesk-Medium.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Medium.woff2","path":"fonts/Inter-UI-Medium.woff2","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Regular.woff2","path":"fonts/Inter-UI-Regular.woff2","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.eot","path":"fonts/PostGrotesk-Book.eot","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/RobotoRegular/RobotoRegular.eot","path":"fonts/RobotoRegular/RobotoRegular.eot","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/RobotoRegular/RobotoRegular.ttf","path":"fonts/RobotoRegular/RobotoRegular.ttf","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/RobotoRegular/RobotoRegular.woff","path":"fonts/RobotoRegular/RobotoRegular.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/androidstudio.css","path":"js/styles/androidstudio.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/agate.css","path":"js/styles/agate.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/arduino-light.css","path":"js/styles/arduino-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/arta.css","path":"js/styles/arta.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/ascetic.css","path":"js/styles/ascetic.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-cave-dark.css","path":"js/styles/atelier-cave-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-dune-light.css","path":"js/styles/atelier-dune-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-dune-dark.css","path":"js/styles/atelier-dune-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-cave-light.css","path":"js/styles/atelier-cave-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-estuary-dark.css","path":"js/styles/atelier-estuary-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-estuary-light.css","path":"js/styles/atelier-estuary-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-heath-dark.css","path":"js/styles/atelier-heath-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-forest-light.css","path":"js/styles/atelier-forest-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-heath-light.css","path":"js/styles/atelier-heath-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-forest-dark.css","path":"js/styles/atelier-forest-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-lakeside-dark.css","path":"js/styles/atelier-lakeside-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-lakeside-light.css","path":"js/styles/atelier-lakeside-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-plateau-dark.css","path":"js/styles/atelier-plateau-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-plateau-light.css","path":"js/styles/atelier-plateau-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-savanna-dark.css","path":"js/styles/atelier-savanna-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-savanna-light.css","path":"js/styles/atelier-savanna-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-seaside-dark.css","path":"js/styles/atelier-seaside-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-seaside-light.css","path":"js/styles/atelier-seaside-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-sulphurpool-dark.css","path":"js/styles/atelier-sulphurpool-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atelier-sulphurpool-light.css","path":"js/styles/atelier-sulphurpool-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atom-one-dark.css","path":"js/styles/atom-one-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/atom-one-light.css","path":"js/styles/atom-one-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/brown-paper.css","path":"js/styles/brown-paper.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/codepen-embed.css","path":"js/styles/codepen-embed.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/brown-papersq.png","path":"js/styles/brown-papersq.png","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/color-brewer.css","path":"js/styles/color-brewer.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/darcula.css","path":"js/styles/darcula.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/dark.css","path":"js/styles/dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/darkula.css","path":"js/styles/darkula.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/default.css","path":"js/styles/default.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/docco.css","path":"js/styles/docco.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/dracula.css","path":"js/styles/dracula.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/far.css","path":"js/styles/far.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/foundation.css","path":"js/styles/foundation.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/github-gist.css","path":"js/styles/github-gist.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/github.css","path":"js/styles/github.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/googlecode.css","path":"js/styles/googlecode.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/grayscale.css","path":"js/styles/grayscale.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/gruvbox-dark.css","path":"js/styles/gruvbox-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/gruvbox-light.css","path":"js/styles/gruvbox-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/hopscotch.css","path":"js/styles/hopscotch.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/hybrid.css","path":"js/styles/hybrid.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/idea.css","path":"js/styles/idea.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/ir-black.css","path":"js/styles/ir-black.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/kimbie.dark.css","path":"js/styles/kimbie.dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/kimbie.light.css","path":"js/styles/kimbie.light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/magula.css","path":"js/styles/magula.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/mono-blue.css","path":"js/styles/mono-blue.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/monokai-sublime.css","path":"js/styles/monokai-sublime.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/obsidian.css","path":"js/styles/obsidian.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/monokai.css","path":"js/styles/monokai.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/ocean.css","path":"js/styles/ocean.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/paraiso-dark.css","path":"js/styles/paraiso-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/paraiso-light.css","path":"js/styles/paraiso-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/pojoaque.jpg","path":"js/styles/pojoaque.jpg","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/pojoaque.css","path":"js/styles/pojoaque.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/purebasic.css","path":"js/styles/purebasic.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/qtcreator_dark.css","path":"js/styles/qtcreator_dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/qtcreator_light.css","path":"js/styles/qtcreator_light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/railscasts.css","path":"js/styles/railscasts.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/rainbow.css","path":"js/styles/rainbow.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/routeros.css","path":"js/styles/routeros.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/school-book.css","path":"js/styles/school-book.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/school-book.png","path":"js/styles/school-book.png","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/solarized-dark.css","path":"js/styles/solarized-dark.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/solarized-light.css","path":"js/styles/solarized-light.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/sunburst.css","path":"js/styles/sunburst.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/tomorrow-night-bright.css","path":"js/styles/tomorrow-night-bright.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/tomorrow-night-blue.css","path":"js/styles/tomorrow-night-blue.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/tomorrow-night.css","path":"js/styles/tomorrow-night.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/tomorrow-night-eighties.css","path":"js/styles/tomorrow-night-eighties.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/vs.css","path":"js/styles/vs.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/tomorrow.css","path":"js/styles/tomorrow.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/vs2015.css","path":"js/styles/vs2015.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/xcode.css","path":"js/styles/xcode.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/xt256.css","path":"js/styles/xt256.css","modified":0,"renderable":1},{"_id":"themes/navy/source/js/styles/zenburn.css","path":"js/styles/zenburn.css","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Bold.woff2","path":"fonts/Inter-UI-Bold.woff2","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-BoldItalic.woff2","path":"fonts/Inter-UI-BoldItalic.woff2","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Italic.woff2","path":"fonts/Inter-UI-Italic.woff2","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-MediumItalic.woff2","path":"fonts/Inter-UI-MediumItalic.woff2","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Regular.woff","path":"fonts/Inter-UI-Regular.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Bold.woff","path":"fonts/Inter-UI-Bold.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Italic.woff","path":"fonts/Inter-UI-Italic.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-Medium.woff","path":"fonts/Inter-UI-Medium.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-MediumItalic.woff","path":"fonts/Inter-UI-MediumItalic.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.eot","path":"fonts/PostGrotesk-Medium.eot","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/Inter-UI-BoldItalic.woff","path":"fonts/Inter-UI-BoldItalic.woff","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.svg","path":"fonts/PostGrotesk-Medium.svg","modified":0,"renderable":1},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.svg","path":"fonts/PostGrotesk-Book.svg","modified":0,"renderable":1}],"Cache":[{"_id":"source/CNAME","hash":"5f91c14c4301d8db36efb9fe206593bf60ffa5e8","modified":1544609960146},{"_id":"source/browserconfig.xml","hash":"adb4cbc9b5bc6e7526871522d199860ee2fd65f4","modified":1544609960149},{"_id":"source/index.md","hash":"d12b19fb2b7b8dce0da020c0438f27b2c776c554","modified":1544609960150},{"_id":"source/robots.txt","hash":"7e49dfd97319f5dd7cdaea8518cf43e0e8d01e5a","modified":1544609960150},{"_id":"source/_data/menu.yml","hash":"9fe5da7dd7fc198763c5ba8fd8d14b67e3807b21","modified":1544609960147},{"_id":"source/_data/sidebar.yml","hash":"02b68005e427a8b9707423e72aee37f6ed6de22b","modified":1547719551519},{"_id":"source/_data/languages.yml","hash":"74e55635eb66bb12833e42f0d1057b03beb65bcf","modified":1544609960146},{"_id":"source/_posts/2018-10-04-incubate.md","hash":"08adbeeeeefd08cec4afbfa3ff0b1276cb1b61ae","modified":1544609960148},{"_id":"source/api/index.md","hash":"4ee5e03c3d1c7dd5791e618314d108e6fac355e6","modified":1547714319931},{"_id":"source/tutorials/go_tutorial.md","hash":"b20974b4273d84cfd53e40fd1a1a5563d39aaedc","modified":1544609960150},{"_id":"source/tutorials/index.md","hash":"54e8f725419b89ebe5aa1a21bba1a1c204113c13","modified":1544609960151},{"_id":"themes/navy/languages/en.yml","hash":"93267f1b8c8d49b95862ca72ac4a446a86e6eac0","modified":1547719551510},{"_id":"themes/navy/layout/index.swig","hash":"c6236a0e31e68ad85b37258068fccdf05d603093","modified":1544609960153},{"_id":"themes/navy/layout/archive.swig","hash":"6076f145cb3bcc55353e1c6169f4ea7f42461af2","modified":1544609960152},{"_id":"themes/navy/layout/layout.swig","hash":"0209073ec2800370429d9bc0e6ef4d32a52b003d","modified":1544609960153},{"_id":"themes/navy/layout/page.swig","hash":"00cd03a847e1e55430a18274780d6a789423fe07","modified":1547719772483},{"_id":"themes/navy/layout/partial/after_footer.swig","hash":"e8faf987ec0d73b551c243cbff204061a16ca238","modified":1544609960155},{"_id":"themes/navy/layout/partial/comment.swig","hash":"6f5494c97586584884ca8d93b9184d4eadbd8e86","modified":1544609960155},{"_id":"themes/navy/layout/partial/head.swig","hash":"70a2fa82452fcca56d8183f9fa7770962ed26f60","modified":1544609960156},{"_id":"themes/navy/layout/partial/mobile_nav.swig","hash":"091be443f0a7d6bb4ba8c707f05fe9b8a1b8cde2","modified":1544609960157},{"_id":"themes/navy/layout/partial/header.swig","hash":"f8c2d3cf32ee8d91dbc225508bc8c676b60f6f38","modified":1544609960157},{"_id":"themes/navy/layout/partial/post.swig","hash":"e75df7f513ec321d2a95b8f689470adbab955760","modified":1544609960158},{"_id":"themes/navy/layout/partial/share.swig","hash":"c5d108982425a9b1a63c062b6773c4cdd43024ce","modified":1544609960158},{"_id":"themes/navy/layout/partial/sidebar.swig","hash":"6bb7120a1ab8eadf5089d16f63100ce7f36d7343","modified":1544609960163},{"_id":"themes/navy/layout/partial/theme.swig","hash":"414bd84be19a8c19c0d901a4f2574c8298c4e111","modified":1544609960163},{"_id":"themes/navy/layout/partial/tutorial.swig","hash":"8cdc4618ddfe277604895e7a37d76f3f54bb7556","modified":1544609960163},{"_id":"themes/navy/source/fonts/GTWalsheim-Bold.woff","hash":"32948c987f6e798d289d7e54fc457b390cead30b","modified":1544609960164},{"_id":"themes/navy/source/fonts/GTWalsheim-Bold.woff2","hash":"95267ddeb508fff67420bcbdd6fb18c8994da3af","modified":1544609960165},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.woff","hash":"d65f746630c83d89cd5da6e08f4cbb7b522a1176","modified":1544609960184},{"_id":"themes/navy/source/img/apple-touch-icon-1024.png","hash":"a55715cf24bc071478bbb7d0124f60761dff8a40","modified":1544609960193},{"_id":"themes/navy/source/img/apple-touch-icon-180.png","hash":"6911dee3827473345f81e1f7ea7422c52c1d0627","modified":1544609960195},{"_id":"themes/navy/source/img/apple-touch-icon-120.png","hash":"0502d7eb9584efa325830cf588001c2aef129063","modified":1544609960193},{"_id":"themes/navy/source/img/apple-touch-icon-152.png","hash":"420c42bfcc4b487b3a70eb06dc5d4ea0ffcdbbe1","modified":1544609960194},{"_id":"themes/navy/source/img/apple-touch-icon-76.png","hash":"2adb7bd7332d523749f6b8757b4b986b0dd32a57","modified":1544609960195},{"_id":"themes/navy/source/img/arrow.svg","hash":"5e77ef571583e45192a76885f51e95e55a154d93","modified":1544609960196},{"_id":"themes/navy/source/img/circle.svg","hash":"4c3fd2f97fa2ada817bfcb081bdcc2d0d288be6c","modified":1544609960196},{"_id":"themes/navy/source/img/close.svg","hash":"dd96a639e6afebe8e1c43c5eea397950a290dc42","modified":1544609960197},{"_id":"themes/navy/source/img/clippy.svg","hash":"386f533083a450bb34f87dab852e495195a7fddb","modified":1544609960197},{"_id":"themes/navy/source/img/footer-logo.svg","hash":"0a4b5ad7aaddf5085b4413e1958ce0f11c87f2c5","modified":1544609960198},{"_id":"themes/navy/source/img/funding.svg","hash":"de90fec4d7bcbbdbda74145d52be5d3cc3d17a61","modified":1544609960198},{"_id":"themes/navy/source/img/icon-arrow-down.svg","hash":"1974bd9f76f041dfd01754152a35c8c641ca981b","modified":1544609960199},{"_id":"themes/navy/source/img/icon-close.svg","hash":"8e19984a84534fbebeae7fa1c5d634d2b9e79ed8","modified":1544609960199},{"_id":"themes/navy/source/img/icon-contribute-blue.svg","hash":"39abb4ebba6618172f7954bd4ca0280983532501","modified":1544609960200},{"_id":"themes/navy/source/img/icon-github-mobile.svg","hash":"388914e14c23a3084fbffea5e634cd467353d180","modified":1544609960201},{"_id":"themes/navy/source/img/icon-discussion-blue.svg","hash":"7c251bcdf17e7cde71c60e4422d2d3c0accf2a03","modified":1544609960200},{"_id":"themes/navy/source/img/icon-github.svg","hash":"e359a807a062cfe17fffc79d35885a448c0b4caf","modified":1544609960201},{"_id":"themes/navy/source/img/icon-gitter-mobile.svg","hash":"3f1486e02d7fbf67b1e17df216b2d2c077d6bea1","modified":1544609960202},{"_id":"themes/navy/source/img/icon-gitter.svg","hash":"c9f157c50409187c48855adc8b50aeb82849060c","modified":1544609960202},{"_id":"themes/navy/source/img/icon-join-blue.svg","hash":"0ec731f6c295a6e7f0d6283c8439beb7d95310b6","modified":1544609960203},{"_id":"themes/navy/source/img/icon-menu.svg","hash":"67498a8b63ecda345c9ad767ae744bce814cd532","modified":1544609960203},{"_id":"themes/navy/source/img/icon-twitter-mobile.svg","hash":"234bff41b8a5c37a4e3742e0762c4d670ad49e38","modified":1544609960204},{"_id":"themes/navy/source/img/icon-twitter.svg","hash":"83274ec106864a2cd9aa7d4997b10f864195ff5a","modified":1544609960204},{"_id":"themes/navy/source/img/icon_fb.svg","hash":"7b2b44b6bc9768ee15f4f26f4da614a86e6fd18d","modified":1544609960205},{"_id":"themes/navy/source/img/icon_gh.svg","hash":"6229f2ef4fa907f9ccf0bbb839a406aa5bb45ef2","modified":1544609960205},{"_id":"themes/navy/source/img/icon_rd.svg","hash":"4c910721d50f5554048771e922b37afb4f812257","modified":1544609960206},{"_id":"themes/navy/source/img/icon_ri.svg","hash":"f31fbefda73ad806fe0e4338a73e04a7ddeb49b1","modified":1544609960206},{"_id":"themes/navy/source/img/icon_yt.svg","hash":"4413001c81c697cf8564f09972858b0d9a1c142e","modified":1544609960207},{"_id":"themes/navy/source/img/icon_tw.svg","hash":"e08f16d1fed449a70511af301f75dc7b660b5992","modified":1544609960207},{"_id":"themes/navy/source/img/incubate_beaker.svg","hash":"2152a4313242a159947ee4820da44b337b7fdad5","modified":1544609960207},{"_id":"themes/navy/source/img/legal_reg.svg","hash":"a4c4d62a28cad2e71265469cb1a7a04ea40c8c9f","modified":1544609960208},{"_id":"themes/navy/source/img/link-arrow.svg","hash":"83418d8c8fb726909c67e28a44860faf3ee61839","modified":1544609960208},{"_id":"themes/navy/source/img/logo-16.png","hash":"52e283cbaaa297fdd0925d9d2d700d6e2b9dbcca","modified":1544609960210},{"_id":"themes/navy/source/img/logo-32.png","hash":"7b2f46c4f1fb0b76048d69da225109f576f82ed8","modified":1544609960210},{"_id":"themes/navy/source/img/logo-dribbble.svg","hash":"476419dd573a8fd23c072beb2c5ce50c3afa77d1","modified":1544609960211},{"_id":"themes/navy/source/img/logo-black.svg","hash":"c468f15ceb77914466b483c5f5237cd32b72723b","modified":1544609960211},{"_id":"themes/navy/source/img/logo-footer.svg","hash":"00b5d579f1675f01a03912cd3af70ef7748fdef9","modified":1544609960212},{"_id":"themes/navy/source/img/logo-flat.svg","hash":"44473193100b9dd07760a2eeebae1ddffe3c3fd1","modified":1544609960212},{"_id":"themes/navy/source/img/logo-status.svg","hash":"ac6d61eacd2634b08819ce4b641411806903a820","modified":1544609960213},{"_id":"themes/navy/source/img/logo-github.svg","hash":"60358ac6ecddf611fe7cb1928197f4c75163c3a9","modified":1544609960213},{"_id":"themes/navy/source/img/logo-youtube.svg","hash":"3ae5d09fa03adc4e8fdbef19db2d1aa11fe95cd4","modified":1544609960214},{"_id":"themes/navy/source/img/logo.svg","hash":"55bce003cf21684686f6968d2e1264c92a52744f","modified":1544609960214},{"_id":"themes/navy/source/img/marketing_comm.svg","hash":"e63b7dec56daf0bbc587a201d9b501e56f9d2817","modified":1544609960215},{"_id":"themes/navy/source/img/mentorship.svg","hash":"4d65cdd9dcd1746a720cf2780520d03742dcb08d","modified":1544609960215},{"_id":"themes/navy/source/img/product_design.svg","hash":"0d8816bac119f96fb16a0a998251746c73b3deb7","modified":1544609960216},{"_id":"themes/navy/source/img/status_logo.svg","hash":"98b9dabe54674dc55d03196bcb2650be8af5441c","modified":1544609960216},{"_id":"themes/navy/source/img/technical_support.svg","hash":"0e334805845d145abcdcced17d677dd2f153cfca","modified":1544609960217},{"_id":"themes/navy/source/js/main.js","hash":"33222c9e6cccc6923fbf502ed11ea5eaa232f6f1","modified":1544609960218},{"_id":"themes/navy/source/js/vendor.js","hash":"788f005abc61d3e9f0f91cadb981664e87d37fe5","modified":1544613285181},{"_id":"themes/navy/source/js/vendor.js.map","hash":"3bdb57f6252e6e6025e4c9fee89dc385bdb4c5d2","modified":1544613285177},{"_id":"themes/navy/source/scss/_variables.scss","hash":"d51d45133f4c2bed4ab2d9778addbd18868981c2","modified":1544609960259},{"_id":"themes/navy/source/scss/archive.scss","hash":"ede7e53a832bf717ebd71b5c986be22d0a3185dd","modified":1544609960259},{"_id":"themes/navy/source/scss/base.scss","hash":"7685e0948b092ac2308b2e842e944aa450e177bd","modified":1544609960259},{"_id":"themes/navy/source/scss/fonts.scss","hash":"bf5c7f0d6789b2ce1a45cbcd9a1bb5e5b17d5b0e","modified":1544609960260},{"_id":"themes/navy/source/scss/colors.scss","hash":"a9e24cea9b3b37a32dfce9c80dc27f970b9a38ff","modified":1544609960260},{"_id":"themes/navy/source/scss/footer.scss","hash":"89ef743275dcb564ca1cfe18404b3b1f7c5e375f","modified":1544609960261},{"_id":"themes/navy/source/scss/formReset.scss","hash":"bf4b9f20523fc5907dfb438f6fac20201bb626cf","modified":1544609960261},{"_id":"themes/navy/source/scss/header.scss","hash":"3921b1418b6d640a41dcfa72089d5ce316e32a1e","modified":1544609960262},{"_id":"themes/navy/source/scss/highlight.scss","hash":"12e0566bea22131ec402193ef9e6942f3e2ca40b","modified":1544609960262},{"_id":"themes/navy/source/scss/index.scss","hash":"7fff944e00ed147f1790b83be1c7647ca0dc14ae","modified":1544609960263},{"_id":"themes/navy/source/scss/main.scss","hash":"80fc4723dea991b40fdf62959d8e84b50db032b2","modified":1544609960263},{"_id":"themes/navy/source/scss/page.scss","hash":"c4ecdec36bf933ba7849e9d7fecfa1ca719dbb87","modified":1544609960264},{"_id":"themes/navy/source/scss/popup.scss","hash":"5830b5d3679d51a32653a2a4d8b1155b32bb13a5","modified":1544609960264},{"_id":"themes/navy/source/scss/sidebar.scss","hash":"c51af117e47dd302edf188f43629cb6036797095","modified":1544609960265},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.ttf","hash":"3a85fb6216d4cc0fc888da2b919a8b1effee12de","modified":1544609960183},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.ttf","hash":"2795ef51769efc5b85e8170a6423dfe71e31341c","modified":1544609960188},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.woff","hash":"5e5549a6b44673303caf45984a7a6b4f00bcf0d3","modified":1544609960189},{"_id":"themes/navy/layout/partial/shared-partials/LICENSE","hash":"d22157abc0fc0b4ae96380c09528e23cf77290a9","modified":1544609960159},{"_id":"themes/navy/layout/partial/shared-partials/README.md","hash":"7b7c568bb486a3ba1d7c43b11ee4c3d1dd24b27c","modified":1544609960159},{"_id":"themes/navy/source/fonts/Inter-UI-Medium.woff2","hash":"fc61451b9c27e2df7cad509d103a705a32ffc453","modified":1544609960174},{"_id":"themes/navy/source/fonts/Inter-UI-Regular.woff2","hash":"96f5364d43c588110e3a3ab447d9b377eed80409","modified":1544609960179},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.eot","hash":"264f4f9745237ca98b3cd796bf2d3a21b232e6c5","modified":1544609960180},{"_id":"themes/navy/source/fonts/RobotoRegular/RobotoRegular.eot","hash":"f045fe2f6ec39d5d794e31e9ab621fdd230c0696","modified":1544609960190},{"_id":"themes/navy/source/fonts/RobotoRegular/RobotoRegular.ttf","hash":"af03242b7545da3e54913906aaadce6a55c5f3bd","modified":1544609960191},{"_id":"themes/navy/source/fonts/RobotoRegular/RobotoRegular.woff","hash":"c729757be40622e32a3cdee9e9ad4eabf80d38bc","modified":1544609960192},{"_id":"themes/navy/source/js/styles/androidstudio.css","hash":"958baa24814c06a625612a3b2b478d54bc1bf1b1","modified":1544609960219},{"_id":"themes/navy/source/js/styles/agate.css","hash":"8e122b0f00f5a7ec4e6dc492bf1560441eeef7f0","modified":1544609960218},{"_id":"themes/navy/source/js/styles/arduino-light.css","hash":"c6e05580b51b755e229e99eb156940ad2cab192b","modified":1544609960219},{"_id":"themes/navy/source/js/styles/arta.css","hash":"17b23b9fa57ef7a05a6aaeea9b5feb5442a8e584","modified":1544609960219},{"_id":"themes/navy/source/js/styles/ascetic.css","hash":"6358377b5c25667886aca0d605cbc497cf02405f","modified":1544609960221},{"_id":"themes/navy/source/js/styles/atelier-cave-dark.css","hash":"f397d4418ce88b998841fd9135242461ba1a79b5","modified":1544609960221},{"_id":"themes/navy/source/js/styles/atelier-dune-light.css","hash":"7ba074de897e6a5e27d8b97f7cd06c1746474e72","modified":1544609960223},{"_id":"themes/navy/source/js/styles/atelier-dune-dark.css","hash":"081d73e454db140cd41b2bb595be297cfcab25e0","modified":1544609960222},{"_id":"themes/navy/source/js/styles/atelier-cave-light.css","hash":"2933f0247ac6d84c2954dd4946e359853abbf70a","modified":1544609960222},{"_id":"themes/navy/source/js/styles/atelier-estuary-dark.css","hash":"a0c46a0f955e3864f5e967ea93f5e61519a17be8","modified":1544609960223},{"_id":"themes/navy/source/js/styles/atelier-estuary-light.css","hash":"91ae4668c15a085ffce15ca21e93da445b5ecf3a","modified":1544609960223},{"_id":"themes/navy/source/js/styles/atelier-heath-dark.css","hash":"b93c2241ff123e62d4edb3dfc20410e4d1da3e78","modified":1544609960225},{"_id":"themes/navy/source/js/styles/atelier-forest-light.css","hash":"8a78a4eea0f32d094d1f9e316d59e990ba739d97","modified":1544609960224},{"_id":"themes/navy/source/js/styles/atelier-heath-light.css","hash":"172b98f783d213a20211ec6aca9a3840ba524f55","modified":1544609960225},{"_id":"themes/navy/source/js/styles/atelier-forest-dark.css","hash":"d8a4dc060b3fc719aa2f7d7b3f1019a3964b8101","modified":1544609960224},{"_id":"themes/navy/source/js/styles/atelier-lakeside-dark.css","hash":"fa707b252d5d5caccc0589374522bed47b7ca100","modified":1544609960226},{"_id":"themes/navy/source/js/styles/atelier-lakeside-light.css","hash":"8ba595b9ba6e8be6dc029bf80caab38e85aed686","modified":1544609960226},{"_id":"themes/navy/source/js/styles/atelier-plateau-dark.css","hash":"240f79f4e1fd63485c13900875b64c5a0d1bd06d","modified":1544609960227},{"_id":"themes/navy/source/js/styles/atelier-plateau-light.css","hash":"9731db1052f23351c983210701edd3f5ceed343f","modified":1544609960227},{"_id":"themes/navy/source/js/styles/atelier-savanna-dark.css","hash":"09d45a218f87b8cb55b5ca7f4e9d76ea89a9404a","modified":1544609960228},{"_id":"themes/navy/source/js/styles/atelier-savanna-light.css","hash":"6bd3a62c32558476d436bd389500e5fbeb693d67","modified":1544609960228},{"_id":"themes/navy/source/js/styles/atelier-seaside-dark.css","hash":"b2800804a21f729a3d0a16b3aadc17679fd0639c","modified":1544609960229},{"_id":"themes/navy/source/js/styles/atelier-seaside-light.css","hash":"3e6b9e2a3a5de455490b8224401f19702df4cde9","modified":1544609960229},{"_id":"themes/navy/source/js/styles/atelier-sulphurpool-dark.css","hash":"1f006f8bd28e2ffbb73f708769605ba766787fff","modified":1544609960230},{"_id":"themes/navy/source/js/styles/atelier-sulphurpool-light.css","hash":"0319c3eea893601b79b6c57652ef49b9222cc9de","modified":1544609960230},{"_id":"themes/navy/source/js/styles/atom-one-dark.css","hash":"a6d28e1c04cee20cd874fc7ac0903d8e2e4bd54e","modified":1544609960231},{"_id":"themes/navy/source/js/styles/atom-one-light.css","hash":"eda63d8cce440dbf3bb823e10577a134e9941deb","modified":1544609960231},{"_id":"themes/navy/source/js/styles/brown-paper.css","hash":"a6817d890e58f80ce79d87620791dae821a70fff","modified":1544609960231},{"_id":"themes/navy/source/js/styles/codepen-embed.css","hash":"c4520e45d18259817b8942d17971f27c94f0fb09","modified":1544609960232},{"_id":"themes/navy/source/js/styles/brown-papersq.png","hash":"3a1332ede3a75a3d24f60b6ed69035b72da5e182","modified":1544609960232},{"_id":"themes/navy/source/js/styles/color-brewer.css","hash":"96332573db854e7b7411caa94ba29b238fede2d3","modified":1544609960233},{"_id":"themes/navy/source/js/styles/darcula.css","hash":"7740224d07375ddc321147dffabbfa83e39f0d8b","modified":1544609960233},{"_id":"themes/navy/source/js/styles/dark.css","hash":"fc77519d4f5d731054c5d4b7e7bbdbb510833271","modified":1544609960234},{"_id":"themes/navy/source/js/styles/darkula.css","hash":"0be948bb84acc05f93a1e5e9b48fe34cf61673a0","modified":1544609960234},{"_id":"themes/navy/source/js/styles/default.css","hash":"fba68624d1b34a5543fe0bf4b2af2ac1ddf65e74","modified":1544609960235},{"_id":"themes/navy/source/js/styles/docco.css","hash":"1be7be09a1b927c22c7f11451becdb335145bdd2","modified":1544609960235},{"_id":"themes/navy/source/js/styles/dracula.css","hash":"2633f2e84680e9f381e9ac1df344b542e28f9774","modified":1544609960235},{"_id":"themes/navy/source/js/styles/far.css","hash":"67e0658b2376e91e4894636a3522a30c2aec42de","modified":1544609960236},{"_id":"themes/navy/source/js/styles/foundation.css","hash":"75b0674dd1ed35d61977bd5c35dc29ca35835a7b","modified":1544609960236},{"_id":"themes/navy/source/js/styles/github-gist.css","hash":"77cf684fda415812d6279a52f299c84850244778","modified":1544609960237},{"_id":"themes/navy/source/js/styles/github.css","hash":"ee593952684a791317ee8b77ad096e729dec649e","modified":1544609960237},{"_id":"themes/navy/source/js/styles/googlecode.css","hash":"fed3d439d0c305b337dd9c0f68dcbfa51429f445","modified":1544609960237},{"_id":"themes/navy/source/js/styles/grayscale.css","hash":"5688658c28fc5799517e8f3c224ae3da3797ba44","modified":1544609960238},{"_id":"themes/navy/source/js/styles/gruvbox-dark.css","hash":"aaf90d076e34bc44016462d70f83985e0e55c8dc","modified":1544609960238},{"_id":"themes/navy/source/js/styles/gruvbox-light.css","hash":"084699ab0aa326fede86e38bf41ebe49edde3a90","modified":1544609960239},{"_id":"themes/navy/source/js/styles/hopscotch.css","hash":"03e7b9ec74bbd375db8d476836aac3729a6c86e8","modified":1544609960239},{"_id":"themes/navy/source/js/styles/hybrid.css","hash":"1e2d54598b5f948b597059909d4bd158b7df021f","modified":1544609960240},{"_id":"themes/navy/source/js/styles/idea.css","hash":"164649ae1e7c891a0d88cca075521af28656e2a1","modified":1544609960240},{"_id":"themes/navy/source/js/styles/ir-black.css","hash":"95aad65ba77183500ce0f7ad62a7535b647ee20c","modified":1544609960241},{"_id":"themes/navy/source/js/styles/kimbie.dark.css","hash":"58ed061c204fbc09d221ee9135d66bad976e5fc3","modified":1544609960241},{"_id":"themes/navy/source/js/styles/kimbie.light.css","hash":"b7aa9b1b21a25dfefcac7649a328962f84c47913","modified":1544609960242},{"_id":"themes/navy/source/js/styles/magula.css","hash":"ebc291f90e456abeeccb16937e7f51735733f4f7","modified":1544609960242},{"_id":"themes/navy/source/js/styles/mono-blue.css","hash":"fd0f39cd4c93ccb8d0bb2f6a1f359522e15a89cb","modified":1544609960243},{"_id":"themes/navy/source/js/styles/monokai-sublime.css","hash":"e4f3df87cbfa634c86b489e9cc43acf44d415986","modified":1544609960243},{"_id":"themes/navy/source/js/styles/obsidian.css","hash":"6ed475813fe4886a58b236297862ff2f181e66f7","modified":1544609960245},{"_id":"themes/navy/source/js/styles/monokai.css","hash":"f69df92e3ccdce28e6f811ec84f11698f2811198","modified":1544609960245},{"_id":"themes/navy/source/js/styles/ocean.css","hash":"fce6858e1c5eb9d2857cb5b5d24069c5994cfc91","modified":1544609960246},{"_id":"themes/navy/source/js/styles/paraiso-dark.css","hash":"8f5445327ce3c83b62f6bf4c1a0d87fa6f036341","modified":1544609960246},{"_id":"themes/navy/source/js/styles/paraiso-light.css","hash":"6eb3ab7a3337f9cad3a2e5fa6bf7dd83685228d8","modified":1544609960247},{"_id":"themes/navy/source/js/styles/pojoaque.jpg","hash":"c5fe6533b88b21f8d90d3d03954c6b29baa67791","modified":1544609960247},{"_id":"themes/navy/source/js/styles/pojoaque.css","hash":"4e1e6a431212f5043a550474a1acda63362cdd6f","modified":1544609960247},{"_id":"themes/navy/source/js/styles/purebasic.css","hash":"f353a2cedf3f261a0676fce7c824bdd2e8197775","modified":1544609960248},{"_id":"themes/navy/source/js/styles/qtcreator_dark.css","hash":"213a40d203c4986cdbcb1bdf7d0b9013b29041ba","modified":1544609960248},{"_id":"themes/navy/source/js/styles/qtcreator_light.css","hash":"9a2a19ac2f6e6a7d5edd7fae67b7de4a3957e878","modified":1544609960249},{"_id":"themes/navy/source/js/styles/railscasts.css","hash":"a6d2043478fae5915926914cbd96fe9b706d98a6","modified":1544609960249},{"_id":"themes/navy/source/js/styles/rainbow.css","hash":"1b2d98ccdda36aa926d0e6d069b673fdacd2d33e","modified":1544609960249},{"_id":"themes/navy/source/js/styles/routeros.css","hash":"fc5db7c8f18d6b31ad92df21a51e7867d459af19","modified":1544609960250},{"_id":"themes/navy/source/js/styles/school-book.css","hash":"60fe3d8063b1acc4d52de02033095adb0a2b35a9","modified":1544609960251},{"_id":"themes/navy/source/js/styles/school-book.png","hash":"711ec983c874e093bb89eb77afcbdf6741fa61ee","modified":1544609960251},{"_id":"themes/navy/source/js/styles/solarized-dark.css","hash":"d02fc2dcbeec4b7af2cadec4bbbfc5b016aed4c7","modified":1544609960251},{"_id":"themes/navy/source/js/styles/solarized-light.css","hash":"6b70caf1e84d096b1bc6318d5dae78d69e5dd1d3","modified":1544609960252},{"_id":"themes/navy/source/js/styles/sunburst.css","hash":"8309eab2e5b1765dbee81a626baacbdad869b76a","modified":1544609960252},{"_id":"themes/navy/source/js/styles/tomorrow-night-bright.css","hash":"04f0af30fdda5e5d6ebdeef5a860b6b7e49cfe89","modified":1544609960253},{"_id":"themes/navy/source/js/styles/tomorrow-night-blue.css","hash":"cd257d7d6a37cd5a09419b5f5f9d34b6b282423f","modified":1544609960253},{"_id":"themes/navy/source/js/styles/tomorrow-night.css","hash":"86264dd861d35a8b135f9fcb8ff2675e9fa69c16","modified":1544609960255},{"_id":"themes/navy/source/js/styles/tomorrow-night-eighties.css","hash":"d82b84bcda0588105dbbc0e8e8ba5e62c208a061","modified":1544609960254},{"_id":"themes/navy/source/js/styles/vs.css","hash":"2ac5e89ceb3d5a0e0fdab1ed6d9a411ec7d221aa","modified":1544609960255},{"_id":"themes/navy/source/js/styles/tomorrow.css","hash":"163593ad70770d0296c5e643fa62e58e63f1b340","modified":1544609960255},{"_id":"themes/navy/source/js/styles/vs2015.css","hash":"3c7fa677de2a785d90fc6c3f7520ac1b11bfd37a","modified":1544609960256},{"_id":"themes/navy/source/js/styles/xcode.css","hash":"65d775a7e11e238c91e0d3c7370547348c92d6b3","modified":1544609960256},{"_id":"themes/navy/source/js/styles/xt256.css","hash":"bbe28ec69177699cb6300d777598adf6323f7861","modified":1544609960257},{"_id":"themes/navy/source/js/styles/zenburn.css","hash":"933a3b196d01254dea5e6f48105ea15e210ae000","modified":1544609960257},{"_id":"themes/navy/source/fonts/Inter-UI-Bold.woff2","hash":"51e75c772ad5bb111cf98d4ceb349cfdaf709252","modified":1544609960168},{"_id":"themes/navy/source/fonts/Inter-UI-BoldItalic.woff2","hash":"cfded946f4f1f1b25c57f76f9bd291fa1af02feb","modified":1544609960170},{"_id":"themes/navy/source/fonts/Inter-UI-Italic.woff2","hash":"bc95f46cc6172ab234234688a44b2372688d9b40","modified":1544609960172},{"_id":"themes/navy/source/fonts/Inter-UI-MediumItalic.woff2","hash":"6e03a165a849a462536ae429e7d8ee47f5e0b557","modified":1544609960176},{"_id":"themes/navy/source/fonts/Inter-UI-Regular.woff","hash":"4b8534f53317657526b9d0fa620f80754b4d0c99","modified":1544609960178},{"_id":"themes/navy/layout/partial/shared-partials/partials/after_footer.swig","hash":"e0732a96599f76aa3865f9a08f4fccf92f03d885","modified":1544609960160},{"_id":"themes/navy/layout/partial/shared-partials/partials/footer.swig","hash":"6e85d1d837ca233c392aecb8c3378b9f1e0d8ae8","modified":1544609960161},{"_id":"themes/navy/layout/partial/shared-partials/partials/head.swig","hash":"1b6d8673e3ba3b93524e5c8602a01a998f978af2","modified":1544609960161},{"_id":"themes/navy/layout/partial/shared-partials/partials/community-popup.swig","hash":"5fda1bb15cb009a7b2eab9d0aa1f40a5fc94311e","modified":1544609960160},{"_id":"themes/navy/layout/partial/shared-partials/partials/header-nav.swig","hash":"c9832d7ffe487124b44718689a125f2a035d652f","modified":1544609960162},{"_id":"themes/navy/source/fonts/Inter-UI-Bold.woff","hash":"bce41a7fe8e352983cfbcebaa6e93dff9623c2e5","modified":1544609960167},{"_id":"themes/navy/layout/partial/shared-partials/partials/projects-popup.swig","hash":"0c514745e5f02128c1819c2b002c8f01a6678723","modified":1544609960162},{"_id":"themes/navy/source/fonts/Inter-UI-Italic.woff","hash":"92dd7479c19e382e450ab5c22e3d20df5321100e","modified":1544609960171},{"_id":"themes/navy/source/fonts/Inter-UI-Medium.woff","hash":"5e8f0fed42fcf3712aee462932fdc42f7f2a5e55","modified":1544609960173},{"_id":"themes/navy/source/fonts/Inter-UI-MediumItalic.woff","hash":"34a733edd673806091ae1b37e8692099b63c59e5","modified":1544609960175},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.eot","hash":"f20a35250d91f6f46e477eda052171180c06a265","modified":1544609960185},{"_id":"themes/navy/source/fonts/Inter-UI-BoldItalic.woff","hash":"9416a3cfc5ea0ec650e9065162f78db92b270ac6","modified":1544609960169},{"_id":"themes/navy/source/fonts/PostGrotesk-Medium.svg","hash":"e911d4b378f3d51de05c484262caca8f03872cbf","modified":1544609960187},{"_id":"themes/navy/source/fonts/PostGrotesk-Book.svg","hash":"ba365bd7c5413c3c5426efb31aa6ab8ce0e56633","modified":1544609960182},{"_id":"public/sitemap.xml","hash":"f8d20c22c6ea5baf91097e9f47589830d254ca55","modified":1547720229486},{"_id":"public/atom.xml","hash":"d74afb9729b2830489a0164707c5aed53a95f25d","modified":1547720229486},{"_id":"public/index.html","hash":"61fd2170400c44b49be42f0d935b1d62d7e90f12","modified":1544613428022},{"_id":"public/tutorials/go_tutorial.html","hash":"0cb9d4c6804c8bf7db5d9d4d9838c54af19c130f","modified":1547719877911},{"_id":"public/tutorials/index.html","hash":"c3c08bdd9fb2c79c430f0e07daa2fdde2304f050","modified":1547719877911},{"_id":"public/news/2018/10/04/incubate/index.html","hash":"6846dc2d60e30ba7bb033872881769802ed9471a","modified":1547719877911},{"_id":"public/news/2018/index.html","hash":"d0baf559e440006b1d55a06172ee15c86d0f7060","modified":1544613428023},{"_id":"public/news/index.html","hash":"bed05698788d6870019164c9313024dcd840505a","modified":1547720229591},{"_id":"public/api/index.html","hash":"6e358caf634682a03b43a9bdd965fe1ec3350682","modified":1547719877911},{"_id":"public/news/2018/10/index.html","hash":"c6cfba8495a319077b3b1eceb03b7ac29a895f02","modified":1544613428023},{"_id":"public/CNAME","hash":"5f91c14c4301d8db36efb9fe206593bf60ffa5e8","modified":1544613428046},{"_id":"public/browserconfig.xml","hash":"adb4cbc9b5bc6e7526871522d199860ee2fd65f4","modified":1544613428046},{"_id":"public/robots.txt","hash":"7e49dfd97319f5dd7cdaea8518cf43e0e8d01e5a","modified":1544613428046},{"_id":"public/fonts/GTWalsheim-Bold.woff2","hash":"95267ddeb508fff67420bcbdd6fb18c8994da3af","modified":1544613428065},{"_id":"public/fonts/GTWalsheim-Bold.woff","hash":"32948c987f6e798d289d7e54fc457b390cead30b","modified":1544613428046},{"_id":"public/img/apple-touch-icon-1024.png","hash":"a55715cf24bc071478bbb7d0124f60761dff8a40","modified":1544613428046},{"_id":"public/img/apple-touch-icon-180.png","hash":"6911dee3827473345f81e1f7ea7422c52c1d0627","modified":1544613428046},{"_id":"public/img/apple-touch-icon-120.png","hash":"0502d7eb9584efa325830cf588001c2aef129063","modified":1544613428047},{"_id":"public/img/apple-touch-icon-152.png","hash":"420c42bfcc4b487b3a70eb06dc5d4ea0ffcdbbe1","modified":1544613428047},{"_id":"public/img/apple-touch-icon-76.png","hash":"2adb7bd7332d523749f6b8757b4b986b0dd32a57","modified":1544613428047},{"_id":"public/img/arrow.svg","hash":"5e77ef571583e45192a76885f51e95e55a154d93","modified":1544613428047},{"_id":"public/img/circle.svg","hash":"4c3fd2f97fa2ada817bfcb081bdcc2d0d288be6c","modified":1544613428047},{"_id":"public/img/close.svg","hash":"dd96a639e6afebe8e1c43c5eea397950a290dc42","modified":1544613428047},{"_id":"public/img/clippy.svg","hash":"386f533083a450bb34f87dab852e495195a7fddb","modified":1544613428047},{"_id":"public/img/footer-logo.svg","hash":"0a4b5ad7aaddf5085b4413e1958ce0f11c87f2c5","modified":1544613428047},{"_id":"public/img/funding.svg","hash":"de90fec4d7bcbbdbda74145d52be5d3cc3d17a61","modified":1544613428047},{"_id":"public/img/icon-arrow-down.svg","hash":"1974bd9f76f041dfd01754152a35c8c641ca981b","modified":1544613428048},{"_id":"public/img/icon-close.svg","hash":"8e19984a84534fbebeae7fa1c5d634d2b9e79ed8","modified":1544613428048},{"_id":"public/img/icon-contribute-blue.svg","hash":"39abb4ebba6618172f7954bd4ca0280983532501","modified":1544613428048},{"_id":"public/img/icon-github-mobile.svg","hash":"388914e14c23a3084fbffea5e634cd467353d180","modified":1544613428048},{"_id":"public/img/icon-discussion-blue.svg","hash":"7c251bcdf17e7cde71c60e4422d2d3c0accf2a03","modified":1544613428048},{"_id":"public/img/icon-github.svg","hash":"e359a807a062cfe17fffc79d35885a448c0b4caf","modified":1544613428048},{"_id":"public/img/icon-gitter-mobile.svg","hash":"3f1486e02d7fbf67b1e17df216b2d2c077d6bea1","modified":1544613428048},{"_id":"public/img/icon-gitter.svg","hash":"c9f157c50409187c48855adc8b50aeb82849060c","modified":1544613428048},{"_id":"public/img/icon-join-blue.svg","hash":"0ec731f6c295a6e7f0d6283c8439beb7d95310b6","modified":1544613428048},{"_id":"public/img/icon-menu.svg","hash":"67498a8b63ecda345c9ad767ae744bce814cd532","modified":1544613428048},{"_id":"public/img/icon-twitter-mobile.svg","hash":"234bff41b8a5c37a4e3742e0762c4d670ad49e38","modified":1544613428049},{"_id":"public/img/icon_fb.svg","hash":"7b2b44b6bc9768ee15f4f26f4da614a86e6fd18d","modified":1544613428049},{"_id":"public/img/icon-twitter.svg","hash":"83274ec106864a2cd9aa7d4997b10f864195ff5a","modified":1544613428049},{"_id":"public/img/icon_gh.svg","hash":"6229f2ef4fa907f9ccf0bbb839a406aa5bb45ef2","modified":1544613428049},{"_id":"public/img/icon_rd.svg","hash":"4c910721d50f5554048771e922b37afb4f812257","modified":1544613428049},{"_id":"public/img/icon_ri.svg","hash":"f31fbefda73ad806fe0e4338a73e04a7ddeb49b1","modified":1544613428049},{"_id":"public/img/icon_tw.svg","hash":"e08f16d1fed449a70511af301f75dc7b660b5992","modified":1544613428050},{"_id":"public/img/icon_yt.svg","hash":"4413001c81c697cf8564f09972858b0d9a1c142e","modified":1544613428049},{"_id":"public/img/incubate_beaker.svg","hash":"2152a4313242a159947ee4820da44b337b7fdad5","modified":1544613428049},{"_id":"public/img/legal_reg.svg","hash":"a4c4d62a28cad2e71265469cb1a7a04ea40c8c9f","modified":1544613428049},{"_id":"public/img/link-arrow.svg","hash":"83418d8c8fb726909c67e28a44860faf3ee61839","modified":1544613428050},{"_id":"public/img/logo-32.png","hash":"7b2f46c4f1fb0b76048d69da225109f576f82ed8","modified":1544613428050},{"_id":"public/img/logo-16.png","hash":"52e283cbaaa297fdd0925d9d2d700d6e2b9dbcca","modified":1544613428050},{"_id":"public/img/logo-dribbble.svg","hash":"476419dd573a8fd23c072beb2c5ce50c3afa77d1","modified":1544613428050},{"_id":"public/img/logo-black.svg","hash":"c468f15ceb77914466b483c5f5237cd32b72723b","modified":1544613428050},{"_id":"public/img/logo-footer.svg","hash":"00b5d579f1675f01a03912cd3af70ef7748fdef9","modified":1544613428050},{"_id":"public/img/logo-flat.svg","hash":"44473193100b9dd07760a2eeebae1ddffe3c3fd1","modified":1544613428050},{"_id":"public/img/logo-status.svg","hash":"ac6d61eacd2634b08819ce4b641411806903a820","modified":1544613428050},{"_id":"public/img/logo-github.svg","hash":"60358ac6ecddf611fe7cb1928197f4c75163c3a9","modified":1544613428050},{"_id":"public/img/logo.svg","hash":"55bce003cf21684686f6968d2e1264c92a52744f","modified":1544613428050},{"_id":"public/img/marketing_comm.svg","hash":"e63b7dec56daf0bbc587a201d9b501e56f9d2817","modified":1544613428051},{"_id":"public/img/mentorship.svg","hash":"4d65cdd9dcd1746a720cf2780520d03742dcb08d","modified":1544613428051},{"_id":"public/img/logo-youtube.svg","hash":"3ae5d09fa03adc4e8fdbef19db2d1aa11fe95cd4","modified":1544613428050},{"_id":"public/img/product_design.svg","hash":"0d8816bac119f96fb16a0a998251746c73b3deb7","modified":1544613428051},{"_id":"public/img/status_logo.svg","hash":"98b9dabe54674dc55d03196bcb2650be8af5441c","modified":1544613428051},{"_id":"public/img/technical_support.svg","hash":"0e334805845d145abcdcced17d677dd2f153cfca","modified":1544613428051},{"_id":"public/js/vendor.js.map","hash":"3bdb57f6252e6e6025e4c9fee89dc385bdb4c5d2","modified":1544613428051},{"_id":"public/scss/archive.scss","hash":"ede7e53a832bf717ebd71b5c986be22d0a3185dd","modified":1544613428051},{"_id":"public/scss/base.scss","hash":"7685e0948b092ac2308b2e842e944aa450e177bd","modified":1544613428051},{"_id":"public/scss/fonts.scss","hash":"bf5c7f0d6789b2ce1a45cbcd9a1bb5e5b17d5b0e","modified":1544613428051},{"_id":"public/scss/colors.scss","hash":"a9e24cea9b3b37a32dfce9c80dc27f970b9a38ff","modified":1544613428051},{"_id":"public/scss/footer.scss","hash":"89ef743275dcb564ca1cfe18404b3b1f7c5e375f","modified":1544613428051},{"_id":"public/scss/formReset.scss","hash":"bf4b9f20523fc5907dfb438f6fac20201bb626cf","modified":1544613428051},{"_id":"public/scss/header.scss","hash":"3921b1418b6d640a41dcfa72089d5ce316e32a1e","modified":1544613428052},{"_id":"public/scss/highlight.scss","hash":"12e0566bea22131ec402193ef9e6942f3e2ca40b","modified":1544613428051},{"_id":"public/scss/index.scss","hash":"7fff944e00ed147f1790b83be1c7647ca0dc14ae","modified":1544613428052},{"_id":"public/scss/main.scss","hash":"80fc4723dea991b40fdf62959d8e84b50db032b2","modified":1544613428052},{"_id":"public/scss/page.scss","hash":"c4ecdec36bf933ba7849e9d7fecfa1ca719dbb87","modified":1544613428052},{"_id":"public/scss/popup.scss","hash":"5830b5d3679d51a32653a2a4d8b1155b32bb13a5","modified":1544613428052},{"_id":"public/scss/sidebar.scss","hash":"c51af117e47dd302edf188f43629cb6036797095","modified":1544613428052},{"_id":"public/fonts/RobotoRegular/RobotoRegular.eot","hash":"f045fe2f6ec39d5d794e31e9ab621fdd230c0696","modified":1544613428053},{"_id":"public/fonts/RobotoRegular/RobotoRegular.ttf","hash":"af03242b7545da3e54913906aaadce6a55c5f3bd","modified":1544613428052},{"_id":"public/fonts/RobotoRegular/RobotoRegular.woff","hash":"c729757be40622e32a3cdee9e9ad4eabf80d38bc","modified":1544613428053},{"_id":"public/js/styles/brown-papersq.png","hash":"3a1332ede3a75a3d24f60b6ed69035b72da5e182","modified":1544613428053},{"_id":"public/js/styles/pojoaque.jpg","hash":"c5fe6533b88b21f8d90d3d03954c6b29baa67791","modified":1544613428053},{"_id":"public/js/styles/school-book.png","hash":"711ec983c874e093bb89eb77afcbdf6741fa61ee","modified":1544613428053},{"_id":"public/fonts/PostGrotesk-Book.woff","hash":"d65f746630c83d89cd5da6e08f4cbb7b522a1176","modified":1544613428047},{"_id":"public/fonts/PostGrotesk-Book.ttf","hash":"3a85fb6216d4cc0fc888da2b919a8b1effee12de","modified":1544613428066},{"_id":"public/fonts/PostGrotesk-Medium.ttf","hash":"2795ef51769efc5b85e8170a6423dfe71e31341c","modified":1544613428090},{"_id":"public/fonts/PostGrotesk-Medium.woff","hash":"5e5549a6b44673303caf45984a7a6b4f00bcf0d3","modified":1544613428069},{"_id":"public/js/vendor.js","hash":"788f005abc61d3e9f0f91cadb981664e87d37fe5","modified":1544613428083},{"_id":"public/js/main.js","hash":"33222c9e6cccc6923fbf502ed11ea5eaa232f6f1","modified":1544613428083},{"_id":"public/js/styles/androidstudio.css","hash":"958baa24814c06a625612a3b2b478d54bc1bf1b1","modified":1544613428083},{"_id":"public/js/styles/agate.css","hash":"8e122b0f00f5a7ec4e6dc492bf1560441eeef7f0","modified":1544613428083},{"_id":"public/js/styles/arta.css","hash":"17b23b9fa57ef7a05a6aaeea9b5feb5442a8e584","modified":1544613428083},{"_id":"public/js/styles/arduino-light.css","hash":"c6e05580b51b755e229e99eb156940ad2cab192b","modified":1544613428083},{"_id":"public/js/styles/atelier-cave-dark.css","hash":"f397d4418ce88b998841fd9135242461ba1a79b5","modified":1544613428083},{"_id":"public/js/styles/ascetic.css","hash":"6358377b5c25667886aca0d605cbc497cf02405f","modified":1544613428083},{"_id":"public/js/styles/atelier-dune-light.css","hash":"7ba074de897e6a5e27d8b97f7cd06c1746474e72","modified":1544613428083},{"_id":"public/js/styles/atelier-dune-dark.css","hash":"081d73e454db140cd41b2bb595be297cfcab25e0","modified":1544613428084},{"_id":"public/js/styles/atelier-cave-light.css","hash":"2933f0247ac6d84c2954dd4946e359853abbf70a","modified":1544613428083},{"_id":"public/js/styles/atelier-estuary-light.css","hash":"91ae4668c15a085ffce15ca21e93da445b5ecf3a","modified":1544613428084},{"_id":"public/js/styles/atelier-heath-dark.css","hash":"b93c2241ff123e62d4edb3dfc20410e4d1da3e78","modified":1544613428084},{"_id":"public/js/styles/atelier-estuary-dark.css","hash":"a0c46a0f955e3864f5e967ea93f5e61519a17be8","modified":1544613428084},{"_id":"public/js/styles/atelier-forest-light.css","hash":"8a78a4eea0f32d094d1f9e316d59e990ba739d97","modified":1544613428084},{"_id":"public/js/styles/atelier-forest-dark.css","hash":"d8a4dc060b3fc719aa2f7d7b3f1019a3964b8101","modified":1544613428084},{"_id":"public/js/styles/atelier-heath-light.css","hash":"172b98f783d213a20211ec6aca9a3840ba524f55","modified":1544613428084},{"_id":"public/js/styles/atelier-lakeside-dark.css","hash":"fa707b252d5d5caccc0589374522bed47b7ca100","modified":1544613428084},{"_id":"public/js/styles/atelier-lakeside-light.css","hash":"8ba595b9ba6e8be6dc029bf80caab38e85aed686","modified":1544613428084},{"_id":"public/js/styles/atelier-plateau-dark.css","hash":"240f79f4e1fd63485c13900875b64c5a0d1bd06d","modified":1544613428084},{"_id":"public/js/styles/atelier-plateau-light.css","hash":"9731db1052f23351c983210701edd3f5ceed343f","modified":1544613428084},{"_id":"public/js/styles/atelier-savanna-dark.css","hash":"09d45a218f87b8cb55b5ca7f4e9d76ea89a9404a","modified":1544613428084},{"_id":"public/js/styles/atelier-savanna-light.css","hash":"6bd3a62c32558476d436bd389500e5fbeb693d67","modified":1544613428085},{"_id":"public/js/styles/atelier-seaside-dark.css","hash":"b2800804a21f729a3d0a16b3aadc17679fd0639c","modified":1544613428084},{"_id":"public/js/styles/atelier-seaside-light.css","hash":"3e6b9e2a3a5de455490b8224401f19702df4cde9","modified":1544613428085},{"_id":"public/js/styles/atelier-sulphurpool-dark.css","hash":"1f006f8bd28e2ffbb73f708769605ba766787fff","modified":1544613428085},{"_id":"public/js/styles/atom-one-dark.css","hash":"a6d28e1c04cee20cd874fc7ac0903d8e2e4bd54e","modified":1544613428085},{"_id":"public/js/styles/atelier-sulphurpool-light.css","hash":"0319c3eea893601b79b6c57652ef49b9222cc9de","modified":1544613428085},{"_id":"public/js/styles/atom-one-light.css","hash":"eda63d8cce440dbf3bb823e10577a134e9941deb","modified":1544613428085},{"_id":"public/js/styles/brown-paper.css","hash":"a6817d890e58f80ce79d87620791dae821a70fff","modified":1544613428085},{"_id":"public/js/styles/codepen-embed.css","hash":"c4520e45d18259817b8942d17971f27c94f0fb09","modified":1544613428085},{"_id":"public/js/styles/color-brewer.css","hash":"96332573db854e7b7411caa94ba29b238fede2d3","modified":1544613428085},{"_id":"public/js/styles/darcula.css","hash":"7740224d07375ddc321147dffabbfa83e39f0d8b","modified":1544613428085},{"_id":"public/js/styles/docco.css","hash":"1be7be09a1b927c22c7f11451becdb335145bdd2","modified":1544613428085},{"_id":"public/js/styles/dark.css","hash":"fc77519d4f5d731054c5d4b7e7bbdbb510833271","modified":1544613428085},{"_id":"public/js/styles/default.css","hash":"fba68624d1b34a5543fe0bf4b2af2ac1ddf65e74","modified":1544613428085},{"_id":"public/js/styles/darkula.css","hash":"0be948bb84acc05f93a1e5e9b48fe34cf61673a0","modified":1544613428085},{"_id":"public/js/styles/far.css","hash":"67e0658b2376e91e4894636a3522a30c2aec42de","modified":1544613428085},{"_id":"public/js/styles/dracula.css","hash":"2633f2e84680e9f381e9ac1df344b542e28f9774","modified":1544613428085},{"_id":"public/js/styles/foundation.css","hash":"75b0674dd1ed35d61977bd5c35dc29ca35835a7b","modified":1544613428086},{"_id":"public/js/styles/github.css","hash":"ee593952684a791317ee8b77ad096e729dec649e","modified":1544613428086},{"_id":"public/js/styles/github-gist.css","hash":"77cf684fda415812d6279a52f299c84850244778","modified":1544613428086},{"_id":"public/js/styles/googlecode.css","hash":"fed3d439d0c305b337dd9c0f68dcbfa51429f445","modified":1544613428086},{"_id":"public/js/styles/grayscale.css","hash":"5688658c28fc5799517e8f3c224ae3da3797ba44","modified":1544613428086},{"_id":"public/js/styles/gruvbox-dark.css","hash":"aaf90d076e34bc44016462d70f83985e0e55c8dc","modified":1544613428086},{"_id":"public/js/styles/gruvbox-light.css","hash":"084699ab0aa326fede86e38bf41ebe49edde3a90","modified":1544613428086},{"_id":"public/js/styles/hybrid.css","hash":"1e2d54598b5f948b597059909d4bd158b7df021f","modified":1544613428086},{"_id":"public/js/styles/hopscotch.css","hash":"03e7b9ec74bbd375db8d476836aac3729a6c86e8","modified":1544613428086},{"_id":"public/js/styles/idea.css","hash":"164649ae1e7c891a0d88cca075521af28656e2a1","modified":1544613428086},{"_id":"public/js/styles/ir-black.css","hash":"95aad65ba77183500ce0f7ad62a7535b647ee20c","modified":1544613428086},{"_id":"public/js/styles/kimbie.dark.css","hash":"58ed061c204fbc09d221ee9135d66bad976e5fc3","modified":1544613428087},{"_id":"public/js/styles/magula.css","hash":"ebc291f90e456abeeccb16937e7f51735733f4f7","modified":1544613428087},{"_id":"public/js/styles/kimbie.light.css","hash":"b7aa9b1b21a25dfefcac7649a328962f84c47913","modified":1544613428087},{"_id":"public/js/styles/mono-blue.css","hash":"fd0f39cd4c93ccb8d0bb2f6a1f359522e15a89cb","modified":1544613428087},{"_id":"public/js/styles/monokai-sublime.css","hash":"e4f3df87cbfa634c86b489e9cc43acf44d415986","modified":1544613428087},{"_id":"public/js/styles/obsidian.css","hash":"6ed475813fe4886a58b236297862ff2f181e66f7","modified":1544613428087},{"_id":"public/js/styles/monokai.css","hash":"f69df92e3ccdce28e6f811ec84f11698f2811198","modified":1544613428087},{"_id":"public/js/styles/ocean.css","hash":"fce6858e1c5eb9d2857cb5b5d24069c5994cfc91","modified":1544613428087},{"_id":"public/js/styles/paraiso-dark.css","hash":"8f5445327ce3c83b62f6bf4c1a0d87fa6f036341","modified":1544613428087},{"_id":"public/js/styles/paraiso-light.css","hash":"6eb3ab7a3337f9cad3a2e5fa6bf7dd83685228d8","modified":1544613428088},{"_id":"public/js/styles/pojoaque.css","hash":"4e1e6a431212f5043a550474a1acda63362cdd6f","modified":1544613428088},{"_id":"public/js/styles/purebasic.css","hash":"f353a2cedf3f261a0676fce7c824bdd2e8197775","modified":1544613428088},{"_id":"public/js/styles/qtcreator_dark.css","hash":"213a40d203c4986cdbcb1bdf7d0b9013b29041ba","modified":1544613428088},{"_id":"public/js/styles/qtcreator_light.css","hash":"9a2a19ac2f6e6a7d5edd7fae67b7de4a3957e878","modified":1544613428088},{"_id":"public/js/styles/railscasts.css","hash":"a6d2043478fae5915926914cbd96fe9b706d98a6","modified":1544613428088},{"_id":"public/js/styles/routeros.css","hash":"fc5db7c8f18d6b31ad92df21a51e7867d459af19","modified":1544613428088},{"_id":"public/js/styles/rainbow.css","hash":"1b2d98ccdda36aa926d0e6d069b673fdacd2d33e","modified":1544613428088},{"_id":"public/js/styles/school-book.css","hash":"60fe3d8063b1acc4d52de02033095adb0a2b35a9","modified":1544613428088},{"_id":"public/js/styles/solarized-dark.css","hash":"d02fc2dcbeec4b7af2cadec4bbbfc5b016aed4c7","modified":1544613428088},{"_id":"public/js/styles/solarized-light.css","hash":"6b70caf1e84d096b1bc6318d5dae78d69e5dd1d3","modified":1544613428088},{"_id":"public/js/styles/sunburst.css","hash":"8309eab2e5b1765dbee81a626baacbdad869b76a","modified":1544613428088},{"_id":"public/js/styles/tomorrow-night-bright.css","hash":"04f0af30fdda5e5d6ebdeef5a860b6b7e49cfe89","modified":1544613428089},{"_id":"public/js/styles/tomorrow-night-blue.css","hash":"cd257d7d6a37cd5a09419b5f5f9d34b6b282423f","modified":1544613428089},{"_id":"public/js/styles/tomorrow-night.css","hash":"86264dd861d35a8b135f9fcb8ff2675e9fa69c16","modified":1544613428088},{"_id":"public/js/styles/tomorrow-night-eighties.css","hash":"d82b84bcda0588105dbbc0e8e8ba5e62c208a061","modified":1544613428089},{"_id":"public/js/styles/vs.css","hash":"2ac5e89ceb3d5a0e0fdab1ed6d9a411ec7d221aa","modified":1544613428089},{"_id":"public/js/styles/tomorrow.css","hash":"163593ad70770d0296c5e643fa62e58e63f1b340","modified":1544613428089},{"_id":"public/js/styles/vs2015.css","hash":"3c7fa677de2a785d90fc6c3f7520ac1b11bfd37a","modified":1544613428089},{"_id":"public/js/styles/xcode.css","hash":"65d775a7e11e238c91e0d3c7370547348c92d6b3","modified":1544613428089},{"_id":"public/js/styles/zenburn.css","hash":"933a3b196d01254dea5e6f48105ea15e210ae000","modified":1544613428090},{"_id":"public/js/styles/xt256.css","hash":"bbe28ec69177699cb6300d777598adf6323f7861","modified":1544613428089},{"_id":"public/fonts/Inter-UI-Bold.woff2","hash":"51e75c772ad5bb111cf98d4ceb349cfdaf709252","modified":1544613428090},{"_id":"public/fonts/PostGrotesk-Medium.eot","hash":"f20a35250d91f6f46e477eda052171180c06a265","modified":1544613428091},{"_id":"public/fonts/Inter-UI-Medium.woff2","hash":"fc61451b9c27e2df7cad509d103a705a32ffc453","modified":1544613428121},{"_id":"public/fonts/Inter-UI-BoldItalic.woff2","hash":"cfded946f4f1f1b25c57f76f9bd291fa1af02feb","modified":1544613428121},{"_id":"public/fonts/Inter-UI-Italic.woff2","hash":"bc95f46cc6172ab234234688a44b2372688d9b40","modified":1544613428101},{"_id":"public/fonts/Inter-UI-MediumItalic.woff2","hash":"6e03a165a849a462536ae429e7d8ee47f5e0b557","modified":1544613428110},{"_id":"public/fonts/Inter-UI-Regular.woff2","hash":"96f5364d43c588110e3a3ab447d9b377eed80409","modified":1544613428090},{"_id":"public/fonts/PostGrotesk-Book.eot","hash":"264f4f9745237ca98b3cd796bf2d3a21b232e6c5","modified":1544613428090},{"_id":"public/fonts/Inter-UI-Medium.woff","hash":"5e8f0fed42fcf3712aee462932fdc42f7f2a5e55","modified":1544613428111},{"_id":"public/fonts/Inter-UI-MediumItalic.woff","hash":"34a733edd673806091ae1b37e8692099b63c59e5","modified":1544613428111},{"_id":"public/fonts/Inter-UI-Regular.woff","hash":"4b8534f53317657526b9d0fa620f80754b4d0c99","modified":1544613428110},{"_id":"public/fonts/Inter-UI-Bold.woff","hash":"bce41a7fe8e352983cfbcebaa6e93dff9623c2e5","modified":1544613428171},{"_id":"public/fonts/Inter-UI-Italic.woff","hash":"92dd7479c19e382e450ab5c22e3d20df5321100e","modified":1544613428121},{"_id":"public/fonts/PostGrotesk-Medium.svg","hash":"e911d4b378f3d51de05c484262caca8f03872cbf","modified":1544613428122},{"_id":"public/fonts/PostGrotesk-Book.svg","hash":"ba365bd7c5413c3c5426efb31aa6ab8ce0e56633","modified":1544613428122},{"_id":"public/fonts/Inter-UI-BoldItalic.woff","hash":"9416a3cfc5ea0ec650e9065162f78db92b270ac6","modified":1544613428111},{"_id":"source/api/apdu.md","hash":"100c07cd5133b6e009a842e795161b78cab3fdee","modified":1547718836190},{"_id":"source/api/java-sdk.md","hash":"8fd260a0535be0c62ee1dc4e12b4bd170171f9ce","modified":1547713746580},{"_id":"public/api/apdu.html","hash":"bea48dbb138d33c226a1db5dd50c66a8dc86a8c9","modified":1547719877911},{"_id":"public/api/java-sdk.html","hash":"ba14398550443a6630cbc9859b016c6deb8521b3","modified":1547719877911},{"_id":"source/_posts/2019-01-16-keycard.md","hash":"42b584a14778ceb42055b46ff1ffaa30ccc3484c","modified":1547720218615},{"_id":"public/news/2019/01/16/keycard/index.html","hash":"07341fbe4289199f36989944b4e2cfb13535452a","modified":1547720229620},{"_id":"public/news/2019/index.html","hash":"fda533bad947f14cd57396036274b884ec0cfc40","modified":1547720229620},{"_id":"public/news/2019/01/index.html","hash":"6be9d1b36a88f3a79105af6b61af4593831fb65b","modified":1547720229620}],"Category":[],"Data":[{"_id":"menu","data":{"docs":"/docs/","blog":"https://our.status.im/incubate"}},{"_id":"languages","data":{"en":"English"}},{"_id":"sidebar","data":{"api":{"API":{"getting_started":"index.html","java_sdk":"java-sdk.html","apdu":"apdu.html"}}}}],"Page":[{"title":"Status","layout":"index","_content":"","source":"index.md","raw":"title: Status\nlayout: index\n---\n","date":"2018-12-12T10:19:20.150Z","updated":"2018-12-12T10:19:20.150Z","path":"index.html","comments":1,"_id":"cjpl2x2yl0000gkqedewg803l","content":"","site":{"data":{"menu":{"docs":"/docs/","blog":"https://our.status.im/incubate"},"languages":{"en":"English"},"sidebar":{"api":{"API":{"overview":"index.html"},"Tutorials":{"learn_more":"../tutorials/"}},"tutorials":{"start":{"start_here":"index.html"},"by_lanaguge":{"go":"go_tutorial.html"},"Api":{"back_to_api":"../api/"}}}}},"excerpt":"","more":""},{"id":"index","title":"Keycard API","_content":"\n# Getting started\n\nKeycard 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).\n\nCommunication with the Keycard happens through a simple APDU interface is provided, 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.\n\nThe 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.\n\nTo 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. \n\n[Read the documentation by clicking here](java-sdk.html)\n\nIf you use a different language, please first refer to the [Java SDK](java-sdk.html) documentation for a high level overview of how to perform different tasks with the Keycard. Then, please check the [APDU protocol documentation](apdu.html) out for the low-level details.","source":"api/index.md","raw":"---\nid: index\ntitle: Keycard API\n---\n\n# Getting started\n\nKeycard 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).\n\nCommunication with the Keycard happens through a simple APDU interface is provided, 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.\n\nThe 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.\n\nTo 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. \n\n[Read the documentation by clicking here](java-sdk.html)\n\nIf you use a different language, please first refer to the [Java SDK](java-sdk.html) documentation for a high level overview of how to perform different tasks with the Keycard. Then, please check the [APDU protocol documentation](apdu.html) out for the low-level details.","date":"2019-01-17T08:38:39.936Z","updated":"2019-01-17T08:38:39.931Z","path":"api/index.html","_id":"cjpl2x2zl0002gkqep1nfz2tm","comments":1,"layout":"page","content":"

Getting started

Keycard provides to developer an hardware implementation of a BIP-32 HD wallet. This means it supports key generation, derivation and signing. It also allows exporting keys defined in the context of EIP-1581.

\n

Communication with the Keycard happens through a simple APDU interface is provided, 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.

\n

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.

\n

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.

\n

Read the documentation by clicking here

\n

If you use a different language, please first refer to the Java SDK documentation for a high level overview of how to perform different tasks with the Keycard. Then, please check the APDU protocol documentation out for the low-level details.

\n","site":{"data":{"menu":{"docs":"/docs/","blog":"https://our.status.im/incubate"},"languages":{"en":"English"},"sidebar":{"api":{"API":{"getting_started":"index.html","java_sdk":"java-sdk.html","apdu":"apdu.html"}}}}},"excerpt":"","more":"

Getting started

Keycard provides to developer an hardware implementation of a BIP-32 HD wallet. This means it supports key generation, derivation and signing. It also allows exporting keys defined in the context of EIP-1581.

\n

Communication with the Keycard happens through a simple APDU interface is provided, 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.

\n

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.

\n

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.

\n

Read the documentation by clicking here

\n

If you use a different language, please first refer to the Java SDK documentation for a high level overview of how to perform different tasks with the Keycard. Then, please check the APDU protocol documentation out for the low-level details.

\n"},{"_content":"# Status Keycard Protocol\n\nThese are the commands supported by the application. When a command has a precondition clause and these are not met the SW 0x6985 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) \n\n## SELECT\n\n* CLA = 0x00\n* INS = 0xA4\n* P1 = 0x04\n* P2 = 0x00\n* Data = the instance AID\n* Response = Application Info Template or ECC public key.\n\nResponse Data format:\n- Tag 0xA4 = Application Info Template\n - Tag 0x8F = Instance UID (16 bytes)\n - Tag 0x80 = ECC public Key\n - Tag 0x02 = Application Version (2 bytes)\n - Tag 0x02 = Number of remaining pairing slots (1 byte)\n - Tag 0x8E = Key UID (0 or 32 bytes)\n\nThe 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) and the public key which must be used by the client to establish the Secure Channel. Additionally it contains the version number of the application, formatted on two bytes. The first byte is the major version and the second is the minor version (e.g: version 1.1 is formatted as 0x0101). The number of remaining pairing slots is also included in the response.\n\nThe Key UID can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key.\n\nWhen the applet is in pre-initializated state, it only returns the ECC public key, BER-TLV encoded with tag 0x80.\n\n## INIT\n* CLA = 0x80\n* INS = 0xFE\n* P1 = 0x00\n* P2 = 0x00\n* Data = EC public key (LV encoded) | IV | encrypted payload\n* Response SW = 0x9000 on success, 0x6D00 if the applet is already initialized, 0x6A80 if the data is invalid\n\nThis 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.\n\nThe 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.\n\nThey payload is the concatenation of the PIN (6 digits/bytes), PUK (12 digits/bytes) and pairing secret (32 bytes).\n\nThis 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.\n\nAfter successful execution, this command cannot be executed anymore. The regular SecureChannel (with pairing) is active and PIN and PUK are initialized.\n\n## OPEN SECURE CHANNEL\n* CLA = 0x80\n* INS = 0x10\n* P1 = the pairing index\n* P2 = 0x00\n* Data = An EC-256 public key on the SECP256k1 curve encoded as an uncompressed point.\n* Response Data = A 256-bit salt and a 128-bit seed IV\n* Response SW = 0x9000 on success, 0x6A86 if P1 is invalid, 0x6A80 if the data is not a public key\n\nThis 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.\n\nThe 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\n\n1. Use their private key and the counterpart public key to generate a secret using the EC-DH algorithm.\n2. The generated secret, the pairing key and the salt are concatenated and the SHA-512 of the concatenated value is calculated.\n3. 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.\n\nThe seed IV is used by the client as the IV for the next encrypted APDU.\n\n## MUTUALLY AUTHENTICATE\n\n* CLA = 0x80\n* INS = 0x11\n* P1 = 0x00\n* P2 = 0x00\n* Data = 256-bit random number\n* Response Data = 256-bit random number\n* 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\n\nThis 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.\n\n## PAIR\n\n* CLA = 0x80\n* INS = 0x12\n* P1 = pairing phase\n* P2 = 0x00\n* Data = see below\n* Response Data = see below\n* 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\n\nP1:\n* 0x00: First step\n* 0x01: Final step\n\nData:\n* On first step: a 256-bit random client challenge\n* On second step: the client cryptogram as SHA-256(shared secret, card challenge)\n\nResponse Data:\n* On first step: the card cryptogram as SHA-256(shared secret, client challenge) followed by a 256-bit card challenge\n* On second step: the pairing index followed by a 256-bit salt\n\nThis APDU is sent to pair a client. Pairing is performed with two commands which must be sent immediately one after the other. \n\nIn the first phase the client sends a random challenge to the card. The card replies with the SHA-256 hash of the challenge and the shared secret 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).\n\nIn 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.\n\nThe shared secret is a 256-bit value which must be be known to both parts being paired.\n\n## UNPAIR\n\n* CLA = 0x80\n* INS = 0x13\n* P1 = the index to unpair\n* P2 = 0x00\n* Response SW = 0x9000 on success, 0x6985 if security conditions are not met, 0x6A86 if the index is higher than the\n highest possible pairing index.\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nThis 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.\n\n## GET STATUS\n\n* CLA = 0x80\n* INS = 0xF2\n* P1 = 0x00 for application status, 0x01 for key path status\n* P2 = 0x00\n* Response SW = 0x9000 on success, 0x6A86 on undefined P1\n* Response Data = Application Status Template or Key Path\n* Preconditions: Secure Channel must be opened\n\nResponse Data format:\nif P1 = 0x00:\n- Tag 0xA3 = Application Status Template\n - Tag 0x02 = PIN retry count (1 byte)\n - Tag 0x02 = PUK retry count (1 byte)\n - Tag 0x01 = 0xff if key is initialized, 0 otherwise\n\nif P1 = 0x01:\n- a sequence of 32-bit numbers indicating the current key path. Empty if master key is selected.\n\n## SET NDEF\n\n* CLA = 0x80\n* INS = 0xF3\n* P1 = 0x00\n* P2 = 0x00\n* Data = the data to store\n* Response SW = 0x9000 on success, 0x6A80 on invalid data\n* Preconditions: Secure Channel must be opened, PIN must be verified\n\nUsed to set the content of the data file owned by the NDEF applet. This allows changing the behavior of Android and other clients when tapping the card with no open client. As an example, it could be used to launch a specific wallet software.\n\nThe data is stored as is. A check is made that the first 2 bytes, read as a MSB first short, are equal to the Lc field minus 2 (the length of the field itself). This does not ensure that the NDEF record is valid, but ensures that no out of bound access happens.\n\n## VERIFY PIN\n\n* CLA = 0x80\n* INS = 0x20\n* P1 = 0x00\n* P2 = 0x00\n* Data = the PIN to be verified\n* Response SW = 0x9000 on success, 0x63CX on failure, where X is the number of attempt remaining\n* Preconditions: Secure Channel must be opened\n\nUsed 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.\n\n## CHANGE PIN\n\n* CLA = 0x80\n* INS = 0x21\n* P1 = PIN identifier\n* P2 = 0x00\n* Data = the new PIN\n* Response SW = 0x9000 on success, 0x6A80 if the PIN format is invalid, 0x6A86 if P1 is invalid\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nUsed 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.\n\nP1:\n* 0x00: User PIN. Must be 6-digits. The updated PIN is authenticated for the rest of the session. \n* 0x01: Applet PUK. Must be 12-digits.\n* 0x02: Pairing secret. Must be 32-bytes long. Existing pairings are not broken, but new pairings will need to use the new secret.\n\n## UNBLOCK PIN\n\n* CLA = 0x80\n* INS = 0x22\n* P1 = 0x00\n* P2 = 0x00\n* Data = the PUK followed by the new PIN\n* Response SW = 0x9000 on success, 0x6A80 if the format is invalid\n* Preconditions: Secure Channel must be opened, user PIN must be blocked\n\nUsed 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.\n\n## LOAD KEY\n\n* CLA = 0x80\n* INS = 0xD0\n* P1 = key type\n* P2 = 0x00\n* Data = the key data\n* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A86 if P1 is invalid\n* Response Data = the key UID, defined as the SHA-256 of the public key\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nP1:\n* 0x01 = ECC SECP256k1 keypair\n* 0x02 = ECC SECP256k1 extended keypair\n* 0x03 = Binary seed as defined in [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)\n\nData:\n\nIf P1 is 0x01 or 0x02\n- Tag 0xA1 = keypair template\n - Tag 0x80 = ECC public key component (can be omitted)\n - Tag 0x81 = ECC private key component\n - Tag 0x82 = chain code (if P1=0x02)\n \nIf 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.\n\nThis command is used to load or replace the keypair used for signing on the card. This command always aborts open signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command will use this keypair for signature.\n\n## DERIVE KEY\n\n* CLA = 0x80\n* INS = 0xD1\n* P1 = derivation options\n* P2 = 0x00\n* Data = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used.\n* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6984 if one of the components in the path generates an invalid key, 0x6B00 if derivation from parent keys is selected but no valid parent key is cached.\n* Preconditions: Secure Channel must be opened, user PIN must be verified (if no PIN-less key is defined), an extended keyset must be loaded\n\nThis command is used before a signing session to generate a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specifications. This command always aborts open signing sessions, if any. The generated key is used for all subsequent SIGN sessions. The maximum depth of derivation from the master key is 10. Any attempt to get deeper results in 0x6A80 being returned. The BIP32 specifications define a few checks which must be performed on the derived keys. If these fail, the 0x6984 is returned and the invalid key is discarded. A client should perform a GET STATUS command to get the actual current key path and resume derivation using a different path.\n\nThe ability to start derivation from the parent keys allows to more efficiently switch between children of the same key. Note however that only the immediate parent of the current key is cached so you cannot use this to go back in the hierarchy. If no valid parent key is available the status code 0x6B00 will be returned.\n\nP1:\n* bit 0-5 = reserved\n* bit 7-6:\n - 00 derive from master keys\n - 01 derive from parent keys\n - 10 derive from current keys\n - 11 reserved\n\n## GENERATE MNEMONIC\n\n* CLA = 0x80\n* INS = 0xD2\n* P1 = checksum size (between 4 and 8)\n* P2 = 0x00\n* Response SW = 0x9000 on success. 0x6A86 if P1 is invalid.\n* Response Data = a sequence of 16-bit integers (most significant byte first).\n* Preconditions: Secure Channel must be opened\n\nUsed 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.\n\n## REMOVE KEY\n\n* CLA = 0x80\n* INS = 0xD3\n* P1 = 0x00\n* P2 = 0x00\n* Response SW = 0x9000 on success.\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nRemoves 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.\n\n## GENERATE KEY\n\n* CLA = 0x80\n* INS = 0xD4\n* P1 = 0x00\n* P2 = 0x00\n* Response SW = 0x9000 on success.\n* Response Data = the key UID, defined as the SHA-256 of the public key\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nGenerates 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.\n\n## DUPLICATE KEY\n\n* CLA = 0x80\n* INS = 0xD5\n* P1 = subcommand\n* P2 = depends on subcommand\n* Data = depends on phase\n* Response SW = 0x9000 on success.\n* Response Data = depends on subcommand\n* Preconditions: depends on subcommand\n\nP1:\n* 0x00: START DUPLICATE\n* 0x01: ADD ENTROPY\n* 0x02: EXPORT DUPLICATE\n* 0x03: IMPORT DUPLICATE\n\n### START DUPLICATE\nThis is the first step to start duplication. Requires an open secure channel and user PIN must be verified. Aborts any on-going duplication session. P2 is the number of entropy pieces to expect in total (including this command). The data contain the first piece of entropy. Returns no data. Must be performed with exactly the same parameters and data on all cards taking part in the duplication.\n\n### ADD ENTROPY\nThis command uses the same one-shot secure channel scheme as defined in the INIT command. P2 is 00. Requires an ongoing duplicate session started with the START DUPLICATE subcommand. Must be performed once per device taking part in the duplication process, for a total number of devices equaling the P2 parameter of the START DUPLICATE subcommand (counting the device which sent the START DUPLICATE command as the first device). The data is a random 256-bit number. The same data must be sent to all the cards taking part in the duplication process.\n\n### EXPORT DUPLICATE\nThis command must be sent to the card which you wish to duplicate. Requires an open secure channel and authenticated PIN. Works only if a duplication session is active and ADD ENTROPY has been performed the required number of times. Returns the encrypted duplicate of the master key and terminates the duplication session for this card. The format is exactly the same as the one defined in the LOAD KEY (TLV) command with omitted public key. It is however prepended by a 16-bytes IV and the entire TLV structure is encrypted.\n\n### IMPORT DUPLICATE\nThis command must be sent to all the cards which are a target for duplication. The Data field must contain the output from the EXPORT DUPLICATE command performed on the source card. Returns the key UID. It follows exactly the same rules as the EXPORT DUPLICATE subcommand.\n\n## SIGN\n\n* CLA = 0x80\n* INS = 0xC0\n* P1 = 0x00\n* P2 = 0x00\n* Data = the hash to sign\n* Response = public key and the signature\n* Response SW = 0x9000 on success, 0x6A80 if the data is not 32-byte long\n* 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\n\nResponse Data format:\n- Tag 0xA0 = signature template\n - Tag 0x80 = ECC public key component\n - Tag 0x30 = ECDSA Signature\n - Tag 0x02 = R value\n - Tag 0x02 = S value\n\nReturns 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.\n\n## SET PINLESS PATH\n\n* CLA = 0x80\n* INS = 0xC1\n* P1 = 0x00\n* P2 = 0x00\n* Data = a sequence of 32-bit integers\n* Response SW = 0x9000 on success, 0x6A80 if data is invalid\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nSets 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 has been performed. An empty sequence means that no PIN-less path is defined.\n\n## EXPORT KEY\n\n* CLA = 0x80\n* INS = 0xC2\n* P1 = derivation options\n* P2 = export options\n* Response SW = 0x9000 on success, 0x6A86 if P1 or P2 are wrong\n* Data = a sequence of 32-bit integers (empty if P1=0x00)\n* Response Data = key pair template\n* Response SW = 0x9000 on success, 0x6985 if the private key cannot be exported, 0x6A80 if the path is malformed\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n \nP1:\n0x00 = Current key\n0x01 = Derive\n0x02 = Derive and make current\n\nP2:\n0x00 = private and public key\n0x01 = public key only\n \nResponse Data format:\n- Tag 0xA1 = keypair template\n - Tag 0x80 = ECC public key component (could be omitted)\n - Tag 0x81 = ECC private key component (if P2=0x00)\n \nThis 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. \n\nThe 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.\n\nIf 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.","source":"api/apdu.md","raw":"# Status Keycard Protocol\n\nThese are the commands supported by the application. When a command has a precondition clause and these are not met the SW 0x6985 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) \n\n## SELECT\n\n* CLA = 0x00\n* INS = 0xA4\n* P1 = 0x04\n* P2 = 0x00\n* Data = the instance AID\n* Response = Application Info Template or ECC public key.\n\nResponse Data format:\n- Tag 0xA4 = Application Info Template\n - Tag 0x8F = Instance UID (16 bytes)\n - Tag 0x80 = ECC public Key\n - Tag 0x02 = Application Version (2 bytes)\n - Tag 0x02 = Number of remaining pairing slots (1 byte)\n - Tag 0x8E = Key UID (0 or 32 bytes)\n\nThe 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) and the public key which must be used by the client to establish the Secure Channel. Additionally it contains the version number of the application, formatted on two bytes. The first byte is the major version and the second is the minor version (e.g: version 1.1 is formatted as 0x0101). The number of remaining pairing slots is also included in the response.\n\nThe Key UID can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key.\n\nWhen the applet is in pre-initializated state, it only returns the ECC public key, BER-TLV encoded with tag 0x80.\n\n## INIT\n* CLA = 0x80\n* INS = 0xFE\n* P1 = 0x00\n* P2 = 0x00\n* Data = EC public key (LV encoded) | IV | encrypted payload\n* Response SW = 0x9000 on success, 0x6D00 if the applet is already initialized, 0x6A80 if the data is invalid\n\nThis 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.\n\nThe 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.\n\nThey payload is the concatenation of the PIN (6 digits/bytes), PUK (12 digits/bytes) and pairing secret (32 bytes).\n\nThis 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.\n\nAfter successful execution, this command cannot be executed anymore. The regular SecureChannel (with pairing) is active and PIN and PUK are initialized.\n\n## OPEN SECURE CHANNEL\n* CLA = 0x80\n* INS = 0x10\n* P1 = the pairing index\n* P2 = 0x00\n* Data = An EC-256 public key on the SECP256k1 curve encoded as an uncompressed point.\n* Response Data = A 256-bit salt and a 128-bit seed IV\n* Response SW = 0x9000 on success, 0x6A86 if P1 is invalid, 0x6A80 if the data is not a public key\n\nThis 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.\n\nThe 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\n\n1. Use their private key and the counterpart public key to generate a secret using the EC-DH algorithm.\n2. The generated secret, the pairing key and the salt are concatenated and the SHA-512 of the concatenated value is calculated.\n3. 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.\n\nThe seed IV is used by the client as the IV for the next encrypted APDU.\n\n## MUTUALLY AUTHENTICATE\n\n* CLA = 0x80\n* INS = 0x11\n* P1 = 0x00\n* P2 = 0x00\n* Data = 256-bit random number\n* Response Data = 256-bit random number\n* 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\n\nThis 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.\n\n## PAIR\n\n* CLA = 0x80\n* INS = 0x12\n* P1 = pairing phase\n* P2 = 0x00\n* Data = see below\n* Response Data = see below\n* 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\n\nP1:\n* 0x00: First step\n* 0x01: Final step\n\nData:\n* On first step: a 256-bit random client challenge\n* On second step: the client cryptogram as SHA-256(shared secret, card challenge)\n\nResponse Data:\n* On first step: the card cryptogram as SHA-256(shared secret, client challenge) followed by a 256-bit card challenge\n* On second step: the pairing index followed by a 256-bit salt\n\nThis APDU is sent to pair a client. Pairing is performed with two commands which must be sent immediately one after the other. \n\nIn the first phase the client sends a random challenge to the card. The card replies with the SHA-256 hash of the challenge and the shared secret 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).\n\nIn 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.\n\nThe shared secret is a 256-bit value which must be be known to both parts being paired.\n\n## UNPAIR\n\n* CLA = 0x80\n* INS = 0x13\n* P1 = the index to unpair\n* P2 = 0x00\n* Response SW = 0x9000 on success, 0x6985 if security conditions are not met, 0x6A86 if the index is higher than the\n highest possible pairing index.\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nThis 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.\n\n## GET STATUS\n\n* CLA = 0x80\n* INS = 0xF2\n* P1 = 0x00 for application status, 0x01 for key path status\n* P2 = 0x00\n* Response SW = 0x9000 on success, 0x6A86 on undefined P1\n* Response Data = Application Status Template or Key Path\n* Preconditions: Secure Channel must be opened\n\nResponse Data format:\nif P1 = 0x00:\n- Tag 0xA3 = Application Status Template\n - Tag 0x02 = PIN retry count (1 byte)\n - Tag 0x02 = PUK retry count (1 byte)\n - Tag 0x01 = 0xff if key is initialized, 0 otherwise\n\nif P1 = 0x01:\n- a sequence of 32-bit numbers indicating the current key path. Empty if master key is selected.\n\n## SET NDEF\n\n* CLA = 0x80\n* INS = 0xF3\n* P1 = 0x00\n* P2 = 0x00\n* Data = the data to store\n* Response SW = 0x9000 on success, 0x6A80 on invalid data\n* Preconditions: Secure Channel must be opened, PIN must be verified\n\nUsed to set the content of the data file owned by the NDEF applet. This allows changing the behavior of Android and other clients when tapping the card with no open client. As an example, it could be used to launch a specific wallet software.\n\nThe data is stored as is. A check is made that the first 2 bytes, read as a MSB first short, are equal to the Lc field minus 2 (the length of the field itself). This does not ensure that the NDEF record is valid, but ensures that no out of bound access happens.\n\n## VERIFY PIN\n\n* CLA = 0x80\n* INS = 0x20\n* P1 = 0x00\n* P2 = 0x00\n* Data = the PIN to be verified\n* Response SW = 0x9000 on success, 0x63CX on failure, where X is the number of attempt remaining\n* Preconditions: Secure Channel must be opened\n\nUsed 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.\n\n## CHANGE PIN\n\n* CLA = 0x80\n* INS = 0x21\n* P1 = PIN identifier\n* P2 = 0x00\n* Data = the new PIN\n* Response SW = 0x9000 on success, 0x6A80 if the PIN format is invalid, 0x6A86 if P1 is invalid\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nUsed 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.\n\nP1:\n* 0x00: User PIN. Must be 6-digits. The updated PIN is authenticated for the rest of the session. \n* 0x01: Applet PUK. Must be 12-digits.\n* 0x02: Pairing secret. Must be 32-bytes long. Existing pairings are not broken, but new pairings will need to use the new secret.\n\n## UNBLOCK PIN\n\n* CLA = 0x80\n* INS = 0x22\n* P1 = 0x00\n* P2 = 0x00\n* Data = the PUK followed by the new PIN\n* Response SW = 0x9000 on success, 0x6A80 if the format is invalid\n* Preconditions: Secure Channel must be opened, user PIN must be blocked\n\nUsed 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.\n\n## LOAD KEY\n\n* CLA = 0x80\n* INS = 0xD0\n* P1 = key type\n* P2 = 0x00\n* Data = the key data\n* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A86 if P1 is invalid\n* Response Data = the key UID, defined as the SHA-256 of the public key\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nP1:\n* 0x01 = ECC SECP256k1 keypair\n* 0x02 = ECC SECP256k1 extended keypair\n* 0x03 = Binary seed as defined in [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)\n\nData:\n\nIf P1 is 0x01 or 0x02\n- Tag 0xA1 = keypair template\n - Tag 0x80 = ECC public key component (can be omitted)\n - Tag 0x81 = ECC private key component\n - Tag 0x82 = chain code (if P1=0x02)\n \nIf 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.\n\nThis command is used to load or replace the keypair used for signing on the card. This command always aborts open signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command will use this keypair for signature.\n\n## DERIVE KEY\n\n* CLA = 0x80\n* INS = 0xD1\n* P1 = derivation options\n* P2 = 0x00\n* Data = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used.\n* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6984 if one of the components in the path generates an invalid key, 0x6B00 if derivation from parent keys is selected but no valid parent key is cached.\n* Preconditions: Secure Channel must be opened, user PIN must be verified (if no PIN-less key is defined), an extended keyset must be loaded\n\nThis command is used before a signing session to generate a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specifications. This command always aborts open signing sessions, if any. The generated key is used for all subsequent SIGN sessions. The maximum depth of derivation from the master key is 10. Any attempt to get deeper results in 0x6A80 being returned. The BIP32 specifications define a few checks which must be performed on the derived keys. If these fail, the 0x6984 is returned and the invalid key is discarded. A client should perform a GET STATUS command to get the actual current key path and resume derivation using a different path.\n\nThe ability to start derivation from the parent keys allows to more efficiently switch between children of the same key. Note however that only the immediate parent of the current key is cached so you cannot use this to go back in the hierarchy. If no valid parent key is available the status code 0x6B00 will be returned.\n\nP1:\n* bit 0-5 = reserved\n* bit 7-6:\n - 00 derive from master keys\n - 01 derive from parent keys\n - 10 derive from current keys\n - 11 reserved\n\n## GENERATE MNEMONIC\n\n* CLA = 0x80\n* INS = 0xD2\n* P1 = checksum size (between 4 and 8)\n* P2 = 0x00\n* Response SW = 0x9000 on success. 0x6A86 if P1 is invalid.\n* Response Data = a sequence of 16-bit integers (most significant byte first).\n* Preconditions: Secure Channel must be opened\n\nUsed 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.\n\n## REMOVE KEY\n\n* CLA = 0x80\n* INS = 0xD3\n* P1 = 0x00\n* P2 = 0x00\n* Response SW = 0x9000 on success.\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nRemoves 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.\n\n## GENERATE KEY\n\n* CLA = 0x80\n* INS = 0xD4\n* P1 = 0x00\n* P2 = 0x00\n* Response SW = 0x9000 on success.\n* Response Data = the key UID, defined as the SHA-256 of the public key\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nGenerates 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.\n\n## DUPLICATE KEY\n\n* CLA = 0x80\n* INS = 0xD5\n* P1 = subcommand\n* P2 = depends on subcommand\n* Data = depends on phase\n* Response SW = 0x9000 on success.\n* Response Data = depends on subcommand\n* Preconditions: depends on subcommand\n\nP1:\n* 0x00: START DUPLICATE\n* 0x01: ADD ENTROPY\n* 0x02: EXPORT DUPLICATE\n* 0x03: IMPORT DUPLICATE\n\n### START DUPLICATE\nThis is the first step to start duplication. Requires an open secure channel and user PIN must be verified. Aborts any on-going duplication session. P2 is the number of entropy pieces to expect in total (including this command). The data contain the first piece of entropy. Returns no data. Must be performed with exactly the same parameters and data on all cards taking part in the duplication.\n\n### ADD ENTROPY\nThis command uses the same one-shot secure channel scheme as defined in the INIT command. P2 is 00. Requires an ongoing duplicate session started with the START DUPLICATE subcommand. Must be performed once per device taking part in the duplication process, for a total number of devices equaling the P2 parameter of the START DUPLICATE subcommand (counting the device which sent the START DUPLICATE command as the first device). The data is a random 256-bit number. The same data must be sent to all the cards taking part in the duplication process.\n\n### EXPORT DUPLICATE\nThis command must be sent to the card which you wish to duplicate. Requires an open secure channel and authenticated PIN. Works only if a duplication session is active and ADD ENTROPY has been performed the required number of times. Returns the encrypted duplicate of the master key and terminates the duplication session for this card. The format is exactly the same as the one defined in the LOAD KEY (TLV) command with omitted public key. It is however prepended by a 16-bytes IV and the entire TLV structure is encrypted.\n\n### IMPORT DUPLICATE\nThis command must be sent to all the cards which are a target for duplication. The Data field must contain the output from the EXPORT DUPLICATE command performed on the source card. Returns the key UID. It follows exactly the same rules as the EXPORT DUPLICATE subcommand.\n\n## SIGN\n\n* CLA = 0x80\n* INS = 0xC0\n* P1 = 0x00\n* P2 = 0x00\n* Data = the hash to sign\n* Response = public key and the signature\n* Response SW = 0x9000 on success, 0x6A80 if the data is not 32-byte long\n* 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\n\nResponse Data format:\n- Tag 0xA0 = signature template\n - Tag 0x80 = ECC public key component\n - Tag 0x30 = ECDSA Signature\n - Tag 0x02 = R value\n - Tag 0x02 = S value\n\nReturns 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.\n\n## SET PINLESS PATH\n\n* CLA = 0x80\n* INS = 0xC1\n* P1 = 0x00\n* P2 = 0x00\n* Data = a sequence of 32-bit integers\n* Response SW = 0x9000 on success, 0x6A80 if data is invalid\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n\nSets 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 has been performed. An empty sequence means that no PIN-less path is defined.\n\n## EXPORT KEY\n\n* CLA = 0x80\n* INS = 0xC2\n* P1 = derivation options\n* P2 = export options\n* Response SW = 0x9000 on success, 0x6A86 if P1 or P2 are wrong\n* Data = a sequence of 32-bit integers (empty if P1=0x00)\n* Response Data = key pair template\n* Response SW = 0x9000 on success, 0x6985 if the private key cannot be exported, 0x6A80 if the path is malformed\n* Preconditions: Secure Channel must be opened, user PIN must be verified\n \nP1:\n0x00 = Current key\n0x01 = Derive\n0x02 = Derive and make current\n\nP2:\n0x00 = private and public key\n0x01 = public key only\n \nResponse Data format:\n- Tag 0xA1 = keypair template\n - Tag 0x80 = ECC public key component (could be omitted)\n - Tag 0x81 = ECC private key component (if P2=0x00)\n \nThis 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. \n\nThe 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.\n\nIf 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.","date":"2019-01-17T09:53:56.194Z","updated":"2019-01-17T09:53:56.190Z","path":"api/apdu.html","_id":"cjqyxw7i30000p0qe505dwpag","title":"","comments":1,"layout":"page","content":"

Status Keycard Protocol

These are the commands supported by the application. When a command has a precondition clause and these are not met the SW 0x6985 is returned. All tagged data structures are encoded in the BER-TLV format

\n

SELECT

\n

Response Data format:

\n\n

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) and the public key which must be used by the client to establish the Secure Channel. Additionally it contains the version number of the application, formatted on two bytes. The first byte is the major version and the second is the minor version (e.g: version 1.1 is formatted as 0x0101). The number of remaining pairing slots is also included in the response.

\n

The Key UID can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key.

\n

When the applet is in pre-initializated state, it only returns the ECC public key, BER-TLV encoded with tag 0x80.

\n

INIT

\n

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.

\n

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.

\n

They payload is the concatenation of the PIN (6 digits/bytes), PUK (12 digits/bytes) and pairing secret (32 bytes).

\n

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.

\n

After successful execution, this command cannot be executed anymore. The regular SecureChannel (with pairing) is active and PIN and PUK are initialized.

\n

OPEN SECURE CHANNEL

\n

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.

\n

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

\n
    \n
  1. Use their private key and the counterpart public key to generate a secret using the EC-DH algorithm.
  2. \n
  3. The generated secret, the pairing key and the salt are concatenated and the SHA-512 of the concatenated value is calculated.
  4. \n
  5. 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.
  6. \n
\n

The seed IV is used by the client as the IV for the next encrypted APDU.

\n

MUTUALLY AUTHENTICATE

\n

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.

\n

PAIR

\n

P1:

\n\n

Data:

\n\n

Response Data:

\n\n

This APDU is sent to pair a client. Pairing is performed with two commands which must be sent immediately one after the other.

\n

In the first phase the client sends a random challenge to the card. The card replies with the SHA-256 hash of the challenge and the shared secret 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).

\n

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.

\n

The shared secret is a 256-bit value which must be be known to both parts being paired.

\n

UNPAIR

\n

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.

\n

GET STATUS

\n

Response Data format:
if P1 = 0x00:

\n\n

if P1 = 0x01:

\n\n

SET NDEF

\n

Used to set the content of the data file owned by the NDEF applet. This allows changing the behavior of Android and other clients when tapping the card with no open client. As an example, it could be used to launch a specific wallet software.

\n

The data is stored as is. A check is made that the first 2 bytes, read as a MSB first short, are equal to the Lc field minus 2 (the length of the field itself). This does not ensure that the NDEF record is valid, but ensures that no out of bound access happens.

\n

VERIFY PIN

\n

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.

\n

CHANGE PIN

\n

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.

\n

P1:

\n\n

UNBLOCK PIN

\n

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.

\n

LOAD KEY

\n

P1:

\n\n

Data:

\n

If P1 is 0x01 or 0x02

\n\n

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 specifications. This is only supported if the hardware supports public key derivation.

\n

This command is used to load or replace the keypair used for signing on the card. This command always aborts open signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command will use this keypair for signature.

\n

DERIVE KEY

\n

This command is used before a signing session to generate a private key according to the BIP32 specifications. This command always aborts open signing sessions, if any. The generated key is used for all subsequent SIGN sessions. The maximum depth of derivation from the master key is 10. Any attempt to get deeper results in 0x6A80 being returned. The BIP32 specifications define a few checks which must be performed on the derived keys. If these fail, the 0x6984 is returned and the invalid key is discarded. A client should perform a GET STATUS command to get the actual current key path and resume derivation using a different path.

\n

The ability to start derivation from the parent keys allows to more efficiently switch between children of the same key. Note however that only the immediate parent of the current key is cached so you cannot use this to go back in the hierarchy. If no valid parent key is available the status code 0x6B00 will be returned.

\n

P1:

\n\n

GENERATE MNEMONIC

\n

Used to generate a mnemonic according to the algorithm specified in BIP39. 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.

\n

REMOVE KEY

\n

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.

\n

GENERATE KEY

\n

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.

\n

DUPLICATE KEY

\n

P1:

\n\n

START DUPLICATE

This is the first step to start duplication. Requires an open secure channel and user PIN must be verified. Aborts any on-going duplication session. P2 is the number of entropy pieces to expect in total (including this command). The data contain the first piece of entropy. Returns no data. Must be performed with exactly the same parameters and data on all cards taking part in the duplication.

\n

ADD ENTROPY

This command uses the same one-shot secure channel scheme as defined in the INIT command. P2 is 00. Requires an ongoing duplicate session started with the START DUPLICATE subcommand. Must be performed once per device taking part in the duplication process, for a total number of devices equaling the P2 parameter of the START DUPLICATE subcommand (counting the device which sent the START DUPLICATE command as the first device). The data is a random 256-bit number. The same data must be sent to all the cards taking part in the duplication process.

\n

EXPORT DUPLICATE

This command must be sent to the card which you wish to duplicate. Requires an open secure channel and authenticated PIN. Works only if a duplication session is active and ADD ENTROPY has been performed the required number of times. Returns the encrypted duplicate of the master key and terminates the duplication session for this card. The format is exactly the same as the one defined in the LOAD KEY (TLV) command with omitted public key. It is however prepended by a 16-bytes IV and the entire TLV structure is encrypted.

\n

IMPORT DUPLICATE

This command must be sent to all the cards which are a target for duplication. The Data field must contain the output from the EXPORT DUPLICATE command performed on the source card. Returns the key UID. It follows exactly the same rules as the EXPORT DUPLICATE subcommand.

\n

SIGN

\n

Response Data format:

\n\n

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.

\n

SET PINLESS PATH

\n

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 has been performed. An empty sequence means that no PIN-less path is defined.

\n

EXPORT KEY

\n

P1:
0x00 = Current key
0x01 = Derive
0x02 = Derive and make current

\n

P2:
0x00 = private and public key
0x01 = public key only

\n

Response Data format:

\n\n

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 subtree.

\n

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.

\n

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.

\n","site":{"data":{"menu":{"docs":"/docs/","blog":"https://our.status.im/incubate"},"languages":{"en":"English"},"sidebar":{"api":{"API":{"getting_started":"index.html","java_sdk":"java-sdk.html","apdu":"apdu.html"}}}}},"excerpt":"","more":"

Status Keycard Protocol

These are the commands supported by the application. When a command has a precondition clause and these are not met the SW 0x6985 is returned. All tagged data structures are encoded in the BER-TLV format

\n

SELECT

\n

Response Data format:

\n\n

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) and the public key which must be used by the client to establish the Secure Channel. Additionally it contains the version number of the application, formatted on two bytes. The first byte is the major version and the second is the minor version (e.g: version 1.1 is formatted as 0x0101). The number of remaining pairing slots is also included in the response.

\n

The Key UID can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key.

\n

When the applet is in pre-initializated state, it only returns the ECC public key, BER-TLV encoded with tag 0x80.

\n

INIT

\n

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.

\n

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.

\n

They payload is the concatenation of the PIN (6 digits/bytes), PUK (12 digits/bytes) and pairing secret (32 bytes).

\n

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.

\n

After successful execution, this command cannot be executed anymore. The regular SecureChannel (with pairing) is active and PIN and PUK are initialized.

\n

OPEN SECURE CHANNEL

\n

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.

\n

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

\n
    \n
  1. Use their private key and the counterpart public key to generate a secret using the EC-DH algorithm.
  2. \n
  3. The generated secret, the pairing key and the salt are concatenated and the SHA-512 of the concatenated value is calculated.
  4. \n
  5. 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.
  6. \n
\n

The seed IV is used by the client as the IV for the next encrypted APDU.

\n

MUTUALLY AUTHENTICATE

\n

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.

\n

PAIR

\n

P1:

\n\n

Data:

\n\n

Response Data:

\n\n

This APDU is sent to pair a client. Pairing is performed with two commands which must be sent immediately one after the other.

\n

In the first phase the client sends a random challenge to the card. The card replies with the SHA-256 hash of the challenge and the shared secret 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).

\n

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.

\n

The shared secret is a 256-bit value which must be be known to both parts being paired.

\n

UNPAIR

\n

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.

\n

GET STATUS

\n

Response Data format:
if P1 = 0x00:

\n\n

if P1 = 0x01:

\n\n

SET NDEF

\n

Used to set the content of the data file owned by the NDEF applet. This allows changing the behavior of Android and other clients when tapping the card with no open client. As an example, it could be used to launch a specific wallet software.

\n

The data is stored as is. A check is made that the first 2 bytes, read as a MSB first short, are equal to the Lc field minus 2 (the length of the field itself). This does not ensure that the NDEF record is valid, but ensures that no out of bound access happens.

\n

VERIFY PIN

\n

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.

\n

CHANGE PIN

\n

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.

\n

P1:

\n\n

UNBLOCK PIN

\n

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.

\n

LOAD KEY

\n

P1:

\n\n

Data:

\n

If P1 is 0x01 or 0x02

\n\n

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 specifications. This is only supported if the hardware supports public key derivation.

\n

This command is used to load or replace the keypair used for signing on the card. This command always aborts open signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command will use this keypair for signature.

\n

DERIVE KEY

\n

This command is used before a signing session to generate a private key according to the BIP32 specifications. This command always aborts open signing sessions, if any. The generated key is used for all subsequent SIGN sessions. The maximum depth of derivation from the master key is 10. Any attempt to get deeper results in 0x6A80 being returned. The BIP32 specifications define a few checks which must be performed on the derived keys. If these fail, the 0x6984 is returned and the invalid key is discarded. A client should perform a GET STATUS command to get the actual current key path and resume derivation using a different path.

\n

The ability to start derivation from the parent keys allows to more efficiently switch between children of the same key. Note however that only the immediate parent of the current key is cached so you cannot use this to go back in the hierarchy. If no valid parent key is available the status code 0x6B00 will be returned.

\n

P1:

\n\n

GENERATE MNEMONIC

\n

Used to generate a mnemonic according to the algorithm specified in BIP39. 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.

\n

REMOVE KEY

\n

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.

\n

GENERATE KEY

\n

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.

\n

DUPLICATE KEY

\n

P1:

\n\n

START DUPLICATE

This is the first step to start duplication. Requires an open secure channel and user PIN must be verified. Aborts any on-going duplication session. P2 is the number of entropy pieces to expect in total (including this command). The data contain the first piece of entropy. Returns no data. Must be performed with exactly the same parameters and data on all cards taking part in the duplication.

\n

ADD ENTROPY

This command uses the same one-shot secure channel scheme as defined in the INIT command. P2 is 00. Requires an ongoing duplicate session started with the START DUPLICATE subcommand. Must be performed once per device taking part in the duplication process, for a total number of devices equaling the P2 parameter of the START DUPLICATE subcommand (counting the device which sent the START DUPLICATE command as the first device). The data is a random 256-bit number. The same data must be sent to all the cards taking part in the duplication process.

\n

EXPORT DUPLICATE

This command must be sent to the card which you wish to duplicate. Requires an open secure channel and authenticated PIN. Works only if a duplication session is active and ADD ENTROPY has been performed the required number of times. Returns the encrypted duplicate of the master key and terminates the duplication session for this card. The format is exactly the same as the one defined in the LOAD KEY (TLV) command with omitted public key. It is however prepended by a 16-bytes IV and the entire TLV structure is encrypted.

\n

IMPORT DUPLICATE

This command must be sent to all the cards which are a target for duplication. The Data field must contain the output from the EXPORT DUPLICATE command performed on the source card. Returns the key UID. It follows exactly the same rules as the EXPORT DUPLICATE subcommand.

\n

SIGN

\n

Response Data format:

\n\n

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.

\n

SET PINLESS PATH

\n

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 has been performed. An empty sequence means that no PIN-less path is defined.

\n

EXPORT KEY

\n

P1:
0x00 = Current key
0x01 = Derive
0x02 = Derive and make current

\n

P2:
0x00 = private and public key
0x01 = public key only

\n

Response Data format:

\n\n

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 subtree.

\n

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.

\n

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.

\n"},{"id":"java-sdk","title":"Keycard Java SDK","_content":"\n## Installation\n\nYou can import the SDK in your Gradle or Maven project using [Jitpack.io](https://jitpack.io). If using Gradle, to use\nJitPack all you have to do is insert these lines in you `build.gradle` file\n\n```groovy\nallprojects {\n repositories {\n maven { url 'https://jitpack.io' }\n }\n}\n```\n\nThen, you must import the correct dependency. In case you are building an Android-based project, you need to add this line\n\n```groovy\ndependencies {\n implementation 'com.github.status-im.status-keycard-java:android:2.0.0'\n}\n```\n\nIf you are working on the desktop, then you need this line instead\n\n```groovy\ndependencies {\n implementation 'com.github.status-im.status-keycard-java:desktop:2.0.0'\n}\n```\n\nIn both case, you will have the same SDK, except for the way connection with the card is established.\n\n## Connecting to the card (Android)\n\nOn 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.\n\n```java\npublic class MainActivity extends AppCompatActivity {\n private NfcAdapter nfcAdapter;\n private NFCCardManager cardManager;\n\n @Override\n protected void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n setContentView(R.layout.activity_main);\n \n // Get the Android NFC default adapter\n nfcAdapter = NfcAdapter.getDefaultAdapter(this);\n \n // Create the NFCCardManager, this class is provided by the Keycard SDK and handles connections to the card\n cardManager = new NFCCardManager();\n\n // The Card Listener receives the connected/disconnected events. These can happen at any time since the user can\n // introduce or remove the card to/from the field at any time. This is where your code goes.\n cardManager.setCardListener(new CardListener() {\n @Override\n public void onConnected(CardChannel cardChannel) {\n // Card is connected. Here you can start working with the Keycard. The CardChannel is what you will use to\n // communicate with the card.\n }\n\n @Override\n public void onDisconnected() {\n // Card is disconnected (was removed from the field). You can perform cleanup here.\n }\n });\n cardManager.start();\n }\n\n @Override\n public void onResume() {\n super.onResume();\n \n // We need to enable the reader on resume.\n if (nfcAdapter != null) {\n nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);\n }\n }\n\n @Override\n public void onPause() {\n super.onPause();\n \n // We disable the reader on pause to allow other apps to use it.\n if (nfcAdapter != null) {\n nfcAdapter.disableReaderMode(this);\n }\n }\n}\n```\n\n## Connecting to the card (Desktop)\n\nOn 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).\n\n```java\n// We create a TerminalFactory object\nTerminalFactory tf = TerminalFactory.getDefault();\nCardTerminal cardTerminal;\n\n// We search a terminal with a card inside\nfor (CardTerminal t : tf.terminals().list()) {\nif (t.isCardPresent()) {\n cardTerminal = t;\n break;\n }\n }\n}\n\n// If not found, we throw an exception. Of course you should decide how to handle this situation\nif (cardTerminal == null) {\n throw new RuntimeException(\"No terminal found\");\n}\n\n// If a terminal is found, we connect to it\nCard apduCard = cardTerminal.connect(\"*\");\n\n// We create a PCSCCardChannel, which is an implementation of CardChannel and can be used with the rest of the SDK.\nPCSCCardChannel apduChannel = new PCSCCardChannel(apduCard.getBasicChannel());\n```\n\n## Working with the card\n\nRegardless 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\n\n```java\n// cardChannel is our CardChannel instance\nKeycardCommandSet cmdSet = new KeycardCommandSet(cardChannel);\n```\n\nModern SmartCards can have several applications installed, so after connection with the card you need to select the Keycard applet. This is easily done with\n\n```java\n// The checkOK method can be called on any APDUResponse object to confirm that the\ncmdSet.select().checkOK();\n```\n\nWhile 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\n\n```java\nApplicationInfo info = new ApplicationInfo(cmdSet.select().checkOK().getData());\n\n// This method tells if the card is initialized (has a PIN, PUK and pairing password). If it is not, it must be\n// initialized and no other operation is possible. Note that initialization touches only credentials to authenticate\n// the user or the client, but does not touch the creation of a wallet on the card\ninfo.isInitializedCard();\n\n// Returns the instance UID of the applet. This can be used to identify this specific applet instance, very\n// useful when storing instance-specific data on the client (pairing info, cached data, etc).\ninfo.getInstanceUID();\n\n// Returns the version of the applet.\ninfo.getAppVersion();\n\n// Returns the number of free pairing slots. If you are not yet paired with the card, it helps you know if you can still\n// pair or not\ninfo.getFreePairingSlots());\n\n// Tells if the card has a wallet or not. If no wallet is available, you must create once before you can perform most\n// operations on the card\ninfo.hasMasterKey();\n\n// Returns the UID of the master key of the wallet. The UID is value generated starting from the public key and is \n// useful to identify if the card has the expected wallet.\ninfo.getKeyUID();\n```\n\nAfter 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)\n\n### Initialization\n\nThis 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).\n\nInitialization is done with\n\n```java\n// Usually, you want to check if the card is initialized before trying to initialize it, otherwise you will receive an\n// error.\nif (!info.isInitializedCard()) {\n // The PIN must be 6 digits, the PUK 12 digits and the pairing password can be any password. \n // All parameters are strings\n cmdSet.init(pin, puk, pairingPassword).checkOK();\n}\n```\n\n### Pairing\n\nClients 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.\n\nOnly 5 clients can be paired at once, but it is possible to unpair previously paired clients.\n\nUsing the SDK, pairing is a simple operation\n\n```java\n// pairingPassword is usually provided by the user. This method throws an exception if pairing fails.\ncmdSet.autoPair(pairingPassword);\n// Retrieves the pairing object from the command set. This is what must be persisted (together with the instance UID)\nPairing pairing = cmdSet.getPairing();\n// The pairing object can be serialized by calling\npairing.toByteArray();\n// or the convenience method\npairing.toBase64();\n```\n\nIf you have already paired, you should instead load the persisted pairing information in the command set\n\n```java\n// serializedPairing can be either the byte array or base64 string representation\nPairing pairing = new Pairing(serializedPairing);\n// Sets the pairing info in the command set. This must be done before further operation is possible\ncmdSet.setPairing(pairing);\n```\n\n### Secure Channel\n\nAfter 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\n\n```java\ncmdSet.autoOpenSecureChannel();\n```\n\n### Authenticating the user\n\nMost 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.\n\nAuthentication 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\n\n```java\n// pin is the user PIN as a string of 6 digits\ntry {\n cmdSet.verifyPIN(pin).checkAuthOK();\n} catch(WrongPINException e) {\n System.out.println(\"Number of remaining attempts: \" + e.getRetryAttempts());\n}\n```\n\nif 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.\n\n```java\ncmdSet.unblockPIN(puk, newPIN).checkAuthOK();\n```\n\n## Creating a wallet\n\nTo 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.\n\n### Creating a BIP39 mnemonic phrase\n\nThis 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\n\n```java\n// Generates a Mnemonic object from the card. You can choose between generating 12, 15, 18, 21 or 24 words\nMnemonic mnemonic = new Mnemonic(cmdSet.generateMnemonic(KeycardCommandSet.GENERATE_MNEMONIC_12_WORDS).checkOK().getData());\n\n// 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\n// fatch the official BIP39 english word list as shown below.\nmnemonic.fetchBIP39EnglishWordlist();\n\n// If we did not verify the PIN before, we can do it now\ncmdSet.verifyPIN(pin).checkOK();\n\n// Loads the key generated from the mnemonic phrase.\ncmdSet.loadKey(mnemonic.toBIP32KeyPair()).checkOK();\n```\n\n### Importing a wallet from BIP39 mnemonic phrase\n\nImporting an existing passphrase requires only the loading step.\n\n```java\n// The passphrase is a string with space separated words. The password can be any non-null string, usually is empty.\ncmdSet.loadKey(Mnemonic.toBinarySeed(passphrase, password)).checkOK();\n```\n\n### Generating keys on-card\n\nThis is the simplest and safest method, because the generated wallet never leaves the card and there is no \"paper backup\" to keep secure. It is possible to create secure duplicates of the wallet on other Keycards, with a mechanism described in later chapters. Using the SDK, you simply do\n\n```java\ncmdSet.generateKey().checkOK();\n```\n\n### Importing an EC keypair\n\nYou 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.\n\nAn example of key import is\n\n```java\n// privKey is the S component of the key, as a 32-byte long byte array\n// chainCode is the extension to the keypair defined by BIP32, this is another 32-byte long byte array. Can be null, in\n// which case the created wallet won't be BIP32 compatible.\n// pubKey is the DER encoded, uncompressed public key. Can be null, in which case it is automatically calculated from\n// the private key.\nBIP32KeyPair keypair = new BIP32KeyPair(privKey, chainCode, pubKey);\n\n// Loads the keypair\ncmdSet.loadKey(keypair).checkOK();\n```\n\n## Key derivation\n\nAs 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, which means that it will be used for all signing operations until a key with a different path is derived. The active key is persisted across sessions, meaning that a power loss or applet reselection does not reset it.\n\nWhen creating or importing a wallet to the Keycard, the active key is the master key. Unless you imported a non-BIP32 compatible wallet, you usually want to set the active key to a currency account by following the BIP44 specifications for paths. Note that the maximum depth of the key path is 10, excluding the master key.\n\nKey derivation requires user authentication\n\nSince a line of code is worth a thousand words, below is an example of deriving a standard key path\n\n```java\ncmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();\n```\n\nSince deriving a key is an expensive operation, you usually want to know what the current path is before performing derivation. You can do this with\n\n```java\n// you can then get is as a string with currentPath.toString()\nKeyPath currentPath = new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData());\n```\n\nTo speed up operations, key derivation can be started not only from the master key, but also from the parent or the current key. The path in this case starts respectively with \"../\" and \"./\". You cannot navigate the hierarchy with multiple \"..\" in the paths, because only the direct parent of the current key is cached. Derivation from parent is especially convenient when switching between accounts of the same type. Example\n\n```java\n// Derive the main account\ncmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();\n\n// switch a secondary account, equivalent to \"m/44'/0'/0'/0/1\" but much faster\ncmdSet.deriveKey(\"../1\").checkOK();\n\n// you can switch back and forth between siblings without limitations.\ncmdSet.deriveKey(\"../0\").checkOK();\n```\n\n## Signing\n\nYour 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.\n\nSigning is done as\n\n```java\n// hash is the hash to sign, for example the Keccak-256 hash of an Ethereum transaction\n// the signature object contains r, s, recId and the public key associated to this signature\nRecoverableSignature signature = new RecoverableSignature(hash, cmdSet.sign(hash).checkOK().getData());\n```\n\nSigning requires user authentication.\n\n## Exporting (public or EIP-1581 compliant) keys\n\nSorry 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.\n\n### Exporting the current key\n\n```java\n// Exports the current public key. This is allowed for any key path\nBIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).checkOK().getData());\n\n// Exports the entire key pair. This is only allowed for key path following the EIP-1581 definition\nBIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(false).checkOK().getData());\n```\n\n### Derive & export\n\nThe 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).\n\nA 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\n\n```java\n// The first parameter is the keypath, the second tells whether that you want to make the derived & exported key active\n// and the third tells that you only want the public key to be exported.\nBIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportKey(\"m/44'/0'/0'/0/0\", true, true).checkOK().getData());\n\n// The line above is equivalent to\n// cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();\n// BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).checkOK().getData());\n```\n\nAnother 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\n\n```java\n// Let's assume the current active path is \"m/44'/0'/0'/0/0\"\n\n// The first parameter is the key path, the second tells that you do not want to make it current and the third that you\n// want the entire keypair, not only the public key\nBIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportKey(\"m/43'/60'/1581'/0'/0\", false, false).checkOK().getData());\n\n// At this point, the current active path would still be \"m/44'/0'/0'/0/0\"\n```\n\n## Changing credentials\n\nAll 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.\n\n```java\n// Changes the user PIN\ncmdSet.changePIN(\"123456\").checkOK();\n\n// Changes the PUK\ncmdSet.changePUK(\"123456123456\").checkOK();\n\n// Changes the pairing password\ncmdSet.changePairingPassword(\"my pairing password\").checkOK();\n```\n\n## Duplication\n\nCard duplication is especially relevant when the keys have been generated on-card, without using BIP39 mnemonic (or when this has been destroyed). To make duplication secure the client must not possess the (full) encryption key. For this reason, a scheme where multiple clients are used and none of them has the full key has been devised. From the user point of view, the duplication process goes like this\n\n1. Take the card to be duplicated (source) and one or more cards to duplicate to (target)\n2. On one of the user's clients initiate the duplication. This involves entering the PIN of each of the involved cards\n3. Tap all cards to one or more additional clients (the amount must be defined before, the order is irrelevant). These clients do not need to be paired or be trusted, so the user can borrow a friend's phone without compromising security. This step does not require entering a PIN\n4. On one of the user's clients, usually the same which initiated the duplication (must be paired, trusted) finalize the duplication by first tapping the source card and then all target cards, again inserting the PIN for each.\n \nAt the end of procedure, each card will have the same master key, but PIN, PUK and pairing key remain unchanged and are independent from each other. A client could propose changing them to be all the same if desired or do this automatically. All cards are fully functional, so at this point there isn't any difference between the source card and the targets.\n\nSince the cards are still protected by the PIN, these can be stored remotely in moderately trusted places to recover from lost or destroyed cards. The duplication has been performed securely since no client ever had the full encryption key and no authentication credentials has been inserted on untrusted clients. For flexibility reason, an arbitrary number of clients can be used. Using a single client could be convenient from an UX point of view, but relies on said client not being compromised. Using 2 or 3 clients greatly increases security. More than 3 clients is probably an overkill.\n\nFrom an implementation point of view, we have two different roles a client can take\n\n1. The trusted client, starting and performing the duplication\n2. The (possibly) untrusted clients, only adding entropy to the encryption key\n\nBoth can be implemented by using the `CardDuplicator` class. On each client, the same instance of the `CardDuplicator` class must be used for the entire duplication process, otherwise duplication will fail.\n\nThe trusted client must also provide an implementation of `DuplicatorCallback`. This is needed to retrieve the Pairing and PIN for each card. Example below\n\n```java\nclass MyDuplicatorCB implements DuplicatorCallback {\n Pairing getPairing(ApplicationInfo applicationInfo) {\n // The Instance UID is the one to use when storing/retrieving pairings\n byte[] uid = applicationInfo.getInstanceUID();\n \n // Using the UID try to retrieve the pairing. The method getSavedPairing is an example and is not part of the SDK,\n // you are responsible of how you store and retrieve pairing data in your app\n Pairing pairing = getSavedPairing(uid);\n \n // Optionally, you could prompt the user and make a new pairing if none if given, but this is an UX decision in\n // your application.\n if (pairing == null) {\n pairing = tryToPair();\n }\n \n // possibly null, in this case the operation requiring pairing is aborted\n return pairing;\n }\n \n String getPIN(ApplicationInfo applicationInfo, int remainingAttempts) {\n // Optionally, you might have a cache of PINs for recently used card, but this should be done carefully as the PIN\n // is sensitive data. You might instead want to just prompt the user each time.\n String pin = getCachedPIN(applicationInfo.getInstanceUID());\n \n if (pin == null) {\n // prompt the user to insert the PIN. You can optionally inform them about how many retry attempts are left.\n // For UX reason you could also use the instance UID to show the user an identifiable name, this is again\n // application specific.\n pin = promptUser(remainingAttempts);\n }\n \n // This must not be null. PIN verification will be performed by the CardDuplicator itself, do no not perform it here!\n return pin;\n }\n}\n```\n\nThe next step to do, is instantiating a CardDuplicator.\n\nFor the trusted client\n\n```java\n// The cmdSet is a KeycardCommandSet instance, duplicatorCallback is an object implementing the DuplicatorCallback\n// interface\ncardDuplicator = new CardDuplicator(cmdSet, duplicatorCallback);\n```\n\nFor the untrusted clients\n\n```java\n// apduChannel is a CardChannel instance. Alternatively the same constructor as for the trusted client can be used,\n// passing null as the second parameter.\ncardDuplicator = new CardDuplicator(apduChannel);\n```\n\nOnce the instance has been created, depending on the role and state the client must perform a specific action every time a new card is presented. The CardDuplicator keeps track of the action performed on any card, so if the user presents the same card twice an `IllegalStateException` exception is thrown.\n\nTo start duplication on a card, for example, you might do\n\n```java\n// Client count is the amount of devices contributing to forming the entire key. This means 1 + the number of clients\n// which will be adding entropy (the untrusted clients)\ncardDuplicator.startDuplication(clientCount);\n```\n\nOn untrusted clients, to add entropy, you do\n\n```java\ncardDuplicator.addEntropy();\n```\n\nWhen the full key has been stored on all cards, you then call the following method on the source card on a trusted client\n\n```java\nbyte[] exportedKey = cardDuplicator.exportKey();\n```\n\nwhereas, from the same client, you invoke on all target cards the following\n\n```java\n// you should then check that the keyUID matches the one of the source card to be sure that the duplication has been\n// performed correctly.\nbyte[] keyUID = cardDuplicator.importKey(exportedKey);\n```\n","source":"api/java-sdk.md","raw":"---\nid: java-sdk\ntitle: Keycard Java SDK\n---\n\n## Installation\n\nYou can import the SDK in your Gradle or Maven project using [Jitpack.io](https://jitpack.io). If using Gradle, to use\nJitPack all you have to do is insert these lines in you `build.gradle` file\n\n```groovy\nallprojects {\n repositories {\n maven { url 'https://jitpack.io' }\n }\n}\n```\n\nThen, you must import the correct dependency. In case you are building an Android-based project, you need to add this line\n\n```groovy\ndependencies {\n implementation 'com.github.status-im.status-keycard-java:android:2.0.0'\n}\n```\n\nIf you are working on the desktop, then you need this line instead\n\n```groovy\ndependencies {\n implementation 'com.github.status-im.status-keycard-java:desktop:2.0.0'\n}\n```\n\nIn both case, you will have the same SDK, except for the way connection with the card is established.\n\n## Connecting to the card (Android)\n\nOn 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.\n\n```java\npublic class MainActivity extends AppCompatActivity {\n private NfcAdapter nfcAdapter;\n private NFCCardManager cardManager;\n\n @Override\n protected void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n setContentView(R.layout.activity_main);\n \n // Get the Android NFC default adapter\n nfcAdapter = NfcAdapter.getDefaultAdapter(this);\n \n // Create the NFCCardManager, this class is provided by the Keycard SDK and handles connections to the card\n cardManager = new NFCCardManager();\n\n // The Card Listener receives the connected/disconnected events. These can happen at any time since the user can\n // introduce or remove the card to/from the field at any time. This is where your code goes.\n cardManager.setCardListener(new CardListener() {\n @Override\n public void onConnected(CardChannel cardChannel) {\n // Card is connected. Here you can start working with the Keycard. The CardChannel is what you will use to\n // communicate with the card.\n }\n\n @Override\n public void onDisconnected() {\n // Card is disconnected (was removed from the field). You can perform cleanup here.\n }\n });\n cardManager.start();\n }\n\n @Override\n public void onResume() {\n super.onResume();\n \n // We need to enable the reader on resume.\n if (nfcAdapter != null) {\n nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);\n }\n }\n\n @Override\n public void onPause() {\n super.onPause();\n \n // We disable the reader on pause to allow other apps to use it.\n if (nfcAdapter != null) {\n nfcAdapter.disableReaderMode(this);\n }\n }\n}\n```\n\n## Connecting to the card (Desktop)\n\nOn 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).\n\n```java\n// We create a TerminalFactory object\nTerminalFactory tf = TerminalFactory.getDefault();\nCardTerminal cardTerminal;\n\n// We search a terminal with a card inside\nfor (CardTerminal t : tf.terminals().list()) {\nif (t.isCardPresent()) {\n cardTerminal = t;\n break;\n }\n }\n}\n\n// If not found, we throw an exception. Of course you should decide how to handle this situation\nif (cardTerminal == null) {\n throw new RuntimeException(\"No terminal found\");\n}\n\n// If a terminal is found, we connect to it\nCard apduCard = cardTerminal.connect(\"*\");\n\n// We create a PCSCCardChannel, which is an implementation of CardChannel and can be used with the rest of the SDK.\nPCSCCardChannel apduChannel = new PCSCCardChannel(apduCard.getBasicChannel());\n```\n\n## Working with the card\n\nRegardless 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\n\n```java\n// cardChannel is our CardChannel instance\nKeycardCommandSet cmdSet = new KeycardCommandSet(cardChannel);\n```\n\nModern SmartCards can have several applications installed, so after connection with the card you need to select the Keycard applet. This is easily done with\n\n```java\n// The checkOK method can be called on any APDUResponse object to confirm that the\ncmdSet.select().checkOK();\n```\n\nWhile 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\n\n```java\nApplicationInfo info = new ApplicationInfo(cmdSet.select().checkOK().getData());\n\n// This method tells if the card is initialized (has a PIN, PUK and pairing password). If it is not, it must be\n// initialized and no other operation is possible. Note that initialization touches only credentials to authenticate\n// the user or the client, but does not touch the creation of a wallet on the card\ninfo.isInitializedCard();\n\n// Returns the instance UID of the applet. This can be used to identify this specific applet instance, very\n// useful when storing instance-specific data on the client (pairing info, cached data, etc).\ninfo.getInstanceUID();\n\n// Returns the version of the applet.\ninfo.getAppVersion();\n\n// Returns the number of free pairing slots. If you are not yet paired with the card, it helps you know if you can still\n// pair or not\ninfo.getFreePairingSlots());\n\n// Tells if the card has a wallet or not. If no wallet is available, you must create once before you can perform most\n// operations on the card\ninfo.hasMasterKey();\n\n// Returns the UID of the master key of the wallet. The UID is value generated starting from the public key and is \n// useful to identify if the card has the expected wallet.\ninfo.getKeyUID();\n```\n\nAfter 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)\n\n### Initialization\n\nThis 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).\n\nInitialization is done with\n\n```java\n// Usually, you want to check if the card is initialized before trying to initialize it, otherwise you will receive an\n// error.\nif (!info.isInitializedCard()) {\n // The PIN must be 6 digits, the PUK 12 digits and the pairing password can be any password. \n // All parameters are strings\n cmdSet.init(pin, puk, pairingPassword).checkOK();\n}\n```\n\n### Pairing\n\nClients 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.\n\nOnly 5 clients can be paired at once, but it is possible to unpair previously paired clients.\n\nUsing the SDK, pairing is a simple operation\n\n```java\n// pairingPassword is usually provided by the user. This method throws an exception if pairing fails.\ncmdSet.autoPair(pairingPassword);\n// Retrieves the pairing object from the command set. This is what must be persisted (together with the instance UID)\nPairing pairing = cmdSet.getPairing();\n// The pairing object can be serialized by calling\npairing.toByteArray();\n// or the convenience method\npairing.toBase64();\n```\n\nIf you have already paired, you should instead load the persisted pairing information in the command set\n\n```java\n// serializedPairing can be either the byte array or base64 string representation\nPairing pairing = new Pairing(serializedPairing);\n// Sets the pairing info in the command set. This must be done before further operation is possible\ncmdSet.setPairing(pairing);\n```\n\n### Secure Channel\n\nAfter 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\n\n```java\ncmdSet.autoOpenSecureChannel();\n```\n\n### Authenticating the user\n\nMost 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.\n\nAuthentication 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\n\n```java\n// pin is the user PIN as a string of 6 digits\ntry {\n cmdSet.verifyPIN(pin).checkAuthOK();\n} catch(WrongPINException e) {\n System.out.println(\"Number of remaining attempts: \" + e.getRetryAttempts());\n}\n```\n\nif 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.\n\n```java\ncmdSet.unblockPIN(puk, newPIN).checkAuthOK();\n```\n\n## Creating a wallet\n\nTo 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.\n\n### Creating a BIP39 mnemonic phrase\n\nThis 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\n\n```java\n// Generates a Mnemonic object from the card. You can choose between generating 12, 15, 18, 21 or 24 words\nMnemonic mnemonic = new Mnemonic(cmdSet.generateMnemonic(KeycardCommandSet.GENERATE_MNEMONIC_12_WORDS).checkOK().getData());\n\n// 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\n// fatch the official BIP39 english word list as shown below.\nmnemonic.fetchBIP39EnglishWordlist();\n\n// If we did not verify the PIN before, we can do it now\ncmdSet.verifyPIN(pin).checkOK();\n\n// Loads the key generated from the mnemonic phrase.\ncmdSet.loadKey(mnemonic.toBIP32KeyPair()).checkOK();\n```\n\n### Importing a wallet from BIP39 mnemonic phrase\n\nImporting an existing passphrase requires only the loading step.\n\n```java\n// The passphrase is a string with space separated words. The password can be any non-null string, usually is empty.\ncmdSet.loadKey(Mnemonic.toBinarySeed(passphrase, password)).checkOK();\n```\n\n### Generating keys on-card\n\nThis is the simplest and safest method, because the generated wallet never leaves the card and there is no \"paper backup\" to keep secure. It is possible to create secure duplicates of the wallet on other Keycards, with a mechanism described in later chapters. Using the SDK, you simply do\n\n```java\ncmdSet.generateKey().checkOK();\n```\n\n### Importing an EC keypair\n\nYou 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.\n\nAn example of key import is\n\n```java\n// privKey is the S component of the key, as a 32-byte long byte array\n// chainCode is the extension to the keypair defined by BIP32, this is another 32-byte long byte array. Can be null, in\n// which case the created wallet won't be BIP32 compatible.\n// pubKey is the DER encoded, uncompressed public key. Can be null, in which case it is automatically calculated from\n// the private key.\nBIP32KeyPair keypair = new BIP32KeyPair(privKey, chainCode, pubKey);\n\n// Loads the keypair\ncmdSet.loadKey(keypair).checkOK();\n```\n\n## Key derivation\n\nAs 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, which means that it will be used for all signing operations until a key with a different path is derived. The active key is persisted across sessions, meaning that a power loss or applet reselection does not reset it.\n\nWhen creating or importing a wallet to the Keycard, the active key is the master key. Unless you imported a non-BIP32 compatible wallet, you usually want to set the active key to a currency account by following the BIP44 specifications for paths. Note that the maximum depth of the key path is 10, excluding the master key.\n\nKey derivation requires user authentication\n\nSince a line of code is worth a thousand words, below is an example of deriving a standard key path\n\n```java\ncmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();\n```\n\nSince deriving a key is an expensive operation, you usually want to know what the current path is before performing derivation. You can do this with\n\n```java\n// you can then get is as a string with currentPath.toString()\nKeyPath currentPath = new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData());\n```\n\nTo speed up operations, key derivation can be started not only from the master key, but also from the parent or the current key. The path in this case starts respectively with \"../\" and \"./\". You cannot navigate the hierarchy with multiple \"..\" in the paths, because only the direct parent of the current key is cached. Derivation from parent is especially convenient when switching between accounts of the same type. Example\n\n```java\n// Derive the main account\ncmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();\n\n// switch a secondary account, equivalent to \"m/44'/0'/0'/0/1\" but much faster\ncmdSet.deriveKey(\"../1\").checkOK();\n\n// you can switch back and forth between siblings without limitations.\ncmdSet.deriveKey(\"../0\").checkOK();\n```\n\n## Signing\n\nYour 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.\n\nSigning is done as\n\n```java\n// hash is the hash to sign, for example the Keccak-256 hash of an Ethereum transaction\n// the signature object contains r, s, recId and the public key associated to this signature\nRecoverableSignature signature = new RecoverableSignature(hash, cmdSet.sign(hash).checkOK().getData());\n```\n\nSigning requires user authentication.\n\n## Exporting (public or EIP-1581 compliant) keys\n\nSorry 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.\n\n### Exporting the current key\n\n```java\n// Exports the current public key. This is allowed for any key path\nBIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).checkOK().getData());\n\n// Exports the entire key pair. This is only allowed for key path following the EIP-1581 definition\nBIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(false).checkOK().getData());\n```\n\n### Derive & export\n\nThe 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).\n\nA 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\n\n```java\n// The first parameter is the keypath, the second tells whether that you want to make the derived & exported key active\n// and the third tells that you only want the public key to be exported.\nBIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportKey(\"m/44'/0'/0'/0/0\", true, true).checkOK().getData());\n\n// The line above is equivalent to\n// cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();\n// BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).checkOK().getData());\n```\n\nAnother 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\n\n```java\n// Let's assume the current active path is \"m/44'/0'/0'/0/0\"\n\n// The first parameter is the key path, the second tells that you do not want to make it current and the third that you\n// want the entire keypair, not only the public key\nBIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportKey(\"m/43'/60'/1581'/0'/0\", false, false).checkOK().getData());\n\n// At this point, the current active path would still be \"m/44'/0'/0'/0/0\"\n```\n\n## Changing credentials\n\nAll 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.\n\n```java\n// Changes the user PIN\ncmdSet.changePIN(\"123456\").checkOK();\n\n// Changes the PUK\ncmdSet.changePUK(\"123456123456\").checkOK();\n\n// Changes the pairing password\ncmdSet.changePairingPassword(\"my pairing password\").checkOK();\n```\n\n## Duplication\n\nCard duplication is especially relevant when the keys have been generated on-card, without using BIP39 mnemonic (or when this has been destroyed). To make duplication secure the client must not possess the (full) encryption key. For this reason, a scheme where multiple clients are used and none of them has the full key has been devised. From the user point of view, the duplication process goes like this\n\n1. Take the card to be duplicated (source) and one or more cards to duplicate to (target)\n2. On one of the user's clients initiate the duplication. This involves entering the PIN of each of the involved cards\n3. Tap all cards to one or more additional clients (the amount must be defined before, the order is irrelevant). These clients do not need to be paired or be trusted, so the user can borrow a friend's phone without compromising security. This step does not require entering a PIN\n4. On one of the user's clients, usually the same which initiated the duplication (must be paired, trusted) finalize the duplication by first tapping the source card and then all target cards, again inserting the PIN for each.\n \nAt the end of procedure, each card will have the same master key, but PIN, PUK and pairing key remain unchanged and are independent from each other. A client could propose changing them to be all the same if desired or do this automatically. All cards are fully functional, so at this point there isn't any difference between the source card and the targets.\n\nSince the cards are still protected by the PIN, these can be stored remotely in moderately trusted places to recover from lost or destroyed cards. The duplication has been performed securely since no client ever had the full encryption key and no authentication credentials has been inserted on untrusted clients. For flexibility reason, an arbitrary number of clients can be used. Using a single client could be convenient from an UX point of view, but relies on said client not being compromised. Using 2 or 3 clients greatly increases security. More than 3 clients is probably an overkill.\n\nFrom an implementation point of view, we have two different roles a client can take\n\n1. The trusted client, starting and performing the duplication\n2. The (possibly) untrusted clients, only adding entropy to the encryption key\n\nBoth can be implemented by using the `CardDuplicator` class. On each client, the same instance of the `CardDuplicator` class must be used for the entire duplication process, otherwise duplication will fail.\n\nThe trusted client must also provide an implementation of `DuplicatorCallback`. This is needed to retrieve the Pairing and PIN for each card. Example below\n\n```java\nclass MyDuplicatorCB implements DuplicatorCallback {\n Pairing getPairing(ApplicationInfo applicationInfo) {\n // The Instance UID is the one to use when storing/retrieving pairings\n byte[] uid = applicationInfo.getInstanceUID();\n \n // Using the UID try to retrieve the pairing. The method getSavedPairing is an example and is not part of the SDK,\n // you are responsible of how you store and retrieve pairing data in your app\n Pairing pairing = getSavedPairing(uid);\n \n // Optionally, you could prompt the user and make a new pairing if none if given, but this is an UX decision in\n // your application.\n if (pairing == null) {\n pairing = tryToPair();\n }\n \n // possibly null, in this case the operation requiring pairing is aborted\n return pairing;\n }\n \n String getPIN(ApplicationInfo applicationInfo, int remainingAttempts) {\n // Optionally, you might have a cache of PINs for recently used card, but this should be done carefully as the PIN\n // is sensitive data. You might instead want to just prompt the user each time.\n String pin = getCachedPIN(applicationInfo.getInstanceUID());\n \n if (pin == null) {\n // prompt the user to insert the PIN. You can optionally inform them about how many retry attempts are left.\n // For UX reason you could also use the instance UID to show the user an identifiable name, this is again\n // application specific.\n pin = promptUser(remainingAttempts);\n }\n \n // This must not be null. PIN verification will be performed by the CardDuplicator itself, do no not perform it here!\n return pin;\n }\n}\n```\n\nThe next step to do, is instantiating a CardDuplicator.\n\nFor the trusted client\n\n```java\n// The cmdSet is a KeycardCommandSet instance, duplicatorCallback is an object implementing the DuplicatorCallback\n// interface\ncardDuplicator = new CardDuplicator(cmdSet, duplicatorCallback);\n```\n\nFor the untrusted clients\n\n```java\n// apduChannel is a CardChannel instance. Alternatively the same constructor as for the trusted client can be used,\n// passing null as the second parameter.\ncardDuplicator = new CardDuplicator(apduChannel);\n```\n\nOnce the instance has been created, depending on the role and state the client must perform a specific action every time a new card is presented. The CardDuplicator keeps track of the action performed on any card, so if the user presents the same card twice an `IllegalStateException` exception is thrown.\n\nTo start duplication on a card, for example, you might do\n\n```java\n// Client count is the amount of devices contributing to forming the entire key. This means 1 + the number of clients\n// which will be adding entropy (the untrusted clients)\ncardDuplicator.startDuplication(clientCount);\n```\n\nOn untrusted clients, to add entropy, you do\n\n```java\ncardDuplicator.addEntropy();\n```\n\nWhen the full key has been stored on all cards, you then call the following method on the source card on a trusted client\n\n```java\nbyte[] exportedKey = cardDuplicator.exportKey();\n```\n\nwhereas, from the same client, you invoke on all target cards the following\n\n```java\n// you should then check that the keyUID matches the one of the source card to be sure that the duplication has been\n// performed correctly.\nbyte[] keyUID = cardDuplicator.importKey(exportedKey);\n```\n","date":"2019-01-17T08:29:06.583Z","updated":"2019-01-17T08:29:06.580Z","path":"api/java-sdk.html","_id":"cjqyxw7kw0001p0qe8jsu6rxq","comments":1,"layout":"page","content":"

Installation

You can import the SDK in your Gradle or Maven project using Jitpack.io. If using Gradle, to use
JitPack all you have to do is insert these lines in you build.gradle file

\n
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
\n

Then, you must import the correct dependency. In case you are building an Android-based project, you need to add this line

\n
dependencies {
implementation 'com.github.status-im.status-keycard-java:android:2.0.0'
}
\n

If you are working on the desktop, then you need this line instead

\n
dependencies {
implementation 'com.github.status-im.status-keycard-java:desktop:2.0.0'
}
\n

In both case, you will have the same SDK, except for the way connection with the card is established.

\n

Connecting to the card (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.

\n
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);
}
}
}
\n

Connecting to the card (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).

\n
// 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());
\n

Working with the card

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

\n
// cardChannel is our CardChannel instance
KeycardCommandSet cmdSet = new KeycardCommandSet(cardChannel);
\n

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

\n
// The checkOK method can be called on any APDUResponse object to confirm that the
cmdSet.select().checkOK();
\n

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

\n
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();
\n

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)

\n

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).

\n

Initialization is done with

\n
// 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();
}
\n

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.

\n

Only 5 clients can be paired at once, but it is possible to unpair previously paired clients.

\n

Using the SDK, pairing is a simple operation

\n
// 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();
\n

If you have already paired, you should instead load the persisted pairing information in the command set

\n
// 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);
\n

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

\n
cmdSet.autoOpenSecureChannel();
\n

Authenticating the user

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.

\n

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

\n
// 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());
}
\n

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.

\n
cmdSet.unblockPIN(puk, newPIN).checkAuthOK();
\n

Creating a wallet

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.

\n

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

\n
// 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();
\n

Importing a wallet from BIP39 mnemonic phrase

Importing an existing passphrase requires only the loading step.

\n
// 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();
\n

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. It is possible to create secure duplicates of the wallet on other Keycards, with a mechanism described in later chapters. Using the SDK, you simply do

\n
cmdSet.generateKey().checkOK();
\n

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.

\n

An example of key import is

\n
// 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();
\n

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, which means that it will be used for all signing operations until a key with a different path is derived. The active key is persisted across sessions, meaning that a power loss or applet reselection does not reset it.

\n

When creating or importing a wallet to the Keycard, the active key is the master key. Unless you imported a non-BIP32 compatible wallet, you usually want to set the active key to a currency account by following the BIP44 specifications for paths. Note that the maximum depth of the key path is 10, excluding the master key.

\n

Key derivation requires user authentication

\n

Since a line of code is worth a thousand words, below is an example of deriving a standard key path

\n
cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();
\n

Since deriving a key is an expensive operation, you usually want to know what the current path is before performing derivation. You can do this with

\n
// 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());
\n

To speed up operations, key derivation can be started not only from the master key, but also from the parent or the current key. The path in this case starts respectively with “../“ and “./“. You cannot navigate the hierarchy with multiple “..” in the paths, because only the direct parent of the current key is cached. Derivation from parent is especially convenient when switching between accounts of the same type. Example

\n
// Derive the main account
cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();

// switch a secondary account, equivalent to \"m/44'/0'/0'/0/1\" but much faster
cmdSet.deriveKey(\"../1\").checkOK();

// you can switch back and forth between siblings without limitations.
cmdSet.deriveKey(\"../0\").checkOK();
\n

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.

\n

Signing is done as

\n
// 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());
\n

Signing requires user authentication.

\n

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. 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.

\n

Exporting the current key

// Exports the current public key. This is allowed for any key path
BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).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(false).checkOK().getData());
\n

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).

\n

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

\n
// The first parameter is the keypath, the second tells whether that you want to make the derived & exported key active
// 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, true).checkOK().getData());

// The line above is equivalent to
// cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();
// BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).checkOK().getData());
\n

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

\n
// 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\"
\n

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.

\n
// 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();
\n

Duplication

Card duplication is especially relevant when the keys have been generated on-card, without using BIP39 mnemonic (or when this has been destroyed). To make duplication secure the client must not possess the (full) encryption key. For this reason, a scheme where multiple clients are used and none of them has the full key has been devised. From the user point of view, the duplication process goes like this

\n
    \n
  1. Take the card to be duplicated (source) and one or more cards to duplicate to (target)
  2. \n
  3. On one of the user’s clients initiate the duplication. This involves entering the PIN of each of the involved cards
  4. \n
  5. Tap all cards to one or more additional clients (the amount must be defined before, the order is irrelevant). These clients do not need to be paired or be trusted, so the user can borrow a friend’s phone without compromising security. This step does not require entering a PIN
  6. \n
  7. On one of the user’s clients, usually the same which initiated the duplication (must be paired, trusted) finalize the duplication by first tapping the source card and then all target cards, again inserting the PIN for each.
  8. \n
\n

At the end of procedure, each card will have the same master key, but PIN, PUK and pairing key remain unchanged and are independent from each other. A client could propose changing them to be all the same if desired or do this automatically. All cards are fully functional, so at this point there isn’t any difference between the source card and the targets.

\n

Since the cards are still protected by the PIN, these can be stored remotely in moderately trusted places to recover from lost or destroyed cards. The duplication has been performed securely since no client ever had the full encryption key and no authentication credentials has been inserted on untrusted clients. For flexibility reason, an arbitrary number of clients can be used. Using a single client could be convenient from an UX point of view, but relies on said client not being compromised. Using 2 or 3 clients greatly increases security. More than 3 clients is probably an overkill.

\n

From an implementation point of view, we have two different roles a client can take

\n
    \n
  1. The trusted client, starting and performing the duplication
  2. \n
  3. The (possibly) untrusted clients, only adding entropy to the encryption key
  4. \n
\n

Both can be implemented by using the CardDuplicator class. On each client, the same instance of the CardDuplicator class must be used for the entire duplication process, otherwise duplication will fail.

\n

The trusted client must also provide an implementation of DuplicatorCallback. This is needed to retrieve the Pairing and PIN for each card. Example below

\n
class MyDuplicatorCB implements DuplicatorCallback {
Pairing getPairing(ApplicationInfo applicationInfo) {
// The Instance UID is the one to use when storing/retrieving pairings
byte[] uid = applicationInfo.getInstanceUID();

// Using the UID try to retrieve the pairing. The method getSavedPairing is an example and is not part of the SDK,
// you are responsible of how you store and retrieve pairing data in your app
Pairing pairing = getSavedPairing(uid);

// Optionally, you could prompt the user and make a new pairing if none if given, but this is an UX decision in
// your application.
if (pairing == null) {
pairing = tryToPair();
}

// possibly null, in this case the operation requiring pairing is aborted
return pairing;
}

String getPIN(ApplicationInfo applicationInfo, int remainingAttempts) {
// Optionally, you might have a cache of PINs for recently used card, but this should be done carefully as the PIN
// is sensitive data. You might instead want to just prompt the user each time.
String pin = getCachedPIN(applicationInfo.getInstanceUID());

if (pin == null) {
// prompt the user to insert the PIN. You can optionally inform them about how many retry attempts are left.
// For UX reason you could also use the instance UID to show the user an identifiable name, this is again
// application specific.
pin = promptUser(remainingAttempts);
}

// This must not be null. PIN verification will be performed by the CardDuplicator itself, do no not perform it here!
return pin;
}
}
\n

The next step to do, is instantiating a CardDuplicator.

\n

For the trusted client

\n
// The cmdSet is a KeycardCommandSet instance, duplicatorCallback is an object implementing the DuplicatorCallback
// interface
cardDuplicator = new CardDuplicator(cmdSet, duplicatorCallback);
\n

For the untrusted clients

\n
// apduChannel is a CardChannel instance. Alternatively the same constructor as for the trusted client can be used,
// passing null as the second parameter.
cardDuplicator = new CardDuplicator(apduChannel);
\n

Once the instance has been created, depending on the role and state the client must perform a specific action every time a new card is presented. The CardDuplicator keeps track of the action performed on any card, so if the user presents the same card twice an IllegalStateException exception is thrown.

\n

To start duplication on a card, for example, you might do

\n
// Client count is the amount of devices contributing to forming the entire key. This means 1 + the number of clients
// which will be adding entropy (the untrusted clients)
cardDuplicator.startDuplication(clientCount);
\n

On untrusted clients, to add entropy, you do

\n
cardDuplicator.addEntropy();
\n

When the full key has been stored on all cards, you then call the following method on the source card on a trusted client

\n
byte[] exportedKey = cardDuplicator.exportKey();
\n

whereas, from the same client, you invoke on all target cards the following

\n
// you should then check that the keyUID matches the one of the source card to be sure that the duplication has been
// performed correctly.
byte[] keyUID = cardDuplicator.importKey(exportedKey);
\n","site":{"data":{"menu":{"docs":"/docs/","blog":"https://our.status.im/incubate"},"languages":{"en":"English"},"sidebar":{"api":{"API":{"overview":"index.html"},"Tutorials":{"learn_more":"../tutorials/"}},"tutorials":{"start":{"start_here":"index.html"},"by_lanaguge":{"go":"go_tutorial.html"},"Api":{"back_to_api":"../api/"}}}}},"excerpt":"","more":"

Installation

You can import the SDK in your Gradle or Maven project using Jitpack.io. If using Gradle, to use
JitPack all you have to do is insert these lines in you build.gradle file

\n
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
\n

Then, you must import the correct dependency. In case you are building an Android-based project, you need to add this line

\n
dependencies {
implementation 'com.github.status-im.status-keycard-java:android:2.0.0'
}
\n

If you are working on the desktop, then you need this line instead

\n
dependencies {
implementation 'com.github.status-im.status-keycard-java:desktop:2.0.0'
}
\n

In both case, you will have the same SDK, except for the way connection with the card is established.

\n

Connecting to the card (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.

\n
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);
}
}
}
\n

Connecting to the card (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).

\n
// 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());
\n

Working with the card

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

\n
// cardChannel is our CardChannel instance
KeycardCommandSet cmdSet = new KeycardCommandSet(cardChannel);
\n

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

\n
// The checkOK method can be called on any APDUResponse object to confirm that the
cmdSet.select().checkOK();
\n

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

\n
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();
\n

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)

\n

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).

\n

Initialization is done with

\n
// 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();
}
\n

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.

\n

Only 5 clients can be paired at once, but it is possible to unpair previously paired clients.

\n

Using the SDK, pairing is a simple operation

\n
// 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();
\n

If you have already paired, you should instead load the persisted pairing information in the command set

\n
// 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);
\n

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

\n
cmdSet.autoOpenSecureChannel();
\n

Authenticating the user

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.

\n

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

\n
// 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());
}
\n

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.

\n
cmdSet.unblockPIN(puk, newPIN).checkAuthOK();
\n

Creating a wallet

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.

\n

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

\n
// 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();
\n

Importing a wallet from BIP39 mnemonic phrase

Importing an existing passphrase requires only the loading step.

\n
// 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();
\n

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. It is possible to create secure duplicates of the wallet on other Keycards, with a mechanism described in later chapters. Using the SDK, you simply do

\n
cmdSet.generateKey().checkOK();
\n

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.

\n

An example of key import is

\n
// 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();
\n

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, which means that it will be used for all signing operations until a key with a different path is derived. The active key is persisted across sessions, meaning that a power loss or applet reselection does not reset it.

\n

When creating or importing a wallet to the Keycard, the active key is the master key. Unless you imported a non-BIP32 compatible wallet, you usually want to set the active key to a currency account by following the BIP44 specifications for paths. Note that the maximum depth of the key path is 10, excluding the master key.

\n

Key derivation requires user authentication

\n

Since a line of code is worth a thousand words, below is an example of deriving a standard key path

\n
cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();
\n

Since deriving a key is an expensive operation, you usually want to know what the current path is before performing derivation. You can do this with

\n
// 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());
\n

To speed up operations, key derivation can be started not only from the master key, but also from the parent or the current key. The path in this case starts respectively with “../“ and “./“. You cannot navigate the hierarchy with multiple “..” in the paths, because only the direct parent of the current key is cached. Derivation from parent is especially convenient when switching between accounts of the same type. Example

\n
// Derive the main account
cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();

// switch a secondary account, equivalent to \"m/44'/0'/0'/0/1\" but much faster
cmdSet.deriveKey(\"../1\").checkOK();

// you can switch back and forth between siblings without limitations.
cmdSet.deriveKey(\"../0\").checkOK();
\n

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.

\n

Signing is done as

\n
// 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());
\n

Signing requires user authentication.

\n

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. 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.

\n

Exporting the current key

// Exports the current public key. This is allowed for any key path
BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).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(false).checkOK().getData());
\n

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).

\n

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

\n
// The first parameter is the keypath, the second tells whether that you want to make the derived & exported key active
// 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, true).checkOK().getData());

// The line above is equivalent to
// cmdSet.deriveKey(\"m/44'/0'/0'/0/0\").checkOK();
// BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(true).checkOK().getData());
\n

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

\n
// 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\"
\n

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.

\n
// 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();
\n

Duplication

Card duplication is especially relevant when the keys have been generated on-card, without using BIP39 mnemonic (or when this has been destroyed). To make duplication secure the client must not possess the (full) encryption key. For this reason, a scheme where multiple clients are used and none of them has the full key has been devised. From the user point of view, the duplication process goes like this

\n
    \n
  1. Take the card to be duplicated (source) and one or more cards to duplicate to (target)
  2. \n
  3. On one of the user’s clients initiate the duplication. This involves entering the PIN of each of the involved cards
  4. \n
  5. Tap all cards to one or more additional clients (the amount must be defined before, the order is irrelevant). These clients do not need to be paired or be trusted, so the user can borrow a friend’s phone without compromising security. This step does not require entering a PIN
  6. \n
  7. On one of the user’s clients, usually the same which initiated the duplication (must be paired, trusted) finalize the duplication by first tapping the source card and then all target cards, again inserting the PIN for each.
  8. \n
\n

At the end of procedure, each card will have the same master key, but PIN, PUK and pairing key remain unchanged and are independent from each other. A client could propose changing them to be all the same if desired or do this automatically. All cards are fully functional, so at this point there isn’t any difference between the source card and the targets.

\n

Since the cards are still protected by the PIN, these can be stored remotely in moderately trusted places to recover from lost or destroyed cards. The duplication has been performed securely since no client ever had the full encryption key and no authentication credentials has been inserted on untrusted clients. For flexibility reason, an arbitrary number of clients can be used. Using a single client could be convenient from an UX point of view, but relies on said client not being compromised. Using 2 or 3 clients greatly increases security. More than 3 clients is probably an overkill.

\n

From an implementation point of view, we have two different roles a client can take

\n
    \n
  1. The trusted client, starting and performing the duplication
  2. \n
  3. The (possibly) untrusted clients, only adding entropy to the encryption key
  4. \n
\n

Both can be implemented by using the CardDuplicator class. On each client, the same instance of the CardDuplicator class must be used for the entire duplication process, otherwise duplication will fail.

\n

The trusted client must also provide an implementation of DuplicatorCallback. This is needed to retrieve the Pairing and PIN for each card. Example below

\n
class MyDuplicatorCB implements DuplicatorCallback {
Pairing getPairing(ApplicationInfo applicationInfo) {
// The Instance UID is the one to use when storing/retrieving pairings
byte[] uid = applicationInfo.getInstanceUID();

// Using the UID try to retrieve the pairing. The method getSavedPairing is an example and is not part of the SDK,
// you are responsible of how you store and retrieve pairing data in your app
Pairing pairing = getSavedPairing(uid);

// Optionally, you could prompt the user and make a new pairing if none if given, but this is an UX decision in
// your application.
if (pairing == null) {
pairing = tryToPair();
}

// possibly null, in this case the operation requiring pairing is aborted
return pairing;
}

String getPIN(ApplicationInfo applicationInfo, int remainingAttempts) {
// Optionally, you might have a cache of PINs for recently used card, but this should be done carefully as the PIN
// is sensitive data. You might instead want to just prompt the user each time.
String pin = getCachedPIN(applicationInfo.getInstanceUID());

if (pin == null) {
// prompt the user to insert the PIN. You can optionally inform them about how many retry attempts are left.
// For UX reason you could also use the instance UID to show the user an identifiable name, this is again
// application specific.
pin = promptUser(remainingAttempts);
}

// This must not be null. PIN verification will be performed by the CardDuplicator itself, do no not perform it here!
return pin;
}
}
\n

The next step to do, is instantiating a CardDuplicator.

\n

For the trusted client

\n
// The cmdSet is a KeycardCommandSet instance, duplicatorCallback is an object implementing the DuplicatorCallback
// interface
cardDuplicator = new CardDuplicator(cmdSet, duplicatorCallback);
\n

For the untrusted clients

\n
// apduChannel is a CardChannel instance. Alternatively the same constructor as for the trusted client can be used,
// passing null as the second parameter.
cardDuplicator = new CardDuplicator(apduChannel);
\n

Once the instance has been created, depending on the role and state the client must perform a specific action every time a new card is presented. The CardDuplicator keeps track of the action performed on any card, so if the user presents the same card twice an IllegalStateException exception is thrown.

\n

To start duplication on a card, for example, you might do

\n
// Client count is the amount of devices contributing to forming the entire key. This means 1 + the number of clients
// which will be adding entropy (the untrusted clients)
cardDuplicator.startDuplication(clientCount);
\n

On untrusted clients, to add entropy, you do

\n
cardDuplicator.addEntropy();
\n

When the full key has been stored on all cards, you then call the following method on the source card on a trusted client

\n
byte[] exportedKey = cardDuplicator.exportKey();
\n

whereas, from the same client, you invoke on all target cards the following

\n
// you should then check that the keyUID matches the one of the source card to be sure that the duplication has been
// performed correctly.
byte[] keyUID = cardDuplicator.importKey(exportedKey);
\n"}],"Post":[{"title":"Status Keycard","_content":"","source":"_posts/2019-01-16-keycard.md","raw":"title: Status Keycard\n---\n","slug":"keycard","published":1,"date":"2019-01-16T00:00:00.000Z","updated":"2019-01-17T10:16:58.615Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cjr0gnvvb0000mgqe841bfhgw","content":"","site":{"data":{"menu":{"docs":"/docs/","blog":"https://our.status.im/incubate"},"languages":{"en":"English"},"sidebar":{"api":{"API":{"getting_started":"index.html","java_sdk":"java-sdk.html","apdu":"apdu.html"}}}}},"excerpt":"","more":""}],"PostAsset":[],"PostCategory":[],"PostTag":[],"Tag":[]}} \ No newline at end of file +{"meta":{"version":1,"warehouse":"2.2.0"},"models":{"Asset":[],"Cache":[],"Category":[],"Data":[],"Page":[],"Post":[],"PostAsset":[],"PostCategory":[],"PostTag":[],"Tag":[]}} \ No newline at end of file