# Construction derive API locally in JS

**URL:** https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412
**Category:** JavaScript
**Created:** [January 23, 2025, 11:07am UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412 "2025-01-23T11:07:28Z")
**Posts on this page:** 8
**Page:** 1

<div class="post-metadata">

### Author: ![Kashif](https://avatars.discourse-cdn.com/v4/letter/k/ecc23a/32.png) [@Kashif](https://forum.dfinity.org/u/Kashif)
#### Post date: [January 23, 2025, 11:07am UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/1 "2025-01-23T11:07:28Z")

</div>

I am trying to have the functionality provided by the Derive API locally in javascript. I have written a script and its working but its not giving the correct account\_identifier. Below is the script, can someone help me here  
the correct principal is dgsc3-x3mgj-35squ-7s7wl-ww65m-o55e2-xikdq-47ri3-27upl-b52sv-kqe  
the correct account\_id is 5b8ba540ea14bcf4f71a03cecbca0f3c8f15959beaad48a704470f1bb1f79ae7

```auto
import elliptic from 'elliptic';
import crypto from 'crypto';
import crc32 from 'crc-32';
import { Principal } from '@dfinity/principal';

const EC = new elliptic.ec('secp256k1');

const PrincipalIdClass = {
    SelfAuthenticating: 0x01,
    Opaque: 0x02,
    Derived: 0x03,
    Anonymous: 0x04,
};

class PrincipalId {
    constructor(bytes) {
        this.bytes = bytes;
    }

    static newSelfAuthenticating(publicKey) {
        const sha224 = crypto.createHash('sha224');
        sha224.update(publicKey);
        const hash = sha224.digest();
        const id = Buffer.concat([hash, Buffer.from([PrincipalIdClass.SelfAuthenticating])]);
        return new PrincipalId(id);
    }
}

function principalIdFromPublicKey(pkHex) {
    const publicKeyBuffer = Buffer.from(pkHex, 'hex');
    
    try {
        const key = EC.keyFromPublic(publicKeyBuffer);
        const sec1PublicKey = key.getPublic(true, 'hex');
        const principalId = PrincipalId.newSelfAuthenticating(Buffer.from(sec1PublicKey, 'hex'));

        const principal = Principal.fromUint8Array(principalId.bytes);
        return { principalId, principal };
    } catch (err) {
        throw new Error(`Error processing public key: ${err.message}`);
    }
}

function generateAccountIdentifier(principal, subAccount = new Uint8Array(32)) {
    const ACCOUNT_DOMAIN_SEPARATOR = new Uint8Array([0x0A, ...Buffer.from('account-id')]);
    const principalBytes = principal.toUint8Array();
    const combinedBytes = new Uint8Array(
        ACCOUNT_DOMAIN_SEPARATOR.length + principalBytes.length + subAccount.length
    );
    combinedBytes.set(ACCOUNT_DOMAIN_SEPARATOR, 0);
    combinedBytes.set(principalBytes, ACCOUNT_DOMAIN_SEPARATOR.length);
    combinedBytes.set(subAccount, ACCOUNT_DOMAIN_SEPARATOR.length + principalBytes.length);
    const sha224Hash = crypto.createHash('sha224').update(combinedBytes).digest();
    const checksum = Buffer.alloc(4);
    checksum.writeUInt32BE(crc32.buf(sha224Hash) >>> 0, 0);
    const accountIdBytes = Buffer.concat([checksum, sha224Hash]);
    return accountIdBytes.toString('hex');
}

const pkHex = '047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367'; // Replace with actual hex public key
try {
    const { principalId, principal } = principalIdFromPublicKey(pkHex);
    console.log('Generated Principal ID:', principalId.bytes.toString('hex'));
    console.log('Generated Principal:', principal);
    
    const subAccount = new Uint8Array(32);
    const accountId = generateAccountIdentifier(principal, subAccount);
    console.log('Generated Account Identifier:', accountId);
} catch (error) {
    console.error('Error:', error.message);
}

```

---

<div class="post-metadata">

### Author: ![Vivienne](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/vivienne/32/29170_2.png) [@Vivienne](https://forum.dfinity.org/u/Vivienne)
#### Post date: [January 23, 2025, 12:04pm UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/2 "2025-01-23T12:04:36Z")

</div>

Any specific reason you’re not using `@dfinity/ledger-icp`?

---

<div class="post-metadata">

### Author: ![Kashif](https://avatars.discourse-cdn.com/v4/letter/k/ecc23a/32.png) [@Kashif](https://forum.dfinity.org/u/Kashif)
#### Post date: [January 23, 2025, 12:27pm UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/3 "2025-01-23T12:27:43Z")

</div>

