Skip to content

Commit e82936b

Browse files
committed
Merge #45: Add price oracle fragments
5e533b7 Add price_oracle1 and price_oracle1_w (sanket1729) b720130 Refactor arith.rs file to support ExtParam in it (sanket1729) 805c66c Add extension spec for oracle checksig (sanket1729) 2844f5e Add some test vector from curl (sanket1729) Pull request description: Unfortunately, I had to use two versions for fragments with type checking rules. `price_oracle1` and `price_oracle1_w` ACKs for top commit: apoelstra: ACK 5e533b7 Tree-SHA512: ffef6f7f0635f2022960d5663fa1478c028cce386e2327b28dd7698bceb8c394733cb80a965c880ecdaf6a0ebea81e20f026ba05de825e780468d3e07f413e3b
2 parents 6042fd4 + 5e533b7 commit e82936b

File tree

8 files changed

+957
-166
lines changed

8 files changed

+957
-166
lines changed

bitcoind-tests/tests/setup/test_util.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ pub struct PubData {
5151
pub values: HashMap<String, confidential::Value>,
5252
pub assets: HashMap<String, confidential::Asset>,
5353
pub spks: HashMap<String, elements::Script>,
54+
55+
// price oracle test data
56+
pub timestamp: u64,
57+
pub price: i64,
5458
}
5559

5660
#[derive(Debug, Clone)]
@@ -134,6 +138,8 @@ impl TestData {
134138
values: HashMap::new(),
135139
assets: HashMap::new(),
136140
spks: HashMap::new(),
141+
timestamp: 414315315u64, // Some dummy time
142+
price: 50_000i64, // Some dummy price
137143
};
138144
let secretdata = SecretData {
139145
sks,

bitcoind-tests/tests/test_csfs.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
//! CheckSigFromStack integration tests
44
//!
55
6-
use miniscript::{elements, bitcoin};
6+
use miniscript::extensions::{sighash_msg_price_oracle_1, check_sig_price_oracle_1};
7+
use miniscript::{elements, bitcoin, TxEnv};
78
use elements::pset::PartiallySignedTransaction as Psbt;
89
use elements::sighash::SigHashCache;
910
use elements::taproot::{LeafVersion, TapLeafHash};
@@ -178,16 +179,48 @@ pub fn test_desc_satisfy(cl: &ElementsD, testdata: &TestData, desc: &str) -> Vec
178179
let sig = secp.sign_schnorr_with_aux_rand(&msg, keypair, &aux_rand);
179180
Some(sig)
180181
}
182+
183+
fn lookup_price_oracle_sig(
184+
&self,
185+
pk: &bitcoin::XOnlyPublicKey,
186+
time: u64,
187+
) -> Option<(secp256k1::schnorr::Signature, i64, u64)> {
188+
let xpk = pk.to_x_only_pubkey();
189+
let known_xpks = &self.0.pubdata.x_only_pks;
190+
let i = known_xpks.iter().position(|&x| x == xpk).unwrap();
191+
192+
// select a time ahead of the current test time
193+
let time_signed = time + 1;
194+
let price = self.0.pubdata.price as u64;
195+
let sighash_msg = sighash_msg_price_oracle_1(time_signed, price);
196+
let keypair = &self.0.secretdata.x_only_keypairs[i];
197+
let mut aux_rand = [0u8; 32];
198+
rand::thread_rng().fill_bytes(&mut aux_rand);
199+
200+
let secp = secp256k1::Secp256k1::new();
201+
let sig = secp.sign_schnorr_with_aux_rand(&sighash_msg, keypair, &aux_rand);
202+
assert!(check_sig_price_oracle_1(&secp, &sig, &xpk, time_signed, price));
203+
Some((sig, self.0.pubdata.price, time_signed))
204+
}
181205
}
182206

183207
let psbt_sat = PsbtInputSatisfier::new(&psbt, 0);
184208
let csfs_sat = CsfsSatisfier(&testdata);
185209

186210
let mut tx = psbt.extract_tx().unwrap();
211+
let txouts = vec![psbt.inputs()[0].witness_utxo.clone().unwrap()];
212+
let extracted_tx = tx.clone(); // Possible to optimize this, but we don't care for this
213+
// Env requires reference of tx, while satisfaction requires mutable access to inputs.
214+
let cov_sat = TxEnv::new(&extracted_tx, &txouts, 0).unwrap();
187215
derived_desc
188-
.satisfy(&mut tx.input[0], (psbt_sat, csfs_sat))
216+
.satisfy(&mut tx.input[0], (psbt_sat, csfs_sat, cov_sat))
189217
.expect("Satisfaction error");
190218

