A confidential payment
Key agreements v1
1. Receriver
Scanning key: S = sG
Spending key: B = bG
2. Sender
ephemeral key: R = rG
3. Shared secret
c = H(rsG) = H(rS) = H(sR)
as a viewing key
4. Destination
cG + B = (c + b)G
The auditor can compute the destination by getting s and R.
Anyone cannot spend the note without knowing c+b.
Key agreements v2
secret_key ---kdf---> random_secret_key ---kdf---> viewing_key ---kdf---> public_key ----> address
viewing_key: a decryption key of Note
public_key: a encryption key of Note
secret_key ---$ \alpha(random) ---> random_secret_key
used in signature
in snarks
primary input: random_public_key
auxiliary input: $ \alpha, random_secret_key
statement: rk = SpendAuthSig.RandomizePublic(α, ak)
Confidential transfer
code: c_transfer.rs
C(
public:
H(old_note),
H(sender_new_note),
H(destination_new_note),
epk, // encryption
ar, // SpendAuthSig.Public // ar
private:
//r, // sender's private key
//cG+B,
//B, // receiver's public key
receiver_payment_address,
sender_payment_address,
value(old),
value(sender),
value(destination),
esk,
ak, // SpendAuthSig.Public,
alpha,
nsk, //proof generation key
)
{
// let R = get_destination_pubkey(r)
old_note == H(sender_payment_address, value(old)) // done
value(old) == value(sender) + value(destination)
H(destination_new_note) == H(receiver_payment_address, value(destination))
H(sender_new_note) == H(sender_payment_address, value(sender))
epk == eskG // done
ar = ak + alphaG // done
pkd = ivk gd // noteに保持されるpkdがnskから抽出されたものであることを保証 // done }
storage: {
note_vec: Vec<H(Note)> // as nullifiers
H(AccountId) => Vec<StorageNote>
}
prover noteのpkdはnsk由来のものであることを証明するためにpayment_addressから直接もってくることはできない。一方、recipientは受け取るpayment_addressを指定しているのでそのままpkdを用いてOK。
pk_d_senderが正しいことはcircuitの中で計算されている
epkはesk * g_dであり、payment_addressに結び付けられることになる。
alphaによりaskからランダム化されたrskによってspend-sigされrkによって検証される。
code:balance.rs
public: {
r''G + v''H, // add r''G, in order to avoid expectability to v''H
rG + vH, // balance pedersen commitment
rk, // the verifying key of the Spend Authorization Signature
epk, // encryption and also the pub_key for generating the stealth address.
g_d_sender,
pk_d_sender,
Enc(r'', v''),
}
private: {
v'', // transfer value commitment
r'', // transfer commitment randomness
v, // balance value commitment
r, // balance commitment randomness
α, // re-randomization parameter ar ak, // proof generation key
esk, // ephemeral secret key that is applying to g_d_receiver
// g_d_sender,
// pk_d_sender,
nsk, // proof generation key // If you do not have nsk, you can not get ivk in the circuit.
}
statement: {
v''.include(0..2^64-1) // range proof // done
(v-v'').include(0..2^64-1) // range proof // done
rG + vH == cm(r, v) // current value commitment integrity of sender's balance // done
r''G + v''H == cm(r'', v'') // transferring value commitment integrity // done
epk == eskG // Ephemeral public key integrity // done rk = ak + αG // spend authority (prove knowledge of the spendingkey(ask)) // done pk_d_sender = ivkg_d_sender // Diversified address integrity // done g_d, ask != O // small order check // done
Enc(r'', v'') == mimc(r'', v'') // encryption validity for receiver
}
functions(
proof, // can be dummy proof for tranferring from stealth address to recipient address
payment_address_s, // would be changed in the future update
payment_address_r, // can be stealth address
// Enc(r'', v''),
Enc(r'', -v'')
): {
assert: verify(proof)
assert: verify(rk, SpendAuthSig.Sign_rsk(SigHash)) // Check the validity of the Spend Authorization Signature
assert: g_d_sender == groupHash(diversifier_sender)
assert: balance_map_spayment_address_s == rG + vH // Ensure the sender has enough balance }
storage: {
balance_map_recipient: payment_address_r => (sigma r')G + (sigma v')H
balance_map_sender: payment_address_s => (sigma r)G + (sigma v)H
txo_map_r: solpayment_address_r => Vec<(Enc(r', v), epk)>
txo_map_s: payment_address_s => Vec<(Enc(r', v), epk)>
}
Frが楕円曲線上、Fsがスカラー有限体
code:barance2.rs
public: {
r''G + v''H, // add r''G, in order to avoid expectability to v''H
rG + vH, // balance pedersen commitment
rk, // the verifying key of the Spend Authorization Signature
epk, // encryption and also the pub_key for generating the stealth address.
g_d_sender,
pk_d_sender,
Enc(r'', v''),
}
private: {
v'', // transfer value commitment
r'', // transfer commitment randomness
v, // balance value commitment
r, // balance commitment randomness
α, // re-randomization parameter ar ak, // proof generation key
esk, // ephemeral secret key that is applying to g_d_receiver
// g_d_sender,
// pk_d_sender,
nsk, // proof generation key // If you do not have nsk, you can not get ivk in the circuit.
}
statement: {
v''.include(0..2^64-1) // range proof // done
(v-v'').include(0..2^64-1) // range proof // done
rG + vH == cm(r, v) // current value commitment integrity of sender's balance // done
r''G + v''H == cm(r'', v'') // transferring value commitment integrity // done
epk == eskG // Ephemeral public key integrity // done rk = ak + αG // spend authority (prove knowledge of the spendingkey(ask)) // done pk_d_sender = ivkg_d_sender // Diversified address integrity // done g_d, ask != O // small order check // done
Enc(r'', v'') == mimc(r'', v'') // encryption validity for receiver
}
functions(
proof, // can be dummy proof for tranferring from stealth address to recipient address
payment_address_s, // would be changed in the future update
payment_address_r, // can be stealth address
// Enc(r'', v''),
Enc(r'', -v'')
): {
assert: verify(proof)
assert: verify(rk, SpendAuthSig.Sign_rsk(SigHash)) // Check the validity of the Spend Authorization Signature
assert: g_d_sender == groupHash(diversifier_sender)
assert: balance_map_spayment_address_s == rG + vH // Ensure the sender has enough balance }
storage: {
balance_map_recipient: payment_address_d_r' => (sigma r')G + (sigma v')H
balance_map_sender: payment_address_d_s' => (sigma r)G + (sigma v)H
txo_map_r: payment_address_d_r => Vec<(Enc(r', v, d_r'), epk)>
txo_map_s: payment_address_d_s => Vec<(Enc(r', v, d_s'), epk)>
}
1つ前のaddress keyに次のaddress keyを参照するdも平文に含めて暗号化して保持し、数珠繋がり的にmapを参照していく。
ストレージの肥大化、残高取得のレイテンシー
code:tx.rs
tx {
Length of the rest of the extrinsic, // 1-5 bytes
Version information, // 1 byte
nonce,
sig, // 64 bytes
sig_verifying_key, // 32bytes
proof, // 192 bytes
balance_commitment(input), // 32 bytes
transfer_commitment(input), // 32bytes
epk(input), // 32 bytes
payment_address_s, // 11 + 32 bytes
payment_address_r, // 11 + 32 bytes
Enc(r'', -v'') // 32 bytes?
}
Constraints
total: 106,604
spend: 98,777
https://gyazo.com/c8c19305fb7fa8101de90c6bcfc1a70f
output: 7,827
https://gyazo.com/3bc6e532dfb4b3bffe9b91a42ec485c2
Update: address confidentiality
=> ring signature and stealth address
=> dummy address using its encryption or blinding factor
send前にreceiverがsenderにone-time-keyを送り、senderはそのone-time-keyとreceiverのaddressからdummy addressを算出
one-time-keyの不正流通
dummy addressが同じである限り一度送金した相手にはkeyが知られてしまう。
Update: Encrypted data explosion
=> encrypt with AHE(Paillier, lifted ElGamal)
small order check
g_d is ensured to be large order. The relationship between g_d and pk_d ultimately binds ivk to the note. If this were a small order point, it would not do this correctly, and the prover could double-spend by finding random ivk's that satisfy the relationship.
Further, if it were small order, epk would be small order too!
would supporte dummy transfer with zero value.
senderが暗号を適当に行うとrecipientの残高が表示できない
CTにおけるbinding factorとamoutのnotification
Finally, by careful use of derandomized signing in the prover, it's possible for the receiver of the coins--who shares a secret with the sender, due to ECDH key agreement with the receivers pubkey--to 'rewind' the proof and use it to extract a message sent by the sender which is 80% of the size of the proof. We use this to signal the value and blinding factor to the receiver, but it could also be used to carry things like reference numbers or refund addresses.
code:c.rs
init: // each account has 100 unit at the time of initialization
- address0: 0xaa
- spending_key0: 0x4ab
- address1: 0xab
- spending_key1: 0x1c8
・・・
transfer 0xab<receiver> 10<amount> 0x4ab<spending_key>
- success
balance 0xaa<addr> 0x4ab<spending_key>
- 90
all_balances
- 0xaa => 0xdaf
- 0xab => 0xfa3
・・・
code:t.rs
transfer(sender, receiver, amount, sk) {
r,v = api::get_balance(sender) // GET
proof = prove() // embedded params
enc = encrypt()
tx = gen_tx()
runtime_api::execute_block() // POST
}
get_balance(addr) -> r, v {
enc = get_storage(addr, from_index)
p = dec(enc)
calc(p)
}
run_chain() {
// (proving_key, verifying_key) = gen_circuit()
start_chain()
}
code:transfer_module.rs
fn c_transfer(proof, enc(sender_new_note), enc(destination_new_note)) {
verify()
delete note_map: H(old_note)
delete owner_map: H(address(R))
or
epk => encNote
}
code:note.rs
struct Note {
viewing_key: u8; 32, // cG + B, (not c) value: u64,
}
// g_d and pk_d are required to get the unique nullifier.
pub struct Note<E: JubjubEngine> {
/// The value of the note
pub value: u64,
/// The diversified base of the address, GH(d)
pub g_d: edwards::Point<E, PrimeOrder>, // DiversifyHash(d)
/// The public key of the address, g_d^ivk
pub pk_d: edwards::Point<E, PrimeOrder>,
}
struct StorageNote {
enc_note: enc(Note),
}
Storage
code: storage.rs
note_vec: Vec<H(Note)> // as nullifiers
H(AccountId) => Vec<StorageNote>
Tx
SIGHASH
Storage v1
code:storage.rs
note_map: H(Note) => H(Owner) // preventing double spend
owner_map: H(Owner) => Enc(Note) // Encrypted by ECIES with c
Storage v2
code:storage.rs
note_vec: Vec<H(Note)> // as nullifiers
note_map: H(Note) => {Enc(Note), epk...,}
// enc_note_vec: Vec<Enc(note)>
code:transfer.rs
fn c_transfer(proof, enc(sender_new_note), enc(destination_new_note)) {
verify()
assert(!note_vec.search(H(old_note))
note_vec.append(H(destination_new_note))
note_vec.append(H(sender_new_note))
(
enc_note_vec.append(enc(sender_new_note)) // enc with r
enc_note_vec.append(enc(destination_new_note)) //enc with c
)
or
}
Storage v3
code:storage.rs
commitment_root: bytes32
note_map: nullifier => enc_note // for getting balance
let nullifier = H(cm(note), )
code:client.rs
struct Note {
value: u64,
rcm: E::Fs // commitment randomness
}
let nullifier = H(cm(Note), pos)
code:c_transfer.rs
C(
public: cm
private: value, pos, path, rt, nf, pk
)
{
assert authenticate_path(rt, pos, path)
cm == computeCm(value, pk)
nf == H(cm, pos)
}
{
verify(proof, )
}
Using pedersen commitment
code:code.rs
// Cm = cH + vG
C(
public: cm(c_old, value(old)), cm(c, value(sender)), cm(c, value(receiver))
private:
value(old),
value(sender),
value(receiver),
c_old,
c,
)
{
commitment equality
value(old) == value(sender) + value(receiver)
}
{
let c_new_sender = cm(c_old, value(old)) - cm(c, value(sender))
let c_new_receiver = cm(c_old', value(old)') + cm(c, value(receiver))
}
// receivers can not realize their own balance
storage {
address => cm(value)
}
code:balance2.rs
public: {
r''G + v''H, // add r''G, in order to avoid expectability to v''H
rG + vH, // balance pedersen commitment
rk, // the verifying key of the Spend Authorization Signature
epk, // encryption and also the pub_key for generating the stealth address.
g_d_sender,
pk_d_sender,
Enc(r'', v''),
}
private: {
v'', // transfer value commitment
r'', // transfer commitment randomness
v, // balance value commitment
r, // balance commitment randomness
α, // re-randomization parameter
ak, // proof generation key
esk, // ephemeral secret key that is applying to g_d_receiver
// g_d_sender,
// pk_d_sender,
nsk, // proof generation key // If you do not have nsk, you can not get ivk in the circuit.
}
statement: {
v''.include(0..2^64-1) // range proof // done
(v-v'').include(0..2^64-1) // range proof // done
rG + vH == cm(r, v) // current value commitment integrity of sender's balance // done
r''G + v''H == cm(r'', v'') // transferring value commitment integrity // done
epk == eskG // Ephemeral public key integrity // done rk = ak + αG // spend authority (prove knowledge of the spendingkey(ask)) // done pk_d_sender = ivkg_d_sender // Diversified address integrity // done g_d, ask != O // small order check // done
Enc(r'', v'') == mimc(r'', v'') // encryption validity for receiver
}
functions(
proof, // can be dummy proof for tranferring from stealth address to recipient address
payment_address_s, // would be changed in the future update
payment_address_r, // can be stealth address
// Enc(r'', v''),
Enc(r'', -v'')
): {
assert: verify(proof)
assert: verify(rk, SpendAuthSig.Sign_rsk(SigHash)) // Check the validity of the Spend Authorization Signature
assert: g_d_sender == groupHash(diversifier_sender)
assert: balance_map_spayment_address_s == rG + vH // Ensure the sender has enough balance }
storage: {
balance_map_r: payment_address_r => (sigma r')G + (sigma v')H
balance_map_s: payment_address_s => (sigma r)G + (sigma v)H
txo_map_r: payment_address_r => Vec<(Enc(r', v), epk)>
txo_map_s: payment_address_s => Vec<(Enc(r', v), epk)>
}
ノードを運営しているアドレスがinputに入っているTxを受け取ったら、そのoutputに対して$ b \gets VerifyKP(pk, sk)でb=1となるアドレスを自身のものとしてupdate。
offchainでnew addressをcommunication
storageで数珠繋ぎ的に保持(storage負荷大)
ノードをトラストし連携。
oblivious transfer
searchable encryption
ノード
UTXO(map)リストを持っているのでランダムにinputのためにピックアップできる
Txのinputに特定(自身)のアドレスが入っているかチェックできる