Last active
June 2, 2024 05:03
-
-
Save xhliu/9f759cb80e079dbcda6f0742be18294a to your computer and use it in GitHub Desktop.
escrow w/ zkp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// require node 10 and Rust to run | |
// https://github.com/ZenGo-X/dlog-verifiable-enc | |
var ve = require('dlog-verifiable-enc').ve; | |
var assert = require('assert'); | |
var { bsv, toHex, buildContractClass, Ripemd160, signTx, PubKey, Sig } = require('scryptlib'); | |
const G = bsv.crypto.Point.getG() | |
const N = bsv.crypto.Point.getN() | |
const BN = bsv.crypto.BN | |
const { inputIndex, inputSatoshis, newTx, loadDesc } = require('./helper'); | |
// bsv library generate compressed publickey default, | |
// but dlog-verifiable-enc library using uncompressed publickey, so need to uncompressed the publicKey | |
function uncompressPublicKey(pubkey) { | |
const hex = toHex(new bsv.PublicKey(Object.assign({}, pubkey.toObject(), { compressed: false }))).substr(2); | |
return Buffer.from(hex, 'hex') | |
} | |
const privKeyA = new bsv.PrivateKey.fromRandom('testnet') | |
const pubKeyA = privKeyA.publicKey; | |
const ucPubKeyA = uncompressPublicKey(pubKeyA); | |
const privKeyB = new bsv.PrivateKey.fromRandom('testnet') | |
const pubKeyB = privKeyB.publicKey; | |
const ucPubKeyB = uncompressPublicKey(pubKeyB); | |
const privKeyEscrow = new bsv.PrivateKey.fromRandom('testnet') | |
const pubKeyEscrow = privKeyEscrow.publicKey | |
const ucPubKeyEscrow = uncompressPublicKey(pubKeyEscrow); | |
const encryptionResultA = ve.encrypt(ucPubKeyEscrow, privKeyA.toBuffer()); | |
const proofA = ve.prove(ucPubKeyEscrow, encryptionResultA); | |
// zero knowledge proof: Bob can verify if encryptionResultA is encrypted privKeyA (without knowing it) with ucPubKeyEscrow, whose corresponding public key is ucPubKeyA | |
const isVerifiedA = ve.verify(proofA, ucPubKeyEscrow, ucPubKeyA, encryptionResultA.ciphertexts); | |
assert(isVerifiedA); | |
const encryptionResultB = ve.encrypt(ucPubKeyEscrow, privKeyB.toBuffer()); | |
const proofB = ve.prove(ucPubKeyEscrow, encryptionResultB); | |
// zero knowledge proof the other way | |
const isVerifiedB = ve.verify(proofB, ucPubKeyEscrow, ucPubKeyB, encryptionResultB.ciphertexts); | |
assert(isVerifiedB); | |
// first, Alice and Bob creates a shared public key, by sharing their own public key with each other | |
// Buyer Alice sends fund to the shared address | |
function aliceFund() { | |
const pubkeyShare = bsv.PublicKey.fromPoint(pubKeyA.point.add(pubKeyB.point)) | |
const publicKeyHash = bsv.crypto.Hash.sha256ripemd160(pubkeyShare.toBuffer()) | |
const P2PKH = buildContractClass(loadDesc("p2pkh_desc.json")) | |
return new P2PKH(new Ripemd160(toHex(publicKeyHash))); | |
} | |
// when the transaction is completed normally, Alice directly gives the private key to Bob, | |
// and Bob can unlock it after getting the private key of Alice | |
function BobUnlockUsingAlicePrivKeyShare() { | |
const tx = newTx(); | |
let context = { tx, inputIndex, inputSatoshis } | |
const sumKey = privKeyA.bn.add(privKeyB.bn).mod(N); | |
const privkeyShare = new bsv.PrivateKey(sumKey) | |
let sig = signTx(tx, privkeyShare, p2pkh.lockingScript.toASM(), inputSatoshis) | |
result = p2pkh.unlock(new Sig(toHex(sig)), new PubKey(toHex(privkeyShare.publicKey))).verify(context) | |
assert(result.success) | |
console.log('Bob unlocks using private key share from Alice successfully') | |
} | |
// both alice and bob can can use their own private keys and unlock utxo through escrow | |
function unlocksByEscrow(playerName, privKey, encryptionResult) { | |
const secretKeyNew = ve.decrypt(privKeyEscrow.toBuffer(), encryptionResult.ciphertexts); | |
const privKeyFromEscrow = new bsv.PrivateKey.fromBuffer(secretKeyNew, 'testnet') | |
const tx = newTx(); | |
let context = { tx, inputIndex, inputSatoshis } | |
const sumKey = privKey.bn.add(privKeyFromEscrow.bn).mod(N); | |
const privkeyShare = new bsv.PrivateKey(sumKey) | |
let sig = signTx(tx, privkeyShare, p2pkh.lockingScript.toASM(), inputSatoshis) | |
result = p2pkh.unlock(new Sig(toHex(sig)), new PubKey(toHex(privkeyShare.publicKey))).verify(context) | |
assert(result.success) | |
console.log(playerName + ' unlocks by escrow successfully') | |
} | |
// alice sends fund to the shared address | |
const p2pkh = aliceFund() | |
// if Alice gets the goods, she gives Bob her private key share | |
BobUnlockUsingAlicePrivKeyShare() | |
// If Bob cheats, Alice gets her money back through escrow. | |
unlocksByEscrow('Alice', privKeyA, encryptionResultB) | |
// if alice cheat, then bob get his money through escrow. | |
unlocksByEscrow('Bob', privKeyB, encryptionResultA) | |
// zero knowledge proof: it should verify fail when alice provide a fake key encrypted does not match ucPubKeyA | |
function fakeKey() { | |
const privKeyFakeA = new bsv.PrivateKey.fromRandom() | |
const pubKeyFakeA = privKeyFakeA.publicKey | |
const uncompressedpubKeyFake = uncompressPublicKey(pubKeyFakeA); | |
const encryptionResultFakeA = ve.encrypt(ucPubKeyEscrow, privKeyFakeA.toBuffer()); | |
const secretKeyNewFakeA = ve.decrypt(privKeyEscrow.toBuffer(), encryptionResultFakeA.ciphertexts); | |
assert(toHex(secretKeyNewFakeA) !== toHex(privKeyA.toBuffer())); | |
const proofFakeA = ve.prove(uncompressedpubKeyFake, encryptionResultFakeA); | |
// private key encrypted does not match ucPubKeyA | |
const isVerifiedA = ve.verify(proofFakeA, ucPubKeyEscrow, ucPubKeyA, encryptionResultFakeA.ciphertexts); | |
assert(!isVerifiedA); | |
} | |
// verify fake key shall fail | |
fakeKey(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment