mirror of
https://github.com/danog/toncontest.git
synced 2024-12-02 09:27:47 +01:00
Move
This commit is contained in:
parent
78626adacc
commit
5fe76ed22b
@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
// Minimum signatures to send order
|
|
||||||
3 : k
|
|
||||||
// Maximum signatures per order
|
|
||||||
10 : n
|
|
||||||
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
"TonUtil.fif" include
|
|
||||||
|
|
||||||
{ ."usage: " @' $0 type ." <privkey>" cr
|
|
||||||
."Create public key files from private keys; if <privkey> doesn't exist, it will be created." cr cr
|
|
||||||
1 halt
|
|
||||||
} : usage
|
|
||||||
$# 1 < ' usage if
|
|
||||||
|
|
||||||
$1 +".pk" load-generate-keypair drop
|
|
||||||
$1 +".pubkey" B>file
|
|
||||||
|
|
||||||
|
|
||||||
."Wrote private key to " $1 +".pk" type cr
|
|
||||||
."Wrote public key to " $1 +".pubkey" type cr
|
|
@ -1,163 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
if (!\file_exists('db.json')) {
|
|
||||||
$db = [
|
|
||||||
'seqno' => 0,
|
|
||||||
'minSig' => 3,
|
|
||||||
'keys' => [
|
|
||||||
'key1'
|
|
||||||
],
|
|
||||||
'messages' => [
|
|
||||||
'hash' => [
|
|
||||||
'expires' => 100,
|
|
||||||
'signatures' => [
|
|
||||||
0 => 'sig' // Generated from key1
|
|
||||||
],
|
|
||||||
'seqno' => 0,
|
|
||||||
'body' => 'body'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$db = \json_decode(\file_get_contents('db.json'), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeDb(array $db)
|
|
||||||
{
|
|
||||||
\file_put_contents('db.json', \json_encode($db));
|
|
||||||
}
|
|
||||||
|
|
||||||
function garbageCollect(array $db): array
|
|
||||||
{
|
|
||||||
$changed = false;
|
|
||||||
foreach ($db['messages'] as $hash => $message) {
|
|
||||||
if ($message['expires'] < \time()) {
|
|
||||||
unset($db['messages'][$hash]);
|
|
||||||
$changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [$changed, $db];
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasKey(string $key, array $db)
|
|
||||||
{
|
|
||||||
foreach ($db['keys'] as $id => $curKey) {
|
|
||||||
if ($curKey === $key) {
|
|
||||||
return $id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$message = \json_decode(\file_get_contents('php://input'));
|
|
||||||
|
|
||||||
$op = $message['op'];
|
|
||||||
$body = $message['body'];
|
|
||||||
if ($op === 11) {
|
|
||||||
return $db['seqno'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($op === 12) {
|
|
||||||
return hasKey($body, $db);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($op === 0) {
|
|
||||||
//list($shouldUpdate, $db) = garbageCollect($db);
|
|
||||||
|
|
||||||
$signatures = $body['signatures'];
|
|
||||||
if (empty($signatures)) {
|
|
||||||
//if ($shouldUpdate) {
|
|
||||||
// storeDb($db);
|
|
||||||
//}
|
|
||||||
return 'not enough signatures';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($body['expires'] < \time()) {
|
|
||||||
//if ($shouldUpdate) {
|
|
||||||
// storeDb($db);
|
|
||||||
//}
|
|
||||||
return 'message expired';
|
|
||||||
}
|
|
||||||
$hash = \hash('sha256', \json_encode($body));
|
|
||||||
|
|
||||||
if ($db['seqno'] === $body['seqno']) {
|
|
||||||
$db['seqno']++;
|
|
||||||
$oldSignatures = [];
|
|
||||||
// Assuming there are no signatures stored
|
|
||||||
} elseif (!isset($db['messages'][$hash])) {
|
|
||||||
return 'wrong seqno or no such message';
|
|
||||||
} else {
|
|
||||||
$oldSignatures = $db['messages'][$hash]['signatures'];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($signatures as $idx => $signature) {
|
|
||||||
$key = $db['keys'][$idx];
|
|
||||||
if (!checkSig($signature, $key)) {
|
|
||||||
return 'wrong sig';
|
|
||||||
}
|
|
||||||
|
|
||||||
$oldSignatures[$idx] = $signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
$count = 0;
|
|
||||||
foreach ($oldSignatures as $sig) {
|
|
||||||
if (++$count >= 3) {
|
|
||||||
sendMessage($body);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($count < 3) {
|
|
||||||
$db['messages'][$hash] = [
|
|
||||||
'expires' => $body['expires'],
|
|
||||||
'signatures' => $oldSignatures,
|
|
||||||
'body' => $body
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
storeDb(garbageCollect($db));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$keys = [
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
8,
|
|
||||||
9,
|
|
||||||
];
|
|
||||||
|
|
||||||
$n = count($keys);
|
|
||||||
$nCells = $n / 3;
|
|
||||||
|
|
||||||
$cells = [];
|
|
||||||
$cell = [];
|
|
||||||
for ($x = 0; $x < n; $x++) {
|
|
||||||
for ($y = 0; $y < 3; $y++) {
|
|
||||||
$z = $x + $y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($keys as $k => $key) {
|
|
||||||
if ($k && !($k % 3)) {
|
|
||||||
$cells []= $cell;
|
|
||||||
$cell = [];
|
|
||||||
}
|
|
||||||
$cell []= $key;
|
|
||||||
}
|
|
||||||
if ($cell) {
|
|
||||||
$cells []= $cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
$final = [];
|
|
||||||
for ($x = $nRev = count($cells) - 1; $x >= 0; $x--) {
|
|
||||||
if ($nRev > 2 && $x != $nRev && $x > 1) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
// This is just an approximated TL scheme, explaining the message formats used by the multisig wallet.
|
|
||||||
// Multisig wallet constructors
|
|
||||||
|
|
||||||
// 256 bits
|
|
||||||
pubKey$_ k:bits256 = PubKey;
|
|
||||||
|
|
||||||
// 512 bits
|
|
||||||
signature$_ R:bits256 s:bits256 = Signature;
|
|
||||||
|
|
||||||
// Message + send ModeMessage
|
|
||||||
modeMessage$_ mode:uint8 body:^(Message X) = ModeMessage X;
|
|
||||||
|
|
||||||
// Actual multisigned message
|
|
||||||
wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
|
||||||
|
|
||||||
// key ID => signature
|
|
||||||
multiSigWrapper$0 signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
|
||||||
//multiSigFuture$1 = MultiSigWrapper X;
|
|
||||||
|
|
||||||
// For internal storage, no constructor ID
|
|
||||||
multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
|
||||||
|
|
||||||
|
|
||||||
pubKeys1$_ key1:PubKey = PubKeys;
|
|
||||||
pubKeys2$_ key1:PubKey key2:PubKey = PubKeys;
|
|
||||||
pubKeys3$_ key1:PubKey key2:PubKey key3:PubKey = PubKeys;
|
|
||||||
|
|
||||||
pubKeys3and$_ key1:PubKey key2:PubKey key3:PubKey _:^PubKeys = PubKeys;
|
|
||||||
|
|
||||||
// Not doing explicit versioning here, since the structure of the storage can only change after an update of the onchain wallet code
|
|
||||||
// Max 10 keys (not sure about this, the contest instruction file says 10 in one place and 100 in another, so I chose 10 => 16 => 2^(4) )
|
|
||||||
//
|
|
||||||
// Keys will be deserialized to a simple tuple, using the correct PubKeys constructor depending on the number n of keys left
|
|
||||||
//
|
|
||||||
// The pubKeys3and constructor is used when 10+ keys have to be stored
|
|
||||||
// in which case the last pubKeys3 constructor will contain a references to another PubKeys constructor,
|
|
||||||
// leaving one reference free in the storage cell to store the messages hashmap.
|
|
||||||
//
|
|
||||||
storage$_ seqno:uint32 minSigs:(## 4) n:(## 4) keys:[ ^PubKeys ] messages:(HashmapE 256 ^(MultiSigWrapperStorage X))
|
|
||||||
{ minSigs > 0 } { n >= minSigs } { n <= 10 } { minSigs <= 10 } = Storage X;
|
|
||||||
|
|
||||||
// TON stuff
|
|
||||||
|
|
||||||
unit$_ = Unit;
|
|
||||||
true$_ = True;
|
|
||||||
// EMPTY False;
|
|
||||||
bool_false$0 = Bool;
|
|
||||||
bool_true$1 = Bool;
|
|
||||||
bool_false$0 = BoolFalse;
|
|
||||||
bool_true$1 = BoolTrue;
|
|
||||||
nothing$0 {X:Type} = Maybe X;
|
|
||||||
just$1 {X:Type} value:X = Maybe X;
|
|
||||||
left$0 {X:Type} {Y:Type} value:X = Either X Y;
|
|
||||||
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
|
|
||||||
pair$_ {X:Type} {Y:Type} first:X second:Y = Both X Y;
|
|
||||||
|
|
||||||
bit$_ (## 1) = Bit;
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* FROM hashmap.tlb
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// ordinary Hashmap / HashmapE, with fixed length keys
|
|
||||||
//
|
|
||||||
hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
|
|
||||||
{n = (~m) + l} node:(HashmapNode m X) = Hashmap n X;
|
|
||||||
|
|
||||||
hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X;
|
|
||||||
hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X)
|
|
||||||
right:^(Hashmap n X) = HashmapNode (n + 1) X;
|
|
||||||
|
|
||||||
hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m;
|
|
||||||
hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m;
|
|
||||||
hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m;
|
|
||||||
|
|
||||||
unary_zero$0 = Unary ~0;
|
|
||||||
unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1);
|
|
||||||
|
|
||||||
hme_empty$0 {n:#} {X:Type} = HashmapE n X;
|
|
||||||
hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X;
|
|
||||||
|
|
||||||
extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32))
|
|
||||||
= ExtraCurrencyCollection;
|
|
||||||
// true#_ = True;
|
|
||||||
_ {n:#} _:(Hashmap n True) = BitstringSet n;
|
|
||||||
|
|
||||||
// HashmapAug, hashmap with an extra value
|
|
||||||
// (augmentation) of type Y at every node
|
|
||||||
//
|
|
||||||
ahm_edge#_ {n:#} {X:Type} {Y:Type} {l:#} {m:#}
|
|
||||||
label:(HmLabel ~l n) {n = (~m) + l}
|
|
||||||
node:(HashmapAugNode m X Y) = HashmapAug n X Y;
|
|
||||||
ahmn_leaf#_ {X:Type} {Y:Type} extra:Y value:X = HashmapAugNode 0 X Y;
|
|
||||||
ahmn_fork#_ {n:#} {X:Type} {Y:Type} left:^(HashmapAug n X Y)
|
|
||||||
right:^(HashmapAug n X Y) extra:Y = HashmapAugNode (n + 1) X Y;
|
|
||||||
|
|
||||||
ahme_empty$0 {n:#} {X:Type} {Y:Type} extra:Y
|
|
||||||
= HashmapAugE n X Y;
|
|
||||||
ahme_root$1 {n:#} {X:Type} {Y:Type} root:^(HashmapAug n X Y)
|
|
||||||
extra:Y = HashmapAugE n X Y;
|
|
||||||
|
|
||||||
// VarHashmap / VarHashmapE, with variable-length keys
|
|
||||||
//
|
|
||||||
vhm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
|
|
||||||
{n = (~m) + l} node:(VarHashmapNode m X)
|
|
||||||
= VarHashmap n X;
|
|
||||||
vhmn_leaf$00 {n:#} {X:Type} value:X = VarHashmapNode n X;
|
|
||||||
vhmn_fork$01 {n:#} {X:Type} left:^(VarHashmap n X)
|
|
||||||
right:^(VarHashmap n X) value:(Maybe X)
|
|
||||||
= VarHashmapNode (n + 1) X;
|
|
||||||
vhmn_cont$1 {n:#} {X:Type} branch:Bit child:^(VarHashmap n X)
|
|
||||||
value:X = VarHashmapNode (n + 1) X;
|
|
||||||
|
|
||||||
// nothing$0 {X:Type} = Maybe X;
|
|
||||||
// just$1 {X:Type} value:X = Maybe X;
|
|
||||||
|
|
||||||
vhme_empty$0 {n:#} {X:Type} = VarHashmapE n X;
|
|
||||||
vhme_root$1 {n:#} {X:Type} root:^(VarHashmap n X)
|
|
||||||
= VarHashmapE n X;
|
|
||||||
|
|
||||||
//
|
|
||||||
// PfxHashmap / PfxHashmapE, with variable-length keys
|
|
||||||
// constituting a prefix code
|
|
||||||
//
|
|
||||||
|
|
||||||
phm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
|
|
||||||
{n = (~m) + l} node:(PfxHashmapNode m X)
|
|
||||||
= PfxHashmap n X;
|
|
||||||
|
|
||||||
phmn_leaf$0 {n:#} {X:Type} value:X = PfxHashmapNode n X;
|
|
||||||
phmn_fork$1 {n:#} {X:Type} left:^(PfxHashmap n X)
|
|
||||||
right:^(PfxHashmap n X) = PfxHashmapNode (n + 1) X;
|
|
||||||
|
|
||||||
phme_empty$0 {n:#} {X:Type} = PfxHashmapE n X;
|
|
||||||
phme_root$1 {n:#} {X:Type} root:^(PfxHashmap n X)
|
|
||||||
= PfxHashmapE n X;
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* END hashmap.tlb
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TON messages
|
|
||||||
|
|
||||||
|
|
||||||
addr_none$00 = MsgAddressExt;
|
|
||||||
addr_extern$01 len:(## 9) external_address:(bits len)
|
|
||||||
= MsgAddressExt;
|
|
||||||
anycast_info$_ depth:(#<= 30) { depth >= 1 }
|
|
||||||
rewrite_pfx:(bits depth) = Anycast;
|
|
||||||
addr_std$10 anycast:(Maybe Anycast)
|
|
||||||
workchain_id:int8 address:bits256 = MsgAddressInt;
|
|
||||||
addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
|
|
||||||
workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
|
|
||||||
_ _:MsgAddressInt = MsgAddress;
|
|
||||||
_ _:MsgAddressExt = MsgAddress;
|
|
||||||
//
|
|
||||||
var_uint$_ {n:#} len:(#< n) value:(uint (len * 8))
|
|
||||||
= VarUInteger n;
|
|
||||||
var_int$_ {n:#} len:(#< n) value:(int (len * 8))
|
|
||||||
= VarInteger n;
|
|
||||||
nanograms$_ amount:(VarUInteger 16) = Grams;
|
|
||||||
//
|
|
||||||
extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32))
|
|
||||||
= ExtraCurrencyCollection;
|
|
||||||
currencies$_ grams:Grams other:ExtraCurrencyCollection
|
|
||||||
= CurrencyCollection;
|
|
||||||
//
|
|
||||||
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
|
|
||||||
src:MsgAddressInt dest:MsgAddressInt
|
|
||||||
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
|
|
||||||
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
|
|
||||||
ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
|
|
||||||
import_fee:Grams = CommonMsgInfo;
|
|
||||||
ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt
|
|
||||||
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
|
|
||||||
|
|
||||||
|
|
||||||
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
|
|
||||||
src:MsgAddress dest:MsgAddressInt
|
|
||||||
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
|
|
||||||
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
|
|
||||||
ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt
|
|
||||||
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
|
|
||||||
|
|
||||||
tick_tock$_ tick:Bool tock:Bool = TickTock;
|
|
||||||
|
|
||||||
_ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
|
|
||||||
code:(Maybe ^Cell) data:(Maybe ^Cell)
|
|
||||||
library:(HashmapE 256 SimpleLib) = StateInit;
|
|
||||||
|
|
||||||
simple_lib$_ public:Bool root:^Cell = SimpleLib;
|
|
||||||
|
|
||||||
// create a message
|
|
||||||
message$_ {X:Type} info:CommonMsgInfo
|
|
||||||
init:(Maybe (Either StateInit ^StateInit))
|
|
||||||
body:(Either X ^X) = Message X;
|
|
||||||
|
|
@ -1,250 +0,0 @@
|
|||||||
;; Multisig wallet smart contract
|
|
||||||
|
|
||||||
cell preload_refidx(slice s, int index) asm "PLDREFVAR";
|
|
||||||
|
|
||||||
;; Tuple manipulation primitives for integers
|
|
||||||
tuple tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
|
|
||||||
(tuple, ()) ~tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
|
|
||||||
|
|
||||||
;; Tuple manipulation primitives for cells
|
|
||||||
tuple tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
|
|
||||||
(tuple, ()) ~tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
|
|
||||||
|
|
||||||
int tuple_len(tuple t) asm "TLEN";
|
|
||||||
|
|
||||||
;; Cleanup expired partial orders
|
|
||||||
;; messages
|
|
||||||
(cell, ()) ~collect_garbage(cell messages) {
|
|
||||||
var hash = -1;
|
|
||||||
do {
|
|
||||||
(hash, var cs, var ok) = messages.udict_get_next?(256, hash);
|
|
||||||
if (ok) {
|
|
||||||
|
|
||||||
;; modeMessage$_ mode:uint8 body:^(Message X) = ModeMessage X;
|
|
||||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
|
||||||
;; multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
|
||||||
;;
|
|
||||||
;; Skip signatures, check expiry
|
|
||||||
;;
|
|
||||||
;; expiry <= now
|
|
||||||
if (cs.skip_dict().preload_uint(32) <= now()) {
|
|
||||||
messages~udict_delete?(256, hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} until (~ ok);
|
|
||||||
|
|
||||||
return (messages, ());
|
|
||||||
}
|
|
||||||
|
|
||||||
;; messages
|
|
||||||
(slice, (tuple, tuple)) ~load_keys(slice messages) {
|
|
||||||
int length = messages~load_uint(4);
|
|
||||||
int index = 0;
|
|
||||||
var keys = nil;
|
|
||||||
var keys_cells = nil;
|
|
||||||
var keys_cell = messages~load_ref();
|
|
||||||
keys_cells~tuple_setcell(index / 3, keys_cell);
|
|
||||||
var keys_slice = keys_cell.begin_parse();
|
|
||||||
do {
|
|
||||||
if ((index % 3 == 0)) {
|
|
||||||
if ((index < 9) & (index > 0)) {
|
|
||||||
keys_slice.end_parse();
|
|
||||||
var keys_cell = messages~load_ref();
|
|
||||||
keys_cells~tuple_setcell(index / 3, keys_cell);
|
|
||||||
var keys_slice = keys_cell.begin_parse();
|
|
||||||
} else {
|
|
||||||
var newkeys_slice = keys_slice.preload_ref().begin_parse();
|
|
||||||
keys_slice.end_parse();
|
|
||||||
keys_slice = newkeys_slice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int key = keys_slice.preload_uint(256);
|
|
||||||
|
|
||||||
keys~tuple_set(index, key);
|
|
||||||
index += 1;
|
|
||||||
} until (index == length);
|
|
||||||
keys_slice.end_parse();
|
|
||||||
|
|
||||||
return (messages, (keys, keys_cells));
|
|
||||||
}
|
|
||||||
;; messages
|
|
||||||
tuple preload_keys(slice messages) {
|
|
||||||
int length = messages~load_uint(4);
|
|
||||||
int index = 0;
|
|
||||||
var keys = nil;
|
|
||||||
var keys_slice = messages~load_ref().begin_parse();
|
|
||||||
do {
|
|
||||||
if ((index % 3 == 0)) {
|
|
||||||
if ((index < 9) & (index > 0)) {
|
|
||||||
var keys_slice = messages~load_ref().begin_parse();
|
|
||||||
} else {
|
|
||||||
var keys_slice = keys_slice.preload_ref().begin_parse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int key = keys_slice~load_uint(256);
|
|
||||||
keys~tuple_set(index, key);
|
|
||||||
index += 1;
|
|
||||||
} until (index == length);
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
slice skip_keys(slice messages) {
|
|
||||||
int length = messages~load_uint(4);
|
|
||||||
length = min(length / 3, 3);
|
|
||||||
int index = 0;
|
|
||||||
do {
|
|
||||||
messages~load_ref();
|
|
||||||
} until (index == length);
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
builder store_keys(builder b, int length, var keys) {
|
|
||||||
b~store_uint(4, length);
|
|
||||||
|
|
||||||
length = keys.tuple_len();
|
|
||||||
int index = 0;
|
|
||||||
do {
|
|
||||||
b.store_ref(keys.cell_at(index));
|
|
||||||
index += 1;
|
|
||||||
} until (index == length);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
() store_db(int seqno, cell keys, cell messages) {
|
|
||||||
set_data(begin_cell().store_uint(seqno, 32).store_dict(keys).store_dict(messages).end_cell());
|
|
||||||
}
|
|
||||||
|
|
||||||
() recv_internal(slice in_msg) impure {
|
|
||||||
;; do nothing for internal messages
|
|
||||||
}
|
|
||||||
|
|
||||||
;; multiSigWrapper$0 keys_signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
|
||||||
() recv_external(slice in_msg) impure {
|
|
||||||
;; Check if multiSigWrapper$0 or future unsupported protocol
|
|
||||||
throw_unless(32, in_msg~load_uint(1));
|
|
||||||
|
|
||||||
;; Check if is hme_empty$0 or hme_root$1
|
|
||||||
;; Throw if empty signature list
|
|
||||||
throw_unless(33, in_msg.preload_uint(1));
|
|
||||||
|
|
||||||
var signatures = in_msg~load_dict();
|
|
||||||
slice message_data = in_msg;
|
|
||||||
|
|
||||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:^(Message X) = WrappedMessage X;
|
|
||||||
(int expires_at, int msg_seqno) = (message_data~load_uint(32), message_data~load_uint(32));
|
|
||||||
;; Message expired
|
|
||||||
throw_if(34, expires_at <= now());
|
|
||||||
|
|
||||||
;; We will need the hash anyway
|
|
||||||
int hash = slice_hash(in_msg);
|
|
||||||
|
|
||||||
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 ^PubKey) messages:(HashmapE 256 ^(StoredMessage X)) = Storage X;
|
|
||||||
slice stored_data = get_data().begin_parse();
|
|
||||||
(int stored_seqno, int min_sigs, (var keys, var kcells), var messages) = (stored_data~load_uint(32), stored_data~load_uint(4), stored_data~load_keys(), stored_data~load_dict());
|
|
||||||
stored_data.end_parse();
|
|
||||||
|
|
||||||
;; This is a new message, so there will be no stored messages
|
|
||||||
var storedMessageSignatures = new_dict();
|
|
||||||
;; If new message, increase seqno
|
|
||||||
if (stored_seqno == msg_seqno) {
|
|
||||||
stored_seqno += 1;
|
|
||||||
|
|
||||||
;; If old message
|
|
||||||
} else {
|
|
||||||
var (storedMessage, ok) = messages.udict_get?(256, hash);
|
|
||||||
;; Throw if old message and doesn't exist in db
|
|
||||||
throw_unless(35, ok);
|
|
||||||
|
|
||||||
;; multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
|
||||||
;;
|
|
||||||
;; Load signatures
|
|
||||||
var storedMessageSignatures = storedMessage~load_ref().begin_parse().preload_dict();
|
|
||||||
storedMessage.end_parse();
|
|
||||||
}
|
|
||||||
|
|
||||||
accept_message();
|
|
||||||
message_data~touch();
|
|
||||||
|
|
||||||
var idx = -1;
|
|
||||||
do {
|
|
||||||
(idx, var signature, var ok) = signatures.udict_get_next?(4, idx);
|
|
||||||
if (ok) {
|
|
||||||
var public_key = keys.int_at(idx);
|
|
||||||
;;throw_unless(at(at(36, kok);
|
|
||||||
|
|
||||||
var signature_cell = signature~load_ref();
|
|
||||||
var signature_slice = signature_cell.begin_parse();
|
|
||||||
var slice_copy = signature_slice;
|
|
||||||
|
|
||||||
signature_slice.end_parse();
|
|
||||||
|
|
||||||
throw_unless(37, check_signature(hash, slice_copy, public_key));
|
|
||||||
|
|
||||||
storedMessageSignatures~udict_set_ref(4, idx, signature_cell);
|
|
||||||
}
|
|
||||||
} until (~ ok);
|
|
||||||
|
|
||||||
var (mode, message) = (message_data~load_uint(8), message_data~load_ref());
|
|
||||||
message_data.end_parse();
|
|
||||||
|
|
||||||
var count = 0;
|
|
||||||
var sent = 0;
|
|
||||||
var idx = -1;
|
|
||||||
do {
|
|
||||||
(idx, var signature, var ok) = storedMessageSignatures.udict_get_next?(4, idx);
|
|
||||||
if (ok) {
|
|
||||||
count += 1;
|
|
||||||
if (count >= min_sigs) {
|
|
||||||
send_raw_message(message, mode);
|
|
||||||
messages~udict_delete?(256, hash);
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} until (~ ok);
|
|
||||||
|
|
||||||
;; modeMessage$_ mode:uint8 body:^(Message X) = ModeMessage X;
|
|
||||||
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
|
||||||
;; multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
|
||||||
;;
|
|
||||||
if (count < min_sigs) {
|
|
||||||
messages~udict_set_ref(256, hash, begin_cell().store_dict(storedMessageSignatures).store_uint(expires_at, 32).store_uint(msg_seqno, 32).store_uint(mode, 8).store_ref(message).end_cell());
|
|
||||||
}
|
|
||||||
messages~collect_garbage();
|
|
||||||
|
|
||||||
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 ^PubKey) messages:(HashmapE 256 ^(Message X)) = Storage X;
|
|
||||||
set_data(begin_cell().store_uint(stored_seqno + 1, 32).store_uint(min_sigs, 4).store_keys(keys.tuple_len(), kcells).store_dict(messages).end_cell());
|
|
||||||
}
|
|
||||||
|
|
||||||
;; Get methods
|
|
||||||
|
|
||||||
int seqno() method_id {
|
|
||||||
return get_data().begin_parse().preload_uint(32);
|
|
||||||
}
|
|
||||||
|
|
||||||
(int, int) getKey(int id) method_id {
|
|
||||||
var res = get_data().begin_parse().skip_bits(36).preload_keys().int_at(id);
|
|
||||||
if (res.null?()) {
|
|
||||||
return (0, 0);
|
|
||||||
}
|
|
||||||
return (-1, res);
|
|
||||||
}
|
|
||||||
(int, int) getId(int key) method_id {
|
|
||||||
var keys = get_data().begin_parse().skip_bits(36).preload_keys();
|
|
||||||
int index = 0;
|
|
||||||
int length = keys.tuple_len();
|
|
||||||
do {
|
|
||||||
if (keys.int_at(index) == key) {
|
|
||||||
return (-1, index);
|
|
||||||
}
|
|
||||||
} until (index == length);
|
|
||||||
return (0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
(cell) getPartials() method_id {
|
|
||||||
return get_data().begin_parse().skip_bits(36).skip_keys().preload_dict();
|
|
||||||
}
|
|
||||||
(cell) getPartialsById(int id) method_id {
|
|
||||||
cell messages = get_data().begin_parse().skip_bits(36).skip_keys().preload_dict();
|
|
||||||
cell messages_found = new_dict();
|
|
||||||
return messages_found;
|
|
||||||
}
|
|
@ -1,416 +0,0 @@
|
|||||||
"Asm.fif" include
|
|
||||||
// automatically generated from `/home/daniil/repos/contest/lib/crypto/smartcont/stdlib.fc` `wallet-code.fc`
|
|
||||||
PROGRAM{
|
|
||||||
DECLPROC ~collect_garbage
|
|
||||||
DECLPROC ~load_keys
|
|
||||||
DECLPROC preload_keys
|
|
||||||
DECLPROC skip_keys
|
|
||||||
DECLPROC store_keys
|
|
||||||
DECLPROC store_db
|
|
||||||
DECLPROC recv_internal
|
|
||||||
DECLPROC recv_external
|
|
||||||
85143 DECLMETHOD seqno
|
|
||||||
119659 DECLMETHOD getKey
|
|
||||||
66593 DECLMETHOD getId
|
|
||||||
124239 DECLMETHOD getPartials
|
|
||||||
87848 DECLMETHOD getPartialsById
|
|
||||||
~collect_garbage PROC:<{
|
|
||||||
-1 PUSHINT
|
|
||||||
UNTIL:<{
|
|
||||||
OVER
|
|
||||||
8 PUSHPOW2
|
|
||||||
DICTUGETNEXT
|
|
||||||
NULLSWAPIFNOT
|
|
||||||
NULLSWAPIFNOT
|
|
||||||
DUP
|
|
||||||
IF:<{
|
|
||||||
s0 s2 XCHG
|
|
||||||
SKIPDICT
|
|
||||||
32 PLDU
|
|
||||||
NOW
|
|
||||||
LEQ
|
|
||||||
IF:<{
|
|
||||||
8 PUSHPOW2
|
|
||||||
s1 s3 s3 PUXC2
|
|
||||||
DICTUDEL
|
|
||||||
DROP
|
|
||||||
}>ELSE<{
|
|
||||||
s0 s2 XCHG
|
|
||||||
}>
|
|
||||||
}>ELSE<{
|
|
||||||
2SWAP
|
|
||||||
DROP
|
|
||||||
}>
|
|
||||||
SWAP
|
|
||||||
NOT
|
|
||||||
s1 s2 XCHG
|
|
||||||
}>
|
|
||||||
DROP
|
|
||||||
}>
|
|
||||||
~load_keys PROC:<{
|
|
||||||
4 LDU
|
|
||||||
0 PUSHINT
|
|
||||||
PUSHNULL
|
|
||||||
PUSHNULL
|
|
||||||
s0 s3 XCHG
|
|
||||||
LDREF
|
|
||||||
s4 s1 XCPU
|
|
||||||
0 PUSHINT
|
|
||||||
SETINDEXVARQ
|
|
||||||
SWAP
|
|
||||||
CTOS
|
|
||||||
UNTIL:<{
|
|
||||||
s3 PUSH
|
|
||||||
3 PUSHINT
|
|
||||||
MOD
|
|
||||||
0 EQINT
|
|
||||||
IF:<{
|
|
||||||
s3 PUSH
|
|
||||||
9 LESSINT
|
|
||||||
s4 PUSH
|
|
||||||
0 GTINT
|
|
||||||
AND
|
|
||||||
IF:<{
|
|
||||||
DUP
|
|
||||||
ENDS
|
|
||||||
s0 s4 XCHG
|
|
||||||
LDREF
|
|
||||||
s4 PUSH
|
|
||||||
3 PUSHINT
|
|
||||||
DIV
|
|
||||||
s3 s2 s(-1) XCPUXC
|
|
||||||
SETINDEXVARQ
|
|
||||||
NIP
|
|
||||||
}>ELSE<{
|
|
||||||
DUP
|
|
||||||
PLDREF
|
|
||||||
CTOS
|
|
||||||
SWAP
|
|
||||||
ENDS
|
|
||||||
s4 s4 XCHG2
|
|
||||||
}>
|
|
||||||
}>ELSE<{
|
|
||||||
s4 s4 XCHG2
|
|
||||||
}>
|
|
||||||
s4 PUSH
|
|
||||||
256 PLDU
|
|
||||||
s3 s0 s4 XC2PU
|
|
||||||
SETINDEXVARQ
|
|
||||||
s0 s3 XCHG
|
|
||||||
INC
|
|
||||||
s0 s5 PUSH2
|
|
||||||
EQUAL
|
|
||||||
s5 s4 s0 XCHG3
|
|
||||||
s3 s3 s0 XCHG3
|
|
||||||
}>
|
|
||||||
s3 POP
|
|
||||||
s4 POP
|
|
||||||
SWAP
|
|
||||||
ENDS
|
|
||||||
ROT
|
|
||||||
}>
|
|
||||||
preload_keys PROC:<{
|
|
||||||
4 LDU
|
|
||||||
0 PUSHINT
|
|
||||||
PUSHNULL
|
|
||||||
s0 s2 XCHG
|
|
||||||
LDREF
|
|
||||||
DROP
|
|
||||||
CTOS
|
|
||||||
UNTIL:<{
|
|
||||||
OVER
|
|
||||||
3 PUSHINT
|
|
||||||
MOD
|
|
||||||
0 EQINT
|
|
||||||
IF:<{
|
|
||||||
OVER
|
|
||||||
9 LESSINT
|
|
||||||
s2 PUSH
|
|
||||||
0 GTINT
|
|
||||||
AND
|
|
||||||
IF:<{
|
|
||||||
}>ELSE<{
|
|
||||||
}>
|
|
||||||
}>
|
|
||||||
256 LDU
|
|
||||||
s3 s3 s2 XC2PU
|
|
||||||
SETINDEXVARQ
|
|
||||||
SWAP
|
|
||||||
INC
|
|
||||||
s0 s3 PUSH2
|
|
||||||
EQUAL
|
|
||||||
s1 s3 s0 XCHG3
|
|
||||||
}>
|
|
||||||
s2 s3 XCHG
|
|
||||||
3 BLKDROP
|
|
||||||
}>
|
|
||||||
skip_keys PROC:<{
|
|
||||||
4 LDU
|
|
||||||
SWAP
|
|
||||||
3 PUSHINT
|
|
||||||
DIV
|
|
||||||
3 PUSHINT
|
|
||||||
MIN
|
|
||||||
UNTIL:<{
|
|
||||||
SWAP
|
|
||||||
LDREF
|
|
||||||
NIP
|
|
||||||
OVER
|
|
||||||
0 EQINT
|
|
||||||
s1 s2 XCHG
|
|
||||||
}>
|
|
||||||
DROP
|
|
||||||
}>
|
|
||||||
store_keys PROC:<{
|
|
||||||
4 PUSHINT
|
|
||||||
2SWAP
|
|
||||||
STUX
|
|
||||||
OVER
|
|
||||||
TLEN
|
|
||||||
s2 POP
|
|
||||||
0 PUSHINT
|
|
||||||
UNTIL:<{
|
|
||||||
INC
|
|
||||||
s0 s2 PUSH2
|
|
||||||
EQUAL
|
|
||||||
}>
|
|
||||||
DROP
|
|
||||||
NIP
|
|
||||||
}>
|
|
||||||
store_db PROC:<{
|
|
||||||
NEWC
|
|
||||||
s1 s3 XCHG
|
|
||||||
32 STU
|
|
||||||
STDICT
|
|
||||||
STDICT
|
|
||||||
ENDC
|
|
||||||
c4 POP
|
|
||||||
}>
|
|
||||||
recv_internal PROC:<{
|
|
||||||
DROP
|
|
||||||
}>
|
|
||||||
recv_external PROC:<{
|
|
||||||
1 LDU
|
|
||||||
SWAP
|
|
||||||
32 THROWIFNOT
|
|
||||||
DUP
|
|
||||||
1 PLDU
|
|
||||||
33 THROWIFNOT
|
|
||||||
LDDICT
|
|
||||||
DUP
|
|
||||||
32 LDU
|
|
||||||
32 LDU
|
|
||||||
s2 PUSH
|
|
||||||
NOW
|
|
||||||
LEQ
|
|
||||||
34 THROWIF
|
|
||||||
s0 s3 XCHG
|
|
||||||
HASHSU
|
|
||||||
c4 PUSH
|
|
||||||
CTOS
|
|
||||||
32 LDU
|
|
||||||
4 LDU
|
|
||||||
~load_keys CALLDICT
|
|
||||||
s0 s2 XCHG
|
|
||||||
LDDICT
|
|
||||||
ENDS
|
|
||||||
NEWDICT
|
|
||||||
s5 s7 PUSH2
|
|
||||||
EQUAL
|
|
||||||
IF:<{
|
|
||||||
s0 s5 XCHG
|
|
||||||
INC
|
|
||||||
}>ELSE<{
|
|
||||||
s6 s1 PUSH2
|
|
||||||
8 PUSHPOW2
|
|
||||||
DICTUGET
|
|
||||||
NULLSWAPIFNOT
|
|
||||||
35 THROWIFNOT
|
|
||||||
LDREF
|
|
||||||
NIP
|
|
||||||
ENDS
|
|
||||||
s0 s5 XCHG
|
|
||||||
}>
|
|
||||||
ACCEPT
|
|
||||||
s0 s9 XCHG
|
|
||||||
-1 PUSHINT
|
|
||||||
UNTIL:<{
|
|
||||||
s11 PUSH
|
|
||||||
4 PUSHINT
|
|
||||||
DICTUGETNEXT
|
|
||||||
NULLSWAPIFNOT
|
|
||||||
NULLSWAPIFNOT
|
|
||||||
DUP
|
|
||||||
IF:<{
|
|
||||||
s5 s1 PUSH2
|
|
||||||
INDEXVAR
|
|
||||||
s0 s3 XCHG
|
|
||||||
LDREF
|
|
||||||
DROP
|
|
||||||
DUP
|
|
||||||
CTOS
|
|
||||||
DUP
|
|
||||||
ENDS
|
|
||||||
s11 s0 s4 PUXC2
|
|
||||||
CHKSIGNU
|
|
||||||
37 THROWIFNOT
|
|
||||||
s1 s2 XCHG
|
|
||||||
4 PUSHINT
|
|
||||||
s3 s9 s9 PUXC2
|
|
||||||
DICTUSETREF
|
|
||||||
}>ELSE<{
|
|
||||||
s1 s8 s8 XCHG3
|
|
||||||
DROP
|
|
||||||
}>
|
|
||||||
s0 s7 XCHG
|
|
||||||
NOT
|
|
||||||
}>
|
|
||||||
DROP
|
|
||||||
s10 POP
|
|
||||||
s0 s9 XCHG
|
|
||||||
8 LDU
|
|
||||||
LDREF
|
|
||||||
ENDS
|
|
||||||
0 PUSHINT
|
|
||||||
-1 PUSHINT
|
|
||||||
UNTIL:<{
|
|
||||||
s7 PUSH
|
|
||||||
4 PUSHINT
|
|
||||||
DICTUGETNEXT
|
|
||||||
NULLSWAPIFNOT
|
|
||||||
NULLSWAPIFNOT
|
|
||||||
s2 POP
|
|
||||||
OVER
|
|
||||||
IF:<{
|
|
||||||
s0 s2 XCHG
|
|
||||||
INC
|
|
||||||
s0 s7 PUSH2
|
|
||||||
GEQ
|
|
||||||
IF:<{
|
|
||||||
NIP
|
|
||||||
s2 s3 PUSH2
|
|
||||||
SENDRAWMSG
|
|
||||||
8 PUSHPOW2
|
|
||||||
s9 s13 s13 PUXC2
|
|
||||||
DICTUDEL
|
|
||||||
DROP
|
|
||||||
0 PUSHINT
|
|
||||||
}>ELSE<{
|
|
||||||
s13 s13 XCHG2
|
|
||||||
}>
|
|
||||||
}>ELSE<{
|
|
||||||
s0 s13 s13 XCHG3
|
|
||||||
}>
|
|
||||||
NOT
|
|
||||||
s13 s13 s0 XCHG3
|
|
||||||
}>
|
|
||||||
DROP
|
|
||||||
s5 PUSH
|
|
||||||
LESS
|
|
||||||
IF:<{
|
|
||||||
NEWC
|
|
||||||
s1 s6 XCHG
|
|
||||||
STDICT
|
|
||||||
s1 s8 XCHG
|
|
||||||
32 STU
|
|
||||||
s1 s6 XCHG
|
|
||||||
32 STU
|
|
||||||
s1 s6 XCHG
|
|
||||||
8 STU
|
|
||||||
s1 s2 XCHG
|
|
||||||
STREF
|
|
||||||
ENDC
|
|
||||||
s0 s0 s6 XCHG3
|
|
||||||
8 PUSHPOW2
|
|
||||||
DICTUSETREF
|
|
||||||
}>ELSE<{
|
|
||||||
s4 s10 XCHG
|
|
||||||
s3 s8 XCHG
|
|
||||||
s2 s7 XCHG
|
|
||||||
s4 s6 XCHG
|
|
||||||
6 BLKDROP
|
|
||||||
}>
|
|
||||||
~collect_garbage CALLDICT
|
|
||||||
s0 s3 XCHG
|
|
||||||
INC
|
|
||||||
NEWC
|
|
||||||
32 STU
|
|
||||||
s1 s4 XCHG
|
|
||||||
4 STU
|
|
||||||
s0 s3 XCHG
|
|
||||||
TLEN
|
|
||||||
s2 s3 XCHG
|
|
||||||
SWAP
|
|
||||||
store_keys CALLDICT
|
|
||||||
STDICT
|
|
||||||
ENDC
|
|
||||||
c4 POP
|
|
||||||
}>
|
|
||||||
seqno PROC:<{
|
|
||||||
c4 PUSH
|
|
||||||
CTOS
|
|
||||||
32 PLDU
|
|
||||||
}>
|
|
||||||
getKey PROC:<{
|
|
||||||
c4 PUSH
|
|
||||||
CTOS
|
|
||||||
36 PUSHINT
|
|
||||||
SDSKIPFIRST
|
|
||||||
preload_keys CALLDICT
|
|
||||||
SWAP
|
|
||||||
INDEXVAR
|
|
||||||
DUP
|
|
||||||
ISNULL
|
|
||||||
IFJMP:<{
|
|
||||||
DROP
|
|
||||||
0 PUSHINT
|
|
||||||
DUP
|
|
||||||
}>
|
|
||||||
-1 PUSHINT
|
|
||||||
SWAP
|
|
||||||
}>
|
|
||||||
getId PROC:<{
|
|
||||||
c4 PUSH
|
|
||||||
CTOS
|
|
||||||
36 PUSHINT
|
|
||||||
SDSKIPFIRST
|
|
||||||
preload_keys CALLDICT
|
|
||||||
0 PUSHINT
|
|
||||||
OVER
|
|
||||||
TLEN
|
|
||||||
UNTIL:<{
|
|
||||||
s2 PUSH
|
|
||||||
0 INDEX
|
|
||||||
s4 PUSH
|
|
||||||
EQUAL
|
|
||||||
IFJMP:<{
|
|
||||||
s1 s3 XCHG
|
|
||||||
3 BLKDROP
|
|
||||||
-1 PUSHINT
|
|
||||||
SWAP
|
|
||||||
}>
|
|
||||||
2DUP
|
|
||||||
EQUAL
|
|
||||||
}>
|
|
||||||
4 BLKDROP
|
|
||||||
0 PUSHINT
|
|
||||||
DUP
|
|
||||||
}>
|
|
||||||
getPartials PROC:<{
|
|
||||||
c4 PUSH
|
|
||||||
CTOS
|
|
||||||
36 PUSHINT
|
|
||||||
SDSKIPFIRST
|
|
||||||
skip_keys CALLDICT
|
|
||||||
PLDDICT
|
|
||||||
}>
|
|
||||||
getPartialsById PROC:<{
|
|
||||||
DROP
|
|
||||||
c4 PUSH
|
|
||||||
CTOS
|
|
||||||
36 PUSHINT
|
|
||||||
SDSKIPFIRST
|
|
||||||
skip_keys CALLDICT
|
|
||||||
PLDDICT
|
|
||||||
}>
|
|
||||||
}END>c
|
|
@ -1,159 +0,0 @@
|
|||||||
"TonUtil.fif" include
|
|
||||||
|
|
||||||
' constant : const
|
|
||||||
|
|
||||||
{ dup ."Loading public key from file " type ."..." cr
|
|
||||||
file>B dup Blen 32 <> abort"Public key must be exactly 32 bytes long"
|
|
||||||
256 B>u@
|
|
||||||
} : load-pubkey
|
|
||||||
{ dup ."Loading order from file " type ."..." cr
|
|
||||||
file>B B>boc
|
|
||||||
} : load-boc
|
|
||||||
|
|
||||||
{ ."usage: " @' $0 type ." <workchain-id> <wallet-name> <n> <k> <privkey1> [<pubkey2> ...] [<boc1> <boc2>]" cr cr
|
|
||||||
."Creates a new multisignature wallet in specified workchain composed of <n> (1-10) keys." cr
|
|
||||||
."The first of the keys must be a private key (pre-existing or not), used to generate the wallet; the rest MUST be public keys." cr
|
|
||||||
."Create or generate public key files from private keys using gen-pub.fif privkey" cr cr
|
|
||||||
."Min <k> (1-10) signatures required to send an order; load <n> pre-existing public keys from files <key1...n>." cr
|
|
||||||
."Optionally load a number of pre-generated partially signed orders <boc1...m> to preload into the contract." cr cr
|
|
||||||
1 halt
|
|
||||||
} : usage
|
|
||||||
$# 5 < ' usage if
|
|
||||||
|
|
||||||
$1 parse-workchain-id =: wc // set workchain id from command line argument
|
|
||||||
$2 constant file-base
|
|
||||||
$3 (number) 1 <> abort"<n> must be a number!" constant n
|
|
||||||
$4 (number) 1 <> abort"<k> must be a number!" constant k
|
|
||||||
$# 4 n + - constant m
|
|
||||||
|
|
||||||
n 1 < n 10 > or abort"<n> must be between 1 and 10"
|
|
||||||
k 1 < k 10 > or abort"<k> must be between 1 and 10"
|
|
||||||
k n <= not abort"<k> must smaller than or equal to <n>"
|
|
||||||
|
|
||||||
$# 4 n + < abort"Not enough keys were provided in args!"
|
|
||||||
|
|
||||||
$5 +".pk" load-generate-keypair const privkey 256 B>u@
|
|
||||||
|
|
||||||
6 { dup $() +".pubkey" load-pubkey swap 1+ } n 1- times drop
|
|
||||||
n tuple constant keys
|
|
||||||
|
|
||||||
5 n + { dup $() +".boc" load-boc swap 1+ } m times drop
|
|
||||||
m tuple constant messages
|
|
||||||
|
|
||||||
cr
|
|
||||||
."Creating new advanced wallet in workchain " wc .
|
|
||||||
."with n=" n .
|
|
||||||
."k=" k .
|
|
||||||
."m=" m . ."..." cr cr
|
|
||||||
|
|
||||||
// idict! (v x s n – s0 −1 or s 0), adds a new value v (represented
|
|
||||||
// by a Slice) with key given by signed big-endian n-bit integer x into
|
|
||||||
// dictionary s with n-bit keys, and returns the new dictionary s0 and −1
|
|
||||||
// on success. Otherwise the unchanged dictionary s and 0 are returned.
|
|
||||||
|
|
||||||
// Create dictionaries with keys and messages
|
|
||||||
|
|
||||||
// Keys will be deserialized to a simple tuple, using the correct PubKeys constructor depending on the number n of keys left
|
|
||||||
//
|
|
||||||
// pubKeys1$_ key1:PubKey = PubKeys;
|
|
||||||
// pubKeys2$_ key1:PubKey key2:PubKey = PubKeys;
|
|
||||||
// pubKeys3$_ key1:PubKey key2:PubKey key3:PubKey = PubKeys;
|
|
||||||
// pubKeys3and$_ key1:PubKey key2:PubKey key3:PubKey _:^PubKeys = PubKeys;
|
|
||||||
//
|
|
||||||
// keys:[ ^PubKeys ]
|
|
||||||
|
|
||||||
// First create builders with groups of (at most) 3 keys each
|
|
||||||
<b
|
|
||||||
0 { dup 1+ swap // Create a counter
|
|
||||||
// Get builder b (or create a new one)
|
|
||||||
// if ($k && !($k % 3)) {
|
|
||||||
dup dup 3 mod 0<> not and { <b } { 2 roll } cond
|
|
||||||
|
|
||||||
keys rot [] // Get n-th value x
|
|
||||||
256 // y
|
|
||||||
|
|
||||||
u, // Write uint
|
|
||||||
swap
|
|
||||||
} n times drop
|
|
||||||
|
|
||||||
// Then convert builders into cells, appropriately inserting references for nested pubkeys
|
|
||||||
n 3 /c const nCeil // Number of builders
|
|
||||||
nCeil 1- const nRev // Steps for reverse counter
|
|
||||||
nCeil 3 > const shouldRef // Whether there are more than 3 builders and references must be created
|
|
||||||
nCeil 1+ const nRoll // Steps for roll
|
|
||||||
|
|
||||||
nRev { dup 1- swap // Create a reverse counter
|
|
||||||
rot // Get current builder
|
|
||||||
swap
|
|
||||||
// if ($x != $nRev && $x > 1 && $nCeil > 3)
|
|
||||||
dup nRev <> swap 1 > shouldRef and and { nCeil pick ref, } if
|
|
||||||
|
|
||||||
b> // Close builder, create cell
|
|
||||||
nCeil -roll // Put cell at the back of the stack
|
|
||||||
|
|
||||||
} nCeil times drop
|
|
||||||
|
|
||||||
// Number of cells to store / Number of already referenced cells to delete
|
|
||||||
nCeil 3 > { 3 nCeil 3 - } { nCeil 0 } cond const nRem const nCells
|
|
||||||
|
|
||||||
// Drop all cells we already referenced
|
|
||||||
' drop nRem times
|
|
||||||
|
|
||||||
// Reverse order of cells
|
|
||||||
nCells 0 reverse
|
|
||||||
|
|
||||||
// Create tuple with cells
|
|
||||||
nCells tuple const keyCells
|
|
||||||
|
|
||||||
// messages:(HashmapE 256 ^(MultiSigWrapperStorage X))
|
|
||||||
dictnew
|
|
||||||
0 { dup 1+ swap // Create a counter
|
|
||||||
messages swap [] // Get n-th value v
|
|
||||||
dup <b swap ref, // ~
|
|
||||||
|
|
||||||
swap hashu // Get x
|
|
||||||
|
|
||||||
3 roll // Get dictionary s
|
|
||||||
256 // Get n
|
|
||||||
b>idict!
|
|
||||||
not abort"Failure storing dictionary value!"
|
|
||||||
|
|
||||||
swap
|
|
||||||
} m times drop const messages-dict
|
|
||||||
|
|
||||||
// code
|
|
||||||
"wallet-code.fif" include
|
|
||||||
// data
|
|
||||||
// storage$_ seqno:uint32 minSigs:(## 4) n:(## 4) keys:[ ^PubKeys ] messages:(HashmapE 256 ^(MultiSigWrapperStorage X))
|
|
||||||
// { minSigs > 0 } { n >= minSigs } { n <= 10 } { minSigs <= 10 } = Storage X;
|
|
||||||
<b 0 32 u,
|
|
||||||
k 4 u,
|
|
||||||
nCells 4 u,
|
|
||||||
keyCells explode roll { swap ref, } nCells times
|
|
||||||
messages-dict dict,
|
|
||||||
b>
|
|
||||||
// no libraries
|
|
||||||
null
|
|
||||||
// create StateInit
|
|
||||||
// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
|
|
||||||
// code:(Maybe ^Cell) data:(Maybe ^Cell)
|
|
||||||
// library:(HashmapE 256 SimpleLib) = StateInit;
|
|
||||||
// split_depth 0 special 0 code 1 data 1
|
|
||||||
//
|
|
||||||
<b b{0011} s, 3 roll ref, rot ref, swap dict, b>
|
|
||||||
|
|
||||||
dup ."StateInit: " <s csr. cr
|
|
||||||
dup hash wc swap 2dup 2constant wallet_addr
|
|
||||||
."new wallet address = " 2dup .addr cr
|
|
||||||
2dup file-base +".addr" save-address-verbose
|
|
||||||
."Non-bounceable address (for init): " 2dup 7 .Addr cr
|
|
||||||
."Bounceable address (for later access): " 6 .Addr cr
|
|
||||||
.s
|
|
||||||
<b 0 32 u, -1 32 i, b>
|
|
||||||
dup ."signing message: " <s csr. cr
|
|
||||||
dup hash privkey ed25519_sign_uint rot
|
|
||||||
<b b{1000100} s, wallet_addr addr, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
|
|
||||||
dup ."External message for initialization is " <s csr. cr
|
|
||||||
2 boc+>B dup Bx. cr
|
|
||||||
file-base +"-query.boc" tuck B>file
|
|
||||||
."(Saved wallet creating query to file " type .")" cr
|
|
@ -1,54 +0,0 @@
|
|||||||
#!/usr/bin/env -S fift -s
|
|
||||||
"TonUtil.fif" include
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
."usage: " @' $0 type ." command [arguments]" cr
|
|
||||||
cr
|
|
||||||
."Available commands: create-wallet, " cr
|
|
||||||
@' $0 type ." create-wallet
|
|
||||||
"<filename-base> <dest-addr> <seqno> <amount> [-B <body-boc>] [<savefile>]" cr
|
|
||||||
."Creates a request to advanced wallet created by new-wallet-v2.fif, with private key loaded from file <filename-base>.pk "
|
|
||||||
."and address from <filename-base>.addr, and saves it into <savefile>.boc ('wallet-query.boc' by default)" cr 1 halt
|
|
||||||
} : usage
|
|
||||||
def? $6 { @' $5 "-B" $= { @' $6 =: body-boc-file [forget] $6 def? $7 { @' $7 =: $5 [forget] $7 } { [forget] $5 } cond
|
|
||||||
@' $# 2- =: $# } if } if
|
|
||||||
$# dup 4 < swap 5 > or ' usage if
|
|
||||||
|
|
||||||
true constant bounce
|
|
||||||
|
|
||||||
$1 =: file-base
|
|
||||||
$2 bounce parse-load-address =: bounce 2=: dest_addr
|
|
||||||
$3 parse-int =: seqno
|
|
||||||
$4 $>GR =: amount
|
|
||||||
def? $5 { @' $5 } { "wallet-query" } cond constant savefile
|
|
||||||
3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
|
|
||||||
60 constant timeout // external message expires in 60 seconds
|
|
||||||
|
|
||||||
file-base +".addr" load-address
|
|
||||||
2dup 2constant wallet_addr
|
|
||||||
."Source wallet address = " 2dup .addr cr 6 .Addr cr
|
|
||||||
file-base +".pk" load-keypair nip constant wallet_pk
|
|
||||||
|
|
||||||
def? body-boc-file { @' body-boc-file file>B B>boc } { <b 0 32 u, "TESTv2" $, b> } cond
|
|
||||||
constant body-cell
|
|
||||||
|
|
||||||
."Transferring " amount .GR ."to account "
|
|
||||||
dest_addr 2dup bounce 7 + .Addr ." = " .addr
|
|
||||||
."seqno=0x" seqno x. ."bounce=" bounce . cr
|
|
||||||
."Body of transfer message is " body-cell <s csr. cr
|
|
||||||
|
|
||||||
// create a message
|
|
||||||
<b b{01} s, bounce 1 i, b{000100} s, dest_addr addr, amount Gram, 0 9 64 32 + + 1+ u,
|
|
||||||
body-cell <s 2dup s-fits? not rot over 1 i, -rot { drop body-cell ref, } { s, } cond
|
|
||||||
b>
|
|
||||||
<b seqno 32 u, now timeout + 32 u, send-mode 8 u, swap ref, b>
|
|
||||||
dup ."signing message: " <s csr. cr
|
|
||||||
dup hash wallet_pk ed25519_sign_uint
|
|
||||||
<b b{1000100} s, wallet_addr addr, 0 Gram, b{00} s,
|
|
||||||
swap B, swap <s s, b>
|
|
||||||
dup ."resulting external message: " <s csr. cr
|
|
||||||
2 boc+>B dup Bx. cr
|
|
||||||
savefile +".boc" tuck B>file
|
|
||||||
."Query expires in " timeout . ."seconds" cr
|
|
||||||
."(Saved to file " type .")" cr
|
|
Loading…
Reference in New Issue
Block a user