Skip to content

Instantly share code, notes, and snippets.

@xhliu
Last active January 25, 2023 10:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xhliu/9f759cb80e079dbcda6f0742be18294a to your computer and use it in GitHub Desktop.
Save xhliu/9f759cb80e079dbcda6f0742be18294a to your computer and use it in GitHub Desktop.
escrow w/ zkp
// 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