diff --git a/package-lock.json b/package-lock.json index 2e7a7ee..a2755c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "6.29.4", "license": "Apache-2.0", "dependencies": { + "@concordium/common-sdk": "^9.5.3", "@concordium/web-sdk": "^8.1.0", "@ledgerhq/errors": "^6.19.1", "@ledgerhq/hw-transport": "^6.31.4", "bip32-path": "^0.4.2", + "cbor": "^10.0.3", "hi-base32": "^0.5.1", "js-sha512": "^0.8.0", "tweetnacl": "^1.0.3" @@ -512,6 +514,74 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@concordium/common-sdk": { + "version": "9.5.3", + "resolved": "https://registry.npmjs.org/@concordium/common-sdk/-/common-sdk-9.5.3.tgz", + "integrity": "sha512-Ly2AZM2dZKv/qAxaLFxlV+KKnLAZ/W8Rr/pzi4+2J6tvL9mzkNNPaqrOhW10bUngK154zRnD3ct329i06vSa+w==", + "dependencies": { + "@concordium/rust-bindings": "1.2.0", + "@grpc/grpc-js": "^1.3.4", + "@noble/ed25519": "^1.7.1", + "@protobuf-ts/runtime-rpc": "^2.8.2", + "@scure/bip39": "^1.1.0", + "big.js": "^6.2.0", + "bs58check": "^2.1.2", + "buffer": "^6.0.3", + "cross-fetch": "3.1.5", + "hash.js": "^1.1.7", + "iso-3166-1": "^2.1.1", + "json-bigint": "^1.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.16.0" + } + }, + "node_modules/@concordium/common-sdk/node_modules/@concordium/rust-bindings": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@concordium/rust-bindings/-/rust-bindings-1.2.0.tgz", + "integrity": "sha512-GlfHg8uQCr0QJZZt6b1qZacJrVrgDXWjj68D0PvE3CNXo0yAqr81JXrUbpgfVzUXNabzfsUaDUifS8z8b197HA==", + "engines": { + "node": ">=14.16.0" + } + }, + "node_modules/@concordium/common-sdk/node_modules/@noble/ed25519": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", + "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@concordium/common-sdk/node_modules/base-x": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", + "integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@concordium/common-sdk/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@concordium/common-sdk/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "node_modules/@concordium/rust-bindings": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@concordium/rust-bindings/-/rust-bindings-3.2.1.tgz", @@ -2115,6 +2185,17 @@ } ] }, + "node_modules/cbor": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.3.tgz", + "integrity": "sha512-72Jnj81xMsqepqdcSdf2+fflz/UDsThOHy5hj2MW5F5xzHL8Oa0KQ6I6V9CwVUPxg5pf+W9xp6W2KilaRXWWtw==", + "dependencies": { + "nofilter": "^3.0.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -2215,6 +2296,18 @@ "node": ">=8" } }, + "node_modules/cipher-base": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.5.tgz", + "integrity": "sha512-xq7ICKB4TMHUx7Tz1L9O2SGKOhYMOTR32oir45Bq28/AQTpHogKgHcoYFSdRbMtddl+ozNXfXY9jWcgYKmde0w==", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cjs-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", @@ -2306,6 +2399,18 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2364,6 +2469,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2922,6 +3035,19 @@ "node": ">=8" } }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -4864,6 +4990,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/mdast-util-definitions": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", @@ -5807,6 +5943,25 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5819,6 +5974,14 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "engines": { + "node": ">=12.19" + } + }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -6380,6 +6543,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6607,6 +6783,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -6627,6 +6812,25 @@ "node": ">=6" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -6638,6 +6842,18 @@ "node": ">=10" } }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6778,6 +6994,14 @@ "node": ">=8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6989,6 +7213,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -7308,6 +7537,11 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -7507,6 +7741,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 99071bf..dc74751 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,12 @@ "types": "lib/Algorand.d.ts", "license": "Apache-2.0", "dependencies": { + "@concordium/common-sdk": "^9.5.3", "@concordium/web-sdk": "^8.1.0", "@ledgerhq/errors": "^6.19.1", "@ledgerhq/hw-transport": "^6.31.4", "bip32-path": "^0.4.2", + "cbor": "^10.0.3", "hi-base32": "^0.5.1", "js-sha512": "^0.8.0", "tweetnacl": "^1.0.3" diff --git a/src/Algorand.ts b/src/Algorand.ts deleted file mode 100644 index 2e07016..0000000 --- a/src/Algorand.ts +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************** - * Ledger Node JS API - * (c) 2017-2018 Ledger - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ********************************************************************************/ -import type Transport from "@ledgerhq/hw-transport"; -import BIPPath from "bip32-path"; -import { UserRefusedOnDevice } from "@ledgerhq/errors"; -import { encodeAddress } from "./utils"; -const CHUNK_SIZE = 250; -// const P1_FIRST = 0x00; -const P1_MORE = 0x80; -const P1_WITH_ACCOUNT_ID = 0x01; -const P2_LAST = 0x00; -const P2_MORE = 0x80; -const SW_OK = 0x9000; -const SW_CANCEL = 0x6986; -const P1_WITH_REQUEST_USER_APPROVAL = 0x80; -// algo spec -const CLA = 0x80; -const INS_GET_PUBLIC_KEY = 0x03; -const INS_SIGN_MSGPACK = 0x08; -/** - * Algorand API - * - * @example - * import Algorand from "@ledgerhq/hw-app-algorand"; - * const algo = new Algorand(transport) - */ - -export default class Algorand { - transport: Transport; - - constructor(transport: Transport) { - this.transport = transport; - transport.decorateAppAPIMethods(this, ["getAddress", "sign"], "ALGO"); - } - - /** - * get Algorant address for a given BIP 32 path. - * @param path a path in BIP 32 format - * @option boolDisplay optionally enable or not the display - * @return an object with a publicKey, address and (optionally) chainCode - * @example - * cosmos.getAddress("44'/283'/0'/0/0").then(o => o.address) - */ - getAddress( - path: string, - boolDisplay?: boolean, - ): Promise<{ - publicKey: string; - address: string; - }> { - const bipPath = BIPPath.fromString(path).toPathArray(); - const buf = Buffer.alloc(4); - buf.writeUInt32BE(bipPath[2], 0); - return this.transport - .send(CLA, INS_GET_PUBLIC_KEY, boolDisplay ? P1_WITH_REQUEST_USER_APPROVAL : 0, 0, buf, [ - SW_OK, - ]) - .then(response => { - const buffer = Buffer.from(response.slice(0, 32)); - const publicKey = buffer.toString("hex"); - const address = encodeAddress(buffer); - return { - publicKey, - address, - }; - }); - } - - foreach(arr: T[], callback: (arg0: T, arg1: number) => Promise): Promise { - function iterate(index, array, result) { - if (index >= array.length) { - return result; - } else - return callback(array[index], index).then(function (res) { - result.push(res); - return iterate(index + 1, array, result); - }); - } - - return Promise.resolve().then(() => iterate(0, arr, [])); - } - - async sign( - path: string, - message: string, - ): Promise<{ - signature: null | Buffer; - }> { - const bipPath = BIPPath.fromString(path).toPathArray(); - const buf = Buffer.alloc(4); - buf.writeUInt32BE(bipPath[2], 0); - const chunks: Buffer[] = []; - const buffer = Buffer.concat([buf, Buffer.from(message, "hex")]); - - for (let i = 0; i < buffer.length; i += CHUNK_SIZE) { - let end = i + CHUNK_SIZE; - - if (i > buffer.length) { - end = buffer.length; - } - - chunks.push(buffer.slice(i, end)); - } - - let response: any = {}; - return this.foreach(chunks, (data, j) => - this.transport - .send( - CLA, - INS_SIGN_MSGPACK, - j === 0 ? P1_WITH_ACCOUNT_ID : P1_MORE, - j + 1 === chunks.length ? P2_LAST : P2_MORE, - data, - [SW_OK, SW_CANCEL], - ) - .then(apduResponse => (response = apduResponse)), - ).then(() => { - const errorCodeData = response; - - if (errorCodeData === 0x6986) { - throw new UserRefusedOnDevice(); - } - - let signature = null; - - if (response.length > 0) { - signature = response.slice(0, response.length); - } - - return { - signature: signature, - }; - }); - } -} diff --git a/src/Concordium.ts b/src/Concordium.ts index 8904564..36d8151 100644 --- a/src/Concordium.ts +++ b/src/Concordium.ts @@ -6,14 +6,17 @@ import { serializeConcordiumTransaction, } from "./serialization"; import BigNumber from "bignumber.js"; -import * as ConcordiumSDK from "@concordium/web-sdk"; +import { encodeInt32 } from "./utils"; const LEDGER_CLA = 0xe0; -const CLA_OFFSET = 0x00; // FOR GET VERSION AND APP NAME const NONE = 0x00; +// FOR VERIFY ADRESS +const P1_LEGACY_VERIFY_ADDRESS = 0x00; +const P1_VERIFY_ADDRESS = 0x01; + // FOR GET PUBLIC KEY const P1_NON_CONFIRM = 0x00; const P1_CONFIRM = 0x01; @@ -24,13 +27,15 @@ const P2_MORE = 0x80; const P2_LAST = 0x00; const INS = { + VERIFY_ADDRESS: 0x00, GET_VERSION: 0x03, GET_APP_NAME: 0x04, GET_PUBLIC_KEY: 0x05, SIGN_TX: 0x06, }; -const concordium_path = "1105'/0'/0'/0/"; +const concordium_path = "44'/919'/0'/0/"; +const concordium_legacy_path = "1105'/0'/0'/0/"; /** * Concordium API @@ -55,6 +60,7 @@ export default class Concordium { [ "getVersion", "getAddress", + "verifyAddress", "signTransaction", ], scrambleKey @@ -81,6 +87,51 @@ export default class Concordium { }; } + /** + * Legacy Verify address. + * + * @returns status + * + * @example + * concordium.verifyAddressLegacy().then(r => r.status) + */ + async verifyAddressLegacy(id: number, cred: number): Promise<{ status: string }> { + const idEncoded = encodeInt32(id); + const credEncoded = encodeInt32(cred); + const statusResult = await this.sendToDevice( + INS.VERIFY_ADDRESS, + P1_LEGACY_VERIFY_ADDRESS, + NONE, + Buffer.concat([idEncoded,credEncoded]) + ); + return { + status: `${statusResult}`, + }; + } + + /** + * Verify address. + * + * @returns status + * + * @example + * concordium.verifyAddress().then(r => r.status) + */ + async verifyAddress(idp:number, id: number, cred: number): Promise<{ status: string }> { + const idEncoded = encodeInt32(id); + const idpEncoded = encodeInt32(idp); + const credEncoded = encodeInt32(cred); + const statusResult = await this.sendToDevice( + INS.VERIFY_ADDRESS, + P1_VERIFY_ADDRESS, + NONE, + Buffer.concat([idpEncoded, idEncoded, credEncoded]) + ); + return { + status: `${statusResult}`, + }; + } + /** * Get Concordium address (public key) for a BIP32 path. * @@ -124,11 +175,11 @@ export default class Concordium { ) .toString("ascii"), chainCode: addressBuffer - .subarray( - 1 + publicKeyLength + 1 + addressLength + 1, - 1 + publicKeyLength + 1 + addressLength + chainCodeLength - ) - .toString("hex"), + .subarray( + 1 + publicKeyLength + 1 + addressLength + 1, + 1 + publicKeyLength + 1 + addressLength + chainCodeLength + ) + .toString("hex"), }; } @@ -136,16 +187,19 @@ export default class Concordium { * Signs a Concordium transaction using the specified account index. * @param txn - The transaction to sign. * @param accountIndex - The index of the account to use for signing. Default is 0. + * @param isLgacyPath - Boolean if true use the legacy derivation path. * @returns An object containing the signature and the signed transaction. * @throws Error if the user declines the transaction. * @example * concordium.signTransfer(txn).then(r => r.signature) */ - async signTransfer(txn:ConcordiumSDK.AccountTransaction, accountIndex=0): Promise<{ signature: string[]; transaction: ConcordiumSDK.AccountTransaction }> { + async signTransfer(txn, isLegacyPath: boolean ,accountIndex = 0 ): Promise<{ signature: string[]; transaction }> { + + const path = isLegacyPath ? concordium_legacy_path : concordium_path - const { payloads } = serializeConcordiumTransaction(txn, concordium_path + accountIndex); + const { payloads } = serializeConcordiumTransaction(txn, path + accountIndex); - let response = Buffer.from([1,2,3]); + let response; for (let i = 0; i < payloads.length; i++) { const lastChunk = i === payloads.length - 1; @@ -159,7 +213,7 @@ export default class Concordium { if (response.length === 1) throw new Error("User has declined."); - const signature= [""]; + const signature = [""]; // const signature = this.serializeAndFormatSignature( // response, // chainId, @@ -169,7 +223,7 @@ export default class Concordium { return { - signature: signature, + signature: response, transaction: txn, }; } diff --git a/src/serialization.ts b/src/serialization.ts index d69e5de..5bb8c7d 100644 --- a/src/serialization.ts +++ b/src/serialization.ts @@ -1,6 +1,7 @@ import * as ConcordiumSDK from "@concordium/web-sdk"; import BigNumber from "bignumber.js"; import BIPPath from "bip32-path"; +import { serializeAccountTransaction } from "./utils"; const MAX_CHUNK_SIZE = 255; @@ -180,19 +181,12 @@ export const serializeConcordiumTransaction = ( path: string ): { payloads: Buffer[]; - // txType: string | null; - // chainId: BigNumber; - // chainIdTruncated: number; } => { - const txnToString = txn.toString(); - const rawTx = Buffer.from(txnToString, "hex"); - - // const { vrsOffset, txType, chainId, chainIdTruncated } = decodeTxInfo( - // rawTx, - // txn.type.toString() - // ); - const payloads = serializeTransactionPayloads(path, rawTx); + const txSerialized = serializeAccountTransaction(txn); + const payloads = serializeTransactionPayloads(path, txSerialized); + console.log("GUI Payload: ",payloads); + return { payloads }; }; diff --git a/src/utils.ts b/src/utils.ts index e131d44..e974b2d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,29 +1,110 @@ -import nacl from "tweetnacl"; -import base32 from "hi-base32"; -import sha512 from "js-sha512"; +import { AccountAddress, getAccountTransactionHandler } from "@concordium/web-sdk"; -const PUBLIC_KEY_LENGTH = nacl.sign.publicKeyLength; -const ALGORAND_ADDRESS_LENGTH = 58; -const ALGORAND_CHECKSUM_BYTE_LENGTH = 4; -export const encodeAddress = (address: Uint8Array): string => { - const checksum = sha512.sha512_256 - .array(address) - .slice(PUBLIC_KEY_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, PUBLIC_KEY_LENGTH); - const addr = base32.encode(concatArrays(address, checksum)); - - return addr.toString().slice(0, ALGORAND_ADDRESS_LENGTH); -}; +/** + * Encodes a 8 bit signed integer to a Buffer using big endian. + * @param value a 8 bit integer + * @returns big endian serialization of the input + */ +export function encodeInt8(value: number): Buffer { + if (value > 127 || value < -128 || !Number.isInteger(value)) { + throw new Error('The input has to be a 8 bit signed integer but it was: ' + value); + } -function concatArrays(...arrs: ArrayLike[]): Uint8Array { - const size = arrs.reduce((sum, arr) => sum + arr.length, 0); - const c = new Uint8Array(size); + return Buffer.from(Buffer.of(value)); +} - let offset = 0; - for (let i = 0; i < arrs.length; i++) { - c.set(arrs[i], offset); - offset += arrs[i].length; +/** + * Encodes a 64 bit unsigned integer to a Buffer using big endian. + * @param value a 64 bit integer + * @param useLittleEndian a boolean value, if not given, the value is serialized in big endian. + * @returns big endian serialization of the input + */ +export function encodeWord64(value, useLittleEndian = false) { + if (value > BigInt(18446744073709551615) || value < BigInt(0)) { + throw new Error('The input has to be a 64 bit unsigned integer but it was: ' + value); + } + const arr = new ArrayBuffer(8); + const view = new DataView(arr); + view.setBigUint64(0, value, useLittleEndian); + return Buffer.from(new Uint8Array(arr)); +} +/** +* Encodes a 32 bit signed integer to a Buffer using big endian. +* @param value a 32 bit integer +* @param useLittleEndian a boolean value, if not given, the value is serialized in big endian. +* @returns big endian serialization of the input +*/ +export function encodeInt32(value, useLittleEndian = false) { + if (value < -2147483648 || value > 2147483647 || !Number.isInteger(value)) { + throw new Error('The input has to be a 32 bit signed integer but it was: ' + value); } + const arr = new ArrayBuffer(4); + const view = new DataView(arr); + view.setInt32(0, value, useLittleEndian); + return Buffer.from(new Int8Array(arr)); +} +/** +* Encodes a 32 bit unsigned integer to a Buffer. +* @param value a 32 bit integer +* @param useLittleEndian a boolean value, if not given, the value is serialized in big endian. +* @returns big endian serialization of the input +*/ +export function encodeWord32(value, useLittleEndian = false) { + if (value > 4294967295 || value < 0 || !Number.isInteger(value)) { + throw new Error('The input has to be a 32 bit unsigned integer but it was: ' + value); + } + const arr = new ArrayBuffer(4); + const view = new DataView(arr); + view.setUint32(0, value, useLittleEndian); + return Buffer.from(new Uint8Array(arr)); +} - return c; +/** + * Serialization of an account transaction header. The payload size is a part of the header, + * but is factored out of the type as it always has to be derived from the serialized + * transaction payload, which cannot happen until the payload has been constructed. + * @param header the account transaction header with metadata about the transaction + * @param payloadSize the byte size of the serialized payload + * @returns the serialized account transaction header + */ +const serializeAccountTransactionHeader = (accountTransaction, payloadSize) => { + console.log("GUI Sender: ", accountTransaction.sender); + const serializedSender = AccountAddress.toBuffer(accountTransaction.sender); + console.log("GUI Sender: ", serializedSender); + const serializedNonce = encodeWord64(accountTransaction.nonce); + console.log("GUI Nonce: ", serializedNonce); + const serializedEnergyAmount = encodeWord64(accountTransaction.energyAmount); + console.log("GUI Energy: ", serializedEnergyAmount); + const serializedPayloadSize = encodeWord32(payloadSize); + console.log("GUI Size: ", serializedPayloadSize); + const serializedExpiry = encodeWord64(accountTransaction.expiry); + console.log("GUI Expiry: ", serializedExpiry); + return Buffer.concat([ + serializedSender, + serializedNonce, + serializedEnergyAmount, + serializedPayloadSize, + serializedExpiry, + ]); } + +/** +* Serializes a transaction and its signatures. This serialization when sha256 hashed +* is considered as the transaction hash, and is used to look up the status of a +* submitted transaction. +* @param accountTransaction the transaction to serialize +* @param signatures signatures on the signed digest of the transaction +* @returns the serialization of the account transaction, which is used to calculate the transaction hash +*/ +export const serializeAccountTransaction = (accountTransaction) => { + const serializedType = Buffer.from(Uint8Array.of(accountTransaction.transactionKind)); + const accountTransactionHandler = getAccountTransactionHandler(accountTransaction.transactionKind); + const serializedPayload = accountTransactionHandler.serialize(accountTransaction.payload); + const serializedHeader = serializeAccountTransactionHeader(accountTransaction, serializedPayload.length + 1); + return Buffer.concat([ + serializedHeader, + serializedType, + serializedPayload, + ]); +} \ No newline at end of file diff --git a/test.js b/test.js index ae6d25f..afef098 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,8 @@ // import Concordium from "./src/Concordium"; -import { AccountAddress, AccountTransactionType, CcdAmount, SequenceNumber, serializeAccountTransactionPayload, serializeAccountTransaction, TransactionExpiry } from "@concordium/web-sdk"; - +import { AccountAddress, AccountTransactionType, CcdAmount, SequenceNumber, TransactionExpiry } from "@concordium/web-sdk"; +import { serializeAccountTransaction } from "./lib-es/utils.js"; +import { encodeInt32, encodeInt8 } from "@concordium/common-sdk/lib/serializationHelpers.js"; +import { serializeConcordiumTransaction } from "./lib-es/serialization.js" // import { createTransport } from "@ledgerhq/hw-transport-mocker"; // const transport = createTransport(); @@ -39,9 +41,24 @@ const sender = AccountAddress.fromBase58(test_sender_address); payload: simpleTransfer, type: AccountTransactionType.Transfer, }; + const idEncoded = encodeInt32(0); + const credEncoded = encodeInt32(0); +console.log("GUI TEST", Buffer.concat([idEncoded,credEncoded]).toString("hex")); + + + const transaction = { + sender, + nonce: nonce.toString(), + expiry: BigInt(123456), + energyAmount: '1234', + transactionKind: AccountTransactionType.Transfer, + payload: simpleTransfer, +}; +const txSerialized = serializeAccountTransaction(transaction); +console.log("Hex transaction", txSerialized.toString('hex')); -const txSerialized = serializeAccountTransaction(accountTransaction, ""); -console.log("Hex transaction", txSerialized); +const trx = serializeConcordiumTransaction(transaction, "44'/919'/0'/0/0") +console.log("GUI TX: ", trx); // const tx = Buffer.from(,txSerialized]) // console.log("Transaction", tx);