diff --git a/circuits/keypair.circom b/circuits/keypair.circom index 81f7d23..70dc53e 100644 --- a/circuits/keypair.circom +++ b/circuits/keypair.circom @@ -8,4 +8,17 @@ template Keypair() { component hasher = Poseidon(1); hasher.inputs[0] <== privateKey; publicKey <== hasher.out; -} \ No newline at end of file +} + +template Signature() { + signal input privateKey; + signal input commitment; + signal input merklePath; + signal output out; + + component hasher = Poseidon(3); + hasher.inputs[0] <== privateKey; + hasher.inputs[1] <== commitment; + hasher.inputs[2] <== merklePath; + out <== hasher.out; +} diff --git a/circuits/transaction.circom b/circuits/transaction.circom index 856d361..b9e5a1c 100644 --- a/circuits/transaction.circom +++ b/circuits/transaction.circom @@ -11,7 +11,7 @@ Utxo structure: } commitment = hash(amount, pubKey, blinding) -nullifier = hash(commitment, merklePath, privKey) +nullifier = hash(commitment, merklePath, sign(commitment + merklePath, privKey)) */ // Universal JoinSplit transaction with nIns inputs and 2 outputs @@ -38,6 +38,7 @@ template Transaction(levels, nIns, nOuts, zeroLeaf) { signal private input outBlinding[nOuts]; component inKeypair[nIns]; + component inSignature[nIns]; component inUtxoHasher[nIns]; component nullifierHasher[nIns]; component tree[nIns]; @@ -54,10 +55,15 @@ template Transaction(levels, nIns, nOuts, zeroLeaf) { inUtxoHasher[tx].inputs[1] <== inKeypair[tx].publicKey; inUtxoHasher[tx].inputs[2] <== inBlinding[tx]; + inSignature[tx] = Signature(); + inSignature[tx].privateKey <== inPrivateKey[tx]; + inSignature[tx].commitment <== inUtxoHasher[tx].out; + inSignature[tx].merklePath <== inPathIndices[tx]; + nullifierHasher[tx] = Poseidon(3); nullifierHasher[tx].inputs[0] <== inUtxoHasher[tx].out; nullifierHasher[tx].inputs[1] <== inPathIndices[tx]; - nullifierHasher[tx].inputs[2] <== inPrivateKey[tx]; + nullifierHasher[tx].inputs[2] <== inSignature[tx].out; nullifierHasher[tx].out === inputNullifier[tx]; tree[tx] = MerkleProof(levels); diff --git a/src/keypair.js b/src/keypair.js index 26f223b..3cc1edc 100644 --- a/src/keypair.js +++ b/src/keypair.js @@ -78,6 +78,17 @@ class Keypair { }) } + /** + * Sign a message using keypair private key + * + * @param {string|number|BigNumber} commitment a hex string with commitment + * @param {string|number|BigNumber} merklePath a hex string with merkle path + * @returns {BigNumber} a hex string with signature + */ + sign(commitment, merklePath) { + return poseidonHash([this.privkey, commitment, merklePath]) + } + /** * Encrypt data using keypair encryption key * diff --git a/src/utxo.js b/src/utxo.js index 65dd420..98817d0 100644 --- a/src/utxo.js +++ b/src/utxo.js @@ -46,7 +46,8 @@ class Utxo { ) { throw new Error('Can not compute nullifier without utxo index or private key') } - this._nullifier = poseidonHash([this.getCommitment(), this.index || 0, this.keypair.privkey || 0]) + const signature = this.keypair.privkey ? this.keypair.sign(this.getCommitment(), this.index || 0) : 0 + this._nullifier = poseidonHash([this.getCommitment(), this.index || 0, signature]) } return this._nullifier }