Bitcoin Forum
November 14, 2024, 11:44:34 AM *
News: Latest Bitcoin Core release: 28.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: multisig using Curve25519  (Read 3586 times)
jl777 (OP)
Legendary
*
Offline Offline

Activity: 1176
Merit: 1134


View Profile WWW
October 12, 2014, 03:11:32 AM
 #1

First off I remind you I am just a simple C programmer and not a cryptographer. If anybody has the math background to confirm or deny my experimental finding, please post!

I was experimenting with Curve25519 yesterday and I observed a very useful property that allows the creation of multisig. https://forum.thesupernet.org/index.php?topic=154.msg1262#msg1262

Now I doubt it was just in the few cases that I discovered that works. I bruteforce searched thousands of combinations and found a very simple relationship that I believe can be used for arbitrary M of N signatures.

The fundamental property of Curve25519 is that if A and B know each others public key, they can create a shared secret. Let A and B be the private keys and a and b the public keys:

curve25519(A,b) == curve25519(B,a)

I searched and searched on the Internet to find little actual useful info, so I started with the above and my intuition and searched for combinations that created the same result and there were many, but the simplest and clearest was:

Add C and c to the above and denote S_ab to being the result of curve25519(A,b) or curve25519(B,a) (they are the same)

curve25519(A,S_bc) == curve25519(B,S_ac) == curve25519(C,S_ab)!

This relationship is quite useful and it is probably obvious to anybody familiar with curve25519 so maybe I am just getting excited over nothing, but the recommendation is to immediately hash the output S_ab. Presumably to avoid any low entropy sections of the point and once you do this, the above relationship does not work as you end up totally scrambling the location of the point in the finite field. I call this the rawsharedkey and maybe it has been explored in depth so we can get some math proofs of the above relationship.

We also know that curve25519(A,curve25519(B,curve25519(C,seed))) is equal to all the permutations of order, I think this is because the field forms an abelian group, but it has been a long time since I did any abstract algebra so I probably have the terms wrong. It is just a fancy way of saying the order doesnt matter.

So how to use these math properties to make multisig?

Since S_xy is constant for each set of keypairs for each node, these values can actually be cached locally. Of course this means that the presence of S_xy in the output does not mean that either X or Y actually signed anything. Turns out this is fine, as the final step requires the signing node to actively participate and the final output can be processed till the dogs come home:

sha256(seed ^ curve25519(A,S_bc))
sha256(seed ^ curve25519(B,S_ac))
sha256(seed ^ curve25519(C,S_ab))

all the above produce the identical result and proves that A, B and C all signed it with the seed (which should have a timestamp in it) and since it goes through sha256 the output gives no useful info about A, B or C. I am not sure if S_xy is leaking any info to the nodes that get access to it, but I dont think so as it is the output of curve25519 and that's supposed to be hard to reverse.

Since it is safe to publish the final number, A, B and C publish it and everybody can verify if 0, 1, 2 or 3 signers signed it. The enforcement of following the result is beyond the scope of this thread

Now, how can this be generalized? I feel strongly that the "triangle" relationship can be generalized, but so far my experimental results are not finding the right sequence.

Let me ramble a bit, that sometimes helps Smiley

Starting with the fundamental triangle:
curve25519(A,S_bc) == curve25519(B,S_ac) == curve25519(C,S_ab)

Let us replace C,c with D, d:
curve25519(A,S_bd) == curve25519(B,S_ad) == curve25519(D,S_ab)

The problem is these are different values as it is S_ab combined with C vs D, however, we can use the priv/pub equivalence:

curve25519(d,curve25519(C,S_ab)) ?? curve25519(c,curve25519(D,S_ab))

nope, that didnt work, but as expected:

curve25519(D,curve25519(C,S_ab)) == curve25519(C,curve25519(D,S_ab))
which means:

curve25519(D,curve25519(C,S_ab)) ==  curve25519(C,curve25519(D,S_ab))  == curve25519(B,curve25519(D,S_ac))  == curve25519(A,curve25519(D,S_bc))

Hey! That means there is a 4 signer solution, but this requires multiple signings and then correlations, so not exactly what I am looking for. Anyway I hope to get some math help here so an efficient M of N multisig using curve25519 is possible. Experimentally I found the triangle relationship, which I am not sure if it is trivially obvious or something significant.

