1
0
mirror of https://github.com/danog/toncontest.git synced 2024-11-29 20:19:11 +01:00
This commit is contained in:
Daniil Gentili 2019-10-11 13:31:43 +02:00
parent 78626adacc
commit 5fe76ed22b
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
8 changed files with 0 additions and 1261 deletions

View File

@ -1,7 +0,0 @@
// Minimum signatures to send order
3 : k
// Maximum signatures per order
10 : n

View File

@ -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

View File

@ -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) {
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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