@Vivienne there is no specific reason for not using `@dfinity/ledger-icp`, I just tried to convert the rosetta rust code to JS  
We want to use the rosetta construction API like combine and submit but not the derive API, therefore we are trying to replicate the derive API locally  
if you have any references/suggestions do let me know

---

<div class="post-metadata">

### Author: ![Vivienne](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/vivienne/32/29170_2.png) [@Vivienne](https://forum.dfinity.org/u/Vivienne)
#### Post date: [January 23, 2025, 1:01pm UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/4 "2025-01-23T13:01:05Z")

</div>

Then I would suggest you look at the `@dfinity/ledger-icp` implementation to see how it works there

---

<div class="post-metadata">

### Author: ![Kashif](https://avatars.discourse-cdn.com/v4/letter/k/ecc23a/32.png) [@Kashif](https://forum.dfinity.org/u/Kashif)
#### Post date: [January 23, 2025, 4:13pm UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/5 "2025-01-23T16:13:38Z")

</div>

@Vivienne I had a look at the `@dfinity/ledger-icp` and I only find a method for converting `principal` to `account_identifier` but not `public_key` to `account_identifier`

---

<div class="post-metadata">

### Author: ![Vivienne](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/vivienne/32/29170_2.png) [@Vivienne](https://forum.dfinity.org/u/Vivienne)
#### Post date: [January 24, 2025, 9:21am UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/6 "2025-01-24T09:21:54Z")

</div>

