Skip to content

Commit a010911

Browse files
committed
fix: proof verification when offset != 0
1 parent da8fda0 commit a010911

File tree

9 files changed

+189
-69
lines changed

9 files changed

+189
-69
lines changed

js/package-lock.json

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

js/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
}
4747
},
4848
"dependencies": {
49+
"@stablelib/chacha20poly1305": "^1.0.0",
4950
"js-base64": "^3.7.7"
5051
},
5152
"devDependencies": {

js/src/config.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import { ChaCha20Poly1305 } from '@stablelib/chacha20poly1305'
2+
import { subtle } from 'crypto'
3+
import { AlgorithmConfig, EncryptionAlgorithm } from './types'
14
import { bitsToUint8Array, bitsToUintArray, toUint8Array, toUintArray, uint8ArrayToBits, uintArrayToBits } from './utils'
25

36
// commit hash for this repo
47
export const GIT_COMMIT_HASH = 'd82fab41fa4033aa13feda3374f80a9df7af52b2'
58

6-
export const CONFIG = {
9+
export const CONFIG: { [E in EncryptionAlgorithm]: AlgorithmConfig } = {
710
'chacha20': {
11+
index: 0,
812
chunkSize: 16,
913
bitsPerWord: 32,
1014
keySizeBytes: 32,
@@ -15,14 +19,20 @@ export const CONFIG = {
1519
// chacha20 circuit uses LE encoding
1620
isLittleEndian: true,
1721
uint8ArrayToBits: (arr: Uint8Array) => (
18-
uintArrayToBits(toUintArray(arr))
22+
uintArrayToBits(toUintArray(arr)).flat()
1923
),
2024
bitsToUint8Array: (bits: number[]) => {
2125
const arr = bitsToUintArray(bits)
2226
return toUint8Array(arr)
2327
},
28+
encrypt({ key, iv, in: data }) {
29+
const cipher = new ChaCha20Poly1305(key)
30+
const ciphertext = cipher.seal(iv, data)
31+
return ciphertext.slice(0, data.length)
32+
},
2433
},
2534
'aes-256-ctr': {
35+
index: 1,
2636
chunkSize: 64,
2737
bitsPerWord: 8,
2838
keySizeBytes: 32,
@@ -34,8 +44,10 @@ export const CONFIG = {
3444
isLittleEndian: false,
3545
uint8ArrayToBits,
3646
bitsToUint8Array,
47+
encrypt: makeAesCtr(256),
3748
},
3849
'aes-128-ctr': {
50+
index: 2,
3951
chunkSize: 64,
4052
bitsPerWord: 8,
4153
keySizeBytes: 16,
@@ -47,5 +59,23 @@ export const CONFIG = {
4759
isLittleEndian: false,
4860
uint8ArrayToBits,
4961
bitsToUint8Array,
62+
encrypt: makeAesCtr(128),
63+
}
64+
}
65+
66+
function makeAesCtr(keyLenBits: number): AlgorithmConfig['encrypt'] {
67+
return async({ key, iv, in: inp }) => {
68+
const keyImp = await subtle.importKey(
69+
'raw', key,
70+
{ name: 'AES-GCM', length: keyLenBits },
71+
false,
72+
['encrypt']
73+
)
74+
const buff = await subtle.encrypt(
75+
{ name: 'AES-GCM', iv },
76+
keyImp,
77+
inp
78+
)
79+
return new Uint8Array(buff).slice(0, inp.length)
5080
}
5181
}

js/src/gnark/operator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ export function makeGnarkZkOperator({
6565
return lib
6666
}
6767

68-
const { id, ext } = ALGS_MAP[algorithm]
68+
const { ext } = ALGS_MAP[algorithm]
69+
const { index: id } = CONFIG[algorithm]
6970
const [pk, r1cs] = await Promise.all([
7071
fetcher.fetch('gnark', `pk.${ext}`, logger),
7172
fetcher.fetch('gnark', `r1cs.${ext}`, logger),

js/src/gnark/utils.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@ export type GnarkLib = {
1313
}
1414

1515
export const ALGS_MAP: {
16-
[key in EncryptionAlgorithm]: {
17-
id: number
18-
ext: string
19-
}
16+
[key in EncryptionAlgorithm]: { ext: string }
2017
} = {
21-
'chacha20': { id: 0, ext: 'chacha20' },
22-
'aes-128-ctr': { id: 1, ext: 'aes128' },
23-
'aes-256-ctr': { id: 2, ext: 'aes256' },
18+
'chacha20': { ext: 'chacha20' },
19+
'aes-128-ctr': { ext: 'aes128' },
20+
'aes-256-ctr': { ext: 'aes256' },
2421
}
2522

2623
// golang uses different arch names
@@ -99,8 +96,7 @@ export function strToUint8Array(str: string) {
9996
return new TextEncoder().encode(str)
10097
}
10198

102-
export
103-
function generateGnarkWitness(cipher: EncryptionAlgorithm, input) {
99+
export function generateGnarkWitness(cipher: EncryptionAlgorithm, input) {
104100
const {
105101
bitsToUint8Array,
106102
isLittleEndian

js/src/snarkjs/operator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export function makeSnarkJsZKOperator({
4242
let wc: Promise<unknown> | undefined
4343

4444
return {
45-
async generateWitness(input, logger) {
45+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
46+
async generateWitness({ out, ...input }, logger) {
4647
circuitWasm ||= getCircuitWasm()
4748
wc ||= (async() => {
4849
if(!snarkjs.wtns.getWtnsCalculator) {

js/src/tests/lib.test.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,12 @@ describe.each(ALL_ALGOS)('%s Lib Tests', (algorithm) => {
8383
)
8484
const publicInput = { ciphertext, iv: iv, offset: 0 }
8585

86-
8786
const proof = await generateProof({
8887
algorithm,
8988
privateInput,
9089
publicInput,
9190
operator
9291
})
93-
// ensure the ZK decrypted data matches the plaintext
94-
expect(
95-
proof.plaintext
96-
.slice(0, plaintext.length)
97-
).toEqual(
98-
plaintext
99-
)
10092
// client will send proof to witness
10193
// witness would verify proof
10294
await verifyProof({ proof, publicInput, operator })
@@ -106,8 +98,6 @@ describe.each(ALL_ALGOS)('%s Lib Tests', (algorithm) => {
10698
const totalPlaintext = new Uint8Array(randomBytes(chunkSizeBytes * 5))
10799
// use a chunk in the middle
108100
const offset = 2
109-
const plaintext = totalPlaintext
110-
.subarray(chunkSizeBytes * offset, chunkSizeBytes * (offset + 1))
111101

112102
const iv = Buffer.alloc(12, 3)
113103
const privateInput: PrivateInput = {
@@ -130,13 +120,8 @@ describe.each(ALL_ALGOS)('%s Lib Tests', (algorithm) => {
130120
publicInput,
131121
operator
132122
})
133-
// ensure the ZK decrypted data matches the plaintext
134-
expect(
135-
proof.plaintext
136-
.slice(0, plaintext.length)
137-
).toEqual(
138-
plaintext
139-
)
123+
124+
await verifyProof({ proof, publicInput, operator })
140125
})
141126

142127
it('should fail to verify incorrect data', async() => {
@@ -146,7 +131,6 @@ describe.each(ALL_ALGOS)('%s Lib Tests', (algorithm) => {
146131
key: Buffer.alloc(keySizeBytes, 2),
147132
}
148133

149-
150134
const iv = Buffer.alloc(12, 3)
151135
const ciphertext = encryptData(
152136
algorithm,

js/src/types.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ export type GenerateProofOpts = {
5858
*/
5959
algorithm: EncryptionAlgorithm
6060
/**
61-
* private input to the circuit
62-
* * will include the key, iv, and counter
61+
* private input to the circuit (i.e. key)
6362
*/
6463
privateInput: PrivateInput
6564
/**
@@ -75,18 +74,42 @@ export type GenerateProofOpts = {
7574
}
7675

7776
export type VerifyProofOpts = {
78-
/** JSON proof generated by "generateProof" fn */
77+
/**
78+
* JSON proof generated by "generateProof" fn
79+
* with the plaintext
80+
* */
7981
proof: Proof
8082
publicInput: PublicInput
8183
operator: ZKOperator
8284
logger?: Logger
8385
}
8486

87+
export type AlgorithmConfig = {
88+
index: number
89+
chunkSize: number
90+
bitsPerWord: number
91+
keySizeBytes: number
92+
ivSizeBytes: number
93+
startCounter: number
94+
blocksPerChunk: number
95+
isLittleEndian: boolean
96+
uint8ArrayToBits: (arr: Uint8Array) => number[]
97+
bitsToUint8Array: (bits: number[]) => Uint8Array
98+
/**
99+
* Encrypt some ciphertext with the given key and IV
100+
*/
101+
encrypt(opts: {
102+
key: Uint8Array
103+
iv: Uint8Array
104+
in: Uint8Array
105+
}): Promise<Uint8Array> | Uint8Array
106+
}
107+
85108
type ZKProof = { [_: string]: unknown } | string
86109

87110
type ZKProofOutput = {
88111
proof: ZKProof
89-
publicSignals: number[]
112+
publicSignals?: number[]
90113
}
91114

92115
type ZKInputItem = number[] | number[][]
@@ -96,6 +119,7 @@ type ZKProofInput = {
96119
nonce: ZKInputItem
97120
counter: ZKInputItem
98121
in: ZKInputItem
122+
out: ZKInputItem
99123
}
100124

101125
/**
@@ -137,7 +161,6 @@ export type PrivateInput = {
137161
export type PublicInput = {
138162
/** the ciphertext to decrypt */
139163
ciphertext: Uint8Array
140-
141164
/** 192 bit IV for the ciphertext decryption */
142165
iv: Uint8Array
143166
/**

0 commit comments

Comments
 (0)