-
Notifications
You must be signed in to change notification settings - Fork 1
Range Proof
Range Proof is used to prove, with zero-knowledge (i.e. without revealing the amount or the blinding factor), that the value committed to in a given Pedersen Commitment falls within a certain range in [0, 2^64], so that user cannot exploit the blinding to produce overflow attacks, etc.
Why range proof is necessary for each output commitment?
Let's have a simple example to demo it.
Suppose we have a transaction case that Alice spend 3 coins to send Bob 2 coins and get 1 coins change. In this transaction, we have:
- 1 input commitment:
113*G+3*H
, the original UTXO of Alice, used in this transaction as the input. - 2 output commitments:
42*G+1*H
(the change of Alice), and99*G+2*H
(the output for Bob). - 1 transaction kernel, which include 3 parts:
- msg (a random number in this example).
- excess (
13*G
, i.e.k1*G
, after splitting the keyk = k1 + k2
. - signature (with private key:
13
, i.e.k1
.
- offset:
15
, i.e.k2
. Note:k=k1+k2=28
.
A detail data of this transaction could be like this: (read Transaction Aggregation for the more info about this transaction example.)
input=113*G+3*H: Commitment(0955e54cd28baa1416c1f795f6ddba1c2d1f760c9d9b515360ef944babe3eea352)
output1= 99*G+2*H: Commitment(0878992a66a47907eefad338908cd0a44941aa4219706a2c9d0de2fa0559863eaa)
output2= 42*G+1*H: Commitment(091c1f29a819b7d3fde56f425692e9cb9a6682ae412a1a1019b3261da6439371e6)
msg: Message(d851581d434b9f2718b89f3441b13062e651cdbc4752e99767b706d640ede882)
excess w/ k1*G: Commitment(09f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8)
Signature: Signature(91bb7da507b3c8b67b0e47e35f1406b5b92381a8edba6b5f9755f79f0555d848bb0b333aa0f6dd67d5fb414620c2d3da74f5f9dce409726b4a89c9ad953ec10e)
k2: SecretKey(000000000000000000000000000000000000000000000000000000000000000f)
sum with k2*G verify OK: output1 + output2 = input + excess + k2*G
Now, let's do a little change on it to create a fraud transaction:)
- 1 input commitment:
113*G+3*H
, the original UTXO of Alice, used in this transaction as the input. - 2 output commitments:
42*G-100*H
(the change of Alice), and99*G+103*H
(the output for Bob). - 1 transaction kernel, which include 3 parts:
- msg (a random number in this example).
- excess (
13*G
, i.e.k1*G
, after splitting the keyk = k1 + k2
. - signature (with private key:
13
, i.e.k1
.
- offset:
15
, i.e.k2
. Note:k=k1+k2=28
.
A detail data of this transaction could be like this:
input=113*G+3*H: Commitment(0955e54cd28baa1416c1f795f6ddba1c2d1f760c9d9b515360ef944babe3eea352)
output1= 99*G+103*H: Commitment(09c1e7b18f9017d841818216898314e81695a06a345cfcce718eb812f82041fbee)
output2= 42*G-100*H: Commitment(09c012fc9b667902ffaa392d12b010ff6d79987c6bd0ae1334e969d6c60d16aa58)
msg: Message(b03bf2764ac43bdb151ecdbdcb15ff794cadcf95131ebe718b1e27c0780ccb5a)
excess w/ k1*G: Commitment(09f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8)
Signature: Signature(91dfb90e0516464de3d1ad197d8a1456533fc27b272fc84cde13f6bcdad1b9275cf0715f54069f80bfbd7dd32d8b944006965e40b3d699e8a45a020e356f5f1c)
k2: SecretKey(000000000000000000000000000000000000000000000000000000000000000f)
Signature verify OK
sum with k2*G verify OK: output1 + output2 = input + excess + k2*G
You can run this demo to check the result:
Environment preparation:
$ git clone https://github.com/garyyu/rust-secp256k1-zkp.git
$ cd rust-secp256k1-zkp
$ cargo build --release
Source Code (Click to expand)
#[test]
fn test_demo_fraud_transactions() {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
fn commit(value: i64, blinding: SecretKey) -> Commitment {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let v: u64;
let mut blind = blinding;
if value >= 0 {
v = value as u64;
secp.commit(v, blind).unwrap()
} else {
v = -value as u64;
if blind != ZERO_KEY {
blind = secp.blind_sum(vec![], vec![blind]).unwrap();
}
let commit = secp.commit(v, blind).unwrap();
secp.commit_sum(vec![], vec![commit]).unwrap()
}
}
let mut r1 = SecretKey([0;32]);
let mut r2 = SecretKey([0;32]);
let mut r3 = SecretKey([0;32]);
r1.0[31] = 113; // input
r3.0[31] = 42; // for change
r2.0[31] = 113-42; // total blinding factor from sender
// split original r=28 into k1+k2
let mut k1 = SecretKey([0;32]);
let mut k2 = SecretKey([0;32]);
k1.0[31] = 13;
k2.0[31] = 15;
let input = commit(3, r1);
let output1 = commit(103, secp.blind_sum(vec![r2, k1, k2], vec![]).unwrap());
let output2 = commit(-100, r3);
let tmp = commit(103, secp.blind_sum(vec![r2, k1], vec![]).unwrap());
// publish k1*G as excess and k2, instead of (k1+k2)*G
// k component:
// (r2+k1+r3-r1)*G = (113-42+13+42-113)*G = 13*G
// ~~~~~ ~~ ~~ ~~~~~~~~~ ~~ ~~~
// v component:
// (103+(-100)-3)*H = 0*H
//
let excess = secp.commit_sum(vec![tmp, output2], vec![input]).unwrap();
println!(" input=113*G+3*H:\t{:?}\noutput1= 99*G+103*H:\t{:?}\noutput2= 42*G-100*H:\t{:?}",
input,
output1,
output2,
);
// sign it only with k1 instead of (k1+k2)
let mut msg = [0u8; 32];
thread_rng().fill_bytes(&mut msg);
let msg = Message::from_slice(&msg).unwrap();
let sig = secp.sign(&msg, &k1).unwrap();
let pubkey = excess.to_pubkey(&secp).unwrap();
println!("\n\tmsg:\t\t{:?}\n\texcess w/ k1*G:\t{:?}\n\tSignature:\t{:?}\n\tk2:\t\t{:?}",
msg,
excess,
sig,
k2,
);
// check that we can successfully verify the signature with the public key
if let Ok(_) = secp.verify(&msg, &sig, &pubkey) {
println!("Signature verify OK");
} else {
println!("Signature verify NOK");
}
if true==secp.verify_commit_sum(
vec![output1, output2],
vec![input, excess, commit(0, k2)],
){
println!("\nsum with k2*G verify OK:\toutput1 + output2 = input + excess + k2*G");
}else{
println!("\nsum with k2*G verify NOK:\toutput1 + output2 != input + excess + k2*G");
}
}
And here is the demo executing command:
$ cargo test test_demo_fraud_transactions -- --nocapture
Explain
- Look above transaction, because of pedersen commitment's good obscuring function, nobody can know this transaction is a fraud one: Alice only has
3
coins but send Bob103
coins! and get a negative value-100
as the change output! - To avoid such kind of
3 = (-100) + 103
fraud transaction, the range proof is required.
Now let's go to more detail about range proof.
Firstly, a simple demo about how to create a range proof. Note: we only demo the Bullet Proof here since it has much smaller size than range proof.
Source Code (Click to expand)
#[test]
fn test_demo_bullet_proof() {
println!("Demo Bullet Proof without extra message data...\n");
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let blinding = SecretKey::new(&secp, &mut OsRng::new().unwrap());
let value = 12345678;
let commit = secp.commit(value, blinding).unwrap();
let bullet_proof = secp.bullet_proof(value, blinding, blinding, None);
println!("Value:\t\t{}\nBlinding:\t{:?}\nCommitment:\t{:?}\n\nBullet Proof:\t{:?}",
value,
blinding,
commit,
bullet_proof,
);
let proof_range = secp.verify_bullet_proof(commit, bullet_proof, None).unwrap();
println!("\nVerification:\t{:#?}", proof_range);
//-----
println!("\nDemo Bullet Proof with extra message data...\n");
let extra_data = [0u8;32].to_vec();
let blinding = SecretKey::new(&secp, &mut OsRng::new().unwrap());
let value = 12345678;
let commit = secp.commit(value, blinding).unwrap();
let bullet_proof = secp.bullet_proof(value, blinding, blinding, Some(extra_data.clone()));
println!("Value:\t\t{}\nBlinding:\t{:?}\nExtra data:\t{:?}\nCommitment:\t{:?}\n\nBullet Proof:\t{:?}",
value,
blinding,
(extra_data),
commit,
bullet_proof,
);
let proof_range = secp.verify_bullet_proof(commit, bullet_proof, Some(extra_data.clone())).unwrap();
println!("\nVerification:\t{:#?}", proof_range);
//-----
println!("\nDemo rewinding. Extracts the value and blinding factor...\n");
let blinding = SecretKey::new(&secp, &mut OsRng::new().unwrap());
let nonce = SecretKey::new(&secp, &mut OsRng::new().unwrap());
let value = 12345678;
let commit = secp.commit(value, blinding).unwrap();
let bullet_proof = secp.bullet_proof(value, blinding, nonce, Some(extra_data.clone()));
println!("Value:\t\t{}\nBlinding:\t{:?}\nExtra data:\t{:?}\nNonce:\t{:?}\nCommitment:\t{:?}\n\nBullet Proof:\t{:?}",
value,
blinding,
(extra_data),
nonce,
commit,
bullet_proof,
);
// Extracts the value and blinding factor from a single-commit rangeproof,
// given a secret 'nonce'.
//
let proof_info = secp.rewind_bullet_proof(commit, nonce, Some(extra_data.clone()), bullet_proof).unwrap();
println!("\nRewind_bullet_proof:\t{:#?}", proof_info);
println!("Bullet Proof:\t{:?}",
bullet_proof,
);
let proof_range = secp.verify_bullet_proof(commit, bullet_proof, Some(extra_data.clone())).unwrap();
println!("\nVerification:\t{:#?}", proof_range);
}
$ cargo test test_demo_bullet_proof -- --nocapture
running 1 test
Demo Bullet Proof without extra message data...
Value: 12345678
Blinding: SecretKey(8d83a0e1840c6363ffe93505bef9eac05b245e475cb280b154b95d96d358db29)
Commitment: Commitment(085b019489a64283329829e045f41b84a37536ce35c6785427ae48de9601ab930c)
Bullet Proof: RangeProof(5625ae48b9d57bff0d16d618d7d9a9fcf24b61a8cc6ed2175845c5df7f03ef43cc3cfe2a24d68785f9ef38c545cac3848d7b0fee626f074b78b5b65d639843d30edd7b4f30ac8aafc41fc6c45d2c9f3ec4f447c212100fa5c4321c18a64b32e72b146769afb15e3aa4ada12db6873e1f5f491f181b921bd1362c008285d742cde54026958c0db39921ae13daa6bb8251791353fca02738851a29bd697b23e5b090e13b9320981c8c0d156cf0eb889285e27439466010dc62a3feac04f21af6e8b67ea53ab9ac909555a65045aab2940bf392ec21d2632d970fab18690a1f0b1a9c6be9e3b4cec806ad2c7606df1385cace3127e40747bfb04d7ed81b2df5b5aa29160580d279c2b493af2f034d4d7473fafb91f34cf0d73d7f716eb43bef269fcbec3c8b6f50309a83ac5580d87c31b9426963cd77b46bea6baf7bbf0be92341eb920d8679e6d705d1d08224b5cbc50a45e776e273cc68f5b257cdc4a0f7e165980201fa9d4dd3f98e3f3b17d1726e9d2a4b37356d362b9232b95f9557d8102f3b0e954e1daa2e85e8edb13144b2b02844c14f38ac918e95de5592e350c1d0beae8d6ee231a1ba43261b39515b775ab1c47444e8bc4cd470a3d1acfda0a55471f4c3ae264ba438084d492175d1707be6bf021495a239bd21047e3d7379ef1c38fb45f1e3d5ebfe6bbc96f517d91dc3e4a1d87e3a86b6c6302206acf1c18e905768ca917b0abad775d98c1a8146825eefce5682789d85735a13604a4ac0b618726613c4901219868d1f104b8460279e6ca8ce9f02ec575f69c2e6bd68e9ec76019bf9b8a074bc5e1ab92db99c37dc300f8983a3a390e0632739e7a576b6a1c5efad47f3c696febe4eeb035bc9154cbb604c8bb5a55314aca43283d4bbec56fe2066dc7301f50700f72b1d30898711591d37c044f920c3a5a494fe4492a547a9d6e01c82)[675]
Verification: ProofRange {
min: 0,
max: 18446744073709551615
}
Demo Bullet Proof with extra message data...
Value: 12345678
Blinding: SecretKey(ce01ba875aece831f2c4afe7c442ff3c58a4d416b79b2edfea1e1db656caf07f)
Extra data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Commitment: Commitment(09b32ee1b16cfa00659207ac8c2f0c79f172588fc4b9b4b48f220a60f05767015d)
Bullet Proof: RangeProof(eeda0aba9414f655bc0eecef7dadf2c819d9b24183f7b9b074c8df5ae8546118cff8779cdfa13b3f6cd3a6bcfdd956048d5449edd02cde5fc2715dbbccaf902c0cf442768716fefe978a0542f3df2493bfbbae4a134e4024d68efb9e8a1f0428ba150959486c30e2cd491771e2e3ed53120da4b5ea964c69c1de767f330e4b61de9d5e55caaea8a00c070816cfb1318d35959922f66048d28e9aa46a45e032c240933736eff09c48bb21d7131b94ef97f0491e9fb1419ea76b995107a14495c8120dbdf495898d135a61830f16ef0bcf6e824781de5809cd08fa3ca512685835d561b7ddfcb5f3ecac1abb0ab90c76edfb20c909c0c171cbd38f7dda048686379c852725f5f7eb8928c1d39d39ec5f7890e5460c90371173fca88550216c68451ebbece1fd97477b5d2402b3359e13965fa47840f1d487df9dcdc875484f1183be13748559bef9d4e4edfbe72d927b7416805e6c0c76449a2db08e51f25503ba2d3e03615c2e45e1e6880328929e56d85d8395770b10c72039679521328da10d1b50b90a8f63649f6567bf0a77a23824c9c0f544c462c0d9884255505cdfa4755e9b2afb66d94a5ee6b5a1e7a06248ad938b15c4f4bb21029e5323a947ae460c0cac6c335b0935e23e32fdb0a67761332d4890f8f68ca0e529e8aca7d684bd435e5c9bb59b9f7a4a29691c3f5cad14cfcf6b8d6935d5316173e8ee4a56cd3900783bbd5cb2ec2a6b3d95f95df99297ae3afd4c3f28c90f13d6bb0d39603f45174c80ab55665b015ed14c6ec94ce6e4a2e22c318177da1c852c6563578721af5323493545895cebd3dce0035441af82e986b8095997c09313af5a2b19cbb4ec4943b266e9cffb40103855c226744e413d75cba64d40393dc8cbd65f9d36648db9de77a45f01e66e81897f67006ecb65539ca6ad7d83898127f61c522c43929445c08cb0)[675]
Verification: ProofRange {
min: 0,
max: 18446744073709551615
}
Demo rewinding. Extracts the value and blinding factor...
Value: 12345678
Blinding: SecretKey(db0b9a91256700e2f7d44ab1db154755f9c840d5bae7fcc30eda0d09dac029d7)
Extra data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Nonce: SecretKey(ae9f6f0b2764368c1fbf610a355585b17d4f933e75a7b400455f5376c0b7409f)
Commitment: Commitment(08632015092e65d14d4deb4b825631c8b6ef23b59dcb691f182ddff1864399caa8)
Bullet Proof: RangeProof(c7b09d32f28087cb40480e03eb9936f0a7a65a365e3be23316a07346e41ebce2f2d39e31ae980d076af52e37090016811993f437f0d27466e5f8e7613c3c0ce80d885d81f1d7a4d9474a358b577b65eccbae92936ddb8b88b6e71dc5f5e85e7e52f24b900b68f5088a041945f53e637ef01d08454ee786ff9fccc263eaefc31faec2d74fcc79e9d04c7136a4789a8397bdb762699fc4503116e844fb7c1617d9b00959cd1bb00a3dd6f4a2b305440d05b041a53ce5069b637f83d0fc7f079169b56af0d9156e1c28a10ded538819b9d84e5c43e9fe2788dc8def9cd1d4cddb7739f2adb467ec90728256c3fd6bb3a20dc04bd82237f09cad7e37ccfb91a3ab54b71cd0775b413afab97e32e4729bc21dadc2a81ef65fdfd6fe8eeef5b7e824af1c2db510a82dde4d475e6eca47f610c789e27b1643898902fea53baaeb7c461576a6a51022e235499a4127a34aed128fb91d95875d66277367c237ba292211d94214005b4650e40672eb937081fe6251097d4b5a01e2cd59b55cfca886baafc564293ddfcbe65200c3e58a08385d038d94ca0d2a7a076eaaebaa591d77b74d1ca59e7d741bb3a55cdd01af41ea6082e9bd23fefd58403aa0aaff7d7652a7bd4b97c11609a7745742b2f0a3200f1f4083e1e851194836894ed30321808996d1cd72464b7b897b4d4d2f5dd9677524760425a7da2eaaf172a5a62f674433f90a42ba4b5daf42491c356295850a50185012023fcc3b029c903a580e2d4ba6c1f6add65aafc183a9788a58a51313d7b4e9be0023a79b44f6ec2113409a44bd1fbcdab201f1ab1c4ddd2bb1d8867ee91f85217da5402a9057b6042e7ec59c285a07411d2479a6ec3b0fc007abdd8479aea08b4aee6814d44bb3c56d9c277ba4e17fcc03f2c68c914f754bd8d56588972c2d3e265f7d4f1a225acb2bcdc2b34fd5dec7ae3fd8)[675]
Rewind_bullet_proof: ProofInfo {
success: true,
value: 12345678,
blinding: SecretKey(db0b9a91256700e2f7d44ab1db154755f9c840d5bae7fcc30eda0d09dac029d7),
message: ProofMessage(),
mlen: 0,
min: 0,
max: 18446744073709551615,
exp: 0,
mantissa: 0
}
Bullet Proof: RangeProof(c7b09d32f28087cb40480e03eb9936f0a7a65a365e3be23316a07346e41ebce2f2d39e31ae980d076af52e37090016811993f437f0d27466e5f8e7613c3c0ce80d885d81f1d7a4d9474a358b577b65eccbae92936ddb8b88b6e71dc5f5e85e7e52f24b900b68f5088a041945f53e637ef01d08454ee786ff9fccc263eaefc31faec2d74fcc79e9d04c7136a4789a8397bdb762699fc4503116e844fb7c1617d9b00959cd1bb00a3dd6f4a2b305440d05b041a53ce5069b637f83d0fc7f079169b56af0d9156e1c28a10ded538819b9d84e5c43e9fe2788dc8def9cd1d4cddb7739f2adb467ec90728256c3fd6bb3a20dc04bd82237f09cad7e37ccfb91a3ab54b71cd0775b413afab97e32e4729bc21dadc2a81ef65fdfd6fe8eeef5b7e824af1c2db510a82dde4d475e6eca47f610c789e27b1643898902fea53baaeb7c461576a6a51022e235499a4127a34aed128fb91d95875d66277367c237ba292211d94214005b4650e40672eb937081fe6251097d4b5a01e2cd59b55cfca886baafc564293ddfcbe65200c3e58a08385d038d94ca0d2a7a076eaaebaa591d77b74d1ca59e7d741bb3a55cdd01af41ea6082e9bd23fefd58403aa0aaff7d7652a7bd4b97c11609a7745742b2f0a3200f1f4083e1e851194836894ed30321808996d1cd72464b7b897b4d4d2f5dd9677524760425a7da2eaaf172a5a62f674433f90a42ba4b5daf42491c356295850a50185012023fcc3b029c903a580e2d4ba6c1f6add65aafc183a9788a58a51313d7b4e9be0023a79b44f6ec2113409a44bd1fbcdab201f1ab1c4ddd2bb1d8867ee91f85217da5402a9057b6042e7ec59c285a07411d2479a6ec3b0fc007abdd8479aea08b4aee6814d44bb3c56d9c277ba4e17fcc03f2c68c914f754bd8d56588972c2d3e265f7d4f1a225acb2bcdc2b34fd5dec7ae3fd8)[675]
Verification: ProofRange {
min: 0,
max: 18446744073709551615
}