Skip to content

Commit e77440c

Browse files
committed
feat: add COSE function for 'ic_message' app
1 parent 96b35bf commit e77440c

File tree

5 files changed

+144
-15
lines changed

5 files changed

+144
-15
lines changed

pnpm-lock.yaml

Lines changed: 23 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ic_panda_frontend/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
"@dfinity/candid": "^2.0.0",
66
"@dfinity/principal": "^2.0.0",
77
"@dfinity/utils": "^2.4.0",
8-
"@ldclabs/cose-ts": "^1.2.0",
8+
"@ldclabs/cose-ts": "^1.3.1",
99
"@noble/hashes": "^1.4.0",
1010
"@paulmillr/qr": "^0.2.0",
1111
"@scure/base": "^1.1.7",
12-
"cborg": "^4.2.3"
12+
"cborg": "^4.2.3",
13+
"idb": "^7.1.1"
1314
},
1415
"devDependencies": {
1516
"@floating-ui/dom": "^1.6.10",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { KVStore } from '$lib/utils/store'
2+
3+
export const userStore = new KVStore('ICPanda_Users')
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { AesGcmKey } from '@ldclabs/cose-ts/aesgcm'
2+
import { Encrypt0Message } from '@ldclabs/cose-ts/encrypt0'
3+
import { Header } from '@ldclabs/cose-ts/header'
4+
import { hkdf256 } from '@ldclabs/cose-ts/hkdf'
5+
import * as iana from '@ldclabs/cose-ts/iana'
6+
import { KDFContext, PartyInfo, SuppPubInfo } from '@ldclabs/cose-ts/kdfcontext'
7+
import { argon2id } from '@noble/hashes/argon2'
8+
9+
export {
10+
assertEqual,
11+
base64ToBytes,
12+
bytesToBase64Url,
13+
bytesToHex,
14+
compareBytes,
15+
concatBytes,
16+
decodeCBOR,
17+
encodeCBOR,
18+
hexToBytes,
19+
randomBytes,
20+
toBytes,
21+
utf8ToBytes
22+
} from '@ldclabs/cose-ts/utils'
23+
24+
export { AesGcmKey } from '@ldclabs/cose-ts/aesgcm'
25+
export { ECDHKey } from '@ldclabs/cose-ts/ecdh'
26+
27+
export function hashPassword(password: string, salt: string): Uint8Array {
28+
// default params from https://docs.rs/argon2/latest/argon2/struct.Params.html
29+
return argon2id(password, salt, { t: 2, m: 19456, p: 1 })
30+
}
31+
32+
// HKDF-SHA-256 with Context Information Structure
33+
// https://datatracker.ietf.org/doc/html/rfc9053#name-context-information-structu
34+
export function deriveA256GCMSecret(
35+
secret: Uint8Array,
36+
salt: Uint8Array
37+
): Uint8Array {
38+
const ctx = new KDFContext(
39+
iana.AlgorithmA256GCM,
40+
new PartyInfo(),
41+
new PartyInfo(),
42+
new SuppPubInfo(
43+
256,
44+
new Header(
45+
new Map([[iana.HeaderParameterAlg, iana.AlgorithmDirect_HKDF_SHA_256]])
46+
)
47+
)
48+
)
49+
50+
return hkdf256(secret, salt, ctx.toBytes(), 32)
51+
}
52+
53+
export async function coseA256GCMEncrypt0(
54+
key: AesGcmKey,
55+
payload: Uint8Array,
56+
aad: Uint8Array,
57+
nonce: Uint8Array, // 12 bytes
58+
key_id?: Uint8Array
59+
): Promise<Uint8Array> {
60+
const protect = new Header().setParam(
61+
iana.HeaderParameterAlg,
62+
iana.AlgorithmA256GCM
63+
)
64+
const unprotected = new Header().setParam(iana.HeaderParameterIV, nonce)
65+
if (key_id) {
66+
unprotected.setParam(iana.HeaderParameterKid, key_id)
67+
}
68+
const msg = new Encrypt0Message(payload, protect, unprotected)
69+
return await msg.toBytes(key, aad)
70+
}
71+
72+
export async function coseA256GCMDecrypt0(
73+
key: AesGcmKey,
74+
data: Uint8Array,
75+
aad: Uint8Array
76+
): Promise<Uint8Array> {
77+
const msg = await Encrypt0Message.fromBytes(key, data, aad)
78+
return msg.payload
79+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { openDB, type IDBPDatabase } from 'idb'
2+
3+
export type IDBValidKey = string | number | Date | BufferSource | IDBValidKey[]
4+
5+
export class KVStore {
6+
private static storeName = 'KV'
7+
private db: Promise<IDBPDatabase>
8+
9+
constructor(dbName: string) {
10+
if (!dbName.trim()) {
11+
throw new Error('dbName is required')
12+
}
13+
this.db = openDB(dbName, 1, {
14+
upgrade(db) {
15+
db.createObjectStore(KVStore.storeName)
16+
}
17+
})
18+
}
19+
20+
async getItem<T>(key: IDBValidKey): Promise<T | null> {
21+
const db = await this.db
22+
return (await db.get(KVStore.storeName, key)) || null
23+
}
24+
25+
async setItem<T>(key: IDBValidKey, value: T): Promise<void> {
26+
const db = await this.db
27+
const tx = db.transaction(KVStore.storeName, 'readwrite')
28+
await Promise.all([tx.store.put(value, key), tx.done])
29+
}
30+
31+
async removeItem(key: IDBValidKey): Promise<void> {
32+
const db = await this.db
33+
const tx = db.transaction(KVStore.storeName, 'readwrite')
34+
await Promise.all([tx.store.delete(key), tx.done])
35+
}
36+
}

0 commit comments

Comments
 (0)