219+
for wit in tx.input[0].witness.script_witness.iter() {
220+
println!("Witness: {} {:x?}", wit.len(), wit);
221+
}
222+
223+
191224
// Send the transactions to bitcoin node for mining.
192225
// Regtest mode has standardness checks
193226
// Check whether the node accepts the transactions
@@ -222,6 +255,19 @@ fn test_descs(cl: &ElementsD, testdata: &TestData) {
222255
// test combining with other miniscript fragments
223256
let wit = test_desc_satisfy(cl, testdata, "tr(X!,and_b(pk(X2),a:csfs(X1,msg4)))");
224257
assert!(wit.len() == 4);
258+
259+
// test price oracle 1
260+
let price = testdata.pubdata.price;
261+
let wit = test_desc_satisfy(cl, testdata, &format!("tr(X!,and_v(v:pk(X2),num64_eq(price_oracle1(X1,123213),{})))", price));
262+
assert_eq!(wit.len(), 4 + 2); // 4 witness elements + 1 for price oracle + 1 for time
263+
264+
// More complex tests
265+
test_desc_satisfy(cl, testdata, "tr(X!,and_v(v:pk(X1),num64_eq(price_oracle1(X2,1),price_oracle1_w(X3,2))))");
266+
test_desc_satisfy(cl, testdata, &format!("tr(X!,and_v(v:pk(X1),num64_eq({},price_oracle1_w(X3,2))))", price));
267+
// Different keys and different times
268+
test_desc_satisfy(cl, testdata, "tr(X!,and_v(v:pk(X2),num64_eq(price_oracle1(X3,1),price_oracle1_w(X4,23))))");
269+
// Combination with other arith fragments
270+
test_desc_satisfy(cl, testdata, "tr(X!,and_v(v:pk(X2),num64_eq(div(add(price_oracle1(X3,1),price_oracle1_w(X4,2)),2),price_oracle1_w(X5,2))))");
225271
}
226272

227273
#[test]

doc/extension_spec.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,26 @@ mod(x,y) | `[X] [Y] DIV64 <1> EQUALVERIFY DROP`
4747
bitand(x,y) | `[X] [Y] AND`
4848
bitor(x,y) | `[X] [Y] OR (cannot fail)`
4949
bitxor(x,y) | `[X] [Y] XOR (cannot fail)`
50-
50+
price_oracle1(K,T) | `2DUP TOALTSTACK <T> OP_GREATERTHANEQ VERIFY CAT SHA256 <K> CHECKSIGFROMSTACKVERIFY OP_FROMATLSTACK`
51+
price_oracle1_w(K,T) | `TOALTSTACK 2DUP TOALTSTACK <T> OP_GREATERTHANEQ VERIFY CAT SHA256 <K> CHECKSIGFROMSTACKVERIFY OP_FROMATLSTACK FROMALTSTACK SWAP`
5152

5253
- The division operation pushes the quotient(a//b) such that the remainder a%b (must be non-negative and less than |b|).
5354
- neg(a) returns -a, whereas bitinv(a) returns ~a.
54-
55+
- `price_oracle1(K,T)` pushes a 64 bit LE integer(price) of signed with key K. It checks whether the price is signed
56+
with at a timestamp greater than T. Roughly spea
57+
- K can be any `KEY` expression in descriptor format, but it not allowed to be uncompressed key.
58+
- T is a 64 byte LE UXIX timestamp.
59+
- `1` is the version of the oracle. There can be multiple versions of the
60+
oracle with different fragments. `price_oracle1` creates a schnorr signature with given key `K` on a message that is
61+
computed as: `sha256(T1||K)`
62+
- The fragment consumes three inputs from stack top: [`signature`, `timestamp`, `price`] where `price` is the
63+
stack top.
64+
- `price_oracle1_w` must be used when the price_oracle is not the first leaf fragment. When price_oracle is the first
65+
argument in fragment, use `price_oracle1`. For example,
66+
- `num64_eq(price_oracle1_(K,T),10))` is valid, but `num64_eq(10,price_oracle1_(K,T))` is not.
67+
- `num64_eq(price_oracle1_w(K,T),10))` is not valid, but `num64_eq(10,price_oracle1_w(K,T))` is also valid.
68+
- `num64_eq(add(10,price_oracle1(K,T)),price_oracle1_w(K,T))` is not valid because `10` is the first leaf terminal.
69+
- `num64_eq(add(price_oracle1(K,T),10),price_oracle1_w(K,T))` is valid because `price_oracle1` is the first leaf terminal.
5570
## Comparison extensions
5671

5772
As mentioned earlier, `NumExpr` directly does not fit in the miniscript model as it pushes a 8 byte computation result.

0 commit comments

Comments
 (0)