Skip to content

Commit

Permalink
test: make e2e tests work
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Burtey committed Sep 17, 2023
1 parent 1eb6936 commit 76e37a8
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 69 deletions.
1 change: 1 addition & 0 deletions apps/boltcard/TODO.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- rate limits
- passing IP to backend
- proper link of .gql (instead of copy)
- Tilt setup
4 changes: 2 additions & 2 deletions apps/boltcard/app/api/ln/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
markCardInitAsUsed,
} from "../../knex"

import { decryptedPToUidCtr } from "../../decoder"
import { decodePToUidCtr } from "../../crypto/decoder"

function generateSecureRandomString(length: number): string {
return randomBytes(Math.ceil(length / 2))
Expand Down Expand Up @@ -88,7 +88,7 @@ export async function GET(req: NextRequest) {
}

// TODO error management
const { uidRaw, uid, ctrRawInverseBytes, ctr } = decryptedPToUidCtr(decryptedP)
const { uidRaw, uid, ctrRawInverseBytes, ctr } = decodePToUidCtr(decryptedP)

console.log({
uid,
Expand Down
39 changes: 17 additions & 22 deletions apps/boltcard/app/crypto/aes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// import { AesCmac } from "aes-cmac"
import { createSV2 } from "./decoder"

const aesCmac = require("node-aes-cmac").aesCmac

const aesjs = require("aes-js")
Expand All @@ -23,35 +24,29 @@ export function aesDecrypt(key: Buffer, data: Buffer): Buffer | DecryptionError
}
}

// used for simulating the coldcard
export function aesEncrypt(key: Buffer, data: Buffer): Buffer | DecryptionError {
try {
const aesCtr = new aesjs.ModeOfOperation.cbc(key)
const decryptedBytes = aesCtr.encrypt(data)
return decryptedBytes
} catch (err) {
console.log(err)
if (err instanceof Error) return new DecryptionError(err)
return new UnknownError()
}
}

export async function checkSignature(
uid: Uint8Array,
ctr: Uint8Array,
k2CmacKey: Buffer,
cmac: Buffer,
): Promise<boolean> {
const sv2 = Buffer.from([
0x3c,
0xc3,
0x00,
0x01,
0x00,
0x80,
uid[0],
uid[1],
uid[2],
uid[3],
uid[4],
uid[5],
uid[6],
ctr[0],
ctr[1],
ctr[2],
])

let calculatedCmac

try {
calculatedCmac = getSunMAC(k2CmacKey, sv2)
calculatedCmac = getSunMAC(k2CmacKey, createSV2(uid, ctr))
} catch (error) {
console.error(error)
throw new Error("issue with cMac")
Expand All @@ -61,7 +56,7 @@ export async function checkSignature(
return Buffer.compare(calculatedCmac, cmac) === 0
}

function getSunMAC(key: Buffer, sv2: Buffer): Buffer {
export function getSunMAC(key: Buffer, sv2: Buffer): Buffer {
const options = { returnAsBuffer: true }
const cmac1 = aesCmac(key, sv2, options)
const cmac2 = aesCmac(cmac1, new Buffer(""), options)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { aesDecrypt, checkSignature } from "../boltcard-galoy/app/crypto/aes"
import { decryptedPToUidCtr } from "./decoder"
import { aesDecrypt, aesEncrypt, checkSignature, getSunMAC } from "./aes"

import { createSV2, decodePToUidCtr, encodeUidCtrToP } from "./decoder"

const aesjs = require("aes-js")

Expand All @@ -12,6 +13,7 @@ const values = [
decrypted_uid: "04996c6a926980",
decrypted_ctr: "030000",
decoded_ctr: 3,
sv2: new Buffer([60, 195, 0, 1, 0, 128, 4, 153, 108, 106, 146, 105, 128, 3, 0, 0]),
},
{
p: aesjs.utils.hex.toBytes("00F48C4F8E386DED06BCDC78FA92E2FE"),
Expand All @@ -33,7 +35,7 @@ const values = [
},
]

describe("crypto", () => {
describe("decoding", () => {
values.forEach(
({
p,
Expand All @@ -50,7 +52,7 @@ describe("crypto", () => {
throw decryptedP
}

const { uid, uidRaw, ctr, ctrRawInverseBytes } = decryptedPToUidCtr(decryptedP)
const { uid, uidRaw, ctr, ctrRawInverseBytes } = decodePToUidCtr(decryptedP)

expect(uid).toEqual(decrypted_uid)
expect(ctr).toEqual(decoded_ctr)
Expand All @@ -69,3 +71,47 @@ describe("crypto", () => {
},
)
})

function uint8ArrayToHex(uint8Array: Uint8Array) {
return Array.from(uint8Array)
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("")
}

describe("encoding", () => {
it("should encrypt uid and ctr", () => {
const value = values[0]

const uid = Buffer.from(value.decrypted_uid, "hex")
const ctr = Buffer.from(value.decrypted_ctr, "hex")

// create P
const p1 = encodeUidCtrToP(uid, ctr)
const p2 = Buffer.from(p1)

// encrypt P
const encryptP = aesEncrypt(value.aes_decrypt_key, p2)
if (encryptP instanceof Error) {
throw encryptP
}

expect(uint8ArrayToHex(encryptP)).toEqual(uint8ArrayToHex(value.p))
})

it("should sign P", () => {
const value = values[0]
const p = value.p
p

const uid = Buffer.from(value.decrypted_uid, "hex")
const ctr = Buffer.from(value.decrypted_ctr, "hex")

// create cv2
const cv2 = createSV2(uid, ctr)
expect(cv2).toEqual(value.sv2)

// create cmac
const cmac = getSunMAC(value.aes_cmac_key, cv2)
expect(cmac).toEqual(value.c)
})
})
73 changes: 73 additions & 0 deletions apps/boltcard/app/crypto/decoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const aesjs = require("aes-js")

// used for encryption
export const decodePToUidCtr = (
decryptedP: Uint8Array,
): { uid: string; uidRaw: Uint8Array; ctr: number; ctrRawInverseBytes: Uint8Array } => {
if (decryptedP[0] !== 0xc7) {
throw new Error("data not starting with 0xC7")
}

const uidRaw = decryptedP.slice(1, 8)
const uid = aesjs.utils.hex.fromBytes(uidRaw)

const ctrRawInverseBytes = decryptedP.slice(8, 11)
const ctr =
(ctrRawInverseBytes[2] << 16) | (ctrRawInverseBytes[1] << 8) | ctrRawInverseBytes[0]

return {
uid,
uidRaw,
ctr,
ctrRawInverseBytes,
}
}

// only used to simulate the cold card
export const encodeUidCtrToP = (uid: Buffer, ctr: Buffer): Uint8Array => {
return new Uint8Array([
0xc7,
uid[0],
uid[1],
uid[2],
uid[3],
uid[4],
uid[5],
uid[6],
ctr[0],
ctr[1],
ctr[2],

// those value can be random, but they are set as is so that
// tests pass for the simulation of the encryption fo the coldcard
2,
63,
181,
243,
74,
])
}

// used for signature
export const createSV2 = (uid: Uint8Array, ctr: Uint8Array) => {
const sv2 = Buffer.from([
0x3c,
0xc3,
0x00,
0x01,
0x00,
0x80,
uid[0],
uid[1],
uid[2],
uid[3],
uid[4],
uid[5],
uid[6],
ctr[0],
ctr[1],
ctr[2],
])

return sv2
}
23 changes: 0 additions & 23 deletions apps/boltcard/app/decoder.ts

This file was deleted.

26 changes: 18 additions & 8 deletions apps/boltcard/bats/e2e-test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,43 @@ load "../../../test/bats/helpers/ln"
RESPONSE=$(curl -s "${CALLBACK_URL}")
echo "$RESPONSE"
[[ $(echo $RESPONSE | jq -r '.protocol_name') == "create_bolt_card_response" ]] || exit 1

K1_VALUE=$(echo $RESPONSE | jq -r '.k1')
K2_VALUE=$(echo $RESPONSE | jq -r '.k2')

cache_value "k1" "$K1_VALUE"
cache_value "k2" "$K2_VALUE"
}

@test "auth: create payment and follow up" {
P_VALUE="4E2E289D945A66BB13377A728884E867"
C_VALUE="E19CCB1FED8892CE"
K1=$(read_value "k1")
K2=$(read_value "k2")

RESPONSE=$(bun run debug/getpandc.ts $K1 $K2)

P_VALUE=$(echo $RESPONSE | jq -r '.p')
C_VALUE=$(echo $RESPONSE | jq -r '.c')

RESPONSE=$(curl -s "http://localhost:3000/api/ln?p=${P_VALUE}&c=${C_VALUE}")
echo "$RESPONSE"

CALLBACK_URL=$(echo $RESPONSE | jq -r '.callback')
K1_VALUE=$(echo $RESPONSE | jq -r '.k1')

echo "K1_VALUE: $K1_VALUE"
cache_value "k1" "$K1_VALUE"
K1_CALLBACK=$(echo $RESPONSE | jq -r '.k1')

exit 1
echo "K1_CALLBACK: $K1_CALLBACK"
cache_value "k1_callback" "$K1_CALLBACK"
}

@test "callback" {
K1_VALUE=$(read_value "k1")
K1_VALUE=$(read_value "k1_callback")
CALLBACK_URL=http://localhost:3000/api/callback

invoice_response="$(lnd_outside_2_cli addinvoice --amt 1000)"
payment_request=$(echo $invoice_response | jq -r '.payment_request')
echo $payment_request

result=$(curl -s "${CALLBACK_URL}?k1=${K1_VALUE}&pr=${payment_request}")
echo "$result"
[[ result.status == "OK" ]] || exit 1
}

Expand Down
34 changes: 34 additions & 0 deletions apps/boltcard/debug/getpandc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { aesEncrypt, getSunMAC } from "@/app/crypto/aes"
import { createSV2, encodeUidCtrToP } from "@/app/crypto/decoder"

const aesjs = require("aes-js")

const uidInit = "04996c6a926980"
const ctrInit = "030000"

export const main = (k1: string, k2: string) => {
// "0c3b25d92b38ae443229dd59ad34b85d"
// k2: b45775776cb224c75bcde7ca3704e933

const uid = Buffer.from(uidInit, "hex")
const ctr = Buffer.from(ctrInit, "hex")

const p1 = encodeUidCtrToP(uid, ctr)
const p2 = Buffer.from(p1)

// encrypt P
const encryptP = aesEncrypt(Buffer.from(k1, "hex"), p2)
if (encryptP instanceof Error) {
throw encryptP
}

const cv2 = createSV2(uid, ctr)
const cmac = getSunMAC(Buffer.from(k2, "hex"), cv2)

return JSON.stringify({
c: cmac.toString("hex"),
p: aesjs.utils.hex.fromBytes(encryptP),
})
}

console.log(main(process.argv[2], process.argv[3]))
10 changes: 0 additions & 10 deletions apps/boltcard/knexfile.ts

This file was deleted.

0 comments on commit 76e37a8

Please sign in to comment.