How bit commitment works¶
Bit commitment and signature¶
Often signature is created with this pseudo code:
mhash = hash(message)
private_key = some_random_number
public_key = create_public_key(private_key) // such as hash
signature = create_signature(mhash, private_key)
assert true == verify_signature(message, signature, public_key)
If message == 0 or 1, we would say this signature is a bit(0/1) commitment.
Bit commitment with Lamport signature¶
Lamport signature is a signature scheme that can be used to do bit commitment. Let's see how it works.
Private key and Public key¶
Private key and public key in Lamport signature consist of a pair of array.
Private key elements(\(s_i^0\), \(s_i^1\)) and public key elements(hash(\(s_i^0\)), hash(\(s_i^1\))) are integer.
0th element | 1st element | 2nd element | 3rd element | ... | |
---|---|---|---|---|---|
private key 0 | \(s_0^0\) | \(s_1^0\) | \(s_2^0\) | \(s_3^0\) | ... |
private key 1 | \(s_0^1\) | \(s_1^1\) | \(s_2^1\) | \(s_3^1\) | ... |
public key 0 | hash(\(s_0^0\)) | hash(\(s_1^0\)) | hash(\(s_2^0\)) | hash(\(s_3^0\)) | ... |
public key 1 | hash(\(s_0^1\)) | hash(\(s_1^1\)) | hash(\(s_2^1\)) | hash(\(s_3^1\)) | ... |
For example:
private key 0: ['0001', '1001', '0001', '1100', ...] // length == 160
private key 1: ['1100', '1111', '1100', '1011', ...]
public key 0: [hash('0001'), hash('1001'), hash('0001'), hash('1100'), ...]
public key 1: [hash('1100'), hash('1111'), hash('1100'), hash('1011'), ...]
Let's make them as a table:
0th element | 1st element | 2nd element | 3rd element | ... | |
---|---|---|---|---|---|
private key 0 | 0001 | 1001 | 0001 | 1100 | ... |
private key 1 | 1100 | 1111 | 1100 | 1011 | ... |
public key 0 | hash(0001) | hash(1001) | hash(0001) | hash(1100) | ... |
public key 1 | hash(1100) | hash(1111) | hash(1100) | hash(1011) | ... |
Signature¶
The logic to create the signature is as follows:
mhash = hash(message) = $[m_0, m_1, m_2, m3...]$
sig = []
sig[0] = $m_0$ == 0 ? $s_0^0$ : $s_0^1$
sig[1] = $m_1$ == 0 ? $s_1^0$ : $s_1^1$
sig[2] = $m_2$ == 0 ? $s_2^0$ : $s_2^1$
sig[3] = $m_3$ == 0 ? $s_3^0$ : $s_3^1$
...
For example, a message and its hash:
message: Hello world
mhash = 1111010111101001010101100110100011011010110111110110111111011110111110000101001000011111011111100001101010101000101001011110011001010000110010011111100001001001 // hash("Hello world")
From lowest bit to highest bit:
mhash[0] = 1
mhash[1] = 0
mhash[2] = 0
mhash[3] = 1
.
.
.
mhash[158] = 1
mhash[159] = 1
The signature for the first 4 bits are:
signature: ['1100', '1001', '0001', '1011', ...]
Bit commitment that done in bitcoin is accomplished with Lamport signature.
Verify¶
Hash each element in signature and compare them with public key.
Bit commitment in Bitcoin¶
Let's commit the first/lowest bit of mhash, which is 0
(m[0]
)
Use Lamport signature to create bit commitment for the 1st bit, we need to put the corresponding public keys for 1st bit onto the stack.
OP_HASH160
OP_DUP
hash(1100) // hash1(the 0th element from public key 1)
OP_EQUAL
OP_DUP
OP_ROT
hash(0001) // hash0(the 0th element from public key 0)
OP_EQUAL
OP_BOOLOR
OP_VERIFY
See how this script works.
The initial state of stack and script is:
Stack | Script |
---|---|
OP_HASH160 OP_DUP hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
If 1100 is pushed to the stack¶
Stack | Script |
---|---|
1100 | OP_HASH160 OP_DUP hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: 1100 OP_HASH160
After execution:
Stack | Script |
---|---|
hash(1100) | OP_DUP hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: hash(1100) OP_DUP
After execution:
Stack | Script |
---|---|
hash(1100) hash(1100) |
hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: hash(1100) hash(1100) hash(1100)
After execution:
Stack | Script |
---|---|
hash(1100) hash(1100) hash(1100) |
OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: hash(1100) hash(1100) OP_EQUAL
After execution:
Stack | Script |
---|---|
True hash(1100) |
OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: True OP_DUP
After execution:
Stack | Script |
---|---|
True True hash(1100) |
OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: True True hash(1100) OP_ROT
After execution:
Stack | Script |
---|---|
hash(1100) True True |
hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: hash(1100) True True hash(0001)
After execution:
Stack | Script |
---|---|
hash(0001) hash(1100) True True |
OP_EQUAL OP_BOOLOR OP_VERIFY |
Execution: hash(0001) hash(1100) OP_EQUAL
After execution:
Stack | Script |
---|---|
False True True |
OP_BOOLOR OP_VERIFY |
Execution: False True OP_BOOLOR
After execution:
Stack | Script |
---|---|
True True |
OP_VERIFY |
Execution: True OP_VERIFY
After execution:
Stack | Script |
---|---|
True |
True
is left in the stack, since the value of False is 1
, so we can say that 1100
is the bit commitment of 1
.
If 0001 is pushed to the stack¶
Since the execution process is similar, we will just put the post-execution state below.
Stack | Script |
---|---|
0001 | OP_HASH160 OP_DUP hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(0001) | OP_DUP hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(0001) hash(0001) |
hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(1100) hash(0001) hash(0001) |
OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
False/0 hash(0001) |
OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
False False hash(0001) |
OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(0001) False False |
hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(0001) hash(0001) False False |
OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
True False False |
OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
True False |
OP_VERIFY |
Stack | Script |
---|---|
False |
False
is left in the stack, since the value of False is 0
, so we can say that 0001
is the bit commitment of 0
.
If 1111 is pushed to the stack¶
Since the execution process is similar, we will just put the post-execution state below.
Stack | Script |
---|---|
1111 | OP_HASH160 OP_DUP hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(1111) | OP_DUP hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(1111) hash(1111) |
hash(1100) OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(1100) hash(1111) hash(1111) |
OP_EQUAL OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
False/0 hash(1111) |
OP_DUP OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
False False hash(1111) |
OP_ROT hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(1111) False False |
hash(0001) OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
hash(0001) hash(1111) False False |
OP_EQUAL OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
False False False |
OP_BOOLOR OP_VERIFY |
Stack | Script |
---|---|
False False |
OP_VERIFY |
Will revert and terminate due to OP_VERIFY fail to verify. | |
## Reference | |
- https://www.youtube.com/live/VIg7BjX_lJw?si=djNaeeufQ6Pq0oIl | |
- https://github.com/SGeetansh/Lamport_Signatures | |
- https://docs.rs/lamport_signatures/latest/src/lamport_signatures | |
- https://github.com/AtropineTears/Lamport-rs/tree/master | |
- https://www.cs.umd.edu/~jkatz/crypto/f02/lectures/lecture36.pdf | |
- https://en.bitcoin.it/wiki/Script | |
- https://bitcoinmagazine.com/technical/script-state-from-lamport-signatures- |