Skip to content

Commit 5e533b7

Browse files
committed
Add price_oracle1 and price_oracle1_w
Arith becomes ArithInner. ArithInner does not check any rules on placement on price_oracle1 or price_oracle1_w. Arith is the checked version that we use the API. Even though it is ugly for the user to write corresponding oracle1 and oracle1_w versions, atleast the type system will test if it is incorrect
1 parent b720130 commit 5e533b7

File tree

7 files changed

+582
-113
lines changed

7 files changed

+582
-113
lines changed

bitcoind-tests/tests/setup/test_util.rs

+6
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

+48-2
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

+11-5
Original file line numberDiff line numberDiff line change
@@ -47,20 +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-
price_oracle_1(K,T) | `2DUP TOALTSTACK <T> OP_GREATERTHANEQ VERIFY CAT SHA256 <K> CHECKSIGFROMSTACKVERIFY OP_FROMATLSTACK`
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-
- `price_oracle_1(K,T)` pushes a 64 bit LE integer(price) of signed with key K. It checks whether the price is signed
55+
- `price_oracle1(K,T)` pushes a 64 bit LE integer(price) of signed with key K. It checks whether the price is signed
5556
with at a timestamp greater than T. Roughly spea
5657
- K can be any `KEY` expression in descriptor format, but it not allowed to be uncompressed key.
5758
- T is a 64 byte LE UXIX timestamp.
58-
- `_1` is the version of the oracle. There can be multiple versions of the
59-
oracle with different fragments. `price_oracle_1` creates a schnorr signature with given key `K` on a message that is
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
6061
computed as: `sha256(T1||K)`
6162
- The fragment consumes three inputs from stack top: [`signature`, `timestamp`, `price`] where `price` is the
6263
stack top.
63-
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.
6470
## Comparison extensions
6571

6672
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)