James

http://www.digitalcatallaxy.com/report2015.html
100+ page annual report for SuperNET
andytoshi
Full Member
***
Offline Offline

Activity: 179
Merit: 151

-


View Profile
October 13, 2014, 07:24:07 PM
 #2

Hi James,

It seems like are a few misunderstandings here. Let me start with some background. It is easier to explain what's going on if we abstract a bit: let's start by defining a group. A group is simply a set with an operation (called +), some set element 0 such that 0 + x = x + 0 = x for all x in the group, and a unary operation - such that x + (-x) = 0 for all x in the group. So, the integers with addition are a group (as are the reals, rationals, etc), the reals with multiplication are a group, the set of invertible nxn matrices are a group under matrix multiplication, etc.

An important group in cryptography is an elliptic curve group, which is a group whose elements are pairs (x, y) of numbers which satisfy an elliptic curve equation. Addition is defined in a bit of a funny way, but there is a Wikipedia page on it that is interesting.

What's important about these groups for cryptography is the following: given a group element G, you can "multiply" by an integer n by adding G to itself n times. (If n is negative, add -G to itself -n times.) It turns out that if you fix an element G in some group, then take all the elements {nG} for integers n, this set is itself a group such that (nG) + (mG) = (n + m)G. It's a true statement that given any nonzero elements in this group, say P and Q, there is always some number n such that P = nQ. But for elliptic curve group, there is the so-called "discrete logarithm assumption" which says that actually calculating n is very hard. There is also the "Diffie-Hellman" assumption which says that given nG, mG in the group, it is hard to calculate nmG. (Of course, if you know n or m, it's easy to calculate. This is why the "shared secret" scheme you described is efficiently computable by the participants, and the Diffie-Hellman assumption is why it is a secret.)

So...everywhere you say Curve25519(n, G), you really mean nG, where G is some element in the elliptic curve group defined by djb's curve25519 curve. Where you say Curve25519(A, B) where both A and B are elements .... I don't know what you mean. Can you clarify what you are actually computing here? It is possible you are interpreting curvepoints as numbers without realize it, in which case you should use a better programming language that has a type system. (In particular, C is has a very weak type system and djb's code uses char* for everything, which is an asinine thing to do in public code with subtle operation. But possibly it compiles to faster code than it would if he'd used wrapper structs..) It is possible to convert a group element to a number, e.g. by choosing a binary encoding, hashing this, and interpreting the hash as a number.

Now, shared secrets are not really useful for building multisignature schemes. They let you build 1-of-N signatures (every party who has the secret is able to sign) but nothing stronger. The reason is that any party who knows the shared secret is able to use this to sign arbitrary messages. As an example of an actual multisig scheme, I will show how to construct a N-of-N multisig Schnorr signature. The semantics will be that as long as all parties agree to sign a specific message, they are able to form a signature on that message. However, no individual will see enough secret material to form signatures or her own, or even with the cooperation of (fewer than N) other members.

djb's ed25519 signature scheme is based off the standard Schnorr signature, but has some changes to allow efficient batch validation. I don't recall what these changes are (and don't have the paper with me on the bus that I'm typing this from), so I'm not sure if this is directly applicable .... but it is illustrative in any case. So here it is:

Suppose we have a public key P, and let G be a generator of some elliptic curve group, and let H be a hash function. A standard Schnorr signature is a pair (s, e) of numbers which satisfy the following relation: if R = eP + sG then e = H(m || R). It is possible to create such a signature if you know x such that xG = P (so x is the secret key here, and the "discrete logarithm assumption" above is why it is secret even though P is public), by the following mechanism: choose random secret k and compute R = kG; then set e = H(m||R) and s = k - xe. The signature is (s, e). Given some very strong assumptions on the hash function (that it is a opaque mystical source of uniformly random numbers except that it returns the same output when given same input), we can prove that any algorithm which forges Schnorr signatures can be extended to extract the secret key. So given such a hash function, forging Schnorr signatures is as hard as the discrete log problem.

Now, the question is: how can we make a 2-of-2 signature? One idea, as you suggested, is to use a shared secret: users have secret values x and y and form a private key from (a hash of) xyG. The problem with this is as mentioned: both parties knows the whole secret, so they can both form signatures on their own. So this is really a 1-of-2 multisig scheme. To get 2-of-2, we need to be a bit more clever. Here is a protocol:
0. Suppose we have two keypairs (x, xG) and (y, yG) belonging to parties A and B. We will show how to construct a signature with the "2-of-2" key (x + y, (x + y)G).
1. Both parties choose secret random numbers. Call A's secret α and B's secret β. A sends αG to B, and B sends βG to A. Now both parties can compute R = αG + βG = (α + β)G, and they compute e = H(m||R).
2. A computes s' = α - xe and B computes s'' = β - ye, and they send these to each other. Then both compute s = s' + s''.

Now (s, e) is a signature of m on public key (x + y)G, which required both parties' cooperation to form, but neither party gained enough information to produce such a signature alone.


It's a worthwhile exercise to (a) verify that the standard Schnorr signature k - xe actually satisfies the relation (e = H(m||R) where R = sG + eP) that I claimed it did, and (b) so does the 2-of-2 version.


This isn't really an answer to your question, but I hope it sheds some light on things.

Andrew
jl777 (OP)
Legendary
*
Offline Offline

Activity: 1176
Merit: 1134


View Profile WWW
October 14, 2014, 12:29:46 AM
 #3

Hi James,

It seems like are a few misunderstandings here. Let me start with some background. It is easier to explain what's going on if we abstract a bit: let's start by defining a group. A group is simply a set with an operation (called +), some set element 0 such that 0 + x = x + 0 = x for all x in the group, and a unary operation - such that x + (-x) = 0 for all x in the group. So, the integers with addition are a group (as are the reals, rationals, etc), the reals with multiplication are a group, the set of invertible nxn matrices are a group under matrix multiplication, etc.

An important group in cryptography is an elliptic curve group, which is a group whose elements are pairs (x, y) of numbers which satisfy an elliptic curve equation. Addition is defined in a bit of a funny way, but there is a Wikipedia page on it that is interesting.

What's important about these groups for cryptography is the following: given a group element G, you can "multiply" by an integer n by adding G to itself n times. (If n is negative, add -G to itself -n times.) It turns out that if you fix an element G in some group, then take all the elements {nG} for integers n, this set is itself a group such that (nG) + (mG) = (n + m)G. It's a true statement that given any nonzero elements in this group, say P and Q, there is always some number n such that P = nQ. But for elliptic curve group, there is the so-called "discrete logarithm assumption" which says that actually calculating n is very hard. There is also the "Diffie-Hellman" assumption which says that given nG, mG in the group, it is hard to calculate nmG. (Of course, if you know n or m, it's easy to calculate. This is why the "shared secret" scheme you described is efficiently computable by the participants, and the Diffie-Hellman assumption is why it is a secret.)

So...everywhere you say Curve25519(n, G), you really mean nG, where G is some element in the elliptic curve group defined by djb's curve25519 curve. Where you say Curve25519(A, B) where both A and B are elements .... I don't know what you mean. Can you clarify what you are actually computing here? It is possible you are interpreting curvepoints as numbers without realize it, in which case you should use a better programming language that has a type system. (In particular, C is has a very weak type system and djb's code uses char* for everything, which is an asinine thing to do in public code with subtle operation. But possibly it compiles to faster code than it would if he'd used wrapper structs..) It is possible to convert a group element to a number, e.g. by choosing a binary encoding, hashing this, and interpreting the hash as a number.

Now, shared secrets are not really useful for building multisignature schemes. They let you build 1-of-N signatures (every party who has the secret is able to sign) but nothing stronger. The reason is that any party who knows the shared secret is able to use this to sign arbitrary messages. As an example of an actual multisig scheme, I will show how to construct a N-of-N multisig Schnorr signature. The semantics will be that as long as all parties agree to sign a specific message, they are able to form a signature on that message. However, no individual will see enough secret material to form signatures or her own, or even with the cooperation of (fewer than N) other members.

djb's ed25519 signature scheme is based off the standard Schnorr signature, but has some changes to allow efficient batch validation. I don't recall what these changes are (and don't have the paper with me on the bus that I'm typing this from), so I'm not sure if this is directly applicable .... but it is illustrative in any case. So here it is:

Suppose we have a public key P, and let G be a generator of some elliptic curve group, and let H be a hash function. A standard Schnorr signature is a pair (s, e) of numbers which satisfy the following relation: if R = eP + sG then e = H(m || R). It is possible to create such a signature if you know x such that xG = P (so x is the secret key here, and the "discrete logarithm assumption" above is why it is secret even though P is public), by the following mechanism: choose random secret k and compute R = kG; then set e = H(m||R) and s = k - xe. The signature is (s, e). Given some very strong assumptions on the hash function (that it is a opaque mystical source of uniformly random numbers except that it returns the same output when given same input), we can prove that any algorithm which forges Schnorr signatures can be extended to extract the secret key. So given such a hash function, forging Schnorr signatures is as hard as the discrete log problem.

Now, the question is: how can we make a 2-of-2 signature? One idea, as you suggested, is to use a shared secret: users have secret values x and y and form a private key from (a hash of) xyG. The problem with this is as mentioned: both parties knows the whole secret, so they can both form signatures on their own. So this is really a 1-of-2 multisig scheme. To get 2-of-2, we need to be a bit more clever. Here is a protocol:
0. Suppose we have two keypairs (x, xG) and (y, yG) belonging to parties A and B. We will show how to construct a signature with the "2-of-2" key (x + y, (x + y)G).
1. Both parties choose secret random numbers. Call A's secret α and B's secret β. A sends αG to B, and B sends βG to A. Now both parties can compute R = αG + βG = (α + β)G, and they compute e = H(m||R).
2. A computes s' = α - xe and B computes s'' = β - ye, and they send these to each other. Then both compute s = s' + s''.

Now (s, e) is a signature of m on public key (x + y)G, which required both parties' cooperation to form, but neither party gained enough information to produce such a signature alone.


It's a worthwhile exercise to (a) verify that the standard Schnorr signature k - xe actually satisfies the relation (e = H(m||R) where R = sG + eP) that I claimed it did, and (b) so does the 2-of-2 version.


This isn't really an answer to your question, but I hope it sheds some light on things.

Andrew

yes! this makes it a lot clearer, but still struggling to map all these operations to the C code.

I was treating the curve25519 function as a blackbox that I thought was doing a field operation, but it was doing a lot of things with some bytes being polynomials, encoded or not and others being numbers:

Code:
curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) {
  limb bp[5], x[5], z[5], zmone[5];
  uint8_t e[32];
  int i;

  for (i = 0;i < 32;++i) e[i] = secret[i];
  e[0] &= 248;
  e[31] &= 127;
  e[31] |= 64;

  fexpand(bp, basepoint);
  cmult(x, z, e, bp);
  crecip(zmone, z);
  fmul(z, x, zmone);
  fcontract(mypublic, z);
}

It seems curve25519(A,B) makes no sense and it can only be used for nG

So to code your protocol:
Quote
0. Suppose we have two keypairs (x, xG) and (y, yG) belonging to parties A and B. We will show how to construct a signature with the "2-of-2" key (x + y, (x + y)G).
1. Both parties choose secret random numbers. Call A's secret α and B's secret β. A sends αG to B, and B sends βG to A.
I believe G is represented by {9}
(x, xG) and (y, yG) are the standard curve25519(x,G) and curve25519(y,G) keypairs

I think I need to use the cmult function to make αG and βG:

Code:
/* Calculates nQ where Q is the x-coordinate of a point on the curve
 *
 *   resultx/resultz: the x coordinate of the resulting curve point (short form)
 *   n: a little endian, 32-byte number
 *   q: a point of the curve (short form)
 */
static void cmult(limb *resultx, limb *resultz, const u8 *n, const limb *q)

A would generate a random 256 bits -> α and sends cmult(x,z, α,G) to B
B generates his random 256 bits -> β and sends cmult(x,z, β,G) to A

now both A and B can calculate (αG + βG)

Quote
Now both parties can compute R = αG + βG = (α + β)G,
, but I am not sure how exactly to do this, there is a "fmonty" function:
Code:
/* Input: Q, Q', Q-Q'
 * Output: 2Q, Q+Q'
 *
 *   x2 z3: long form
 *   x3 z3: long form
 *   x z: short form, destroyed
 *   xprime zprime: short form, destroyed
 *   qmqp: short form, preserved
 */
static void
fmonty(limb *x2, limb *z2, /* output 2Q */
       limb *x3, limb *z3, /* output Q + Q' */
       limb *x, limb *z,   /* input Q */
       limb *xprime, limb *zprime, /* input Q' */
       const limb *qmqp /* input Q - Q' */)
I think Q can be αG and Q' βG, but not sure how to get the third input (Q-Q') or (αG - βG), looking at the code it looks like it could be simply G {9}?

