From 3499e0e505b6f41eafa50ed8647d7653bfd8ecd5 Mon Sep 17 00:00:00 2001 From: GuilaneDen <83951892+GuilaneDen@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:32:55 +0100 Subject: [PATCH] feat: implemented credentialDeployment --- .github/workflows/publish.yaml | 4 +- package-lock.json | 4 +- package.json | 25 ++++---- src/Concordium.ts | 110 ++++++++++++++++++++++++++++++++- src/serialization.ts | 50 ++++++++++++++- 5 files changed, 174 insertions(+), 19 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 6d3d316..c20508a 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - name: Install node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16' registry-url: "https://registry.npmjs.org/" diff --git a/package-lock.json b/package-lock.json index a2755c7..7caa0c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@ledgerhq/hw-app-algorand", + "name": "@ledgerhq/hw-app-concordium", "version": "6.29.4", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@ledgerhq/hw-app-algorand", + "name": "@ledgerhq/hw-app-concordium", "version": "6.29.4", "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index dc74751..b01dace 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,34 @@ { - "name": "@ledgerhq/hw-app-algorand", + "name": "@ledgerhq/hw-app-concordium", "type": "module", "version": "6.29.4", - "description": "Ledger Hardware Wallet Algorand Application API", + "description": "Ledger Hardware Wallet Concordium Application API", "keywords": [ "Ledger", "LedgerWallet", - "algo", - "Algorand", - "NanoS", + "CCD", + "Concordium", + "NanoS+", + "NanoX", + "Flex", + "Stax", "Blue", "Hardware Wallet" ], "repository": { "type": "git", - "url": "https://github.com/LedgerHQ/ledger-live.git" + "url": "https://github.com/blooo-io/hw-app-concordium.git" }, "bugs": { - "url": "https://github.com/LedgerHQ/ledger-live/issues" + "url": "https://github.com/blooo-io/hw-app-concordium/issues" }, - "homepage": "https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledgerjs/packages/hw-app-algorand", + "homepage": "https://github.com/blooo-io/hw-app-concordium", "publishConfig": { "access": "public" }, - "main": "lib/Algorand.js", - "module": "lib-es/Algorand.js", - "types": "lib/Algorand.d.ts", + "main": "lib/Concordium.js", + "module": "lib-es/Concordium.js", + "types": "lib/Concordium.d.ts", "license": "Apache-2.0", "dependencies": { "@concordium/common-sdk": "^9.5.3", diff --git a/src/Concordium.ts b/src/Concordium.ts index e1401cb..7b1d1f7 100644 --- a/src/Concordium.ts +++ b/src/Concordium.ts @@ -14,10 +14,10 @@ import { serializeInitContract, serializeUpdateContract, serializeTransactionPayloads, - serializeUpdateCredentials + serializeUpdateCredentials, + serializeCredentialDeployment } from "./serialization"; -import BigNumber from "bignumber.js"; -import { encodeInt32 } from "./utils"; +import { encodeInt32, encodeInt8, encodeWord64 } from "./utils"; const LEDGER_CLA = 0xe0; @@ -80,6 +80,7 @@ const INS = { GET_PUBLIC_KEY: 0x01, SIGN_TRANSFER: 0x02, SIGN_TRANSFER_SCHEDULE: 0x03, + SIGN_CREDENTIAL_DEPLOYMENT: 0x04, SIGN_TRANSFER_TO_PUBLIC: 0x12, SIGN_CONFIGURE_DELEGATION: 0x17, SIGN_CONFIGURE_BAKER: 0x18, @@ -567,6 +568,109 @@ export default class Concordium { }; } + async signCredentialDeployment(txn, isNew: boolean, addressOrExpiry: string | BigInt, path: string): Promise<{ signature: string[] }> { + + const { payloadDerivationPath, numberOfVerificationKeys, keyIndexAndSchemeAndVerificationKey, thresholdAndRegIdAndIPIdentity, encIdCredPubShareAndKey, validToAndCreatedAtAndAttributesLength, tag, valueLength, value, proofLength, proofs } = serializeCredentialDeployment(txn, path); + + let response; + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_INITIAL_PACKET, + NONE, + payloadDerivationPath + ); + + + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_VERIFICATION_KEY_LENGTH, + NONE, + numberOfVerificationKeys + ); + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_VERIFICATION_KEY, + NONE, + keyIndexAndSchemeAndVerificationKey + ); + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_SIGNATURE_THRESHOLD, + NONE, + thresholdAndRegIdAndIPIdentity + ); + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_AR_IDENTITY, + NONE, + encIdCredPubShareAndKey + ); + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_CREDENTIAL_DATES, + NONE, + validToAndCreatedAtAndAttributesLength + ); + for (let i = 0; i < Object.keys(txn.policy.revealedAttributes).length; i++) { + const tagAndValueLength = Buffer.concat([tag[i], valueLength[i]]) + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_ATTRIBUTE_TAG, + NONE, + tagAndValueLength + ); + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_ATTRIBUTE_VALUE, + NONE, + value[i] + ); + } + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_LENGTH_OF_PROOFS, + NONE, + proofLength + ); + + const proofPayload = serializeTransactionPayloads(proofs); + for (let i = 0; i < proofPayload.length; i++) { + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_PROOFS, + NONE, + proofPayload[i] + ); + } + + if (isNew) { + const isNew = encodeInt8(0); + const serializeExpiry = encodeWord64(addressOrExpiry as BigInt); + const expiry = Buffer.concat([isNew, serializeExpiry]) + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_NEW_OR_EXISTING, + NONE, + expiry + ); + } else { + const isNew = encodeInt8(1); + const address = Buffer.concat([isNew, Buffer.from(addressOrExpiry as string, "hex")]) + response = await this.sendToDevice( + INS.SIGN_CREDENTIAL_DEPLOYMENT, + P1_NEW_OR_EXISTING, + NONE, + address + ); + } + + if (response.length === 1) throw new Error("User has declined."); + + return { + signature: response.toString("hex"), + }; + } + async signUpdateCredentials(txn, path: string): Promise<{ signature: string[] }> { const { payloadHeaderKindAndIndexLength, credentialIndex, numberOfVerificationKeys, keyIndexAndSchemeAndVerificationKey, thresholdAndRegIdAndIPIdentity, encIdCredPubShareAndKey, validToAndCreatedAtAndAttributesLength, tag, valueLength, value, proofLength, proofs, credentialIdCount, credentialIds, threshold } = serializeUpdateCredentials(txn, path); diff --git a/src/serialization.ts b/src/serialization.ts index 29386f6..1f087f1 100644 --- a/src/serialization.ts +++ b/src/serialization.ts @@ -3,6 +3,7 @@ import { encodeDataBlob, encodeInt8, encodeWord16, encodeWord64, serializeAccoun import { DataBlob } from "@concordium/common-sdk/lib/types/DataBlob"; import { Buffer as NodeBuffer } from 'buffer/index'; import { AccountAddress } from "@concordium/web-sdk"; +import { serializeCredentialDeploymentInfo } from "@concordium/common-sdk/lib/serialization"; const MAX_CHUNK_SIZE = 255; const MAX_SCHEDULE_CHUNK_SIZE = 15; @@ -344,6 +345,53 @@ export const serializeUpdateContract = (txn: any, path: string): { payloads: Buf return serializeTransaction(txn, path); }; +export const serializeCredentialDeployment = (txn: any, path: string): { payloadDerivationPath: Buffer, numberOfVerificationKeys: Buffer, keyIndexAndSchemeAndVerificationKey: Buffer, thresholdAndRegIdAndIPIdentity: Buffer, encIdCredPubShareAndKey: Buffer, validToAndCreatedAtAndAttributesLength: Buffer, attributesLength: Buffer, tag: Buffer[], valueLength: Buffer[], value: Buffer[], proofLength: Buffer, proofs: Buffer } => { + let offset = 0; + const txSerialized = serializeCredentialDeploymentInfo(txn); + const payloadDerivationPath = pathToBuffer(path); + let numberOfVerificationKeys: Buffer = Buffer.alloc(0); + let keyIndexAndSchemeAndVerificationKey: Buffer = Buffer.alloc(0); + let thresholdAndRegIdAndIPIdentity: Buffer = Buffer.alloc(0); + let encIdCredPubShareAndKey: Buffer = Buffer.alloc(0); + let validToAndCreatedAtAndAttributesLength: Buffer = Buffer.alloc(0); + let attributesLength: Buffer = Buffer.alloc(0); + let tag: Buffer[] = []; + let valueLength: Buffer[] = []; + let value: Buffer[] = []; + let proofLength: Buffer = Buffer.alloc(0); + let proofs: Buffer = Buffer.alloc(0); + + numberOfVerificationKeys = Buffer.from(txSerialized.subarray(offset, offset + INDEX_LENGTH)); + offset += INDEX_LENGTH; + keyIndexAndSchemeAndVerificationKey = Buffer.from(txSerialized.subarray(offset, offset + 2 * ONE_OCTET_LENGTH + KEY_LENGTH)); + offset += 2 * ONE_OCTET_LENGTH + KEY_LENGTH; + thresholdAndRegIdAndIPIdentity = Buffer.from(txSerialized.subarray(offset, offset + 2 * ONE_OCTET_LENGTH + REG_ID_LENGTH + IP_IDENTITY_LENGTH + AR_DATA_LENGTH)); + offset += 2 * ONE_OCTET_LENGTH + REG_ID_LENGTH + IP_IDENTITY_LENGTH + AR_DATA_LENGTH; + encIdCredPubShareAndKey = Buffer.from(txSerialized.subarray(offset, offset + 4 * ONE_OCTET_LENGTH + ID_CRED_PUB_SHARE_LENGTH)); + offset += 4 * ONE_OCTET_LENGTH + ID_CRED_PUB_SHARE_LENGTH; + validToAndCreatedAtAndAttributesLength = Buffer.from(txSerialized.subarray(offset, offset + ATTRIBUTES_LENGTH + VALID_TO_LENGTH + CREATED_AT_LENGTH)); + offset += ATTRIBUTES_LENGTH + VALID_TO_LENGTH + CREATED_AT_LENGTH; + attributesLength = validToAndCreatedAtAndAttributesLength.subarray(-ATTRIBUTES_LENGTH); + tag = []; + valueLength = []; + value = []; + for (let j = 0; j < attributesLength.readUInt16BE(0); j++) { + tag.push(Buffer.from(txSerialized.subarray(offset, offset + TAG_LENGTH))); + offset += TAG_LENGTH; + valueLength.push(Buffer.from(txSerialized.subarray(offset, offset + VALUE_LENGTH))); + offset += VALUE_LENGTH; + value.push(Buffer.from(txSerialized.subarray(offset, offset + valueLength[j].readUInt8(0)))); + offset += valueLength[j].readUInt8(0); + } + + proofLength = Buffer.from(txSerialized.subarray(offset, offset + PROOF_LENGTH_LENGTH)); + offset += PROOF_LENGTH_LENGTH; + proofs = Buffer.from(txSerialized.subarray(offset, offset + proofLength.readUInt32BE(0))); + offset += proofLength.readUInt32BE(0); + + return { payloadDerivationPath, numberOfVerificationKeys, keyIndexAndSchemeAndVerificationKey, thresholdAndRegIdAndIPIdentity, encIdCredPubShareAndKey, validToAndCreatedAtAndAttributesLength, attributesLength, tag, valueLength, value, proofLength, proofs }; +}; + export const serializeUpdateCredentials = (txn: any, path: string): { payloadHeaderKindAndIndexLength: Buffer[], credentialIndex: Buffer[], numberOfVerificationKeys: Buffer[], keyIndexAndSchemeAndVerificationKey: Buffer[], thresholdAndRegIdAndIPIdentity: Buffer[], encIdCredPubShareAndKey: Buffer[], validToAndCreatedAtAndAttributesLength: Buffer[], attributesLength: Buffer[], tag: Buffer[][], valueLength: Buffer[][], value: Buffer[][], proofLength: Buffer[], proofs: Buffer[], credentialIdCount: Buffer, credentialIds: Buffer[], threshold: Buffer } => { let offset = 0; const txSerialized = serializeAccountTransaction(txn); @@ -358,7 +406,7 @@ export const serializeUpdateCredentials = (txn: any, path: string): { payloadHea let encIdCredPubShareAndKey: Buffer[] = []; let validToAndCreatedAtAndAttributesLength: Buffer[] = []; let attributesLength: Buffer[] = []; - let tag: Buffer[][] = [[]]; + let tag: Buffer[][] = [[]]; let valueLength: Buffer[][] = [[]]; let value: Buffer[][] = [[]]; let proofLength: Buffer[] = [];