Ah sorry, I overlooked that you want to start with a public key, not a principal. If `@dfinity/principal` does not help, maybe the [relevant section in the spec](https://internetcomputer.org/docs/current/references/ic-interface-spec#principal) could help?

My first guess is that you messed up the class bytes. Per the spec SelfAuthenticating is `0x02`, not `0x01`

---

<div class="post-metadata">

### Author: ![Vivienne](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/vivienne/32/29170_2.png) [@Vivienne](https://forum.dfinity.org/u/Vivienne)
#### Post date: [January 24, 2025, 12:24pm UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/7 "2025-01-24T12:24:22Z")

</div>

Someone internal just tried go from a PEM file to a principal. Here’s some snippets from that conversation that could be useful:

> To my knowledge the public key has to be DER encoded before it is hashed (sha224). I suppose your openssl command probably doesn’t output DER-encoding.

> Try openssl with `-outform DER` option

> This is how I generated the files (I’m using this with the test identity created with dfx nns installbut it should work with any test identity)

```auto
$ dfx identity export nns-test > nns-test-private.pem
$ openssl ec -in nns-test-private.pem -pubout -out nns-test-public.pem

```

> In any case, you are right. This worked:

```auto
#!/bin/bash

function textual_encode() {
  ( echo -n "$1" | xxd -r -p | /usr/bin/crc32 /dev/stdin; echo -n "$1" ) |
  xxd -r -p | base32 | tr A-Z a-z |
  tr -d = | fold -w5 | paste -sd'-' -
}

UNCOMPRESSED_DER_SHA224=$(openssl ec -pubin -in nns-test-public.pem -outform DER | sha224)
PRINCIPAL_ID_HEX="${UNCOMPRESSED_DER_SHA224}02"

textual_encode ${PRINCIPAL_ID_HEX}

```

---

<div class="post-metadata">

### Author: ![Kashif](https://avatars.discourse-cdn.com/v4/letter/k/ecc23a/32.png) [@Kashif](https://forum.dfinity.org/u/Kashif)
#### Post date: [January 24, 2025, 3:42pm UTC](https://forum.dfinity.org/t/construction-derive-api-locally-in-js/40412/8 "2025-01-24T15:42:26Z")

</div>

@Vivienne after taking some reference from [agent-js/packages/agent/src/der.ts at ce618ebb43173ffdc3f9be66dbf806f40a9519be · dfinity/agent-js · GitHub](https://github.com/dfinity/agent-js/blob/ce618ebb43173ffdc3f9be66dbf806f40a9519be/packages/agent/src/der.ts)  
I was able to get the correct principal and account\_identifier from the below script

```import
import crypto from 'crypto';
import crc32 from 'crc-32';
import { Principal } from '@dfinity/principal';

const EC = new elliptic.ec('secp256k1');

const PrincipalIdClass = {
    Opaque: 0x01,
    SelfAuthenticating: 0x02,
    Derived: 0x03,
    Anonymous: 0x04,
};

class PrincipalId {
    constructor(bytes) {
        this.bytes = bytes;
    }

    static newSelfAuthenticating(publicKey) {
        const sha224 = crypto.createHash('sha224');
        sha224.update(publicKey);
        const hash = sha224.digest();
        const id = Buffer.concat([hash, Buffer.from([PrincipalIdClass.SelfAuthenticating])]);
        return new PrincipalId(id);
    }
}

function principalIdFromPublicKey(pkHex) {
    const publicKeyBuffer = Buffer.from(pkHex, 'hex');

    try {
        const key = EC.keyFromPublic(publicKeyBuffer);
        const sec1PublicKey = key.getPublic(false, 'hex');
        console.log('sec1PublicKey:', sec1PublicKey);
        const pk_der = wrapDER(Buffer.from(sec1PublicKey, 'hex'), SECP256K1_OID);
        console.log('der:', pk_der.toString('hex'));
        const principalId = PrincipalId.newSelfAuthenticating(Buffer.from(pk_der, 'hex'));

        const principal = Principal.fromUint8Array(principalId.bytes);
        return { principalId, principal };
    } catch (err) {
        throw new Error(`Error processing public key: ${err.message}`);
    }
}

const SECP256K1_OID = Uint8Array.from([
    ...[0x30, 0x10],
    ...[0x06, 0x07],
    ...[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01],
    ...[0x06, 0x05],
    ...[0x2b, 0x81, 0x04, 0x00, 0x0a],
]);

const encodeLenBytes = (len) => {
    if (len <= 0x7f) {
        return 1;
    } else if (len <= 0xff) {
        return 2;
    } else if (len <= 0xffff) {
        return 3;
    } else if (len <= 0xffffff) {
        return 4;
    } else {
        throw new Error('Length too long (> 4 bytes)');
    }
};

const encodeLen = (buf, offset, len) => {
    if (len <= 0x7f) {
        buf[offset] = len;
        return 1;
    } else if (len <= 0xff) {
        buf[offset] = 0x81;
        buf[offset + 1] = len;
        return 2;
    } else if (len <= 0xffff) {
        buf[offset] = 0x82;
        buf[offset + 1] = len >> 8;
        buf[offset + 2] = len;
        return 3;
    } else if (len <= 0xffffff) {
        buf[offset] = 0x83;
        buf[offset + 1] = len >> 16;
        buf[offset + 2] = len >> 8;
        buf[offset + 3] = len;
        return 4;
    } else {
        throw new Error('Length too long (> 4 bytes)');
    }
};

function wrapDER(payload, oid) {
    const bitStringHeaderLength = 2 + encodeLenBytes(payload.byteLength + 1);
    const len = oid.byteLength + bitStringHeaderLength + payload.byteLength;
    let offset = 0;
    const buf = new Uint8Array(1 + encodeLenBytes(len) + len);
    buf[offset++] = 0x30;
    offset += encodeLen(buf, offset, len);
    buf.set(oid, offset);
    offset += oid.byteLength;
    buf[offset++] = 0x03;
    offset += encodeLen(buf, offset, payload.byteLength + 1);
    buf[offset++] = 0x00;
    buf.set(new Uint8Array(payload), offset);
    return buf;
}

function generateAccountIdentifier(principal, subAccount = new Uint8Array(32)) {
    const ACCOUNT_DOMAIN_SEPARATOR = new Uint8Array([0x0A, ...Buffer.from('account-id')]);
    const principalBytes = principal.toUint8Array();
    const combinedBytes = new Uint8Array(
        ACCOUNT_DOMAIN_SEPARATOR.length + principalBytes.length + subAccount.length
    );
    combinedBytes.set(ACCOUNT_DOMAIN_SEPARATOR, 0);
    combinedBytes.set(principalBytes, ACCOUNT_DOMAIN_SEPARATOR.length);
    combinedBytes.set(subAccount, ACCOUNT_DOMAIN_SEPARATOR.length + principalBytes.length);
    const sha224Hash = crypto.createHash('sha224').update(combinedBytes).digest();
    const checksum = Buffer.alloc(4);
    checksum.writeUInt32BE(crc32.buf(sha224Hash) >>> 0, 0);
    const accountIdBytes = Buffer.concat([checksum, sha224Hash]);
    return accountIdBytes.toString('hex');
}

const pkHex = '0431eea4ca769823ce19b0386177a856322de426c603f0e7f3c552e1a65903d0b5dcd1f39704c0e56afd194525b04a79e27f72cdde8a9ce410685ca4e934dd5bcd';
try {
    const { principalId, principal } = principalIdFromPublicKey(pkHex);
    console.log('Generated Principal ID:', principalId.bytes.toString('hex'));
    console.log('Generated Principal:', principal.toString());

    const subAccount = new Uint8Array(32);
    const accountId = generateAccountIdentifier(principal, subAccount);
    console.log('Generated Account Identifier:', accountId);
} catch (error) {
    console.error('Error:', error.message);
}

```