Quote
and they compute e = H(m||R).
I assume H can be sha256 and both A and B can compute R = αG + βG

e = sha256(m || (αG + βG))

and m is the signature, but I am totally confused by the following:

Quote
2. A computes s' = α - xe and B computes s'' = β - ye, and they send these to each other. Then both compute s = s' + s''.

The signature is two numbers s and e, but m is the signature and it is inside the hash function.
sha256((s,e) || (αG + βG)) ?? have no idea how to do this.

Assuming somehow e can be calculated, then there is the final question of these addition, mult and subtractions:

A (α - xe) =  s'  -> B
B (β - ye) = s'' -> A

and the s = s' + s''

Maybe these are normal numbers and normal arithmetic can be used?

I apologize for probably super simply misunderstanding, but at least now I see a lot better the type of thing that is going on.

James

P.S. Thank you very much for a super informative post!





http://www.digitalcatallaxy.com/report2015.html
100+ page annual report for SuperNET
andytoshi
Full Member
***
Offline Offline

Activity: 179
Merit: 151

-


View Profile
October 14, 2014, 12:32:26 AM
 #4

Hi James,

m is the message to be signed Smiley. The signature is the pair (r, s).

Andrew
jl777 (OP)
Legendary
*
Offline Offline

Activity: 1176
Merit: 1134


