Repository: decentralized-identity/did-jwt (ts)

Issue:
[BUG] "Unexpected end of data" during bigintToBytes()
### Current Behavior

biginToBytes() converts a bigInt to a hex string, which sometimes misses left padding, which causes a conversion error

### Expected Behavior

biginToBytes() converts a bigInt to a hex string

### Failure Information

Solution:
```ts
export function bigintToBytes(n: bigint): Uint8Array {
    let b64 = n.toString(16);

    // Pad an extra '0' if the hex string is an odd length
    if (b64.length % 2 !== 0) {
        b64 = `0${b64}`;
    }

    return u8a.fromString(b64, 'base16');
}
```

Issue we found: https://github.com/Tonomy-Foundation/Tonomy-App-Websites/issues/76
Solution we made: https://github.com/Tonomy-Foundation/Tonomy-ID-SDK/pull/228/files#diff-4335ca2354d2963bc461d6d160ca76c2ac0bd3524a6a8eaba6c627407ca1cf41R26

#### Steps to Reproduce

```ts
const bi = 1881146970754576322752261068397796891246589699629597037555588131642783231506n
const biBytes = bigintToBytes(bi);
const base64 = bytesToBase64(biBytes);
expect(base64).toBe('BCixAySH6XqSNMR6MVnd4SCluKq3Ey5RQIy0/0Eu7hI=');
// fails with error "Unexpected end of data"

const bi = 100968908336250941489582664670319762383316987426946165788206218268821633081179n
const biBytes = bigintToBytes(bi);
const base64 = bytesToBase64(biBytes);
expect(base64).toBe('3zpgfkpIN/0k/xkychS26ElYP4Bnb24RcYACzsbzn1s=');
// succeeds
```

#### Environment Details

* node version: v18.12.1
* OS Version: ubuntu 20.04

Pull request:
fix: add padding to bigints whose byte-length is expected
fixes #283

Patch:
diff --git a/src/util.ts b/src/util.ts
index af2078e..93d29c0 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -56,8 +56,18 @@ export function bytesToMultibase(b: Uint8Array, base: keyof typeof bases): strin
   return bases[base].encode(b)
 }
 
-export function hexToBytes(s: string): Uint8Array {
-  const input = s.startsWith('0x') ? s.substring(2) : s
+export function hexToBytes(s: string, minLength?: number): Uint8Array {
+  let input = s.startsWith('0x') ? s.substring(2) : s
+
+  if (input.length % 2 !== 0) {
+    input = `0${input}`
+  }
+
+  if (minLength) {
+    const paddedLength = Math.max(input.length, minLength * 2)
+    input = input.padStart(paddedLength, '00')
+  }
+
   return u8a.fromString(input.toLowerCase(), 'base16')
 }
 
@@ -77,8 +87,8 @@ export function bytesToBigInt(b: Uint8Array): bigint {
   return BigInt(`0x` + u8a.toString(b, 'base16'))
 }
 
-export function bigintToBytes(n: bigint): Uint8Array {
-  return u8a.fromString(n.toString(16), 'base16')
+export function bigintToBytes(n: bigint, minLength?: number): Uint8Array {
+  return hexToBytes(n.toString(16), minLength)
 }
 
 export function stringToBytes(s: string): Uint8Array {


Test patch:
diff --git a/src/__tests__/VerifierAlgorithm.test.ts b/src/__tests__/VerifierAlgorithm.test.ts
index 014e5ba..b50b1ae 100644
--- a/src/__tests__/VerifierAlgorithm.test.ts
+++ b/src/__tests__/VerifierAlgorithm.test.ts
@@ -211,8 +211,8 @@ const publicKeyBase58 = bytesToBase58(publicKeyBytes)
 const publicKeyJwk = {
   crv: 'secp256k1',
   kty: 'EC',
-  x: bytesToBase64url(bigintToBytes(publicKeyPoint.x)),
-  y: bytesToBase64url(bigintToBytes(publicKeyPoint.y)),
+  x: bytesToBase64url(bigintToBytes(publicKeyPoint.x, 32)),
+  y: bytesToBase64url(bigintToBytes(publicKeyPoint.y, 32)),
 }
 const publicKeyMultibase = bytesToMultibase(publicKeyBytes, 'base58btc')
 const eip155 = toEthereumAddress(publicKeyHex)
diff --git a/src/__tests__/util.test.ts b/src/__tests__/util.test.ts
new file mode 100644
index 0000000..1183564
--- /dev/null
+++ b/src/__tests__/util.test.ts
@@ -0,0 +1,65 @@
+import { bigintToBytes, hexToBytes } from '../util'
+
+describe('bigintToBytes', () => {
+  it('should convert a bigint to bytes', () => {
+    const bn = BigInt(65535)
+    const bytes = bigintToBytes(bn)
+    expect(bytes).toEqual(new Uint8Array([255, 255]))
+  })
+
+  it('should convert a bigint to bytes given a minimum length', () => {
+    const bn = BigInt(65535)
+    const bytes = bigintToBytes(bn, 32)
+    expect(bytes).toEqual(
+      new Uint8Array([
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255,
+      ])
+    )
+  })
+
+  it('should convert a bigint to bytes given a minimum length less than the number', () => {
+    const bn = BigInt('0x112233445566778899')
+    const bytes = bigintToBytes(bn, 5)
+    expect(bytes).toEqual(new Uint8Array([17, 34, 51, 68, 85, 102, 119, 136, 153]))
+  })
+
+  it('should convert a bigint to bytes given an odd number of bytes', () => {
+    const bn = BigInt('0x101010101010101')
+    const bytes = bigintToBytes(bn)
+    expect(bytes).toEqual(new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1]))
+  })
+})
+
+describe('hexToBytes', () => {
+  it('should convert a hex string to bytes', () => {
+    const bn = '0101'
+    const bytes = hexToBytes(bn)
+    expect(bytes).toEqual(new Uint8Array([1, 1]))
+  })
+
+  it('should convert a hex string with a prefix to bytes', () => {
+    const bn = '0x0101'
+    const bytes = hexToBytes(bn)
+    expect(bytes).toEqual(new Uint8Array([1, 1]))
+  })
+
+  it('should convert a hex string to bytes given a minimum length', () => {
+    const bn = '0101'
+    const bytes = hexToBytes(bn, 32)
+    expect(bytes).toEqual(
+      new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1])
+    )
+  })
+
+  it('should convert a hex string to bytes given a minimum length less than the number', () => {
+    const bn = '0x112233445566778899'
+    const bytes = hexToBytes(bn, 5)
+    expect(bytes).toEqual(new Uint8Array([17, 34, 51, 68, 85, 102, 119, 136, 153]))
+  })
+
+  it('should convert a hexString to bytes given an odd number of bytes', () => {
+    const bn = '0x101010101010101'
+    const bytes = hexToBytes(bn)
+    expect(bytes).toEqual(new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1]))
+  })
+})