View Profile WWW
October 14, 2014, 12:44:30 AM
 #5

Hi James,

m is the message to be signed Smiley. The signature is the pair (r, s).

Andrew

tricky! m for message Smiley

e = sha256(m || (αG + βG))

ok so m can just be sha256(msg) and the "||" presumably can be XOR, so:

e = sha256(sha256(msg) ^ (αG + βG))

then if the following three are just normal 256 bit arithmetic:
(α - xe)
(β - ye)
(s' + s'')

the only missing piece is how to calculate (αG - βG),  it seems it is just one point not two and in the code it was {9} for the keypair calculation, so if it is as simple as just using {9} then I can code up something that tests this

James

http://www.digitalcatallaxy.com/report2015.html
100+ page annual report for SuperNET
andytoshi
Full Member
***
Offline Offline

Activity: 179
Merit: 151

-


View Profile
October 14, 2014, 01:01:49 AM
 #6

Oops! || is concatenation -- you hash the message, then hash R with the same sha2 state. (The existing ed25519 code should do this somewhere..) (I say Oops because I thought to write this and evidently forgot..)

Using xor is not secure.
jl777 (OP)
Legendary
*
Offline Offline

Activity: 1176
Merit: 1134


View Profile WWW
October 14, 2014, 01:08:29 AM
 #7

Oops! || is concatenation -- you hash the message, then hash R with the same sha2 state. (The existing ed25519 code should do this somewhere..) (I say Oops because I thought to write this and evidently forgot..)

Using xor is not secure.
I use libtom:

    sha256_init(&md);
    sha256_process(&md,src,len);
    sha256_done(&md,hash);

so I think I just add a second     sha256_process(&md,src,len) before the sha256_done to concatenate.

    sha256_init(&md);
    sha256_process(&md,msg,msglen);
    sha256_process(&md,&R,256>>3);
    sha256_done(&md,hash);

James

http://www.digitalcatallaxy.com/report2015.html
100+ page annual report for SuperNET
jl777 (OP)
Legendary
*
Offline Offline

Activity: 1176
Merit: 1134


View Profile WWW
October 14, 2014, 08:16:38 AM
Last edit: October 16, 2014, 11:37:35 PM by jl777
 #8

I must have made a mistake somewhere...

Code:
void test3()
{
    static bits256 G,x,y,a,b,xG,yG,e,s;
    uint64_t buf[2][5];
    int i;
    limb bp[5],xbp[5],ybp[5],e_xGx[5],e_xGz[5],e_yGx[5],e_yGz[5],sGx[5],sGz[5],aGx[5],aGz[5],bGx[5],bGz[5];
    limb Q2x[5],Q2z[5],Rx[5],Rz[5],xpoly[5],ypoly[5],epoly[5],apoly[5],bpoly[5],xe[5],ye[5],spoly[5],xsumx[5],xsumz[5],ysumx[5],ysumz[5];
    G.bytes[0] = 9, fexpand(bp,G.bytes);
    
    // A
    randombytes(x.bytes,sizeof(x)), x = mask_key(x), fexpand(xpoly,x.bytes);
    randombytes(a.bytes,sizeof(a)), a = mask_key(a), fexpand(apoly,a.bytes);
    xG = curve25519(x,G);
    cmult(aGx,aGz,a.bytes,bp);

    // B
    randombytes(y.bytes,sizeof(y)), y = mask_key(y), fexpand(ypoly,y.bytes);
    randombytes(b.bytes,sizeof(b)), b = mask_key(b), fexpand(bpoly,b.bytes);
    yG = curve25519(y,G);
    cmult(bGx,bGz,b.bytes,bp);
    
    // Both
    fmonty(Q2x,Q2z,Rx,Rz,aGx,aGz,bGx,bGz,bp); // A and B exchange (aGx,aGz) and (bGx,bGz) so both can compute e
    memcpy(buf[0],Rx,sizeof(buf[0]));
    memcpy(buf[1],Rz,sizeof(buf[1]));
    char *src = "hello world";
    calc_sha256cat(e.bytes,(unsigned char *)src,(int32_t)strlen(src),(unsigned char *)buf,sizeof(buf));
    
    // A
    fmul(xe,xpoly,epoly); // xe = x * e
    fdifference_backwards(xe,apoly); // (α - xe) = s'
    
    // B
    fmul(ye,ypoly,epoly); // ye = y * e
    fdifference_backwards(ye,bpoly); // (β - ye) = s''
    
    // finally A and B share s' and s''
    for (i=0; i<5; i++) // s = s' + s''
        spoly[i] = (xe[i] + ye[i]);
    disp_limb("spoly",spoly);
    fcontract(s.bytes,spoly), disp_bits256(" s\n",s);

    cmult(sGx,sGz,s.bytes,bp);  // sG
    disp_xz("sG xz\n",sGx,sGz);
 
    // R = eP + sG -> (e*xG + s*G) should equal (e*yG + s*G)
    fexpand(xbp,xG.bytes);
    cmult(e_xGx,e_xGz,e.bytes,xbp); // eP for x
    fmonty(Q2x,Q2z,xsumx,xsumz,e_xGx,e_xGz,sGx,sGz,bp); // (eP + sG)
    disp_xz("xsum xz\n",xsumx,xsumz);

    fexpand(ybp,yG.bytes);
    cmult(e_yGx,e_yGz,e.bytes,ybp); // eP for y
    fmonty(Q2x,Q2z,ysumx,ysumz,e_yGx,e_yGz,sGx,sGz,bp); // (eP + sG)
    disp_xz("ysum xz\n",ysumx,ysumz);
}

I noticed in polynomial form it can vary, but when compressed to 256 bit number it is always the same, so I display both the x and z vals.


  8e0539c79e4c30   8926b81faef662   811fdcd5840e1d   86c88ac5c0c565   829f821a0b73fa  spoly
604d9ec739059eb377fdc035898b036135f747ea8a818b1591ad40b7a021f829  s

2c7332292b79212c31ae926c6cf62ea339c80452761a7d3f53eced415368c430   sGx
2dfd7e0f309e76ec163b674dfffdf8b4b07156abbcc8d282456f1c7494838637 sGz

4d8a4ccda4b4c3959c7040478fe6ac3830aafc28d6432da97d4c834224888412   xsumx (eP + sG)
28768f66cef5f334af3746d27b427f52d5b91f1e1338b73af6e625b870f5d275 xsumz

908c0ac5a707594261b3df6fc64896033f61166afd6b98b7f0e32f615a514259   ysumx (eP + sG)
9422a680f5a74e05d561458d8ad25945ab64b5fc73aeb8b070e166a2eb34b93e ysumz

Not 100% sure that the (eP + sG) checks are even right, and maybe s or e are messed up. Without anyway to have known values part way, it seems it cant have any bugs through the entire process. Hopefully, I am at least close.

James

http://www.digitalcatallaxy.com/report2015.html
100+ page annual report for SuperNET
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!